[OS] System call
System Call
목차
- 시스템 콜 정의
- 시스템 콜 제공
- 시스템 콜 세부구성
- 시스템 콜 구현
시스템 콜 정의
시스템 콜(System Call)은 사용자 공간(User Space) 과 커널 공간(Kernel Space) 사이를 이어주는 인터페이스다.
시스템 콜이 필요한 이유는 크게 3가지다.
- 하드웨어를 직접 다루지 않아도 되게 해준다.
예) 파일 입출력을 할 때, 애플리케이션은 디스크 구조나 파일시스템 내부 구현을 몰라도 된다.
- 보안과 안정성을 지켜준다.
예) 커널이 중간에서 권한 검사와 자원 접근 통제를 수행한다.
- 프로세스마다 가상화된 실행 환경을 제공한다.
예) 각 프로세스는 자신만의 주소 공간을 가진 것처럼 동작한다.
리눅스에서 사용자 공간 프로세스가 커널 기능을 사용하려면, 기본적으로 시스템 콜 경로를 거쳐야 한다.
- 트랩(Trap): 소프트웨어가 발생시키는 동기식 인터럽트.
예) 0으로 나누기, 페이지 폴트.
x86 기준 시스템 콜은 수백 개(대략 300개 이상)가 있으며, 아키텍처별로 일부 차이가 있다.
syscall
리눅스에서는 system call을 줄여서 syscall이라고 부른다. 보통 사용자 코드는 라이브러리 함수를 호출하고, 내부에서 시스템 콜이 실행된다.
예) 파일에 데이터 쓰기, 프로세스 ID 조회.
시스템 콜의 반환형은 long인 경우가 많다. 이는 32비트/64비트 환경 호환성을 고려한 설계다.
예시로 getpid()는 현재 프로세스의 PID를 반환한다.
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
각 요소의 의미:
SYSCALL_DEFINE0: 인자 0개인 시스템 콜 정의 매크로getpid: 시스템 콜 이름task_tgid_vnr(current): 현재 태스크의 TGID 반환
왜 getpid()에서 TGID를 반환할까?
- 일반적인 단일 스레드 프로세스에서는 PID와 TGID가 동일하다.
- 그래서 결과적으로 사용자 입장에서는 PID를 얻는 것과 같다.
이 매크로는 내부적으로 아래 형태의 심볼로 연결된다.
asmlinkage long sys_getpid(void);
각 요소의 의미:
asmlinkage: 인자를 스택에서 전달받도록 컴파일러에 알리는 키워드- 반환형
long: 아키텍처 호환성 유지 sys_: 시스템 콜 구현 함수 접두어
예) bar() 시스템 콜 구현 함수 이름은 sys_bar() 형태.
대부분의 시스템 콜은 호출 번호 + 매개변수로 커널에 전달되며, 아키텍처 규약에 따라 레지스터를 사용한다.
시스템 콜 제공
애플리케이션은 보통 시스템 콜을 직접 호출하지 않고, 사용자 공간 API를 통해 간접적으로 사용한다.
- API: Application Programming Interface
- 대표 표준: POSIX(Portable Operating System Interface)
POSIX 덕분에 운영체제 내부 구현이 달라도, 애플리케이션은 비슷한 인터페이스를 사용할 수 있다.

리눅스의 시스템 콜 인터페이스는 보통 C 라이브러리(예: glibc)를 통해 제공된다.
- 개발자는 API 사용에 집중
- 커널은 시스템 콜 구현과 안정성에 집중
유닉스 철학의 핵심 문장:
- Provide mechanism, not policy.
- 정책(어떻게 쓸지)은 사용자/프로그램이 결정하고, 커널은 수단(메커니즘)을 제공한다.
사용자 공간에서 시스템 콜
일반적으로 C 라이브러리가 시스템 콜 진입 과정을 감싼 래퍼(wrapper)를 제공한다.
즉, 애플리케이션은 표준 헤더와 라이브러리 링크만으로 시스템 콜을 쓸 수 있다.
리눅스에는 과거 _syscalln() 계열 매크로가 있었고, 여기서 n은 인자 개수(0~6)를 의미했다.
예시:
long open(const char *filename, int flags, int mode)
#define __NR_open 5
_syscall3(long, open, const char *, filename, int, flags, int, mode)
핵심 포인트:
- 앞 2개 인자: 반환형, 시스템 콜 이름
- 그 뒤 인자들:
(타입, 이름)쌍 __NR_open: 시스템 콜 번호
알맞은 시스템 콜 찾기
사용자 공간에서 커널로 진입해도, 어떤 시스템 콜을 실행할지 번호로 알려줘야 한다.
x86 아키텍처에서는 전통적으로 eax 레지스터를 사용해 시스템 콜 번호를 전달한다.
흐름:
- 사용자 공간에서 시스템 콜 번호 준비
- 예외/트랩을 통해 커널 모드 진입
- 시스템 콜 핸들러가 번호를 읽어 해당 구현으로 분기
커널은 번호가 유효한지 NR_syscalls 범위를 기준으로 확인한다.
시스템 콜 세부구성
시스템 콜은 크게 번호(System Call Number) 와 핸들러(System Call Handler) 를 중심으로 동작한다.
시스템 콜 번호
시스템 콜 핸들러는 전달받은 번호가 유효한지 먼저 검사한다.
시스템 콜 번호의 성질:
- 한 번 할당된 번호는 바꾸지 않는다.
- 제거된 시스템 콜 번호는 보통 재사용하지 않는다.
이유:
- 번호를 바꾸거나 재사용하면, 이미 빌드된 기존 프로그램이 잘못된 시스템 콜을 호출할 수 있다.
리눅스는 구현되지 않은 시스템 콜 처리용으로 sys_ni_syscall()을 제공한다.
- 반환값:
-ENOSYS(해당 시스템 콜 미지원)
시스템 콜 매핑은 아키텍처별 sys_call_table에 저장된다.
시스템 콜 핸들러
사용자 프로세스가 시스템 콜을 실행하려면 커널 모드로 전환되어야 하며, 이를 위해 소프트웨어 인터럽트/예외 메커니즘을 사용한다.
핸들러의 역할:
- 전달받은 시스템 콜 번호 확인
- 매개변수 검증
- 실제 커널 내부 구현 호출
- 결과를 사용자 공간에 반환
x86-64에서는 진입 코드가 entry_64.S 등 아키텍처별 엔트리 코드에 정의되어 있다.
시스템 콜 구현
구현 순서
시스템 콜을 설계/구현할 때는 아래 순서로 보는 것이 좋다.
- 목적을 하나로 명확히 정의
- 한 시스템 콜은 한 가지 핵심 목적에 집중하는 것이 유지보수에 유리하다.
- 의미와 동작은 공개 후 바꾸기 어렵다(호환성 문제).
- 미래 확장을 고려해 플래그 인자를 두는 방식이 자주 사용된다.
- 매개변수 유효성 검증
- 잘못된 파일 디스크립터, 잘못된 PID, 범위 밖 값 등을 검사해야 한다.
- 사용자 포인터는 반드시 접근 가능성과 주소 공간 유효성을 확인해야 한다.
검증 예시:
- 사용자 공간 주소인지 확인
- 해당 프로세스 주소 공간인지 확인
- 읽기/쓰기 권한이 맞는지 확인
- 권한 검증
capable()로 필요한 권한을 갖췄는지 검사한다.
예) capable(CAP_SYS_NICE)
사용자 공간과 데이터 교환 시 대표 함수:
copy_to_user(dst, src, size)
- 커널 -> 사용자 공간 복사
copy_from_user(dst, src, size)
- 사용자 -> 커널 공간 복사
두 함수는 실패 시 남은 바이트 수를 반환하고, 성공 시 0을 반환한다.
검증/복사 로직까지 완성하면 시스템 콜 테이블에 항목을 추가한다.
- 지원 아키텍처별 테이블 반영
unistd.h계열 헤더에 번호 정의- 커널 이미지에 포함되도록 빌드
시스템 콜 컨택스트
시스템 콜 컨텍스트에서는 현재 프로세스가 커널 모드에서 실행된다.
중요한 특성:
- 블로킹(수면) 가능
- 선점 가능
- 따라서 재진입성을 고려한 동기화/보호가 필요
시스템 콜이 끝나면 제어권은 커널의 반환 경로를 거쳐 다시 사용자 공간으로 돌아가고, 사용자 프로세스 실행이 재개된다.
추가 조사
소프트웨어 인터럽트
인터럽트 섹션에서 별도로 정리 예정.
syscall 매크로 정의 위치

참고자료: kernel에 system call 추가(ubuntu)
EAX 레지스터
정의:
- EAX(Extended Accumulator Register)는 산술/논리 연산과 반환값 전달에 자주 쓰이는 레지스터다.
참고 포인트:
- 64비트 레지스터
RAX의 하위 32비트가EAX - 호출 규약/아키텍처 규칙에 따라 반환값 전달과 시스템 콜 번호 전달에 사용될 수 있음
크기 관계:
RAX(64)->EAX(32)->AX(16)->AH/AL(8)
시스템 콜 호출시 시스템 스코프 이동 블럭 다이어그램

흐름 요약:
- 사용자 코드가 라이브러리 함수 호출
- 라이브러리 래퍼가 시스템 콜 번호/인자 준비
- 커널 모드 진입 후 시스템 콜 핸들러 실행
- 핸들러가 번호를 기반으로 실제 커널 함수 호출
- 필요하면
copy_to_user()/copy_from_user()수행 - 사용자 공간으로 복귀
- 시스템 콜 테이블은 보통 번호와 핸들러 매핑 정보를 가진다.
예시 이미지:

매핑 테이블 참고: Linux System Call Table for x86_64
sys_ni_syscall()
sys_ni_syscall()은 유효하지 않은 시스템 콜에 대해 -ENOSYS를 반환한다.

출처: elixir.bootlin.com - kernel/sys_ni.c
capable() 리턴 정보
capable()에서 사용할 수 있는 권한(capability) 항목은 <linux/capability.h>에서 확인 가능하다.
참고: capability.h 문서