사용자 모드와 커널 모드
고친 사람 github-actions[bot]
사용자 모드와 커널 모드는 지금 도는 코드가 컴퓨터에 어디까지 손댈 수 있는지를 갈라 줍니다. 응용 프로그램은 사용자 모드에서 돌기 때문에 장치나 남의 메모리를 직접 건드리지 못합니다. 그런 일은 커널 모드에서 도는 커널이 대신 맡습니다. 그래서 프로그램 하나가 잘못 돌아도 기계 전체가 같이 멈추지 않습니다.
쉽고 빠른 이해
두 모드는 컴퓨터가 코드마다 허락하는 일의 범위입니다. 웹 서버가 파일을 읽을 때도 디스크를 직접 만지지 않습니다. 커널에 부탁하고 결과만 받습니다.
이렇게 가르지 않으면 버그 하나가 기계 전체를 무너뜨립니다. 아무 프로그램이나 남의 메모리를 덮어쓸 수 있기 때문입니다. 장치도 멋대로 다룰 수 있습니다.
- 응용 프로그램은 할 수 있는 일이 적은 사용자 모드에서 돕니다
- 장치나 메모리를 다룰 일이 생기면 정해진 길로 커널에 부탁합니다
- 그 순간 커널 모드로 바뀌고 커널이 일을 처리한 뒤 사용자 모드로 돌아옵니다
대가는 시간입니다. 모드를 오갈 때마다 하던 일을 저장했다가 되돌리는 비용이 듭니다. 부탁이 잦으면 그만큼 시간이 쌓입니다.
상세
은행 창구를 떠올리면 됩니다. 손님은 창구 앞에서 서류를 냅니다. 그리고 기다립니다. 금고를 여는 일과 장부를 고치는 일은 창구 안쪽 직원만 합니다. 손님이 응용 프로그램, 창구 안 직원이 커널입니다.
CPU(Central Processing Unit, 중앙 처리 장치)는 지금 실행하는 코드가 어느 모드인지를 늘 들고 있습니다. 이 표시를 흔히 모드 비트라고 부릅니다. 값이 한쪽이면 사용자 모드입니다. 다른 쪽이면 커널 모드입니다. CPU 는 명령을 하나 실행할 때마다 이 표시를 보고 그 명령을 허락할지 정합니다.
사용자 모드는 응용 프로그램이 도는 모드입니다. 웹 서버도 데이터베이스도 편집기도 이 모드에서 돕니다. 커널 모드는 운영체제의 알맹이인 커널이 도는 모드입니다. 장치를 다루는 디바이스 드라이버도 대개 커널 모드에서 돕니다.
커널 모드는 특권 모드나 감독자 모드(supervisor mode)라고도 부릅니다. 할 수 있는 일이 더 많다는 뜻에서 붙은 이름입니다. 사용자 모드는 비특권 모드라고도 부릅니다.
둘로 가르는 까닭
모든 프로그램이 모든 명령을 쓸 수 있으면 포인터 실수 하나가 커널의 메모리를 덮어씁니다. 그러면 그 프로그램만이 아니라 기계 전체가 멈춥니다.
남의 데이터도 지킬 수 없습니다. 한 기계에서 여러 프로그램이 돌면 서로의 메모리와 파일을 들여다볼 수 있게 됩니다. 운영체제가 정한 권한 규칙도 소용이 없어집니다. 규칙을 검사하는 코드를 프로그램이 건너뛰면 그만이기 때문입니다.
CPU 시간을 나누는 일도 무너집니다. 운영체제는 타이머를 걸어 둡니다. 때가 되면 CPU 를 되찾아 다른 프로그램에 넘깁니다. 프로그램이 이 타이머를 끄고 끝없이 돌면 다른 프로그램은 차례를 영영 못 받습니다. 모드를 가르면 이런 명령을 커널만 쓰게 묶어 둘 수 있습니다.
사용자 모드에서 막히는 명령
커널 모드에서만 실행되는 명령을 특권 명령이라고 부릅니다. 사용자 모드에서 막히는 일은 대개 아래 넷입니다.
| 막히는 일 | 막지 않으면 생기는 일 |
|---|---|
| 입출력 장치를 직접 다루기 | 두 프로그램이 같은 디스크에 함께 써서 파일이 깨진다 |
| 장치 신호를 끄고 켜기 | 장치가 보내는 신호를 무시하고 CPU 를 붙잡는다 |
| 타이머 설정 바꾸기 | 운영체제가 CPU 를 되찾아 올 수 없다 |
| 메모리 지도 바꾸기 | 남의 메모리를 자기 것처럼 읽고 쓴다 |
둘째 줄의 장치 신호는 인터럽트를 말합니다. 디스크나 네트워크 카드가 「일이 끝났다」고 CPU 에 알리는 신호입니다. 이 신호를 끌 수 있으면 프로그램 하나가 CPU 를 붙들고 놓지 않을 수 있습니다.
넷째 줄의 메모리 지도는 페이지 테이블을 말합니다. 프로그램이 쓰는 주소를 실제 메모리의 어디로 이을지 적어 둔 표입니다. 이 표를 커널만 고칠 수 있어서 프로그램마다 제 몫의 메모리만 보게 됩니다.
프로그램은 자기만의 주소로 메모리를 봅니다. 이렇게 프로그램마다 따로 보이는 주소 체계가 가상 메모리입니다.
메모리에도 같은 구분이 붙습니다. 커널이 쓰는 메모리에는 「커널 모드에서만 읽고 쓸 수 있음」이라는 표시가 붙어 있습니다. 이 구역이 커널 공간입니다. 나머지는 사용자 공간입니다.
사용자 모드에서 막힌 명령을 실행하거나 막힌 메모리를 건드리면 CPU 가 그 명령을 멈춥니다. 그리고 제어를 커널로 넘깁니다. 커널은 대개 그 프로그램을 끝냅니다. 유닉스 계열에서 흔히 보는 세그멘테이션 폴트가 메모리를 잘못 건드려 끝난 경우입니다.
커널 모드로 들어가는 세 길
사용자 모드의 코드는 모드 비트를 스스로 바꿀 수 없습니다. 바꾸는 명령 자체가 특권 명령이기 때문입니다. 커널 모드로 들어가는 길은 CPU 가 정해 둔 몇 가지뿐입니다.
길마다 커널이 미리 등록해 둔 진입 코드가 있습니다. CPU 는 모드를 바꿉니다. 그리고 곧바로 그 코드로 건너뜁니다. 그래서 프로그램은 커널 모드에 들어가도 자기가 고른 코드를 실행할 수 없습니다.
| 길 | 누가 일으키나 | 예 |
|---|---|---|
| 시스템 콜 | 프로그램이 일부러 | 파일 읽기 · 소켓에 쓰기 · 새 프로그램 띄우기 |
| 예외 | 프로그램이 실행한 명령이 실패해서 | 0으로 나누기 · 막힌 메모리 건드리기 |
| 인터럽트 | 바깥 장치가 | 네트워크 카드에 데이터 도착 · 타이머 만료 |
시스템 콜은 프로그램이 커널에 일을 맡기는 정식 길입니다. 예외와 인터럽트는 프로그램이 원하지 않아도 일어납니다. 세 길은 하던 일을 멈추고 커널이 정해 둔 코드로 건너뛴다는 점에서 같습니다. 프로그램이 일으키는 앞의 둘을 묶어 트랩이라고 부르기도 합니다.
나오는 길은 하나입니다. 커널은 일을 마치면 사용자 모드로 돌아가는 복귀 명령을 실행합니다. 이 명령이 모드 비트를 사용자 쪽으로 돌립니다. 그리고 프로그램이 멈췄던 곳으로 돌아갑니다.
시스템 콜 한 번의 흐름
백엔드 코드가 모드를 가장 자주 바꾸는 때는 시스템 콜입니다. 파일에서 한 덩어리를 읽는 read() 한 번을 따라가 봅니다.
앱이 부르는 read() 는 표준 라이브러리의 함수입니다. 표준 라이브러리는 언어나 운영체제가 기본으로 딸려 주는 함수 묶음입니다. read() 는 파일을 직접 읽지 않습니다. 커널에 부탁을 넘깁니다.
부탁을 넘기려면 먼저 인자를 레지스터에 담습니다. 레지스터는 CPU 안에 있는 아주 작은 저장 칸입니다.
그다음 시스템 콜 명령을 실행합니다. 시스템 콜 명령은 CPU 에 커널 모드로 바꾸라고 하는 특별한 명령입니다. 보통 함수 호출과 달리 이 명령은 모드를 바꿉니다. 그리고 커널의 진입 코드로 건너뜁니다.
커널은 인자가 올바른지 검사합니다. 이 프로그램이 그 파일을 읽어도 되는지도 검사합니다. 통과하면 앱이 넘겨준 메모리 칸에 데이터를 복사합니다. 이 칸을 버퍼라고 합니다.
일을 마친 커널은 복귀 명령을 실행해 사용자 모드로 돌아옵니다. 아래 그림이 이 왕복을 한 번에 보여 줍니다.
sequenceDiagram
participant 앱
participant 라이브러리 as 표준 라이브러리
participant CPU
participant 커널
앱->>라이브러리: read() 호출
라이브러리->>CPU: 시스템 콜 명령
Note over CPU: 커널 모드로 바뀜
CPU->>커널: 진입 코드로 건너뜀
커널->>커널: 권한 검사 · 버퍼에 복사
커널->>CPU: 복귀 명령
Note over CPU: 사용자 모드로 바뀜
CPU-->>라이브러리: 돌아옴
라이브러리-->>앱: 읽은 바이트 수
앱 쪽에서 보면 평범한 함수 호출 하나입니다. 모드가 두 번 바뀐 일은 코드에 안 드러납니다.
어느 모드에서 시간을 썼나
유닉스 계열의 time 명령은 프로그램이 쓴 CPU 시간을 두 모드로 나눠 보여 줍니다. 파일을 복사하는 스크립트를 재면 아래처럼 나옵니다. 숫자는 예로 든 값입니다.
$ time python3 copy.py
real 0m2.10s # 시계로 잰 전체 시간
user 0m0.40s # 사용자 모드에서 쓴 시간
sys 0m1.50s # 커널 모드에서 쓴 시간
user 는 프로그램 자신의 코드와 라이브러리 코드를 돌린 시간입니다. sys 는 커널이 그 프로그램의 부탁을 처리한 시간입니다. 위처럼 sys 가 user 보다 크면 계산보다 커널에 맡긴 일이 많았다는 뜻입니다.
흔한 원인은 작은 시스템 콜을 아주 많이 부르는 것입니다. 파일을 한 바이트씩 읽으면 그렇게 됩니다. 로그를 한 줄마다 따로 써도 그렇게 됩니다.
오갈 때 드는 비용
모드를 바꾸는 일은 보통 함수 호출보다 비용이 큽니다. CPU 는 사용자 모드의 레지스터 값을 저장해 두었다가 돌아올 때 되돌려야 합니다. 커널은 넘어온 인자를 하나하나 검사합니다.
눈에 안 보이는 비용도 있습니다. CPU 는 곧 쓸 명령과 데이터를 캐시에 올려 두고 돕니다. 커널 코드로 건너뛰면 그 내용 일부가 커널의 것으로 밀려납니다. 돌아온 프로그램은 밀려난 것을 다시 채우는 동안 기다립니다.
한 번은 짧지만 횟수가 쌓이면 커집니다. 그래서 부탁의 횟수를 줄이는 방법이 여럿 있습니다. 작은 쓰기를 모아 한 번에 내보내는 버퍼링이 가장 흔합니다. 여러 요청을 한 번의 호출로 넘기는 배치 처리도 같은 목적입니다.
헷갈리기 쉬운 세 이웃
두 모드와 자주 섞이는 이웃이 셋 있습니다. 사용자 공간, root, 문맥 교환입니다.
모드는 CPU 의 상태입니다. 사용자 공간과 커널 공간은 메모리의 구역입니다. 둘은 짝을 이루지만 같은 것이 아닙니다. 사용자 모드에서는 사용자 공간만 건드릴 수 있습니다. 커널 모드에서는 두 구역을 다 건드릴 수 있습니다.
root 사용자도 사용자 모드에서 돕니다. root 는 운영체제가 권한 검사에서 대부분 통과시켜 주는 계정일 뿐입니다. root 로 띄운 프로그램도 장치를 만지려면 시스템 콜로 커널에 부탁해야 합니다.
모드 전환은 문맥 교환과도 다릅니다. 실행 중인 프로그램 하나하나를 프로세스라고 합니다. 문맥 교환은 CPU 가 돌리던 프로세스를 다른 프로세스로 바꾸는 일입니다. 시스템 콜 한 번은 모드만 바꿉니다. 끝나면 같은 프로세스로 돌아옵니다.
문맥 교환은 커널 모드 안에서 일어납니다. 저장했다가 되돌릴 것도 모드 전환보다 많습니다.
둘보다 많은 권한 단계
CPU 에 따라 권한 단계를 둘보다 많이 두기도 합니다. x86 계열은 넷으로 나눠 링 0부터 링 3까지로 부릅니다. 숫자가 작을수록 권한이 셉니다.
흔한 운영체제는 커널에 링 0을 줍니다. 응용 프로그램에는 링 3을 줍니다. 가운데 둘은 쓰지 않습니다. 이렇게 겹겹이 나눈 구조를 보호 링이라고 부릅니다.
가상화는 한 기계에서 운영체제 여럿을 따로 돌리는 기술입니다. 운영체제 하나가 들어가 도는 칸 하나하나를 가상 머신이라고 합니다.
가상화를 지원하는 CPU 는 커널 모드보다 한 단계 더 센 모드를 둡니다. 그 모드에서 가상 머신들을 띄우고 지켜보는 프로그램이 하이퍼바이저입니다. 그래서 가상 머신 안의 커널은 커널 모드에서 돌아도 하이퍼바이저의 감시를 받습니다.
관련 항목
두 모드를 가르는 CPU 장치
CPU · 모드 비트 · 특권 명령 · 특권 수준 · 보호 링 · 레지스터
사용자 모드에서 커널 모드로 들어가는 통로
시스템 콜 · 트랩 · 예외 · 인터럽트 · 인터럽트 벡터 테이블
두 모드가 나눠 쓰는 메모리 구역
사용자 공간 · 커널 공간 · 가상 메모리 · 페이지 테이블 · 메모리 보호
커널 모드에서 실행되는 코드
커널 · 디바이스 드라이버 · 커널 모듈 · 인터럽트 핸들러 · eBPF
사용자 모드에서 실행되는 프로그램
응용 프로그램 · 프로세스 · 스레드 · 표준 라이브러리 · 셸 · 데몬
커널이 CPU 를 되찾아 오는 장치
모드 전환 횟수를 줄이는 기법
버퍼링 · 배치 처리 · 버퍼 · vDSO · io_uring · 제로 카피 · 커널 우회
모드 경계에서 터지는 오류와 공격
세그멘테이션 폴트 · 페이지 폴트 · 일반 보호 오류 · 커널 패닉 · 권한 상승
모드와 별개로 도는 계정 권한 체계
두 모드를 쓰는 방식으로 갈리는 커널 설계
모놀리식 커널 · 마이크로커널 · 하이브리드 커널 · 유니커널
커널 모드 위에 한 단계를 더 얹는 가상화 기술
가상화 · 하이퍼바이저 · 가상 머신 · 하드웨어 가상화 지원
두 모드에서 쓴 시간을 재는 도구
strace · perf · top · 프로파일링
다른 이름: user mode and kernel mode · 사용자 모드 · 커널 모드 · 유저 모드 · user mode · kernel mode · 특권 모드 · 비특권 모드 · 감독자 모드 · supervisor mode