Coding — IBM. Kernel command using Linux system calls
На днях наткнулся на статью Тима Джонса (Tim Jones) под названием "Kernel command using Linux system calls" и поначалу обрадовался, что автор решил вдохнуть в статью новую жизнь (так как первый раз она была опубликована в 2007 году), но тут же пришло разочарование, увидев, что статья не сильно изменила свой облик и все еще шла речь о ядре 2.6.18 и более ранних версиях. Было принято решение о том, чтобы перевести статью (перевод получился крайне поршивый) и изменить ее содержимое, наполнив более актуальной информацией, то есть для ядер с версии 2.6.32 (на более ранних версиях я не проводил тесты) и до версии 2.6.35. Итак, что из этого вышло судить Вам...
Системные вызовы Linux® — мы используем их каждый день. Но знаете ли Вы как системный вызов осуществляет переход из пространства пользователя в пространство ядра? Исследуем system call interface (SCI), изучим как добавить новые системные вызовы и узнаем утилиты связанные с SCI.
Системные вызовы (system calls) являются интерфейсом между приложениями пространства пользователя и сервисами, которые предоставляет ядро. Вызов не может быть осуществлен напрямую, так как сервисы предоставляются ядром (реализованы в ядре); вместо этого Вам необходимо использовать процесс пересечения границы user-space/kernel. Способы сделать это отличаются в зависимости от конкретной архитектуры. По этой причине я буду придерживаться наиболее распространенной архитектуры i386.
В этой статье я рассмотрю Linux SCI, продемонстрирую добавление системного вызова в ядре 2.6.32 и более поздних (до ядра 2.6.35) и затем вызов этой функции из пространства пользователя. Я также рассмотрю некоторые из функций, которые Вы можете счесть полезными для реализации системного вызова. Наконец я опишу некоторые вспомогательные механизмы связанные с системными вызовами, такие как трассировка.
Вы не должны полностью понимать строение SCI, чтобы изменять его, поэтому я рассмотрю простой вариант работы системного вызова (см. Рис.1). Каждый системный вызов мультиплексируется (multiplexed) в ядро через единую точку входа. Регистр eax используется для определения конкретного системного вызова, который должен быть вызван и который определен в библиотеке языка C (для вызова из пользовательского приложения). Когда библиотека C загрузила индекс (номер) системного вызова и необходимые аргументы, вызывается программное прерывание (прерывание 0x80), которое приводит к выполнению (через обработчик прерываний) функции system_call. Эта функция обрабатывает все системные вызовы, которые определяются содержимым регистра eax. После нескольких простых тестов, фактически, системный вызов вызывается, используя system_call_table и номер, содержащийся в eax. После возвращения из системного вызова, в конечном счете достигается syscall_exit и вызывается resume_userspace для перехода назад в пространство пользователя. Выполнение возобновляется в библиотеке C, которая затем возвращается к пользовательскому приложению.
Рис.1. Упрощенная схема системного вызова, использующего прерывание
В основе SCI лежит таблица демультиплексирования системного вызова. Эта таблица показана на Рис.2, используя номер в eax для определения, какой системный вызов должен быть вызван из таблицы (system_call_table). Также показан простой пример содержимого этой таблицы и расположение системных вызовов.
Рис.2. Таблица системных вызовов и различные связи
Добавление системного вызова является в основном процедурным, хотя Вы должны обратить внимание на некоторые вещи. В этом разделе мы пройдем через создание нескольких системных вызовов, чтобы продемонстрировать их реализацию и использование приложениями пространства пользователя.
Для добавления нового системного вызова в ядро, необходимо проделать три основных шага:
Создадим новый файл для функций arch/x86/kernel/my_call.c. Первые две функции показанные в Листинге 1 являются простыми примерами системных вызовов. Третья функция более сложная, и использует указатель в аргументах.
Листинг 1. Примеры функций системных вызовов
В Листинге 1 функции sys_getjiffies и sys_diffjiffies предназначены для мониторинга jiffies. Первая функция возвращает текущий jiffies, а вторая возвращает разницу между текущим и значением, которое передается. Обратите внимание на использование модификатора asmlinkage. Это макрос (определен в arch/x86/include/asm/linkage.h) говорит компилятору положить все аргументы функции в стек.
Третья функция принимает два аргумента: long и указатель на long, который определен как __user. Макрос __user просто сообщает компилятору (через noderef), что указатель не должен быть разыменован (так как это не имеет значения в текущем адресном пространстве). Эта функция высчитывает разницу между двумя значениями jiffies, а затем возвращает результат пользователю через указатель пространства пользователя. Функция put_user размещает значение результата в том месте пользовательского пространства, на которое указывает presult. Если произойдет ошибка, то будет возращено значение -EFAULT (плохой адрес; ошибки определены в файле include/asm-generic/errno-base.h), т.о., также уведомив пользователя о результате.
На втором шаге я обновляю заголовочные файлы, чтобы освободить место для новых функций в таблице системных вызовов. Для этого я обновляю заголовочный файл arch/x86/include/asm/unistd_32.h с номером нового системного вызова.
Листинг 2. Изменения в unistd_32.h
Теперь я имею в ядре системные вызовы и номера для их представления. Все что осталось сделать, это провести зависимости между номерами (таблицей индексов) и самими функциями. Третий шаг — обновление таблицы системных вызовов. Как показано в Листинге 3, я обновил файл arch/x86/kernel/syscall_table_32.S для новых функций, к которым можно будет обратиться по индексам, показанным в Листинге 2.
Листинг 3. Обновление таблицы системных вызовов
Замечание: Размер этой таблицы определен символической константой NR_syscalls
К этому моменту сделаны все изменения. Теперь осталось перекомпилировать ядро и создать новый образ доступный для загрузки, прежде чем тестировать приложения пространства пользователя.
Замечание: Не забудьте добавить запись о файле с нашими системными вызовами в arch/x86/kernel/Makefile:
Чтение и запись пользовательской памяти
Ядро Linux предоставляет несколько функций, которые Вы можете использовать для перемещения аргументов системного вызова в/из пространства пользователя. Основные методы включают простые функции для базовых типов (такие как get_user и put_user). Для перемещения блоков данных, таких как структуры или массивы, вы можете использовать другой набор функций: copy_from_user и copy_to_user. Для перемещения NULL-terminated строк (т.е. с нулевым символом) есть свои собственные вызовы: strncpy_from_user и strlen_from_user. Вы также можете проверить является ли указатель пространства пользователя действительным через вызов access_ok. Эти функции определены в include/asm-generic/uaccess.h.
Вы можете использовать макрос access_ok, чтобы проверить действительность указателя пространства пользователя для данной операции. Эта функция принимает тип доступа (VERIFY_READ или VERIFY_WRITE) , указатель на блок памяти пространства пользователя и размер блока (в байтах). Функция возвращает нуль в случае успеха:
Перемещение простых типов между ядром и пространством пользователя (таких как целые или длинные) легко осуществляется при помощи put_user и get_user. Каждый из этих макросов принимает значение и указатель на переменную. Функция get_user перемещает значение пользовательского пространства, на которое указывает адрес (ptr) в указанную переменную ядра (x). Функция put_user перемещает значение, на которое указывает переменная ядра (x) в пространство пользователя по указанному адресу (ptr). Функции возвращают нуль в случае успеха:
Для перемещения больших объектов, таких как структуры или массивы, Вы можете использовать функции copy_from_user и copy_to_user. Эти функции перемещают весь блок данных между пространством пользователя и пространством ядра. Функция copy_from_user перемещает блок данных из пространства пользователя в пространство ядра, а copy_to_user перемещает блок данных из пространства ядра в пространство пользователя:
Наконец вы можете копировать NULL-terminated строки из пространства пользователя в пространство ядра, используя функцию strncpy_from_user. Перед вызовом этой функции Вы должны получить размер строки пользовательского пространства с помощью функции strlen_user:
Эти функции являются основными для перемещения значений между пространством пользователя и пространством ядра. Существуют дополнительные функции (такие как те, которые уменьшают количество выполнения проверок). Вы можете найти эти функции в uaccess.h.
Теперь, когда в ядро включены несколько новых системных вызовов, давайте посмотрим на то, что необходимо для их использования в приложении пространства пользователя. Есть два пути, которые позволяют использовать новые системные вызовы. Первый метод удобный (но не настолько, чтобы вы захотели включить его в производительный код), второй метод традиционный и требует чуть больше усилий.
В первом методе, Вы вызываете Ваши новые системные вызовы через функцию syscall. В функции syscall Вы можете вызвать системный вызов указав его номер и набор аргументов. Например, маленькая программа, показанная в Листинге 4 вызывает Вашу функцию sys_getjiffies, используя ее номер.
Листинг 4. Использование syscall для исполнения системного вызова
Как Вы можете заметить, функция syscall включает в себя в качестве первого аргумента номер из таблицы системных вызовов. Если есть еще аргументы, то они должны быть указаны после номера системного вызова. Большинство системных вызовов включает символическую константу SYS_, указывая свое отображение на номера __NR_. Например, Вы можете ссылаться на номер __NR_getpid в syscall как:
Функция syscall архитектурно-зависима и использует механизм передачи управления ядру. Аргумент основывается на отображении индексов __NR в символы SYS_, представленные в /usr/include/bits/syscall.h (определенный, когда libc собран). Никогда не используйте этот файл на прямую; вместо него используйте /usr/include/sys/syscall.h.
Традиционный метод требует, чтобы Вы создавали функции, вызовы которых соответствуют определениям в ядре с точки зрения номера системного вызова (так, чтобы Вы вызывали правильные службы ядра). Linux предоставляет набор макросов для реализации этой возможности. Макрос _syscallN определен в /usr/include/linux/unistd.h и имеет следующий формат:
Макрос _syscall принимает до шести аргументов (хотя здесь показаны только три).
Здесь показано, как используется макрос _syscall, чтобы сделать Ваши новые системные вызовы видимыми пространству пользователя. Листинг 6 демонстрирует приложение, которое использует все Ваши системные вызовы, определив их с помощью макроса _syscall.
Листинг 5. Использование макроса _syscall для разработки приложений пространства пользователя
Заметьте, что индексы __NR необходимы в этом приложении, потому что макрос _syscall использует имя функции, чтобы создать индекс __NR (getjiffies -> __NR_getjiffies). Результат заключается в том, что теперь Вы можете вызывать Ваши функции ядра, используя их имена, также как и любой другой системный вызов.
Замечание: Чтобы программа из Листинга 5 работала на ядрах 2.6.18 и выше, следует заменить:
на
Результатом будет довольно большой дамп, показывающий различные системные вызовы, которые были выполнены в контексте вызова команды call_test1. Вы можете увидеть загрузку разделяемых библиотек, отображение памяти и — в конце трассировки — вывод информации о текущем значении jiffies на стандартный вывод.
Трассировка осуществляется в ядре и если текущий системный вызов имеет установленный набор специальных полей (флагов), то вызывается syscall_trace, который приводит к вызову функции do_syscall_trace. Вы также можете найти трассировку функции, как часть системного вызова в arch/x86/kernel/entry_32.S (см. syscall_trace_entry).
Если воспользоваться командой ltrace, то можно получить чуть более интересный результат:
Здесь можно видеть вызов функции syscall и передачу ей в качестве аргумента номер системного вызова (337) и дальнейший вызов нашей функции sys_getjiffies (SYS_337).
2) Если встретите грубые ошибки перевода (т.е. те, которые искажают смысл) сообщите в ЛС.
3) Выкладываю pdf'ку.
4) Offtop - Агитация:
Системные вызовы Linux® — мы используем их каждый день. Но знаете ли Вы как системный вызов осуществляет переход из пространства пользователя в пространство ядра? Исследуем system call interface (SCI), изучим как добавить новые системные вызовы и узнаем утилиты связанные с SCI.
Системные вызовы (system calls) являются интерфейсом между приложениями пространства пользователя и сервисами, которые предоставляет ядро. Вызов не может быть осуществлен напрямую, так как сервисы предоставляются ядром (реализованы в ядре); вместо этого Вам необходимо использовать процесс пересечения границы user-space/kernel. Способы сделать это отличаются в зависимости от конкретной архитектуры. По этой причине я буду придерживаться наиболее распространенной архитектуры i386.
В этой статье я рассмотрю Linux SCI, продемонстрирую добавление системного вызова в ядре 2.6.32 и более поздних (до ядра 2.6.35) и затем вызов этой функции из пространства пользователя. Я также рассмотрю некоторые из функций, которые Вы можете счесть полезными для реализации системного вызова. Наконец я опишу некоторые вспомогательные механизмы связанные с системными вызовами, такие как трассировка.
SCI
Реализация системных вызовов в Linux различается в зависимости от архитектуры, но она также может отличатся в рамках одной архитектуры. Например, старые процессоры x86 использовали механизм прерываний для перехода из пространства пользователя в пространство ядра, но новые процессоры IA-32 обеспечивают инструкции, которые оптимизируют этот переход (используя инструкции sysenter и sysexit). Поскольку существует очень много вариантов и конечный результат является довольно сложным, то я буду придерживаться поверхностного обсуждения.Вы не должны полностью понимать строение SCI, чтобы изменять его, поэтому я рассмотрю простой вариант работы системного вызова (см. Рис.1). Каждый системный вызов мультиплексируется (multiplexed) в ядро через единую точку входа. Регистр eax используется для определения конкретного системного вызова, который должен быть вызван и который определен в библиотеке языка C (для вызова из пользовательского приложения). Когда библиотека C загрузила индекс (номер) системного вызова и необходимые аргументы, вызывается программное прерывание (прерывание 0x80), которое приводит к выполнению (через обработчик прерываний) функции system_call. Эта функция обрабатывает все системные вызовы, которые определяются содержимым регистра eax. После нескольких простых тестов, фактически, системный вызов вызывается, используя system_call_table и номер, содержащийся в eax. После возвращения из системного вызова, в конечном счете достигается syscall_exit и вызывается resume_userspace для перехода назад в пространство пользователя. Выполнение возобновляется в библиотеке C, которая затем возвращается к пользовательскому приложению.
Рис.1. Упрощенная схема системного вызова, использующего прерывание
В основе SCI лежит таблица демультиплексирования системного вызова. Эта таблица показана на Рис.2, используя номер в eax для определения, какой системный вызов должен быть вызван из таблицы (system_call_table). Также показан простой пример содержимого этой таблицы и расположение системных вызовов.
Рис.2. Таблица системных вызовов и различные связи
Добавление системного вызова Linux
Демультиплексирование системного вызова
Некоторые системные вызовы в дальнейшем демультиплексируются ядром. Например, вызовы сокетов (socket, bind, connect и т.д.) Berkeley Software Distribution (BSD) связаны с одним номером системного вызова (__NR_socketcall), но демультиплексируются в ядре к соответствующему вызову через дополнительный аргумент. См. функцию sys_socketcall в /linux/net/socket.c.
Некоторые системные вызовы в дальнейшем демультиплексируются ядром. Например, вызовы сокетов (socket, bind, connect и т.д.) Berkeley Software Distribution (BSD) связаны с одним номером системного вызова (__NR_socketcall), но демультиплексируются в ядре к соответствующему вызову через дополнительный аргумент. См. функцию sys_socketcall в /linux/net/socket.c.
Добавление системного вызова является в основном процедурным, хотя Вы должны обратить внимание на некоторые вещи. В этом разделе мы пройдем через создание нескольких системных вызовов, чтобы продемонстрировать их реализацию и использование приложениями пространства пользователя.
Для добавления нового системного вызова в ядро, необходимо проделать три основных шага:
- Добавить новую функцию.
- Обновить заголовочные файлы.
- Обновить таблицу системных вызовов для новой функции.
Создадим новый файл для функций arch/x86/kernel/my_call.c. Первые две функции показанные в Листинге 1 являются простыми примерами системных вызовов. Третья функция более сложная, и использует указатель в аргументах.
Листинг 1. Примеры функций системных вызовов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <linux/linkage.h> |
В Листинге 1 функции sys_getjiffies и sys_diffjiffies предназначены для мониторинга jiffies. Первая функция возвращает текущий jiffies, а вторая возвращает разницу между текущим и значением, которое передается. Обратите внимание на использование модификатора asmlinkage. Это макрос (определен в arch/x86/include/asm/linkage.h) говорит компилятору положить все аргументы функции в стек.
Kernel jiffies
Ядро Linux поддерживает глобальную переменную jiffies, которая представляет собой число тиков таймера начиная со старта машины. Эта переменная инициализируется нулем и наращивается с каждым прерыванием таймера. Вы можете получить значение jiffies с помощью функции get_jiffies_64, а затем преобразовать это значение в миллисекунды, используя jiffies_to_msecs или микросекунды с помощью jiffies_to_usecs. Время работы системы равно jiffies/HZ секунд, где HZ — количество прерываний системного таймера в секунду (определен в include/asm-generic/param.h). Переменная jiffies и связанные с ней функции определены в include/linux/jiffies.h.
Ядро Linux поддерживает глобальную переменную jiffies, которая представляет собой число тиков таймера начиная со старта машины. Эта переменная инициализируется нулем и наращивается с каждым прерыванием таймера. Вы можете получить значение jiffies с помощью функции get_jiffies_64, а затем преобразовать это значение в миллисекунды, используя jiffies_to_msecs или микросекунды с помощью jiffies_to_usecs. Время работы системы равно jiffies/HZ секунд, где HZ — количество прерываний системного таймера в секунду (определен в include/asm-generic/param.h). Переменная jiffies и связанные с ней функции определены в include/linux/jiffies.h.
Третья функция принимает два аргумента: long и указатель на long, который определен как __user. Макрос __user просто сообщает компилятору (через noderef), что указатель не должен быть разыменован (так как это не имеет значения в текущем адресном пространстве). Эта функция высчитывает разницу между двумя значениями jiffies, а затем возвращает результат пользователю через указатель пространства пользователя. Функция put_user размещает значение результата в том месте пользовательского пространства, на которое указывает presult. Если произойдет ошибка, то будет возращено значение -EFAULT (плохой адрес; ошибки определены в файле include/asm-generic/errno-base.h), т.о., также уведомив пользователя о результате.
На втором шаге я обновляю заголовочные файлы, чтобы освободить место для новых функций в таблице системных вызовов. Для этого я обновляю заголовочный файл arch/x86/include/asm/unistd_32.h с номером нового системного вызова.
Листинг 2. Изменения в unistd_32.h
1 2 3 4 5 |
#define __NR_rt_tgsigqueueinfo 335 |
Теперь я имею в ядре системные вызовы и номера для их представления. Все что осталось сделать, это провести зависимости между номерами (таблицей индексов) и самими функциями. Третий шаг — обновление таблицы системных вызовов. Как показано в Листинге 3, я обновил файл arch/x86/kernel/syscall_table_32.S для новых функций, к которым можно будет обратиться по индексам, показанным в Листинге 2.
Листинг 3. Обновление таблицы системных вызовов
1 2 3 4 5 |
.long sys_rt_tgsigqueueinfo /* 335 */ |
Замечание: Размер этой таблицы определен символической константой NR_syscalls
К этому моменту сделаны все изменения. Теперь осталось перекомпилировать ядро и создать новый образ доступный для загрузки, прежде чем тестировать приложения пространства пользователя.
Замечание: Не забудьте добавить запись о файле с нашими системными вызовами в arch/x86/kernel/Makefile:
obj-y += my_call.o
Чтение и запись пользовательской памяти
Ядро Linux предоставляет несколько функций, которые Вы можете использовать для перемещения аргументов системного вызова в/из пространства пользователя. Основные методы включают простые функции для базовых типов (такие как get_user и put_user). Для перемещения блоков данных, таких как структуры или массивы, вы можете использовать другой набор функций: copy_from_user и copy_to_user. Для перемещения NULL-terminated строк (т.е. с нулевым символом) есть свои собственные вызовы: strncpy_from_user и strlen_from_user. Вы также можете проверить является ли указатель пространства пользователя действительным через вызов access_ok. Эти функции определены в include/asm-generic/uaccess.h.
Вы можете использовать макрос access_ok, чтобы проверить действительность указателя пространства пользователя для данной операции. Эта функция принимает тип доступа (VERIFY_READ или VERIFY_WRITE) , указатель на блок памяти пространства пользователя и размер блока (в байтах). Функция возвращает нуль в случае успеха:
#define access_ok(type, addr, size) __access_ok((unsigned long)(addr),(size))
Перемещение простых типов между ядром и пространством пользователя (таких как целые или длинные) легко осуществляется при помощи put_user и get_user. Каждый из этих макросов принимает значение и указатель на переменную. Функция get_user перемещает значение пользовательского пространства, на которое указывает адрес (ptr) в указанную переменную ядра (x). Функция put_user перемещает значение, на которое указывает переменная ядра (x) в пространство пользователя по указанному адресу (ptr). Функции возвращают нуль в случае успеха:
1 2 |
#define get_user(x, ptr) |
Для перемещения больших объектов, таких как структуры или массивы, Вы можете использовать функции copy_from_user и copy_to_user. Эти функции перемещают весь блок данных между пространством пользователя и пространством ядра. Функция copy_from_user перемещает блок данных из пространства пользователя в пространство ядра, а copy_to_user перемещает блок данных из пространства ядра в пространство пользователя:
1 2 |
static inline long copy_from_user(void *to, const void __user * from, unsigned long n) |
Наконец вы можете копировать NULL-terminated строки из пространства пользователя в пространство ядра, используя функцию strncpy_from_user. Перед вызовом этой функции Вы должны получить размер строки пользовательского пространства с помощью функции strlen_user:
1 2 |
static inline long strncpy_from_user(char *dst, const char __user *src, long count) |
Эти функции являются основными для перемещения значений между пространством пользователя и пространством ядра. Существуют дополнительные функции (такие как те, которые уменьшают количество выполнения проверок). Вы можете найти эти функции в uaccess.h.
Использование системных вызовов
Использование ядер 2.6.18 и выше
Макрос _syscallN был удален в ядре 2.6.18, так что вместо использования макросов должны быть использована сама функция syscall. Эта функция поддерживает произвольное количество аргументов (int syscall(int number, ...)).
Макрос _syscallN был удален в ядре 2.6.18, так что вместо использования макросов должны быть использована сама функция syscall. Эта функция поддерживает произвольное количество аргументов (int syscall(int number, ...)).
Теперь, когда в ядро включены несколько новых системных вызовов, давайте посмотрим на то, что необходимо для их использования в приложении пространства пользователя. Есть два пути, которые позволяют использовать новые системные вызовы. Первый метод удобный (но не настолько, чтобы вы захотели включить его в производительный код), второй метод традиционный и требует чуть больше усилий.
В первом методе, Вы вызываете Ваши новые системные вызовы через функцию syscall. В функции syscall Вы можете вызвать системный вызов указав его номер и набор аргументов. Например, маленькая программа, показанная в Листинге 4 вызывает Вашу функцию sys_getjiffies, используя ее номер.
Листинг 4. Использование syscall для исполнения системного вызова
1 2 3 4 5 6 7 8 9 10 |
#include <linux/unistd.h> |
Как Вы можете заметить, функция syscall включает в себя в качестве первого аргумента номер из таблицы системных вызовов. Если есть еще аргументы, то они должны быть указаны после номера системного вызова. Большинство системных вызовов включает символическую константу SYS_, указывая свое отображение на номера __NR_. Например, Вы можете ссылаться на номер __NR_getpid в syscall как:
syscall( SYS_getpid )
Функция syscall архитектурно-зависима и использует механизм передачи управления ядру. Аргумент основывается на отображении индексов __NR в символы SYS_, представленные в /usr/include/bits/syscall.h (определенный, когда libc собран). Никогда не используйте этот файл на прямую; вместо него используйте /usr/include/sys/syscall.h.
Традиционный метод требует, чтобы Вы создавали функции, вызовы которых соответствуют определениям в ядре с точки зрения номера системного вызова (так, чтобы Вы вызывали правильные службы ядра). Linux предоставляет набор макросов для реализации этой возможности. Макрос _syscallN определен в /usr/include/linux/unistd.h и имеет следующий формат:
1 2 3 |
_syscall0 ( ret-type, func-name ) |
Пространство пользователя и константы __NR
Обратите внимание, что в Листинге 6 я указал символические константы __NR. Вы можете найти их в /usr/include/asm/unistd_32.h (для стандартных системных вызовов).
Обратите внимание, что в Листинге 6 я указал символические константы __NR. Вы можете найти их в /usr/include/asm/unistd_32.h (для стандартных системных вызовов).
Макрос _syscall принимает до шести аргументов (хотя здесь показаны только три).
Здесь показано, как используется макрос _syscall, чтобы сделать Ваши новые системные вызовы видимыми пространству пользователя. Листинг 6 демонстрирует приложение, которое использует все Ваши системные вызовы, определив их с помощью макроса _syscall.
Листинг 5. Использование макроса _syscall для разработки приложений пространства пользователя
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> |
Заметьте, что индексы __NR необходимы в этом приложении, потому что макрос _syscall использует имя функции, чтобы создать индекс __NR (getjiffies -> __NR_getjiffies). Результат заключается в том, что теперь Вы можете вызывать Ваши функции ядра, используя их имена, также как и любой другой системный вызов.
Замечание: Чтобы программа из Листинга 5 работала на ядрах 2.6.18 и выше, следует заменить:
1 2 3 |
_syscall0( long, getjiffies ); |
1 2 3 |
#define getjiffies() syscall(__NR_getjiffies) |
Трассировка системных вызовов при помощи strace
Ядро Linux предоставляет удобный способ трассировки системных вызовов, которые вызывает процесс (а также те сигналы, которые получит процесс). Утилита называется strace и вызывается из командной строки, используя в качестве аргумента приложение, которое вы хотите трассировать. Например, если вы хотите знать какие системные вызовы были вызваны в течении работы программы из Листинга 4 (назвав программу call_test1), выполните следующую команду:strace ./call_test1
Результатом будет довольно большой дамп, показывающий различные системные вызовы, которые были выполнены в контексте вызова команды call_test1. Вы можете увидеть загрузку разделяемых библиотек, отображение памяти и — в конце трассировки — вывод информации о текущем значении jiffies на стандартный вывод.
1 2 3 4 5 6 7 |
... |
Трассировка осуществляется в ядре и если текущий системный вызов имеет установленный набор специальных полей (флагов), то вызывается syscall_trace, который приводит к вызову функции do_syscall_trace. Вы также можете найти трассировку функции, как часть системного вызова в arch/x86/kernel/entry_32.S (см. syscall_trace_entry).
Если воспользоваться командой ltrace, то можно получить чуть более интересный результат:
1 2 3 4 5 6 |
$ ltrace -S ./call_test1 |
Здесь можно видеть вызов функции syscall и передачу ей в качестве аргумента номер системного вызова (337) и дальнейший вызов нашей функции sys_getjiffies (SYS_337).
Источники
- Статья Тима Джонса - Kernel command using Linux system calls
- rflinux.ru - Системные вызовы в Linux
- Manugarg - Sysenter Based System Call Mechanism in Linux 2.6
2) Если встретите грубые ошибки перевода (т.е. те, которые искажают смысл) сообщите в ЛС.
3) Выкладываю pdf'ку.
4) Offtop - Агитация:
Мы с Вами используем ОС Linux, либо для повседневной деятельности, либо для решения узких задач. Но многие из нас не имеют представления о том, что происходит за кулисами пользовательских приложений или во время "простоя" системы. Поэтому я хотел бы сагитировать участников проекта приподнять вуаль таинственности над работой, которая зачастую остается незамеченной, в самом сердце системы - ядре. Я понимаю, что многие из нас не являются выходцами технических профессий, а кому-то это просто не интересно, но надеюсь, что найдутся и такие, кто захочет разобраться, потратить толику свободного времени и выложить результаты своих исследований на общее обозрение и критику (которой не стоит бояться). Надеюсь, что вскоре появится блог посвященный ядру и будет также активно пополнятся, как и блог "Есть вопрос!".