운영체제는 무엇을 하는가?

컴퓨터를 사용할 때 우리는 브라우저를 열고, 터미널에서 명령을 실행하고, 파일을 저장한다. 이러한 행위가 자연스럽게 느껴지는 이유는 운영체제가 하드웨어의 복잡성을 숨기고 있기 때문이다.

운영체제의 역할은 크게 두 가지로 요약할 수 있다. 첫째, 하드웨어를 추상화하여 응용 프로그램이 하드웨어의 세부 사항을 몰라도 동작할 수 있게 한다. 둘째, 여러 프로그램이 동시에 실행될 때 CPU, 메모리, 디스크 같은 자원을 공정하게 분배한다. 이 두 가지를 수행하지 못하면 모든 프로그램이 하드웨어를 직접 제어해야 하며, 프로그램 간의 충돌을 피할 방법이 없어지는 것이다.

예를 들어 텍스트 편집기가 파일 하나를 저장하는 순간에도 운영체제는 여러 일을 대신한다. 사용자 입력을 처리하고, 해당 프로세스에 CPU 시간을 배분하고, 페이지 캐시에 데이터를 쓰고, 파일 시스템 메타데이터를 갱신하고, 필요하면 디스크 드라이버까지 호출한다. 겉보기에는 단순한 저장 버튼 하나지만, 그 뒤에는 운영체제의 거의 모든 핵심 기능이 관여한다.

커널이란?

운영체제의 핵심 구성 요소가 커널이다. 커널은 하드웨어와 직접 소통하는 유일한 소프트웨어이며, 모든 응용 프로그램은 커널을 통해서만 하드웨어에 접근할 수 있다.

그렇다면 커널과 운영체제는 같은 것인가? 엄밀히 말하면 다르다. 운영체제는 커널에 더해 시스템 라이브러리, 셸, 유틸리티 등을 포함하는 더 넓은 개념이다. 리눅스라는 이름은 공식적으로 커널만을 가리킨다. 우리가 흔히 리눅스라고 부르는 것은 이 커널 위에 GNU 도구와 각종 소프트웨어를 올린 배포판인 것이다.

유저 공간과 커널 공간

리눅스는 메모리를 유저 공간과 커널 공간으로 나눈다. 이 분리가 존재하는 이유는 안정성과 보안 때문이다.

유저 공간에서 실행되는 프로그램은 하드웨어에 직접 접근할 수 없다. 파일을 읽으려면 시스템 콜을 통해 커널에 요청해야 한다. 커널은 이 요청이 정당한지 확인한 후 하드웨어 조작을 수행하고 결과를 돌려준다. 만약 이런 분리가 없다면 어떤 프로그램이든 디스크의 아무 영역을 덮어쓸 수 있고, 다른 프로세스의 메모리를 읽을 수 있을 것이다.

┌─────────────────────────────────────┐
│         유저 공간 (User Space)        │
│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐  │
│  │ bash│ │nginx│ │ python│ │ java│  │
│  └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘  │
├─────┴───────┴───────┴───────┴──────┤
│          시스템 콜 인터페이스          │
├────────────────────────────────────┤
│        커널 공간 (Kernel Space)       │
│  프로세스 관리 │ 메모리 관리 │ VFS     │
│  네트워킹     │ 디바이스 드라이버      │
├────────────────────────────────────┤
│              하드웨어                │
└────────────────────────────────────┘

CPU 자체가 이 분리를 하드웨어 수준에서 지원한다. x86 아키텍처에서는 Ring 0(커널 모드)과 Ring 3(유저 모드)라는 권한 수준이 존재하며, 유저 모드에서 커널 모드의 명령을 실행하려 하면 CPU가 예외를 발생시킨다. 이러한 하드웨어 보호 메커니즘이 운영체제의 안정성을 보장하는 기반이 된다.

이 구조 덕분에 애플리케이션이 실수로 잘못된 포인터를 역참조하더라도 보통은 해당 프로세스만 죽고 시스템 전체가 무너지지는 않는다. 물론 커널 버그는 여전히 치명적이지만, 최소한 모든 프로그램이 같은 특권을 갖는 혼란스러운 환경은 피할 수 있게 된 것이다.

리눅스 커널의 주요 서브시스템

리눅스 커널은 여러 서브시스템으로 구성되어 있으며, 각각이 특정 영역의 자원 관리를 담당한다.

서브시스템역할
프로세스 관리프로세스 생성, 스케줄링, 종료
메모리 관리가상 메모리, 페이징, 메모리 할당
파일 시스템 (VFS)파일 읽기/쓰기, 다양한 파일 시스템 추상화
네트워킹TCP/IP 스택, 소켓, 패킷 처리
디바이스 드라이버하드웨어 장치와의 통신
IPC프로세스 간 통신 (파이프, 시그널, 공유 메모리)

이 서브시스템들이 서로 협력하여 하나의 통합된 환경을 제공한다. 예를 들어, 웹 서버가 클라이언트의 요청을 처리하려면 네트워킹 서브시스템이 패킷을 수신하고, 프로세스 관리가 워커 프로세스를 스케줄링하고, 파일 시스템이 정적 파일을 읽어 메모리에 올리는 과정이 동시에 일어난다.

리눅스 내부를 공부할 때 중요한 태도도 여기에 있다. 프로세스, 메모리, 파일 시스템, 네트워킹을 각각 따로 배우더라도 실제 시스템에서는 늘 함께 움직인다. 어느 한 부분만 이해해서는 실제 성능 문제나 장애 원인을 끝까지 추적하기 어렵다.

모놀리식 커널 vs 마이크로커널

커널 설계에는 크게 두 가지 접근법이 있다. 모놀리식 커널은 모든 서브시스템이 하나의 큰 바이너리로 컴파일되어 같은 주소 공간에서 실행되는 구조이다. 마이크로커널은 최소한의 기능만 커널에 두고 나머지를 유저 공간의 서버 프로세스로 분리하는 구조이다.

과연 마이크로커널이 더 우수한 설계인가? 이론적으로는 그렇다. 모듈 간의 격리가 더 강하고, 하나의 서브시스템이 죽어도 전체 시스템이 멈추지 않는다. 하지만 현실에서는 유저 공간과 커널 공간 사이의 빈번한 컨텍스트 스위칭으로 인한 성능 저하가 문제가 된다.

리눅스는 모놀리식 커널을 채택하고 있다. 다만, 커널 모듈이라는 메커니즘을 통해 기능을 동적으로 로드하고 언로드할 수 있어 순수한 모놀리식의 경직성을 보완하고 있다. 디바이스 드라이버가 대표적인 예로, 필요할 때만 메모리에 올리고 사용하지 않을 때는 해제할 수 있다.

이 시리즈에서 다루는 것

이 시리즈에서는 리눅스 커널의 내부 동작을 하나씩 짚어 간다. 프로세스가 어떻게 생성되고 스케줄링되는지, 가상 메모리가 어떻게 동작하는지, 파일 시스템이 데이터를 어떻게 관리하는지, 그리고 최종적으로 컨테이너가 이 모든 커널 기능을 어떻게 활용하는지까지를 다룬다.

다음 글에서는 리눅스의 프로세스와 스레드를 다룬다.