Online video hd

Смотреть с сюжетом видео

Официальный сайт morning-news 24/7/365

Смотреть видео бесплатно

17.08.10 02:55 dementiy

CodingIBM. 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

Реализация системных вызовов в 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.

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

Для добавления нового системного вызова в ядро, необходимо проделать три основных шага:
  1. Добавить новую функцию.
  2. Обновить заголовочные файлы.
  3. Обновить таблицу системных вызовов для новой функции.

Создадим новый файл для функций 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>
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
asmlinkage long sys_getjiffies( void )
{
        return (long)get_jiffies_64();
}
asmlinkage long sys_diffjiffies( long ujiffies )
{
        return (long)get_jiffies_64() - ujiffies;
}
asmlinkage long sys_pdiffjiffies( long ujiffies,
                                     long __user *presult )
{
        long cur_jiffies = (long)get_jiffies_64();
        long result;
        int err = 0;

        if (presult) {
                result = curr_jiffies — ujiffies;
                err = put_user( result, presult );
        }

        return err ? -EFAULT : 0;
}

В Листинге 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.

Третья функция принимает два аргумента: 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    
#define __NR_perf_event_open    336
#define __NR_getjiffies         337
#define __NR_diffjiffies        338
#define __NR_pdiffjiffies       339


Теперь я имею в ядре системные вызовы и номера для их представления. Все что осталось сделать, это провести зависимости между номерами (таблицей индексов) и самими функциями. Третий шаг — обновление таблицы системных вызовов. Как показано в Листинге 3, я обновил файл arch/x86/kernel/syscall_table_32.S для новых функций, к которым можно будет обратиться по индексам, показанным в Листинге 2.

Листинг 3. Обновление таблицы системных вызовов
1
2
3
4
5
.long sys_rt_tgsigqueueinfo     /* 335 */
.long sys_perf_event_open
.long sys_getjiffies
.long sys_diffjiffies
.long sys_pdiffjiffies

Замечание: Размер этой таблицы определен символической константой 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)
#define put_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)
static inline long copy_to_user(void __user *to, const void *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)
static inline long strlen_user(const char __user *src)

Эти функции являются основными для перемещения значений между пространством пользователя и пространством ядра. Существуют дополнительные функции (такие как те, которые уменьшают количество выполнения проверок). Вы можете найти эти функции в uaccess.h.

Использование системных вызовов

Использование ядер 2.6.18 и выше
Макрос _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>
#include <sys/syscall.h>
#define __NR_getjiffies 320
int main()
{
        long jiffies;
        jiffies = syscall( __NR_getjiffies );
        printf( "Current jiffies is %lx\n", jiffies );
        return 0;
}


Как Вы можете заметить, функция 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 )
_syscall1 ( ret-type, func-name, arg1-type, arg1-name )
_syscall2 ( ret-type, func-name, arg1-type, arg1-name, arg2-type, arg2-name )

Пространство пользователя и константы __NR
Обратите внимание, что в Листинге 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>
#include <linux/unistd.h>
#include <sys/syscall.h>
#define __NR_getjiffies         320
#define __NR_diffjiffies        321
#define __NR_pdiffjiffies       322
_syscall0( long, getjiffies );
_syscall1( long, diffjiffies, long, ujiffies );
_syscall2( long, pdiffjiffies, long, ujiffies, long*, presult);
int main()
{
        long jifs, result;
        int err;
        jifs = getjiffies();
        printf( "difference is %lx\n", diffjiffies(jifs) );
        err = pdiffjiffies( jifs, &result );
        if (!err) {
                printf( "difference is %lx\n", result );
        } else {
                printf( "error\n" );
        }
        return 0;
}


Заметьте, что индексы __NR необходимы в этом приложении, потому что макрос _syscall использует имя функции, чтобы создать индекс __NR (getjiffies -> __NR_getjiffies). Результат заключается в том, что теперь Вы можете вызывать Ваши функции ядра, используя их имена, также как и любой другой системный вызов.

Замечание: Чтобы программа из Листинга 5 работала на ядрах 2.6.18 и выше, следует заменить:

1
2
3
_syscall0( long, getjiffies );
_syscall1( long, diffjiffies, long, ujiffies );
_syscall2( long, pdiffjiffies, long, ujiffies, long*, presult );
на
1
2
3
#define getjiffies()                    syscall(__NR_getjiffies)
#define diffjiffies(ujiffies)           syscall(__NR_diffjiffies, ujiffies)
#define pdiffjiffies(ujiffies, presult) syscall(__NR_pdiffjiffies, ujiffies, presult)

Трассировка системных вызовов при помощи strace

Ядро Linux предоставляет удобный способ трассировки системных вызовов, которые вызывает процесс (а также те сигналы, которые получит процесс). Утилита называется strace и вызывается из командной строки, используя в качестве аргумента приложение, которое вы хотите трассировать. Например, если вы хотите знать какие системные вызовы были вызваны в течении работы программы из Листинга 4 (назвав программу call_test1), выполните следующую команду:

strace ./call_test1

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

1
2
3
4
5
6
7
...                                                                                            
SYS_337(0xb78b0ff4, 0x8048460, 0xbf825588, 0xb77a345, 0xb78d3d20) = 468207     
...                                                                                            
write(1, "Current jiffies is 724ef\n", 25Current jiffies is 724ef                      
) = 25                                                                                         
exit_group(0)                           = ?                                            
$

Трассировка осуществляется в ядре и если текущий системный вызов имеет установленный набор специальных полей (флагов), то вызывается 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,  0xb78b0ff4, 0x8048460, 0xbf825588, 0xb77a345 <unfinished...>     
SYS_337(0xb78b0ff4, 0x8048460, 0xbf825588, 0xb77a345, 0xb78d3d20) = 468207     
<...syscall resumed> )                                                                 
...

Здесь можно видеть вызов функции 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
P.S. 1) Пара слов о изменениях. Практически все пути и заголовочные файлы были изменены (в том числе и в Рис.2), также Вы могли заметить, что для системных вызовов мы создаем новый файл и включаем туда все три функции предложенные для тестирования автором, это сделано для большей наглядности. В разделе "Использование системных вызовов" я решил оставить старый метод (как говорит автор традицонный) несмотря на то, что он более не актуален. Последние два раздела "Альтернативы" и "Двигаясь дальше" я не стал включать, так как на мой взгляд это "вода". Остальные изменения Вы можете получить сравнив оригинал статьи с данной.
2) Если встретите грубые ошибки перевода (т.е. те, которые искажают смысл) сообщите в ЛС.
3) Выкладываю pdf'ку.
4) Offtop - Агитация:
Мы с Вами используем ОС Linux, либо для повседневной деятельности, либо для решения узких задач. Но многие из нас не имеют представления о том, что происходит за кулисами пользовательских приложений или во время "простоя" системы. Поэтому я хотел бы сагитировать участников проекта приподнять вуаль таинственности над работой, которая зачастую остается незамеченной, в самом сердце системы - ядре. Я понимаю, что многие из нас не являются выходцами технических профессий, а кому-то это просто не интересно, но надеюсь, что найдутся и такие, кто захочет разобраться, потратить толику свободного времени и выложить результаты своих исследований на общее обозрение и критику (которой не стоит бояться). Надеюсь, что вскоре появится блог посвященный ядру и будет также активно пополнятся, как и блог "Есть вопрос!".


Теги:

predator 17.08.10 05:33 # +2
Офигенно!
main 17.08.10 09:29 # +3
Вы молодец!
Даёшь качественный текст!
strk 17.08.10 11:16 # +-3
Отлично! Больше матана на вилинуксе!
blackraven 17.08.10 23:29 # +0
Изменения выделены жирным шрифтом и показаны в Листинге 2.

Не узрел жирного шрифта...

А вообще - хорошая, годная статья - продолжайте в том же духе!
Особенно в направлении Kernel hacking :)
dementiy 17.08.10 23:42 # +1
Спасибо, я недоглядел. Продолжать обязательно буду.

Лучшие блоги (все 133)
Топ пользователей Топ блогов
Топ пользователей Топ блогов
Элита (все 2589 из 203 городов)
Топ пользователей Топ блогов
welinux.ru

В хорошем качестве hd видео

Онлайн видео бесплатно


Смотреть русское с разговорами видео

Online video HD

Видео скачать на телефон

Русские фильмы бесплатно

Full HD video online

Смотреть видео онлайн

Смотреть HD видео бесплатно

School смотреть онлайн