[WEEK 09] 컴퓨터 시스템 - 8. 예외적인 제어흐름

신호정 벨로그·2021년 10월 9일
0

Today I Learned

목록 보기
49/89

8. 예외적인 제어흐름

8.1.3 트랩과 시스템 콜

트랩은 의도적인 예외상황으로, 어떤 인스트럭션을 실행한 결과로 발생한다.

트랩 핸들러는 제어를 다음 인스트럭션으로 리턴한다.

트랩의 가장 중요한 사용은 시스템 콜이라고 알려진 사용자 프로그램과 커널 사이의 프로시저와 유사한 인터페이스를 제공하는 것이다.

사용자 프로그램은 파일을 읽거나(read), 새로운 프로세스를 만들거나(fork), 새 프로그램을 로드하고(execve), 현재 프로세스를 종료하는 등의 서비스를 커널에게 요청한다.

이러한 커널 서비스의 제한된 접근을 하기 위해서 프로세서는 특별한 "n" 인스트럭션을 제공하며, 이들은 사용자 프로그램이 서비스 n을 요청하고자 할 때 사용자 프로그램이 사용할 수 있는 인스트럭션이다.

syscall 인스트럭션을 실행하면 트랩이 인자들을 해독하고 적절한 커널 루틴을 호출하는 예외 핸들러로 가게 한다.

시스템 콜은 일반적인 함수 호출과 동일하나 함수는 사용자 모드에서 돌아가며, 실행할 수 있는 인스트럭션은 제한적이고 호출하는 함수와 동일한 스택을 사용한다.

시스템 콜은 커널 모드에서 돌아가며, 이로 인해 커널 내에서 정의된 스택에 접근하며, 특권을 가진 인스트럭션을 실행할 수 있도록 해준다.

리눅스/x86-64 시스템 콜

리눅스는 파일을 읽거나 쓸 때, 또는 새로운 프로세스를 만들 때 응용 프로그램이 사용할 수 있는 다양한 시스템 콜을 제공한다.

각 시스템 콜은 커널 점프 테이블의 오프셋에 대응되는 유일한 정수를 갖는다.

C 프로그램은 syscall 함수를 사용해서 직접 시스템 콜을 호출할 수 있다.

표준 C 라이브러리는 대부분의 시스템 콜에 대해서 래퍼 함수들을 제공한다.

래퍼 함수는 인자들을 패키징하고, 커널을 적절한 시스템 콜 인스트럭션으로 트랩을 걸고, 호출하는 프로그램으로 시스템 콜의 리턴 상태를 전달한다.

x86-64 시스템에서 시스템 콜은 syscall이라고 부르는 트랩 인스트럭션을 통해서 제공된다.

리눅스 시스템 콜에 전달되는 모든 인자들은 스택보다는 범용 레지스터를 통해서 이뤄진다.

%rax는 시스템 콜 번호를 보관하며, %rdi, %rsi, %rdx, %r10, %r8, %r9에 최대한 여섯 개의 인자들을 보관할 수 있다.

8.2 프로세스

프로세스는 실행 프로그램의 인스턴스로 정의되며, 시스템 내의 각 프로그램은 어떤 프로세스의 문맥(context)에서 돌아간다.

문맥은 프로그램이 정확하게 돌아가기 위해서 필요한 상태로 구성되며, 메모리에 저장된 프로그램의 코드와 데이터, 스택, 범용 레지스터의 내용, 프로그램 카운터, 환경변수, 열려 있는 파일의 식별자를 포함한다.

8.2.1 논리적인 제어흐름

하나의 프로세서를 사용해서 여러 프로세스들이 교대로 돌아간다.

8.2.2 동시성 흐름

자신의 실행시간이 다른 흐름과 겹치는 논리흐름을 동시성 흐름이라고 부르며, 이 두 흐름은 동시에 실행한다고 말한다.

한 프로세스가 자신의 흐름 일부를 실행하는 매 시간 주기를 타임 슬라이스라고 부른다.

8.2.3 사적 주소공간

프로세스는 각 프로그램에 자신이 시스템의 주소공간을 혼자서 사용한다는 착각을 불러 일으킨다.

프로세스는 각 프로그램에 자신만의 사적 주소공간을 제공한다.

x86-64 리눅스 프로세스에 대한 주소공간의 구조는 커널 가상 메모리, 사용자 스택, 공유 라이브러리를 위한 매모리 매핑 영역, 런타입 힙, 코드와 데이터 세그먼트로 구성된다.

주소공간의 아랫 부분은 일반적인 코드, 데이터, 힙, 스택 세그먼트를 갖는 사용자 프로그램을 위해 예약된다.

코드 세그먼트는 항상 주소 0x400000에서 시작한다.

주소공간의 윗 부분은 커널을 위해 예약되어 있으며, 이 부분은 커널이 프로세스를 대신해서 인스트럭션을 실행할 때(예를 들면 응용 프로그램이 시스템 콜을 실행할 때) 사용하는 코드, 데이터, 스택을 포함하고 있다.

8.2.4 사용자 및 커널 모드

프로세서는 응용 프로그램이 접근할 수 있는 주소공간 부분 뿐만 아니라 응용 프로그램이 실행할 수 있는 인스트럭션들을 제한하는 메커니즘을 제공한다.

이러한 작업을 지원하기 위해서 프로세스가 현재 가지고 있는 특권을 저장하는 일부 제어 레지스터로 모드 비트를 제공한다.

모드 비트가 설정되면 프로세스는 커널 모드로 동작한다.

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

모드 비트가 세트되지 않을 때, 프로세스는 사용자 모드에서 돌고 있는 것이다.

사용자 프로그램은 시스템 콜을 통해서 커널 코드와 데이터에 간접적으로 접근해야 한다.

응용 코드를 실행하는 프로세스는 처음에는 사용자 모드에 있게 된다.

프로세스가 사용자 모드에서 커널 모드로 진입하는 유일한 방법은 인터럽트, 오류, 트랩 시스템 콜 같은 예외를 통해서다.

예외가 발생해서 제어가 예외 핸들러로 넘어가면, 프로세서는 사용자 모드에서 커널 모드로 변경한다.

핸들러는 커널 모드에서 돌아간다.

제어가 응용 코드로 돌아오면 프로세서는 모드를 커널 모드에서 다시 사용자 모드로 변경한다.

8.2.5 문맥 전환

운영체제 커널은 문맥 전환(context switch)라고 알려진 예외적인 제어흐름의 상위수준 형태를 사용해서 멀티태스킹을 구현한다.

커널은 각 프로세스마다 컨텍스트를 유지하며 컨텍스트는 커널이 선점된 프로세스를 다시 시작하기 위해서 필요로 하는 상태다.

이것은 범용 레지스터, 부동소수점 레지스터, 프로그램 카운터, 사용자 스택, 상태 레지스터, 커널 스택, 여러 가지 커널 자료구조와 같은 주소공간을 규정하는 페이지 테이블, 현재 프로세스에 관한 정보를 가지고 있는 프로세스 테이블, 프로세스가 오픈한 파일에 관한 정보를 저장하는 파일 테이블 같은 객체들의 값으로 구성된다.

커널은 프로세스가 실행되는 동안의 어떤 시점에 현재 프로세스를 선점하고 이전에 선점된 프로세스를 다시 시작할 것을 결정할 수 있다.

이 결정은 시케줄링이라고 알려져 있으며, 스케줄러라고 불리는 커널 내부의 코드에 의해 처리된다.

커널이 실행할 때 새 프로세스를 선택할 때 커널이 그 프로세스를 스케줄했다고 표현한다.

커널이 실행할 새 프로세스를 스케줄한 후에 현재 프로세스를 선점하는 것을 문맥 전환(context switch)라고 하며, 문맥 전환을 사용해서 새로운 프로세스로 제어를 이동한다.

문맥 전환의 수행 절차는 (1) 현재 프로세스의 컨텍스트를 저장하고, (2) 이전에 선점(일시적으로 정지)된 프로세스의 저장된 컨텍스트를 복원하며, (3) 제어를 이 새롭게 복원된 프로세스로 전달한다.

문맥 전환은 커널이 사용자를 대신해서 시스템 콜을 실행하고 있을 때 발생할 수 있다.

시스템 콜이 어떤 이벤트의 발생을 기다리기 때문에 블록된다면 커널은 현재 프로세스를 sleep시키고 다른 프로세스로 전환한다.

또한 문맥 전환은 인터럽트의 결과로 발생할 수 있다.

모든 시스템은 대개 1ms 또는 10ms마다 주기적인 타이머 인터럽트를 생성하는 메커니즘을 가지고 있다.

타이머 인터럽트가 일어날 때마다 커널은 현재 프로세스가 충분히 오래 실행되었다고 판단하여 새로운 프로세스로 전환할 것을 결정할 수 있다.

8.4 프로세스의 제어

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

프로세스는 실행중, 정지, 종료 세 가지의 상태 중 하나로 생각할 수 있다.

부모 프로세스는 fork 함수를 호출해서 자식 프로세스를 생성한다.

자식은 코드, 데이터 세그먼트, 힙, 공유된 라이브러리, 사용자 스택을 포함하는 부모의 사용자 수준 가상 주소공간과 동일한 복사본을 갖는다.

부모와 새롭게 생성된 자식 간의 중요한 차이는 서로 다른 PID를 가진다는 것이다.

부모와 자식은 별도의 프로세스이므로 이들은 자신만의 사적 주소공간을 가진다.

execve 함수는 현재 프로그램의 컨텍스트 내에서 새로운 프로그램을 로드하고 실행하며, 실행가능 목적파일 filename과 인자 리스트 argv, 환경변수 리스트 envp을 사용해서 로드하고 실행한다.

0개의 댓글