SYSCALL(2)

ИМЯ
syscall - непрямой системный вызов

ОБЗОР
#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 & 0xFFFFFFFF),
            (unsigned int) (offset >> 32),
            count);

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

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

Заметим, что хотя в parisc C ABI также используется выравнивание пары регистров, в нём используется слой shim, который прячет проблему от пространства пользователя.

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

Это не влияет на syscall-ы, который самостоятельно разделяют и собирают 64-битные значения, например _llseek(2), preadv(2), preadv2(2), pwritev(2) и pwritev2(2). Добро пожаловать в чудесную страну исторического наследия.

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

Поля первой таблицы: инструкция для перехода в режим ядра (может быть не быстрым или лучшим способом перехода в ядро, лучше использовать vdso(7)), регистр для указания номера системного вызова, регистр возврата результата работы системного вызова и регистр сигнализации ошибки.
арх/ABI | инструкция | № syscall | возв.знач. | ошибка | замечания
alpha | callsys | v0 | a0 | a3 | [1]
arc | trap0 | r8 | r0 | -
arm/OABI | swi NR | - | a1 | - | [2]
arm/EABI | swi 0x0 | r7 | r0 | -
arm64 | svc #0 | x8 | x0 | -
blackfin | excpt 0x0 | P0 | R0 | -
i386 | int $0x80 | eax | eax | -
ia64 | break 0x100000 | r15 | r8 | r10 | [1]
m68k | trap #0 | d0 | d0 | -
microblaze | brki r14,8 | r12 | r3 | -
mips | syscall | v0 | v0 | a3 | [1]
nios2 | trap | r2 | r2 | r7
parisc | ble 0x100(%sr2, %r0) | r20 | r28 | -
powerpc | sc | r0 | r3 | r0 | [1]
riscv | scall | a7 | a0 | -
s390 | svc 0 | r1 | r2 | - | [3]
s390x | svc 0 | r1 | r2 | - | [3]
superh | trap #0x17 | r3 | r0 | - | [4]
sparc/32 | t 0x10 | g1 | o0 | psr/csr | [1]
sparc/64 | t 0x6d | g1 | o0 | psr/csr | [1]
tile | swint1 | R10 | R00 | R01 | [1]
x86-64 | syscall | rax | rax | - | [5]
x32 | syscall | rax | rax | - | [5]
xtensa | syscall | a2 | a2 | -

Замечания:

    [1] На некоторых архитектурах для показа отказа системного вызова используется регистр с простым логическим значением (0 — нет ошибки, -1 — ошибка). Настоящее значение ошибки всё равно содержится в регистре возврата. На sparc, вместо полного регистра используется бит переноса (csr) в регистре состояния процессора (psr).

    [2] NR — номер системного вызова.

    [3] Для s390 и s390x NR (номер системного вызова) может передаваться напрямую с помощью svc NR, если он меньше 256.

    [4] На SuperH, номер ловушки (trap number) контролирует максимальное количество передаваемых аргументов.trap #0x10 можно использовать только с системными вызовами без аргументов, trap #0x11 можно использовать только с системными вызовами без или с одним аргументом и так далее до trap #0x17 (7 аргументов в системном вызове).

    [5] В программном двоичном интерфейсе x32 используются те же инструкции что и в x86-64 и используются на тех же процессорах. Разница в том, что для системных вызовов в программном интерфейсе x32 битовая маска __X32_SYSCALL_BIT побитно складывается с номером системного вызова. При этом доступны обе таблицы системных вызовов и установка бита не является большим требованием.

Во второй таблице показаны регистры, которые используются для передачи аргументов в системный вызов.
арх/ABI | arg1 | arg2 | arg3 | arg4 | arg5 | arg6 | arg7 | замечания
alpha | a0 | a1 | a2 | a3 | a4 | a5 | -
arc | r0 | r1 | r2 | r3 | r4 | r5 | -
arm/OABI | a1 | a2 | a3 | a4 | v1 | v2 | v3
arm/EABI | r0 | r1 | r2 | r3 | r4 | r5 | r6
arm64 | x0 | x1 | x2 | x3 | x4 | x5 | -
blackfin | R0 | R1 | R2 | R3 | R4 | R5 | -
i386 | ebx | ecx | edx | esi | edi | ebp | -
ia64 | out0 | out1 | out2 | out3 | out4 | out5 | -
m68k | d1 | d2 | d3 | d4 | d5 | a0 | -
microblaze | r5 | r6 | r7 | r8 | r9 | r10 | -
mips/o32 | a0 | a1 | a2 | a3 | - | - | - | [1]
mips/n32,64 | a0 | a1 | a2 | a3 | a4 | a5 | -
nios2 | r4 | r5 | r6 | r7 | r8 | r9 | -
parisc | r26 | r25 | r24 | r23 | r22 | r21 | -
powerpc | r3 | r4 | r5 | r6 | r7 | r8 | r9
riscv | a0 | a1 | a2 | a3 | a4 | a5 | -
s390 | r2 | r3 | r4 | r5 | r6 | r7 | -
s390x | r2 | r3 | r4 | r5 | r6 | r7 | -
superh | r4 | r5 | r6 | r7 | r0 | r1 | r2
sparc/32 | o0 | o1 | o2 | o3 | o4 | o5 | -
sparc/64 | o0 | o1 | o2 | o3 | o4 | o5 | -
tile | R00 | R01 | R02 | R03 | R04 | R05 | -
x86-64 | rdi | rsi | rdx | r10 | r8 | r9 | -
x32 | rdi | rsi | rdx | r10 | r8 | r9 | -
xtensa | a6 | a3 | a4 | a5 | a8 | a9 | -

Замечания:

    [1] По соглашению в mips/o32 аргументы системных вызовов с 5 по 8 передаются через пользовательский стек.

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

ПРИМЕР
    #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);
        syscall(SYS_tgkill, getpid(), tid, SIGHUP);
    }

СМОТРИТЕ ТАКЖЕ
_syscall(2), intro(2), syscalls(2), errno(3), vdso(7)