[Daily morning study] 사용자 모드와 커널 모드 차이
#daily morning study
왜 두 가지 모드가 필요한가
OS는 여러 프로그램이 동시에 실행되는 환경을 관리한다. 이때 일반 애플리케이션이 메모리나 하드웨어에 직접 접근할 수 있다면 시스템 전체가 불안정해질 수 있다. 이를 막기 위해 CPU는 하드웨어 수준에서 두 가지 실행 권한 수준을 제공한다.
- 사용자 모드 (User Mode): 일반 애플리케이션이 실행되는 모드. 제한된 명령어만 실행 가능.
- 커널 모드 (Kernel Mode): OS 커널이 실행되는 모드. 모든 하드웨어 자원에 직접 접근 가능.
두 모드의 차이
| 구분 | 사용자 모드 | 커널 모드 |
|---|---|---|
| 실행 주체 | 일반 프로그램 (앱, 쉘 등) | OS 커널 |
| 접근 가능 자원 | 제한된 메모리 영역 | 모든 메모리 및 하드웨어 |
| 특권 명령 실행 | 불가 | 가능 |
| CPU 링 (x86) | Ring 3 | Ring 0 |
| 잘못된 접근 시 | 프로세스 종료 (Segfault) | 시스템 패닉 |
모드 전환 (Mode Switch)
사용자 모드에서 커널 모드로 전환되는 시점은 세 가지다.
1. 시스템 콜 (System Call)
프로그램이 파일 읽기, 네트워크 통신, 프로세스 생성 같은 OS 서비스를 요청할 때 발생한다. read(), write(), fork() 같은 함수가 내부적으로 시스템 콜을 발생시킨다.
// C에서 파일을 읽는 코드
int fd = open("file.txt", O_RDONLY); // 시스템 콜 → 커널 모드 전환
read(fd, buffer, size); // 시스템 콜 → 커널 모드 전환
2. 인터럽트 (Interrupt)
키보드 입력, 타이머, 디스크 I/O 완료 같은 하드웨어 이벤트가 발생하면 CPU가 현재 실행을 중단하고 커널의 인터럽트 핸들러로 제어권을 넘긴다.
3. 예외 (Exception)
0으로 나누기, 허용되지 않은 메모리 접근(Segmentation Fault) 같은 오류가 발생하면 CPU가 커널 모드로 전환해 예외 처리기를 실행한다.
모드 전환 과정 (시스템 콜 기준)
[사용자 모드]
프로세스가 시스템 콜 호출 (예: write())
│
▼
소프트웨어 인터럽트 발생 (x86: INT 0x80 또는 SYSCALL 명령어)
│
▼
[커널 모드]
CPU → Ring 0으로 전환
현재 레지스터 상태 저장 (컨텍스트)
시스템 콜 번호로 핸들러 테이블 조회
해당 커널 함수 실행
│
▼
결과값 사용자 공간으로 반환
레지스터 상태 복원
[사용자 모드]
프로세스 실행 재개
사용자 공간과 커널 공간
가상 메모리도 두 영역으로 나뉜다.
가상 주소 공간 (32비트 예시, 4GB)
┌─────────────────┐ 0xFFFFFFFF
│ 커널 공간 │ (약 1GB, 커널 모드에서만 접근)
├─────────────────┤ 0xC0000000
│ │
│ 사용자 공간 │ (약 3GB, 사용자 모드 접근 가능)
│ (스택, 힙, │
│ 코드, 데이터) │
│ │
└─────────────────┘ 0x00000000
사용자 프로그램이 커널 공간 주소에 접근하려 하면 하드웨어가 바로 차단한다. 이 덕분에 한 프로세스의 버그가 다른 프로세스나 커널을 망가뜨리지 않는다.
모드 전환 비용
모드 전환은 공짜가 아니다. 전환할 때마다 다음 작업이 발생한다.
- CPU 레지스터 상태 저장/복원
- 커널 스택으로 전환
- TLB(Translation Lookaside Buffer) 일부 무효화
- 캐시 미스 증가
시스템 콜이 자주 발생하는 고성능 서버에서 이 비용이 병목이 될 수 있다. 이를 줄이기 위한 기법으로 vDSO(virtual Dynamic Shared Object)가 있다. gettimeofday()처럼 단순히 값만 읽는 시스템 콜은 커널 모드 전환 없이 사용자 공간에서 처리할 수 있도록 커널이 특정 데이터를 공유 메모리에 매핑해 두는 방식이다.
실제로 시스템 콜 추적하기
strace 명령어로 프로세스가 어떤 시스템 콜을 호출하는지 볼 수 있다.
strace ls
# execve("/bin/ls", ["ls"], 0x7fff...) = 0
# brk(NULL) = 0x5...
# openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
# read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>...", 832) = 832
# ...
ls 명령어 하나를 실행하는 데도 수십 개의 시스템 콜이 발생한다.
커널 모드가 강력한 이유
커널 모드에서는 다음을 직접 제어할 수 있다.
- 메모리 관리: 물리 메모리 할당, 페이지 테이블 수정
- 프로세스 스케줄링: 어떤 프로세스가 CPU를 사용할지 결정
- 디바이스 드라이버: 하드웨어 레지스터 직접 읽기/쓰기
- 네트워크 스택: 패킷 수신/송신 처리
일반 프로그램은 이 모든 작업을 시스템 콜을 통해 커널에 위임한다.
요약
- CPU는 Ring 레벨로 실행 권한을 구분한다. 커널은 Ring 0, 사용자 프로그램은 Ring 3에서 실행된다.
- 사용자 모드 프로그램이 하드웨어나 OS 기능을 쓰려면 반드시 시스템 콜로 커널에 요청해야 한다.
- 모드 전환은 시스템 콜, 인터럽트, 예외 세 가지 경우에 발생한다.
- 가상 메모리도 커널 공간과 사용자 공간으로 나뉘어 격리된다.
- 모드 전환에는 비용이 따르기 때문에 불필요한 시스템 콜을 줄이는 것이 성능 최적화의 한 방법이다.