CSAPP 8장 | 예외는 어떻게 정의되고, 어떻게 처리될까?

맹쥐·2025년 4월 22일

정글-개발일지

목록 보기
19/24
post-thumbnail

지난 이야기 ➡️ CSAPP 8장 준비 | 예외적인 제어 흐름(ECF)이란? – 시스템 흐름이 깨지는 순간들


지난 글에서 예외상황이 무엇인지, 이를 어떻게 처리하는지 살펴보았다.
이번에는

  • 실제로 예외상황을 누가 정의하고, 처리하는 것인지,
  • 예외상황의 종류가 어떤 것이 있는지

살펴보자.

컴퓨터시스템은 크게 세가지 계층인 하드웨어/운영체제/응용프로그램 으로 나눌 수 있으며,
예외적인 제어흐름은 컴퓨터 시스템의 모든 수준에서 발생한다고 언급한 적이 있다.

한 시스템 내에서 가능한 예외상황의 종류마다 중복되지 않는 양의 정수를 예외번호로 할당하고 있다.

예외번호는 누가 정의하고, 누가 처리할까?

컴퓨터 시스템은 크게 하드웨어 / 운영체제 / 응용프로그램 세 가지 계층으로 나뉘며,
예외적인 제어 흐름(ECF)은 이 모든 계층에서 발생할 수 있다.
(👉 지난 글에서 다뤘던 내용!)

시스템은 다양한 예외 상황들을 구분하기 위해, 각 예외에 고유한 예외 번호(양의 정수) 를 부여해 관리한다.


하지만 여기서 한 가지 꼭 짚고 넘어가야 할 점이 있다.

예외를 정의하는 주체와
예외가 실제로 발생했을 때 처리하는 주체는 다를 수 있다!

(CSAPP 책에서는 이 부분이 명확히 나뉘어 있지 않아서 처음 볼 때 매우 헷갈렸다.😵‍💫)


예외번호는 누가 정의할까?

예외번호는 시스템의 구조에 따라 하드웨어와 소프트웨어가 역할을 나눠 정의한다.
각자의 책임을 명확히 하기 때문이다.

예시를 살펴보자.

예) 예외 번호의 분배 (일반적인 x86 시스템 기준)

1번 ~ 31번: 하드웨어가 정의한 예외 번호  
→ 예: 0번은 "0으로 나누기", 14번은 "페이지 폴트(page fault)"

32번 ~ 127번: 운영체제가 정의한 예외 번호  
→ 예: 타이머 인터럽트, 키보드 입력, 디스크 I/O 등

128번 이상: 사용자 프로그램이 사용하는 번호  
→ 예: 시스템 호출 시 128번 사용 (int $0x80)

※ 정확한 번호 범위는 시스템에 따라 달라질 수 있음.

이렇게 번호를 구분해두면, 서로 충돌 없이 예외를 처리할 수 있고,
운영체제는 예외번호만 보고 어떤 계층에서 발생했는지 빠르게 판단할 수 있다.

예외는 누가 처리할까?

예외가 실제로 발생했을 때는,
그걸 직접 처리하는 주체는 대부분 운영체제(OS) 다.

  1. 예외 발생 → CPU는 예외 번호를 확인
  2. 예외 테이블(IDT)에서 해당 번호에 연결된 핸들러 주소를 찾음
  3. 그 핸들러 주소로 점프(jump)
  4. 해당 위치에 있는 코드(예외 처리 루틴)는 운영체제가 작성한 것

📌 예외번호는 하드웨어와 OS가 함께 정의하지만,
예외가 실제로 발생했을 때 행동하는 건 운영체제다.

요약 정리

구분예외 번호 정의 주체예외 처리 주체예시
예외 번호 0~31하드웨어(CPU)운영체제(OS)Divide Error, Page Fault
예외 번호 32~127운영체제(OS)운영체제(OS)타이머, 키보드, 디스크 등
예외 번호 128 이상응용프로그램운영체제(OS)시스템 호출 (int $0x80)

예외상황의 정의와 처리 주체에 대해 알아봤다.
이제는 예외 상황을 성격에 따라 어떻게 분류하는지 살펴보자.


🚩 예외의 종류

예외 상황은 크게 두 가지 범주로 나눌 수 있다.

  • 외부에서 발생하는 👉 인터럽트 (Interrupt)
  • CPU가 실행 중인 명령어 때문에 발생하는 👉 예외 (Exception)

이 중 예외(Exception) 는 다시 세 가지로 나뉜다.

  1. 트랩 (Trap)
  2. 오류, 폴트 (Fault)
  3. 중단, 어보트 (Abort)

즉, 총 네 가지 종류로 구분할 수 있다.

1.인터럽트

인터럽트는 컴퓨터 외부(입출력 디바이스)에서 보내는 신호 때문에 발생한다.
지금 CPU가 뭐 하고 있든 말든,그냥 갑자기 툭! 끼어드는 거다.
그래서 인터럽트는 비동기적(asynchronous) 으로 발생한다고 한다.

<비동기/동기>

  • 비동기 (자기 잘못 없음)
    지금 CPU가 어떤 명령어를 실행 중이든 상관없이 발생
    → 예: 키보드를 누르거나 마우스를 움직이는 순간 발생하는 인터럽트

  • 동기 (자기 잘못)
    CPU가 지금 실행 중인 명령어 때문에 발생
    → 예: 0으로 나누기!, 잘못된 메모리 접근 같은 오류
    </비동기/동기>

현재 인스트럭션을 실행하다가 인터럽트가 발생하면,
예외번호를 읽어 적절한 핸들러를 호출한다.
그다음 할일을 다 끝내면 다음 인스트럭션으로 돌아간다.


2. 트랩과 시스템콜

트랩은 일부러 흐름을 바꾸기 위한 예외다.
오류가 아니라, 말 그대로 “의도된 점프”에 가깝다.

대표적인 예가 바로 시스템 콜(system call) 이다.
그런데 시스템 콜을 이해하려면, 먼저 유저 모드와 커널 모드에 대해 간단히 짚고 넘어가야 한다.

💡 유저 모드와 커널 모드

사용자 프로그램은 마음대로 운영체제의 핵심 기능에 접근할 수 없다.
왜냐하면 운영체제는 컴퓨터 전체를 관리하는 아주 중요한 영역이기 때문이다.

마치 “컴퓨터의 관리실” 같은 공간인데,
아무나 막 들어가게 하면 컴퓨터가 망가질 수도 있으니까,컴퓨터는 두 개의 실행 모드를 만들어서 구분한다.

[ 유저 모드 ]
→ 일반 사용자 프로그램이 동작하는 안전한 공간

[ 커널 모드 ]
→ 운영체제만 접근할 수 있는 강력한 관리자 권한 영역

즉, 우리가 printf() 같은 함수를 호출할 때
그 내부적으로는 운영체제의 파일 출력 기능을 쓰기 때문에,
유저 모드에서는 직접 할 수 없고, 커널 모드의 도움이 필요하다.

이럴 때 사용하는 게 바로 시스템 콜(system call) 이다.
시스템 콜은 "운영체제야, 이 작업 좀 대신 해줘!" 하는 공식적인 요청 방식이다.

이때 유저 모드에서 커널 모드로 넘어갈 수 있는 통로를 열어주는 역할이 바로 트랩(Trap) 이다.

트랩 명령어를 통해 운영체제에게 요청을 보내면,
운영체제가 해당 작업을 처리한 뒤 다시 유저 모드로 돌아온다.


3. 오류 (fault)

오류(fault)는 핸들러가 정정할 수 있다는 가정하에 발생한다.
쉽게 말하면, 복구 시도는 해본다.

대표적인 예로 페이지 폴트(Page Fault) 가 있다.
페이지 폴트는, 프로그램이 아직 메모리에 로드되지 않은 주소에 접근하려 할 때 발생한다.
운영체제는 디스크에서 해당 페이지를 메모리로 불러오고,
그 후 CPU는 같은 명령어부터 다시 실행하게 된다.



📌 다만, 모든 fault가 복구 가능한 것은 아니다.
예를 들어 잘못된 포인터를 참조한 경우처럼,
운영체제가 판단하기에 복구 불가능한 상황이면

프로그램을 강제로 종료시키고, Segmentation Fault 같은 메시지를 띄운다.

이처럼 fault는 “복구 가능성을 열어둔 예외”일 뿐이며,
실제 복구 여부는 운영체제가 판단한다.


4. 중단 (abort)

중단(Abort)은 진짜 심각한 문제일 때, 고칠 수 없고 그냥 멈춰야 할 때 발생하는 예외이다.

앞서 설명했던 fault는 복구 시도라도 해보는 예외였지만,
abort는 아예 복구 시도조차 하지 않고 프로그램을 강제 종료시킨다.

어떤 상황에서 중단이 발생할까?

  • 하드웨어 오류 (예: 메모리 칩 고장, 버스 에러)
  • 중요 시스템 구조의 손상 (예: 커널 내부 구조가 손상됨)
  • 메모리 보호 테이블 자체가 깨짐 (→ 오류를 감지할 기반조차 없는 상태)

이런 경우에는 운영체제 입장에서
❗️“이건 내가 손쓸 수 있는 영역이 아니야.”
라고 판단하고, 즉시 프로그램을 종료하게 된다.

profile
이유민

2개의 댓글

comment-user-thumbnail
2025년 4월 22일

와 예외처리란 이런거구나!

1개의 답글