안동민 개발노트

본문 시작

커널과 시스템 콜

사용자 모드와 커널 모드의 권한 경계, 커널 구조의 차이, 시스템 콜이 하드웨어 요청을 처리하는 과정을 연결해 이해합니다.

커널(Kernel)은 하드웨어 제어와 자원 관리를 담당하는 운영체제의 상주 구성 요소입니다.

메모리에 항상 머물면서 프로세스 관리, 메모리 관리, 파일 시스템, 장치 제어를 수행합니다.

우리가 작성하는 프로그램은 직접 화면에 글자를 찍거나, 디스크에 파일을 쓰거나, 네트워크로 데이터를 보낼 수 없습니다.

일반 애플리케이션은 커널이 허용한 접근 범위 안에서 실행됩니다. 장치 메모리를 매핑하는 등의 인터페이스도 커널이 권한과 범위를 설정합니다.

왜 이런 구조가 필요한지, 커널은 어떤 형태로 설계될 수 있는지, 그리고 프로그램이 커널에 요청을 보내는 시스템 콜은 실제로 어떻게 동작하는지를 이 절에서 깊이 있게 다루겠습니다.


커널의 역할

커널은 프로그램의 자원 접근을 통제하는 핵심 경계입니다.

애플리케이션이 파일을 읽거나, 네트워크 패킷을 보내거나, 새 프로세스를 생성하려면, 반드시 커널을 거쳐야 합니다.

이를 위해 CPU는 두 가지 실행 모드를 제공합니다.

이 모드 분리는 현대 OS의 안정성을 지탱하는 가장 근본적인 메커니즘입니다.

사용자 모드(User Mode): 일반 프로그램이 실행되는 모드입니다.

허용되지 않은 특권 명령이나 메모리 접근은 CPU 예외를 일으킬 수 있습니다. 커널은 원인에 따라 복구하거나 프로세스에 신호를 보냅니다. 모든 예외가 곧 강제 종료인 것은 아닙니다.

우리가 보는 "Segmentation Fault"나 "Access Violation" 같은 에러가 바로 이 메커니즘의 결과입니다.

커널 모드(Kernel Mode): 운영체제 커널이 실행되는 모드입니다.

모든 하드웨어와 메모리에 접근할 수 있는 특권 모드입니다.

높은 특권으로 명령을 실행하지만 페이지 매핑, 하드웨어 보호 기능과 가상화 정책 등의 제약은 여전히 적용됩니다.

이 분리가 없다면 어떻게 될까요?

웹 브라우저의 버그가 그래픽 카드의 레지스터를 덮어써서 화면이 깨지고, 메일 프로그램이 디스크 컨트롤러를 직접 조작하다가 파일 시스템을 통째로 날릴 수 있습니다.

실제로 MS-DOS 시절에는 이런 일이 가능했고, 하나의 프로그램 버그가 전체 시스템을 마비시키는 일이 빈번했습니다.

x86 CPU는 이 개념을 좀 더 세분화하여 링(Ring)이라는 4단계 보호 수준을 제공합니다.

Ring 0이 가장 높은 특권(커널 모드)이고, Ring 3이 가장 낮은 특권(사용자 모드)입니다.

대부분의 OS는 Ring 0과 Ring 3만 사용하고, Ring 1과 Ring 2는 사용하지 않습니다.

가상화를 설명할 때 하이퍼바이저를 관용적으로 Ring -1이라고 부르기도 합니다. 실제 x86의 새로운 음수 링은 아니며 VMX root/non-root와 기존 특권 링을 구분합니다.

13장에서 가상화를 다룰 때 다시 만나게 됩니다.

ARM 프로세서(스마트폰, Apple Silicon)에서는 EL0(사용자), EL1(커널), EL2(하이퍼바이저), EL3(보안 모니터)의 4단계 Exception Level을 사용합니다.

이름은 다르지만 핵심 원리 — 낮은 단계에서 높은 단계의 기능을 직접 호출할 수 없고, 정해진 인터페이스(시스템 콜)를 통해야 한다 — 는 동일합니다.

커널은 이 권한 경계를 기준으로 자원 접근을 통제하고, 실패가 시스템 전체로 번지지 않도록 격리합니다.


커널의 종류

커널을 어떤 구조로 설계하느냐에 따라 성능, 안정성, 확장성이 크게 달라집니다.

크게 세 가지 접근법이 있습니다.

모놀리식 커널 (Monolithic Kernel)

모놀리식 커널은 모든 OS 서비스 — 프로세스 관리, 메모리 관리, 파일 시스템, 네트워크 스택, 디바이스 드라이버 — 가 하나의 커다란 커널 모드 이미지 안에서 동작하는 구조입니다.

Linux, 전통적 UNIX가 이 방식입니다.

장점은 성능입니다.

파일 시스템이 디스크 드라이버를 호출할 때, 같은 주소 공간에 있으므로 일반 함수 호출과 동일한 비용입니다.

컨텍스트 스위칭이나 메시지 복사가 필요 없습니다.

단점은 안정성입니다.

디바이스 드라이버에 버그가 있으면 — 예를 들어 잘못된 포인터로 메모리를 덮어쓰면 — 같은 주소 공간의 커널 전체가 오염됩니다.

드라이버는 커널과 같은 특권으로 실행하므로 잘못된 메모리 접근이 큰 영향을 줄 수 있습니다.

커널 전체가 한 주소 공간에 있기 때문에, 드라이버 하나의 버그가 커널 패닉(시스템 전체 중단)으로 이어질 수 있습니다.

Linux는 배포·확장의 유연성을 위해 로더블 커널 모듈(Loadable Kernel Module, LKM)을 제공합니다.

필요한 드라이버를 런타임에 커널에 삽입하거나 제거할 수 있습니다.

모듈 자체는 여전히 커널 모드에서 동작하므로 안정성 문제가 해결되는 것은 아니지만, 모든 드라이버를 커널에 정적으로 컴파일하지 않아도 되므로 유연성이 높아집니다.

Linux 커널 모듈의 기본 구조
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void) {
    printk(KERN_INFO "Hello, Kernel!\n");
    return 0;
}

static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye, Kernel!\n");
}

module_init(hello_init);
module_exit(hello_exit);

insmod로 이 모듈을 커널에 삽입하면 hello_init이 호출되고, rmmod로 제거하면 hello_exit이 호출됩니다.

GPU 드라이버, 파일 시스템, 네트워크 프로토콜 같은 것들이 이런 모듈 형태로 관리됩니다.

마이크로커널 (Microkernel)

마이크로커널은 정반대의 철학입니다.

커널에는 정말 최소한의 기능만 남깁니다 — 프로세스 간 통신(IPC), 기본 스케줄링, 메모리 주소 공간 관리 정도입니다.

파일 시스템, 네트워크 스택, 디바이스 드라이버 등은 모두 사용자 모드의 별도 프로세스(서버)로 분리합니다.

Minix, QNX, L4 계열이 마이크로커널 구조입니다.

QNX는 자동차, 의료 기기, 원자력 발전소 등 미션 크리티컬 시스템에서 사용되는데, 그 이유가 안정성입니다.

파일 시스템 서버를 별도 주소 공간에 두면 커널 메모리 오염을 제한할 수 있습니다. 다만 해당 서버의 이용자는 영향을 받으며, 재시작·상태 복구에는 별도 설계가 필요합니다.

자동차의 브레이크 제어 소프트웨어가 GPS 네비게이션의 버그 때문에 멈추면 안 되니까요.

단점은 성능입니다.

파일을 읽으려면 애플리케이션 → 커널(IPC) → 파일 시스템 서버 → 커널(IPC) → 디스크 드라이버 서버 → 커널 → 파일 시스템 서버 → 커널 → 애플리케이션, 이렇게 여러 번의 IPC와 모드 전환이 발생합니다.

모놀리식에서는 단 한 번의 시스템 콜이면 되는 작업이 여러 번의 컨텍스트 스위칭을 거치므로, 순수 성능에서는 불리합니다.

1990년대 리누스 토르발스(Linux)와 앤드류 타넨바움(Minix)이 이 주제로 유명한 논쟁을 벌였습니다.

타넨바움은 마이크로커널이 기술적으로 우월하며, 모놀리식은 퇴보라고 주장했고, 리누스는 현실적으로 모놀리식이 빠르고 실용적이라고 반박했습니다.

30년이 지난 지금, 범용 OS에서는 모놀리식(Linux)이 압도적으로 지배하고 있지만, 안전이 최우선인 임베디드 분야에서는 마이크로커널이 여전히 중요한 위치를 차지하고 있습니다.

하이브리드 커널 (Hybrid Kernel)

하이브리드 커널은 두 방식의 장점을 취합하려는 시도입니다.

Windows NT 커널, macOS의 XNU 커널이 이 구조입니다.

기본적으로는 마이크로커널 철학(모듈 분리, 잘 정의된 인터페이스)을 따르되, 성능이 중요한 구성 요소(그래픽 서브시스템, 네트워크 스택 등)는 커널 모드에서 실행합니다.

XNU는 X is Not UNIX를 뜻합니다.

CMU의 Mach 마이크로커널과 BSD 유닉스 코드를 결합한 구조입니다.

Mach 부분이 프로세스 관리와 IPC를 담당하고, BSD 부분이 파일 시스템과 네트워크를 담당합니다.

그런데 BSD 코드가 커널 모드에서 직접 실행되므로, 순수한 마이크로커널이라고 보기는 어렵습니다.

구조·예서비스 배치와 주요 비용
모놀리식: Linux·전통 UNIX많은 서비스가 커널 주소 공간에서 함수로 연결됩니다. 모듈 확장이 쉽지만 잘못된 메모리 접근이 커널에 영향을 줄 수 있습니다.
마이크로커널: QNX·MINIX·L4 계열최소 기능을 커널에 두고 서비스를 분리합니다. IPC 비용·프로토콜과 실패 복구를 설계해야 합니다.
하이브리드: Windows NT·XNU기능별로 커널·사용자 공간 배치를 절충합니다. 성능·안정성은 구체적 구현과 작업 부하에 따라 달라집니다.

커널 구조의 차이는 결국 OS 서비스가 어디서 실행되고, 실패가 어디까지 번지는지를 결정합니다.


시스템 콜의 동작 원리

시스템 콜(System Call)은 사용자 모드의 프로그램이 커널 모드의 OS 서비스를 요청하는 유일한 공식 인터페이스입니다.

"사용자 모드에서 커널 모드로 넘어가는 관문"이라고 이해하면 됩니다.

사용자 모드 프로세스는 하드웨어와 커널 내부 자료구조에 직접 접근하지 않습니다.

파일 읽기, 프로세스 생성, 네트워크 송신처럼 권한이 필요한 작업은 시스템 콜을 통해 커널에 요청하고, 커널은 권한과 인자를 검사한 뒤 커널 모드에서 작업을 수행합니다.

프로그램이 파일을 읽으려 할 때의 과정을 단계별로 따라가 보겠습니다.

  1. 프로그램이 read() 함수를 호출합니다.
  2. C 라이브러리(glibc)의 래퍼 함수가 시스템 콜 번호(x86_64에서 read는 0번)와 인자(파일 디스크립터, 버퍼 주소, 크기)를 CPU 레지스터에 설정합니다. rax에 시스템 콜 번호, rdi, rsi, rdx에 각각 인자를 넣습니다.
  3. syscall 명령어(x86_64)를 실행합니다. 이 특수 명령어가 CPU에게 모드 전환을 해달라고 알립니다.
  4. x86-64의 syscall이 설정된 커널 진입점으로 이동합니다. 이 명령 자체는 rsp를 바꾸거나 스택에 상태를 저장하지 않습니다. 커널 진입 코드가 사용자 스택 포인터 보존과 커널 스택 준비를 수행합니다.
  5. 커널은 시스템 콜 번호에 맞는 read 처리 경로로 보냅니다. 실제 디스패치 코드와 내부 함수명은 커널 버전에 따라 달라질 수 있습니다.
  6. 파일 디스크립터와 접근 모드를 확인하고 파일 종류에 맞는 읽기 경로를 실행합니다. 데이터가 페이지 캐시에 있으면 디스크 접근 없이 반환할 수 있으며 파이프·소켓도 다른 경로를 사용합니다.
  7. 반환값을 준비하고 사용자 모드로 돌아갑니다. x86-64에서는 조건에 따라 sysret 또는 iret 경로를 사용합니다.
  8. glibc 래퍼 함수가 rax의 값을 확인하여, 음수면 errno를 설정하고 -1을 반환합니다.

경계 진입에는 일반 함수 호출에 없는 상태 전환 비용이 있습니다. 실제 지연은 CPU·보안 완화·커널 경로와 I/O 대기 여부에 따라 달라지므로 이 설명을 read 전체의 고정 시간으로 해석하지 않습니다.

이 때문에 시스템 콜을 줄이는 것이 성능 최적화라는 말이 나옵니다.

Linux read의 사용자·커널 경계
read 요청과 결과 반환실선은 read의 커널 요청, 점선은 결과 반환입니다. x86-64 syscall 명령 자체가 스택 포인터를 바꾸지는 않으며 커널 진입 코드가 스택을 준비합니다.사용자 공간앱 · glibc커널 공간파일 읽기 처리read(fd, buf, count)진입 코드가 커널 스택 준비fd · 접근 모드 · 사용자 버퍼 확인파일 종류에 맞는 읽기 처리바이트 수 또는 커널 오류 코드glibc: 성공값 또는 -1과 errno
read 요청과 결과 반환실선은 read의 커널 요청, 점선은 결과 반환입니다. x86-64 syscall 명령 자체가 스택 포인터를 바꾸지는 않으며 커널 진입 코드가 스택을 준비합니다.사용자 공간앱 · glibc커널 공간파일 읽기 처리read(fd, buf, count)진입 코드가 커널 스택 준비fd · 접근 모드 · 사용자 버퍼 확인파일 종류에 맞는 읽기 처리바이트 수 또는 커널 오류 코드glibc: 성공값 또는 -1과 errno

일반적인 read 호출의 개념도입니다. 데이터는 파일 종류에 따라 페이지 캐시·장치·파이프·소켓 등에서 오며, 모든 read가 디스크 I/O를 발생시키거나 다른 프로세스로 전환되지는 않습니다.

x86-64 Linux 진입 코드

실제로 시스템 콜 오버헤드를 줄이기 위한 다양한 기법이 존재합니다.

Linux의 vDSO(virtual Dynamic Shared Object)는 일부 자주 사용되는 시스템 콜(gettimeofday 등)을 커널 모드 전환 없이 사용자 모드에서 바로 실행할 수 있게 합니다.

또한 io_uring(Linux 5.1+)은 커널과 사용자 공간이 공유하는 링 버퍼를 통해 I/O 요청을 배치 처리하여, 시스템 콜 횟수를 크게 줄입니다.

시스템 콜을 직접 확인하고 싶다면, strace(Linux) 명령어를 사용할 수 있습니다.

strace로 시스템 콜 추적하기
// hello.c
#include <stdio.h>

int main() {
    printf("Hello, OS!\n");
    return 0;
}
컴파일 후 strace 실행
gcc -o hello hello.c
strace ./hello

출력에서 write(1, "Hello, OS!\n", 11) 같은 시스템 콜을 직접 확인할 수 있습니다.

printf가 내부적으로 write 시스템 콜을 호출한다는 사실을 눈으로 보는 것입니다.

디버깅 현장에서 프로그램이 어떤 파일을 열고, 어떤 소켓에 연결하고, 어디서 블로킹되는지를 strace로 추적하는 일이 매우 잦으므로, 기억해두면 좋습니다.


주요 시스템 콜

운영체제의 기능별로 대표적인 시스템 콜을 살펴보겠습니다.

각 시스템 콜이 어떤 상황에서 사용되는지를 이해하면, 이후 장에서 프로세스, 파일, 메모리를 다룰 때 기초가 됩니다.

프로세스 관리

  • fork() — 현재 프로세스를 복제하여 새 프로세스(자식)를 생성합니다. 부모와 자식은 동일한 코드와 데이터를 갖지만, 서로 독립적인 프로세스입니다.
  • exec() — 현재 프로세스의 코드와 데이터를 새 프로그램으로 교체합니다. fork()로 만든 자식에서 exec()을 호출하여 다른프로그램을 실행하는 것이 일반적인 패턴입니다.
  • wait() — 자식 프로세스의 종료를 기다립니다. 자식의 종료 상태를 수거하지 않으면 좀비 프로세스가 됩니다.
  • exit() — 프로세스를 종료합니다. 열린 파일을 닫고, 메모리를 해제합니다.
fork()와 exec()의 조합
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        return 1;
    }
    if (pid == 0) {
        /* 자식 프로세스: 성공하면 돌아오지 않습니다. */
        execlp("ls", "ls", "-l", NULL);
        perror("execlp");
        _exit(127);
    }
    /* 부모 프로세스: 자식 종료 대기 */
    int status;
    pid_t waited;
    do {
        waited = waitpid(pid, &status, 0);
    } while (waited < 0 && errno == EINTR);
    if (waited < 0) {
        perror("waitpid");
        return 1;
    }
    if (WIFEXITED(status)) {
        printf("자식 종료, 상태: %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("자식 종료 신호: %d\n", WTERMSIG(status));
    }
    return 0;
}

fork·exec·wait의 기본 조합입니다. 실제 셸은 내장 명령, 파이프라인, 작업 제어 등에 따라 다른 경로도 사용합니다. waitpid와 종료 상태

3장에서 프로세스를 다룰 때 더 깊이 살펴보겠습니다.

파일 관리

UNIX의 모든 것은 파일이다 철학에 따라, 파일 관련 시스템 콜은 일반 파일뿐 아니라 디바이스, 파이프, 소켓에도 동일하게 사용됩니다.

  • open() — 파일을 열고 파일 디스크립터(File Descriptor)를 반환합니다. 파일 디스크립터는 정수형 핸들로, 0이 표준 입력(stdin), 1이 표준 출력(stdout), 2가 표준 에러(stderr)입니다.
  • read() — 파일 디스크립터에서 데이터를 읽어 버퍼에 저장합니다. 반환값은 실제로 읽은 바이트 수이며, 0이면 파일 끝(EOF)입니다.
  • write() — 버퍼의 데이터를 파일 디스크립터에 씁니다.
  • close() — 파일 디스크립터를 닫습니다. 닫지 않으면 프로세스당 열 수 있는 파일 개수 제한(보통 1024)에 걸릴 수 있습니다.
  • lseek() — 파일 내의 읽기/쓰기 위치를 변경합니다.
파일 읽기 시스템 콜 사용 예
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open("/etc/hostname", O_RDONLY);
    if (fd < 0) {
        perror("open 실패");
        return 1;
    }

    char buf[256];
    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    if (n > 0) {
        buf[n] = '\0';
        printf("호스트명: %s", buf);
    }

    close(fd);
    return 0;
}

파일 읽기 예시는 단순히 read() 한 번으로 끝나는 것이 아니라, 파일 디스크립터 테이블, VFS, 사용자 버퍼 복사를 차례로 거칩니다.

메모리 관리

  • mmap() — 파일이나 장치를 프로세스의 가상 주소 공간에 매핑합니다. 파일의 내용을 메모리처럼 접근할 수 있어, 대용량 파일 처리에 유리합니다.
  • munmap() — mmap으로 매핑한 영역을 해제합니다.
  • brk() / sbrk() — 힙 영역의 크기를 확장하거나 축소합니다. malloc 구현은 기존 할당 영역을 재사용하거나 필요에 따라 brk·mmap 등을 사용할 수 있습니다.

프로그래머는 보통 라이브러리 래퍼나 언어 런타임을 통해 시스템 콜을 사용합니다.

대부분 C 라이브러리(glibc, musl)나 언어 런타임이 시스템 콜을 감싸서 더 편리하고 이식 가능한 API를 제공합니다.

CPython의 파일 I/O는 _io.FileIO에서 OS의 파일 디스크립터 인터페이스를 사용합니다. POSIX 경로에서는 open을 호출하며 반드시 C stdio의 fopen을 거치는 것은 아닙니다. 텍스트·버퍼 계층은 그 위에 놓입니다. CPython FileIO 구현

하지만 시스템 프로그래밍, 고성능 서버, 컨테이너 런타임 등을 개발할 때는 시스템 콜의 존재와 동작 원리를 반드시 알아야 합니다.

epoll, io_uring, clone, unshare 같은 시스템 콜은 표준 라이브러리가 직접 제공하지 않는 저수준 기능이기 때문입니다.


시스템 콜과 라이브러리 함수의 차이

이 구분은 혼동하기 쉬운 부분입니다.

printf()는 시스템 콜이 아닙니다.

C 라이브러리 함수입니다.

내부적으로 시스템 콜(write)을 호출하지만, printf 자체는 사용자 공간에서 서식 문자열을 처리하고 버퍼에 저장한 뒤, 버퍼가 차거나 flush·종료 등이 일어나면 쓰기를 수행합니다. 줄바꿈이 flush를 일으키는지는 줄 단위 버퍼링 여부에 따릅니다.

이 버퍼링 전략이 성능에 큰 차이를 만듭니다.

버퍼링이 묶어 주는 경우 printf 여러 번을 더 적은 시스템 콜로 처리할 수 있습니다.

반면 write를 100번 직접 호출하면, 시스템 콜도 100번 발생합니다.

성능 영향은 출력 크기·버퍼링 모드·장치에 따라 달라집니다.

Python에서도 마찬가지입니다.

print()를 호출할 때마다 즉시 시스템 콜이 발생하는 것이 아니라, 내부 버퍼에 데이터가 쌓이고 버퍼가 가득 차거나 flush 조건을 만족할 때 실제 쓰기가 수행됩니다. 줄바꿈 flush 여부는 출력 스트림 설정에 따라 달라집니다.

sys.stdout.flush()를 호출하면 버퍼를 강제로 비워 즉시 출력할 수 있습니다.

라이브러리 함수와 시스템 콜을 구분할 때는 "커널 경계를 넘는가"를 기준으로 보면 됩니다.


셸의 역할

셸(Shell)은 사용자가 OS와 상호작용하는 인터페이스입니다.

명령줄 셸(bash, zsh)에서 ls를 입력하면, 셸이 이를 해석하여 적절한 시스템 콜을 호출합니다.

셸은 사용자의 텍스트 명령을 시스템 콜 시퀀스로 번역하는 통역자입니다.

셸이 ls 명령을 처리하는 과정은 이렇습니다.

  1. 셸이 fork()로 자식 프로세스를 생성합니다.
  2. 자식 프로세스가 exec()으로 /usr/bin/ls 프로그램을 실행합니다.
  3. ls가 opendir·readdir 같은 라이브러리 인터페이스를 사용합니다. Linux에서는 그 아래에서 openat·getdents64 등의 시스템 콜이 사용될 수 있습니다.
  4. 결과를 write()로 표준 출력(파일 디스크립터 1)에 출력합니다.
  5. ls가 exit()으로 종료합니다.
  6. 부모 셸은 wait()으로 자식의 종료 상태를 수거하고, 다시 프롬프트를 표시합니다.

이 과정에서 파이프(|)가 사용되면 더 흥미로워집니다.

ls | grep .txt를 입력하면, 셸은 pipe() 시스템 콜로 파이프를 만들고, fork()를 두 번 호출하여 ls와 grep을 각각 실행합니다.

ls의 표준 출력이 파이프로 연결되고, grep의 표준 입력이 같은 파이프에 연결됩니다.

ls가 출력한 데이터가 파이프를 통해 grep에게 전달되는 것입니다.

셸 스크립트를 작성할 때 $?로 직전 명령의 종료 코드를 확인하는 것, 파이프로 명령을 연결하는 것, 리다이렉션(>, <)으로 입출력을 변경하는 것 — 이 모든 것이 시스템 콜의 조합입니다.

셸은 이 조합을 사람이 읽기 편한 텍스트 인터페이스로 포장한 것뿐입니다.

프로그램 실행 문제를 추적할 때는 라이브러리, 트랩 진입, 커널 검사, 셸의 fork/exec 조합을 분리해서 보면 원인이 빨리 좁혀집니다.


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

커널과 시스템 콜은 OS의 거의 모든 개념이 수렴하는 지점입니다.

프로세스를 만드는 것, 메모리를 할당하는 것, 파일을 읽는 것, 네트워크 통신을 하는 것 — 모든 것이 시스템 콜을 통해 커널에 요청됩니다.

3장에서 다룰 프로세스의 fork()/exec(), 8장에서 다룰 메모리의 mmap()/brk(), 10장에서 다룰 파일 시스템의 open()/read()/write() 모두 여기서 소개한 시스템 콜의 확장입니다.

다음 장에서는 OS가 관리하는 컴퓨터 하드웨어의 구조 — CPU, 메모리, I/O 장치가 어떻게 연결되어 있고, OS가 각각을 어떻게 제어하는지 — 를 살펴보겠습니다.

시스템 콜은 일반 프로그램이 커널 권한이 필요한 작업을 안전하게 요청하기 위한 경계입니다.