NAME

syscall - 간접적 시스템 호출

SYNOPSIS

#include <unistd.h>
#include <sys/syscall.h>    /* SYS_xxx 정의 */

long syscall(long number, ...);

glibc 기능 확인 매크로 요건 (feature_test_macros(7) 참고):

syscall():
glibc 2.19부터:
_DEFAULT_SOURCE
glibc 2.19 전:
_BSD_SOURCE || _SVID_SOURCE

DESCRIPTION

syscall()은 작은 라이브러리 함수이며, 지정한 번호 number와 지정한 인자들을 가진 어셈블리 언어 인터페이스의 시스템 호출을 부른다. 예를 들어 C 라이브러리에 래퍼 함수가 없는 시스템 호출을 부를 때 syscall()이 유용하다.

syscall()은 시스템 호출을 하기 전에 CPU 레지스터들을 저장해 두고, 시스템 호출 반환 후 그 레지스터들을 복원하며, 시스템 호출이 오류를 반환했으면 그 오류를 errno(3)에 저장한다.

시스템 호출 번호 상수들을 헤더 파일 <sys/syscall.h>에서 찾을 수 있다.

RETURN VALUE

부르려고 하는 시스템 호출이 반환 값을 규정한다. 일반적으로 반환 값 0은 성공을 나타낸다. 반환 값 -1은 오류를 나타내며 errno에 오류 번호가 저장된다.

NOTES

4BSD에서 syscall()이 처음 등장했다.

아키텍처별 요구 사항

각각의 아키텍처 ABI마다 시스템 호출을 어떻게 커널로 전달하는지에 대한 나름의 요구 사항이 있다. glibc 래퍼가 있는 시스템 호출(즉, 대부분의 시스템 호출)에서는 아키텍처에 맞는 방식으로 인자를 레지스터로 복사하는 세부 사항들을 glibc가 다뤄 준다. 하지만 syscall()을 이용해 시스템 호출을 할 때는 호출자가 아키텍처별 세부 사항들을 다뤄야 할 수도 있다. 특정 32비트 아키텍처들에서 이런 요구 사항을 가장 흔히 만나게 된다.

예를 들어 ARM 아키텍처 임베디드 ABI(EABI)에서는 64비트 값(가령 long long)을 짝수 번 레지스터 쌍에 정렬시켜야 한다. 그래서 리틀 엔디언 모드 EABI ARM 아키텍처에서 glibc가 제공하는 래퍼 대신 syscall()을 사용한다면 readahead(2) 시스템 호출을 다음과 같이 부르게 될 것이다.

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

offset 인자가 64비트이고 첫 번째 인자(fd)를 r0로 전달하므로 호출자가 64비트 값을 직접 쪼개고 정렬해서 r2/r3 레지스터 쌍으로 전달되게 해야 한다. 즉 r1에 더미 값을 넣는다 (두 번째 인자 0). 쪼개기가 (플랫폼에 대한 C ABI에 따른) 엔디언 규약을 따르도록 하는 데에도 주의를 기울여야 한다.

O32 ABI의 MIPS, 32비트 ABI의 PowerPC 및 parisc, 그리고 Xtensa에서도 비슷한 문제가 발생할 수 있다.

참고로 parisc C ABI에서도 정렬된 레지스터 쌍을 사용하지만 중간 계층을 이용해 사용자 공간에게 문제를 감춰 준다.

영향 받는 시스템 호출은 fadvise64_64(2), ftruncate64(2), posix_fadvise(2), pread64(2), pwrite64(2), readahead(2), sync_file_range(2), truncate64(2)이다.

_llseek(2), preadv(2), preadv2(2), pwritev(2), pwritev2(2)처럼 직접 64비트 값을 쪼개고 합치는 시스템 호출들은 영향을 받지 않는다. 역사적 잔재들의 멋진 세계에 온 것을 환영한다.

아키텍처 호출 규약

아키텍처마다 커널을 호출하고 인자를 전달하는 나름의 방식이 있다. 여러 아키텍처의 세부 사항들이 아래 두 표에 나열되어 있다.

첫 번째 표는 커널로 전환하는 데 쓰는 인스트럭션 (커널로 전환하는 최단 내지 최선의 방법이 아닐 수도 있으며, 그래서 vdso(7)를 참고해야 할 수도 있음), 시스템 호출 번호를 나타내는 데 쓰는 레지스터, 커널 호출 결과를 반환하는 데 쓰는 레지스터(들), 오류를 알리는 데 쓰는 레지스터를 보여 준다.

아키텍처/ABI 인스트럭션 시스템
호출 #
반환
반환
값 2
오류 참고
alpha callsys v0 v0 a4 a3 1, 6
arc trap0 r8 r0 - -
arm/OABI swi NR - r0 - - 2
arm/EABI swi 0x0 r7 r0 r1 -
arm64 svc #0 w8 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 trapa #31 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에서는 레지스터 전체 대신 프로세서 상태 레지스터(psr)의 캐리 비트(csr)를 사용한다. powerpc64에서는 조건 레지스터(cr0) 0번 필드의 summary overflow 비트(SO)를 사용한다.

[2] NR이 시스템 호출 번호이다.

[3] s390과 s390x에서는 NR(시스템 호출 번호)가 256보다 작으면 svc NR로 직접 전달할 수도 있다.

[4] SuperH에서는 역사적 이유로 다른 트랩 번호들을 추가로 지원하지만 권장하는 "통합" ABI는 trapa#31이다.

[5] x32 ABI는 x86-64 ABI와 시스템 호출 테이블을 공유하되 몇 가지 미묘한 차이가 있다.

[6] 일부 아키텍처(Alpha, IA-64, MIPS, SuperH, sparc/32, sparc/64)에서는 추가 레지스터(위 표의 "반환 값 2")를 사용해서 pipe(2) 시스템 호출의 두 번째 반환 값을 되돌려 준다. Alpha에선 아키텍처 한정 시스템 호출인 getxpid(2), getxuid(2), getxgid(2)에서도 이 기법을 쓴다. 다른 아키텍처들에선 System V ABI에 정의돼 있는 경우조차도 시스템 호출 인터페이스에서 두 번째 반환 값 레지스터를 쓰지 않는다.

두 번째 표는 시스템 호출 인자들을 전달하는 데 쓰는 레지스터들을 보여 준다.

아키텍처/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 참고
alpha a0 a1 a2 a3 a4 a5 -
arc r0 r1 r2 r3 r4 r5 -
arm/OABI r0 r1 r2 r3 r4 r5 r6
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번까지 인자들을 사용자 스택으로 전달한다.

참고로 이 표들이 호출 규약 전체를 포괄하는 것은 아니다. 일부 아키텍처에서 여기 나열 안 된 다른 레지스터들을 마구 건드릴 수도 있다.

EXAMPLES

#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);
}

SEE ALSO

_syscall(2), intro(2), syscalls(2), errno(3), vdso(7)


2021-03-22