syscall(2)
| 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 |
| powerpc64 | sc | r0 | r3 | - | cr0.SO | 1 |
| riscv | ecall | 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). На powerpc64, используется бит общего переполнения (SO) в поле 0 регистра условия (cr0).
- [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 | |
| powerpc64 | r3 | r4 | r5 | r6 | r7 | r8 | - | |
| 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);
}
СМОТРИТЕ ТАКЖЕ
| 2018-04-30 | Linux |
