netlib.narod.ru | < Назад | Оглавление | Далее > |
Ядро не требует загрузки в память целиком. Конечно, определенная часть ядра должна присутствовать в памяти постоянно, например, все время резидентным должен быть код планировщика процессов. Однако другие фрагменты ядра, скажем, драйверы устройств, должны загружаться лишь тогда, когда в них возникает необходимость.
Например, код взаимодействия с устройством чтения CD-ROM должен присутствовать в памяти только в течение собственно взаимодействия с CD-ROM. Поэтому ядро можно сконфигурировать таким образом, что упомянутый код будет загружаться непосредственно перед началом обращения к CD-ROM. Как только взаимодействие завершено, ядро «забывает» о коде, т.е. код, который больше не используется, может быть удален из памяти. Разделы ядра, которые можно загружать и удалять во время выполнения, носят название модулей ядра (kernel modules).
Одно из достоинств модулей ядра заключается в упрощении процесса разработки самого ядра. Вообразите себе следующую ситуацию: вы приобрели совершенно новое устройство чтения CD-ROM со специальным высокоскоростным режимом доступа, который не поддерживается существующим драйвером. Разумеется, очень хочется вкусить все преимущества высокоскоростного режима в своей системе. Если компилировать новый драйвер устройства как модуль ядра, это даст массу преимуществ: после компиляции драйвер можно загрузить в ядро, протестировать, выгрузить, внести изменения, вновь загрузить, протестировать и т.д. В том же случае, когда драйвер погружается непосредственно в ядро, после каждой модификации драйвера придется перекомпилировать ядро целиком и каждый раз перегружать систему. Ну о-о-очень медленно!
Соблюдайте осторожность при работе с модулями ядра. Нельзя удалять из памяти модуль взаимодействия с диском, на котором расположены другие модули ядра, поскольку ядро будет обращаться к этому диску в поисках как раз модуля взаимодействия с диском (новость не особенно хороша). Это еще одна причина принятия решения, как должен компилироваться раздел ядра — как модуль либо как часть ядра, постоянно находящаяся в памяти. Поскольку вам известно как должна устанавливаться система, вам и карты в руки. (Если вы — сторонник наиболее безопасного подхода, компилируйте все в ядро.)
С использованием модулей связаны небольшие накладные расходы в смысле скорости, потому как код необходимого модуля должен быть предварительно считан с диска. Однако общая производительность системы, как правило, увеличивается за счет освобождения дополнительного объема ОЗУ под нужды прикладных приложений. Если ОЗУ резервируется под ядро, увеличивается свопинг прикладных приложений, что приводит к резкому снижению производительности. (Свопинг, или подкачка, рассматривается в главе 8.)
За модули ядра приходится платить также и сложностью. Это связано с тем, что добавление и удаление фрагментов ядра во время выполнения требует дополнительного кода. Однако сложностью можно управлять, как будет показано ниже. Дальнейшее снижение сложности достигается за счет делегирования части необходимой работы некоторой внешней программе. (Если быть более точным, это скорее перераспределяет сложность, нежели уменьшает.) Вот вам изящное дополнение к философии модулей ядра: даже поддержка модулей ядра является частично внешней и загружается только по мере необходимости.
Для этих целей используется программа, именуемая modprobe. Рассмотрение кода modprobe выходит за рамки данной книги, однако его можно найти во всех дистрибутивах Linux. Остаток раздела посвящен исследованиям кода ядра, который взаимодействует с modprobe для загрузки модулей.
24432: Как гласит комментарий, предшествующий этой строке, request_module представляет собой функцию, которая вызывается всегда, когда возникает необходимость загрузить модуль ядра. Как и со всем остальным, что делает ядро, этот запрос выполняется от имени текущего выполняемого процесса. С точки зрения процесса запрос всегда неявный — во время выполнения ядром других запросов вскрывается потребность в загрузке некоторого модуля. Подобный пример можно наблюдать в строке 10070, которая относится к коду, обсуждаемому в главе 7.
24446: Выполнение функции exec_modprobe (строка 24384) в виде отдельного процесса в рамках ядра. Это нельзя сделать в виде простого вызова функции, поскольку exec_modprobe будет приводить к обращению к exec для программы. Следовательно, простой вызов exec_modprobe никогда не приведет к возврату. Вызов весьма похож на использование fork для подготовки exec, поэтому о kernel_thread можно думать как об облегченной версии fork для ядра, хотя kernel_thread имеет одно существенное отличие от fork, которое заключается в том, что процесс начинает выполнение с поименованной функции, а не с точки вызова. Как и в fork, возвращаемым значением kernel_thread является идентификатор (ID) нового процесса.
24448: Опять таки, как и в fork, отрицательное значение возвращаемое kernel_thread означает ошибку.
24455: Как гласит комментарий к функции, для текущего процесса временно блокируется большинство сигналов.
24462: Ожидание завершения exec_modprobe, которое покажет, как загрузился требуемый модуль — успешно или с ошибкой.
24465: Завершение, восстановление сигналов и печать сообщения об ошибке, если exec_modprobe возвратила код ошибки.
24384: exec_modprobe запускает программу, которая присоединяет модуль к ядру. Имя модуля определяется как void *, а не char *, поскольку порождаемые kernel_thread функции требуют одного параметра void *.
24386: Установка списка параметров и среды для modprobe. modprobe_path (строка 24363), который определяет расположение программы modprobe, изменяется через возможность sysctl ядра, описываемую в главе 11 (см. строку 30388). Это означает, что администратор может динамически задавать программу, которая будет выполняться вместо /sbin/modprobe, например, если modprobe находится в другом каталоге.
24400: Избавление от задержанных сигналов и их дескрипторов, что связано с вопросами безопасности (как описано в коде). Наиболее важной частью здесь является обращение к flush_signal_handlers (строка 28041), которая заменяет все определенные пользователем дескрипторы сигналов на дескрипторы, определенные в ядре по умолчанию. На любые сигналы, которые могут поступить после этого момента, будет обеспечиваться стандартная реакция, которая заключается либо в игнорировании сигнала, либо в уничтожении процесса; в данном случае минимизируется риск нарушения системы безопасности. Ввиду того, что эта функция разветвляется из процесса, который ее активизирует (как описывалось ранее), не будет никакой разницы, если в этом месте оригинальный процесс изменит свои установленные дескрипторы сигналов.
24405: Закрытие всех файлов, которые могли быть открыты вызываемым процессом. Самое важное — это то, что программа modprobe не будет наследовать у вызываемого процесса ни стандартного ввода, ни стандартного вывода, ни стандартного потока ошибок, что потенциально может оказаться прорехой в системе защиты. (Проблема может быть решена в программе, которая придет на замену modprobe.)
24413: Программа modprobe запускается как привелигированный процесс, со всеми вытекающими из этого последствиями. Следует отметить, что этот привелигированный процесс, как и во множестве других мест в ядре, получает идентификатор пользователя всегда равный 0. Пользовательские идентификаторы, а также разрешительная система рассматриваются в главе 7.
24421: Попытка запуска программы modprobe. В случае неудачного завершения этой попытки ядро выдает сообщение об ошибке (при помощи printk) и возвращает код ошибки. Это одно из тех мест, где может возникнуть переполнение буфера printk — поскольку длина module_name не задается, все, что можно сказать, глядя на вызов, так это то, что длина может составлять миллионы символов. Дабы иметь уверенность в невозможности переполнения буфера, стоит пройтись по всем вызовам printk (в данном случае по вызовам request_module) и убедиться, что все они работают с достаточно короткими именами модулей, т.е. проблем с printk возникать не должно.
24427: Если execve выполняется удачно, возврата из нее не происходит, а это значит, что данная точка не достигается. Однако, компилятору это неизвестно, потому присутствующий оператор return служит только для gcc.
Более глубокое исследование системы модулей ядра выходит далеко за пределы этой главы и частично рассматривается в главах 4 и 5. Дополнительно исследуются два файла — include/linux/module.h (начинается со строки 15529) и kernel/module.c (начинается со строки 24476). В частности обратите внимание на struct module (строка 15581), а также на функции sys_create_module (строка 24586), sys_init_module (строка 24637), sys_delete_module (строка 24860) и sys_query_module (строка 25148). Эти функции реализуют системные вызовы, которые использует modprobe, плюс связанные программы insmod, lsmod и rmmod для установки, поиска и удаления модулей.
Может вызвать удивление тот факт, что ядро активизирует программу, которая просто делает вызовы того же ядра. Однако при этом выполняется дополнительная работа. С одной стороны, программа modprobe выполняет поиск на диске необходимых файлов модуля, которые должны быть загружены. Кроме того, и это более важно, привелигированный пользователь (root) получает больший контроль над системой модулей ядра, поскольку такой пользователь может запускать modprobe и связанные с нею программы. Следовательно, привелигированный пользователь имеет возможность вручную загружать, запрашивать и удалять модули, либо это может делать само ядро в автоматическом режиме.
netlib.narod.ru | < Назад | Оглавление | Далее > |