CSAPP 독서 내용 정리 8-1 ~ 8-4

이형준·2023년 5월 12일
0

CSAPP

목록 보기
7/10

8. 예외적인 제어흐름

a1, a2, a3 ... 프로세서에 전원을 공급하는 시점부터 전원을 끌 때까지 프로그램 카운터는 계속하여 주소를 읽고, 인스트럭션을 수행한다. a1에서 a2로와 같은 주소간의 전환은 제어이동이라 한다. 이러한 제어이동의 배열은 제어흐름 또는 프로세서의 제어흐름이라고 부른다.

가장 간단하고 흔한 제어흐름은 '점진적인' 순서로, 두 주소가 메모리에서 서로 나란히 있는 경우다. 이러한 점진적인 흐름에 변화가 생기는 경우는 일반적으로 jumpcall리턴 같은 친숙한 프로그램 인스트럭션에 의해서이다.

그러나 시스템들은 내부 프로그램 변수에 의해 표시되지 않으며, 프로그램의 실행과 관련되어 있지 않은 시스템 상의 변화에도 반응할 수 있어야 한다.

현대의 시스템들은 제어흐름의 갑작스런 변화를 만드는 방법으로 이러한 상황에 반응한다.

예외상황 ⚠️

예외상황은 어떤 프로세서 상태의 변화에 대한 대응으로, 제어흐름의 갑작스런 변화이다. 이러한 상태 변화는 이벤트라고도 부른다. 이벤트는 인스트럭션들의 실행에 직접적으로 관련이 있을 수도(ex. 산술 오버플로우), 혹은 없을 수도(ex. 시스템 타이머 정지) 있다.

어떤 경우이든지 프로세서가 이벤트 발생을 감지하면, 예외 테이블이라고 하는 점프 테이블을 통해 이벤트 처리를 위한 운영체제 서브루틴(예외처리 핸들러)으로 간접 프로시저 콜을 하게 된다.

예외처리 핸들러가 처리를 끝마치면, 예외상황을 발생시킨 이벤트의 종류에 따라서 다음 세 가지 중의 한 가지 일이 발생한다.

  1. 핸들러는 제어를 이벤트가 발생했을 때 실행되고 있던 인스트럭션으로 돌려준다.

  2. 핸들러는 제어를 예외상황이 발생하지 않았더라면 다음에 실행되었을 인스트럭션으로 돌려준다.

  3. 핸들러는 중단된 프로그램을 종료한다.

예외처리

한 시스템 내에서 가능한 예외상황의 종류마다 중복되지 않는 양의 정수를 예외번호로 할당하고 있다. 이 숫자들의 일부는 프로세서 설계자가, 그 외는 운영체제 커널 설계자가 할당한다.

  • 예외 테이블의 모습과 예외 핸들러의 주소를 만드는 모습

    프로그램을 실행하던 중 프로세서가 이벤트가 발생했다는 것을 감지하고, 해당 이벤트에 대응되는 예외번호 k를 결정한다. 프로세서는 이후 k를 통해서 간접 프로시저 콜을 하는 방법으로 예외상황을 발생시킨다. 이러한 예외상황은 일반적인 프로시저 콜과 유사하지만, 일부 중요한 차이점이 있다고 한다 🤔

  • 예외상황은 프로시저 콜과 비슷하게 분기 전 스택에 리턴 주소를 푸시하지만, 예외의 종류에 따라 리턴주소가 현재 인스트럭션일수도, 바로 다음 인스트럭션일수도 있다.

  • 핸들러가 리턴할 때 중단된 프로그램을 재개하기 위해 추가적인 프로세서 상태를 스택에 푸시한다.

  • 제어가 사용자 프로그램에서 커널로 전환될 때, 아이템들(이해가 안되서 검색해봤는데, 레지스터 값, 프로그램 카운터, 스택 포인터 등을 의미하는 것 같다)은 사용자 스택이 아닌 커널 스택에 푸시된다.

  • 예외 핸들러는 커널 모드에서 돌아간다. 따라서 모든 시스템 자원에 완전히 접근할 수 있다.

핸들러는 예외처리로 인해 발생한 이벤트를 처리한 후 원래 위치로 복귀라는 특수한 인스트럭션을 실행해, 인터럽트 발생 전 프로세서의 제어 상태와 데이터 레지스터 상태를 스택으로부터 팝해서 돌려준다. 마지막으론 제어를 중단되었던 프로그램으로 돌려준다.

예외의 종류

예외상황은 네 가지 종류로 구분 가능하다. 인터럽트, 트랩, 오류, 중단

  • 인터럽트
    인터럽트는 입출력 디바이로스들로부터 발생한다. 핸들러가 인터럽트를 처리한 이후에는 응용프로그램의 제어흐름에서 다음 인스트럭션으로 제어를 돌려준다.

  • 트랩과 시스템 콜
    트랩은 의도적인 예외상황으로, 어떠한 인스트럭션을 실행한 결과로 발생한다. 인터럽트 핸들러와 동일하게 트랩 핸들러도 다음 인스트럭션으로 제어를 넘겨준다. 트랩의 가장 주요한 사용처는 시스템 콜이라고 알려진 인터페이스(사용자 프로그램 <-> 커널)를 제공하는 일이다.
    사용자 프로그램은 파일을 읽거나, 새로운 프로세스를 만들거나, 새 프로그램을 로드하거나 종료하는 등의 서비스를 종종 커널에게 요청할 필요가 생긴다. 이러한 커널 서비스의 제한된 접근을 하기 위해서 프로세는 특별한 n 인스트럭션을 제공한다. 바로 이녀석이 위와 같은 서비스를 요청할 때 사용할 수 있는 인스트럭션인 것 ❗이러한 시스템 콜은 척 보기에 일반 함수를 부를때와 비슷해 보이지만, 시스템 콜은 커널 모드에서 돌아가기에 커널 내 스택에 접근하고, 특별한 인스트럭션을 실행할 수 있는 특권이 있다.

  • 오류(fault와 error는 다르다)
    오류는 핸들러가 정정할 가능성이 있는 에러 조건에서 발생한다. 오류 핸들러가 에러 조건을 정정할 수 있다면, 제어를 원래 오류를 발생시킨 인스트럭션으로 돌려주어서 재실행하도록 한다. 반면에 정정할 수 없다면, 핸들러는 커널 내부의 abort 루틴으로 리턴해서 오류를 발생시킨 응용프로그램을 종료한다.

  • 중단
    중단은 대개 DRAM 이나 SRAM이 고장날 때 발생하는 패리티 에러와 하드웨어 에러같은 복구할 수 없는 치명적인 에러에서 발생한다. 이 경우 중단 핸들러는 절대로 응용 프로그램으로 제어를 리턴하지 않는다.

프로세스 🪧

예외상황은 전산학에서 가장 심오하고 성공적인 개념 중 하나인 프로세스 개념을 운영체제 커널이 제공할 수 있도록 하는 기본 구성 블록이다. 어떠한 프로그램을 실행한다면, 마치 프로그램이 단 한개만 시스템에서 돌아가는 것 같은 착각이 든다. 또한 프로그램의 코드와 데이터는 시스템 메모리 상의 유일한 객체인 것 같은 느낌은 덤으로. 이러한 착각은 프로세스라는 개념에 의해 이뤄지는 추상화 덕이다. 이러한 추상화에 대해서 더 자세히 살펴보자.

논리적인 제어흐름

  • 사진과 같은 논리적 제어흐름은 각 프로그램에 마치 프로세서를 혼자 사용한다는 착각을 제공한다.

논리적인 제어흐름의 요점은 하나의 프로세서를 사용해서 여러 프로세스들이 교대로 돌아간다는 점이다. 각 프로세스는 자신의 흐름의 일부분을 실행하고 나서 다른 프로세스들로 순서를 바꾸어 실행하는 동안 일시적으로 정지된다.

동시성 흐름

  • 자신의 실행시간이 다른 흐름과 겹치는 논리흐름을 동시성 흐름이라고 한다.

  • 프로세스가 다른 프로세스들과 교대로 실행된다는 개념은 멀티태스킹이라고 알려져 있다.

  • 한 프로세스가 자신의 흐름 일부를 실행하는 매 시간 주기를 타임 슬라이스라고 부르고, 따라서 멀티태스킹타임 슬라이싱이라고 부르기도 한다.

사용자 및 커널 모드

프로세서는 대게 제어 레지스터로 모드 비트를 제공한다. 모드 비트가 설정되면 프로세스는 커널 모드(슈퍼바이저 모드)로 동작한다.

커널 모드에서 돌고 있는 프로세스는 인스트럭션 집합의 어떠한 인스트럭션도 실행할 수 있으며, 시스템 내의 어떠한 메모리 위치도 접근할 수 있다.

반면에 모드 비트가 세트되지 않았다면, 프로세스는 사용자 모드에서 돌고 있는 것이다. 커널 모드와는 달리 프로세서를 멈추거나, 모드 비트를 변경하는 등의 특수 인스트럭션들을 사용할 수 없다. 또한 주소공간의 커널 영역에 있는 코드나 데이터를 직접 참조할 수도 없다. 이러한 시도를 하게 되면 치명적인 보호 에러가 발생하는데, 이 경우에는 시스템 콜을 통해 커널 코드와 데이터에 간접적으로 접근해야 한다.

문맥 전환

운영체제 커널은 문맥 전환(Context Switch)이라고 부르는 예외적인 제어흐름을 통해 멀티태스킹을 구현한다. 여기서 컨텍스트는 커널이 선점된 프로세스를 다시 시작하기 위해 필요한 상태를 일컫는다. 이를테면 레지스터 정보, PC, 사용자 스택 등..

문맥 전환은 현재 프로세스의 컨텍스트를 저장 -> 이전에 일시적으로 정지된 프로세스의 컨텍스트를 복원 -> 제어를 새롭게 복원된 프로세스로 전달과 같은 절차로 이루어진다.

이 때 어느 시점에 이전의 프로세스로 돌아갈 지 결정하는 것을 스케줄링이라고 하며, 스케줄러라고 하는 커널 내부의 코드에 의해 처리된다. 예를 들어 커널이 실행할 새 프로세스를 선택할 때, 커널이 그 프로세스를 '스케줄'했다고 한다.

프로세스의 제어 🕹️

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

프로세스 ID 가져오기

  • getpid 함수는 호출하는 함수의 PID(프로세스 ID)를 리턴한다.

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

프로세스의 생성과 종료

프로세스는 프로그래머의 관점으로 보면 세 상태로 나뉜다. 실행중, 정지, 종료.

  • exit 함수는 종료 상태 status로 프로세스를 종료한다.

  • fork 함수를 불러서 자식 프로세스를 생성한다. 자식 프로세스는 부모와 거의 동일하다(복사본)

  • 비슷해 보이지만 부모와 자식이 별도의 프로세스이고, 별도의 사적 공간을 가진다는 것을 기억하자.

자식 프로세스의 청소

프로세스가 모종의 이유로 종료될 때, 커널은 시스템에서 이를 즉시 제거하지 않는다. 프로세스는 부모가 청소할 때까지 종료된 상태로 남아 있다. 이 때 청소되지 않고 남아있는 프로세스를 좀비라고 한다.

  • waitpid 함수는 지정한 자식 프로세스가 종료될 때 까지 부모 프로세스를 일시정지 시키는데, 이를 통해 자식 프로세스들의 상태, 종료 여부를 확인할 수 있다.

프로세스 재우기

  • sleep 함수는 일정 기간 동안 프로세스를 정지시킨다.

프로그램의 로딩과 실행

  • execve 함수는 현재 프로그램의 컨텍스트 내에서 새로운 프로그램을 로드하고 실행한다.
  • 프로그램과 프로세스의 차이점 알아두기
    프로그램은 코드와 데이터가 합쳐진 것으로, 디스크 상에 목적파일이나 주소공간에 세그먼트로 존재할 수 있다. 프로세스는 실행중에 있는 프로그램의 특정 사례이다. 프로그램은 항상 어떤 프로세스의 컨텍스트 내에서 돌아간다.
profile
저의 미약한 재능이 세상을 바꿀 수 있을 거라 믿습니다.

0개의 댓글

관련 채용 정보