Разработка модулей ядра Linux: Часть 7. Анализ выполнения системного вызоваИсточник: ibm Олег Цилюрик
Введение Продолжая изучение отличий между модулем ядра и пользовательским процессом, вернемся к технике осуществления системного вызова из процесса. Рассмотрение системных вызовов, приведенное в прошлых частях цикла, было исключительно теоретическим, и сейчас можно это компенсировать. Кроме того, именно представленный пример лучше всего продемонстрирует нюансы различий, описанные выше. Пример системного вызова Все обсуждаемые примеры содержатся в архиве int80.tgz (который можно найти в разделе Материалы для скачивания), и, в отличие от всех остальных примеров, они применимы только к архитектуре Intel x86 , так как в них реализуется прямой системный вызов Linux через команду ассемблера int 80h. Первый пример (файл mp.c) демонстрирует пользовательский процесс, последовательно выполняющий системные вызовы, эквивалентные библиотечным: getpid(), write(), mknod(), причём write() выполняется именно на дескриптор 1, то есть printf(). Листинг 1. Обычный пользовательский процесс
Пример написан с использованием ассемблерных inline-вставок компилятора GCC, которые будут рассмотрены в отдельной статье. Пример прост и интуитивно понятен: в каждом случае регистры загружаются значениями из переменных C-кода и вызывается прерывание. Ниже приведен результат его запуска:
Всё хорошо, за исключением вызова mknod(), но стоит вспомнить, что одноимённая консольная команда требует прав root:
В результате запуска программы удалось создать именованное символьное устройство, при этом не в каталоге /dev (где оно должно находиться), а в текущем рабочем каталоге. Но желательно потом удалить это имя, как показано в последней команде. Рассмотрим следующий вопрос: нельзя ли выполнить эти (а значит и другие) системные вызовы из кода модуля ядра, то есть изнутри ядра? Оформим код, фактически совпадающий с приведенным в листинге 1, в форме модуля ядра. Но так как хотелось бы написать два почти идентичных модуля (mdu.c и mdc.c), то код, общий для них, необходимо поместить в общий включаемый файл (syscall.h): Листинг 2. Код, общий для двух модулей
В листинге 3 приведен первый модуль mdu.c, который практически полностью повторяет код выполнявшегося выше процесса. Листинг 3. Код модуля, выполненного по правилам
Результат его запуска показан ниже:
В общем, всё совершенно ожидаемо (ошибки выполнения), кроме вызова getpid(), который вызывает некоторые подозрения, но об этом позже. Цель успешно достигнута: было показано главное различие между пользовательским процессом и ядром. Оно состоит в том, что при выполнении системного вызова из любого процесса, код обработчика системного вызова (в ядре!) должен копировать данные параметров вызова из адресного пространства процесса в пространство ядра, а после выполнения копировать данные результатов обратно в адресное пространства процесса. А при попытке вызвать системный обработчик из контекста ядра (модуля), что только что было сделано, адресного пространства процесса не существует, так как нет самого процесса! Однако getpid() успешно выполнился и показал PID какого-то процесса. Он выполнился, так как этот системный вызов не получает параметров и не копирует результатов (он возвращает значение в регистре). А возвращен был PID того процесса, в контексте которого выполнялся системный вызов, т.е. процесса insmod. Но всё-таки системный вызов был выполнен из модуля! Далее необходимо переписать модуль mdc.c, незначительно изменив пример из листинга 2. Листинг 3. Код модуля, выполненного в нарушение правил
Примечание. Вызовы set_fs(), get_ds() выполняют смену сегмента данных с пользователя на ядро, но они будут рассматриваться в следующих статьях. Результат запуска модуля mdc.c, приведенный ниже, может сильно удивить.
Ранее говорилось, что модуль ядра не сможет выполнить printf() и осуществить вывод на графический терминал. Однако видно, что перед инсталляционным сообщением была выведена текстовая строка! На какой управляющий терминал был тогда произведен вывод? Конечно, на терминал запускающей программы insmod, и сделать подобное можно только из функции инициализации модуля. Но главное не это, а то, что в этом примере все системные вызовы успешно выполнились! А значит, выполнится и любой системный вызов, предназначенный для пользовательского пространства. Заключение Подобные эксперименты помогают достичь ясности в понимании взаимосвязей между процессами, ядром и модулями. Поэтому рекомендуется тщательно изучить представленные примеры или создать свои, а следующая статья будет посвящена интерфейсам, используемым модулями ядра. |