Saturday, June 13, 2009

Quik Note: Системные вызовы Linux

Один из основных механизмов системы который нам строить и жить помогает - это механизм системных вызовов. Как ни странно о том чтоже это такое можно долго читать и так ничего и не понять.

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

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

Вообще их можно воспринимать как своеобразные API для доступа к аппаратным ресурсам (главное не путать их с API пользовательского уровня).

Но лучше перейти к практики, а так можно до бесконечности.

Что происходит когда программист запрашивает открытие какого лирбо файла с помощью функции fopen() системной библиотеки libc ?

А происходит следующее:

0x00h) Внутри вункции fopen() происходит вызов системного вызова (но это только начало). Посмотрим в детали. Вызвать системный вызов можно двумя способами - используя ассемблерную иструкцию int 0x80h (классика жанра) или вызвать ассемблерную инструкцию sysenter (доступной начиная со времен процессоров Intel Pentium II). Во всех последних версиях ядер Linux поддерживаются оба способа.

 push dword mode //Все аргументы передаются через стек по stdcall соглашению
push dword flags
push dword path
mov eax, 5 // Номер системного вызова передается через eax (5 = __NR_open)
int 80h
или sysenter (правда не особо к месту, AT&T синтаксис и подробнее расписан тут)
 #include 
int pid;
int main (){
__asm (
"movl $20, %eax \n" // Номер системного вызова
"call *%gs:0x10 \n"
"movl %eax, pid \n" // Сохраняем значение
);
printf ("pid: %d\n", pid);
return 0;
}

0x01h) Обработчик этого вызова располагается в самом ядре (arch/x86/kernel/entry_32.S):

 syscall_call:
call *sys_call_table(,%eax,4) // Используя таблицу системных вызовов вычисляем адрес запрошенного систменого вызова
movl %eax,PT_EAX(%esp) // store the return value

Таблица системных вызовов это специальный массив содержащий в себе адреса всех системных вызовов. Индекс нужного вызова можно посмотреть в include/asm/unistd_32.h.

0xo2h) Дальше управление наконецто переходит к самому системному вызову

0x03h) После этого происходит востановление возвращаемого значения и поройдя еще есколько процедур управление передается туда от куда пришло.

Это всеголишь квик нот то больше подробностей описывать не будем, темболее что их и так много:

тут http://www.int80h.org/
тут http://manugarg.googlepages.com/systemcallinlinux2_6.html
еще очень подробно расписанно тут http://rflinux.blogspot.com/2008/03/linux-syscalls-linux.html

5 comments:

  1. А где описание всех вызовов можно посмотреть?
    (и что там при этом должно быть в таком-то регистре......?)

    ReplyDelete
  2. Список всех системных вызовов можно посмотреть в исходных кодах ядра. Для этого нужно установить соответствующий пакет в вашем дистрибутиве, о будет называтся как-то вроде kernel-sources. После этого исходные коды будут обиnать в районе /usr/src.
    Исходник в котором список системных вызовов arch/x86(если вы используете эту архитектуру)/include/asm/unistd_32.h

    Что в каком регистре перед вызовом системного вызва определяет сам программист согласно описанию системного вызова.

    ReplyDelete
  3. Сори, привел пример bsdшного вызова, в Linux действительно все параметры идут через регистр:
    EAX - хранит номер системного вызова (номер можно узнать в /include/asm/unistd_32.h)
    EBX - 1-й аргумент
    ECX - 2-й аргумент
    EDX - 3-й аргумент
    ESI - 4-й аргумент
    EDI - 5-й аргумент
    EBP - 6-й аргумент
    если имеется больше аргументов то они передаются через стэк.

    Возвращаемое значение хранится в EAX, но есть редкие исключения (если возникают подозрения команды gdb -q /usr/libc.*.so и далее disas *ситемный вызов* могут сказать точно).

    Портотипы самых вызовов можно посмотреть в arch/x86(если вы используете эту архитектуру)/include/asm/syscalls.h

    ReplyDelete
  4. А как ядро распознает брать аргументы из регистров или через стек?

    ReplyDelete
  5. Функция системного вызова "знает" сколько параметров должна принимать и если все регистры "обобраны" а еще не все параметры заполнены то параметры берутся из стека.
    Такое положение вещей задается соглашением вызова функции в данном случае fastcall

    ReplyDelete