CSAPP(8) - Exceptional Control Flow
업데이트:
- 제어 흐름의 가장 간단한 종류는 “smooth” 시퀀스다. 인접한 메모리에 있는 명령어들을 순차적으로 실행하는 것.
- 인접하지 않은 명령어를 수행해야 할 때도 있다. jump, call, return 등.
- 시스템은 프로그램 내에서 일어나지 않는 변화에 대해서도 반응할 수 있어야 한다.
- 하드웨어 타이머, 패킷이 도착해서 메모리에 올려야 할 때, 디스크에 데이터를 요청하고 기다릴 때 등.
- 앞으로 이를 exceptional contol flow(ECF)라 부르겠다.
- 하드웨어 수준에서는 하드웨어 트리거에 의해 이벤트가 감지되어 예외 핸들러에게 제어를 전달하고
- OS수준에서는 커널이 context switching으로 한 유저 프로세스의 제어를 다른 유저 프로세스로 전달한다.
- 애플리케이션 수준에서는 한 프로세스가 다른 프로세스에 signal을 보낼 수 있다. 시그널은 수신자의 시그널 핸들러로 제어를 전달한다.
- 개별 프로그램은 에러 발생 시 다른 함수의 임의 위치로 점프할 수 있다.
1. Exceptions
- 예외는 ECF중 하나로 일부는 하드웨어, 일부는 OS에 구현되어 있다.
- 일부는 하드웨어에 구현되어 있어서 시스템에 따라 디테일이 다르다. 다만 기본 아이디어는 동일하다.
- 이벤트는 가상 메모리 페이지 폴트, 산술 오버플로, divide by zero와 같이 현재 명령어 실행과 관계있을 수도 있고
- 시스템 타이머, 입출력 요청 완료와 같이 관계없을수도 있다.
프로세서가 이벤트 발생을 감지하면 exception table을 통해서 exception handler를 호출한다. 예외 핸들러가 처리를 끝내면 아래 세 가지 중 하나를 한다.
- 핸들러는 제어를 이벤트 발생 시점의 명령어로 반환한다.
- 핸들러는 제어를 이벤트 발생 시점의 명령어 다음 명령어로 반환한다.
- 핸들러는 프로그램을 중단한다.
1.1. Exception handling
각 예외는 고유의 exception number를 갖는다.
- 몇 개는 프로세서 설계자가 지정하고
- divide by zero, 페이지 폴트, 메모리 접근 위반, 산술 오버플로
- 나머지는 커널 설계자가 지정한다
- 시스템 콜, 외부 입출력 장치 시그널
시스템 부트 시점에 os는 exception table이라 하는 점프 테이블을 할당하고 초기화한다.
런타임에 프로세서는 이벤트를 감지하고 예외 번호 k를 결정한다. 프로세서는 예외 테이블을 통해 핸들러를 간접 프로시저 호출하여 예외를 트리거한다.
- k는 예외 테이블의 인덱스다. 테이블의 시작 주소는 exception table base register에 존재한다.
예외는 프로시저 호출과 유사하나 다르다.
- 프로시저 호출은 스택에 반환 주소를 넣지만, 예외는 현재 명령 혹은 다음 명령을 반환한다.
- 예외는 스택에 추가 프로세서 상태를 넣는다.
- 제어가 유저 프로그램에서 커널로 전달될 때는 커널의 스택에 푸시된다.
- 예외 핸들러는 커널 모드에서 실행된다. 시스템 자원에 완전히 접근 가능하다.
1.2. Classes of Exceptions
예외는 4개로 구분된다. interrupts, traps, faults, aborts
class | Cause | Async/Sync | Return behavior |
---|---|---|---|
인터럽트 | 입출력 장치의 시그널 | 비동기 | 언제나 다음 명령어를 반환 |
트랩 | 의도적인 예외 | 동기 | 언제나 다음 명령어를 반환 |
폴트 | 잠재적으로 회복가능한 에러 | 동기 | 아마도 현재 명령어를 반환 |
중단 | 회복불가능한 에러 | 동기 | 절대 반환하지 않음 |
인터럽트
- 입출력장치의 시그널로 비동기적으로 발생한다.
- 하드웨어 인터럽트는 비동기다. 특정 명령의 실행으로 발생하지 않기 때문.
- 인터럽트의 예외 핸들러는 interrupt handler라 한다.
- 핸들러는 제어를 다음 명령어로 반환한다. 프로그램은 마치 인터럽트가 발생하지 않았던 것처럼 실행을 계속한다.
트랩과 시스템 콜
- 트랩은 의도적인 예외다.
- 트랩의 주요 쓰임새는 유저 프로그램과 커널 간 인터페이스를 제공하는 것이다.(시스템 콜)
- 프로세서는 커널에 제어된 접근을 허용하기 위해
syscall n
명령어를 제공한다. syscall
명령은 예외 핸들러에 트랩을 발생시켜서 적절한 커널 루틴을 호출한다.- 프로그래머 관점에서 시스템 콜은 함수 호출과 동일하다.
- 다만 내부적으로 커널 모드에서 실행된다.
폴트
- 폴트는 핸들러가 복구할 수 있는 에러의 결과다.
- 폴트가 발생하면 프로세스는 제어를 폴트 핸들러로 넘긴다.
- 핸들러가 에러를 고칠 수 있으면 폴팅 명령으로 제어를 반환한다.
- 그렇지 않으면 핸들러는 커널의 중지 루틴으로 반환한다.
- eg. 페이지 폴트 발생 시 페이지 폴트 핸들러는 적절한 페이지를 디스크에서 메모리로 적재한다. 이후 제어를 폴트 발생 명령어로 반환한다.
중단
- 복구 불가능한 에러의 결과
- 제어를 애플리케이션에 반환하지 않는다.
1.3. Exceptions in Linux/x86-64 Systems
Exception number | Description | Exception class |
---|---|---|
0 | Divide error (Floating exceptions) | Fault (not recover) |
13 | General protection faults (Segmentation faults) | Fault (not recover) |
14 | Page fault | Fault (recover) |
18 | Machine check | Abort |
32-255 | OS-defined exceptions | Interrupt or trap |
2. Processes
프로세스는 애플리케이션에 다음 주요 추상화를 제공한다.
- 독립적인 제어 흐름. 프로그램이 프로세서를 독점 사용중인 것처럼 보이게 한다.
- 사설 주소 공간. 프로그램이 메모리 시스템을 독점 사용중인 것처럼 보이게 한다.
Logical Control Flow
프로세서가 선점되어 멈추고 다시 실행하더라도, 프로그램의 메모리 위치나 레지스터는 변경되지 않는다.
Concurrent Flows
동시 흐름은 한 논리 흐름이 다른 흐름과 시간이 겹쳐서 실행되는 것을 말한다. 병렬 흐름은 동시 흐름의 부분집합으로 두 흐름이 다른 프로세서나 코어 혹은 컴퓨터에서 동시에 실행되는 것을 말한다.
Private Address Space
프로세스는 각 프로그램에 고유한 개인 주소 공간을 제공한다.
- 특정 주소에 있는 바이트는 다른 프로세스에 의해 읽거나 쓰일 수 없다.
User and Kernel Modes
OS커널이 프로세스 추상화를 제공하기 위해, 프로세서는 애플리케이션이 실행 가능한 명령과 접근 가능한 주소 공간을 제한해야 한다.
- 프로세서는 mode bit라는 제어 레지스터를 활용한다.
- 모드 비트가 설정되면 프로세스는 커널 모드에서 실행된다. 모든 명령어와 메모리 공간에 접근 가능하다.
- 모드 비트가 설정되지 않으면 프로세스는 유저 모드에서 실행된다. 프로그램은 시스템 콜을 통해서만 커널 코드와 데이터에 접근 가능하다.
- 프로세스가 커널 모드로 전환하려면 인터럽트, 폴트, 트랩 시스템 콜로만 가능하다.
- 예외가 발생하면 제어를 예외 핸들러로 넘기고 프로세서는 커널 모드로 바뀐다. 핸들러는 커널 모드에서 실행된다. 애플리케이션으로 반환할 때 프로세서는 유저 모드로 다시 바꾼다.
리눅스는 /proc
파일시스템 방법을 제공한다.
- 유저 모드 프로세스가 커널 자료구조에 접근 가능하게 한다.
- 커널 자료구조가 계층화된 텍스트 파일로 노출되게 하고, 유저 프로그램은 읽는다.
- eg.
- CPU type(
/proc/cpuinfo
) - 특정 프로세스가 사용하는 메모리 구역(
proc/{process-id}/maps
)
- CPU type(
Context Switches
os커널은 예외 제어 흐름의 고수준 형태인 context switch를 통해 멀티태스킹을 구현한다.
- 현재 프로세스의 맥락을 저장한다.
- 이전에 선점된 프로세스의 저장된 맥락을 복구한다
- 새롭게 복구된 프로세스로 제어를 전달한다.
컨텍스트 스위치는 다음과 같은 상황에 발생한다.
- 시스템 콜 실행 시
- 입터럽트의 결과
- 타이머 인터럽트(시분할)
4. Process Control
주요 시스템 콜들을 소개하는 내용
5. Signals
지금까지 어떻게 하드웨어와 소프트웨어가 저수준 예외 매커니즘으로 협력했는지 보았다. 또한 os가 컨텍스트 스위치를 위해 어떻게 예외를 사용하는지 보았다.
이번 장에서는 Linux signal이라 알려진 예외 제어 흐름의 고수준 형태를 공부할 것이다. 이것은 프로세스와 커널이 다른 프로세스를 인터럽트하는 것을 허용한다.
저수준 하드웨어 예외는 커널의 예외 핸들러에 의해 처리되고 일반적으로 유저 프로세스에게 보이지 않는다. 시그널은 유저 프로세스에게 저수준 예외 발생을 노출하는 매커니즘을 제공한다.
5.1. Signal Terminology
시그널을 목적지로 전달하는 과정은 두 단계로 발생한다.
- 시그널 전달 : 커널이 목적 프로세스의 컨텍스트 상태를 업데이트하는 방법으로 시그널을 전달한다. 시그널이 전달되는 이유는 둘 중 하나다.
- 커널이 divide-by-zero / 자식 프로세스의 종료와 같은 시스템 이벤트를 감지
- 프로세스가
kill
함수를 호출하여 커널에게 목적 프로세스로 시그널을 전달하도록 요청했다. 프로세스는 시그널을 자기 자신에게 전달할 수 있다.
- 시그널 수신 : 목적 프로세스는 커널이 시그널에 반응하도록 강요할 때 시그널을 받는다. 프로세스는 유저 레벨 함수인 시그널 핸들러를 통해 무시, 종료, catch할 수 있다.
5.2. Sending Signals
각 프로세스는 정확히 하나의 프로세스 그룹에 속한다.
- 디폴트로 자식 프로세스는 부모 프로세스와 동일한 프로세스 그룹에 속한다.
- 프로세스 그룹을 바꿀 수도 있다.
시그널을 보내는 방법
/bin/kill
프로그램을 통해 다른 프로세스에 시그널을 보낸다.- 키보드를 통해 보낸다.
Ctrl+C
를 타이핑하면 커널이 포어그라운드 프로세스 그룹에 속한 프로세스에게 SIGINT 시그널을 보낸다. 디폴트로는 포어그라운드 잡을 종료한다.Ctrl+Z
는 커널이 SIGTSTP 시그널을 포어그라운드 프로세스 그룹에 속한 프로세스에게 보낸다.
kill
함수로 보낸다.alarm
함수로 보낸다.- SIGALRM 시그널을 보낸다.
5.3. Receiving Signals
6. Nonlocal Jumps
C는 nonlocal jump라 하는 유저 레벨 ECF를 제공한다.
- 한 함수에서 다른 함수로 제어를 직접 전달한다.
- 일반 호출-반환 시퀀스가 아님
setjmp
와longjmp
함수로 제공된다.
7. Tools for Manipulating Processes
STRACE
: 프로그램의 각 시스템 콜을 tracePMAP
: 프로세스의 메모리 맵을 보기/proc
: 가상 파일시스템으로 커널 자료구조의 다양한 내용을 노출한다. 유저 프로그램에 의해 읽힐 수 있다.
8. Summary
예외적인 제어 흐름(ECF)는 컴퓨터 시스템의 모든 레벨에서 발생하며 동시성을 제공하기 위한 매커니즘이다.
- 하드웨어 레벨 : 프로세서에 의해 발생. 소프트웨어 핸들러에 의해 처리되고 제어를 반환
- os레벨 : 커널이 프로세스 개념을 제공하기 위해 사용. 프로세스는 다음 추상화를 제공.
- 프로그램이 프로세서를 독점하는것처럼 보이게 함.
- 프로그램이 메모리를 독점하는것처럼 보이게 함.
- 애플리케이션 레벨 : C프로그램은 nonlocal jump를 사용하여 함수에서 다른 함수로 직접 분기한다.
예외의 종류(하드웨어와 os레벨)
- 인터럽트 : 비동기. 외부 입출력 장치가 프로세서에 인터럽트.
- 폴트 : 동기. 명령의 결과로 발생. 핸들러가 제어를 반환
- 무시 : 동기. 명령의 결과로 발생. 핸들러가 제어 반환하지 않음.
- 트랩 : 시스템 콜을 구현하기 위한 함수
댓글남기기