SYSCALL(2) Руководство программиста Linux 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 и parisc с 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 Инструкция Система Ret Ret Ошибка замечания
call # знач знач2
_
alpha callsys v0 v0 a4 a3 1, 6
arc trap0 r8 r0 - -
arm/OABI swi NR - a1 - - 2
arm/EABI swi 0x0 r7 r0 r1 -
arm64 svc #0 x8 x0 x1 -
blackfin excpt 0x0 P0 R0 - -
i386 int $0x80 eax eax edx -
ia64 break 0x100000 r15 r8 r9 r10 1, 6
m68k trap #0 d0 d0 - -
microblaze brki r14,8 r12 r3 - -
mips syscall v0 v0 v1 a3 1, 6
nios2 trap r2 r2 - r7
parisc ble 0x100(%sr2, %r0) r20 r28 - -
powerpc sc r0 r3 - r0 1
riscv scall a7 a0 a1 -
s390 svc 0 r1 r2 r3 - 3
s390x svc 0 r1 r2 r3 - 3
superh trap #0x17 r3 r0 r1 - 4, 6
sparc/32 t 0x10 g1 o0 o1 psr/csr 1, 6
sparc/64 t 0x6d g1 o0 o1 psr/csr 1, 6
tile swint1 R10 R00 - R01 1
x86-64 syscall rax rax rdx - 5
x32 syscall rax rax rdx - 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 ABI используется общая с x86-64 ABI таблица syscall, но есть несколько отличий:
  • Чтобы показать, что системный вызов запрошен из x32 ABI, введёндополнительный бит __X32_SYSCALL_BIT, который побитно складывается с номером системного вызова. ABI, используемый процессом, частично влияет на поведение процесса, включая обработку сигналов или перезапуск или системного вызова.
  • Так как на x32 размеры типа long и указателя различаются, то различны раскладки некоторых (но не всех; например, struct timeval или struct rlimit 64-битные) структур. Для их обработки в таблицу системных вызовов добавлены дополнительные системные вызовы; они начинаются с номера 512 (без __X32_SYSCALL_BIT). Например, __NR_readv определён как 19 в x86-64 ABI и как __X32_SYSCALL_BIT | 515 в x32 ABI. Большинство этих дополнительных системных вызовов на самом деле одинаковы с системными вызовами, используемыми для предоставления совместимости с i386. Однако есть исключения, например, preadv2(2), который использует элементы struct iovec с 4-байтовыми указателями и размерами (в терминах ядра — «compat_iovec»), но передаёт 8-байтовый аргумент pos в одном регистре, а не в двух, как это делается в других ABI.
[6]
Некоторые архитектуры (Alpha, IA-64, MIPS, SuperH, sparc/32 sparc/64) используют дополнительный регистр ("Retval2" в таблице выше) для обратной передачи второго возвращаемого значения из системного вызова pipe(2); в Alpha это также используется в зависимых от архитектуры системных вызовах getxpid(2), getxuid(2) и getxgid(2). На других архитектурах в интерфейсе системных вызовов второй регистр для возвращаемого значения не используется, даже если он определён в System V ABI.

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

Арх/ABI арг1 арг2 арг3 арг4 арг5 арг6 арг7 замечания
_
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)

2018-04-30 Linux