Step 67. 운영체제의 역할 — 모든 부탁은 커널에게

Step 67. 운영체제의 역할 — 모든 부탁은 커널에게

Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간

전제: Step 56~66을 마쳤다. C 프로그램을 컴파일할 수 있고, 실행 파일의 구조와 메모리 지도를 안다.

  • 준비물: 리눅스 터미널(WSL 또는 우분투)과 gcc. 참고용 도구 strace가 설치되어 있으면 더 좋지만, 없어도 오늘 실험 전부를 다른 방법으로 확인할 수 있게 준비했습니다.
  • 주의: 오늘 실습은 100% 안전합니다. 일부러 망가지는 프로그램(badboy)을 하나 만들지만, 망가지는 것은 그 프로그램 자신뿐입니다.

우리가 만든 프로그램들은 파일을 읽고, 화면에 글자를 찍고, 메모리를 빌렸습니다. 그런데 생각해 보세요. 디스크는 어떻게 읽는 걸까요? 화면에는 어떻게 그리는 걸까요? 우리 코드 어디에도 "디스크를 움직여라" 같은 명령은 없었습니다. 정답은, 우리 프로그램이 그 일을 직접 하지 않는다는 것입니다. 전부 운영체제 — 정확히는 그 심장인 커널 — 에게 "부탁"한 것입니다. 오늘은 그 부탁의 통로인 시스템 콜을 직접 불러 보고, 부탁의 비용까지 재 봅니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • 커널이 무엇이고 왜 프로그램이 하드웨어를 직접 못 건드리는지 설명한다
  • 유저 모드와 커널 모드의 구분을 말한다
  • 시스템 콜이 무엇인지 예시와 함께 설명하고, 직접 호출해 본다
  • "라이브러리 함수 → 시스템 콜 → 커널" 삼단 흐름을 그린다
  • strace 출력을 읽고 프로그램의 부탁을 추적한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 C 언어 + 리눅스 터미널 (WSL 우분투 24.04에서 실측)
오늘의 명령어·도구 strace(시스템 콜 추적 — 미설치 환경에서는 출력 예시로 학습), grep으로 시스템 콜 번호표 보기, /proc/self/fd로 파일 번호표 확인, time으로 부탁 비용 측정
오늘의 C 함수 syscall(), write(), printf(), fopen()/fgets(), fileno()
필요한 개념 커널, 유저 모드/커널 모드, 시스템 콜, 파일 디스크립터(번호표), 세그멘테이션 폴트
오늘의 산출물 시스템 콜 추적 일지 — 세 프로그램의 "함수 → 시스템 콜" 대조표

2-1. 커널 — 운영체제의 심장

운영체제의 핵심 부분을 커널(kernel)이라고 합니다. 메모리를 나눠 주고, 파일을 읽고, 네트워크를 다루고, 프로그램들 사이의 순서를 정하는 컴퓨터의 총괄 관리자입니다. 이 관리자만이 하드웨어를 직접 만질 수 있습니다.

2-2. 유저 모드와 커널 모드 — 두 개의 신분

CPU에는 두 가지 신분 모드가 있습니다. 우리 프로그램은 유저 모드(낮은 신분)에서 돌고, 커널은 커널 모드(높은 신분)에서 돕니다. 유저 모드의 프로그램은 할 수 있는 일이 제한됩니다. 하드웨어를 직접 건드리거나 남의 메모리를 들여다보는 명령은 실행이 금지됩니다. 이 신분 구분은 프로그램의 선의에 기대는 것이 아니라, CPU라는 하드웨어가 직접 강제합니다.

2-3. 시스템 콜 — 문지방의 사용법

그럼 유저 모드의 프로그램이 파일을 읽고 싶으면? 문지방을 통과하는 정식 절차가 있습니다. 시스템 콜(system call)입니다. "커널님, 이 파일 좀 읽어 주세요"라고 정해진 방법으로 요청하면, CPU가 잠시 커널 모드로 바뀌어 커널이 일을 하고, 결과를 들고 유저 모드로 돌아옵니다. 파일 열기(openat), 읽기(read), 쓰기(write), 메모리 빌리기(brk, mmap) 등 모든 대외 활동이 이 통로를 지납니다.

2-4. 왜 문지방이 있는가 — 가장 오래된 울타리

만약 이 문지방이 없다면, 어떤 프로그램이든 하드웨어를 마음대로 하고 남의 메모리를 봅니다. 한 프로그램의 버그나 악의가 컴퓨터 전체를 망가뜨리죠. 문지방 덕분에 악성코드조차 "부탁"을 해야 하고, 커널은 그 부탁을 검사할 수 있습니다. 그리고 이 구조를 아는 사람은 다음 질문을 하게 됩니다. "그럼 권한 상승(privilege escalation) 공격은 이 문지방을 어떻게 넘는 거지?" 좋은 질문입니다. 그것이 나중의 공부 주제이고, 오늘은 문지방의 존재를 확인하는 것으로 충분합니다.


3. 따라 하기

3-1. 시스템 콜 번호표 엿보기

커널에게의 부탁에는 번호가 매겨져 있습니다. 리눅스에 내장된 번호표 파일을 직접 열어 봅시다.

입력

grep -E "define __NR_(write|read|openat|brk|mmap|exit_group) " /usr/include/x86_64-linux-gnu/asm/unistd_64.h

출력 (2026-09-09 실측):

#define __NR_read 0
#define __NR_write 1
#define __NR_mmap 9
#define __NR_brk 12
#define __NR_exit_group 231
#define __NR_openat 257

읽는 법: __NR_write 1은 "써 주세요" 부탁의 번호가 1번이라는 뜻입니다. 부탁은 이름이 아니라 번호로 커널에 전달됩니다. 이 표가 바로 커널의 업무 창구 목록입니다.

: 시스템 콜이 추상적인 개념이 아니라, 번호가 매겨진 실제 창구라는 것을 확인하는 첫 단계입니다.

3-2. 커널에게 직접 부탁하기 — 세 가지 방법

이번에는 그 창구를 직접 두드려 봅니다. 같은 "화면에 써 주세요"를 세 층에서 각각 불러 봅니다.

입력 (direct.c)

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

int main(void) {
    syscall(SYS_write, 1, "syscall로 직접: 커널님, 써 주세요\n", 46);
    write(1, "write 함수로: 커널님, 써 주세요\n", 42);
    printf("printf로: 커널님, (속으로는) 써 주세요\n");
    return 0;
}

입력

gcc -g -O0 direct.c -o direct && ./direct

출력 (2026-09-09 실측):

syscall로 직접: 커널님, 써 주세요
write 함수로: 커널님, 써 주세요
printf로: 커널님, (속으로는) 써 주세요

읽는 법: 세 줄 전부 화면에 나왔습니다. syscall(SYS_write, ...)는 3-1의 번호표에서 찾은 창구(write)를 직접 두드린 것입니다. write()는 그 창구를 감싼 얇은 함수입니다. printf()는 서식 처리와 출력 모으기까지 해 주는 두꺼운 포장지이지만, 마지막엔 결국 속으로 write를 부릅니다. 첫 번째 인자 1은 "1번 출력 창구(화면)"라는 뜻인데, 다음 실험에서 이 번호표의 정체를 확인합니다.

: "라이브러리 함수 → 시스템 콜 → 커널"이라는 삼단 흐름을 코드로 직접 만진 것이 오늘의 핵심입니다. printf의 진짜 얼굴을 본 것입니다.

3-3. 파일 번호표 — 0, 1, 2, 그리고 3

입력 (reader.c)

#include <stdio.h>

int main(void) {
    FILE *f = fopen("test.txt", "r");
    char buf[100];
    fgets(buf, 100, f);
    printf("읽은 내용: %s", buf);
    printf("파일 번호표(fd): %d\n", fileno(f));
    fclose(f);
    return 0;
}

준비와 실행

echo "안녕하세요" > test.txt
gcc -g -O0 reader.c -o reader
./reader
ls -l /proc/self/fd/

출력 (2026-09-09 실측):

읽은 내용: 안녕하세요
파일 번호표(fd): 3
lr-x------ 1 root root 64 ... 0 -> pipe:[5984]
l-wx------ 1 root root 64 ... 1 -> pipe:[5985]
l-wx------ 1 root root 64 ... 2 -> pipe:[5986]
lr-x------ 1 root root 64 ... 3 -> /proc/.../fd

읽는 법: fopen으로 연 파일이 번호표 3번을 받았습니다. /proc/self/fd(지금 실행 중인 프로세스의 번호표함)를 열어 보면 그 이유가 보입니다 — 0(표준 입력), 1(표준 출력), 2(오류 출력)는 모든 프로세스가 태어날 때부터 배정받는 번호이고, 우리가 연 파일은 그다음 번호인 3부터 받습니다. 이 번호를 파일 디스크립터(file descriptor, 파일 번호표)라고 합니다.

: "커널에게 부탁할 때는 무엇을 할지(시스템 콜 번호)와 어디에 할지(파일 번호표)를 함께 전달한다"는 구조가 보이는 단계입니다. write(1, …)의 1이 "화면"을 가리켰던 것도 이 약속 때문입니다.

3-4. strace — 부탁의 근무 기록 엿보기 (출력 예시)

부탁을 전부 기록해 보여 주는 도구가 strace입니다. 참고로 이 책의 실측 랩(WSL)에는 strace가 설치되어 있지 않고, 이 책의 규칙상 실습 중 설치를 하지 않기로 했으므로 아래 출력은 실측이 아닌 "출력 예시"입니다. 여러분의 환경에 strace 명령이 있다면(없으면 sudo apt install strace로 설치할 수 있는 환경이라면) 직접 따라 해 보세요.

입력 (예시)

strace -e trace=write ./hello

출력 예시 (일반적인 우분투 환경에서의 모습):

write(1, "Hello, C!\n", 10)            = 10
+++ exited with 0 +++

읽는 법: strace는 프로그램이 커널에게 한 부탁들을 순서대로 보여 줍니다. -e trace=write는 "write 부탁만 보여 줘"라는 필터입니다. write(1, "Hello, C!\n", 10) = 10은 "1번(화면)에 열 글자를 써 주세요 → 열 글자 썼습니다"라는 부탁과 답입니다. printf가 화면에 글자를 찍는 게 아니었습니다. printf가 준비하고, 커널이 write로 찍습니다. 옵션 없이 strace ./hello를 실행하면 시작 준비 부탁까지 수십 줄이 나오는데, 그때는 끝부분부터 거꾸로 읽으세요 — 우리 코드의 부탁은 대개 뒤쪽에 있습니다.

: strace가 없어도 3-2에서 우리는 같은 사실을 코드로 증명했습니다(printf와 syscall이 같은 화면에 닿았죠). strace는 그 삼단 흐름을 "밖에서 관찰"하는 도구일 뿐, 원리는 이미 손에 있습니다.

3-5. 문지방 넘기 — 금지된 일은 어떻게 막히나

부탁(시스템 콜)이 아니라 포인터로 직접 남의 땅을 찌르면 어떻게 될까요?

입력 (badboy.c)

#include <stdio.h>

int main(void) {
    int *p = (int *)0x1;
    *p = 42;
    printf("이 줄이 보일까요?\n");
    return 0;
}

컴파일과 실행

gcc badboy.c -o badboy
./badboy; echo "종료 코드: $?"

출력 (2026-09-09 실측):

Segmentation fault (core dumped)
종료 코드: 139

읽는 법: 주소 1번지(내 땅이 아닌 곳)에 쓰려 했더니 즉시 퇴장당했습니다. "이 줄이 보일까요?"는 찍히지 않았습니다 — 시스템 콜을 부를 기회도 없이, 메모리 관리 장치가 "유저 모드의 네 땅이 아니다"라고 막은 것입니다. 종료 코드 139는 128 + 11로, 11번 신호(SIGSEGV)로 죽었다는 뜻입니다. 문지방은 부탁의 통로(시스템 콜)와 직접 차단(이런 메모리 보호), 두 겹으로 되어 있습니다.

: 프로그램이 "왜 죽었지?" 할 때 대답은 거의 항상 마지막 줄과 종료 코드에 있습니다. 종료 부호를 읽는 습관은 디버깅의 출발점입니다.

3-6. 부탁에도 비용이 든다 — 시간으로 재 보기

문지방을 넘는 것은 공짜가 아닙니다. 유저 모드에서 커널 모드로 바뀌었다 돌아오는 데 시간이 듭니다. 얼마나 드는지 직접 재 봅시다. 같은 글자 백만 개를, 한 번은 시스템 콜 백만 번으로, 한 번은 버퍼에 모았다가 나눠 내는 방식으로 써 봅니다.

입력 (cost.c — 1바이트씩 시스템 콜)

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(void) {
    int fd = open("/dev/null", O_WRONLY);
    char c = 'x';
    for (int i = 0; i < 1000000; i++) write(fd, &c, 1);
    close(fd);
    return 0;
}

입력 (cost2.c — 버퍼에 모아 쓰기)

#include <stdio.h>

int main(void) {
    FILE *f = fopen("/dev/null", "w");
    for (int i = 0; i < 1000000; i++) fputc('x', f);
    fclose(f);
    return 0;
}

컴파일과 시간 측정

gcc -O2 cost.c -o cost && gcc -O2 cost2.c -o cost2
time ./cost
time ./cost2

출력 (2026-09-09 실측):

== 1바이트씩 write(시스템 콜) 100만 회 ==
real    0m0.071s
user    0m0.029s
sys     0m0.042s
== 버퍼에 모아 쓰기(fputc) 100만 회 ==
real    0m0.002s
user    0m0.002s
sys     0m0.000s

읽는 법: 같은 백만 글자인데 0.071초와 0.002초, 약 35배 차이가 났습니다. 특히 sys 열을 보세요 — write 버전은 0.042초를 커널 모드에서 보냈고(sys = 커널에서 일한 시간), 버퍼 버전은 sys가 0입니다. fputc은 메모리의 임시 보관함(버퍼)에 모아 두었다가 fclose 때 한 번에 write하기 때문입니다. printf가 속으로 출력을 모아 두는 이유가 바로 이 비용입니다.

: 문지방의 존재가 보안뿐 아니라 성능의 세계도 설명합니다. "시스템 콜을 몇 번 부르는가"가 성능 튜닝의 기본 질문인 이유를 몸으로 확인한 것입니다.


4. 미션과 연습문제

미션 — 시스템 콜 추적 일지

  1. hello, reader, cost 세 프로그램(오늘 만든 것)에 대해 표를 만듭니다. 표의 열: 프로그램, 우리가 쓴 함수, 실제 시스템 콜, 하는 일. (오늘 배운 번호표·코드·실행 결과를 근거로 채웁니다)
  2. strace가 있는 환경이라면 strace -e trace=openat,read,write ./reader를 실행해, 표에 적은 예측이 맞는지 검증합니다. 없는 환경이라면 일지에 그 사실을 적고 출력 예시와 비교합니다
  3. reader 실행에서 파일 번호표 0, 1, 2, 3이 각각 어디에 쓰였는지 표시합니다
  4. "printf("안녕")이 화면에 나오기까지의 여정"을 라이브러리 → 시스템 콜 → 커널 그림으로 그립니다
  5. 노트에 답합니다: "왜 운영체제는 프로그램이 하드웨어를 직접 만지게 두지 않는가 — 세 가지 이유."

연습문제

문제 1. printf와 write의 관계를 "포장지와 부탁"이라는 구분으로 설명해 보세요.

문제 2. ./badboySegmentation fault로 죽고 종료 코드 139를 남겼습니다. 139를 어떻게 해석하며, printf 줄이 찍히지 않은 이유는 무엇인가요?

문제 3. fopen으로 연 파일이 번호표 3번을 받는 이유를 0, 1, 2번의 정체와 함께 설명해 보세요.

문제 4. 같은 백만 글자를 쓰는데 1바이트씩 write는 0.071초, fputc은 0.002초가 걸렸습니다(2026-09-09 실측). 무엇이 이 차이를 만들었으며, time 출력의 어느 열이 그 증거인가요?


5. 모범 답안과 완료 기준

미션 모범 답안

추적 일지의 표 예시 (2026-09-09 실측 근거):

프로그램 우리가 쓴 함수 실제 시스템 콜 하는 일
hello printf write (1번) 화면(1번 번호표)에 글자 쓰기
reader fopen openat (257번) test.txt 열고 번호표 3 받기
reader fgets read (0번) 3번 번호표에서 읽기
reader printf write (1번) 읽은 내용을 화면에 쓰기
cost write write (1번) /dev/null에 1바이트씩 백만 번

파일 번호표 정리: 0 = 표준 입력, 1 = 표준 출력(화면), 2 = 오류 출력 — 모두 태어날 때 배정. 3 = reader가 fopen으로 연 test.txt (2026-09-09 실측으로 fileno가 3 확인).

그림: printf("안녕") → C 라이브러리가 서식을 만들고 버퍼에 모음 → write(1, "안녕...", ...) 시스템 콜 → CPU가 커널 모드로 전환 → 커널이 화면 출력 처리 → 결과를 들고 유저 모드로 복귀.

세 가지 이유 예시: ① 안전 — 한 프로그램의 버그가 전체를 망가뜨리지 못하게. ② 격리 — 남의 메모리와 파일을 함부로 못 보게. ③ 검문 — 모든 대외 활동을 커널이 검사하고 기록할 수 있게(백신·EDR 같은 감시 도구가 바로 이 통로를 지켜봅니다).

검증하는 법: ① 표의 시스템 콜 번호가 번호표 파일(unistd_64.h)의 번호와 일치하는가. ② 그림에 "커널 모드 전환"이 들어갔는가. ③ strace를 쓴 환경이라면 write 줄이 표의 예측과 일치했는가. 셋이 ‘예’이면 완성입니다.

연습문제 해답

문제 1 해답. printf는 부탁을 "준비하는" 포장지(서식 처리, 출력 모으기)이고, write가 커널에게 전달되는 부탁 "자체"입니다. 3-2 실험에서 syscall(SYS_write) 직접 호출과 printf가 같은 화면에 닿은 것이, 둘이 결국 같은 창구로 향한다는 증거입니다.

문제 2 해답. 139 = 128 + 11로, 11번 신호(SIGSEGV, 잘못된 메모리 접근)로 종료됐다는 뜻입니다. printf 줄이 안 찍힌 이유는, 그 줄에 도달하기 전인 *p = 42;에서 메모리 관리 장치가 즉시 차단했기 때문입니다 — 부탁(시스템 콜)을 부를 기회조차 없었습니다 (2026-09-09 실측).

문제 3 해답. 0, 1, 2번은 모든 프로세스가 시작할 때 자동 배정받는 번호로 각각 표준 입력·표준 출력·오류 출력이기 때문입니다. 그래서 프로세스가 새로 여는 파일은 그다음 빈 번호인 3부터 받습니다. /proc/self/fd에서 0, 1, 2가 이미 자리 잡고 있는 것을 실측으로 확인했습니다.

문제 4 해답. 시스템 콜 백만 번의 문지방 통과 비용이 차이를 만들었습니다. fputc은 메모리 버퍼에 모았다가 fclose 때 나눠서 write하므로 문지방을 거의 넘지 않습니다. 증거는 time 출력의 sys 열입니다 — write 버전은 sys 0.042초(커널에서 일한 시간), fputc 버전은 sys 0.000초 (2026-09-09 실측).

완료 기준 체크리스트

  • [ ] 커널과 유저 모드/커널 모드의 구분을 설명할 수 있다
  • [ ] 시스템 콜이 무엇인지 번호표와 함께 설명할 수 있다
  • [ ] syscall(SYS_write, …)로 커널에게 직접 부탁해 봤다
  • [ ] 파일 디스크립터 0, 1, 2, 3의 의미를 설명할 수 있다
  • [ ] 라이브러리 함수 → 시스템 콜 → 커널 흐름을 그릴 수 있다
  • [ ] 시스템 콜에 비용이 든다는 것을 time 실험으로 확인했다
  • [ ] 미션: 시스템 콜 추적 일지를 완성했다

6. 흔한 실수와 해결

벽 1. strace 출력이 너무 많아 패닉

증상 (출력 예시): 옵션 없이 strace를 쳤더니 수십 수백 줄이 쏟아집니다.

execve("./hello", ["./hello"], ...) = 0
brk(NULL)                               = 0x55...
... (수십 줄) ...
write(1, "Hello, C!\n", 10)            = 10
exit_group(0)                           = ?
+++ exited with 0 +++

원인: 프로그램은 시작할 때부터 수많은 부탁(라이브러리 준비, 메모리 설정 등)을 합니다.
해결: 처음에는 양해하고 건너뜁니다. -e trace=write처럼 관심 있는 부탁만 거르는 것이 정석이고, 출력의 "끝부분"부터 보세요. 우리 코드의 부탁은 대개 뒤쪽에 있습니다.

벽 2. 한글이 \354\225\210처럼 보인다

증상 (출력 예시): strace 출력에서 한글이 read(3, "\354\225\210\353\205\225...", 4096) = 15처럼 숫자 나열로 보입니다.
원인: strace는 바이트를 8진수로 표시합니다. 우리 눈에는 낯선 모양이지만 내용은 정상입니다.
해결: Step 50을 기억하세요. 한글은 UTF-8 바이트들입니다. "깨진 것"이 아니라 "바이트로 보이는 것"입니다. 문자열 뒤의 = 15(읽은 바이트 수)를 읽는 데 집중하세요.

벽 3. 시스템 콜과 라이브러리 함수를 같은 것으로 안다

증상: printf = 시스템 콜이라고 생각합니다.
원인: 포장지와 내용물의 혼동입니다.
해결: 삼단 흐름을 기억하세요. printf(라이브러리) → write(시스템 콜) → 커널. 3-2에서 syscall 직접 호출과 printf가 같은 결과를 낸 것이 둘이 다른 층이라는 증거입니다. 같은 창구에 도착하지만, 가는 길의 두께가 다릅니다.

벽 4. 왜 배우는지를 놓친다

증상: 명령은 되는데 "그래서 이게 보안과 무슨 상관?"이라는 생각이 듭니다.
원인: 아직 연결선을 못 그린 것뿐입니다.
해결: 이렇게 연결하세요. 모든 대외 활동이 커널을 지난다 → 커널은 검문소다 → 악성코드의 행동도 이 통로에 드러난다 → 그래서 방어 도구(백신, EDR)는 시스템 콜을 감시한다. 그리고 권한 상승 공격은 이 검문소의 신분 확인을 속이는 기술입니다. 오늘의 실험은 그 검문소의 근무 규칙을 읽는 훈련입니다.

벽 5. cost 실험의 시간이 예상과 다르다

증상: 책의 0.071초와 다르게 나옵니다 (더 크거나 더 작거나).
원인: 시간 측정은 CPU 성능과 그 순간의 시스템 부하에 따라 매번 달라집니다. 책의 수치는 2026-09-09 실측 한 번의 기록입니다.
해결: 절댓값이 아니라 "두 버전의 비율"과 "sys 열의 대비"를 보세요. 여러 번 실행해 보면 비율은 대체로 유지됩니다.


7. 정리

오늘의 개념

개념 한 줄 설명
커널 하드웨어를 직접 만지는 유일한 관리자
유저 모드 / 커널 모드 CPU가 강제하는 두 신분 — 우리 프로그램은 낮은 쪽
시스템 콜 유저 모드가 커널에게 일을 부탁하는 정식 통로 (번호로 호출)
파일 디스크립터 부탁의 대상을 가리키는 번호표 — 0 입력, 1 출력, 2 오류, 3부터 우리 파일
세그멘테이션 폴트 허가되지 않은 메모리 접근의 즉시 차단 (종료 코드 139 = 128+11)
버퍼링 시스템 콜 비용을 아끼려고 출력을 모았다가 나눠 내는 기법

오늘의 명령어·함수

명령어·함수 하는 일
grep ... unistd_64.h 시스템 콜 번호표 보기
syscall(SYS_write, 1, ...) 시스템 콜 창구를 직접 두드리기
strace -e trace=이름 프로그램 프로그램의 부탁 추적 (선택 도구)
ls -l /proc/self/fd/ 지금 실행 중인 프로세스의 번호표함 보기
time ./프로그램 실행 시간 측정 — sys 열이 커널에서 일한 시간
echo $? 직전 명령의 종료 코드 확인 (139 = SIGSEGV)

명령어보다 중요한 감각

문지방은 두 겹이라는 것을 기억하세요. 첫째 겹은 시스템 콜 — 파일·네트워크·프로세스 같은 자원에 손대려면 반드시 커널의 문지방을 통과하고, 부탁하는 행위 자체가 검문받습니다. 둘째 겹은 메모리 보호 — badboy.c처럼 부탁조차 하지 않고 포인터로 직접 찌를 때도 차단되며, 여기서 검문하는 것은 "행위"가 아니라 "손이 향한 번지"입니다. 보안 공부를 계속할수록 이 두 겹이 seccomp, SELinux, DEP 같은 구체적인 기술 이름으로 갈라져 나오는데, 전부 오늘 본 "부탁은 검문받는다 / 번지는 허가된 곳만"의 후손입니다.

보안과의 연결: 방어자는 이 통로를 감시합니다 — "이 프로그램이 갑자기 이상한 파일을 열고 이상한 곳에 쓴다"는 식으로요. 백신과 EDR의 상당 부분이 이 감시 기술이고, 공격자는 반대로 통로를 우회하거나 검문소(커널) 자체의 약점을 노립니다. 오늘 여러분은 그 전장의 지형을 본 것입니다. 실험은 내 가상머신의 내 프로그램에서만입니다. 마지막으로 리눅스의 철학 "모든 것은 파일이다"를 기억하세요 — 화면도 키보드도 파일처럼 열고 읽고 씁니다. 오늘 본 번호표 0, 1, 2가 그 철학의 입구입니다.


전부 체크되면 Step 67 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.