Для обеспечения безопасности процессы (приложения) должны работать в режиме пользователя. Однако, часто для выполнения различных действий может потребоваться режим ядра (например, все действия, связанные с управлением аппаратными средствами), но этот режим монопольно используется только самой ОС. Выполнение привилегированных операций перекладывается с плеч приложения на плечи ОС. Обеспечение такого механизма реализуется путем использования интерфейса прикладного программирования (Application Programming Interface, API). Приложения выполняют обращение к функциям API с помощью системных вызовов (system call).
При генерации системных вызовов управление передается ОС, которая либо сама выполняет действия, либо формирует управляющее воздействие на оборудование (device control). В последнем случае оборудование выполняет операцию и сигнализирует об ошибке или нормальном завершении операции прерыванием (interrupt). Обработав прерывание, ОС формирует отклик (response) процессу – результат системного вызова (рис. 2).
Набор функций для обеспечения реализации системных вызовов, отклики на них, а также соглашения о порядке их использования составляет интерфейс процессов. Интерфейс оборудования – это набор управляющих воздействий на устройства и организация системы прерываний для работы с ними.
Рис. 2. Общая схема работы приложений с оборудованием.
Таким образом, доступ к ресурсам осуществляется только посредством ОС. Это гарантирует, что процессы пользователя не смогут испортить или монопольно захватить ресурсы системы, но при этом на ОС должны возлагаться повышенные требования.
СИСТЕМНЫЕ ВЫЗОВЫ
Системные вызовы обеспечивают интерфейс между запущенным на выполнение процессом и ОС.
Частота генерации системных вызовов является весьма высокой. Например, в ОС Windows 2000 при активной работе происходит в среднем 2200 системных вызовов в секунду, при простое системы – 90 вызовов в секунду. Любая программа, находящаяся в стадии выполнения производит десятки вызовов. Рассмотрим следующий пример. Проследим выполнение следующей простейшей программы hello.c:
main() {
printf(“Hello\n”);
}
Для отслеживания системных вызовов в ОС Linux используется команда strace. Выполним ее со следующими параметрами:
strace –с hello
где –c – необязательный параметр, указывающий, что необходимо выдать краткую статистику использования системных вызовов, hello – имя исполняемого модуля. В результате оказывается, что работа этой программы требует выполнения 27 системных вызовов.
Для прикладного программиста ОС представляется как библиотека функций (API), с помощью которых можно упростить программу или выполнить запрещённые в режиме пользователя действия (ввод/вывод и т.д.).
Реализация системных вызовов должна:
– обеспечивать переключение в режим ядра;
– обладать высокой скоростью вызовов процедур ОС;
– обеспечивать единообразное обращение к системным вызовам для всех аппаратных платформ, на которых работает ОС;
– обладать свойством расширяемости;
– обеспечивать контроль со стороны ОС за корректностью использованных системных вызовов.
Переключение в режим ядра для большинства аппаратных платформ выполняется с помощью механизма прерываний.
Максимальная скорость вызова процедур ОС может быть достигнута при закреплении системных вызовов за прерываниями (один системный вызов – одно прерывание), но это напрямую сделать нельзя. Например, архитектура Intel поддерживает всего 256 векторов прерываний – явно недостаточно. Так, если в ОС Caldera OpenLinux зарегистрировано 167 системных вызовов (их перечень можно найти в файле /usr/include/sys/syscall.h), то в ОС FreeBSD Unix 4.6 364 системных вызова, а в ОС Windows 2000 их гораздо больше – тысячи (перечень можно получить из файлов Ntdll.dll, Gdi32.dll, User32.dll).
В большинстве современных ОС системные вызовы обслуживаются по такой схеме (рис. а9): любой системный вызов генерирует только одно прерывание (переход 1). Происходит переход в режим ядра. В таблице векторов прерываний (ТВП) для этого номера прерывания находится адрес диспетчера системных вызовов. Это прерывание обслуживает диспетчер системных вызовов (переход 2), который определяет по таблице диспетчеризации системных вызовов, какую именно процедуру ОС необходимо выполнить (переход 3) и проверяется наличие минимально необходимого количества параметров. Далее диспетчер передает управление найденной процедуре (переход 4), которая и реализует нужные действия. После завершения работы процедуры управление передается диспетчеру (переход 5), который производит обработку результатов и обеспечивает их представление для передачи процессу, который обратился к системному вызову. Последним действием является переключение в режим пользователя и возврат в вызывающий процесс (переход 6).
Рис. а9. Реализация обработки системных вызовов.
Для обеспечения передачи параметров диспетчеру системных вызовов и процедурам ОС могут использоваться различные механизмы:
– передача параметров через регистры (самый быстрый механизм, но сильное ограничение по количеству передаваемой информации);
– передача параметров через область памяти, адрес которой заносится в регистр (позволяет передать максимально возможное количество параметров);
– передача параметров через стек процесса (не задействуем дополнительный регистр, нет проблем с памятью, так как параметры будут сразу помещены в доступную для процесса память).
ОС может выполнять системные вызовы в синхронном и асинхронном режимах. При синхронном режиме процесс, выдавший системный вызов, блокируется до выполнения этого вызова. При асинхронном вызове работа процесса продолжается.