💡 ECF란?
💥 Exceptional Control Flow (예외적인 제어 흐름)
평소 흐름대로 진행되던 프로그램이 갑자기 다른 곳으로 “점프”해서 실행되는 현상
ECF 종류 | 설명 | 예시 |
인터럽트 | 외부 장치가 CPU를 호출함 | 키보드 입력, 타이머 |
예외 (Exception) | CPU가 실행 중 에러 감지 | 0으로 나누기, 페이지 폴트 |
시스템 콜 (Trap) | 프로그램이 OS에 서비스 요청 | read(), write() 등 |
시그널 | OS나 다른 프로세스가 프로그램에 알림 | Ctrl+C, SIGCHLD 등 |
비지역 점프 | 함수 호출 스택 무시하고 점프 | setjmp, longjmp, throw/catch |
🧠 왜 중요하냐?
✔ 운영체제가 동작하는 기본 원리
- 모든 입출력, 메모리 관리, 프로세스 전환은 결국 ECF 덕분에 가능함.
- 시스템 콜도 trap이라는 ECF의 일종.
✔ 프로세스/스레드 전환
- 다른 프로세스로 제어권을 넘기는 문맥 전환(context switch) 자체도 ECF!
✔ 네트워크, 디스크, 타이머 등 외부 이벤트 처리
- 예외 흐름이 없으면 비동기 처리 자체가 불가능
8.1 예외상황
💥 예외(Exception):
💡 예외는 CPU가 어떤 이벤트(문제 or 신호)를 감지했을 때 실행 흐름을 갑자기 튀게 만드는 것
*_→ *_정상 흐름 중 발생한 중요한 사건에 반응하는 방법
🧠 예외의 정체: 그냥 갑자기 “점프”
- CPU가 명령어를 실행하다가, 뭔가 문제가 생기거나 외부 신호가 들어오면→ 운영체제에 알려줘야 함!
- → “이건 평범한 흐름이 아니다!”
그래서 CPU는 예외 벡터(예외 테이블)를 참고해서
해당 예외에 대응되는 OS 함수(예외 처리 핸들러)로 점프!
📦 예외가 발생하는 두 가지 상황
상황 | 예시 |
🧠 내부 이벤트 (명령어 자체 문제) | - 0으로 나누기- 페이지 폴트- 산술 오버플로우 |
⚙️ 외부 이벤트 (명령어와 직접 관련 X) | - 타이머 인터럽트- 디스크 I/O 완료- 네트워크 패킷 수신 |
✅ 예외 처리 후 흐름의 3가지 경우
- 현재 명령어로 돌아가기
- 예: 페이지 폴트 → 메모리 로드하고 → 그 명령 다시 실행
- 다음 명령어로 넘어가기
- 예: 디버깅용 트랩 → 감시 끝났으니 그냥 다음으로
- 프로그램 종료
- 예: 0으로 나누기, 권한 오류 등 → 복구 불가 상황
8.1.1 예외처리
예외 처리 과정
단계 | 설명 |
1. 예외 발생 | CPU가 어떤 이벤트(에러, 인터럽트 등)를 감지함 |
2. 예외번호 결정 | 각 예외마다 고유 번호가 있음 (예: 0 → divide by zero) |
3. 예외 테이블 조회 | 예외번호를 인덱스로 사용해서 핸들러 주소 확인 |
4. 예외 핸들러 호출 | 운영체제의 커널 코드로 점프 |
5. 핸들러 처리 | 문제 처리 후 흐름 복귀 또는 종료 |
🎯 예외번호는 누가 정하나?
종류 | 예시 | 정하는 주체 |
하드웨어 예외 | divide by zero, 페이지 폴트 등 | CPU 설계자 |
소프트웨어 예외 | 시스템 콜, I/O 인터럽트 등 | OS 설계자 |
💡 예외 테이블과 베이스 레지스터
- 예외 테이블: 예외번호(k)에 따라 핸들러 주소를 저장해둔 점프 테이블
- 베이스 레지스터: 예외 테이블이 메모리 어디 있는지 저장된 특수 CPU 레지스터
- 예외 발생 → 주소 = 베이스 + (k * 항목 크기)로 핸들러 주소 계산
⚠️ 예외 처리와 함수 호출의 차이점
항목 | 함수 호출 | 예외 처리 |
리턴주소 저장 | 무조건 다음 인스트럭션 | 예외 종류에 따라 현재 or 다음 |
저장 위치 | 사용자 스택 | 커널 스택 |
모드 전환 | 없음 | 사용자 모드 → 커널 모드 |
저장 상태 | 리턴주소만 | 리턴주소 + CPU 상태 (EFLAGS 등) |
📦 예외 처리 시 스택에 저장되는 것들
- 리턴 주소
- EFLAGS 레지스터 (조건 코드 포함)
- 기타 CPU 레지스터 상태
- → 전부 커널 스택에 저장됨 (사용자 스택 아님!)
🔙 복귀 시 사용되는 명령
- 예외 핸들러가 끝나면, iretq (x86-64 기준) 같은 명령으로
- 커널 스택에서 상태 복원
- 사용자 모드로 전환
- 중단되었던 프로그램으로 복귀
8.1.2 예외의 종류
종류 | 설명 | 예시 | 리턴 |
인터럽트 | CPU 외부에서 발생, 비동기적, 하드웨어 신호에 반응 | 타이머, 디스크 I/O, 네트워크 패킷 수신 등 | 다음 인스트럭션 |
트랩 | 프로그램이 의도적으로 발생시킨 예외, 동기적 | syscall, breakpoint, 디버그용 trap | 다음 인스트럭션 |
오류(fault) | 프로그램 실행 중 복구 가능한 오류, 동기적 | 페이지 폴트, 보호 에러 등 | 현재 인스트럭션 (재실행) |
중단(abort) | 복구 불가능한 치명적 오류, 프로그램 강제 종료 | 메모리 패리티 에러, 하드웨어 오류 등 | 리턴하지 않음 |
🔍 각 예외의 핵심 정리
인터럽트 (Interrupt)
- 비동기적: CPU가 지금 어떤 명령을 실행하든 관계없이 외부에서 날아옴
- 발생 시점: 현재 명령이 끝난 후
- 처리 방식: 인터럽트 핸들러 실행 후 → 다음 인스트럭션으로 이동
- 예시: 네트워크 패킷 도착, 타이머 만료, 디스크 데이터 준비 완료
트랩 (Trap)
- 동기적: 현재 실행 중인 명령어가 명시적으로 트랩을 발생시킴
- 대표 용도: 시스템 콜
- 처리 방식: 커널 루틴 실행 후 → 다음 인스트럭션으로 이동
- 예시: read(), write(), fork(), exit() 같은 시스템 콜
오류 (Fault)
- 동기적 오류지만, 복구 가능할 수도 있음
- 처리 방식:
- 복구 가능: 현재 인스트럭션으로 돌아가서 재실행
- 불가능: abort 루틴으로 → 프로그램 종료
- 대표 예시: 페이지 폴트
- 해당 메모리 페이지를 디스크에서 불러오고 다시 시도
중단 (Abort)
- 복구 불가능한 치명적 오류
- 처리 방식: 예외 핸들러가 즉시 응용 프로그램 종료
- 예시: 메모리 하드웨어 오류, 버스 에러
📌 기억하기 쉽게
- 💥 동기적 오류: “내가 잘못한 거야” → 지금 이 명령이 문제
- 🔔 비동기적 오류: “밖에서 누가 부른 거야” → 외부에서 이벤트가 온 거야
8.1.3 리눅스/x86—64 시스템에서의 예외상황
예외 번호 | 예외 이름 | 설명 |
0 | 나누기 오류 | 0으로 나누거나 결과가 너무 클 때 → 프로그램 강제 종료 (복구 안 됨) |
13 | 일반 보호 오류 | 잘못된 메모리 접근 (예: 읽기 전용 공간에 쓰기 시도) → 세그폴트 |
14 | 페이지 폴트 | 접근한 메모리 페이지가 아직 로드되지 않음 → 디스크에서 불러와 복구 |
18 | 머신 체크 | 치명적 하드웨어 오류 (DRAM, CPU 내부 등) → 복구 불가, 즉시 종료 |
🧠 포인트 1: 예외 번호
- x86-64 시스템에서는 예외를 0 ~ 255까지 구분
- 0~31: CPU(하드웨어)에서 정의한 예외
- 32~255: 운영체제(OS)에서 정의한 예외 (시스템 콜, 인터럽트 등)
🧠 포인트 2: 시스템 콜은 “트랩” 예외
- 리눅스에서 시스템 콜은 syscall이라는 특별한 명령어로 발생
- 이건 사용자 모드 → 커널 모드로 진입하는 방법
레지스터 | 역할 |
%rax | 시스템 콜 번호 (예: write = 1) |
%rdi | 첫 번째 인자 |
%rsi | 두 번째 인자 |
%rdx | 세 번째 인자 |
%r10 | 네 번째 인자 |
%r8 | 다섯 번째 인자 |
%r9 | 여섯 번째 인자 |
syscall 리턴 | %rax (성공 시 값, 실패 시 음수 errno) |
int main() {
write(1, "hello, world\n", 13); // 시스템 콜 호출
_exit(0); // 프로그램 종료 syscall
}
- write()는 파일 디스크립터 1(STDOUT)에 문자열을 출력
- _exit()은 리턴 없이 프로그램을 바로 종료
mov $1, %rax # syscall 번호: write
mov $1, %rdi # fd: stdout
mov $msg, %rsi # 메시지 주소
mov $13, %rdx # 바이트 수
syscall # 시스템 콜 실행
mov $60, %rax # syscall 번호: exit
xor %rdi, %rdi # 종료 코드 0
syscall # 시스템 콜 실행
📌 이걸 개발자로서 안다는 건…
✅ 1. 프로그램이 어떻게 운영체제와 대화하는지를 이해
- 파일을 읽고, 쓰고, 종료하고, 자식 프로세스를 만들고…
- 이 모든 건 예외적 흐름(=시스템 콜, 트랩)을 통해 커널과 직접 대화하는 것
👉 개발자는 단순히 함수 쓰는 게 아니라, OS에 명령을 내리는 행위를 하는 것
✅ 2. 버그와 오류에 대한 깊은 이해
- 프로그램이 0으로 나눴다 → CPU가 예외 감지 → 커널이 프로세스를 죽인다 → Segmentation fault
- 페이지 폴트가 난다 → 커널이 메모리 불러오고 → 현재 명령어 재실행
👉 이 흐름을 이해하면 디버깅이 훨씬 깊어진다.
“아, 이건 커널에서 나를 죽인 거구나”
“이건 핸들러에서 복구되었구나”
✅ 3. 보안, 안정성, 리소스 보호 원리를 이해
- 사용자 모드에서는 제한된 명령만 사용 가능
- 예외 발생 시만 커널 모드 진입 → 커널 스택 사용 → 자원 통제
👉 왜 우리가 OS 없이 root 권한도 막고, 프로세스끼리 메모리 접근도 못 하게 되는지 이해하게 됨
→ 권한 분리와 보안 모델의 기반
✅ 4. 시스템 프로그래밍 능력 향상
- 직접 시스템 콜 호출 (syscall)
- 예외 핸들러 작성 (커널 모듈, low-level code)
- 시그널 처리 (SIGSEGV, SIGCHLD 등)
👉 이건 고급 프로그래머, 특히 백엔드, 시스템, 인프라 개발자라면 반드시 알아야 할 수준
✅ 5. 동시성과 인터럽트 기반 설계 능력
- 타이머 인터럽트 → 문맥 전환
- 인터럽트 기반 이벤트 루프 → 서버/디바이스 드라이버 설계
- select, epoll, signal, alarm 같은 메커니즘의 본질 이해
👉 네트워크 프로그래밍, 이벤트 기반 서버를 구현할 때까지 연결되는 기반
'개발 공부 > 컴퓨터시스템 (CSAPP)' 카테고리의 다른 글
7주차_키워드 정리 (가상메모리, 페이징, 단편화, DMA, malloc) (0) | 2025.05.01 |
---|---|
7장 링커 (0) | 2025.04.22 |
[운영체제 맛보기] 동시성 vs 병렬성 / 추상화 / 쓰레드 / 프로세스 / 인스트럭션 (4) | 2025.03.28 |
1. 컴퓨터 시스템으로의 여행 [1.4~1.7] (0) | 2025.03.15 |
1. 컴퓨터 시스템으로의 여행 [1.1~1.3] (0) | 2025.03.14 |