Chapter 08. 예외적인 제어흐름

  • 프로세서에서 전원을 처음 공급하는 시점부터 전원을 끌 때까지 프로그램 카운터는 연속된 값들을 가정한다.
    • 인스트럭션 I에 대응되는 주소가 ak -> ak+1 로의 전환은 제어이동이라고 부른다.
    • 이러한 제어이동의 배열은 제어흐름 또는 프로세서의 제어흐름이라고 부른다.
  • 프로그램의 실행과는 반드시 관련되어 있지 않은 시스템 상태의 변화에도 반응할 수 있어야 한다.
    • 현대의 시스템들은 갑작스런 변화를 만드는 방법으로 이러한 상황에 반응한다.
    • 일반적으로 이와 같은 급격한 변화를 예외적인 제어흐름exceptional control flow(ECF)라고 한다.
  • ECF의 중요성
    • ECF는 운영체제가 입출력, 프로세스, 가상메모리를 구현하기 위해 사용하는 기본 메커니즘이다.
    • ECF를 이해하면 어떻게 application들이 운영체제와 상호작용하는지를 이해할 수 있다.
    • ECF는 컴퓨터 시스템에서 동시성을 구현하는 기본 메커니즘이다.
    • ECF를 이해하면소프트웨어적인 예외상황이 어떻게 동작하는지 이해할 수 있다.

7장까지는 시스템에 대한 application이 하드웨어와 어떻게 상호작용하는지에 관한 것이었다. 98장은 application이 운영체제와 어떻게 상호작용하는지 배우게 되는 측면에서 전환점이라고 볼 수 있다.

  • 컴퓨터 수준에 존재하는 ECF의 다양한 형태
    1. 하드웨어와 운영체제의 교차점에 놓인 예외
    2. application에게 운영체제 내부로 엔트리 포인트를 제공하는 예외인 시스템콜
    3. application과 운영체제의 교차점에 위치한 프로세스와 시그널
    4. 비지역성 점프 - ECF의 응용수준

8.1 예외상황

  • 예외상황은 부분적으로는 하드웨어와 운영체제에 의해서 구현된 예외적인 제어흐름의 한가지 형태다.

  • 위의 그림은 예외상황의 기본 아이디어다.
  • 프로세서가 이벤트(상태 변화)가 발생했다는 것을 감지하면, 예외 테이블이라고 하는 점프 테이블을 통해서 이 특정 종류의 이벤트를 처리하기 위해 특별히 설계된 운영체제 서브루틴(예외처리 핸들러)으로 간접 프로시저 콜을 하게 된다.
  • 예외처리 핸들러가 처리를 끝마치면, 다음 세 가지 중의 한 가지 일을 발생한다.
    1. 핸들러는 제어를 현재 인스트럭션으로 돌려준다.
    2. 핸들러는 제어를 다음 예외상황이 발생하지 않았더라면 다음에 실행되었을 인스트럭션으로 돌려준다.
    3. 핸들러는 중단된 프로그램을 종료한다.

8.1.1 예외처리

  1. 한 시스템 내에서 가능한 예외상황의 종류마다 중복되지 않는 양의 정수를 예외번호로 할당하고 있다.
  2. 시스템 부팅 시, 운영체제는 예외 테이블이라고 하는 점프 테이블을 할당하고 초기화해서 엔트리 k가 예외상황 k에 대한 핸들러의 주소를 갖는다.
  3. 런타임에 프로세서는 이벤트가 발생했다는 것을 감지하고, 대응되는 예외번호 k를 결정한다.
  4. k를 통해서 간접 프로시저 콜을 하는 방법으로 예외상황을 발생시킨다.
  5. 예외번호는 예외 테이블에서 인덱스이며, 이 테이블의 시작 주소는 ‘예외 테이블 베이스 레지스터’라는 특별한 CPU 레지스터에 저장되어 있다.

  • 위의 그림은 예외테이블이다.

  • 예외상황과 프로시저 콜의 차이점

    • 프로시저 콜: 스택에 리턴주소 푸시, 예외상황: 현재 또는 다음 인스트럭션
    • 핸들러가 리턴할 때 중단된 프로그램을 다시 시작하기 위해 스택 상에 추가적인 프로세서 상태를 푸시한다.
    • 모든 아이템들은 사용자 스택 위가 아니라 커널 스택 상에 푸시된다.
    • 예외 핸들러는 ‘커널 모드’에서 돌아간다.

8.1.2 예외의 종류

  • 예외상황은 네 가지 종류를 구분할 수 있다.
    • 인터럽트
    • 트랩
    • 오류fault
    • 중단abort

인터럽트

  • 프로세서 외부에 있는 입출력 디바이스로부터의 시그널의 결과로 비동기적으로 발생한다.(특정 인스트럭션을 싫애해서 발생한 것이 아니라는 의미)
  • 핸들러가 리턴할 때, 제어를 다음 인스트럭션으로 돌려준다.

인터럽트를 제외한 나머지 예외의 종류들은 동기적으로 일어난다.

트랩과 시스템 콜

  • 의도적인 예외상황으로, 어떤 인스트럭션을 실행한 결과로 발생한다.
  • 트랩 핸들러는 제어를 다음 인스트럭션으로 리턴한다.
  • 트랩의 가장 중요한 사용은 시스템콜이라고 알려진 사용자 프로그램과 커널 사이의 프로시저와 유사한 인터페이스를 제공하는 것이다.
  • 시스템 콜은 ‘커널 모드’에서 돌아가며, 이로 인해 커널 내에서 정의된 스택에 접근하며, 특권을 가진 인스트럭션을 실행할 수 있도록 해준다.

오류(fault는 error와 다르다.)

  • 오류는 핸들러가 정정할 수 있을 가능성이 있는 에러 조건으로부터 발생한다.
  • 핸들러가 에러 조건을 정정한다면, 제어를 오류를 발생시킨 인스트럭션으로 돌려주어 거기서부터 재실행한다.
  • 정정하지 못한다면, 커널 내부의 abort 루틴으로 리턴해서 프로그램을 종료한다.

중단 abort

  • 중단 핸들러는 절대로 응용프로그램으로 제어를 리턴하지 않는다.

8.1.3 리눅스/x86-64 시스템에서의 예외상황

오류와 중단

  • 나누기 에러: 0으로 나누려할 때
  • 일반 보호 오류: 프로그램이 가상메모리의 정의되지 않은 영역을 참조하거나 read-only를 쓰려고 하기 때문에 발생
  • 페이지 오류: 가상메모리 페이지 오류
  • 머신 체크: 오류 인스트럭션을 실행하는 동안에 검출된 치명적인 하드웨 에러의 결과로 발생

시스템 콜

  • 리눅스는 파일을 읽거나 쓸 때, 또는 새로운 프로세스를 만들 때 응용프로그램이 사용할 수 있는 수백 개의 시스템 콜을 제공한다.
  • x86-64 시스템에서 시스템 콜은 syscall이라고 부르는 트랩 인스트럭션을 통해서 제공된다.

  • 위의 그림은 리눅스 x86-64 시스템의 주요 시스템 콜

8.2 프로세스

  • 예외상황은 프로세스 개념을 운영체제 커널이 제공할 수 있게 하는 기본 구성 블록이다.
  • 프로세스의 고전적인 정의는 실행 프로그램의 인스턴스이다.
  • 시스템 내의 각 프로그램은 어떤 프로세스의 문맥context에서 돌아간다.
  • 사용자가 실행 목적파일의 이름을 쉘에 입력해서 프로그램을 돌릴 때마다 쉘은 새로운 프로세스를 생성하고, 실행 목적파일을 이 새로운 프로세스의 문맥에서 실행한다.
  • 프로세스가 application에 제공하는 주요 추상화
    • 프로세서를 혼자서 사용한다는 착각을 제공하는 독립적 논리 제어 흐름
    • 혼자서 메모리 시스템을 가진다는 착각을 제공하는 사적 주소공간

8.2.1 논리적인 제어흐름

  • 논리흐름
    • 프로그램과 동적으로 런타임에 링크된 공유 객체 내의 인스트럭션들에게 일련의 프로그램 카운터 PC 값들이 대응된다는 것을 관찰할 수 있다.
    • 이러한 PC 값들의 배열을 논리적 제어흐름 또는 논리흐름이라고 부른다.

  • 위의 그림은 논리적 제어흐름이다.
    • 하나의 프로세서를 사용해서 여러 프로세스들이 교대로 돌아간다.

8.2.2 동시성 흐름

  • 논리흐름은 컴퓨터 시스템 내에서 여러 가지 다른 형태를 갖는다.
    • 예외 핸들러, 프로세스, 시그널 핸들러, 쓰레드, 자바 프로세스는 모두 논리흐름의 예다.
  • 자신의 실행시간이 다른 흐름과 겹치는 논리흐름을 동시성 흐름이라고 부른다.
  • 동시성: 공동으로 실행되는 흐름의 일반적인 현상
  • 멀티태스킹: 프로세스가 다른 프로세스들과 교대로 실행된다는 개념
  • 타임 슬라이스: 프로세스가 자신의 흐름 일부를 실행하는 매 시간 주기

두 개의 흐름이 서로 다른 프로세서 코어나 컴퓨터에서 동시에 돌아간다면, 이것은 병렬 흐름 이라고 한다.

8.2.3 사적 주소공간

  • 프로세스는 각 프로그램에 자신이 시스템의 주소공간을 혼자서 사용한다는 착각을 불러일으킨다.
  • 프로세스는 각 프로그램에 자신만의 사적 주소공간을 제공한다.
    • 이 공간의 특정 주소에 연결된 메모리의 한 개의 바이트가 일반적으로 다른 프로세스에 의해서 읽히거나 쓰일 수 없다는 의미로 이 공간은 사적이다.

  • 위의 그림은 프로세스 주소공간이다.

8.2.4 사용자 및 커널 모드

  • 운영체제가 완벽한 프로세스 추상화를 제공하기 위해서 프로세서는 응용프로그램이 접근할 수 있는 주소공간 뿐만 아니라 응용프로그램이 실행할 수 있는 인스트럭션들을 제한하는 메커니즘을 제공해야한다.
  • 커널 모드에서 돌고 있는 프로세스는 인스트럭션 집합의 어떤 인스트럭션도 실행할 수 있으며, 시스템 내의 어떤 메모리 위치도 접근할 수 있다.
  • 예외가 발생해서 제어가 예외 핸들러로 넘어가면, 프로세서는 사용자 모드에서 커널 모드로 변경한다.
    • 핸들러는 커널 모드에서 돌아간다.

8.2.5 문맥 전환

  • 운영체제 커널은 문맥 전환context switch 이라고 알려진 예외적인 제어흐름의 상위수준 형태를 사용해서 멀티태스킹을 구현하고 있다.
  • 커널은 각 프로세스마다 컨텍스트를 유지한다. 컨텍스트는 커널이 선점된 프로세스를 다시 시작하기 위해서 필요로 하는 상태다.

  • 위의 그림은 프로세스 문맥 전환의 분석이다.

  • 스케줄링
    • 커널은 프로세서가 실행되는 동안의 어떤 시점에 현재 프로세스를 선점하고 이전에 선점된 프로세스를 다시 시작할 것을 결정한다
    • 스케줄러라고 불리는 커널 내부의 코드에 의해 처리된다.
    • 커널이 실행할 새 프로세스를 선택할 때 커널이 그 프로세스를 스케줄 했다고 말한다.
  • 문맥 전환
    • 커널이 실행할 새 프로세스를 스케줄한 후에 현재 프로세스를 선점하는 것을 말한다.
  • 문맥 전환의 수행 절차
    1. 현재 프로세스의 컨텍스트를 저장한다.
    2. 이전에 선점된(일시적으로 정지된) 프로세스의 저장된 컨텍스트를 복원한다.
    3. 제어를 이 새롭게 복원된 프로세스로 전달한다.

8.3 시스템 콜의 에러 처리

  • Unix의 시스템 수준 함수가 에러를 만날 때 이들은 대개 -1을 리턴하고, 전역 정수 변수인 errno를 세팅해서 무엇이 잘못되었는지를 나타낸다.

8.4 프로세스의 제어

  • Unix는 C 프로그램으로부터 프로세스를 제어하기 위한 많은 시스템 콜을 제공한다.

8.4.1 프로세스 ID 가져오기

  • 각각의 프로세스는 고유의 양수 프로세스 ID(PID)를 가진다.

  • getpid 함수는 호출하는 함수의 PID를 리턴한다.
  • getppid 함수는 자신의 부모의 PID를 리턴한다.

8.4.2 프로세스의 생성과 종료

  • 프로그래머의 관점에서 프로세느는 세 가지 상태가 존재한다.
    • 실행중 running: 프로세스는 CPU에서 실행하고 있거나 실행을 기다리고 있다. 궁극적으로 커널에 의해서 스케줄될 것이다.
    • 정지 stopped: 프로세스의 실행은 정지한 상태이고 스케줄되지 않는다.
    • 종료 terminated: 프로세스는 영구적으로 정지된다.
  • exit 함수는 종료 상태 status로 프로세스를 종료한다.
  • fork 함수는 부모 프로세스가 자식 프로세스를 생성한다.
    • 한 번 호출하고 두 번 리턴한다.(한 번은 부모가, 다른 한 번은 자식이)
    • 동시 실행한다.(부모와 자식은 동시에 돌아가는 별도의 프로세스)
    • 중복되었지만 별도의 주소공간이다.
    • 파일을 공유한다.(자식이 부모가 오픈한 모든 파일들을 상속받음)

8.4.3 자식 프로세스의 청소

  • 프로세스가 어떤 이유로 종료할 때, 커널은 시스템에서 즉시 제거하지 않는다.
    • 부모가 청소할 때까지 종료된 상태로 남아 있는다.
    • 부모가 종료된 자식을 청소할 때 커널은 자식의 exit 상태를 부모에게 전달 후 종료된 프로세스를 없앤다. 이 때 프로세스가 사라진다.
    • 종료되었지만 아직 청소되지 않은 프로세스를 좀비라고 한다.
  • waitpid 함수는 자신의 자식들이 종료되거나 정지되기를 기다린다.
  • wait 함수는 waitpid의 단순화된 버전이다

8.4.4 프로세스 재우기

  • sleep 함수는 일정 기간 동안 프로세스를 정지시킨다.
  • pause 함수는 호출하는 함수를 시그널이 프로세스에 의해 수신될 때까지 잠을 재운다.

8.4.5 프로그램의 로딩과 실행

  • execve 함수는 현재 프로그램의 컨텍스트 내에서 새로운 프로그램을 로드하고 실행한다.

8.5 시그널

  • 리눅스 시그널이라고 알려진 상위수준의 소프트웨어 형태의 예외적 제어흐름을 배운다.
    • 해당 시그널은 프로세스와 커널이 다른 프로세스를 중단하도록 한다.
  • 시그널
    • 작은 메시지 형태로, 프로세스에게 시스템 내에 어떤 종류의 이벤트가 일어났다는 것을 알려준다.

  • 위의 그림은 리눅스 시스템에서 지원되는 30개의 서로 다른 종류의 시그널을 보여준다.

8.5.1 시그널 용어

  • 시그널을 목적지 프로세스로 전달하는 것은 두 단계로 이루어진다:
    1. 시그널 보내기: 커널은 목적지 프로세스의 컨텍스트 내에 있는 일부 상태를 갱신해서 시그널을 목적지 프로세스로 보낸다(배달한다).
    2. 시그널 받기: 목적지 프로세스는 배달된 신호에 대해서 커널이 어떤 방식으로 반응해야 할 때 목적지 프로세스는 시그널을 받는다.ㅜ
  • 펜딩pending 시그널: 보내졌지만 아직 받지 않은 시그널

8.5.2 시그널 보내기

  • Unix 시스템은 시그널을 프로세스로 보내는 여러가지 메커니즘을 제공한다.
    • 모든 메커니즘은 프로세스 그룹 개념을 사용한다.

프로세스 그룹

  • 모든 프로세스는 정확히 한 개의 프로세스 그룹에 속한다.

  • getpgrp 함수는 현재 프로세스의 프로세스 그룹 ID를 리턴한다.
  • setpgid 함수는 프로세스 pid의 프로세스 그룹을 pgid로 변경한다.

시그널을 /bin/kill 프로그램을 사용해서 보내기

  • /bin/kill -9 15213: 시그널 9번(SIGKILL)을 프로세스 15213에 보낸다.
  • /bin/kill -9 -15213: SIGKILL 시그널이 프로세스 그룹 15213 내의 모든 프로세스에게 보내지도록 한다. (음수 PID는 전체에게 보냄)

키보드에서 시그널 보내기

  • Ctrl+C를 입력하면 커널은 SIGINT 시그널을 포그라운드 프로세스 그룹에 속한 모든 프로세스에게 보낸다.
  • Ctrl+Z를 입력하면 마찬가지로 모든 프로세스에 SIGSTP 시그널을 보낸다.

kill 함수로 시그널 보내기

  • 프로세스는 kill 함수를 호출해서 시그널을 다른 프로세스로 보낸다.

alarm 함수로 시그널 보내기

  • 프로세스는 SIGALRM 시그널을 alarm 함수를 호출해서 자기 자신에게 보낼 수 있다.

8.5.3 시그널의 수신

  • 커널이 프로세스 p를 커널 모드에서 사용자모드로 전환할 때, 커널은 프로세스 p에 대한 블록되지 않은 펜딩 시그널의 집합을 체크한다.
    • 만일 이 집합이 비어 있다면, 커널은 제어를 p의 논리 제어흐름 내의 다음 인스트럭션으로 전달한다.
    • 집합이 비어 있지 않다면, 커널은 집합 내 어떤 시그널 k를 선택해서 p가 시그널 k를 수신하도록 한다.

8.5.4 시그널 블록하기와 블록 해제하기

  • 묵시적 블록 방법
    • 기본적으로, 커널은 핸들러에 의해 처리되고 있는 유형의 모든 대기 시그널들의 처리를 막는다.
  • 명시적 블록 방법
    • 응용 프로그램들은 sigprocmak 함수와 이들의 도움함수를 이용해서 시그널들을 명시적으로 블록하거나 해제할 수 있다.

8.6 비지역성 점프

  • C는 비지역성 점프라고 부르는 사용자 수준의 예외적 제어흐름의 형태를 제공한다.
    • 이것은 보통의 콜-리턴 순서를 통할 필요 없이 하나의 함수에서 현재 실행하고 있는 다른 함수로 제어를 이동한다.
    • setjmp와 longjmp 함수로 제공된다.
  • 비지역성 점프의 중요한 응용은 심하게 중첩된 함 수콜에서, 어떤 에러 조건을 검출한 결과 즉시 리턴을 허용하는 것이다.
    • 예를 들어, 에러 조건이 중첩된 함수 호출의 깊은 곳에서 발견되면, 콜 스택을 거꾸로 돌아가는 대신 공통의 지역적인 에러 핸들러로 직접 리턴하기 위해 사용한다.

8.7 요약

  • 예외적 제어흐름 ECF는 컴퓨터 시스템의 모든 수준에서 일어나며, 컴퓨터 시스템에 동시성을 제공하는 기본 메커니즘이다.
  • 운영체제 수준에서, 커널은 ECF를 사용해서 프로세스의 근본적인 개념을 제공한다.
  • 프로세스는 응용프로그램에 두 개의 추상화를 제공한다:
    1. 각 프로그램에 자신이 프로세서를 혼자서 사용하고 있다는 착각을 느끼게 하는 논리적 제어흐름.
    2. 각 프로그램이 메인 메모리를 혼자서 사용하는 착각을 제공하는 사적 주소공간.

8.4 프로세스의 제어, 8.5.5 시그널 핸들러 작성하기 8.6 비지역성 점프 부분은 한번 더 꼭 읽어보자.

태그: ,

카테고리:

업데이트:

댓글남기기