При осуществлении системного вызова или вызова библиотечной функции обязательно нужно проверять код возврата, чтобы определить, насколько успешно прошел вызов. Поэтому в главе описан порядок проведения таких проверок и представлен набор функций, которые используются в большинстве приводимых здесь примеров программ для диагностики ошибок, возвращаемых системными вызовами и библиотечными функциями.
В завершение будут рассмотрены различные вопросы, относящиеся к программированию портируемых программных средств, в частности использование макросов проверки возможностей и определенных в SUSv3 стандартных типов системных данных.
Перед тем как перейти к подробностям работы системных вызовов, следует упомянуть о некоторых их общих характеристиках.
• Системный вызов изменяет состояние процессора, переводя его из пользовательского режима в режим ядра, позволяя таким образом центральному процессору получать доступ к защищенной памяти ядра.
• Набор системных вызовов не изменяется. Каждый системный вызов идентифицируется по уникальному номеру. (Обычно программам эта система нумерации неизвестна, они идентифицируют системные вызовы по именам.)
• У каждого системного вызова может быть набор аргументов, определяющих информацию, которая должна быть передана из пользовательского пространства (то есть из виртуального адресного пространства процесса) в пространство ядра и наоборот.
С точки зрения программирования, инициирование системного вызова во многом похоже на вызов функции языка C. Но при выполнении системного вызова многое происходит закулисно. Чтобы пояснить, рассмотрим все последовательные этапы происходящего на конкретной аппаратной реализации — x86-32.
1. Прикладная программа осуществляет системный вызов, вызвав функцию-оболочку из библиотеки C.
2. Функция-оболочка должна обеспечить доступность всех аргументов системного вызова подпрограмме его перехвата и обработки (которая вскоре будет рассмотрена). Эти аргументы передаются функции-оболочке через стек, но ядро ожидает их появления в конкретных регистрах центрального процессора. Функция-оболочка копирует аргументы в эти регистры.
3. Поскольку вход в ядро всеми системными вызовами осуществляется одинаково, ядру нужен какой-нибудь метод идентификации системного вызова. Для обеспечения такой возможности функция-оболочка копирует номер системного вызова в конкретный регистр (%eax).
4. В функции-оболочке выполняется машинный код
В более современных архитектурах x86-32 реализуется инструкция sysenter, предоставляющая более быстрый способ входа в режим ядра, по сравнению с обычной инструкцией системного прерывания int 0x80. Использование sysenter поддерживается в версии ядра 2.6 и в glibc, начиная с версии 2.3.2.
5. В ответ на системное прерывание 0x80 ядро для его обработки инициирует свою подпрограмму system_call() (которая находится в ассемблерном файле arch/x86/kernel/entry.S). Обработчик прерывания делает следующее;
1) сохраняет значения регистров в стеке ядра (см. раздел 6.5);
2) проверяет допустимость номера системного вызова;
3) вызывает соответствующую подпрограмму обслуживания системного вызова. Ее поиск ведется по номеру системного вызова: в таблице всех подпрограмм обслуживания системных вызовов в качестве индекса используется номер системного вызова (переменная ядра sys_call_table). Если у подпрограммы обслуживания системного вызова имеются аргументы, то она сначала проверяет их допустимость. Например, она проверяет, что адреса указывают на места, допустимые в пользовательской памяти. Затем подпрограмма обслуживания системного вызова выполняет требуемую задачу, которая может предполагать изменение значений адресов, указанных в переданных аргументах, и перемещение данных между пользовательской памятью и памятью ядра (например, в операциях ввода-вывода). И наконец, подпрограмма обслуживания системного вызова возвращает подпрограмме system_call() код возврата;
4) восстанавливает значения регистров из стека ядра и помещает в стек возвращаемое значение системного вызова;
5) возвращает управление функции-оболочке, одновременно переводя процессор в пользовательский режим.