syscall(2) непрямой системный вызов

ОБЗОР

#define _GNU_SOURCE /* см. feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h> /* для определений SYS_xxx */
long syscall(long number, ...);

ОПИСАНИЕ

syscall() — это маленькая библиотечная функция, которая делает системный вызов, чей интерфейс ассемблерного языка указывается в number, с дополнительными аргументами. Выполнение syscall() нужно, например, для запуска системного вызова, у которого нет обёрточной функции в библиотеке C.

При вызове syscall() сохраняет регистры ЦП до выполнения системного вызова, восстанавливает регистры при возврате из системного вызова и если возникла ошибка, то сохраняет любой код, полученный от системного вызова, в errno(3).

Символьные константы для системных вызовов можно найти в заголовочном файле <sys/syscall.h>.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

Возвращаемое значение определяется вызываемым системным вызовом. При успешном выполнении обычно возвращается 0. При ошибке возвращается -1, при этом код ошибки сохраняется в errno.

ЗАМЕЧАНИЯ

Вызов syscall() впервые появился в 4BSD.

Требования, зависящие от архитектуры

Каждый ABI архитектуры имеет свои собственные требования по передаче аргументов системного вызова в ядро. Для системных вызовов, имеющих обёртку в glibc (большинство системных вызовов), копирование аргументов в правильные регистры с учётом архитектуры выполняется в самой glibc. Однако при выполнении системного вызова через syscall(), вызывающий сам должен учитывать особенности архитектуры; чаще всего это относится к 32-битным архитектурам.

Например, на архитектуре ARM Embedded ABI (EABI) 64-битное значение (long long) должно быть выровнено по чётной паре регистров. То есть, при использовании syscall() вместо обёрточной функции glibc системный вызов readahead() на ARM вызывался бы с учётом EABI следующим образом:

syscall(SYS_readahead, fd, 0,
        (unsigned int) (offset >> 32),
        (unsigned int) (offset & 0xFFFFFFFF),
        count);

Так как смещение аргумента 64 бита, и первый аргумент (fd) передаётся в регистре r0, вызывающий должен разделить и выровнять 64-битное значение так, чтобы оно передавалось в паре регистров r2/r3. Это выполняется вставкой пустого значения в r1 (второго аргумент 0).

Подобные сложности можно видеть на MIPS с O32 ABI, на PowerPC с 32-битным ABI и на Xtensa.

Это относится к системным вызовам fadvise64_64(2), ftruncate64(2), posix_fadvise(2), pread64(2), pwrite64(2), readahead(2), sync_file_range(2) и truncate64(2).

Архитектурные соглашения по вызовам

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

Поля первой таблицы: инструкция для перехода в режим ядра (может быть не быстрым или лучшим способом перехода в ядро, лучше использовать vdso(7)), регистр для указания номера системного вызова, регистр возврата результата работы системного вызова и регистр сигнализации ошибки.

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

Заметим, что эти таблицы не описывают полное соглашение о вызове — некоторые архитектуры могут затирать другие регистры и это здесь не описано.

ПРИМЕР

#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <signal.h>
int
main(int argc, char *argv[])
{
    pid_t tid;
    tid = syscall(SYS_gettid);
    tid = syscall(SYS_tgkill, getpid(), tid, SIGHUP);
}