Введение
Системные вызовы выступают в роли посредников между приложениями и ядром. Они создают уровень абстракции, который защищает важные компоненты системы, такие как ЦП и память, от случайных ошибок и целенаправленных атак.
В этой статье мы рассмотрим, как работают различные системные вызовы, разберемся в их функциях, а также постараемся сделать работу вашей системы более безопасной и предсказуемой.
Что такое системный вызов?
Системный вызов – это интерфейс между программой, которая работает в пользовательском пространстве, и операционной системой (ОС). Прикладные программы используют системные вызовы для того, чтобы запрашивать действия и функции у ядра ОС. Этот механизм позволяет программе вызывать действия, например, чтение из файла, не обращаясь к системным ресурсам напрямую.
Когда программа обращается к системному вызову, контекст выполнения переключается с пользовательского режима в режим ядра, позволяя системе получить доступ к аппаратному обеспечению и безопасно выполнить необходимые операции. После того, как операции завершены, управление возвращает в пользовательский режим, а программа продолжает свое выполнение.
Такой многоуровневый подход, осуществляемый при помощи системных вызовов:
- Гарантирует изоляцию аппаратных ресурсов от процессов пользовательского пространства
- Не допускает прямого доступа к ядру или аппаратной памяти
- Позволяет коду приложения работать на различных аппаратных архитектурах
Какова задача системного вызова?
Системные вызовы выполняют несколько довольно важных функций:
- Граница между пользователем и ядром. При запросе действия у ядра системные вызовы выступают в качестве авторизованного шлюза для пользовательских программ. Они гарантируют, что последние не смогут получить доступ к функциям ядра или критически важным системным ресурсам без достаточных оснований.
- Управление ресурсами. С помощью системных вызовов пользовательские программы могут запрашивать важными ресурсами и управлять ими, например, временем ЦП, памятью и хранилищем файлов. ОС контролирует этот процесс и гарантирует, что он выполняется по всем правилам.
- Более простая разработка. Системные вызовы позволяют абстрагироваться от сложностей аппаратного обеспечения. Таким образом, разработчики могут выполнять такие операции, как чтение и запись в файл или управление данными сети, и не писать отдельный код для аппаратного обеспечения.
- Безопасность и контроль доступа. Системные вызовы выполняют проверки для того, чтобы гарантировать, что запросы, отправленные пользовательскими программами, действительны и что программы имеют необходимые права доступа для выполнения запрашиваемых операций.
- Межпроцессное взаимодействие (IPC - Inter-Process Communication). Системные вызовы предоставляют механизмы, с помощью которых процессы могут взаимодействовать друг с другом. Они предлагают такие средства, как каналы, очереди сообщений и общую память, для упрощения межпроцессного взаимодействия.
- Сетевые операции. Системные вызовы предоставляют фреймворк для того, чтобы программы могли взаимодействовать по сети. Разработчики могут полностью сконцентрироваться на построении логики своего приложения вместо того, чтобы уделять внимание низкоуровневому сетевому программированию.
Как работают системные вызовы?
Здесь кратко описано как работают системные вызовы:
1. Запрос системного вызова. Приложение запрашивает системный вызов, используя для этого соответствующую функцию. Например, для того, чтобы прочитать данные из файла, программа может использовать функцию read().
2. Переключение контекста в пространство ядра. Для инициирования переключения контекста и перехода из пользовательского режима в режим ядра используются программное прерывание и специальные инструкции.
3. Идентификация системного вызова. Для идентификации системного вызова и адреса соответствующей функции ядра система использует индекс.
4. Выполнение функции ядра. Выполняется функция ядра, соответствующая системному вызову, например, чтение данных из файла.
5. Подготовка возвращаемых значений. После того, как функция ядра завершает свою работу, все возвращаемые значения или результаты подготавливаются для отправки в пользовательское приложение.
6. Переключение контекста в пользовательское пространство. Контекст выполнения переключается обратно из режима ядра в пользовательский режим.
7. Возобновление работы приложения. Приложение возобновляет свою работу с момента своей остановки, но теперь уже с результатами системного вызова.
Примечание: точное количество шагов и принцип работы системных вызовов могут различаться в зависимости от операционной системы.
Каковы функции системных вызовов?
Ниже представлены функции, характеризующие системные вызовы:
- Безопасность. Системные вызовы гарантируют, что приложения пользовательского пространства никак не смогут навредить системе или помешать другим процессам.
- Абстракция. Программам, например, не нужно знать особенности конфигурации сетевого оборудования для того, чтобы отправлять данные через Интернет или выполнять операции с диском для чтения файлов, так как эти задачи выполняет операционная система.
- Контроль доступа. Системные вызовы обеспечивают соблюдение всех необходимых мер безопасности, проверяя, имеет ли программа соответствующие права доступа для обращения к ресурсам.
- Согласованность. Взаимодействие между ОС и программой остается согласованным, независимо от базовой конфигурации аппаратного обеспечения. Одна и та же программа может работать на разном оборудовании, главное, чтобы ее поддерживала операционная система.
- Синхронное выполнение. Многие системные вызовы выполняются синхронно, блокируя вызывающий процесс до завершения операции. Однако существуют и асинхронные системные вызовы, которые позволяют процессам продолжать выполнение, не ожидая завершения операции.
- Управление процессами. Системные вызовы упрощают управление процессами и выполнение нескольких задач одновременно за счет механизмов создания, завершения, планирования и синхронизации процессов.
- Управление файлами. Системные вызовы поддерживают операции с файлами, такие как такие как чтение, запись, открытие и закрытие файлов.
- Управление устройствами. С помощью системных вызовов процессы могут запрашивать доступ к устройствам, выполнять операции чтения/записи на этих устройствах и освобождать их.
- Управление ресурсами. Системные вызовы помогают выделять и освобождать ресурсы, такие как память, время ЦП и устройства ввода-вывода.
- Текущее обслуживание. Системные вызовы можно использовать для получения или настройки системной информации, такой как дата и время или состояние процесса.
- Взаимодействие. Системные вызовы позволяют процессам взаимодействовать друг с другом и синхронизировать свои действия.
- Обработка ошибок. Если системный вызов не может быть завершен, он возвращает код ошибки, который может обработать вызывающая программа.
Типы системных вызовов
Следующий перечень представляет собой классификацию системных вызовов на основании их функций:
1. Управление процессами
Системные вызовы играют важную роль в управлении системными процессами. С их помощью можно:
- Создавать новые процессы и завершать текущие
- Загружать и выполнять программы в пространстве процесса
- Планировать процессы и устанавливать параметры выполнения, например, приоритет
- Дожидаться завершения процесса или подавать сигнал о его завершении
2. Управление файлами
Системные вызовы могут выполнять различные операции с файлами, например,
- Чтение файлов и запись в файлы
- Открытие и закрытие файлов
- Удаление и изменение файловых атрибутов
- Перемещение или переименование файлов
3. Управление устройствами
Системные вызовы можно использовать для помощи в управлении устройствами, а именно для:
- Запроса доступа к устройству и его освобождения после использования
- Установка атрибутов и параметров устройства
- Чтение с устройств или запись на устройства
- Сопоставление имен логических устройств с физическими
4. Информационное обеспечение
Эти системные вызовы позволяют процессам:
- Получать и изменять различные системные атрибуты
- Устанавливать системную дату и время
- Запрашивать показатели производительности системы
5. Взаимодействие
Эти системные вызовы упрощают:
- Отправку и получение сообщение между процессами
- Синхронизацию действий между пользовательскими процессами
- Организации областей разделяемой памяти для межпроцессного взаимодействия
- Работу в сети через сокеты
6. Безопасность и управление доступом
Системные вызовы вносят свой вклад в обеспечение безопасности и управление доступом за счет:
- Определения того, какие процессы или пользователи получают доступ к тем или иным ресурсам, и кто может читать, записывать и выполнять ресурсы
- Упрощения процедур аутентификации пользователей
Примеры системных вызовов
Ниже в таблице перечислены самые распространенные системные вызовы Unix и Windows и их описания.
Примечание: поведение системного вызова, его параметры и возвращаемые значения могут отличаться в зависимости от операционной системы и ее версии. Более подробную информацию можно найти в документации к операционной системе.
СИСТЕМНЫЙ ВЫЗОВЫ UNIX |
ОПИСАНИЕ |
СИСТЕМНЫЕ ВЫЗОВЫ WINDOWS |
ОПИСАНИЕ |
Управление процессами | |||
fork() |
Создает новый процесс |
CreateProcess() |
Создает новый процесс |
exit() |
Завершает текущий процесс |
ExitProcess() |
Завершает текущий процесс |
wait() |
Переводит процесс в режим ожидания до тех пор, пока не завершатся его дочерние процессы |
WaitForSingleObject() |
Ждет, пока процесс или поток завершит свою работу |
exec() |
Выполняет новую программу в процессе |
CreateProcess() или |
Выполняет новую программу в процессе |
getpid() |
Получает уникальный идентификатор процесса |
GetCurrentProcessId() |
Получает уникальный идентификатор процесса |
Управление файлами | |||
open() |
Открывает файл (или устройство) |
open() |
Открывает или создает файл или устройство |
close() |
Закрывает открытый файл (или устройство) |
close() |
Закрывает дескриптор открытого объекта |
read() |
Выполняет чтение из файла (или устройства) |
read() |
Выполняет чтение данных из файла или устройство ввода |
write() |
Выполняет запись в файл (или устройство) |
write() |
Выполняет запись данных в файл или устройство вывода |
lseek() |
Изменяет место выполнения чтения/записи в файле |
lseek() |
Устанавливает положение указателя позиции в файле |
unlink() |
Удаляет файл |
unlink() |
Удаляет существующий файл |
rename() |
Переименовывает файл |
rename() |
Перемещает или переименовывает файл |
Управление каталогами | |||
mkdir() |
Создает новый каталог |
CreateDirectory() |
Создает новый каталог |
rmdir() |
Удаляет каталог |
RemoveDirectory() |
Удаляет существующий каталог |
chdir() |
Изменяет текущий каталог |
SetCurrentDirectory() |
Изменяет текущий каталог |
stat() |
Получает статус файла |
GetFileAttributesEx() |
Получает расширенные атрибуты файла |
fstat() |
Получает статус открытого файла |
GetFileInformationByHandle() |
Получает информацию о файле, используя его дескриптор |
link() |
Создает ссылку на файл |
CreateHardLink() |
Создает жесткую ссылку на существующий файл |
symlink() |
Получает статус открытого файла |
CreateSymbolicLink() |
Создает символическую ссылку |
Управление устройствами | |||
brk()или sbrk() |
Увеличивает/уменьшает пространство данных программы |
VirtualAlloc()или |
Резервирует, фиксирует изменения или освобождает область памяти |
mmap() |
Проецируют файлы или устройства в память |
MapViewOfFile() |
Проецирует файл в адресное пространство приложения |
Информационное обеспечение | |||
time() |
Получает текущее время |
GetSystemTime() |
Получает текущее системное время |
alarm() |
Получает статус открытого файла |
SetWaitableTimer() |
Устанавливает таймер |
getuid() |
Устанавливает будильник для подачи сигнала |
GetUserName()или |
Получает имя пользователям или его ID |
getgid() |
Получает идентификатор группы |
GetTokenInformation() |
Получает информацию о маркере доступа |
Взаимодействие | |||
socket() |
Создает новый сокет |
socket() |
Создает новый сокет |
bind() |
Привязывает сокет к сетевому адресу |
bind() |
Привязывает сокет к сетевому адресу |
listen() |
Привязывает сокет к сетевому адресу |
listen() |
Отслеживает соединения в сокете |
accept() |
Принимает новое соединение в сокете |
accept() |
Принимает новое соединение в сокете |
connect() |
Инициализирует соединение в сокете |
connect() |
Инициализирует соединение в сокете |
send()или recv() |
Отправляет и получает данные через сокет |
send()или recv() |
Отправляет и получает данные через сокет |
Безопасность и управление доступом | |||
chmod()или umask() |
Изменяет права доступа/режим файла |
SetFileAttributes()или SetSecurityInfo() |
Изменяет атрибуты файла или сведения о защите |
chown() |
Изменяет владельца или группу файла |
SetSecurityInfo() |
Устанавливает сведения о защите |
Как передавать параметры системным вызовам?
Когда пользовательская программа обращается к системному вызову, ей, как правило, для того, чтобы указать запрос, нужно передать ему дополнительные параметры. То, насколько грамотно передаются эти параметры между пользователем и ядром, влияет на производительность системы.
Способ передачи параметров зависит от системной архитектуры, но есть некоторые общие правила:
- Ограниченное количество параметров. Чаще всего системные вызовы принимают ограниченное количество параметров. Это правило нужно для того, чтобы упростить взаимодействие и принудить пользователей использовать структуры данных или блоки памяти.
- Использование регистров ЦП. Регистры ЦП – это области памяти, к которым можно быстрее всего получить доступ. Количество регистров ЦП ограничено, а это значит, что количество передаваемых вызову параметров также ограничено. Если вы передаете небольшое количество параметров, используйте регистры ЦП.
- Использование указателей для агрегирования данных. Вместо того, чтобы передавать огромное количество параметров или большие объемы данных, используйте переменные-указатели, чтобы указывать на блоки или структуры памяти (которые содержат все параметры). Ядро будет использовать этот указатель для того, чтобы получить доступ к этому блоку памяти и извлечь параметры.
- Проверка целостности и безопасности данных. Ядро должно проверять любые указатели, которые передаются из пользовательского пространства. Оно должно проверить, что эти указатели относятся только к тем областям, к которым имеет доступ пользовательская программа. Кроме того, прежде чем использовать данные, которые поступают из пользовательских программ, оно их перепроверяет.
- Обработка стековых параметров. Некоторые системы помещают параметры в стек, и для того, чтобы выполнить их обработку, ядру приходится их извлекать. Такой метод не так распространен, как использование регистров ЦП и указателей, поскольку он более сложен с точки зрения реализации и управления.
- Изоляция данных путем их копирования. Ядро часто копирует данные из пространства пользователя в пространство ядра (и наоборот). Оно это делает для того, чтобы защитить систему от ошибочных или вредоносных данных. В связи с этим, данные, которые передаются между этими двумя пространствами, не должны передаваться напрямую.
- Возвращаемые значения и обработка ошибок. Системный вызов возвращает значение – как правило, это код успешного завершения или ошибки. В случае получения кода ошибки, всегда пытайтесь найти больше информации об этой ошибке. Сообщения об ошибках чаще всего хранятся в определенных местах, например, в переменной errno (Linux).
Примечание: правила и методы, приведенные выше, могут различаться в зависимости от архитектуры (x86, ARM, MIPS и т.д.) и особенностей операционной системы. Всегда обращайтесь к документации операционной системы или исходному коду, чтобы получить более точную информацию.
Заключение
В этой статье мы рассмотрели, как работают системные вызовы и почему они столь важны для обеспечения бесперебойной работы, безопасности и управления аппаратными и программными ресурсами.