LLM WikiAccess-protected knowledge portal
← 스터디 홈
1편 · 약 21분

커널 아키텍처와 시스템 콜

Linux는 왜 이렇게 설계됐는가

1991년 8월 25일, 핀란드 학생 Linus Torvalds가 comp.os.minix 뉴스그룹에 짧은 게시글을 올렸다. "나는 386(486) AT 클론용으로 자유로운(무료) 운영체제를 만들고 있다." 그 소스는 monolithic kernel — 커널의 모든 핵심 기능이 단일 주소 공간에서 실행되는 구조 — 으로 설계되었고, 30년이 지난 지금도 그 철학이 유지된다.

당시 Andrew Tanenbaum이 "Linux는 구조적으로 1970년대 설계다"라고 비판했지만, Torvalds는 실용주의로 응수했다: 모놀리식 커널은 마이크로커널보다 IPC(프로세스 간 통신) 오버헤드 없이 훨씬 빠르다. 오늘날 Kubernetes·Docker·systemd가 모두 Linux 위에서 돌아가는 것은 이 성능 선택의 결과다.

Linux (Monolithic)
Kernel Space (Ring 0) 프로세스 스케줄러 메모리 관리 VFS / 파일시스템 네트워크 스택 디바이스 드라이버
직접 함수 호출 → 빠름
Microkernel (예: Mach)
Kernel Space (Ring 0) 스케줄러 (최소) IPC 기본 메모리 관리
User Space 파일시스템 서버 드라이버 서버 네트워크 서버
IPC 오버헤드 → 느림
Linux 커널 설계 철학: 모놀리식 vs 마이크로커널

특권 레벨: Ring 0와 Ring 3

x86 아키텍처는 4개의 보호 레벨(Ring 0 ~ Ring 3)을 정의하지만, Linux는 두 레벨만 쓴다.

  • Ring 0 (Kernel Mode): 모든 CPU 명령어 실행 가능, 하드웨어에 직접 접근, 모든 메모리 주소 읽기/쓰기 가능.
  • Ring 3 (User Mode): 제한된 명령어만 허용, I/O 포트 직접 접근 불가, 커널 메모리 직접 접근 불가.

프로그램이 파일을 열거나 네트워크 패킷을 보내려면 반드시 커널에 요청해야 한다. 그 창구가 시스템 콜(System Call) 이다.

가상 주소 공간 (128TiB × 2)
커널 공간 (0xFFFF... 상위 128TiB) — Ring 0 커널 코드, 커널 데이터, 커널 스택, 디바이스 매핑 유저 공간 (하위 128TiB) — Ring 3 텍스트(코드), 데이터, 힙, 스택, mmap 영역
커널 공간 주소는 모든 프로세스가 공유하지만, 유저 모드에서는 접근 시 즉시 fault가 발생한다.
메모리 공간 분리: 유저 공간 vs 커널 공간 (x86-64, 가상 주소 기준)

커널 주요 서브시스템

Linux 커널은 여러 서브시스템으로 나뉜다. 각 서브시스템은 독립적으로 개발되지만 같은 주소 공간에서 실행된다.

서브시스템역할핵심 코드 위치
프로세스 스케줄러CPU 할당, 선점, 우선순위kernel/sched/
메모리 관리가상 메모리, 페이징, slab 할당자mm/
VFS (Virtual File System)통일된 파일 추상화fs/
네트워크 스택TCP/IP, 소켓net/
디바이스 드라이버하드웨어 추상화drivers/
IPC파이프, 소켓, 공유 메모리ipc/
보안 (LSM)SELinux, AppArmorsecurity/

로드 가능 커널 모듈(LKM) 은 모놀리식 설계의 실용적 타협점이다. 드라이버나 파일시스템을 런타임에 insmod/modprobe로 로드하고 rmmod로 제거할 수 있다. 커널 전체를 재빌드할 필요 없다.

시스템 콜: 유저-커널 경계의 관문

시스템 콜은 사용자 프로그램이 커널 기능을 요청하는 유일하고 공식적인 인터페이스다.

x86-64 시스템 콜 호출 규약

x86-64에서 시스템 콜은 syscall 명령어로 트리거된다:

  1. rax 레지스터에 시스템 콜 번호를 설정
  2. 인자를 순서대로 rdi, rsi, rdx, r10, r8, r9에 설정 (최대 6개)
  3. syscall 명령어 실행 → CPU가 Ring 3 → Ring 0으로 전환, 실행 흐름을 커널 진입점으로 이동
  4. 커널이 처리 후 반환 값을 rax에 저장
  5. sysret 명령어로 Ring 3 복귀
유저 프로그램
(Ring 3)
glibc wrapper
read(fd, buf, n)
rax=0 (read) syscall 명령어
커널 진입점
(Ring 0)
레지스터 저장
스택 전환
sys_call_table
조회
sys_read() 호출
VFS / 드라이버
실행
데이터 복사
rax에 결과
↓ sysret — Ring 3 복귀
syscall 명령어 실행 비용: ~100ns (TLB flush 없는 SYSRET 경로 기준)
시스템 콜 실행 흐름 (x86-64)

핵심 시스템 콜 카탈로그

# 파일 I/O
open(path, flags, mode) → fd       # syscall 2
read(fd, buf, count) → bytes       # syscall 0
write(fd, buf, count) → bytes      # syscall 1
close(fd)                          # syscall 3

# 프로세스
fork() → pid                       # syscall 57 (clone 기반)
execve(path, argv, envp)           # syscall 59
exit(status)                       # syscall 60
wait4(pid, wstatus, opts, rusage)  # syscall 61

# 메모리
mmap(addr, len, prot, flags, fd, off) → addr  # syscall 9
brk(addr)                                      # syscall 12
munmap(addr, len)                              # syscall 11

# 네트워크
socket(domain, type, protocol) → fd   # syscall 41
bind(fd, addr, addrlen)               # syscall 49
connect(fd, addr, addrlen)            # syscall 42

glibc가 시스템 콜을 감추는 방법

C 표준 라이브러리 glibc는 시스템 콜을 직접 호출하는 대신 래퍼(wrapper) 함수를 제공한다.

// 개발자가 쓰는 코드
ssize_t n = read(fd, buf, sizeof(buf));

// glibc 내부 (단순화)
// 실제로는 아키텍처별 어셈블리로 구현됨
long syscall(SYS_read, fd, buf, count);
// → mov rax, 0; mov rdi, fd; syscall; ret

strace 명령어로 프로세스가 실제로 호출하는 시스템 콜을 추적할 수 있다.

$ strace -c ls /tmp
% time     seconds  usecs/call     calls    syscall
------ ----------- ----------- --------- --------
 38.45    0.000250          10        24 mmap
 22.15    0.000144          48         3 read
 15.23    0.000099          16         6 openat
  9.88    0.000064          32         2 getdents64
  ...

vDSO: 커널 진입 없는 빠른 시스템 콜

일부 시스템 콜은 실제 커널 모드 전환 없이도 응답할 수 있다. 대표적인 예가 gettimeofday() — 현재 시각을 반환하는 호출.

vDSO(virtual Dynamic Shared Object) 는 커널이 모든 프로세스의 가상 주소 공간에 자동으로 매핑하는 소형 공유 라이브러리다. 커널은 타이머 인터럽트마다 vvar 페이지(읽기 전용 공유 메모리)를 업데이트하고, vDSO 코드는 이 페이지를 평범한 메모리 읽기로 접근한다. Ring 전환이 없으므로 전통적인 syscall 대비 약 10배 빠르다.

$ ldd /bin/ls
  linux-vdso.so.1 (0x00007ffd3c5f6000)  ← 커널이 자동 주입
  libselinux.so.1 => ...
  libc.so.6 => ...

vDSO를 통해 가속되는 주요 함수: clock_gettime(), gettimeofday(), getpid().

Loadable Kernel Modules

드라이버를 커널에 영구 컴파일하지 않고 런타임에 로드하는 방식이다. /lib/modules/$(uname -r)/ 아래에 .ko 파일로 저장된다.

$ lsmod | head -5
Module                  Size  Used by
nf_tables             225280  0
nfnetlink              20480  1 nf_tables
xfs                  1572864  2

$ modprobe xfs        # 로드
$ rmmod xfs           # 제거
$ modinfo xfs | head  # 모듈 정보

모듈은 Ring 0에서 실행되므로 버그 있는 모듈은 커널 패닉을 일으킬 수 있다. 이 때문에 드라이버 코드에는 서명 검증(module signing)이 적용된다.

정리: 시스템 콜이 중요한 이유

컨테이너·eBPF·성능 분석·보안 정책을 다루는 인프라 엔지니어에게 시스템 콜은 핵심 도구다.

  • seccomp: 허용된 시스템 콜 목록만 통과시키는 샌드박스 (Docker 기본값으로 300여 개 중 ~50개를 차단)
  • eBPF: 커널 이벤트(시스템 콜 포함)에 후크를 걸어 추적·필터링
  • ptrace: 디버거(gdb)와 strace의 기반 — 시스템 콜을 가로채거나 프로세스 레지스터를 검사

다음 편에서는 이 시스템 콜 위에서 동작하는 프로세스와 스레드의 내부 구조를 살펴본다.

References