Главная » Ядро Linux » Контекст системного  вызова

0

Как уже обсуждалось в главе 3, "Управление процессами",  при выполнении  системного  вызова ядро работает в контексте  процесса.  Указатель curren t указывает на текущее задание, которое и есть процессом,  выполняющим системный вызов.

В контексте  процесса ядро может переходит в приостановленное состояние  (например,  если системный вызов блокируется при вызове функции или явно вызывает функцию  schedule ()), а также является полностью вытесняемым.  Эти два момента важны.  Возможность  переходить в приостановленное состояние  означает,  что системный  вызов может использовать большую часть функциональных возможностей ядра. Как будет видно из главы 6, "Прерывания  и обработка прерываний",  наличие возможности  переходить в приостановленное состояние значительно упрощает программирование  ядра7. Тот факт, что контекст процесса является  вытесняемым,  подразумевает,  что, как и в пространстве  пользователя,  текущее задание может быть вытеснено другим заданием. Так как новое задание может выполнить тот же системный  вызов,  необходимо убедиться, что системные  вызовы являются реентерабельными. Это очень похоже на требования,  выдвигаемые для симметричной  мультипроцессорной  обработки.  Способы  защиты,  которые обеспечивают реентерабельность, описаны  в главе 8, "Введение в синхронизацию выполнения  кода ядра", и в главе 9, "Средства синхронизации в ядре".

После завершение системного вызова управление передается обратно в функцию system_cal l (), которая в конце  концов  производит переключение  в пространство пользователя,  и далее выполнение  пользовательского  процесса продолжается.

Окончательные шаги регистрации системного вызова

После того как системный  вызов написан,  процедура его регистрации в качестве официального  системного вызова тривиальна и состоит в следующем.

• Добавляется запись в конец таблицы системных вызовов. Это необходимо сделать для всех аппаратных  платформ,  которые поддерживают этот системный вызов (для большинства системных вызовов — это все возможные платформы). Положение  системного вызова в таблице — это номер системного вызова, начиная с нуля. Например,  десятая запись таблицы соответствует системному вызову с номером девять.

• Для всех поддерживаемых  аппаратных платформ  номер  системной  функции должен быть определен в файле  include/linux/unistd.h .

• Системный  вызов должен быть вкомпилирован в образ ядра (в противоположность компиляции в качестве загружаемого модуля8). Это просто соответствует размещению  кода в каком-нибудь  важном файле каталога kernel/ .

7 Обработчики      прерывани й  не  могут  переходить  в  приостановленное  состояние  и,  следовательно, более  ограничен ы  в  своих  действиях  по  сравнению   с системными   вызовами,   которые  работают  в контексте   процесса.

8   Регистрации   новых  постоянны х системных  вызовов  в ядр е  требует  компиляци и  системного  вызова  в  обра з  ядра.  Тем  не  менее  есть принципиальна я  возможность  с  помощью  динамически   загружаемого  модуля  ядр а  перехватить  существующие  системные   вызовы  и  даже,   цено й  некоторых усилий, динамически зарегистрировать новые. — Примеч. персе.

Давайте более детально рассмотрим эти шаги на примере функции системного вызова,  fоо () . Вначале функция  sys_fоо ()  должна быть добавлена в  таблицу системных вызовов. Для большинства аппаратных платформ таблица системных вызовов размещается в файле entry. S  и выглядит примерно следующим образом.

ENTRY(sys_call_table)

.long sys_restart_syscall   /* 0 */

.long sys_exit

.long sys_fork

.long sys_read

.long sys_write

.long sys_open    /*    5    */

.long sys_timer_delete

.long sys_clock_settime

.long sys_clock_gettime     /* 280 */

.long sys_clock_getres

.long sys_clock_nanosleep

Необходимо добавить новый системный вызов в конец этого списка:

.long sys_foo

Нашему системному вызову будет назначен следующий свободный номер, 283, котя мы этого явно и не указывали. Для каждой аппаратной платформы, которую мы будем поддерживать, системный вызов должен быть добавлен в  таблицу системных вызовов соответствующей аппаратной платформы (нет необходимости получать номер системного вызова для каждой платформы). Обычно необходимо сделать системный вызов доступным для всех аппаратных платформ. Следует обратить внимание на договоренность указывать комментарии с номером системного вызова через каждые пять записей, что позволяет быстро найти, какой номер какому системному вызову соответствует.

Далее необходимо добавить номер  системного вызова в  заголовочный файл include/asm/unistd.h , который сейчас выглядит примерно так.

/*

* This file contains the system call numbers.

*/

#define   NR_restart_syscall    0

#define   NR_exit 1

#define   NR_fork 2

#define   NR_read 3

#define   HR_write 4

#define    NR_open 5

#define   NR_mq_unlink          278

#define   NR_mq_timedsend       279

#define    NR_mq_timedreceive    280

#define   NR_mq_notify          281

#define    NR_mq_getsetattr      282

В конец файла  добавляется следующая строка.

#define   NR_foo 283

В конце концов необходимо реализовать сам  системный вызов  fоо () . Так  как  системный вызов  должен   быть  вкомпилорован в  образ  ядра  во  всех  конфигурациях, мы  его поместим в файл   kernel/sys.с . Код  необходимо размещать в наиболее подходящем файле. Например,  если  функция относится к  планированию выполнения процессов, то ее  необходимо помещать в файл  sched.с .

/*

* sys_foo всеми любимый системный вызов.

*

* Возвращает размер стека ядра процесса

*/

asmlinkage long sys_foo(void)

{

return THREAD_SIZE;

}

Это  все!  Загрузите новое  ядро.  Теперь  из  пространства пользователя можно вызвать  системную функцию  foo () .

Доступ к системным вызовам из пространства пользователя

В  большинстве случаев  системные  вызовы поддерживаются библиотекой  функций  языка С.  Пользовательские приложения могут  получать прототипы  функций из  стандартных заголовочных файлов  и  компоновать программы с  библиотекой С для  использования вашего  системного вызова  (или  библиотечной функции, которая вызывает ваш  системный вызов). Однако если  вы  только   что  написали системный вызов, то  маловероятно, что  библиотека glib c  уже  его  поддерживает!

К  счастью,  ОС  Linux  предоставляет набор   макросов-оболочек для  доступа  к  системным вызовам. Они  позволяют установить содержимое регистров и  выполнить машинную инструкцию  in t   50×80.  Эти  макросы имеют  имя  syscall n () , где  п — число  от  нуля  до  шести.   Это  число  соответствует числу  параметров,  которые должны  передаваться в системный вызов, так как  макросу необходима информация  о том, сколько ожидается параметров, и соответственно, нужно  записать эти  параметры  в регистры процессора.  Например,  рассмотрим системный вызов  open  (), который определен следующим образом.

long open(const char "filename, int flags, int model

Макрос для  вызова  этой  системной функции будет выглядеть так.

#define NR_open 5

_syscall3(long, NR_open, const char *, filename, int, flags, int, mode)

После  этого  приложение может  просто  вызывать функцию open  () .

Каждый макрос принимает 2 + 2*n параметров. Первый параметр соответствует типу  возвращаемого значения  системного вызова. Второй  параметр — имя  системного  вызова.  После   этого  следуют  тип  и  имя  каждого   параметра в том  же  поряд-

ке, что  и у системного вызова. Постоянная NR_open, которая определена в файле

<asm/unistd.h>, — это номер системного вызова. В функцию на языке программирования С  такой вызов превращается с помощью вставок на языке ассемблера, которые выполняют рассмотренные в предыдущем разделе шаги. Значения аргументов помещаются в соответствующие регистры, и выполняется программное прерывание, которое перехватывается в режиме ядра. Вставка данного макроса в приложение — это все, что  необходимо для выполнения системного вызова open ().

Напишем макрос,  который позволяет вызвать нашу замечательную системную функцию, и соответствующий код, который позволяет этот вызов протестировать.

#define   NR_foo 283

  syscallO(long, foo)

int main ()

{

long stack_size;

stack_size = foo () ;

printf ("Размер стека ядра равен %ld\n", stack_size);

return 0;

}

Почему не нужно создавать системные вызовы

Новый системный вызов  легко  реализовать, тем  не  менее  это  необходимо делать только  тогда,  когда  ничего   другого  не  остается. Часто, для  того  чтобы  обеспечить новый системный вызов,  существуют более  подходящие варианты.  Давайте рассмотрим  некоторые "за" и  "против"  и возможные варианты.

Для  создания нового   интерфейса в виде  системного вызова  могут  быть  следующие  "за".

• Системные вызовы просто  реализовать и легко  использовать.

• Производительность системных вызовов в операционной системе Linux очень  высока.

Возможные "против".

• Необходимо получить номер  системного вызова, который должен  быть официально  назначен в период  работы  над  разрабатываемыми сериями ядер.

• После  того  как  системный вызов  включен в стабильную серию  ядра, он  становится  "высеченным в камне". Интерфейс не  должен  меняться, чтобы  не  нарушить  совместимости с прикладными  пользовательскими программами.

• Для  каждой аппаратной платформы необходимо регистрировать отдельный системный вызов  и  осуществлять его  поддержку.

• Для  простого обмена информацией системный вызов — это "стрельба  из пушки по  воробьям".

Возможные  варианты.

• Реализовать файл  устройства и  использовать функции  rea d ()   и  writ e  ()  для этого  устройства,  а также  использовать функцию  ioct l  ()   для  манипуляции специфическими параметрами  или  для получения  специфической информации.

• Некоторые интерфейсы,  например семафоры, могут  быть  представлены  через дескрипторы  файлов.  Управлять  этими  устройствами  также  можно  по  аналогии  с файлами.

• Добавить информационный  файл  в соответствующем месте  файловой  системы sysfs .

Для  большого  числа интерфейсов,  системные  вызовы— это  правильный  выбор. В  операционной системе   Linux  пытаются избегать   простого добавления  системного нызова для  поддержки каждой  повой, вдруг  появляющейся абстракции. В результате получился  удивительно  четкий  уровень  системных  вызовов,  который  принес  очень мало разочарований  и привел  к малому числу не рекомендованных к использованию и устаревших (deprecated) интерфейсов  (т.е.  таких, которые больше   не  используются  или не поддерживаются).

Малая  частота добавления  новых  системных  вызовов  свидетельствует  о том,  что Linux—  это  стабильная операционная система  с  полным набором функций. Очень немного  системных вызовов  было добавлено  во  время  разработки  серий  ядер  2.3 и

2.5.  Большая  часть из новых  системных  вызовов  предназначена  для улучшения  про-

изводительности.

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии