서로 다른 시스템 콜을 어떻게 구분할 수 있을까요?

각 시스템 콜에 부여된 고유 번호(시스템 콜 번호)를 RAX 레지스터에 저장하고, 커널이 시스템 콜 테이블(sys_call_table)에서 해당 번호로 커널 함수를 조회하여 구분합니다.

구분 메커니즘 요약

사용자 프로세스: "read()를 호출하고 싶다"
    │
    ▼
C 라이브러리: RAX = 0 (sys_read의 고유 번호)
    │
    ▼
syscall 명령어 → 커널 모드 전환
    │
    ▼
커널 디스패처: RAX 값 확인 → sys_call_table[0] 조회
    │
    ▼
sys_read() 함수 실행

구분 과정 상세

1단계: 라이브러리가 시스템 콜 번호를 RAX에 저장

read(fd, buf, size)  호출 시 → RAX = 0
write(fd, buf, size) 호출 시 → RAX = 1
open(path, flags)    호출 시 → RAX = 2
fork()               호출 시 → RAX = 57

각 시스템 콜마다 고유 번호가 미리 정의되어 있음

2단계: syscall 명령어로 커널 진입

사용자 모드                        커널 모드
┌──────────────────┐           ┌──────────────────────┐
│ RAX = 0           │           │                      │
│ RDI = fd          │  syscall  │ system_call 핸들러    │
│ RSI = buf         │ ────────→ │ 진입                  │
│ RDX = size        │           │                      │
└──────────────────┘           └──────────────────────┘

3단계: 커널 디스패처가 RAX로 테이블 조회

시스템 콜 디스패처 (do_syscall)
    │
    ├── RAX 범위 검사: 0 <= RAX < NR_syscalls ?
    │    ├── Yes → 다음 단계
    │    └── No  → -ENOSYS 에러 반환 ("존재하지 않는 시스템 콜")
    │
    └── sys_call_table[RAX] → 커널 함수 주소 획득 → 호출
sys_call_table
┌────────┬───────────────────┐
│ 인덱스  │ 커널 함수 주소      │
├────────┼───────────────────┤
│   0    │ → sys_read()      │ ← RAX=0 이면 여기
│   1    │ → sys_write()     │ ← RAX=1 이면 여기
│   2    │ → sys_open()      │ ← RAX=2 이면 여기
│   3    │ → sys_close()     │
│  ...   │ ...               │
│  57    │ → sys_fork()      │ ← RAX=57 이면 여기
│  59    │ → sys_execve()    │
│  60    │ → sys_exit()      │
│  ...   │ ...               │
│ ~450   │ (Linux 6.x 기준)  │
└────────┴───────────────────┘

커널 소스로 보는 구분 과정

// 1. 시스템 콜 번호 정의 (커널 헤더)
// include/uapi/asm-generic/unistd.h
#define __NR_read     0
#define __NR_write    1
#define __NR_open     2
#define __NR_close    3
#define __NR_fork     57
#define __NR_execve   59
#define __NR_exit     60

// 2. 시스템 콜 테이블 정의
// arch/x86/entry/syscall_64.c
const sys_call_ptr_t sys_call_table[] = {
    [0]  = sys_read,
    [1]  = sys_write,
    [2]  = sys_open,
    [3]  = sys_close,
    // ...
    [57] = sys_fork,
    [59] = sys_execve,
    [60] = sys_exit,
};

// 3. 디스패처 — RAX로 테이블 조회
// arch/x86/entry/common.c (간략화)
void do_syscall(struct pt_regs *regs) {
    unsigned long nr = regs->rax;       // ① RAX에서 번호 추출

    if (nr < NR_syscalls) {             // ② 범위 검사
        regs->rax = sys_call_table[nr]( // ③ 테이블에서 함수 조회 후 호출
            regs->rdi,                  //    첫 번째 인자
            regs->rsi,                  //    두 번째 인자
            regs->rdx,                  //    세 번째 인자
            regs->r10,                  //    네 번째 인자
            regs->r8,                   //    다섯 번째 인자
            regs->r9                    //    여섯 번째 인자
        );
    } else {
        regs->rax = -ENOSYS;           // 잘못된 번호 → 에러
    }
}

전체 흐름 시각화

Java: fis.read(buf)
  │
  ▼
JVM native: read0()
  │
  ▼
glibc: read(3, buf, 100)
  │
  ├── RAX = 0        ← 시스템 콜 번호로 구분
  ├── RDI = 3
  ├── RSI = buf 주소
  ├── RDX = 100
  │
  ▼
syscall 명령어 → 커널 모드 전환
  │
  ▼
커널 디스패처:
  ├── RAX = 0 확인
  ├── sys_call_table[0] 조회 → sys_read 주소
  └── sys_read(3, buf, 100) 호출   ← 올바른 함수 실행
  │
  ▼
처리 완료 → RAX = 100 (읽은 바이트) → sysret → 사용자 모드 복귀

다른 시스템 콜 호출 시 비교

호출RAX 값테이블 조회실행 함수
read(3, buf, 100)0sys_call_table[0]sys_read()
write(1, "hi", 2)1sys_call_table[1]sys_write()
open("/tmp/a", 0)2sys_call_table[2]sys_open()
fork()57sys_call_table[57]sys_fork()
exit(0)60sys_call_table[60]sys_exit()
결국 모든 시스템 콜의 구분은:

   RAX 레지스터의 값
        │
        ▼
   sys_call_table[RAX]
        │
        ▼
   해당 커널 함수 실행

이 단순한 "번호 → 테이블 인덱싱" 방식으로 이루어집니다.

CSLOG에서 보기