Как компилятор узнает, что используемая функция является системным вызовом?
Для следующего фрагмента кода,
int n;
char buf[100];
int fd = open ("/etc/passwd", O_RDONLY);
n = read ( fd, buf, 100);
Как компилятор узнает, что read-это системный вызов, а не какая-либо библиотечная функция?
Как он получает номер системного вызова (__NR_read
)?
5 ответов:
Я очень сомневаюсь, что компилятор знает, что это системный вызов. Гораздо более вероятно, что
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 toopen
. Если вы выполните: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 заботится об этом.