티스토리 뷰

제2장 커널의 개요

1장에서는 고수준의 관점에서 UNIX 시스템 환경을 설명하였다. 2장에서는 커널에 초점을 맞추어 그 구조를 개략적으로 알아보고, 이 책의 나머지 부분을 이해할 수 있도록 필수적인 기본 개념 및 구조를 간단하게 설명한다.

2.1 UNIX 운영체제의 구조(architecture)

번역의 변명: 

디바이스와 장치를 혼용하여 쓴다. 읽는 이가 적절히 알아보길..
예를 들어 'device driver'는 '디바이스 드라이버'로, 'storage device'는 '기억 장치'로 번역


UNIX 시스템은 마치 파일 시스템이 "장소"를 가지고 있고, 프로세스가 "생명"을 가진 것처럼 환상을 부린다고 언급한 바 있다. 파일과 프로세스는 UNIX 시스템 모델에서 핵심적인 두 가지 개념이다. 그림 2.1은 커널에 대한 블럭 다이어그램으로서 여러 모듈 및 그들간의 관계를 보여주고 있다. 특히 이 그림은 커널을 이루는 두가지 주요 요소인 파일 서브시스템과 프로세스 제어 서브시스템을 각각 왼쪽과 오른쪽에 보여주고 있다. 몇몇 모듈이 다른 모듈의 내부동작과 상호작용을 하기 때문에 실제 커널은 이와 다르지만, 이 다이어그램은 커널을 논리적이고도 효과적으로 표현하고 있다.


그림 2.1에는 사용자, 커널, 하드웨어의 세가지 레벨이 나타나 있다. 시스템 콜과 라이브러리 인터페이스는 그림 1.1에 나온 커널과 사용자 프로그램간의 경계 역할을 한다. 시스템 호출은 C 프로그램에서의 일반적 함수 호출과 같은 형태이며, 라이브러리는 함수호출을 운영체제 속으로 들어가는데 필요한 프리미티브로 바꾸어 준다. 이에 관해서는 6장에서 상세하게 설명할 것이다. 어셈블리 언어 프로그램은 시스템 콜 라이브러없이 직접 시스템 콜을 호출할 수 있다. 프로그램들은 표준 입출력 라이브러리와 비슷한 라이브러리들을 이용하여 시스템 호출을 효과적으로 사용할 때가 맣다. 라이브러리는 컴파일 할 때,프로그램에 링크되어 사용자 프로그램의 일부가 되어 소기의 목적을 수행한다. 나중에 이것을 설명하는 예가 나올 것이다. 

그림 2.1은 시스템 호출을 파일 서브시스템과 관련된 것과 프로세스 제어 서브시스템과 관련된 것의 두 집합으로 나눈 것이다. 파일 서브시스템은 파일 공간의 할당, 파일 미사용 공간의 관리, 파일들에 대한 액세스를 제어, 사용자를 위한 데이터의 적재 등을 다룬다. 프로세스는 여러 특정 시스템 콜을 이용하여 파일 서브시스템을 사용한다. 여기에는 open()close()read()write()stat()(파일의 속성을 확인), chown()(파일 소유자를 변경), chmod()(파일 액세스 권한을 변경) 등이 있다. 5장에서 이들을 포함한 여러 시스템 호출에 대해 상세히 설명할 것이다.

파일 서브시스템은 커널과 보조 기억 장치간의 데이터 흐름을 제어하는 버퍼 메커니즘을 사용하여 파일 데이터에 액세스 한다. 버퍼링 메커니즘은 블럭 I/O 디바이스 드라이버와 상호 작용하여 커널과의 데이터 전송을 시작한다. 디바이스 드라이버는 주변 장치의 동작을 제어하는 커널 모듈이다. 블럭 I/O 디바이스는 랜덤(random) 액세스 기억장치이다; 다르게 표현하자면, 디바이스 드라이버는 블럭 I/O 장치를 랜덤 액세스 기억 장치처럼 보이도록 해준다. 예를 들어, 테이프 드라이버는 커널이 테이프 장치를 랜덤 액세스 기억장치로 취급하도록 한다. 파일 서브시스템은 버퍼 기법을 사용하지 않는 "로우(raw)" 입출력 디바이스 드라이버를 직접 다루기도 한다. 캐릭터 디바이스 라고도 불리는 로우 디바이스는 블럭 디바이스가 아닌 모든 장치를 포함한다.

프로세스 제어 서브시스템은 프로세스 동기화, 프로세스간 통신, 메모리 관리, 프로세스 스케쥴링을 담당한다. 프로세스를 실행(executing)하기 위해 파일을 메모리에 적재(loading)할 때, 파일 서브시스템과 프로세스 제어 서브시스템은 서로 상호작용 한다.(7장에서 다룬다). 즉, 프로세스 시스템은 파일을 실행하기 전에 파일을 메모리로 읽어 들여야 한다.

프로세스를 제어하는 시스템 콜 중에는 프로세스를 생성하는 fork(), 동작중인 프로세스에 다른 프로그램 이미지로 뒤덮는(overlay) exec(), 프로세스의 실행을 마치는 exit(), 프로세스에 할당된 메모리의 크기를 제어하는 brk(), 프로세스가 비정상적 이벤트를 처리하도록 제어하는 signal() 등이 있다. 7장에서 이것들을 포함하여, 여러 시스템 콜을 상세하게 다룰 것이다. 

메모리 관리 모듈은 메모리의 할당을 제어한다. 언제라도 시스템이 모든 프로세스를 위한 실제 메모리가 부족해진다면, 모든 프로세스가 공평하게 실행될 수 있도록, 커널은 프로세스들을 메인에서 보조 메모리로 혹은 보조에서 메인 메모리로 옮긴다. 9장에서 메모리를 관리하는 두가지 전략, 스왑(swapping)과 요구페이징(demand paging)을 설명한다. 스와퍼 프로세스(swapper process)는 때때로 스케줄러라고 불린다. 왜냐하면 스왑 프로세스가 프로세스 메모리 할당을 조정(schedule)하고, CPU 스케줄러의 동작에 영향을 주기 때문이다. 그러나 이 책에서는 CPU 스케줄러와의 혼동을 피하기 위해서 '스와퍼'라고 부를 것이다.

'스케줄러' 모듈은 프로세스에게 CPU를 할당하는 일을 한다. 프로세스가 자원(resource)을 확보하기 위하여 CPU를 자발적으로 양보할 때까지, 혹은 프로세스가 시간 할당량(time quantum)을 초과하여 커널이 프로세스를 선점(preempt)할 때까지, 스케줄러는 프로세스의 실행 일정을 조정한다. 그리고 나서 스케줄러는 프로세스들 중 가장 높은 우선 순위를 가지는 프로세스를 선택한다; 원래의 프로세스가 가장 높은 순위를 가지며 유효(available)할 때, 그 프로세스가 다시 실행될 것이다. 프로세스간 통신에는 비동기적 이벤트 신호로부터 동기적 메시지 전송에 이르기까지 여러가지가 있다.


번역노트:

  1. 대부분의 교과서에서 preemptive를 '선점'으로 번역한다. 하지만 선점은 preoccupy이며, 어원을 보자면 'pre앞서-occupy차지한다' 이다. 
  2. preempt가 쓰인 영어 문장은 대체적으로 "A process preempts other process."의 형태를 갖고 있다. 즉 "프로세스가 다른 프로세스를 선점한다"라는 식으로 번역된다. 이걸 보면 '선점'이라는 번역용어는 "프로세스가 CPU가 아닌 프로세스를 선점하다니??"라는 의문을 가졌으며, 번역용어가 적절치 못하다는 느낌이 들었었다. 
  3. preemtive의 어원을 보자면 'pre앞서-empt비운다' 이다. 적당한 번역용어로는 '선제(앞서 견제한다)' 겠지만, 번역으로 선점을 그대로 쓴다. 나는 선점이라 쓰고 프리엠션이라 읽는다
  4. 맘이 바뀌면 언제라도 '선제'라는 단어를 써야겠다.

참고로 영영사전에서 preempt의 정의는 다음과 같다.
If you pre-empt an action, you prevent it from happening by doing something which makes it unnecessary or impossible.


마지막으로, 하드웨어 제어는 인터럽트 처리 및 컴퓨터와의 통신을 책임진다. CPU가 어떤 프로세스르 실행하는 중에, 디스크나 단말기 같은 장치가 CPU를 인터럽트할 수도 있다. 만일 인터럽트가 걸리면, 인터럽트를 처리하고 난 다음, 커널은 인터럽트 당한 프로세스를 계속해서 실행할 것이다. 인터럽트는 특별한 프로세스가 처리하는 것이 아니라, 커널의 특별한 함수에 의해 처리된다. (함수는 그 시간에 동작하고 있는 프로세스의 컨텍스내에서 호출된다)

2.2 시스템 개념의 소개

이 절에서는 주요 커널 자료 구조(data structure)를 소개하고, 그림 2.1에서 소개한 여러 모듈의 기능을 더욱 자세히 설명한다.

2.2.1 파일 서브시스템 Overview

파일의 내부 표현은 아이노드(inode)로 한다. 아이노드는 파일 데이터의 디스크 내 배치에 대한 정보(위치 및 크기)와 그 외 정보(파일 소유자, 액세스 권한 정보, 액세스 시간 등)를 가지고 있다. (역주: 아이노드에 포함된 정보는 두가지 부류로 나뉘어지며, 파일 자체에 관한 정보와 부가적인 정보로 분류가 된다.) 아이노드는 인덱스 노드(index node)의 줄임말로서 UNIX 시스템에 관한 문헌에서 공통적으로 사용된다. 모든 파일은 각각 하나의 아이노드를 가지고 있다. 파일은 여러 개의 이름을 가질수 있으나, 모든 이름은 원래 파일의 아이노드에 매핑된다. 그리고 각 이름은 "링크(link)" 라고 불린다. 프로세스가 이름으로 파일을 참조할 때, 커널은 파일 이름에서 한 번에 한 구성요소를 파싱한 다음, 프로세스가 경로상에 있는 디렉토리를 액세스할 권한이 있는지 체크하여, 마침내 파일에 대한 아이노드를 가지고 온다(retreive). (역주: retreive는 "검색"과 "회수"의 개념을 내포한다. 즉 "검색해서 가지고 온다"라는 표현으로 받아들여야 한다.) 예를 들어, 프로세스가 


open("/fs2/mjb/rje/sourcefile", 1); /* 1 means O_WRONY */

를 호출하면, 커널은 "/fs2/mjb/rje/sourcefile"에 대한 아이노드를 가지고 온다.


프로세스가 새로운 파일을 생성할 때는, 커널이 그 파일에 새로운 아이노드를 할당한다. 아이노드는 파일 시스템에 저장되는 반면, 커널이 파일을 다룰 때는 아이노드를 메인 메모리에 있는 인코어(in-core) 아이노드 테이블로 읽어 들인다. (core라는 용어는 하드웨어 기술이 아닌, 머신의 1차 메모리(primary memory)를 나타낸다.) 


커널은 아이노드 테이블 외, "파일 테이블"과 "사용자 파일 디스크립터(descriptor) 테이블"을 가지고 있다. 파일 테이블은 전역(global) 자료 구조지만, 사용자 파일 디스크립터 테이블은 각프로세스마다 하나씩 할당된다. 프로세스가 파일을 열거나 생성하면, 커널은 각 테이블마다 엔트리를 하나씩 할당하는데 이 엔트리는 파일의 아이노드에 상응한다. 3개의 자료 구조, 사용자 파일 디스크립터 테이블, 파일 테이블, 아이노드 테이블에 있는 엔트리들은 파일 상태 정보와 파일에 대한 사용자 액세스 상태 정보를 유지한다. 파일 테이블은 사용자의 다음 읽기나 쓰기가 어디에서 시작할지를 나타내는 바이트 오프셋과 파일을 open()한 프로세스에 허용된 액세스 권한에 대한 정보를 끊임없이 갱신한다(keep track of). 사용자 파일 디스크립터 테이블은 사용자 프로세스에 의해 열린 모든 파일들을 분간해 준다. 그림 2.2는 3개 테이블과 테이블들의 관계를 보여준다. 시스템 콜 open()과 creat()은 파일 디스크립터를 반환하며, 이는 사용자 파일 디스크립터 테이블의 인덱스(index)값 이다. read()와 write()를 실행할 때, 커널은 파일 디스크립터를 이용하여 사용자 파일 디스크립터 테이블에 접근한 뒤, 파일 테이블의 엔트리를 가리키는 포인터, 다음 아이노드 테이블 엔트리를 가리키는 포인터, 순으로 따라간다. 그 다음, 아이노드 테이블 엔트리에 있는 아이노드로부터 파일에 있는 데이터를 찾아낸다. 4장과 5장에서 이들 자료 구조를 자세히 설명한다. 지금은, 3개의 테이블을 이용하면, 다양한 공유등급(sharing degrees)으로 파일에 접근할 수 있다는 점을 말하기에 충분하다. (For now, suffice it to say that use of three talbes allows various degrees of sharing access to a file.)


UNIX 시스템은 일반파일과 디렉토리를 테이프나 디스크같은 블럭 디바이스에 저장한다. 테이프는 액세스 시간이 디스크에 비해 너무 느리기 때문에, UNIX 시스템이 설치될 파일 시스템에 테이프를 사용하는 경우는 거의 없다. 다가올 미래에는 디스크가 없는 워크스테이션이 널리 이용될 것이며, 파일이 원격 시스템에 저장되고 네트워크를 통하여 액세스될 것이다(13장 참조). 그러나, 책 내용이 간결하도록, 책 나머지 부븐에서는 디스크를 사용한다고 가정한다. 설치된 시스템은 몇 개의 물리 디스크 유닛을 가지고 있을 것이며, 각 디스크에는 하나 이상의 파일 시스템(file system)이 있을 것이다. 하나의 디스크를 몇 개의 파일 시스템으로 나누는 파티셔닝(Partitioning)은 시스템 관리자가 저장된 데이터를 쉽게 관리하도록 해준다. 커널이 디스크와 교류하기 보다 파일 시스템과 논리적 수준에서 교류하며, 각 파일 시스템을 논리적 디바이스 번호로 식별되는 논리 디바이스로 간주한다. 논리적 디바이스(파일 시스템)과 물리적 디바이스(디스크) 간의 주소 변환은 "디스크 드라이버"가 한다. 이 책에서 디바이스라는 용어는 특별히 언급하지 않는 한 논리적인 장치를 이미한다. 


파일 시스템은 논리 블럭의 연속으로 구성되며, 각각 512, 1024, 2048, 혹은 시스템 구현에 따른 512 바이트의 정수배이다. 논리 블럭의 크기는 파일 시스템 내에서는 동일하지만, 그 시스템의 다른 파일 시스템은 다른 크기일 수도 있다. 논리 블럭을 크게 설정하면, 디스크와 메모리간에 유효 데이터 전송율이 증가한다. 왜냐하면, 커널은 디스크 연산(operation)당 더 많은 데이터를 전송하여, 연산 시간이 짧아진다. 에를 들어, 예를 들어 디스크로부터 1K 바이트를 한 번의 연산으로 읽는 것은 512바이트를 두번 읽는 것보다 빠르다. 하지만, 만약 로지컬 블럭이 크다면, 저장 용량의 효울성은 떨이질 것이다. 이 내용은 5장에서 나온다. 이 책에서 "블럭"이란 용어는 논리 블럭을 의미하며, 별도로 정의하지 않는다면, 논리 블럭의 크기는 1KB 이다.

파일 시스템은 다음의 구조를 가지고 있다. (그림 2.3)

  • 부트 블럭(boot block)은 파일 시스템의 제일 첫부분(일반적으로 첫번째 섹터(sect))에 위치하고 있으며, 부트스트랩(bootstrap) 코드를 가지고 있다. 한편, 부트스트랩 코드는 운영체제를 머신으로 읽어들이던가 머신을 초기화하는 코드이다. 시스템을 부팅하기 위해서는 단 하나의 부트 블럭이 필요하지만, 모든 파일 시스템들은 제 각기 부트블럭(가능하면 비어 있다)을 가지고 있다. 
  • 슈퍼 블럭(super blcok)은 파일 시스템의 상태를 기술한다. 파일 시스템이 얼마나 큰지, 얼마나 많은 파일을 저장할 수 있는지, 빈공간을 어디에서 찾을 수 있는 지와 그 외 정보 등이 있다.
  • 아이노드 리스트(inode list)는 파일 시스템에서 수퍼 블럭 뒤에 나온다. 파일 시스템을 구성할때 관리자가 아이노드의 리스트의 크기를 지정한다. 관리자가 파일시스템을 구성할 때, 아이노드 리스트의 크기를 지정한다. 커널은 아이노드 리스트에서 인덱스르르 이용하여 아이노드를 참조한다. 특정한 한 아이노드는 "루트 아이노드(root inode)" 이다. mount() 시스템 콜을 호출하고 나면, 루트 아이노드를 통해서 파일 시스템의 디렉토리 구조에 접근한다.
  • 데이터 블럭(data block)은 아이노드 리스트의 끝에서 시작하며, 파일 데이터와 관리 데이터가 들어있다. 할당된 데이터 블럭은 파일 시스템 내의 단 하나의 파일에만 소속된다.

2.2.2 프로세스

이 절에서는 프로세스 서브시스템에 대해 설명한다. 프로세스의 구조와 프로세스가 메모리를 관리를 하기 위해 필요한 자료구조를 설명한다. 프로세스 다이어그램을 보고나서, 몇몇 상태 전이와 관련된 이슈들을 고려해 볼 것이다.


프로세스는 "실행 중인 프로그램"이며, CPU가 기계 명령("텍스트"라고 함), 데이터 및 스택으로 해석되는 바이트들의 형식(pattern)으로 구성된다. 커널이 프로세스 실행을 위해 스케줄링하므로, 많은 프로세스가 동시에 실행되는 것처럼 보인다. 몇몇 프로세스는 같은 프로그램이 실행된 것들일 수도 있다. 프로세스는 제한된 시퀀스 혹은 그 프로세스가 포함한 기계명령어만 실행하며, 다른 프로세스의 기계명령어를 실행할 수 없다. (프로세스는 자신의 "데이터 섹션(data section)"과 "스택 섹션(stack section)"에서 읽고 쓸수 있지만, 다른 프로세스의 데이터 섹션과 스택 섹션을 읽고 쓸수 없다.) 프로세스는 시스템 콜을 통하여 다른 프로세스 혹은 세상의 다른 시스템과 통신한다.


실무적인 용어로, UNIX 시스템의 프로세스는 시스템 콜 fork()로 생성된 엔티티(entity, 구별된 그리고 독립적인 존재)이다. "프로세스 0" 을 제외한 모든 프로세스는 다른 프로세스가 fork()를 실행할 때 생성된다. fork()를 호출한 프로세스를 "부모 프로세스(parent process)"라 부르고, 새로 생성된 프로세스는 "자식 프로세스(child process)"라 부른다. 어떤 프로세스라도 단 하나의 부모 프로세스를 가지지만, 프로세스는 여러 자식 프로세스를 가질 수 있다. 커널은 각 프로세스를 프로세스 번호로 구별하는데, "프로세스 ID" (PID)라 한다. 프로세스 0은 시스템 부팅할때 "직접(by hand)" 생성한 특별한 프로세스이다. 프로세스 0이 자식 프로세스(PID가 1)를 생성(fork)하면, 프로세스 0은 "스와퍼(swapper)" 프로세스가 된다. 프로세스 1(init으로 알려져 있음)은 시스템 내 모든 프로세스의 "조상(ancestor)"이며, 매 프로세스마다 특별한 관계를 맺는다 (7장에서 설명한다).


사용자는 소스 코드로부터 실행파일(executable file)을 생성하기 위해 프로그램 소스코드를 컴파일 한다. 실행파일은 여러 부분(part)들로 이루어져 있다: 

  • "헤더(header)"의 집합(헤더는 파일의 속성을 나타낸다)
  • 프로그램 명령어로 이루어진 텍스트
  • 프로그램이 시작할 때 쓰는 초기화 값을 가지고 있는 데이터 및 그와 관련된 기계 언어적 표현과 커널이 bss를 위해 얼마나 공간 할당해 주어야 하는지에 대한 표시(bss는 초기화되지 않은 데이터지만, 런타임 때 커널이 0으로 초기화 한다)
  • 심볼 테이블(symbol table) 등과 같은 나머지 섹션

역주: 1) 이 부분은 어셈블리어에 관한 용어가 섞여 나온다. 어셈블리어 기초를 다진 후 보면 좀 더 이해가 쉽다. 2) xNIX계열이라면 GNU의 objdump 명령어를 이용하여 위의 사항들을 확인해 볼 수 있다. 


그림 1.3 프로그램을 예로 들자면, 실행 파일의 텍스트 섹션은 main()과 copy()이 컴파일된 코드이며, 초기화된 데이터 섹션은 변수 version이며(초기화된 데이터를 가져야 하므로 소스코드에 기입한다), 초기화 되지 않은 데이터 섹션은 배열 buffer이다. System V의 C 컴파일러는 기본적으로 데이터 영역과 텍스트 영역을 분리하여 생성하지만, 기계명령어가 데이터 섹션에 포함될 수 있도록 옵션을 따로 제공한다.


커널은 시스템 콜 exec()이 실행되는 동안 실행 파일을 메모리로 로딩하며, 로딩된 프로세스는 최소 세 영역(region)으로 이루어 진다. 세 영역은 텍스트(text), 데이터(data) 그리고 스택(stack)이다. 텍스트와 데이터 영역은 각각 실행 파일의 텍스트 섹션과 data-bss 섹션에 상응한다. 하지만, 스택 영역(stack resgion)은 동적으로 생성되며, 스택 영역의 크기는 런타임(run time) 시점에 커널에 의해 동적으로 조정된다.(역주: 실제로 uname 명령어로 스택 크기를 사용자가 변경하며, 커널이 그 설정 값으로 프로그램을 로딩한다). 스택은 논리적인 "스택 프레임(stack frame)"들로 구성되어 있다. 함수를 호출할 때는 스택 프레임을 push하고, 반환할 때 스택 프레임을 pop한다.("스택 포인터(stack pointer)"라 불리는 특별한 레지스터가 스택의 현재 깊이를 알려준다). 스택 프레임은 함수의 매개변수, 지역 변수(local variables),와 이전 스택프레임을 복구하기 위한 정보가 있다(그 정보는 프로그램 카운터(PC) 값과 이전 함수의 스택포인터를 포함한다). 커널은 필요에 따라 스택 공간을 할당하며, 프로그램 코드는 스택 증가를 관리하는 기계명령어들을 가지고 있다. 그림 1.3의 프로그램에서 main() 함수의 매개변수 argcargv와 변수 fdoldfdnew는 main() 함수가 호출되었을때 해당 스택 프레임에 나타나며(관습에 의해, main()은 매 프로그램마다 한 번만 호출된다), copy() 함수의 매개변수 oldnew 그리고 지역변수 count는 copy()가 호출될 때마다 해당 스택 프레임에 나타난다.


UNIX 시스템의 프로세스는 유저모드와 커널모드로 실행되기 때문에, 각 모드 별로 별도의 스택을 사용한다. 유저 스택은 유저모드 실행되는 함수들을 위한 인수(arguements), 지역 변수와 그 외 정보를 담고 있다. 그림 2.4의 왼쪽은, 그림 1.3 copy 프로그램이 write() 시스템 콜을 호출했을 때 프로세스의 유저 스택을 보여주고 있다. 프로세스 시작 프로시져(라이브러리에 포함되어 있음)가 1번 스택 프레임을 사용자 스택에 푸시하여 두 매개변수를 가진 main() 함수를 호출하며,main() 함수가 1번 프레임에 지역변수 공간을 먼저 확보한다. 그리고, main()이 두 파라미터를 가진 copy() 함수를 호출하며, 2번 프레임을 유저 스텍에 push 한다. 역시 copy() 함수도 자신의 지역변수를 위한 공간을 2번 프레임에서 확보한다. 마지막으로, 라이브러리 함수 write()를 호출하여 시스템 콜 write()를 호출한다. 각 시스템 콜은 시스템 콜 라이브러리에 진입점(entry point)이 있다. 시스템 콜 라이브러리는 어셈블리어로 작성되어 있으며, "트랩(trap)"이라는 특별한 기계명령어를 포함한다. 트랩이 실행되면 언터럽트가 발생되고, 인터럽트가 하드웨어를 커널모드로 전환시킨다. 프로세스가 특정 시스템 콜을 호출하기 위해 라이브러리의 진입점을 부른다면, 라이브러리 함수의 스택 프레임을 생성한다. 프로세스가 특별한 기계명령어를 실행할 때, 프로세스는 커널모드로 전환되어 커널코드를 실행하고, 커널 스택을 사용하게 된다.


커널 스택은 "커널 모드로 실행되는 함수"를 위한 스택 프레임들로 구성된다. 커널 스택상의 함수와 데이터 엔트리는 사용자 프로그램이 아닌 커널의 함수와 데이터를 가리킨다. 그러나 구성은 사용자 스택과 동일하다. 프로세스가 사용자 모드로 실행 중 일때, 프로세스의 커널 스택은 비어있다. 그림 2.4의 오른쪽 부분은 write()를 호출한 copy 프로세스의 커널 스택을 보여준다. 여기에 있는 알고리즘에 대해서는 나중에 write() 시스템 콜을 설명하면서 상세히 다룰 것이다.


모든 프로세스는 커널의 프로세스 테이블(process table)안에 엔트리를 가지며, 각 프로세스는 "u-area"를 할당받는다. u area은 커널만 조작가능한 비공유(private) 데이터를 담고 있다. 프로세스 테이블은 "프로세스 당 영역 테이블(a per process region table)"을 포함하며(혹은 가리키며), 프로세스 당 영역테이블의 엔트리는 "영역테이블(region table)"의 엔트리를 가리킨다. "리전(region)"은 텍스트, 데이터, 스택 등, 프로세스 주소 공간의 연속적인 공간이다. 리전 테이블 엔트릐는 리전의 속성을 설명한다. 리전 속성은 리전이 텍스트를 가지고 있는지 혹은 데이터를 가지고 있는지, 공유 혹은 비공유 되었는지, 리전의 "데이터"가 메모리의 어느 부분에 위치 했는가 등이 있다. 단계를 하나 더 둔 간접 액세스(프로세스 당 리전테이블을 거쳐서 리전테이블에 액세스)을 통해, 독립적인 여러 프로세스가 리전 테이블을 공유하는 것이 가능하다. 프로세스가 exec()를 호출할 때, 커널은 프로세스가 사용해오던 리전을 해제(free)한 다음, 텍스트 리전, 데이터 리전 그리고 스택 리전을 새로 할당한다(역주: 왜냐하면 프로세스 이미지, 즉 프로그램이 다르니까). 프로세스가 fork()를 호출할 때, 커널은 이전 프로세스의 주소공간을 복제(duplicate)하는데, 가능하다면 프로세스가 공간을 공유하고, 그렇지 않다면 실제로 복사한다. (역주: 텍스트 리전은 공유하고, 그 외 리전(데이터, 스택, 힙 리전 등)은 복사). 프로세스가 exit()를 호출할 때, 커널은 프로세스가 사용했던 리전을 해제한다. 그림 2.5에서 동작중인 프로세스와 관련된 자료구조를 보여준다: 프로세스 테이블은 프로세스 당 리전테이블을 가리키며, 프로세스 당 리전테이블의 엔트리는 리전테이블을 가리킨다. 리전 테이블의 엔트리는 프로세스의 텍스트, 데이터, 그리고 스택 리전을 가리킨다. 


프로세스 테이블 엔트리와 u area는 프로세스의 제어 정보 및 상태 정보를 가지고 있다. u area는 프로세스 테이블의 연장선(extention)이다. 6장에서 이 두 테이블 간의 차이를 검토한다. 뒷 장에서 논의할 프로세스 테이블의 항목(field)에 다음과 같은 것들이 있다.

  • 상태(state field)
  • 프로세스 소유자의 식별자(user ID 혹은 UID)
  • 프로세스가 suspend될 때(sleep 상태로 변경될 때) 설정된 이벤트 디스크립터.

프로세스가 실행될 때(executing), 커널은 "(프로세스가) 접근할 필요가 있는 프로세스 정보"를 u area에 저장한다. (역주: 그렇다고 유저모드에서 접근가능하다는 뜻이 아니다. 유저모드에서는 커널 오브젝트를 액세스할 수 없다) u area의 중요한 항목은 다음과 같다.

  • 현재 실행중인 프로세스의 프로세스 테이블 슬롯을 가리키는 포인터
  • 시스템 콜의 매개변수, 리턴값, 에러코드
  • 열려진 모든 파일의 파일 디스크립터
  • 내부 I/O를 위한 매개변수
  • 현재 디랙토리 위치와 현재 루트 (5장 참조)
  • 프로세스와 파일 크기의 제한값들

역주: execute와 run 둘 다 사전적 의미로 '실행하다'을 가지고 있다. 그런데, execute는 "커널이 프로그램을 메모리로 올려서 실행"을 함의한다 (즉, exec()가 하는 일이다). 그리고, run은 "프로세스가 자신의 동작을 실행"을 내포하기 때문이다. 따라서, execute를 "실행되다", run을 "동작하다"로 구별하여 번역한다.


커널은 실행하고 있는 프로세스의 u area를 직접 액세스하지만, 다른 프로세스의 u area를 액세스할 수 없다. 내부적으로, 커널은 현재 동작하는 프로세스의 u area를 액세스하기 위해, 구조체 변수 u를 참조한다. 또한, 다른 프로세스가 실행될 때, 커널은 그 프로세스의 가상주소를 재구성하여, 구조체 u가 새 프로세스의 u area를 가리키게 한다. 이러한 구조, 즉 커널이 u area에서 프로세스 테이블 엔트리로 가는 포인터를 참조하는 방식에서는 커널이 프로세스를 쉽게 식별할 수 있다.

2.2.2.1 프로세스의 컨텍스트

프로세스 컨텍스트는 프로세스의 상태이며, 텍스트, 전역 사용자 변수와 자료구조의 값, 프로세스가 사용하는 CPU 레지스터 값, 프로세스의 프로세스테이블의 슬롯과 u area의 값, 그리고 유저스택과 커널스택의 내용으로 정의된다. 운영체제의 텍스트 영역과 전역 자료구조는 모든 프로세서에 의해 공유되지만(역주: 커널모드를 상기하자), 프로세스 컨텍스트의 한 부분으로 구성되지 않는다.


프로세스를 실행할 때, 시스템이 그 프로세스의 컨텍스트 안에서 실행된다고 말할 수 있다. 커널이 '다른 프로세스'를 실행시키고자 결정할 때, 시스템이 '어느 프로세스'의 컨텍스트 안에서 실행되고 있어서 "컨텍스트 스위치"를 한다. 커널은 뒤에서 살펴볼 특정한 조건하에서만 컨텍스트 스위치를 허락한다. 컨텍스트 스위치되어 내려간 프로세스가 나중에 되돌아와서 실행을 재개해야하므로, 컨텍스트 스위치를 할 때 커널은 프로세스의 정보를 충분히 저장한다. 유사하게, 유저모드에서 커널모드로 전환할 때, 유저모드로 돌아와서 전환되었던 지점에서 실행을 계속할 수 있도록 커널은 충분한 정보를 저장한다. 유저모드와 커널모드 사이를 이동하는 것은 모드 변경이지 컨텍스트 스위치가 아니다. 그림 1.5를 상기해보면, 커널이 프로세스 A에서 프로세스 B로 환경(context)을 변경할 때, 커널은 컨텍스트 스위치를 한다. 반면에 커널이 모드를 커널에서 유저로 혹은 유저에서 커널로 변경해도, 프로세스는 하나의 문맥상에서 실행되고 있을 뿐이다. 


커널은 인터럽트를 당한 프로세스의 컨텍스트 내에서 인터럽트를 처리한다 (심지어 인터럽트를 건 프로세스가 자신이 아니어도). 인터럽트 된 프로세스는 유저모드 혹은 커널모드에서 실행되고 있었을 것이다. 커널이 인터럽트 당한 프로세스가 후에 실행을 재개하고 커널모드에서 인터럽트를 처리해야 하기 때문에, 커널은 인터럽트를 처리하기 위하여 프로세스를 따로 생성하거나 스케줄링을 하지 않는다.

2.2.2.2 프로세스의 상태

프로세스의 생애는 여러가지 상태로 나뉘어질 수 있으며, 상태는 프로세스를 기술하는 확고한 특징이다. 6장에서 프로세스의 모든 상태에 대해 언급하지만, 지금은 다음 상태들을 이해하는 것이 필수적이다.


  1. 해당 프로세스가 유저모드에서 실행되고 있다.
  2. 해당 프로세스가 커널 모드에서 실행되고 있다.
  3. 해당 프로세스가 실행되고 있지 않지만, 스케줄러가 그 프로세스를 선택하자 마자, 그 프로세스는 곧바로 실행을 준비한다. 많은 프로세스가 이 상태에 있으며, 스케줄링 알고리즘에서 다음에 실행될 것이 결정된다. 
  4. 프로세스가 수면 중(sleeping)이다. 프로세스는 I/O가 완료될 때까지 기다리는 것과 같이 실행을 계속할 수 없을 때, 프로세스는 자신을 슬립상태로 둔다.

한 시점에 단 하나의 프로세스만 실행될 수 있기 때문에, 최대 한 프로세스가 1번 혹은 2번 상태에 있을 수 있다. 그리고, 1번과 2번 항목은 각각 유저모드와 커널모드에 상응한다.

2.2.2.3 상태 전이(state transition)

위에서 기술한 프로세스 상태가 정적인 것처럼 보여질 수 있지만, 실제로 프로세스는 잘 정의된 규칙에 따라 자신의 상태를 끊임없이 변경한다. 상태전이도(state transition diagram)은 방향성 그래프(directed graph)인데, 그래프의 노드는 자신이 들어갈 수 있는 상태를 나타내며 그래프의 간선(edge)은 상태를 다른 것으로 변경되도록 하는 사건(event)를 나타낸다. 만일 앞의 상태와 뒤의 상태 사이에 간선이 존재한다면, 두 상태의 전이는 가능한 것(legal)이다. 하나의 상태로부터 여러 상태로 전이될 수도 있으나, 프로세스는 그 시점에 발생한 시스템 이벤트와 관계된 오직 하나의 전이만 수행한다. 그림 2.6은 위에서 정의된 프로세스 상태에 대한 상태전이도이다.


앞서 기술한 바와 같이, 많은 프로세스는 시공유(time-shared) 방법을 통해 동시에 실행될 수 있고, 커널모드에서도 동시에 실행될 수 있을 것이다. 만일 프로세스들이 제약 없이 커널모드에서 실행된다면, 프로세스들이 커널 자료 구조를 망쳐버릴 수도 있다. 임의의 컨텍스트 스위치를 금지하는 것과 인터럽트의 발생을 제어하는 것을 통해, 커널은 자신의 일관성을 보호한다.


프로세스가 오직 "커널 동작(kernel running)"에서 "메모리에서 슬립(asleep in memory)" 상태로 전이할 때만, 커널은 컨텍스트 스위치를 허용한다. 시스템은 유저모드 프로세스를 선제한다. 하지만, 커널모드에서 동작중인 프로세스는 다른 프로세스에 의해 선점될 수 없다. 그래서 커널은 "비선점(non-preemptive)"이라고 말할 수 있다. 커널의 자료구조는 비선점이기 때문에, 커널은 커널 자료구조의 일관성을 유지할 수 있다. 그 때문에, 상호 배제(mutual execlusion) 문제를 해결한다. 한번에 최대 한 프로세스만 임계 영역(crtical section)과 관련된 코드를 실행한다.


예를 들어, 그림 2.7의 예제 코드를 고려해보자. 자료구조 bp1을 자료구조 bp의 다음에 위치하게 이중 링크드 리스트에 놓는다. 만일 시스템이 코드를 실행하는 중간에 컨텍스트 스위치를 허용한다면, 다음과 같은 경우가 발생할 수 있다. 커널모드 프로세스 A가 주석 처리한 부분까지 실행했는데 컨텍스트 스위치가 발생했다고 가정하자. 이때 이중 연결 구조는 일관성이 깨진 상태가 된다. 즉, bp1의 링크드 리스트 연결이 반은 끊어지고 반은 연결된 상태가 된다. 만일 이때 다른 프로세스가 앞으로 가는 포인터(forp)를 계속해서 따라가면, bp1을 찾을 수 있으나, 뒤로가는 포인터(backp)를 계속해서 따라가면 bp1을 찾을 수 없다(그림 2.5). 만일 기존 프로세스 A가 다시 동작하기 이전에 다른 프로세스들이 저 포인터들을 수정한다고 치자. 그렇다면, 이중 연결 리스트의 자료구조는 영구적으로 파괴될 것이다. UNIX 시스템은 이러한 상황을 예방하기 위하여 커널모드 프로세스가 컨텍스트 스위치 되는 것을 금지한다. 만일 프로세스가 "sleep" 상태가 되어 컨텍스트 스위치를 허락한다면, 커널 알고리즘은 시스템 자료구조가 안전하고 일관적인 상태를 보장하도록 작성되었다는 것이다.


인터럽트 처리는 커널 상태 정보를 변경시킬 수 있으며, 이로 인해 커널 데이터의 일관성을 해칠 수도 있다. 예를 들어, 그림 2.7의 코드를 커널이 실행하였고, 주석 부분에서 인터럽트가 발생했는데, 인터럽트 핸들러가 포인터를 조작했다면, 이전에 봤던 그림처럼 자료구조가 망가진다. 이러한 문제를 해결하기 위해, 시스템은 커널모드에서 동작하는 동안 모든 인터럽트를 방지할 수도 있다. 하지만, 인터럽트 처리가 지연되며, 전체 시스템 처리량이 떨어질 수도 있다. 이전 방법 대신에, 코드상의 임계영역에 진입할 때, 커널은 프로세서의 실행 레벨을 올려 인터럽트를 방지한다. 만일 임의의 인터럽트 핸들러가 실행되어 일관성이 깨지는 것을 야기한다면, 그 영역은 임계영역이라 한다. 예를들어, 디스크 인터럽트 핸들러가 그림의 버퍼큐를 조작한다면, 버퍼큐를 조작하는 코드 영역을 디스크 인터럽트 핸들러와 관련된 임계영역이다. 임계영역은 작고 빈번하지 않기에, 임계영역은 시스템 처리량에 큰 영향을 주지 않는다. 다른 운영체제는 이러한 문제를 해결하기 위해, 시스템 상태에서 실행될 때 모든 인터럽트를 막는 방법 혹은 일관성을 유지하도록 복잡한 락킹 스키마를 이용하는 방법을 이용한다. 위에서 언급한 문제들을 12장에서 멀티프로세서에 관한 이슈와 같이 다룬다. 여기서 설명한 해결법으로는 부족하다. 

리뷰를 하자면, (역주:커널의 일관성을 망가뜨리는 조건는 두 가지가 있다. 컨텍스트 스위칭과 중첩된 인터럽트 처리이다.) 커널은 일관성을 보호하기 위해, 프로세스 스스로가 "sleep" 상태로 들어갈 때만 컨텍스트 스위치를 허용하고 어떤 프로세스가 다른 프로세스 상태를 변경하는 것을 금지한다. 뿐만아니라, 일관성을 해칠 수 있는 또 다른 문제인 인터럽트는 프로세서 실행 레벨을 높여서 해결한다. 프로세스가 CPU 사용을 독점하지 못하도록, 프로세스 스케줄러는 주기적으로 유저모드에서 실행되고 있는 프로세스들을 선점한다.

2.2.2.3 sleep과 wakeup

커널모드에서 실행되는 프로세스는 시스템 이벤트에 대해 어떻게 반응할 지를 결정함에 있어 엄청난 자율성을 가지고 있다. 프로세스들은 그들간에 통신을 할 수 있으며 다른 대안도 제시할 수 있다. 하지만, 최종적인 결정은 프로세스 스스로가 한다. 곧 설명하겠지만, 프로세스가 다양한 환경에 직면할 때, 프로세스가 따르는 규칙이 몇 가지가 있으나, 최종적으로 각 프로세스는 자신의 결정으로 규칙을 따른다. 예를 들어, 프로세스가 동작을 일시적으로 보류(suspend)해야 할 때("sleep 상태가 된다"), 프로세스 자신의 의지로 그렇게 한다. 그런데 만일 인터럽트 중에 "sleep"이 가능하다면, 인터럽트 당한 프로세스가 (자신의 의지와는 상관없이) sleep 상태가 될 수 있는 상황이 발생한다. 따라서, 이러한 상황을 막기 위해, 인터럽트 핸들러는 "sleep" 상태로 갈 수 없다.


역주
인터럽트의 발생은 프로세스 자신의 의지가 아니라 외부에서 의도한 것이다! 따라서 자신의 의지로 sleep을 해야하는 것을 위배한다.


어떤 이벤트가 발생되기 전까지, 프로세스는 "sleep" 상태에 놓여지는데, 이벤트에는 주변장치의 I/O가 끝나는 것, 프로세스의 종료, 시스템 자원을 사용할 수 있는 것 등이 있다. 프로세스가 "이벤트에 대해 sleep 한다"라는 것은, "프로세스가 이벤트가 발생하기 전까지 sleep 상태에 있다"는 것을 의미하며, 이벤트가 발생한 시점에 프로세스는 깨어나서(wake up) "실행 준비(ready to run)" 상태로 들어간다. 많은 프로세스들이 이벤트에 대해 sleep할 수 있으며, 이벤트가 발생했을 때는 이벤트에 대해 sleep 상태였던 모든 프로세스가 깨어난다 (이벤트 조건이 더 이상 true가 아니므로). 프로세스가 깨어날 때, 프로세스의 상태는 "sleep"에서 "ready-to-run"으로 전이되며, 상태 "ready-to-run"는 해당 프로세스가 이후에 실행될(scheduling) 자격이 있음을 나타낸다 (즉각 실행되지는 않는다). sleep 프로세스는 CPU 자원 소비하지 않는다. 조금 자세하게 말하자면, 커널은 프로세스가 sleep 중인지 끊임없이 체크하지 않는다. 그 보다는, 커널은 이벤트가 발생되길 기다리고, 이벤트가 발생하면 프로세스들을 깨운다.


예를 들어, 커널모드 프로세스는 이후 단계에서 sleep 되는 경우에 대비하여 자료구조를 lock해야 한다. 자세히 말하자면, 잠긴(locked) 자료구조를 조작하려는 프로세스는 lock을 반드시 체크해야 하며, 만일 다른 프로세스가 lock을 소유하고 있다면 sleep 한다. 커널에서 그런 lock을 다음과 같은 방식으로 구현한다:


while (condition is true) /* condition: the buffer is locked? */
    sleep (event: the condition becomes false);
set condition true;


다음과 같은 방식으로 lock을 풀고 해당 lock에 대해 sleep하고 있던 모든 프로세스를 깨운다:


set condition false; /* condition: buffer is locked? */
wakeup (event: the condition is false);


그림 2.9는 A,B,C 세 프로세스가 lock된 버퍼를 획득하려고 경쟁하는 시나리오를 보여주고 있다. sleep 조건은 '버퍼가 잠겨 있음'이다. 프로세스들 중 한번에 하나만 실행되고, 버퍼가 잠겨있음을 알고, 버퍼가 unlock 되는 이벤트에 대해 sleep한다. 마침내, 버퍼는 unlock되고, 모든 프로세스는 깨어난 후 "ready-to-run" 상태가 된다. 이후 커널은 실행할 어느 한 프로세스(B라고 하자)를 선택한다. 프로세스 B는 "while" 루프를 돌면서 버퍼가 unlock된 것을 발견한 다음, 버퍼를 lock한 뒤, (자신의 일을) 진행한다. 만일 프로세스 B가 버퍼를 unlock 하기 전에 다시 sleep 상태로 들어간다면(I/O가 끝나길 기다리는 것), 커널은 다른 프로세스가 실행되도록 스케줄링 한다. 스케줄링 후에 프로세스 A가 선택되었다면, 프로세스 A는 "while" 루프를 돌면서 버퍼가 잠겨있음을 확인하여, 또 다시 "sleep" 상태로 들어갈 것이다. 프로세스 C의 과정도 A와 비슷하게 진행될 것이다. 그런 다음 프로세스 B가 깨어나고 버퍼를 unlock하여, A와 C중 하나가 버퍼에 접근할 수 있도록 한다. 이와 같이, "while-sleep" 루프는 '단 하나의 프로세스만이 자원에 접근함'을 보장한다. 


6장에서 "sleep"과 "wakeup"에 대해 더 자세하게 설명한다. 그러는동안, "원자성(atomic)"에 대해서도 고려해야 한다. 즉, 프로세스가 즉시 sleep 상태로 들어가고 wakeup 전까지 거기에 머물러야 한다. sleep 상태로 들어간 후에, 커널은 다른 프로세스가 실행되도록 스케줄링 하고, 프로세스의 컨텍스트를 스위치 한다.

2.3 커널의 자료 구조(data structure)

대부분의 커널 자료구조는 다소 동적으로 할당된 공간보다 고정된 크기의 테이블에 저장된다. 이러한 접근법의 장점은 커널 코드가 단순해지는 것이다. 하지만, 자료구조를 위한 엔트리 개수는 시스템을 설치할 때 설정되므로 제한적이다. 만약, 시스템 운영 동안 커널이 자료구조 엔트리를 다 쓴다면, 동적으로 새 엔트리에 대해 메모리 할당을 할 수 없다.(다만 요청한 사용자에게 에러만 알려준다). 반면에, 만일 테이블 공간이 부족해지는 것을 막기위해, 커널을 재구성한다면, 그 공간은 다른 목적으로 사용되지 않기 때문에, 테이블의 남는 공간은 낭비된다. 그럼에도, 일반적으로 커널 알고리즘의 단순함이 극단적인 효율성보다 더 중요하게 고려된다. 일반적으로 알고리즘은 비어있는 테이블 엔트리를 찾기 위해 단순한 루프를 사용하는데, 그 방법은 이해하기 쉬우며 어떠한 경우에는 복잡한 할당 알고리즘보다 더 효율적으로 동작한다.

2.4 시스템 관리

관리 프로세스들은 사용자 편의를 위해 다양한 기능을 제공하는 프로세스로 막연하게 분류된다. 그런 기능에는 디스크 포맷, 새파일 시스템 생성, 손상된 파일시스템 수리, 커널 디버깅 등을 포함한다. 개념적으로, 관리 프로세스와 사용자 프로세스간에 어떠한 차이도 없다. 그들은 일반 사용자들이 호출할 수 있는 동일한 시스템 콜을 사용한다. 허락된 특권만이 관리 프로세스와 일반 사용자 프로세스와의 차이점이다. 예를들어, 파일 퍼미션은 관리 프로세스가 파일을 그냥 조작할 수 있게 설정된 반면, 일반 사용자에게는 제한되어 있을 것이다. 내부적으로, 커널은 "슈퍼유저(super user)"라 불리는 특별한 사용자를 구별하며, 그 사용자에게 특별한 권한을 부여한다(이후에 살펴봄). 사용자가 슈퍼유저가 되기 위해, 로그인 과정을 통과하거나 특별한 프로그램을 실행한다. 슈퍼유저의 다른 용도는 이후에 살펴보기로 한다. 요약하자면, 커널은 관리 프로세스를 별도의 집단으로 인식하지 않는다.

2.5 요약

이장에서 커널의 구조를 설명했다. 커널은 파일 서브시스템과 프로세스 서브시템, 두 개의 주요 컴포넌트로 이루어져 있다. 파일 서브시스템은 스토리지와 사용자 파일의 데이터 검색을 제어한다. 파일은 파일시스템 안에서 조직되며, 파일시스템은 논리 디바이스로 다뤄진다. 디스크와 같은 물리적인 디바이스는 여러 논리 디바이스(파일시스템)를 포함할 수 있다. 각 파일시스템은 파일시스템의 구조와 내용물을 기술하는 슈퍼블럭을 가지고 있고, 파일시템의 각 파일은 파일 속성을 가지고 있는 아이노드로 표현된다. 파일을 다루는 시스템 콜은 inode를 이용하여 자신의 기능을 수행한다.


프로세스는 다양한 상태에 존재할 수 있으며, 잘-정의된 전이 규칙에 따라 상태가 변한다. 특이하게, 커널 모드에서 실행중인 프로세스는 실행을 보류하여 sleep 상태로 들어갈 수 있다. 하지만 프로세스는 다른 프로세스를 sleep 시킬 수 없다. 커널은 비선점적이며, 이것은 커널모드 프로세스는 sleep 상태가 되기전까지 혹은 유저모드로 변경되기 전까지 계속 실행된다는 것을 의미한다. 임계영역에 접근하는 코드를 실행할 때, 커널은 커널 자료구조의 일관성을 유지하도록 비선점 정책을 강제적으로 적용하거나 혹은 인터럽트를 막아(blocking)버린다.


책의 나머지 부분에서는 그림 2.1에 나온 서브시스템들과 그들간의 상호작용에 대해 자세히 설명하는데, 파일 시스템에서 시작하여 프로세스 서브시스템으로 나아간다. 다음 장은 버퍼 캐시와 버퍼 할당 알고리즘을 설명하는데, 그 알고리즘은 4, 5, 7에 나올 알고리즘 내에서 쓰인다. 4장에서는 파일 시스템의 내부 알고리즘을 검증하며, 내부 알고리즘은 아이노드 조작, 파일 구조, 아이노드에서 파일명으로 변환 등을 포함한다. 5장에서는 파일을 엑세스하는 open()read()write()와 같은 시스템 콜을 설명하며, 그 시스템 콜들은 4장에 나온 알고리즘을 이용한다. 6장은 프로세스의 컨텍스트와 프로세스의 주소 공간에 대한 기본 개념을 다루며, 7장에서는 메모리를 관리하는 시스템 콜에 대해 다루는데, 6장에서 나온 알고리즘을 사용한다. 8장에서는 프로세스 스케줄링을 검토하며, 9장에서는 메모리 관리 알고리즘에 대해 논의한다. 10장에서는 디바이스 드라이버를 다루는 데, 이유는 터미널 드라이버와 프로세스 관리간의 상호 관계를 설명하므로 10장으로 미루었다. 11장은 여러 형태의 프로세스간 통신을 설명한다. 마지막으로 끝의 두 장은 멀티프로세스 시스템과 분산시스템과 같은 고급 주제를 다룬다.

2.6 연습문제

생략


저작자 표시 비영리 변경 금지
신고
댓글
댓글쓰기 폼