Ядро Linux в комментариях

       

System_call


System_call (строка )— это точка входа для всех системных вызовов (в случае родного рода; lcall7 используется для поддержки iBCS2). Как гласит находящийся выше комментарий, цель заключается в сохранении кода максимально последовательным без переходов, поэтому части функций разбросаны повсюду — общий поток управления усложнен ради исключения любых ветвлений. (Ветвления стоят того, чтобы их исключить, поскольку им присущи дополнительные накладные расходы. Они могут полностью разгрузить конвейер ЦП, отвергая внутренний параллелизм, который существенно повышает быстродействие современных процессоров.)

На рис. 5.1 показаны метки переходов, являющиеся частью system_call, а также направление потока управления между ними. Рисунок может оказаться полезным в плане настоящей дискуссии. Метки system_call и restore_all на этом рисунке изображены более крупным шрифтом, поскольку они представляют собой точки нормального входа и нормального выхода для данной функции.

Рис. 5.1. Поток управления для system_call

System_call вызывается из стандартной библиотеки С, которая обеспечивает загрузку необходимых аргументов в регистры ЦП и последующий вызов прерывания 0x80. (Таким образом, system_call представляет собой обработчик прерываний.) В строке ядро регистрирует ассоциацию между программным прерыванием и функцией system_call (SYSCALL_VECTOR определен как 0x80 в строке ).


Читает указатель на текущую задачу из регистра ЕВХ. Это одно из немногих мест, где используется макрос, отвечающий за выполнение данных действий, GET_CURRENT (строка ). В подавляющем большинстве кода применяется функция get_current (строка ).

Начиная с этого места, выражения наподобие foo(%ebx) или foo(%esp) представляют собой код доступа к полям структуры, представляющей текущий процесс (struct task_struct в строке ), которая подробно рассматривается в . (Чтобы быть более точным, смещения относительно %ebx находятся в struct task_struct, а смещения относительно %esp — в struct pt_regs, связанной с struct task_struct. Однако, существующие различия здесь не важны.)

Превысил ли номер системного вызова (в регистре ЕАХ) свое максимальное значение? (Значение ЕАХ обрабатывается здесь как беззнаковое, поэтому оно не может быть отрицательным.) Если превышение имеет место, выполняется переход на badsys (строка ).

Отслеживаются ли системные вызовы? Программы, подобные strace, обеспечивают трассировку для особо заинтересованных разработчиков, а также дополнительную отладочную информацию — вы сможете сказать гораздо больше о такого рода программах после того, как с их помощью проследите за выполнением системных вызовов. В случае, если системные вызовы таки отслеживаются, управление передается tracesys (строка ).

Вызов системной функции. Здесь выполняются многие вещи. Во-первых, ничего не делающий макрос SYMBOL_NAME просто заменяется текстом его аргументов, поэтому его можно проигнорировать. sys_call_table определяется в конце файла arch/i386/kernel/entry.S, начиная со строки . Здесь расположена таблица указателей на функции ядра, которые реализуют разнообразные системные вызовы.

Второй набор скобок в строке содержит аргументы, разделенные запятыми (первый аргумент — пустой); все это необходимо для индексирования элементов в массиве. Разумеется, индексируется массив sys_call_table; он-то и носит название смещения. Три аргумента представляют базовый адрес массива, индекс (ЕАХ, в котором хранится номер системного вызова) и размер, который суть количество байт, приходящихся на каждый элемент массива (в данном случае 4). Поскольку базовый адрес массива пуст, он трактуется как 0, тем не менее, он добавляется к адресу смещения sys_call_table, что в конечном итоге приводит к тому, что sys_call_table рассматривается в качестве базового адреса массива. Данная строка эквивалентна следующему выражению на С:

/* Вызов функции в массиве функций. */ (sys_call_table[eax]) ();



Не следует забывать, что С обеспечивает выполнение черновой работы, такой как запоминание размера элементов массива. Кстати, аргументы, передаваемые в системный вызов, уже находятся в стеке, поскольку они передаются в system_call вызывающей функцией и записываются в стек при помощи SAVE_ALL.



Возврат из системного вызова. Возвращаемое значение (которое является также и возвращаемым значением system_call), находящееся в регистре ЕАХ, сохраняется. Значение возврата сохраняется в позиции ЕАХ стека, поэтому RESTORE_ALL имеет возможность восстановить актуальное значение регистра ЕАХ, равно как и других регистров.

Следующий код, будучи частью system_call, представляет собой отдельную точку входа, известную под именами ret_from_sys_call и ret_from_intr. Так получилось, что на эти точки производятся ссылки из system_call плюс они вызываются непосредственно из С.

Несколько следующих строк проверяют, активна ли «нижняя половина» (bottom half), и если это так, обеспечивают переход на метку handle_bottom_half (строка ) с целью обработки этой нижней (или отложенной) половины прерывания. Нижние половины являются частями процесса обработки прерываний и подробно рассматриваются в следующей главе.

Проверка, помечен ли процесс как такой, для которого должно повторно выполниться планирование (вспомните, что выражение $0 — ни что иное как константа 0). Если для процесса должно выполняться планирование, происходит переход на метку reschedule (строка ).

Проверка, задержан ли сигнал, и если так, то следующая строка выполнит переход непосредственно на signal_return (строка ).

Метка restore_all соответствует точке возврата для system_call. Весь код сводится к вызову макроса RESTORE_ALL (строка ), который восстанавливает аргументы, ранее сохраненные по SAVE_ALL, и возвращается в точку вызова system_call.

Метка signal_return достигается, когда до возврата из системного вызова system_call выясняет, что сигнал достиг текущего процесса. Все начинается с разрешения прерываний (см. ).



Если произошел возврат в виртуальный режим 8086 (в книге не рассматривается), выполняется переход на метку v86_signal_return (строка ).

system_call собирается вызвать С-функцию do_signal (строка , обсуждаемая в ) с целью доставки сигнала. do_signal ожидает два аргумента, передаваемые в регистрах; первый аргумент — это регистр ЕАХ, а второй — регистр EDX. system_call уже установила (в строке A HREF="part1_01.htm#l200">200) ЕАХ в значение, необходимое для первого аргумента; в данном случае выполняется операция XOR для регистра EDX, в результате чего он обнуляется, поэтому do_signal будет рассматривать его как указатель NULL.

Вызов do_signal с целью доставки сигнала и переход на restore_all (строка ) для завершения обработки.

Поскольку виртуальный режим 8086 не относится к тематике книги, в основном, v86_signal_return будет игнорироваться, Однако, следует отметить, что он подобен случаю signal_return.

Метка tracesys достигается в случае, если системные вызовы, присутствующие в текущем процессе, трассируются его родительским процессом, как это делается программой strace. Основная идея, заложенная в эту порцию кода, связана с вызовом системной функции через syscall_table (см. строку ), но с помещением вызова в скобки вместе с обращениями к функции syscall_trace. Последняя функция в книге не рассматривается; она останавливает текущий процесс и уведомляет соответствующий родительский процесс о необходимости активизации системного вызова.

Первым делом путаницу вносят чередующиеся с кодом манипуляции с ЕАХ. system_call устанавливает сохраненную в стеке копию ЕАХ в значение -ENOSYS, обращается к syscall_trace, восстанавливает значение ЕАХ на основе сохраненной в строке копии, выполняет реальный системный вызов и затем помещает значение возврата из системного вызова в стек по месту хранения ЕАХ, после чего еще раз вызывает syscall_trace.

Основная подоплека всего этого связана с тем, что syscal_trace (точнее, программа трассировки) должна знать, когда она вызывается — до или после реального системного вызова. Значение -ENOSYS можно использовать для отражения того, что обращение происходит перед реальным системным вызовом, поскольку ни одна из существующих систем не возвращает -ENOSYS. Следовательно, стековая копия ЕАХ будет равна -ENOSYS перед первым вызовом syscall_trace, но не будет равна упомянутому значению перед вторым вызовом (исключая обращение к sys_ni_syscall, когда о трассировке никто и не думает). Задействование ЕАХ в строках и просто отыскивают активизируемый системный вызов, как и в случае отсутствия трассировки.



Возврат их вызова системы трассировки; управление передается ret_from_sys_call (строка ), как и в случае, когда трассировка отсутствует.

Метка badsys достигается, когда передаваемый номер системного вызова выходит за пределы допустимых значений. В таком случае system_call должен вернуть -ENOSYS (значение ENOSYS устанавливается равным 38 в строке ). Как упоминалось ранее, вызывающая функция воспринимает это как ошибку, поскольку значение возврата попадает в диапазон между –1 и –4095.

Метка ret_from_exception достигается, когда имеют место прерывания в исключительных ситуациях ЦП, такие как ошибка деления на ноль (см. строку ); эта метка никогда не достигается из кода system_call. В случае активности «нижней половины» обеспечивается ее обслуживание.

Метка ret_from_intr достигается после обработки «нижней половины» либо когда имеет место случай возникновения исключительной ситуации ЦП. К данной метке имеется глобальный доступ из многих мест в ядре.

Сохраненные регистры ЦП EFLAGS и CS комбинируются в ЕАХ таким образом, что старших 24 разряда (в том числе и одно интересное значение VM_MASK, определенное в строке ) представляют EFLAGS, а младших 8 разрядов — из CS. Эта строка скрыто проверяет части обоих регистров одновременно, чтобы выяснить, вернулся ли процесс в виртуальный режим 8086 (часть VM_MASK) или в пользовательский режим (часть с константой 3 — пользовательский режим имеет уровень привилегий 3). Эквивалентный код на С выглядит следующим образом:

/* Скомпоновать в еах значения eflags и cs. */ еах = eflags & ~0xff; еах |= cs & 0xff; /* Проверить одновременно 2 младших разряда и разряд VM_MASK. */ if (еах & (VM_MASK | 3)) goto ret_with_reschedule; goto restore_all;

Если одно из двух условий оказывается истинным, управление передается на метку ret_with_reschedule (строка ) для выяснения того, следует ли выполнить повторное планирование для процесса перед возвратом из system_call. В противном случае system_call пропускает часть, относящаяся к повторному планированию, и переходит на метку restore_all (строка ), поскольку вызов был произведен задачей ядра.

Метка handle_bottom_half достигается всякий раз, когда в system_call имеется «нижняя половина». Все сводится к вызову функции С с именем do_bottom_half (строка ; см. ) и переходу на метку ret_from_intr (строка ).

Последний компонент system_call находится под меткой reschedule. Эта метка достигается, когда процесс, выполнивший системный вызов, помечен как такой, для которого необходимо выполнить повторное планирование; как правило, подобное происходит в случае, если выделенный для процесса квант времени исчерпан и ЦП следует отдать для других процессов. За подобного рода обработку отвечает функция С с именем schedule (строка ). Далее управление переходит на строку . Планированию процессов посвящена вся .


Содержание раздела