티스토리 뷰

크리에이티브 커먼즈 라이선스
Creative Commons License

1부 시스템 전반적인 개요

제1장 시스템의 개관

UNIX 시스템은 1969년 개발이 시작된 이래 마이크로프로세서에서 메인 프레임에 이르기까지 다양한 연산능력을 갖추고 있는 여러 컴퓨터상에서 동작되고 있으며, 그것들을 아우르는 공통된 실행 환경을 제공해주고 있다. UNIX 시스템은 크게 두개의 부분으로 구성되어 있는데, 첫째로 UNIX 시스템 환경이 널리 보급되도록 지원해 주고 있는 프로그램과 서비스 부분이다. 즉 사용자들이 직접 접하는 쉘, 메일, 문서처리 패키지(text processing package), 소스코드 제어 시스템 등의 프로그램이 이에 포함된다. 두번째는 이러한 프로그램과 서비스들을 지원해주는 운영체제로 구성되어 있다. 이 책에서는 운영체제의 자세한 설명을 목적으로 하고 있다. 또한 AT&T사에서 개발된 UNIX System V를 중점적으로 설명하지만 다른 버전에서 제공되는 중요한 특징들도 포함되어 있다. 특히 궁극적으로 사용자에게 표준 사용자 인터페이스를 제공하는 운영체제의 주요 자료구조와 알고리즘을 살펴볼 것이다. 1장은 UNIX 시스템에 대한 소개 부분이다. 따라서 전체적인 시스템의 구조와역사를 살펴본다. 2장에서는 운영체제에 대한 구체적인 내용을 소개한다.

1.1 역사

1975년 벨 연구소는 GE사와 MIT의 MAC 프로젝트와 공동으로 새로운 운영체제인 Multics[Organick 72]를 개발하였다. Multics 시스템의 목적은 여러 사용자들이 동시에 컴퓨터를 액세스할 수 있고, 기억장치와 연산능력을 증대시키고, 사용자들이 원한다면 데이터를 공유할 수 있도록 해주는데 있다. 후에 UNIX 시스템 개발을 시작했던 대다수의 사람들이 벨 연구소의 Multics 개발에 참여했던 사람들이었다. Multics 시스템의 초기 버전은 1969년에 발표되어 GE 645 컴퓨터에서 실행되기는 했지만, 처음에 의도했던 서비스를 제공해 주지 못했을 뿐더러 그 목적과도 정확히 일치되지는 않았다. 결과적으로 벨 연구소는 그 프로젝트에의 참여를 끝내게 되었으며 결국 벨 연구소의 Computing Science Research Center의 직원들은 "편리한 상호 작용형 연산 서비스[Ritchie 84a]"를 제공하지 못한 채 Multics 프로젝트를 마치게 된 것이다. 그 후 그들의 프로그래밍 환경을 개선하고자 Ken Thompson, Dennis Ritchie, 그 외 사람들이 일종의 파일 시스템을 설계했는데, 그것이 발전하여 UNIX 파일 시스템이 되었다. Thompson은 파일 시스템과 요구 패이징(demand paging) 환경의 동작을 시뮬레이트하는 프로그램을 작성하였고, 또한 GE 645 컴퓨터의 간단한 커널을 만들었다. 동시에 그는 GECOS시스템 상에서 게임 "우주여행"을 FORTRAN으로 작성하였다. 그러나 그 프로그램의 문제점은 "우주선"을 조정하기가 어렵고, 프로그램이 동작하기 위해 많은 비용이 든다는 것이었다. Thompson은 후에 좋은 그래픽 디스플레이를 제공하면서, 적은 비용으로 동작될 수 있는 PDP-7 컴퓨터를 발견하게 되었다. PDP-7 컴퓨터 상에서 "우주 여행" 프로그램을 통해 Thompson은 기계에 대해 많은 것을 배우게 되었으나, 프로그램 개발 환경은 GECOS 컴퓨터 상의 크로스-어셈블리가 필요했고, PDP-7으로 입력하기 위해 종이 테이프를 날라야 했다. 그래서 좀더 좋은 개발환경을 위해 Thompson과 Ritchie는 UNIX 파일 시스템의 초기 버전과 프로세스 서브시스템, 유틸리티 프로그램의 일부를 포함하여 PDP-7 상에서 시스템 설계를 구현하였다. 마침내, 새로운 시스템은 GECOS 시스템이 개발환경으로 요구되지 않았으며, 시스템 스스로를 지원할 수 있게 되었다. Computing Science Researche Center의 다른 연구원인 Brian Kernghan가 새로운 시스템에 UNIX라는 이름을 붙였다. 


비록 UNIX 시스템의 초기 버전이 많은 가능성을 지니고 있었지만, 실제 프로젝트에서 사용되기 전까지 가능성을 실현하지 못했다. 그후 벨 연구소의 특허부서에 문서처리 시스템을 제공하는 동안 UNIX 시스템은 1971년 PDP-11로 이식되었다. 그 시스템의 특징으로는 작은 규모, 즉 시스템 자체가 16KB 사용자 프로그램을 위해 8KB, 512KB의 디스크, 한 파일당 64KB의 제한하는 점 등이 있다. 초기의 성공 이후에 Thompson은 새로운 시스템을 위해 FORTRAN 컴파일러를 만들었으며 또한 BCPL[Richard 69]의 영향을 받은 B라는 언어를 고안하게 되었다. B 언어는 해석적(interpretive) 인 구조가 가지는 성능의 문제가 있어서, Ritchie는 B를 발전시켜 기계어 코드의 생성, 데이터 유형의 선언, 자료 구조의 정의가 가능한 언어인 C를 만들었다. 1973년에 운영체제는 C 언어로 다시 쓰여졌는데, 이는 전례가 없는 일로써 사용자들에게 큰 충격이었다고 할 수 있다. 벨 연구소 내에도 UNIX 시스템이 25개나 설치될 정도로 증가되었고 내부 지원을 위하여 UNIX 시스템 그룹이 형성되었다.


그 당시 AT&T사는 1956년 연방 정부와 맺은 법령에 따라 컴퓨터 제품을 팔 수 엇ㅂ는 입장이었지만 교육용으로 희망하는 일부 대학에는 UNIX 시스템을 제공해 주었다. AT&T사는 그 법령을 준수하여 결코 시스템을 광고하거나 팔거나 지원을 해주지는 않았는데, 그럼에도 시스템의 인기는 점차 높아지기 시작하였다. 1974년에 Thompson과 Ritchie는 UNIX 시스테메 관한 논문을 Communication of ACM에 발표하였는데 이것이 UNIX 시스템 보급의 또 다른 계기가 되었다. 1977년경에는 125개 대학을 포함하여 약 500여곳에 UNIX 시스템이 설치되었다. UNIX 시스템은 프로그램 개발, 네트워크 처리 서비스, MERT[Lycklama 78a]를 통한 실시간 처리에 있어 좋은 환경을 제공해 주었기 때문에 전화교환 회사 등에서 특히 인기가 있었다. 결국 대학 뿐 아니라 상업적인 단체에서도 UNIX 시스템에 대한 허가를 갖게 되었다. 1977년 경에 Interactive System Corporation 은 UNIX 시스템의 첫번째 Value Added Reseller(VAR)이 되었는데 이들의 주목적은 사무 자동화용 시스템의 개발이었다. 또한 1977년에는 최초로 UNIX 시스템이 PDP가 아닌 Interdata 8/32라는 컴퓨터에 이식(port)되었다. 


마이크로프로세서의 보급과 함꼐 다른 회사에서도 UNIX 시스템을 새로운 컴퓨터에 이식하게 되었는데, UNIX 시스템이 간단하고 알기 쉽기 때문에 많은 개발자들이 자기만의 방법으로 시스템을 개선하였으며, 결과적으로 여러 버전이 나오게 되었다. 1977년부터 1982년 사이에 벨 연구소는 여러 AT&T 변종들을 하나의 시스템에 통합하였는데, 상업적으로 UNIX System III이라 알려져 있다. 이후 벨 연구소는 여러 기능을 UNIX System III에 추가하였는데, UNIX System V라 불리우며, AT&T사는 198년 1월에 이 시스템에 대한 공식적인 지원을 발표하였다. 그러나 Berkeley에 있는 캘리포니아대학에서 몇 가지 새롭고 흥미로운 기능들을 제공하는 UNIX 시스템의 변형을 개발하였다.(최신 버전은 VAX에서 수행되는 4.3 BSD). 이책에서는 UNIX System V를 중점적으로 기술하며, 경우에 따라 BSD 시스템에서 제공되는 기능을 언급한다.


1984년 초에는 UNIX 시스템이 100,000여곳에 설치 되었는데, 머신들은 각각 마이크로프로세서에서부터 메인프레임에 이르기까지 다양한 계산능력을 보이며, 많은 회사의 서로 다른 제품군에 걸쳐있다. 이러한 요구를를 수용하는 다른 운영체제는 없었다. UNIX 시스템의 성공과 인기에 대해 몇가지 이유를 들 수 있다. 


  • 시스템 자체가 고수준 언어(High-level language)로 작성되어 있어서, 읽고 이해하고, 변경하기 쉬우며, 다른 시스템에 이식하기가 쉽다. Ritchie는 C 언어로 쓰여진 것이 어셈블리로 작성한 것 보다 20-40% 정도 무겁고 느리다고 추정했다. 하지만, 고수준 언어를 사용하는 장점은 단점보다 더 많다.
  • 사용자가 요구하는 서비스를 쉽게 제공해 줄 수 있는 간단한 사용자 인터페이스가 있다.
  • 단순한 프로그램으로부터 복잡한 프로그램을 만들 수 있도록 프리미티브(primitives)를 제공한다.
  • 유지가 용이하고 구현을 효율적으로 할 수 있는 계층적(hirearchical) 파일 시스템을 사용한다.
  • 응용 프로그램을 쉽게 만들 수 있는 일관된 파일 형식, 즉 바이트 스트림을 사용한다.
  • 주변 디바이스와의 인터페이스가 간단하고 일관성이 있다.
  • 멀티유저, 멀티프로세스 시스템이다. 즉, 각 사용자들이 동시에 여러개의 프로세스를 실행할 수 있다.
  • 사용자가 컴퓨터의 구조를 몰라도 되므로, 다른 하드웨어에서도 동작하는 프로그램을 작성하기가 쉽다.

단순함과 일관성의 철학은, UNIX 시스템을 부각시키며 위에 언급된 이유들의 근본이 된다. 운영체제와 많은 명령어 프로그램이 C 언어로 쓰여졌음에도 UNIX 시스템은 Fortran, Basic, Pascal, Ada, Cobol, Lisp, Prolog 와 같은 다른 언어도 지원한다. 즉 UNIX 시스템은 어떠한 언어라도 지원한다. 컴파일러 또는 인터프리터를 가지는 언어라도 지원하며, 사용자 요구를 UNIX 시스템이 제공하는 것으로 매핑된 인터페이스를 가진 언어라도 지원한다.


프리미티브란? 사전적 의미로 '원시적인 도구'이지만, '투박하고 거친 근원적인 도구'란 의미에 가깝다. 하위 레벨, 즉 커널에서 제공하는 기능들을 의미한다. semaphore 라던가 소켓이 대표적인 예이다.

1.2 시스템 구조

그림 1.1은 UNIX 시스템에 대한 구조를 개략적으로 나타낸 것이다. 그림 중앙의 하드웨어는 운영체제의 1.5절에서 설명하게 될 기본적인 서비스를 제공한다. 또한 운영체제는 하드웨어와 직접 상호작용하여 프로그램에 공통적인 서비스를 제공하고, 하드웨어의 개별적인 특성에 무관하게 동작할 수 있도록 해준다. 시스템을 층(layer)의 집합으로 볼 때, 운영체제는 시스템 커널 또는 간단히 커널이라고 불리우며 사용자 프로그램과는 구분이 된다. 프로그램에 하드웨어 종속적인 코드가 없다면, UNIX 시스템이 서로 다른 하드웨어에서 동작되더라도 개발자는 프로그램을 쉽게 이식할 수 있다. 왜냐하면 프로그램은 밑에 있는 하드웨어에 독립적이기 때문이다. 예를 들어 워드(word)의 크리를 얼마로 가정하여 작성한 프로그램은 이를 가정하지 않고 작성한 프로그램보다 이식하기 어렵다.


그림 1.1 UNIX 시스템의 구조

그림에서 바깥쪽에 나타난 쉘이나 편집기(ed나 vi) 등의 프로그램은 시스템 콜(system call)을 통해 커널과 상호작용을 한다. 시스템 콜은 커널로 하여금 호출한 프로그램을 위해 여러가지 작업을 하고 커널과 프로그램 간에 데이터 교환을 하도록 지시한다.

그림에 나타난 몇몇 프로그램은 표준 시스템에 있는 것들로 명령어(Command)라고 불린다. 그러나 사용자 프로그램도 C 컴파일러에 의해 만들어진 실행 파일인 a.out으로서 명령어 층에 존재할 수도 있다. 그 밖의 다른 응용 프로그램은 저수준 프로그램의 상단, 즉 그림의 가장 외부에 존재한다. 예를 들어 표준 C 컴파일러인 cc는 그림의 가장 외부에 있는데, 이는 C 전처리기(preprocessor), 2 패스(two-pass) 컴파일러, 어셈블러, 로더(링크-에디터) 등 하위 프로그램을 부르게 된다. 그림 상에서는 비록 응용프로그램을 2단계로 나눠 놓았지만, 적절한 수준이 무엇이 되든 간에 사용자는 계층구조를 확장할 수 있다. UNIX 시스템상에서 바람직한 프로그래밍 스타일은 태스크(Task)를 완성하기 위해 기존의 프로그램을 조합하는 것이다.


고수준의 응용서브시스템, 그리고, 쉘, 에디터, SCCS(Source Code Control System) 및 문서처리 팩키지와 같은 프로그램들을 함친 것이 점점 "UNIX 시스템" 이라고 불리게 되었다. 그러나 궁극적으로는 모든 프로그램은 커널이 제공하는 저수준 서비스를 이용한다. System V에는 64개의 시스템 콜이 있는데, 자주 쓰이는 것은 32개 미만이다, 시스템 콜은 사용이 용이하면서도 더 많은 기능을 제공하기 위햐여 옵션을 사용할 수 있도록 되어있다. 시스템 호출과 이들을 구현한 내부적인 알고리즘들이 커널의 본체를 형성하며, 이책에 제시된 UNIX 운영체제 연구는 여러 시스템 콜과 시스템 콜간의 상호작용을 자세히 연구하고 분석하는 쪽으로 흘러간다. 요약하면 커널은 UNIX 시스템의 모든 응용프로그램이 의존하는 서비스들을 제공하며, 이런 서비스들을 정의한다. 이책에서 "UNIX 시스템", "커널", "시스템" 과 같은 말이 많이 나오는데 모두 운영체제의 커널을 의미하는 말로 혼돈이 없기를 마란다.

1.3 사용자 관점

이 절에서는 파일 시스템, 처리 환경, 빌딩 블럭 프리미티브(예를 들면 pipe)와 같은 UNIX 시스템의 고수준 기능에 대해 알아본다. 뒷 장에서 이러한 기능에 대한 커널의 동작을 자세히 알아본다.


def) Building Block
a component that fits with others to form a whole.
전체를 구성하기 위하여 다른 것들에게 끼우는 것

1.3.1 파일 시스템

UNIX 파일시스템의 특징은 다음과 같다.


  • 계층적 구조
  • 파일 데이터의 일관된 처리
  • 파일 생성과 삭제 기능
  • 파일 크기의 동적인 증가
  • 파일 데이터 보호
  • 디바이스를 파일처럼 처리 (터미널 및 테이프 장치와 같은 것들)

파일 시스템은 루트라고(root: "/" 로 표시됨) 불리는 노드에 연결된 트리 처럼 형성이 되어 있다; 파일 시스템 구조의 모든 leaf-노드 가 아닌 것들은 디렉토리이며, 트리의 leaf-노드에 파일들은 디렉토리, 일반 파일(regular files), 또는 특수 디바이스 파일(special device files)이다. 경로이름(path name)은 파일이 파일 시스템 내에서 어떻게 위치해 있는 가를 설명한다. 한편 파일 이름은 경로이름(path name)으로 주어진다. 경로이름은 슬래쉬 문자에 의해 구분되는 구성 요소들로 나열되어 있는데, 그 요소들은 파일이 유일하게 속한 이전 디렉토리의 이름을 나타내는 문자열이다. 총경로이름(full path name)은 슬래쉬 문자로 시작하는데, 파일 시스템의 루트에서 시작하여 파일 트리를 순회하고 경로이름 상의 그 다음 구성요소의 가지를 방문하는 식으로 순회를 한다. 따라서 "/etc/passwd" 나 "/bin/who", "/usr/src/cmd/who.c" 등은 그림 1.2에 나타난 파일들이지만, "/bin/passwd", "/usr/src/cmd/date.c" 등은 없다. 어느 경로이름은 루트로부터 시작하지 않고, 실행중인 프로세스의 현재 디랙토리에 상대적으로 지정해 줄 수 있는데, 이 경우 경로 이름에서 첫번째 슬래쉬를 제거한다. 따라서, "/dev"에서 시작한다면, 경로이름 "tty01" 은 총경로이름 "/dev/tty01"로 지정된 파일이다.


그림 1.2 파일 시스템 트리의 예

UNIX 시스템의 프로그램은 커널이 파일 데이터를 어떤 내부 형식으로 저장하는지 알수 없으며, 파일을 바이트의 무형식(unformatted) 스트림으로 취급한다. 프로그램은 자신이 원하는 바대로 바이트 스트림을 해석해도 되지만, 그것은 운영체제의 데이터 저장 방법과 아무런 관계가 없다. 즉, 파일의 데이터를 액세스하는 규칙(syntax)은 시스템에 의해 결정되며 모든 프로그램상에서 동일하지만, 데이터의 의미(semantic)은 프로그램에 의해 결정된다. 예를 들어 포매팅(formatting) 프로그램인 troff는 각 텍스트 줄의 끝에 "리턴(carriage return)" 문자가 있음을 가정한다. 그리고 시스템 회계 프로그램인 acctcom은 같은 크기의 레코드를 전제로 한다. 두 프로그램 모두 파일의 데이터를 바이트 스트림처럼 액세스하여 동일한 시스템 서비스를 사용하고, 내부적으로 스트림을 적당한 포맷으로 해석(parse)한다. 만일 어떤 프로그램이라도 포맷이 잘못된 것을 알았다면, 적절한 후속조치를 해야할 책임이 있다.


시스템이 디렉토리 데이터를 바이트 스트림처럼 처리한다는 측면에서 디렉토리는 일반 파일과 유사한다. 하지만 시스템은 디렉토리 내 파일들의 이름을 알려진(predictable) 형식으로 디렉토리 데이터에 저장하고 있다. 따라서, 운영체제나 ls(파일의 이름과 속성을 나열함)와 같은 프로그램이 디렉토리 내의 파일들을 쉽게 찾을 수 있다.


파일 액세스는 파일에 부여된 액세스 허가(access permission)에 의해 결정된다. 액세스 허가는 사용자의 세부류(파일 소유자, 파일 그룹, 그외 사용자들)에 대해 파일을 읽고, 쓰고, 실행할 수 있는지를 독립적으로 설정할 수 있다. 사용자는 그 디렉토리에 액세스가 허용된다면 파일을 생성할 수 있다. 새로 생성된 파일은 파일 시스템 디렉토리 구조의 리프 노드가 된다.

사용자 입장에서, UNIX 시스템은 디바이스를 파일처럼 다루는 것으로 보인다. 특수 디바이스 파일로 지정된 디바이스는 파일 시스템 디렉토리 구조에서 노드로 존재한다. 프로그램은 일반파일을 액세스하는 것과 동일한 규칙으로 디바이스에 액세스하며, 디바이스를 읽고 쓴다는 점에 있어서도 일반 파일을 읽고 쓰는 것과 크게 다를 바 없다. 디바이스들은 일반파일과 동일한 방식으로 보호된다. 즉, 액세스 허가를 적절히 설정해주는 방식으로 보호한다. 디바이스 이름은 일반파일 이름과 다를 바 없고, 디바이스나 일반파일에 대해 같은 연산들(operations)이 이용되기 때문에, 대부분의 프로그램은 조작하는 파일의 종류를 내부적으로 몰라도 된다.


#include <fcntl.h>
char buffer[2048];
int version = 1; /* Chater 2 explains this */
int main( int argc, char *argv[] )
{
int fdold, fdnew;
if( argc!=3 )
{
printf( "need 2 arguments for copy program\n");
exit(1);
}
fdold = open( argv[1], O_RDONLY );
if( fdold == -1 )
{
printf( "cannot open file %s\n", argv[1] );
exit( 1 );
}
fdnew = creat( argv[2], 0666);
if( fdnew == -1 )
{
printf( "cannot open file %s\n", argv[2] );
exit( 1 );
}
copy(fdold, fdnew);
exit(0);
}
void copy( int old, int new )
{
int count;
while( ( count == read( old, buffer, sizeof(buffer) ) ) > 0 )
write(new, buffer, count);
}

그림 1.3 파일을 복사하는 프로그램

예를 들어, 이미 존재하고 있는 파일을 복사하는 그림 1.3의 C 프로그램을 보자. 이 프로그램의 이름은 copy라고 했다면 사용자는 단말기에서 다움과 같이 타이핑한다.

$ copy oldfile newfile

그러면 이 프로그램이 실행되는데, 여기서 oldfile은 이미 존재하고 있던 파일 이름이고 newfile은 새로 생길 파일이름이다. 시스템은 리스트 argv 매개변수 개수를 나타내는 argc를 주고, argv의 각 요소들이 사용자가 입력한 매개변수를 갖도록 초기화시킨후 main()을 호출한다. 위의 예에서 argc는 3개이고, argv[0]은 문자열 "copy"이며(일반적으로 프로그램 이름은 0번째 매개변수이다)를 가리키며, argv[1]과 argv[2]는 각각 문자열 "oldfile""newfile"을 가리키게 된다. 그러면 프로그램은 이 매개변수의 개수가 맞는지 확인하게 된다. 만약 맞다면 oldfile을 open() 시스템 콜을 불러서 "read-only"로 열고, 만약 제대로 열었다면 newfile을 생성하기 위해 creat() 시스템 콜을 호출한다. 새로 생성될 파일의 허가모드는 8진수로 0666, 즉 모든 사용자가 읽고 쓸 수 있도록 설정된다. 모든 시스템 호출은 실패 했을 경우 -1을 반환한다. 만약 open()이나 creat() 시스템 콜이 실패한다면 프로그램은 메시지를 출력하고 반환 상태(return status)를 1의 값으로 exit() 시스템 콜을 호출하여, 프로그램 실행을 끝내고 무언가 잘못되었음을 가리킨다.


open()과 creat() 시스템 콜은 이후 프로그램이 파일을 계속 참조할 때 이용하는 파일 디스크립터(file descriptor)라는 정수 값을 반환한다. 프로그램은 이제 copy()라는 서브루틴을 부르고 반복문(loop)를 돌면서 기존 파일에서 버퍼용량 만큼 읽기 위해 read() 시스템 호출을 부르고, 읽은 내용을 쓰기 위해 write() 시스템 콜을 호출하게 된다. read() 시스템 콜은 읽은 바이트의 크기를 반환하는데, 파일끝(EOF: End-Of-File)에 도달했을땐 0을 반환한다. 따라서 프로그램은 파일의 끝에 도달하거나, read()를 호출할 때 에러가 발생한 경우(write() 리턴값은 조사하지 않는다) 종료한다. 프로그램이 copy()와 반환 상태가 0으로 세팅된 exit()에서 복귀하였다면, 프로그램이 성공적으로 종료되었음을 나타낸다.


copy 프로그램이 매개 변수로 주어진 파일을 읽거나 생성할 수 있는 권한이 있다면, 어떤 파일이든 복사한다. 파일은 프로그램의 소스코드 처럼 출력 가능한 파일일 수 있고, 심지어 copy 프로그램 자신이 될 수도 있다. 따라서 다음 두 명령어는 모두 동작한다.

$ copy copy.c newcopy.c
$ copy copy newcopy

oldfile은 디렉토리일 수도 있다. 가령

$ copy . dircontents

는 현재 디렉토리("." 으로 표시)의 모든 내용을 복사 dircontents라는 일반파일에 복사하게 되는데, 새로운 파일의 데이터는 디렉토리의 내용과 같지만 파일자체는 일반파일이다. (시스템 콜 mknod()는 새로운 디렉토리를 생성한다.) 마지막으로 두 파일 중 하나가 특수 디바이스 파일이 될 수 있다.
예를 들어,

$ copy /dev/tty terminalread

는 터미널(특수 파일 /dev/tty는 사용자 터미널을 의미한다)에 입력된 문자를 읽어서 terminalread라는 파일로 복사하게 되는데, 사용자가 Ctrl-d를 칠때까지 입력된 문자들이 복사된다. 이와 비슷하게,

$ copy /dev/tty /dev/tty
는 터미널에 입력된 문자를 읽고, 복사하여 문자를 터미널에 되돌려 준다.

1.3.2 처리 환경

프로그램은 실행 파일(executable file)이고 프로세스는 실행 중에 있는 포르그램의 인스턴스(instance: 실제로 수행되는 것)이다. UNIX 시스템에서 여러 프로세스들이 논리적인 개수에 재한 없이 동시에 실행된다(이런 기능은 때때로 멀티태스캉(multitasking)이라 불린다), 또한, 동일한 프로그램이 여러 프로세스로 동시에 수행될 수 있다. 프로세스가, 새로운 프로세스를 생성, 프로세스를 종료, 프로세스의 실행 단계를 동기화, 이벤트 후속처리(예: signal)를 제어하는 것을 할 수 있는데, 다양한 시스템 콜을 통해 가능하다. 프로세스가 시스템 콜을 이용한다는 조건하에, 프로세스들은 각각 독립적으로 실행된다. 

int main( int argc, char *argv[])
{
/* assume 2args: source file and target file */
if( fork() == 0 )
execl("copy", "copy", argv[1], argv[2], 0);
wait((int *)0);
printf("copy done\n");
return 0;
}

그림 1.4 파일을 복사하는 프로세스를 생성하는 프로그램

예를 들어, 그림 1.4 프로그램이 실행되었다면, 그 프로세스가 새로운 프로세스를 생성하기 위해 fork()시스템 콜을 호출한다. 자식 프로세스(child process)라 불리우는 새로운 프로세스는 fork()로 부터 반환값을 0을 받고는, 그림 1.3의 copy를 실행하도록 execl() 시스템 호출을 부르게 된다. execl()은 자식 프로세세의 주소 공간에 copy 파일을 겹쳐올려 놓고, 사용자가 제공한 매개변수들을 사용하여 프로그램을 동작시킨다("copy"파일은 같은 디렉토리에 있어야 한다). 만약 execl()이 성공적으로 실행되면 복귀하지 않는데, 프로세스가 새주소 공간에서 실행되기 때문이다(7장에서 설명한다). 한편으로, fork()를 호출했던 부모 프로세스(parent process) 0이 아닌 리턴값을 받아 wait()를 호출하고 copy가 끝날 때까지 기다렸다가 복사가 완료되었다는 메시지를 출력하고 exit()를 호출한다. (모든 프로그램은 컴파일 시 링크되는 표준 C 라이브러리에 의해 main()의 끝에서 exit()를 부르게 되어있다) 예를들어, 실행할 수 있는 이 프로그램의 이름을 run이라고 하고 사용자가 다음과 같이 이 프로그램을 실행하면,
$ run oldfile newfile
이 프로세스은 oldfile에서 newfile로 복사하고, 메시지를 출력한다. 비록 이 프로그램이 copy 프로그램에 적은 부분을 추가했지만, 프로세스 제어를 위해 사용된 4개의 주요 시스템 콜-fork(), exec(), wait(), exit()-을 보여준다. 


일반적으로, 시스템 콜은 사용자가 복잡한(sophisticated) 작업을 수행하는 프로그램을 작성하는 것을 가능케 한다. 결과적으로, 다른 시스템의 커널에는 포함된 기능들이, UNIX 시스템의 커널에는 포함되지 않는다. 컴파일러와 에디터를 포함한 그러한 기능들은 UNIX 시스템에서 사용자-레벨의 프로그램이다. 이런 프로그램의 가장 뚜렷한 예가 쉘이며, 쉘은 사용자가 시스템에 로그인한 후에 일반적으로 실행하는 명령어 해석 프로그램(command interprete programm)이다. 쉘은 명령어 라인(command line)의 첫 단어를 명령어 이름으로 해석한다 (대부분 경우, 쉘은 fork()를 호출하고 난뒤, 그 자식 프로세스가 명령어 이름으로 exec()를 호출하는데, 명령어 라인에서 명령어 이름을 제외한 나머지 단어들을 매개변수로 이용한다).


쉘에서는 세가지 형태의 명령어가 가능하다. 첫번째, 소스코드를 컴파일하여 생성된 오브젝트 파일이 포함된 실행 파일이다. 두번째, 다수의 쉘 명령어 라인들로 구성된 실행 파일이 명령어가 될 수 있다. 마지막으로 내부적인 쉘 명령어가 될 수 있다. 내부 명령어는 쉘을 명령어 해석기 외에도 프로그래밍 언어로도 만들어 주는데, 반복 명령어(for-in-do-done과 while-do-done), 조건 명령어(if-then-else-fi), case 명령어, 프로세스의 현재 디렉토리를 바꾸는 명령어(cd)과 그외 명령어들을 포함한다. 쉘 구문 규칙에 의하여 패턴 매칭 매개변수 처리가 가능하다. 사용자들은 명령어 유형을 몰라도 실행시킬 수 있다. 


쉘은 주어진 순서대로 디렉토리 안에서 명령어를 찾는데, 이 순서는 수행되는 쉘마다 변경될 수 있다. 일반적으로, 명령어를 동기적인 방법으로 실행한다. 즉, 쉘은 다음의 명령어 라인을 읽기 전에 앞의 명령어가 끝나길 기다린다. 그러나 비동기적인 방식도 허용한다. 즉, 앞서 시작한 프로그램이 끝나길 기다리지 않고도 다음 명령어를 읽어서 실행할 수도 있다. 비동기적으로 실행된 명령어들은 백그라운드(background)에서 실행된다고 말한다. 예를 들어,

$ who

를 치면, 시스템은 /bin/who 로 저장된 파일을 실행한다. (who는 해당 시스템에 현재 접속한 사람들의 목록을 출력한다) who를 실행하는 동안 쉘은 그것이 끝나길 기다리고나서 다음 명령어를 위한 프롬프트(prompt)를 내보낸다. 다음과 같이 입력하면

$ who &

시스템은 who를 백그라운드로 실행시키고 쉘은 즉각 다음의 명령어를 수행할 준비를 한다. 

UNIX 시스템에서 실행 중인 모든 프로세스는 현재 디랙토리를 포함한 실행환경을 갖는다. 모든 경로이름에서 슬래쉬 문자로 시작하지 않는다면 그 경로의 시작 디렉토는 현재 디렉토리가 된다. 사용자는 파일 시스템에서 현재 디렉토리를 변경하기 위해 디렉토리 변경 쉘 명령어인 cd를 사용할 수 있다.

$ cd /usr/src/uts

위의 명령어는 쉘의 현재 디렉토리를 "/usr/src/uts"로 변경해 준다. 명령어

$ cd ../..

는 쉘의 현재 디렉토리를 루트 방향으로 두 노드 위로 이동시켜 주는데, 여기서 ".."는 현재 디렉토리의 부모 노드를 의미한다. 


쉘은 커널의 일부가 아닌 사용자 프로그램이기 때문에, 수정 혹은 특정 환경으로 이식하는 것이 쉽다. 예를 들어, 사용자는 System V의 일부로 제공되었던 Bourne 쉘 대신에, history기능을 제공하며, 최근까지 사용된 명령어를 다시 입력하지 않아도 되는 C 쉘을 사용할 수 있다. UNIX 시스템은 여러 쉘을 동시에 실행할 수 있다. 사용자는 여러 프로세스를 동시에 수행할 수 있고, 프로세스는 다른 프로세스를 생성하여 그들 간에 동기화시킬 수도 있다. 이러한 가능들이 사용자들에게는 강력한 수행 환경을 제공한다. 쉘의 강력한 능력은 프로그래밍 언어로서의 능력과 매개변수의 패턴 매칭 능력에서 비롯된 것이지만, 이 절에서는 시스템이 쉘을 통해 제공하는 프로세스 환경에 대해 집중한다. 그밖의 다른 주요 기능은 이 책의 범위를 벗어난다.(쉘에 대한 자세한 설명은 [Bourne 78]을 참고하라)

1.3.3 구성 요소 프리미티브(Building Block Primitives)

Building Block?
a component that fits with others to form a whole.
전체를 구성하기 위하여 다른 것들에게 끼우는 것


프리미티브란? 

사전적 의미로 '원시적인 도구'이지만, '투박하고 거친 근원적인 도구'란 의미에 가깝다. 하위 레벨, 즉 기본적인 기능들을 의미한다. 예를 들어 semaphore 라던가 소켓은 커널 프리미티브의 대표적인 예이다.


앞에서 언급한 바와 같이 작은 프로그램을 작성할 수 있도록, 운영체제 프리미티브를 제공하는 것이 UNIX의 철학이다.(프리미티브는 복잡한 프로그램을 조합(build)하기 위해 building block처럼 사용될 수 있는 조립식 프로그램이다). (역주: "프리미티브가 있으므로, 너희들이 프로그램을 자그마하게 작성하는 것이 가능하다"는 의미에 가깝다) 쉘 사용자가 볼 수 있는 프리미티브 중 하나가 입출력 리다이렉션(redirect I/O) 이다. 프로세스는 기본적으로 세가지 파일에 대해 접근할 수 있는데, 표준 입력 파일로 부터 읽고, 표준 출력 파일에 쓰고, 표준 에러 파일에 쓸 수 있다. 터미널에서 수행중인 프로세스는 일반적으로 이 세가지 화일용으로 터미널을 사용하지만, 각기 독립적으로 리다이렉션할 수 있다. 가령 명령어 

$ ls

는 현재 디렉토리의 모든 파일을 표준 출력 상에 나열하지만,

$ ls > output

는 위에서 설명한 creat() 시스템 콜을 사용하여 표준 출력을 현재 디렉토리의 "output" 파일로 리다이렉션 한다. 마찬가지로,

$ mail mjb < letter

는 "letter"라는 파일을 표준 입력으로 open()하여, 그 내용을 "mjb"라는 사용자에게 메일로 보내게 된다. 프로세스는 다음과 같이 입력과 출력을 동시에 리다이렉션 할 수 있다.

$ nroff -mm < doc1 > doc1.out 2> errors

이때 텍스트 포매터 nroff는 파일 "doc1"을 읽고, 출력을 "doc1.out"로 보내고, 오류 메시지는 이를 "errors" 파일로 보낸다. ("2>" 표기법은 표준 에러에 해당하는 2번 파일 디스크립터를 출력 파일로 리다이렉션한다는 것을 의미한다) 프로그램 lsmailnroff 같은 프로그램은 어떤 파일이 표준 입력, 표준 출력, 표준 에러 파일이 될지 모르며, 쉘이 "<", ">", "2>" 와 같은 기호를 인식하고, 실행 전에 적절한 표준 입출력, 표준 에러를 설정한다.


두번째 구성요소 프리미티브는 읽기 프로세스와 쓰기 프로세스 간에 데이터 스트림을 전달하는 파이프(pipe)이다. 프로세스는 표준 출력을 파이프로 리다이렉션 할 수 있으며, 표준 입력을 그 파이프로 리다이렉션했던 다른 프로세스는 파이프로 부터 읽어들인다. 첫번째 프로세스가 파이프에 쓴 데이터는 두번째 프로세스의 입력값이다. (첫번째 프로세스가 파이프에 쓴 데이터를 두번째 프로세스가 읽어들이게 된다.) 또, 두번째 프로세스도 출력을 리다이렉션할 수 있는데, 프로그래밍 필요에 따라 결정된다. 다시 말하건데, 프로세스는 표준 출력이 어떤 종류인지 전혀 알 필요가 없다; 프로세스는 표준 출력이 일반 파일이건, 파이프이건, 혹은 디바이스이건 상관없이 동작한다. 크고 복잡한 프로그램의 구성요소로서 작은 프로그램들을 사용할 때, 프로그래머는 조각 부분들(작은 프로그램)을 통합히기 위해 파이프 프리미티브와 입출력 리다이렉션을을 사용할 수 있다. 따라서 시스템은 프로그래머가 기전의 프로그램을 적절히 활용하여 새로운 프로그램을 만들 수 있도록 기능을 제공하고 있는 것이다. 


예를 들어, grep 이란 프로그램은 파일에서 주어진 패턴을 찾아내는 일을 하는데

$ grep main a.c b.c c.c

은 a.c, b.c, c.c 세 개 파일에서 "main"이라는 문자열이 포함된 라인을 찾고, 찾은 라인을 표준 출력에 출력한다. 그 출력의 예는 아마도 다음과 같을 수 있다.

a.c : main(argc, argv)
c.c : /* here is the main loop in the program */
c.c : main()

또 wc 라는 프로그램에 옵션 -l을 사용하게 되면, 표준 입력 파일의 총 라인 수를 계산해 준다. 따라서 명령어가
$ grep main a.c b.c c.c | wc -l
은 문자열 "main"이 포함된 라인의 수를 계산한다(grep의 표준 출력이 wc의 명령어에 직접적으로 piped 되었다) 위 grep의 결과가 파이프된 명령의 결과는 아래와 같다.
3

위와 같이 파이프를 사용하면 임시 파일을 자주 생성할 필요가 없다. 

1.4 운영체제의 서비스

그림 1.1 은 사용자응용 프로그램 층 바로 아래에 커널 층이 있음을 나타내고 있다. 커널은 사용자 프로세스에게 앞서 언급한 사용자 인터페이스를 지원해 주는 다양한 기본기능들을 제공해 주공 시다. 커널에 의해 제공되는 서비스로는 다음과 같은 것들이 있다. 


  • 프로세스의 생성, 종료 혹은 중단, 통신을 통한 프로세스의 실행 관리
  • CPU에서 프로세스 실행되는 프로세스를 공정하게 스케줄링. 프로세스는 시분할(time-shared) 방식으로 CPU를 공유한다. 즉, CPU는 프로세스를 실행시키고, 커널은 프로세스의 시간 할당량(time quantum)이 경과하면 해당 프로세스를 잠시 보류(suspend)한 다음, 다른 프로세스를 실행하기 위해 시간조정(schedule)한다. 커널은 멈춰진 프로세스를 나중에 스케줄한다.
  • 실행하는 프로세스를 위한 메모리 할당. 커널은 프로세스들이 적절한 조건하에서 프로세들의 메모리의 일부를 공유하는 것을 지원하지만, 사적인(private) 주소 공간은 외부 조작으로부터 보보한다. 만약 시스템에 여유공간이 부족하다면, 커널은 한 프로세스를 스왑(swap) 디바이스라 불리는 보조 메모리(secndary memmory)에 일시적으로 저장하여 메모리를 확보한다. 만약 커널이 모든 프로세스들을 스왑 디비이스에 쓴다면, 그 구현을 스왑 시스템이라 부란다(만약 스왑 디바이스에 메모리의 페이지를 쓴다면, 페이징 시스템이라 부른다)
  • 사용자 데이터의 효율적인 저장 및 검색을 위한 보조 메모리의 할당. 이러한 서비스들은 파일 시스템을 구성한다. 커널은 사용자 파일을 위해 보조 저장장치를 할당하고, 사용되고 있지 않는 저장장치를 회수하고, 파일 시스템을 이해하기 쉬운 방향으로 구성하며, 불법적인 액세스로부터 사용자 파일을 보호한다.
  • 프로세스가 터미널, 테이프 디바이스, 디스크 디바이스, 네트워크 디바이스와 같은 주변기기를 프로세스가 적절히 액세스하도록 허용한다.

커널은 이 서비스들을 일관성있게 제공한다. 가령 커널은 주어진 파일이 일반파일인지 혹은 장치파일인지 구별할 수 있지만, 사용자 프로세스는 구분할 수 없다. 마찬가지로 커널은 파일의 데이터를 내부 저장 장치에 맞도록 형식을 맞추지만, 사용자 프로세스에게는 내부형식을 숨기며 바이트 스트림 형식 데이터를 준다. 끝으로, 사용자 포르세스가 사용자의 서비스를 제공해야 할때, 커널은 사용자의 서비스를 지원하는 필수 서비스를 제공하지만, 사용자 수준에서 구현가능한 서비스는 생략된다. 예를 들어, 커널은 쉘이 명령어 해석기로 동작하는데 필요한 서비스는 지원해 준다. 즉 쉘이 터미널 입력을 읽어들이고, 프로세스를 동적으로 생성하고, 프로세스 실행을 동기화하며, 파이프나 입출력을 리다이렉션할 수 있도록 해준다. 사용자는 다른 사용자에 영향으르 주지 않고도, 자신의 요구와 환경에 맞춰주는 쉘의 개인(private) 버전을 개발할 수 있다. 이런 프로그램들은 표준 쉘과 같이 동일한 커널 서비스를 사용한다. 

1.5 하드웨어에 대한 가정

UNIX 시스템에서 사용자 프로세스의 실행은 "유저와 커널", 두 레벨로 나뉘어진다. 프로세스가 시스템 콜을 실행하면 "실행모드(execution mode)"는 "유저모드(user mode)"에서 "커널모드(kernel mode)"로 변경 된다. 운영체제는 사용자 요구에 대한 서비스를 수행 및 시도한다.(만일 실패하면 에러 코드를 반환한다). 만약 사용자가 명확하지 않은 운영체제 서비스를 요청했다 하더라도, 운영체제는 자신의 일, 사용자 프로세스에 관련된 작업을 기록하고, 인터럽트를 처리하며, 프로세스를 스케줄링하고, 메모리를 관리하는 일 등을 여전히 할 것이다. 많은 컴퓨터 구조(그리고 그들의 운영체제)는 여기서 요약된 두 모드보다 더 많은 레벨을 제공하지만, UNIX시스템은 유저모드와 커널모드, 두 모드로 충분하다. 


두 모드의 차이점은 다음과 같다.


  • 사용자 모드의 프로세스는 자신의 기계명령어(instruction)과 데이터를 액세스할 수 있지만, 다른 프로세스나 커널의 기계명령어 및 데이터는 액세스할 수 없다. 그러나 커널 모드의 프로세스는 사용자와 커널의 주소공간을 액세스할 수 있다. 예를 들어, 프로세스의 가상 주소 공간(Virtual address space)은 커널 모드일때만 액세스가 가능한 공간과 모드에 상관없이 가능한 공간으로 나뉘어진다.
  • 어떤 명령어는 특권적이며, 특권 기계명령어(privileged instruction)을 유저 모드에서 실행한다면 에러가 발생한다. 예를 들어, 머신이 프로세스의 상태 레지스터를 조작하는 기계명령어를 가지고 있다 하더라도, 유저 모드에서 실행되는 프로세스는 이러한 기능을 수행할 수 없어야 한다.

간단히 하자면, 하드웨어는 커널 모드와 유저 모드의 관점으로 세계를 보며, 그러한 모드에서 프로세스를 수행하는 사용자를 구별하지 않는다. 운영체제는 시스템 상에서 실행중인 많은 프로세스를 구별하기 위해 내부적인 기록을 유지하고 있다. 그림 1.5는 이 구분을 보여주고 있는데, 커널은 가로 축으로 프로세스 A, B, C, D 들을 구별하며, 하드웨어는 세로 축으로 실행 모드를 구별한다. 


시스템이 프로그램을 실행하더라도, (모드에 상관없이) 커널이 사용자 프로세스를 대신하여 동작한다. 커널은 사용자 프로세스들과 구별되는 프로세스들의 집합이 아니다. 커널은 각 사용자 프로세스의 일부분이다. 앞으로 자원을 할당하는 "커널", 다양한 작업을 하는 "커널" 과 같이 인용할 건데, 프로세스가 커널 모드에서 자원을 할당한다던가 다양한 일을 한다는 것을 의미한다. 예를 들어, 쉘이 사용자 터미널에서 입력값을 시스템 콜을 통해서 읽는다는 것은, 쉘 프로세스를 대신하여 실행하는 커널이 터미널의 작업을 제어하고, 입력된 문자들을 쉘에게 되돌려 준다. 그 다음 쉘은 유저모드에서 실행되고, 사용자로부터 입력받은 문자열을 해석하고, 지정된 작업을 수행한다.(지정된 작업을 하기 위해 다른 시스템 콜을 호출해야할 지도 모른다.) 

1.5.1 인터럽트와 예외

UNIX 시스템은 I/O 주변기기나 시스템 클럭와 같은 장치들이 CPU를 비동기적으로 인터럽트할 수 있도록 해준다. 인터럽트를 받을 때, 커널은 현재 컨택스트(context: 프로세스가 무엇을 해왔는지에 대한 이미지)를 저장하고, 인터럽트가 발생한 원인을 알아내어(determine), 인터럽트를 해결한다. 커널이 인터럽트를 처리하고 나면, 커널은 컨텍스트를 복구하고, 아무 것도 일어나지 않은 것처럼 계속 진행한다. 하드웨어는 인터럽트가 처리되는 순서에 따라 디바이스에 대한 우선 순위를 지정한다( 커널이 인터럽트를 처리할 때, 커널은 낮은 순위의 인터럽트를 블락(block:봉쇄)하고 높은 순위의 인터럽트는 먼저 처리한다).


예외 조건(exception condition)이란 불법적인 메모리 주소 액세스, 특권 기계명령어의 실행, 0으로 나누는 것 등과 같이 어떤 프로세스에 의해 기대하지 않았던 상황이 발생되는 상황을 말한다. 이들은 프로세스 외부사건에 의해 발생되는 인터럽트와는 구분된다. 즉 예외는 명령의 실행 "도중"에 발생한다. 이때 시스템은 예외를 처리한 후, 기계명령어을 다시 시작하게 된다. 반면 인터럽트는 두 기계명령어 실행 사이에 발생하는 것으로 시스템은 인터럽트를 처리한 후, 다음 기계명령어을 계속 처리한다. UNIX 시스템은 인터럽트와 예외 조건을 처리하기 위해 동일한 메커니즘을 사용한다. 

1.5.2 프로세서 실행 레벨

어떤 중요한 수행작업을 하는 도중에 인터럽트가 수행되어 잘못된 결과가 야기될 수 있다면, 커널은 그 작업을 하는 도중에 인터럽트가 발생하는 것을 막아야할(prevent) 때가 종종 있다. 예를 들어 커널은 디스크의 저장된 링크드 리스트를 조작하는 동안 디스크 인터럽트를 받길 원하지 않는다. 왜냐하면, 인터럽터를 처리하면 링크의 포인터가 깨질수 있기 때문이다(다음장에서 설명한다). 일반적으로 컴퓨터는 프로세스 상태 워드(processor status word)에 프로세서 실행 레벨을 지정하는 특권기계 명령어 집합을 가지고 있다. 프로세서 실행 레벨을 특정 값으로 설정하는 것은 해당 수준 및 하위 수준의 인터럽트가 마스크(mask off)되며, 높은 수준의 인터럽트만 허용된다. 그림 1.6은 실행 레벨의 예시를 보여준다. 만약 커널이 디시크 인터럽트를 마스크한다면 클럭 인터럽트와 머신 에러 인터럽트를 제외한 모든 인터럽트가 막힌다(prevent). 만일 소프트웨어 인터럽트를 마스크한다면, 소프트웨어 인터럽트를 제외한 모든 인터럽트가 발생할 수 있다.

1.5.3 메모리 관리

커널은 현재 실행중인 프로세스가 하는 것처럼 메인 메모리에 영구적으로 존재한다 (혹은, 최소한 실행되는 프로세스의 부분으로). 프로그램을 컴파일 할때, 컴파일러는 프로그램 내의 주소의 집합을 생성한다 (자료구조 그리고 변수의 주소, 혹은 함수와 같은 기계명령어의 주소). 컴파일러는 가상 머신(virtual machine)을 위한 주소를 생성하는데, 실제 물리적 머신에서 프로그램들이 실행된다면, 동시에 수행될 수 없는 주소이다.


프로그램이 머신에서 수행될 때, 커널은 메인 메모리에 프로그램을 위한 공간을 할당하게 된다.하지만 컴파일러가 생성한 가상 주소(virtual address)는 커널이 할당한 물리 주소와 동일하지 않다. 커널은 가상주소-물리주소 변환을 설정하기 위하여 컴퓨터 하드웨어와 협조한다. (컴퓨터 하드웨어는 컴파일러가 생성한 주소에서 물리 주소로 매핑한다). 이런 매핑은 하드웨어 기능에 의존적이며, 그 까닭에 그 하드웨어와 협업을 하는 UNIX 시스템의 부분들은 머신에 종속적이다. 예를 들어, 몇몇 머신은 요구 페이징을 지원하는 특별한 하드웨어를 가지고 있다. 6장과 9장에서 메모리 관리의 이슈들을 논의하고, 어떻게 하드웨어와 관련되어 있는 지 자세히 알아본다.

1.6 요약

이 장에서 UNIX 시스템의 전반적인 구조, 유저모드와 커널모드로 동작하는 프로세스들의 관계, 그리고 커널이 하드웨어에 관해 가정한 사항들에 대해 설명하였다. 프로세스는 커널모드 혹은 유저모드에서 실행되는데, 잘 정의된 시스템 호출들을 사용하여 시스템 서비스를 제공받는다. UNIX 시스템의 설계 원칙에 따르면, 프로그래머가 간단한 기능을 수행하되 잘 돌아가는 작은 프로그램을 작성하고, 복잡한 처리를 하기 위해 파이프와 I/O 리다이렉션을 사용하여 작은 프로그램들을 조합하는 것을 장려한다.


프로세스는 프로세스에게 금지된 작업을 시스템 콜은 통해 실행할 수 있다. 시스템 콜을 제공하는 것 뿐만 아니라, 커널은 유저 커뮤니티, 프로세스 스케줄링을 제어, 기억장치 관리, 메인 메모리 상의 프로세스를 보호, 인터럽트 처리, 파일과 디바이스 관리 그리고 시스템 에러 조건를 다루는 일을 한다. UNIX 시스템 커널은 다른 운영체제에서 제공될만한 많은 기능들이 생갹되었으며, 사용자 레벨의 프로세스가 필수 기능들을 할 수 있게 최소한의 시스템 콜을 제공한다. 다음 장에서 커널을 좀 더 상세하게 소개할 것이다. 즉, 커널의 구조와 구현에 이용된 기본적인 개념을 설명한다. 


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