10 분 소요

1. 이중 동작 모드 (Dual-mode Operation)

1.1 개요: 왜 운영체제는 ‘계급’을 나누는가?

운영체제(OS)의 가장 본질적인 역할은 자원 관리보호입니다.

만약 모든 프로그램이 CPU의 모든 명령어를 실행하고 RAM의 모든 영역에 접근할 수 있다면,

하나의 프로그램에 발생한 사소한 버그(예: 잘못된 포인터 참조)가 운영체제의 핵심 데이터를 파괴하거나 다른 프로그램의 정보를 훔쳐볼 수 있습니다.

이를 방지하기 위해 현대 컴퓨터 아키텍처는 “하드웨어적으로 실행 권한을 분리”하는 방식을 채택했습니다. 이것이 바로 이중 동작 모드입니다.

1.2 하드웨어 수준의 구현: Mode Bit와 CPL

이중 모드의 핵심은 CPU가 “지금 실행하는 이 코드가 믿을만한가?”를 실시간으로 판별하는 것입니다.

이를 위해 CPU는 레지스터(Register)라는 초고속 저장 공간에 권한 상태를 박아넣습니다.

  • Mode Bit (제어 레지스터): CPU 내부에는 현재 어떤 권한으로 코드가 실행 중인지를 나타내는 비트가 있습니다.
    • 0 (Kernel Mode): 특권 모드. 모든 하드웨어 자원을 제어할 수 있는 무한 권한을 가집니다.
    • 1 (User Mode): 비특권 모드. 실행 가능한 명령어와 접근 가능한 메모리 범위가 엄격히 제한됩니다.
  • CPL (Current Privilege Level): x86 아키텍처(Intel/AMD)에서는 이를 더 구체적으로 관리합니다. CPU의 CS (Code Segment) 레지스터 하위 2비트가 이 역할을 수행합니다.
    • 00 (2^0) : 가장 높은 권한 (Kernel)
    • 11 (2^3) : 가장 낮은 권한 (User)

1.3 계급의 구조: Protection Rings (보호 링)

x86 설계자들은 권한을 총 4단계(Ring 0 ~ Ring 3)로 설계했습니다. 숫자가 낮을수록 안쪽에 위치하며 권한이 높습니다.

  1. Ring 0 (Kernel Mode): 운영체제의 심장부(Scheduler, Memory Manager, File System 등)가 위치합니다.
    • CPU의 전역 설정을 변경하거나 인터럽트 벡터 테이블(IDT)을 수정할 수 있는 유일한 계급입니다.
  2. Ring 1 & 2: 원래는 디바이스 드라이버나 프로토콜 스택을 위해 설계되었습니다.
    • 하지만 모드 전환 시 발생하는 오버헤드(Overhead)와 설계의 복잡성 때문에, 현대의 Windows와 Linux는 성능 최적화를 위해 이 단계를 건너뛰고 0과 3만 사용합니다. (Monolithic Kernel 구조)
  3. Ring 3 (User Mode): 우리가 사용하는 모든 일반 애플리케이션(Chrome, Games, Word 등)이 여기서 실행됩니다.
    • 직접적인 하드웨어 조작이 차단되어 있어, 시스템을 파괴하려 해도 CPU가 허용하지 않습니다.

Image

1.4 특권 명령어 (Privileged Instructions)

특권 명령어란 시스템의 핵심 자원(CPU 상태, 메모리 맵, I/O 장치 등)을 직접적으로 조작하는 기계어 명령입니다.

만약 유저 모드 프로그램이 이 명령을 실행하게 두면, 시스템 전체의 일관성이 파괴되거나 보안이 완전히 붕괴됩니다.

따라서 커널 모드에서만 실행 가능하며, 유저 모드에서 실행하려고 시도할 경우 CPU가 “General Protection Fault” 예외를 발생시켜 프로세스를 즉시 종료시킵니다.

  • I/O 제어: IN, OUT 명령어. 하드웨어 포트에 직접 데이터를 쓰고 읽는 행위.
  • 인터럽트 제어: CLI (Clear Interrupt Flag - 인터럽트 중단), STI (Set Interrupt Flag - 인터럽트 허용).
  • 시스템 제어: HLT (Halt - CPU 정지), LIDT (Load IDT - 인터럽트 지도 변경).
  • 메모리 관리: MOV CR3, ... (페이지 테이블의 시작 주소를 변경하는 명령).

1.5 메모리 공간의 분리 (Kernel Space vs User Space)

운영체제는 모든 프로세스에게 가상 메모리(Virtual Memory)라는 환상을 제공합니다.

하지만 이 거대한 가상 주소 공간은 사실 두 개의 거대한 대륙으로 나뉘어 관리됩니다.

현대 64비트 시스템을 기준으로, 프로세스가 사용할 수 있는 전체 가상 주소 범위는 천문학적으로 넓습니다. OS는 이를 엄격하게 구분합니다.

  • 유저 공간 (User Space): 가상 주소 공간의 낮은 주소 영역에 위치합니다.
    • 프로세스의 코드(Text), 데이터, 힙(Heap), 스택(Stack)이 포함됩니다.
    • 오직 해당 프로세스 본인만 접근할 수 있으며, 다른 프로세스의 유저 공간은 볼 수 없습니다.
  • 커널 공간 (Kernel Space): 가상 주소 공간의 높은 주소 영역에 위치합니다.
    • 운영체제의 핵심 코드, 인터럽트 핸들러, 장치 드라이버, PCB(Process Control Block) 등이 상주합니다.
    • 핵심 특징: 모든 프로세스의 가상 주소 공간 상단에는 동일한 커널 공간이 매핑되어 있습니다. 즉, 어떤 프로세스가 실행되든 커널은 항상 같은 ‘높은 주소’에 대기하고 있습니다.

가상 주소를 실제 물리 주소로 변환하는 장치인 MMU (Memory Management Unit)는 주소 번역만 하는 게 아니라 ‘통행 검사’를 병행합니다.

  • PTE (Page Table Entry)의 구조: 페이지 테이블의 각 항목(PTE)에는 주소 정보 외에 여러 상태 비트가 있는데, 그중 U/S 비트가 핵심입니다.
    • U/S = 0 (Supervisor): 오직 Ring 0(커널 모드)에서만 접근 가능.
    • U/S = 1 (User): Ring 3(유저 모드)에서도 접근 가능.
  • 검사 로직: 유저 모드 프로그램이 커널 공간에 해당하는 주소(U/S=0)를 읽거나 쓰려고 시도하면, MMU는 즉시 주소 번역을 중단하고 CPU에 Page Fault(또는 Access Violation) 예외를 던집니다.

“보안이 중요하다면 커널을 아예 따로 분리해두지, 왜 프로세스마다 머리 위에 얹어두나요?”라는 의문이 생길 수 있습니다. 그 이유는 성능(Efficiency) 때문입니다.

1. 빠른 시스템 콜 처리: ‘시스템 콜이 발생할 때마다’ ‘커널 메모리 지도를 새로 불러’와야 한다면(TLB Flush), 오버헤드가 너무 커집니다.
커널이 항상 같은 자리에 매핑되어 있으면 모드 비트만 바꾸고 바로 커널 코드를 실행할 수 있습니다.

2. 인터럽트 대응: 하드웨어 인터럽트는 언제 발생할지 모릅니다. 어떤 프로그램이 돌고 있든 커널이 주소 공간 안에 있어야 즉각적으로 핸들러를 실행할 수 있습니다.

1.6 모드 전환의 트리거 (How to Switch?)

유저 모드에서 커널 모드로의 전환은 프로그램이 임의로 주소를 점프해서 일어나는 것이 아닙니다.

반드시 하드웨어 인터럽트 라인이나 특수 명령어를 통해서만 미리 약속된 지점(Entry Point)으로 진입할 수 있습니다.

① 시스템 콜 (System Call / Trap): 의도적인 요청

유저 모드 프로그램은 하드웨어 자원에 직접 접근할 권한이 없습니다.

만약 프로그램이 직접 디스크에 데이터를 쓰거나 네트워크 패킷을 보낼 수 있다면, 다른 프로그램의 데이터를 변조하거나 삭제하는 보안 사고를 막을 길이 없습니다.

시스템 콜(System Call)은 운영체제가 제공하는 서비스에 접근하기 위해 프로그램이 호출하는 ‘프로그래밍 인터페이스’입니다.

이는 유저와 커널 사이의 유일하고 안전한 통로이며, 모든 자원 요청은 반드시 이 관문을 통과해야 합니다.

  • 성격: 소프트웨어 인터럽트(Software Interrupt).
  • 상황: 프로그램이 파일 읽기, 프로세스 생성, 네트워크 통신 등 커널의 서비스가 필요할 때 스스로 발생시킵니다.
  • 명령어: 현대 x86-64에서는 SYSCALL, 과거 x86에서는 INT 0x80 명령어를 사용합니다.
  • 특징: 프로그램 실행 흐름상 예측 가능한 지점에서 발생합니다.

② 하드웨어 인터럽트 (Hardware Interrupt): 외부 사건

  • 성격: 비동기적(Asynchronous) 이벤트.
  • 상황: 외부 하드웨어 장치가 CPU에 신호를 보낼 때 발생합니다.
    • 키보드 타이핑, 마우스 클릭, 패킷 도착, 디스크 읽기 완료.
    • 타이머 인터럽트: 스케줄링을 위해 수 밀리초마다 발생하는 가장 중요한 인터럽트.
  • 특징: 프로그램의 실행 위치와 상관없이 언제든 발생할 수 있으며, CPU는 현재 명령어를 마치는 즉시 모드를 전환합니다.

③ 예외 (Exception / Fault): 실행 중 오류

  • 성격: 동기적(Synchronous) 이벤트.
  • 상황: CPU가 명령어를 실행하다가 정상적으로 처리할 수 없는 상황을 맞닥뜨렸을 때 발생합니다.
    • 0으로 나누기 (Divide by Zero).
    • 잘못된 메모리 주소 참조 (Segmentation Fault / Page Fault).
    • 권한이 없는 특권 명령어 실행 시도.
  • 특징: 문제가 생긴 그 시점에 즉시 발생하며, 커널은 이 예외를 보고 프로그램을 종료할지(Abort), 아니면 문제를 해결하고 다시 실행할지(Page Fault 해결 등) 결정합니다.

CPU는 매우 빠르지만, 주변 장치(키보드, 디스크 등)는 매우 느립니다.

CPU가 데이터가 도착했는지 계속 확인하는 방식을 폴링(Polling)이라고 합니다. 이는 마치 택배가 올 때까지 현관문 앞에서 계속 기다리는 것과 같아 CPU 자원을 엄청나게 낭비합니다.

인터럽트(Interrupt)는 “택배가 오면 벨을 눌러줘, 난 내 할 일을 하고 있을게”라고 말하는 방식입니다. 비동기적으로 발생하는 이벤트를 효율적으로 처리하기 위한 현대 컴퓨팅의 핵심 메커니즘입니다.

아래는 이러한 인터럽트를 통해 모드 전환이 일어날 때 CPU 내부에서 발생하는 물리적인 단계입니다. 이 과정은 매우 빠르게 일어나며 하드웨어가 자동으로 수행하는 부분이 많습니다.

  1. Context Saving (상태 저장):
    • CPU는 현재 실행 중이던 유저 프로그램의 상태를 저장해야 합니다.
    • 커널 스택(Kernel Stack)으로 자동 전환되며, 여기에 현재의 플래그 레지스터(EFLAGS), 코드 세그먼트(CS), 명령어 포인터(EIP/RIP) 등을 푸시(Push)합니다.
    • 이는 나중에 커널 업무가 끝난 후 유저 프로그램의 “중단된 바로 그 지점”으로 돌아오기 위함입니다.
  2. Mode Bit 변경 (CPL Update):
    • CPU는 CS 레지스터의 하위 2비트(CPL)를 11(User)에서 00(Kernel)으로 변경합니다.
    • 이제부터 CPU는 모든 특권 명령어를 실행할 수 있는 상태가 됩니다.
  3. Entry Point 찾기 (IDT 참조):
    • CPU는 무작정 커널로 가는 것이 아니라, IDT(Interrupt Descriptor Table)라는 지도를 봅니다.
    • 발생한 인터럽트나 시스템 콜 번호에 해당하는 ISR(Interrupt Service Routine)의 주소를 찾아 그곳으로 PC(Program Counter)를 옮깁니다.
  4. Kernel Handler 실행:
    • 이제 운영체제의 코드가 실행됩니다. 시스템 콜이라면 요청된 기능을 수행하고, 인터럽트라면 장치 처리를 수행합니다.

1.7 프로세스 스케줄링

현대 운영체제는 수백 개의 프로세스가 동시에 실행되는 것처럼 보이는 멀티태스킹(Multitasking) 환경을 제공합니다.

하지만 실제 CPU 코어의 수는 제한적입니다. 스케줄링은 “어떤 프로세스에게, 언제, 얼마나 오랫동안 CPU라는 권력을 줄 것인가”를 결정하는 고도의 통제 전략입니다.

프로세스는 생성부터 종료까지 커널의 통제 아래 여러 상태를 오갑니다. 스케줄러는 이 상태 변화를 관리하는 주체입니다.

  1. New (생성): 프로그램이 메모리에 적재되어 PCB가 생성된 단계.
  2. Ready (준비): CPU를 제외한 모든 자원(메모리 등)을 할당받아, 선택만 되면 즉시 실행 가능한 상태. Ready Queue에서 대기합니다.
  3. Running (실행): 실제로 CPU를 점유하여 명령어를 실행 중인 상태.
  4. Waiting/Blocked (대기): I/O 작업이나 특정 이벤트(세마포어 등)를 기다리며 휴면 중인 상태. CPU를 주어도 아무 일도 할 수 없습니다.
  5. Terminated (종료): 실행을 마치고 자원을 반납하는 단계.

현대 OS는 시스템의 부하를 조절하기 위해 세 단계의 스케줄러를 운영합니다.

  • 장기 스케줄러 (Long-term Scheduler / Job Scheduler): 어떤 프로세스를 Ready Queue에 넣을지(메모리에 올릴지) 결정합니다.
    • 다중 프로그래밍의 정도(Degree of Multiprogramming)**를 제어합니다. 현대 시분할 시스템에서는 메모리가 충분하여 보통 생략되거나 중기 스케줄러가 역할을 대신합니다.
  • 단기 스케줄러 (Short-term Scheduler / CPU Scheduler): 메모리에 있는 Ready 상태의 프로세스 중 누구에게 CPU를 줄지 결정합니다.
    • 매우 빈번하게 호출되므로(수 밀리초 단위) 속도가 극도로 빨라야 합니다.
  • 중기 스케줄러 (Medium-term Scheduler / Swapper): * 메모리 부족 시 프로세스를 통째로 디스크로 쫓아내거나(Swap-out), 다시 불러오는(Swap-in) 역할을 합니다.

CPU의 주인이 바뀔 때, 이전 프로세스의 상태를 저장하고 새 프로세스의 상태를 로드하는 과정을 문맥 교환이라고 합니다.

1. 작동 원리: 타이머 인터럽트가 발생하면 커널은 현재 프로세스 A의 레지스터, PC(Program Counter), 스택 포인터 등을 PCB A에 저장합니다.
이후 스케줄러가 선택한 프로세스 B의 정보를 PCB B에서 꺼내 CPU 레지스터에 덮어씌웁니다.

2. 오버헤드 (Overhead): 문맥 교환이 일어나는 동안 CPU는 유저의 실제 코드를 실행하지 못합니다.
프로세스가 바뀌면 CPU 캐시나 TLB(주소 변환 캐시)에 들어있던 데이터들이 쓸모없게 되어 성능이 일시적으로 급락합니다.

스케줄링 알고리즘

1. 비선점형 (Non-preemptive) 알고리즘

프로세스가 스스로 CPU를 반납할 때까지 뺏지 않는 방식입니다.

  • FCFS (First-Come, First-Served): 단순히 먼저 온 순서대로 처리.

    • Convoy Effect(호위 효과): 긴 프로세스가 앞을 막으면 짧은 프로세스들이 끝없이 대기하는 현상이 발생합니다.
  • SJF (Shortest Job First): 실행 시간이 짧은 애부터 처리.
    • 평균 대기 시간을 줄이는 데 최적이지만, 실행 시간을 미리 알기 어렵고 긴 프로세스가 굶는 Starvation(기아) 문제가 발생합니다.
      2.선점형 (Preemptive) 알고리즘 (현대 OS 표준)

OS가 강제로 CPU를 회수할 수 있는 방식입니다.

  • Round Robin (RR): 각 프로세스에게 동일한 Time Quantum(시간 할당량)을 부여. 현대 시분할 시스템의 근간입니다.
    • 시간 할당량이 너무 크면 FCFS가 되고, 너무 작으면 문맥 교환 비용이 커집니다. (보통 10~100ms)
  • Priority Scheduling (우선순위 스케줄링): * 중요도가 높은 프로세스 먼저 처리.
    • Aging(노화) 기법: 오래 기다린 프로세스의 우선순위를 높여주어 기아 현상을 해결합니다.
      3. 다단계 피드백 큐 (MLFQ, Multilevel Feedback Queue)

현대 Linux와 Windows가 사용하는 가장 정교한 방식입니다.

  • 여러 개의 큐를 두고, CPU를 많이 쓰는 프로세스는 우선순위가 낮은 큐로 내리고(Penalty), I/O를 많이 쓰는 프로세스는 높은 우선순위 큐에 배치합니다.
  • 목표: I/O 위주 프로세스(대화형 앱)에게는 빠른 응답성을, CPU 위주 프로세스(연산 앱)에게는 높은 처리량을 자동으로 보장합니다.

[종합 정리]

현대 운영체제의 동작은 단순히 명령어를 실행하는 것을 넘어, 하드웨어의 감시와 소프트웨어의 제어가 1초에 수천 번씩 교차하는 정교한 핑퐁 게임과 같습니다.

1. 유저 모드에서의 고립된 실행 (The Illusion of Sole Possession)

먼저, 우리가 브라우저를 띄우거나 게임을 실행하는 순간을 상상해 보십시오. 이때 CPU는 유저 모드(User Mode, CPL=3)에서 작동합니다.

프로세스는 운영체제가 제공한 ‘가상 메모리’라는 가짜 영토 안에서 자신이 컴퓨터의 모든 자원을 독점하고 있다고 착각하며 실행됩니다.

하드웨어 레벨에서는 MMU(메모리 관리 유닛)가 프로세스의 일거수일투족을 감시합니다.

만약 프로세스가 허락되지 않은 주소를 건드리거나 특권 명령어를 실행하려 하면, 하드웨어는 즉시 실행을 차단하고 예외를 터뜨립니다.

이 단계에서 프로세스는 안전하게 격리되어 있으며, 시스템의 다른 부분에 영향을 줄 수 없는 ‘제한된 평화’ 상태를 유지합니다.

2. 하드웨어 타이머와 권한의 강제 회수 (The Heartbeat of the System)

하지만 한 프로세스가 CPU를 영원히 점유하게 둘 수는 없습니다. 여기서 운영체제의 ‘공권력’이 발휘됩니다.

컴퓨터 메인보드에 장착된 하드웨어 타이머는 시스템의 심장 박동처럼 일정한 간격(Time Slice)마다 CPU에 전기적 신호를 보냅니다. 이것이 바로 타이머 인터럽트입니다.

이 신호가 발생하는 순간, CPU는 현재 실행 중이던 유저 코드를 즉시 멈춥니다. 그리고 하드웨어적으로 모드 비트(Mode Bit)를 1에서 0으로 뒤집으며 커널 모드(Kernel Mode, CPL=0)로 진입합니다.

이 과정은 소프트웨어의 개입 없이 CPU 아키텍처 자체에서 일어나며, 프로세스의 ‘동의’를 구하지 않는 강제적인 권한 회수입니다.

이것이 바로 현대 OS가 안정성을 유지하는 핵심 원리인 ‘선점(Preemption)’의 시작입니다.

3. 커널의 개입과 중재 (The Authority of the Kernel)

이제 제어권은 운영체제의 심장부인 커널로 넘어왔습니다. CPU는 인터럽트 벡터 테이블(IDT)을 참조하여 미리 약속된 인터럽트 핸들러 주소로 점프합니다.

커널은 현재 어떤 프로세스가 CPU를 얼마나 사용했는지 확인하고, 스케줄러를 호출하여 다음 순서를 결정합니다.

이때 커널은 단순히 다음 프로그램을 고르는 데 그치지 않습니다. 현재 중단된 프로세스가 나중에 다시 돌아왔을 때, 마치 아무 일도 없었다는 듯이 실행을 재개할 수 있도록 ‘기록’을 남겨야 합니다.

CPU 내부에 있던 레지스터 값, 다음에 실행할 명령어 주소(PC), 스택 포인터 등을 프로세스의 주민등록증인 PCB(Process Control Block)에 꼼꼼히 저장합니다.

4. 문맥 교환과 주소 공간의 전환 (The Magic of Context Switching)

스케줄러가 새로운 프로세스를 선택하면, 시스템의 ‘자아’를 통째로 바꾸는 문맥 교환(Context Switch)이 일어납니다.

CPU의 제어 레지스터(CR3)를 새 프로세스의 페이지 테이블 주소로 교체하는 순간, 메모리의 지도는 완전히 바뀝니다. 이전 프로세스의 데이터는 사라지고, 새 프로세스의 가상 세계가 눈앞에 펼쳐지는 것입니다.

동시에 PCB에 저장되어 있던 새 프로세스의 이전 상태 값들을 CPU 레지스터로 복원합니다.

이제 CPU는 이전 프로세스의 흔적을 지우고, 새 프로세스가 과거에 멈췄던 바로 그 시점의 상태로 완벽하게 빙의하게 됩니다.

5. 유저 모드로의 복원과 순환의 반복 (The Return to Peace)

모든 준비가 끝나면 커널은 특수 명령어(IRET 등)를 실행합니다.

이 명령어는 CPU의 권한을 다시 유저 모드(CPL=3)로 낮추고, 저장되었던 복귀 주소로 실행 흐름을 던집니다.

이제 새 프로세스는 자신이 잠시 멈췄었다는 사실조차 모른 채 다시 실행을 이어갑니다.

이 모든 하드웨어적 모드 전환, 인터럽트 처리, 스케줄링, 그리고 문맥 교환의 과정은 우리가 인지하지 못하는 찰나의 순간(수 밀리초) 동안 1초에 수백 번씩 반복됩니다.

유저는 여러 프로그램이 동시에 돌아가는 것처럼 느끼지만, 사실 그 이면에는 하드웨어와 커널이 쉴 새 없이 권한을 뺏고 빼앗기는 치열한 상호작용이 존재합니다.

6. 결론적 의의: 왜 이 메커니즘인가?

결국 하드웨어-커널-유저의 상호작용은 시스템의 안정성민주성을 보장하기 위한 거대한 설계입니다.

하드웨어가 권한(이중 모드)을 감시하고, 인터럽트가 실행의 공정성을 확보하며, 커널이 스케줄링을 통해 자원을 중재함으로써,

우리는 악성 코드나 버그가 있는 프로그램 속에서도 시스템 전체가 무너지지 않는 안전한 컴퓨팅 환경을 누릴 수 있는 것입니다.

태그: ,

카테고리: ,

업데이트:

댓글남기기