Как компилятор узнает, что используемая функция является системным вызовом?


Для следующего фрагмента кода,

int n;
char buf[100];
int fd = open ("/etc/passwd", O_RDONLY);
n = read ( fd, buf, 100);

Как компилятор узнает, что read-это системный вызов, а не какая-либо библиотечная функция?

Как он получает номер системного вызова (__NR_read)?

5 5

5 ответов:

Open () - это библиотечная функция, она находится в libc.а / libc.so

Я очень сомневаюсь, что компилятор знает, что это системный вызов. Гораздо более вероятно, что open находится где-то в библиотеке, и код внутри библиотеки вызывает соответствующий интерфейс ядра.

Вывод сборки из простой программы:

#include <stdio.h>
int main (void) {
    int fd = open("xyz");
    return 0;
}

Is (удалены несущественные биты):

main:
    pushl   %ebp            ; stack frame setup.
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp

    movl    $.LC0, (%esp)   ; Store file name address.
    call    open            ; call the library function.
    movl    %eax, 28(%esp)  ; save returned file descriptor.

    movl    $0, %eax        ; return 0 error code.

    leave                   ; stack frame teardown.
    ret

.LC0:
    .string "xyz"           ; file name to open.
Первое, что вы заметите, это то, что естьвызов к open. Другими словами, это функция. Там нет int 80 или sysenter в поле зрения, который является механизм, используемый для правильных системных вызовов (на моей платформе во всяком случае - YMMV).

Функции-оболочки в libc являются тем местом, где выполняется фактическая работа по доступу к интерфейсу системного вызова.

Выдержка из Википедии о системных вызовах :

Как правило, системы предоставляют библиотеку, которая находится между обычными программами и операционной системой, обычно это реализация библиотеки C (libc), такой как glibc. Эта библиотека существует между ОС и приложением, и повышает мобильность. В системах, основанных на экзокерне, библиотека особенно важна как посредник. В exokernels библиотеки защищают пользовательские приложения от очень низкоуровневого API ядра и обеспечивают абстракции и управление ресурсами. Термины "системный вызов" и "syscall" часто неправильно используются для обозначения функций стандартной библиотеки C, особенно тех, которые действуют как оболочка для соответствующих системных вызовов с тем же именем. Звонок в полицию сама библиотечная функция не вызывает переключения в режим ядра (если выполнение еще не было в режиме ядра) и обычно является обычным вызовом подпрограммы (т. е. использование инструкции сборки "вызов" в некоторых Isa). Фактический системный вызов действительно передает управление ядру (и более зависим от реализации, чем абстрагирующий его библиотечный вызов). Например, fork и execve являются функциями GLIBC, которые в свою очередь вызывают системные вызовы fork и execve.

И, после небольшого поиска, функция __open находится в glibc 2.9 в файле io/open.c и weakref'ed to open. Если вы выполните:

nm /usr/lib/libc.a | egrep 'W __open$|W open$'

Вы можете увидеть их там:

00000000 W __open
00000000 W open

Read-это вызов библиотеки для компилятора. Так уж получилось, что реализация libc определяет read для генерации программного прерывания с правильным номером.

Компилятор может видеть объявление этой функции В, и он генерирует объектный код, который делает вызов этой функции.

Попробуйте скомпилировать с gcc -S , и вы увидите что-то вроде:

movl    $100, %edx
movq    %rcx, %rsi
movl    %eax, %edi
call    read

Системный вызов выполняется из реализации библиотеки C read(2).

EDIT: в частности, GNU libc (который, вероятно, есть у вас в Linux) устанавливает отношения между числами syscall и именами функций в glibc-2.12.1/sysdeps/syscalls.list. Каждая строка этого файла преобразуется в исходный код языка ассемблера (основанный на sysdeps/unix/syscall-template.S), скомпилированный и добавленный в библиотеку при построении libc.

Ниже приведена реализация Android read in bionic (эквивалент Android для libc)

/* autogenerated by gensyscalls.py */
#include <sys/linux-syscalls.h>

    .text
    .type read, #function
    .globl read
    .align 4
    .fnstart

read:
    .save   {r4, r7}
    stmfd   sp!, {r4, r7}
    ldr     r7, =__NR_read
    swi     #0
    ldmfd   sp!, {r4, r7}
    movs    r0, r0
    bxpl    lr
    b       __set_syscall_errno
    .fnend

Вы можете видеть, что он загружает __NR_read в r7 и затем вызывает SWI, SWI-это программное прерывание, которое переключает prcessor в режим ядра. Поэтому компилятору ничего не нужно знать о том, как делать системные вызовы, libc заботится об этом.