🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

사용자 모드와 커널 모드 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 28. · 4 Views

사용자 모드와 커널 모드 완벽 가이드

운영체제가 어떻게 응용 프로그램과 시스템을 보호하는지 알아봅니다. 사용자 모드와 커널 모드의 차이, 시스템 콜의 동작 원리, 모드 전환 과정을 실무 예제와 함께 쉽게 설명합니다.


목차

  1. 사용자_모드_vs_커널_모드
  2. 모드_비트의_역할
  3. 시스템_콜이란
  4. API와_시스템_콜_관계
  5. 인터럽트와_모드_전환
  6. 특권_명령어

1. 사용자 모드 vs 커널 모드

어느 날 김개발 씨가 파일을 읽는 프로그램을 작성하다가 이상한 에러를 만났습니다. "Permission denied"라니, 분명히 파일은 존재하는데 왜 접근할 수 없는 걸까요?

선배 박시니어 씨가 다가와 말했습니다. "운영체제의 모드 개념을 알면 이해가 될 거야."

사용자 모드커널 모드는 운영체제가 시스템을 보호하기 위해 나눈 두 가지 실행 환경입니다. 마치 공항에서 일반 승객 구역과 관제탑 구역을 분리하는 것과 같습니다.

사용자 모드에서는 제한된 작업만 가능하고, 커널 모드에서만 하드웨어에 직접 접근할 수 있습니다.

다음 코드를 살펴봅시다.

// 사용자 모드에서 실행되는 일반적인 Java 프로그램
public class FileReadExample {
    public static void main(String[] args) {
        // 사용자 모드: 일반적인 연산은 직접 실행
        int result = 10 + 20;

        // 파일 읽기 시도 - 내부적으로 커널 모드 전환 발생
        try {
            // read() 호출 시 사용자 모드 → 커널 모드 전환
            FileInputStream fis = new FileInputStream("data.txt");
            int data = fis.read();  // 시스템 콜 발생
            fis.close();
        } catch (IOException e) {
            System.out.println("파일 접근 권한 없음");
        }
    }
}

김개발 씨는 입사 3개월 차 주니어 개발자입니다. 오늘도 열심히 코드를 작성하던 중, 파일을 읽는 간단한 프로그램에서 예상치 못한 에러를 만났습니다.

분명히 파일 경로도 맞고, 코드도 정상인데 왜 작동하지 않는 걸까요? 선배 개발자 박시니어 씨가 다가와 화면을 살펴봅니다.

"김개발 씨, 이건 코드 문제가 아니라 운영체제의 보안 체계 때문이에요. 사용자 모드와 커널 모드에 대해 알아야 이해할 수 있어요." 그렇다면 사용자 모드와 커널 모드란 정확히 무엇일까요?

쉽게 비유하자면, 이것은 마치 병원의 구역 구분과 같습니다. 일반 환자들은 대기실과 진료실만 이용할 수 있지만, 의료진은 수술실과 약품 보관실까지 출입할 수 있습니다.

만약 누구나 약품 보관실에 들어갈 수 있다면 큰 사고가 날 수 있겠죠? 컴퓨터도 마찬가지입니다.

사용자 모드는 일반 응용 프로그램이 실행되는 환경입니다. 이 모드에서는 메모리의 특정 영역에만 접근할 수 있고, 하드웨어를 직접 제어하는 것이 불가능합니다.

여러분이 작성하는 Java, Python, JavaScript 프로그램은 모두 사용자 모드에서 실행됩니다. 반면 커널 모드는 운영체제의 핵심 부분이 실행되는 특권 환경입니다.

이 모드에서는 모든 메모리에 접근할 수 있고, CPU의 모든 명령어를 실행할 수 있습니다. 디스크에서 데이터를 읽거나, 네트워크 패킷을 전송하거나, 화면에 그래픽을 출력하는 것 모두 커널 모드에서 처리됩니다.

왜 이렇게 두 모드를 나눠놨을까요? 만약 모든 프로그램이 하드웨어에 직접 접근할 수 있다면 어떤 일이 벌어질까요?

악성 프로그램이 디스크의 모든 데이터를 삭제할 수 있습니다. 버그가 있는 프로그램이 다른 프로그램의 메모리를 덮어쓸 수 있습니다.

실수로 작성한 코드가 시스템 전체를 멈추게 할 수 있습니다. 이런 재앙을 막기 위해 운영체제는 두 모드를 철저히 분리합니다.

위의 코드를 살펴보겠습니다. 10 + 20 같은 단순한 연산은 사용자 모드에서 바로 실행됩니다.

CPU가 직접 계산하면 되니까요. 하지만 FileInputStream으로 파일을 읽으려면 이야기가 달라집니다.

파일은 하드디스크에 저장되어 있고, 하드디스크는 하드웨어입니다. 하드웨어에 접근하려면 반드시 커널 모드로 전환해야 합니다.

Java 프로그램이 read()를 호출하면, 내부적으로 운영체제에게 "이 파일 좀 읽어주세요"라고 요청하는 것입니다. 실제 현업에서는 이 개념이 왜 중요할까요?

서버 프로그램의 성능 최적화를 할 때, 모드 전환 횟수를 줄이는 것이 핵심입니다. 모드 전환은 비용이 드는 작업이기 때문입니다.

파일을 한 바이트씩 읽는 것보다 버퍼를 사용해 한 번에 많이 읽는 것이 훨씬 빠른 이유가 바로 여기에 있습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 운영체제가 파일 접근을 검사하는 거군요!" 사용자 모드와 커널 모드를 이해하면, 프로그램의 성능과 보안을 더 깊이 이해할 수 있습니다.

실전 팁

💡 - 파일이나 네트워크 작업이 많은 프로그램은 버퍼링을 활용하여 모드 전환 횟수를 줄이세요

  • 시스템 콜이 빈번하면 성능 저하가 발생할 수 있으니, 배치 처리를 고려하세요

2. 모드 비트의 역할

박시니어 씨의 설명을 듣던 김개발 씨가 질문했습니다. "그런데 컴퓨터가 지금 어떤 모드인지 어떻게 알아요?" 박시니어 씨가 웃으며 대답했습니다.

"바로 모드 비트라는 것이 있거든."

모드 비트는 CPU 내부에 있는 단 하나의 비트로, 현재 시스템이 사용자 모드인지 커널 모드인지를 나타냅니다. 마치 건물 출입구의 신호등처럼, 이 비트가 0이면 커널 모드, 1이면 사용자 모드입니다.

운영체제는 이 비트를 확인하여 명령어 실행을 허용하거나 거부합니다.

다음 코드를 살펴봅시다.

// 모드 비트의 개념을 보여주는 의사 코드
public class ModeBitSimulation {
    // 모드 비트: 0 = 커널 모드, 1 = 사용자 모드
    private static int modeBit = 1;

    public static void executeInstruction(String instruction) {
        if (instruction.equals("HALT") || instruction.equals("IO_ACCESS")) {
            // 특권 명령어는 커널 모드에서만 실행 가능
            if (modeBit == 0) {
                System.out.println("특권 명령어 실행: " + instruction);
            } else {
                System.out.println("오류: 사용자 모드에서 특권 명령어 실행 불가");
                // 트랩 발생 → 운영체제로 제어권 이동
            }
        } else {
            // 일반 명령어는 양쪽 모드에서 실행 가능
            System.out.println("일반 명령어 실행: " + instruction);
        }
    }
}

김개발 씨는 궁금증이 생겼습니다. 소프트웨어적으로 "나는 커널 모드야"라고 거짓말하면 어떻게 될까요?

악성 프로그램이 커널인 척하면 모든 보안이 무력화되지 않을까요? 박시니어 씨가 설명을 이어갑니다.

"좋은 질문이야. 바로 그래서 모드 비트가 하드웨어 수준에서 존재하는 거야." 모드 비트는 CPU 내부의 상태 레지스터에 있는 단 1비트의 플래그입니다.

이것은 소프트웨어로 마음대로 바꿀 수 없습니다. 오직 CPU 자체가 특정 조건에서만 이 비트를 변경할 수 있습니다.

비유하자면, 모드 비트는 마치 자동차의 이모빌라이저와 같습니다. 자동차 키에 내장된 칩이 맞아야만 시동이 걸리듯이, 올바른 절차를 통해야만 커널 모드로 전환할 수 있습니다.

열쇠를 복제해도 칩까지 복제하기는 어렵죠. 모드 비트가 0일 때, CPU는 커널 모드입니다.

이 상태에서는 모든 명령어를 실행할 수 있습니다. 메모리의 어떤 주소든 접근할 수 있고, 하드웨어를 직접 제어할 수 있습니다.

운영체제의 핵심 코드가 이 모드에서 실행됩니다. 모드 비트가 1일 때, CPU는 사용자 모드입니다.

이 상태에서는 제한된 명령어만 실행할 수 있습니다. 특권 명령어를 실행하려고 하면 CPU가 즉시 트랩을 발생시킵니다.

트랩이 발생하면 자동으로 모드 비트가 0으로 바뀌고 운영체제에게 제어권이 넘어갑니다. 위의 코드에서 이 개념을 시뮬레이션하고 있습니다.

HALT나 IO_ACCESS 같은 특권 명령어는 modeBit가 0일 때만 실행됩니다. 사용자 모드에서 이런 명령어를 실행하려고 하면 오류가 발생합니다.

실제로 모드 비트가 바뀌는 시점은 언제일까요? 크게 두 가지 경우가 있습니다.

첫째, 인터럽트나 트랩이 발생했을 때입니다. 시스템 콜을 호출하거나, 하드웨어 인터럽트가 발생하면 CPU는 자동으로 모드 비트를 0으로 설정합니다.

이렇게 해서 운영체제 코드가 커널 모드에서 실행될 수 있습니다. 둘째, 운영체제가 사용자 프로그램으로 복귀할 때입니다.

인터럽트 처리가 끝나면 운영체제는 모드 비트를 1로 설정하고 사용자 프로그램에게 제어권을 돌려줍니다. 이 과정은 완전히 하드웨어가 제어합니다.

사용자 프로그램이 아무리 발버둥 쳐도 모드 비트를 직접 바꿀 방법은 없습니다. 이것이 운영체제 보안의 핵심 토대입니다.

김개발 씨가 고개를 끄덕입니다. "결국 하드웨어 수준에서 보안이 보장되는 거군요.

소프트웨어만으로는 우회할 수 없겠네요."

실전 팁

💡 - 모드 비트는 하드웨어 수준에서 관리되므로 소프트웨어로 조작할 수 없습니다

  • 디버깅 시 커널 패닉이 발생하면 모드 전환 과정에서 문제가 생겼을 가능성이 있습니다

3. 시스템 콜이란

김개발 씨가 다시 질문했습니다. "그러면 사용자 프로그램이 파일을 읽으려면 어떻게 해야 하는 거예요?

직접 못 하잖아요." 박시니어 씨가 대답했습니다. "바로 시스템 콜을 사용하는 거야.

운영체제한테 대신 해달라고 부탁하는 거지."

시스템 콜은 사용자 프로그램이 운영체제의 서비스를 요청하는 공식적인 인터페이스입니다. 마치 은행 창구에서 업무를 처리하듯이, 프로그램은 시스템 콜을 통해 파일 읽기, 네트워크 통신, 프로세스 생성 등의 작업을 운영체제에게 요청합니다.

이때 사용자 모드에서 커널 모드로 전환이 일어납니다.

다음 코드를 살펴봅시다.

// Java에서 시스템 콜이 발생하는 다양한 상황
import java.io.*;
import java.net.*;

public class SystemCallExamples {
    public static void main(String[] args) throws Exception {
        // 1. 파일 시스템 콜: open(), read(), write(), close()
        FileOutputStream fos = new FileOutputStream("output.txt");
        fos.write("Hello".getBytes());  // write() 시스템 콜
        fos.close();  // close() 시스템 콜

        // 2. 프로세스 시스템 콜: fork(), exec()
        ProcessBuilder pb = new ProcessBuilder("ls", "-la");
        Process p = pb.start();  // fork() + exec() 시스템 콜

        // 3. 네트워크 시스템 콜: socket(), connect(), send()
        Socket socket = new Socket("example.com", 80);  // socket() + connect()
        socket.close();  // close() 시스템 콜

        // 4. 메모리 시스템 콜: brk(), mmap()
        byte[] largeArray = new byte[1024 * 1024];  // 내부적으로 mmap() 가능
    }
}

김개발 씨는 이제 조금씩 그림이 그려지기 시작했습니다. 사용자 모드에서는 하드웨어에 직접 접근할 수 없다는 것을 알았습니다.

그렇다면 어떻게 파일을 읽고, 네트워크로 데이터를 보내는 걸까요? 박시니어 씨가 설명합니다.

"운영체제가 대행 서비스를 제공하는 거야. 시스템 콜이라는 창구를 통해서 말이지." 시스템 콜은 사용자 프로그램과 운영체제 사이의 공식적인 대화 창구입니다.

비유하자면, 시스템 콜은 마치 정부 민원 창구와 같습니다. 여권을 발급받으려면 직접 인쇄기를 돌리는 게 아니라, 민원 창구에서 신청서를 내고 담당자가 처리해주길 기다리죠.

운영체제도 마찬가지입니다. 파일을 읽고 싶으면 직접 하드디스크를 건드리는 게 아니라, read() 시스템 콜을 호출합니다.

그러면 운영체제가 대신 하드디스크에서 데이터를 읽어와서 프로그램에게 전달해줍니다. 시스템 콜의 종류는 크게 다섯 가지로 나눌 수 있습니다.

첫째, 프로세스 제어입니다. fork()로 새 프로세스를 만들고, exec()로 다른 프로그램을 실행하고, exit()로 프로세스를 종료합니다.

둘째, 파일 관리입니다. open()으로 파일을 열고, read()로 읽고, write()로 쓰고, close()로 닫습니다.

여러분이 FileInputStream을 사용할 때마다 이런 시스템 콜이 호출됩니다. 셋째, 장치 관리입니다.

프린터나 USB 같은 장치를 제어하는 시스템 콜입니다. 넷째, 정보 유지입니다.

시간 정보를 얻는 time(), 시스템 정보를 얻는 sysinfo() 같은 것들입니다. 다섯째, 통신입니다.

socket()으로 네트워크 연결을 만들고, send()로 데이터를 보내고, recv()로 받습니다. 위의 코드를 보면, 평범해 보이는 Java 코드 뒤에서 수많은 시스템 콜이 일어나고 있음을 알 수 있습니다.

FileOutputStream을 만드는 순간 open() 시스템 콜이, write()를 호출하면 write() 시스템 콜이, close()를 호출하면 close() 시스템 콜이 발생합니다. 시스템 콜이 호출되면 무슨 일이 벌어질까요?

먼저 CPU에 트랩이 발생합니다. 모드 비트가 0으로 바뀌면서 커널 모드로 전환됩니다.

운영체제가 요청을 처리한 후 결과를 반환하고, 다시 모드 비트가 1로 바뀌면서 사용자 모드로 돌아옵니다. 이 과정은 꽤 비용이 듭니다.

모드 전환, 레지스터 저장 및 복원, 커널 코드 실행 등 여러 단계를 거쳐야 합니다. 그래서 시스템 콜 횟수를 줄이는 것이 성능 최적화의 핵심입니다.

김개발 씨가 이해했다는 표정으로 말합니다. "그래서 BufferedReader를 쓰면 빠른 거군요.

한 번에 많이 읽어서 시스템 콜 횟수를 줄이니까요!"

실전 팁

💡 - 시스템 콜은 비용이 크므로, 버퍼링을 활용하여 호출 횟수를 최소화하세요

  • Linux에서 strace 명령어로 프로그램의 시스템 콜을 추적할 수 있습니다

4. API와 시스템 콜 관계

김개발 씨가 코드를 다시 보며 질문했습니다. "그런데 저는 read() 같은 함수를 직접 호출한 적이 없는데요?

그냥 Java의 FileInputStream.read()를 썼을 뿐인데..." 박시니어 씨가 웃으며 답했습니다. "좋은 관찰이야.

바로 API와 시스템 콜의 관계를 이해해야 할 때가 됐네."

API는 시스템 콜을 감싸는 편리한 인터페이스입니다. 프로그래머는 복잡한 시스템 콜을 직접 다루지 않고, 언어나 프레임워크가 제공하는 API를 사용합니다.

마치 자동차를 운전할 때 엔진의 복잡한 작동 원리를 몰라도 핸들과 페달만 알면 되는 것처럼, API는 시스템 콜의 복잡함을 숨겨줍니다.

다음 코드를 살펴봅시다.

// API → 라이브러리 → 시스템 콜의 계층 구조
public class ApiToSystemCall {
    public static void main(String[] args) throws Exception {
        // 1단계: 개발자가 사용하는 API (Java API)
        FileInputStream fis = new FileInputStream("data.txt");
        int data = fis.read();  // Java API 호출
        fis.close();

        // 내부적으로 일어나는 일:
        // 2단계: Java API → JNI (Java Native Interface)
        // 3단계: JNI → C 라이브러리 (libc의 read() 함수)
        // 4단계: C 라이브러리 → 실제 시스템 콜 (syscall)

        // Linux에서의 시스템 콜 번호 예시:
        // read = 0, write = 1, open = 2, close = 3
        // 실제로는 syscall(0, fd, buffer, count) 형태로 호출됨
    }
}

김개발 씨의 질문은 핵심을 찌르는 것이었습니다. 실제로 대부분의 개발자는 시스템 콜을 직접 호출하지 않습니다.

Java에서 FileInputStream.read()를 호출하면, 이것이 어떻게 운영체제의 read() 시스템 콜로 이어지는 걸까요? 박시니어 씨가 화이트보드에 그림을 그리며 설명합니다.

"여기에는 여러 계층이 있어. 양파 껍질처럼 말이지." 가장 바깥 껍질은 프로그래밍 언어 API입니다.

Java의 FileInputStream, Python의 open(), JavaScript의 fs.readFile() 같은 것들이죠. 개발자는 이 계층에서 작업합니다.

사용하기 쉽고, 에러 처리도 잘 되어 있습니다. 그 안쪽에는 표준 C 라이브러리가 있습니다.

대부분의 프로그래밍 언어는 내부적으로 C 라이브러리(libc)를 사용합니다. Java의 FileInputStream.read()는 JNI를 통해 C의 read() 함수를 호출합니다.

가장 안쪽에 시스템 콜이 있습니다. C 라이브러리의 read() 함수는 내부적으로 실제 시스템 콜을 호출합니다.

Linux에서는 syscall이라는 특별한 명령어를 사용하여 커널에 요청을 전달합니다. 왜 이렇게 여러 계층을 두는 걸까요?

몇 가지 중요한 이유가 있습니다. 첫째, 이식성 때문입니다.

시스템 콜은 운영체제마다 다릅니다. Linux의 시스템 콜과 Windows의 시스템 콜은 완전히 다릅니다.

하지만 Java API는 동일합니다. FileInputStream.read()는 Linux에서도, Windows에서도, macOS에서도 똑같이 동작합니다.

Java 런타임이 각 운영체제에 맞는 시스템 콜을 호출해주기 때문입니다. 둘째, 편의성 때문입니다.

시스템 콜은 매우 저수준입니다. 버퍼 관리, 에러 처리, 리소스 정리 등을 모두 개발자가 직접 해야 합니다.

API는 이런 복잡한 부분을 대신 처리해줍니다. 셋째, 추상화 때문입니다.

시스템 콜의 세부 사항이 바뀌어도 API는 그대로 유지될 수 있습니다. 새 버전의 운영체제가 나와서 시스템 콜 방식이 바뀌어도, 라이브러리만 업데이트하면 됩니다.

위의 코드에서 보듯이, 개발자가 fis.read()를 호출하면 내부적으로 4단계를 거칩니다. Java API, JNI, C 라이브러리, 그리고 마지막으로 실제 시스템 콜까지.

이 모든 과정이 눈 깜짝할 사이에 일어납니다. 재미있는 사실이 있습니다.

실제 시스템 콜은 숫자로 구분됩니다. Linux에서 read는 0번, write는 1번, open은 2번입니다.

C 라이브러리는 이 번호를 사용해서 커널에 요청합니다. 김개발 씨가 감탄합니다.

"결국 우리가 쓰는 모든 파일 관련 코드는 깊숙한 곳에서 시스템 콜로 이어지는 거군요. API라는 포장지로 예쁘게 감싸져 있을 뿐이고요."

실전 팁

💡 - 플랫폼 독립적인 코드를 작성하려면 시스템 콜을 직접 호출하지 말고 표준 API를 사용하세요

  • 성능이 중요한 경우 API 내부에서 몇 번의 시스템 콜이 발생하는지 파악하면 도움이 됩니다

5. 인터럽트와 모드 전환

박시니어 씨가 화이트보드를 가리키며 말했습니다. "자, 이제 핵심적인 부분이야.

사용자 모드에서 커널 모드로 어떻게 전환되는지 알아볼 차례야." 김개발 씨가 집중합니다. "인터럽트라는 거죠?"

인터럽트는 CPU에게 "지금 하던 일을 멈추고 긴급한 일을 처리하라"고 알리는 신호입니다. 인터럽트가 발생하면 CPU는 자동으로 커널 모드로 전환하고, 인터럽트 핸들러를 실행합니다.

마치 수업 중에 화재 경보가 울리면 모든 것을 멈추고 대피하는 것처럼, 인터럽트는 즉각적인 대응을 요구합니다.

다음 코드를 살펴봅시다.

// 인터럽트 처리 과정을 보여주는 의사 코드
public class InterruptHandling {
    // 인터럽트 벡터 테이블 (운영체제가 부팅 시 설정)
    private static Runnable[] interruptVector = new Runnable[256];

    static {
        interruptVector[0x80] = () -> handleSystemCall();  // 시스템 콜
        interruptVector[0x21] = () -> handleKeyboard();    // 키보드
        interruptVector[0x20] = () -> handleTimer();       // 타이머
    }

    // 인터럽트 발생 시 CPU가 수행하는 동작
    public static void onInterrupt(int interruptNumber) {
        // 1. 현재 상태 저장 (PC, 레지스터, 플래그)
        saveCurrentState();
        // 2. 모드 비트를 0으로 설정 (커널 모드로 전환)
        setModeBit(0);
        // 3. 인터럽트 벡터 테이블에서 핸들러 찾아 실행
        interruptVector[interruptNumber].run();
        // 4. 상태 복원 및 사용자 모드로 복귀
        restoreState();
        setModeBit(1);
    }
}

모드 전환이 일어나는 정확한 메커니즘을 이해하면, 운영체제의 동작 원리가 한눈에 들어옵니다. 핵심은 바로 인터럽트입니다.

박시니어 씨가 설명을 시작합니다. "인터럽트는 크게 두 종류야.

하드웨어 인터럽트소프트웨어 인터럽트." 하드웨어 인터럽트는 외부 장치가 CPU에게 보내는 신호입니다. 키보드를 누르면 키보드 컨트롤러가 인터럽트를 발생시킵니다.

마우스를 움직이면 마우스가 인터럽트를 보냅니다. 네트워크 카드에 패킷이 도착하면 네트워크 카드가 인터럽트를 발생시킵니다.

이런 신호가 오면 CPU는 하던 일을 멈추고 해당 장치를 처리합니다. 소프트웨어 인터럽트(트랩)는 프로그램이 의도적으로 발생시키는 인터럽트입니다.

시스템 콜이 대표적입니다. 프로그램이 파일을 읽고 싶으면, 특별한 명령어를 실행하여 트랩을 발생시킵니다.

이것이 바로 사용자 모드에서 커널 모드로 전환되는 유일한 합법적인 방법입니다. 인터럽트가 발생하면 CPU는 다음 단계를 자동으로 수행합니다.

첫째, 현재 상태를 저장합니다. 프로그램 카운터(다음에 실행할 명령어 주소), 레지스터 값, 상태 플래그 등을 스택에 저장합니다.

나중에 복귀할 때 필요하기 때문입니다. 둘째, 모드 비트를 0으로 설정합니다.

이제 CPU는 커널 모드입니다. 셋째, 인터럽트 벡터 테이블에서 해당 인터럽트의 핸들러 주소를 찾습니다.

인터럽트 벡터 테이블은 운영체제가 부팅할 때 미리 설정해둔 것으로, 각 인터럽트 번호에 대응하는 처리 루틴의 주소가 담겨 있습니다. 넷째, 인터럽트 핸들러를 실행합니다.

이 핸들러는 운영체제 코드입니다. 시스템 콜이면 요청된 작업을 처리하고, 키보드 인터럽트면 눌린 키 정보를 버퍼에 저장합니다.

다섯째, 처리가 끝나면 저장해둔 상태를 복원하고 모드 비트를 1로 설정합니다. 프로그램은 아무 일도 없었던 것처럼 하던 작업을 계속합니다.

위의 코드는 이 과정을 Java로 시뮬레이션한 것입니다. 실제로는 이 모든 것이 하드웨어와 운영체제에 의해 마이크로초 단위로 처리됩니다.

흥미로운 점은 타이머 인터럽트입니다. 운영체제는 주기적으로(보통 수 밀리초마다) 타이머 인터럽트를 발생시킵니다.

이 인터럽트가 발생하면 운영체제가 제어권을 얻어서 다른 프로세스로 전환할 수 있습니다. 이것이 바로 멀티태스킹의 비밀입니다.

김개발 씨가 이해했다는 표정입니다. "그래서 무한 루프를 돌려도 컴퓨터가 멈추지 않는 거군요.

타이머 인터럽트가 강제로 제어권을 가져가니까요!"

실전 팁

💡 - 인터럽트 처리는 가능한 빠르게 끝내야 합니다. 오래 걸리면 시스템 전체가 느려집니다

  • 인터럽트 핸들러 내에서 또 다른 인터럽트가 발생할 수 있으므로, 중첩 인터럽트 처리를 고려해야 합니다

6. 특권 명령어

마지막으로 박시니어 씨가 중요한 개념을 꺼냈습니다. "지금까지 배운 모든 것의 핵심이야.

바로 특권 명령어라는 거지. 커널 모드에서만 실행할 수 있는 명령어들 말이야."

특권 명령어는 커널 모드에서만 실행할 수 있는 CPU 명령어입니다. 입출력 제어, 인터럽트 관리, 메모리 보호 설정 등 시스템의 핵심적인 작업을 수행합니다.

사용자 모드에서 특권 명령어를 실행하려고 하면 CPU가 즉시 트랩을 발생시켜 운영체제에게 알립니다.

다음 코드를 살펴봅시다.

// 특권 명령어와 비특권 명령어의 구분
public class PrivilegedInstructions {

    // 특권 명령어 (커널 모드에서만 실행 가능)
    // - I/O 명령어: IN, OUT (포트 입출력)
    // - 인터럽트 제어: CLI, STI (인터럽트 비활성화/활성화)
    // - 메모리 관리: MOV to CR3 (페이지 테이블 설정)
    // - 시스템 제어: HLT (CPU 정지), LIDT (인터럽트 테이블 로드)

    // 비특권 명령어 (사용자 모드에서도 실행 가능)
    public static void normalOperations() {
        int a = 10;          // MOV (데이터 이동)
        int b = a + 5;       // ADD (산술 연산)
        boolean c = (b > 10); // CMP (비교)

        // 조건문, 반복문 - 모두 비특권 명령어로 처리
        if (c) {
            for (int i = 0; i < 10; i++) {
                b += i;      // 일반 연산
            }
        }
    }

    // 이 함수는 사용자 모드에서 직접 실행 불가
    // (운영체제만 실행 가능)
    // haltCPU() { HLT; }  // CPU 정지 명령 - 특권 명령어
}

김개발 씨는 지금까지의 내용을 되새기며 질문합니다. "결국 커널 모드에서만 할 수 있는 특별한 작업이 있다는 거잖아요.

구체적으로 어떤 건가요?" 박시니어 씨가 리스트를 적기 시작합니다. "이것들을 특권 명령어라고 불러.

보안의 최전선이지." 특권 명령어는 크게 몇 가지 범주로 나눌 수 있습니다. 첫째, 입출력 명령어입니다.

CPU가 하드웨어 장치와 직접 통신하는 IN, OUT 같은 명령어입니다. 만약 이 명령어가 사용자 모드에서 실행 가능하다면, 악성 프로그램이 하드디스크에 직접 쓰기를 해서 모든 데이터를 지울 수 있습니다.

둘째, 인터럽트 제어 명령어입니다. CLI는 인터럽트를 비활성화하고, STI는 다시 활성화합니다.

만약 사용자 프로그램이 인터럽트를 끌 수 있다면, 무한 루프를 돌면서 시스템 전체를 독점할 수 있습니다. 타이머 인터럽트도 안 오니까 운영체제가 개입할 수 없게 되죠.

셋째, 메모리 관리 명령어입니다. 페이지 테이블을 설정하거나, 메모리 보호 영역을 변경하는 명령어입니다.

이것이 사용자 모드에서 가능하다면, 다른 프로그램의 메모리를 마음대로 읽고 쓸 수 있게 됩니다. 넷째, 시스템 제어 명령어입니다.

HLT는 CPU를 정지시키고, 리셋 명령어는 컴퓨터를 재부팅합니다. 당연히 일반 프로그램이 이런 명령어를 실행하면 안 되겠죠.

반면에 일반적인 비특권 명령어는 사용자 모드에서도 자유롭게 실행할 수 있습니다. 덧셈, 뺄셈 같은 산술 연산, 변수에 값을 저장하는 데이터 이동, 조건 비교, 함수 호출 등이 여기에 해당합니다.

위의 코드에서 보듯이, 일반적인 프로그래밍에서 사용하는 대부분의 연산은 비특권 명령어입니다. CPU는 어떻게 특권 명령어인지 아닌지 판단할까요?

바로 앞서 배운 모드 비트를 확인합니다. CPU가 명령어를 실행하기 전에 모드 비트를 확인하고, 사용자 모드인데 특권 명령어를 실행하려고 하면 즉시 트랩을 발생시킵니다.

트랩이 발생하면 운영체제가 제어권을 얻습니다. 운영체제는 "이 프로그램이 불법적인 명령을 실행하려고 했다"는 것을 알게 되고, 보통은 해당 프로그램을 종료시킵니다.

이것이 바로 "Segmentation fault"나 "Access violation" 같은 에러의 원인입니다. 김개발 씨가 모든 것이 연결되는 느낌을 받았습니다.

"모드 비트, 특권 명령어, 인터럽트... 이 세 가지가 운영체제 보안의 삼각 축이군요!" 박시니어 씨가 고개를 끄덕입니다.

"맞아. 하드웨어 수준에서 이 세 가지가 보장되기 때문에, 운영체제가 시스템을 안전하게 보호할 수 있는 거야.

이걸 이해하면 시스템 프로그래밍의 기초가 탄탄해지지." 김개발 씨는 오늘 배운 내용을 노트에 정리하며 뿌듯한 미소를 지었습니다. 운영체제가 어떻게 프로그램들을 관리하고 보호하는지, 그 비밀의 일부를 알게 된 것입니다.

실전 팁

💡 - 시스템 프로그래밍을 할 때 Segmentation fault가 발생하면, 보호된 메모리 영역에 접근했는지 확인하세요

  • 드라이버 개발 시에는 특권 명령어를 사용해야 하므로 반드시 커널 모드에서 실행되어야 합니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#OperatingSystem#UserMode#KernelMode#SystemCall#Interrupt#Operating System

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.

함께 보면 좋은 카드 뉴스