유저 프로그램이 커널에게 일을 부탁하는 유일한 통로.
왜 모드를 나누나
-
유저 모드 — 제한된 권한. 자기 주소 공간만 만질 수 있다
-
커널 모드 — 모든 권한. 하드웨어·다른 프로세스의 메모리에 접근 가능
-
나누지 않으면
- 프로그램 하나가 다른 프로그램의 메모리를 읽고 쓸 수 있다
- 디스크를 직접 지울 수 있다
- 무한 루프로 CPU 를 영영 붙잡을 수 있다
"아무나 하드웨어를 못 만지게" 가 OS 보호의 출발점이고, CPU가 하드웨어로 이를 강제한다.
어떻게 넘어가나
-
① 유저 프로그램이 시스템 콜 번호와 인자를 레지스터에 넣는다
-
② 특별한 명령을 실행한다 (x86-64 는 syscall)
-
③ CPU 가 커널 모드로 전환하고 정해진 진입점으로 점프한다
-
④ 커널이 번호를 보고 해당 처리를 한다
-
⑤ 결과를 반환하며 유저 모드로 돌아온다
-
③ 이 핵심 — '아무 주소' 로 못 뛴다. 커널이 정한 진입점으로만 들어간다
strace -c ls # 어떤 시스템 콜을 몇 번 했나
# openat 12 read 8 write 1 close 12 ...
strace -T ls # 각 호출에 걸린 시간
비용이 있다
모드 전환 자체 + 레지스터 저장·복원 + 캐시·TLB 오염
- 수백 나노초 ~ 수 마이크로초
함수 호출(수 나노초) 보다 수백 배 비싸다
- "시스템 콜 횟수를 줄이는 것" 이 그대로 최적화가 된다
// ❌ 1바이트씩 100만 번 → 시스템 콜 100만 번
for (int i = 0; i < 1000000; i++) write(fd, &buf[i], 1);
// ✅ 버퍼에 모았다 한 번에 → 시스템 콜 몇 번
write(fd, buf, 1000000);
표준 라이브러리의 버퍼링(printf·BufferedWriter)이 존재하는 이유가 이것이다.
네 가지 추상화
OS 가 하드웨어를 가려 주는 것
CPU → 프로세스 (혼자 쓰는 것처럼)
메모리 → 가상 주소 공간 (연속된 큰 공간인 것처럼)
디스크 → 파일 (이름 붙은 바이트 나열인 것처럼)
장치 → 파일 인터페이스 (리눅스는 거의 모든 것이 파일)
면접 함정
- ❌ "시스템 콜은 함수 호출과 같다" → 모드 전환이 끼어 수백 배 비싸다.
- ❌ "라이브러리 함수 = 시스템 콜" →
printf는 버퍼가 찰 때만write를 부른다.
vDSO — 시스템 콜을 안 하는 시스템 콜
gettimeofday(&tv, NULL); // 시간을 묻는다 — 매우 자주 불린다
매번 커널로 들어가면 비싸다
- 커널이 '시간 정보가 담긴 페이지' 를 유저 공간에 읽기 전용으로 매핑해 둔다
- 그 함수는 모드 전환 없이 그 페이지를 읽고 끝난다 (vDSO)
"자주 불리고 읽기만 하는 것" 을 커널 밖으로 빼는 최적화다
cat /proc/<pid>/maps | grep vdso
strace -e trace=all ./app 2>&1 | grep gettimeofday # 안 잡힌다 (진짜 시스템 콜이 아니므로)
배치로 줄이는 방향
-
readv / writev — 여러 버퍼를 한 번의 호출로 (scatter-gather)
-
sendfile — 디스크 → 소켓을 유저 공간을 거치지 않고 (zero-copy)
-
io_uring — 여러 요청을 링 버퍼에 쌓아 한 번에 제출한다
- 극단적으로는 시스템 콜 없이도 I/O 가 진행된다
-
방향이 일관된다 — 호출 횟수를 줄이고 복사를 줄인다