본문 바로가기

Linux and Unix

Unix System 프로그래밍

『리눅스 학당-리눅스 강좌 / 연재 (go LINUX)』 53번

제 목:[moohou] Unix System Programming 1

올린이:kingcrab(박재화 ) 94/10/16 12:35 읽음:6378 관련자료 없음

------------------------------------------------------

___________________________________________

Unix System Programming 1 - 김성호(moohou)

___________________________________________


UNIX 시스템 프로그래밍

제1장 기본개념과 용어

1.1 화일

UNIX 시스템에 대한 정보는 화일에 저장된다. UNIX 화일은 복잡하고 특수한 구조 (인덱스 화일구조 등)를 개발할 수 있도록 설계된 명확하고 일반적인 개념이다. 한줄의 끝을 알려주는 개행문자는, UNIX에 관한 한 단지 시스템 유틸리티(utility)와 사용자프로그램에 의해 읽히고 쓰여지는 하나의 문자에 불과하다.


1.1.1 디렉토리와 경로이름

디렉토리는 화일의 집합으로, 화일시스템의 논리적인 구성을 가능하게 한다. UNIX 화일의 저장은 각 중간 노드(non-terminal node)가 각 디렉토리에 해당되 는 계층적인 트리구조로서 생각될 수 있다. 이 트리의 맨꼭대기는 루트 디렉토리(root directory)라 불리는 한개의 디렉토리이다. '/'문자로 시작하지않는 경로이름은 상대적인 (relative) 경로이름이라고 부르고, 현재 사용자가 작업 하고 있는 디렉토리(current working directory)에 대하여 상대적인 경로를 알려준다. 경로이름의 각 요소(component)는 최대 14문자까지만 허용된다는 것을 명심해야 한다.


1.1.2. 소유권과 허가

소유권(ownership)은 허가(permission)와 같은 화일의 속성을 바꿀수 있는 권리를 부여한다.


1.1.3. 화일개념과 일반화

UNIX는 화일 개념을 디스크 화일 뿐만 아니라 주변장치와 프로세스간 통신채널(interprocess communication channel)까지 확장한다.


1.2. 프로세스

UNIX용어로서 프로세스는 단지 수행중인 프로그램을 말한다. 프로세스를 생성 하는 가장 쉬운 방법은 UNIX 명령어 처리기 또는 쉘 (shell)에 명령을 하는 것이다.

1.2.1. 프로세스간 통신

UNIX는 다양한 프로세스간 통신 방법들을 사용하여 프로세스들이 서로 협력하도록 한다. 다른 UNIX 프로세스간 통신기능으로서 signal이 있다. 이것은 인터럽트(interrupt)를 기초로 하는 통신모델을 제공한다. 또 다른 기능으로는 공유메모리(shared memory)와 세마포어(semaphore)가 있다.


1.3. 시스템호출과 라이브러리 서브루틴

시스템호출은 소프트웨어 개발자가 UNIX 커널(kernel)로 들어가기 위한 수단이다. 커널은 소프트웨어의 한 부분으로서 항상 메모리에 존재하여 UNIX 시스 템의 프로세스 스케줄링(process scheduling)과 I/O 제어를 다룬다. 본질적으 로 커널은 UNIX에서 진정한 운영체제로서 간주되는 부분이다. 커널에 의해서 모든 사용자 프로세스와 모든 화일 시스템 접근들은 감독되고 제어된다. 시스 템호출에 의해 수행되는 코드의 대부분은 호출 프로그램이 아니라 실제 커널 그 자체인 점이다. 다른 말로 호출 프로그램은 커널에 의해 제공되는 기능을 직접 이용한다. 사용자 프로세스와 커널 사이의 전환(switch) 은 보통 소프트 웨어 인터럽트 절차를 통하여 이루어진다. 프로세스의 경우 시스템호출 명령은 새로운 프로세스를 생성하고, 존재하는 프로세스를 종료하고, 프로세스 상태에 대해 정보를 얻고, 두 프로세스 사이에 통신 채널을 설치하는 것을 포함한다. UNIX 시스템은 시스템호출 뿐만 아니라 라이브러리와 표준 서브루틴을 제공한 다. 이에 대한 가장 중요한 예는 표준 I/O 라이브러리이다. 이것은 화일 접근 프리미티브로 구성된 시스템호출에 의해서는 제공되지 않는 형식화된 변환 (formatted conversion)과 자동 버퍼링(automatic buffering)등과 같은 기능을 제공한다. 또한 이러한 기능들을 효율적으로 수행하도록 한다. 그러나 표준 I/O 서브루틴들은 궁극적으로 시스템호출 인터페이스를 사용한다. 이것들은 분 리된 부시스템(subsystem)이 아니라 시스템호출 프리미티브에 기초한 화일접근 기능 의 다른 면처럼 보인다. 여기서 주안접은 환경(environment)과 접촉하는 어떤 프로세스도 시스템호출을 사용해야 한다는 것이다.


제2장 UNIX 화일 접근 프리미티브

2.1 파일 접근

2.1.1 서론

UNIX 화일 접근 프리미티브

--------------------------------------------

이름 의미

--------------------------------------------

open 읽거나 쓰기위해 화일을 연다.

creat 빈 화일을 생성한다.

close 앞서 열려진 화일을 닫는다.

read 화일로부터 정보를 추출한다.

lseek 화일 안의 특정 바이트로 옮긴다.

unlink 화일을 제거한다.

--------------------------------------------

성공하면 0 , 실패하면 -1 을 돌려준다. 모든 개방된 화일은 프로세스가 종료할때 자동적으로 닫힌다.


2.1.2. open 시스템 호출

존재하는 화일을 읽거나 쓰기전에 반드시 open 시스템호출로 화일을 개방해야 한다.

#include <fcntl.h>

int filedes, flsgs;

char *pathname;

.

.

filedes = open(pathname, flags);

첫번째 인수 pathname은 개방될 화일의 경로이름을 갖고 있는 문자열에 대한 포인터이다. 화일은 open이 호출되기전에 반드시 존재해야 한다. 그렇지 않으 면 오류가 발생한다.

fcntl.h 에서 정의되는 상수

- O_RDONLY 읽기만 허용하는 개방 화일

- O_WRONLY 쓰기만 허용하는 개방 화일

- O_RDWR 읽고 쓰는 것을 허용하는 개방 화일

오류가 발생하면 open은 -1을 돌려준다. 오류는 화일이 존재하지 않을 경우 발생한다. 오류조사(error checking)를 시스템호출을 사용하는 모든 프로그램 에 대해서 해 주는 것은 발생할 지도 모르는 오류를 고려할때 매우 유익하다.

주의사항(caleats)

1. 수행중인 프로그램에 의해 동시에 개방될 수 있는 화일의 수에는 제한이 있다.(보통20) 또한 존재하는 모든 프로세스에 의해 개방될 수 있는 화일 의 수에도 제한이 있다. 이것은 커널안에 있는 표의 크기에 의해 결정된 다.

2. 초기 UNIX에는 인클루드 화일 fcntl.h가 존재하지 않았기 때문에 실제 숫자가 flags 인수로서 사용되었다.


2.1.3. creat 시스템 호출

creat 시스템호출은 새로운 화일을 생성하거나 존재하는 화일의 길이를 0 으로 고쳐놓기 위해 사용된다. open 과 같이 creat 호출은 음이 아닌 화일 기술 어 또는 오류코드 -1을 돌려준다.

int filedes, mode;

char *pathname;

.

.

filedes = creat(pathname, mode);

첫번째 인수 pathname은 UNIX 경로이름을 나타낸다. 이것은 새 화일의 이름과 위치, 또는 존재하는 화일의 이름을 지정한다. 두번째 인수 mode 는 정수값을 갖고 화일의 접근 허가(access permission)를 부여한다. mode 의 값은 화일이 생성될 때만 의미를 갖고, 존재하는 화일의 경우에는 의미를 지니지 못한다. 존재하는 화일의 경우는 원래의 허가를 계속 가지게 된다. creat 는 쓰기 전용으로만 화일을 개방한다는 것에 유의하라.


2.1.4. close 시스템 호출

시스템 호출 close는 open과 반대이다. close 호출 프로세스가 화일에 관련된 작업을 끝냈다는 것을 시스템에 알려준다.

int retval, filedes;

.

.

retval = close(filedes);

close 가 성공하면 0 을 돌려주고, 오류가 발생하면(정당한 화일 기술어를 인수로 갖지 않을 경우) -1을 돌려준다.


2.1.5. read 시스템 호출

read 는 화일로부터 임의길이의 문자나 바이트를 호출 프로그램의 제어하에 있는 버퍼로 복사하기 위해 사용된다. 이때 버퍼는 char 의 배열로서 정의된 다.

int nread, filedes, n;

char *bufptr;

.

.

nread = read(filedes, bufptr, n);

read 의 첫번째 인수 filedes 는 앞서 호출한 open 이나 creat 에 의해서 만들어진 화일 기술어이다. 두번째 인수 bufptr 은 자료가 복사되어질 문자 배열 에 대한 포인터이다. 새번째 인수는 화일로부터 읽어질 바이트의 수를 가지는 양의 정수값이다. read 로부터의 복귀값 nread 는 실제로 읽힌 바이트의 수를 나타낸다. read 의 경우, 시스템은 각 호출 후에 읽혀진 바이트의 수만큼 읽기 -쓰기 포인터를 전진 시킨다. read 호출에 의해 요청된 문자의 수가 화일에 남 아있는 문자의 수보다 클 경우 시스템은 남아있는 문자들만 옮기고, 복귀값을 적적하게 고친다. 이제 이후에 호출되는 read 의 복귀값은 0 이 된다. 즉, 남아있는 문자가 하나도 없게 된다. 사실, read 로부터 복귀값이 0 인가를 조사 하는 것이 프로그램 안에서 화일의 끝을 조사하는 보통의 방법이다.


2.1.6. write 시스템 호출

write 시스템호출은 read 의 정반대이다. 이 호출은 문자 배열로서 정의된 프로그램 버퍼로부터 자료를 외부화일로 복사한다.

int nwrite, filedes, n;

char *bufptr;

.

.

nwrite = write(filedes, bufptr, n);

n 은 쓰여질 문자의 수인 양수이다. nwrite 로의 복귀값은 쓰여진 문자의 수 또는 오류코드 -1 이다. 실제적으로 -1 이 아니면 nwrite 는 거의 n 과 동일하 다. 만약 n 보다 작으면 무언가 잘못된 일이 발생했다. 화일은 처음에 0 바이트 길이로 초기화 된후 write 호출이 있을 때마다 화일의 끝에 자료가 더해진다. 읽기-쓰기 포인터는 마지막 바이트 가 쓰여진 후 곧바로 위치를 바꾼다.


2.1.7. copyfile 예

-- 생략


2.1.8. read, write 와 효율성

-- 생략


2.1.9. lseek 과 임의 접근

사용자는 lseek 시스템 호출을 사용하여 읽기-쓰기 포인터의 위치, 즉 다음에 읽거나 쓸 바이트의 위치를 변경할 수 있다. 그래서 lseek 은 화일에 대한 임의 접근을 가능하게 한다.

long newpos, offset, lseek();

int filedes, direction;

.

.

newpos = lseek(filedes, offset, direction);

filedes 는 개방되어 있는 화일의 화일기술어이다. long 정수형 offset 은 읽기-쓰기 포인터의 새 위치를 결정한다. 이것은 시작위치에 더해질 바이트의 수 를 지정한다. 시작위치는 direction 에 의해 결정된다. direction 의 값이 0 이면, offset 이 화일의 맨앞에서부터 더해진다. 값이 1 이면, offset 이 화일 포인터의 현재위치에 더해진다. 값이 2 이면, 화일의 마지막 바이트의 번호에 더해진다.

주의해야 할 몇 가지 주안점.

1. newpos 와 offset 둘다 long 정수형이다.

2. offset 은 음수가 될수 있다.

3. 화일의 끝보다 더 위의 위치를 지정하는 것이 가능하다.


2.1.10. 호텔의 예

-- 생략


2.1.11. 시스템 호출 unlink

시스템 호출 unlink 는 화일을 시스템으로부터 제거하기 위해 사용된다. 이것은 임시적인 작업화일을 다루는 데 유용하다.

int retval;

char *filename;

.

.

retval = unlink(filename);

unlink 는 제거할 화일의 이름을 가진 문자열로 된 한개의 인수만을 가진다. unlink 는 성공하면 0 을 실패하면 -1을 돌려준다.


2.1.12. open 호출의 확장된 특징

open 이 부가적인 특징을 제공한다. 부가의 상수 O_APPEND 를 소개한다. 만일 O_APPEND 가 지정되면, 쓰기가 일어날 때마다 화일의 끝에 위치하게 된다. 이 것은 프로그래머 가 원래의 내용은 그대로 두고 화일의 끝에 자료를 덧붙이기 를 원하는 경우에 유용하다. O_WRONLY 와 O_APPEND 가 기호 '|' (c 의 비트 단 위 OR 명령어)에 의해 결합되는 방법에 유의하라.

filedes = open("yetanother", O_WRONLY|O_AP PPEND);

open 은 creat 와 유사한 방법으로 화일을 생성하거나 화일의 내용을 백지화 하는 데 사용 될 수 있다. 세가지 기본 플래그중의 하나와 fcntl.h 에 있는 부 가의 플래그, O_CREAT, O_TRUNC, O_EXCL 를 결합함으로써 얻을 수 있다.

fd = open("file", O_WRONLY|O_CREAT|O_AP PPEND, 0644);

만일 화일의 내용을 백지화하고 싶으면, O_CREAT 와는 별개로 O_TRUNC 를 사 용해야 한다.

fd = open("file", O_WRONLY|O_CREAT|O_TR UNC, 0644);

O_EXCL 은 독립적으로 사용될 수 없다. 그러나 O_CREAT 와 함께 지정되면, 화 일이 존재할 때는 open 이 실패하게 된다.

fd = open("lock", O_WRONLY|O_CREAT|O_EX CL, 0644);

은 화일이 존재하지 않을 경우는 화일 lock 을 생성하고, 화일이 존재하면 실 패(-1을 돌려줌) 하라는 명령이다. 이것은 동시에 여러 프로세스가 같은 화일 을 생성할 수 있는 상황에서 우연히 화일이 훼손되는 것을 막기 위한 방법으로 유용하게 사용될 수 있다.


2.1.13. 시스템 호출 fcntl

fcntl 시스템 호출은 이미 열려있는 화일에 대해서 제어를 하기 위해 사용된 다. fcntl 은 하나의 잘 정의된 기능을 가진 시스템 호출이 아니라 여러 기능 을 수행하는 다소 이상한 시스템 호출이다.

#include <fcntl.h>

/*

*NB: type of 'args' can vary

*/

int status, cmd, filedes, args;

.

.

status = fcntl(filedes, cmd, args);

화일과 프로세스의 상호작용에 관계된 것 F_GETFL 과 F_SETFL 등이 있다. F_GETFL 은 open 에 의해 지정된 현재 화일 상태 플러그(current file status flags) 를 fcntl 이 돌려 주도록 하기 위해 사용된다. F_SETFL 은 화일에 관련 된 화일상태 프래그를 다시 지정하기 위해 사용된다.


2.2. 표준 입력, 표준 출력, 표준 오류

2.2.1. 기본개념

UNIX 시스템은 수행중인 프로그램에 대해서 자동적으로 세개의 화일을 개방한 다. 그것들은 각각 표준입력, 표준출력, 표준오류라 불리고, 프로그램 안에서 가가 화일 기술어 0, 1, 2 로서 항상 식별된다.


2.2.2. 프로그램 예 io

-- 생략


2.2.3. 표준오류

표준오류는 오류와 경고 메시지를 주기위한 다소 특별한 출력 채널이다.


2.3. I/O 라이브러리 : 미리 살펴보기

표준 I/O 와 시스템 호출 프리미티브 사이의 가장 분명한 차이점은 화일이 기술되는 방법에 있다. 정수형 화일 기술어 대신에 표준 I/O 루틴은 스트림 (stream)이라 불리는 객체(entity)를 가지고 작업을 한다. 스트림은 FILE이라 불리는 구조형(structure type)으로 나타낸다.

fprintf 로 오류메시지 출력

printf 는 특정 메시지를 출력하기 위해 사용된다.

#include <stdio.h> /*for stderr definition */

.

.

fprintf(stderr, "error number %dn",err no);

일관성을 위해 표준오류를 사용하는 명령어나 프로그램에서는 fprintf 를 사 용하는 것이 좋다.


2.4. errno 변수와 시스템호출

프로그래머는 C 프로그램안에서 정수형의 extern 변수로 선언하여 errno 를 사용할 수 있다.

extern int errno;


2.4.1. perror 서브루틴

errno 뿐만 아니라 UNIX 는 perror 이라 불리는 루틴(시스템 호출이 아닙)을 제공한다. 이것은 하나의 문자열 인수를 가진다. perror 이 호출되면, 루틴에 전달된 문자열 인수, 콜론, errno 변수의 현재 값에 대응되는 부가의 메시지 등으로 구성되는 메시지를 표준 오류로 출력한다. 오류 메시지를 표준 출력이 아니라 표준오류로 출력하기 때문에 더 유용하다.

perror("error opening nonesuch");


제3장. 문맥상의 화일

3.1. 다중 사용자 환경에서의 화일

3.1.1. 사용자와 소유권

UNIX 시스템의 모든 화일은 시스템의 사용자중 하나에 의해 소유된다. 이 사 용자는 보통 화일을 생성한 사용자이다.

유효그룹식별번호와 유효사용자식별번호 실제로 프로세스를 수행시킨 사용자 의 사용자 식별번호는 그 프로세스의 실사용자식별번호(real user-id)라고 부 르고, 실제 생성된 화일에 대한 소유권을 갖는 사용자의 사용자식별번호를 유 효사용자식별번호라고 부른다 물론 대부분의 경우, 유효사용자식별번호와 실사 용자식별번호는 동일하다.


3.1.2. 허가와 화일모드

다른종류의 소유권과 같이 화일의 소유권은 소유자에게 어떤 권리를 준다. 특 히, 소유자는 화일에 관련된 허가(permission)를 선택할수 있다.

다음과 같은 사용자의 세가지 유형이 허가에 의해 영향을 받을 수 있다.

1. 화일의 소유자

2. 화일의 소유자와 같은 그룹에 속하는 사용자

3. 1 과 2 의 범주에 속하지 않는 사용자

사용자의 각 범주에 대해, 화일허가의 세가지 기본적인 유형이 있다.

1. 화일의 읽기

2. 화일에 쓰기

3. 화일의 수행(화일이 프로그램 또는 쉘 명령어 리스트를 가지고 있는 경우)

화일 허가를 정하기 위한 팔진수 값

--------------------------------------------

팔진수 값 의 미

--------------------------------------------

0400 소유자에게 읽기를 허가함

0200 소유자에게 쓰기를 허가함

0100 소유자에게 화일의 수행을 허가함

0040 그룹에 대해 읽기를 허가함

0020 그룹에 대해 쓰기를 허가함

0100 그룹에 대해 화일의 수행을 허가함

0004 다른 모든 사용자에 대해 읽기를 허가함

0002 다른 모든 사용자에 대해 쓰기를 허가함

0001 다른 모든 사용자에 대해 화일의 수행을

허가함

--------------------------------------------


3.1.4. open 과 creat

화일의 초기 허가는 creat 또는 open 호출을 통해서 화일이 생성된다.

filedes = creat("datafile",0644);


3.1.5. access 호출로 화일 접근 가능성 판별

시스템 호출 access 는 유효 사용자식별번호가 아니라 실 사용자 식별번호에 준하여 프로세스가 화일에 접근할 수 있는가를 알수 있게 하는 유용한 수단이다.

int result, amode;

char *pathname;

.

.

result = access(pathname, amode);

access 는 화일에 대한 접근 유형을 시스템에 알려준다. 이 인수의 값은 원래의 화일 모드와 유사한 다음의 팔진수 값으로 나타낸다.

04 호출 프로세스가 읽기 접근이 가능한가?

02 호출 프로세스가 쓰기 접근이 가능한가?

01 호출 프로세스가 화일을 수행시킬 수 있는가?


3.1.6. chmod 로 화일 허가 변경

int retval, newmode;

char *pathname;

.

.

retval = chmod(pathname, newmode);

chmod 시스템 호출은 존재하는 화일의 허가를 변경하기 위해 사용된다. 이것은 단지 화일의 소유자나 수퍼사용자만이 이용할 수 있다.


3.1.7. chown 으로 소유권 변경

chown 은 화일의 소유자와 그룹을 함께 변경하기 위해 사용된다.

int retval, owner_id, group_id;

char *pathname;

.

.

retval = chown(pathname, owner_id, group_id);


3.1.8. 화일생성 마스크와 umask 시스템 호출

각 프로세스와 관련하여 화일생성(file creation mask)라는 정수값이 존재한 다. 이것은 화일이 생성될때 또는 creat 또는 open 호출로 화일모드가 주어질 때마다 자동적으로 허가비트를 모두 0 으로 만들어 주기 위해 사용된다. 이것 은 특정한 허가가 우연히 지정되는 것을 방지해 주기 때문에 생성된 화일의 안 전을 위해서 유용하다. 기본 생각은 다음과 같다: 만일 한 허가비트가 화일 생 성 마스크에서 지정되면, 화일이 생성될때 그 허가비트를 항상 0 으로 만들어 준다.

다음과 같은 문장

filedes = creat(pathname, mode);

는 실제적으로 다음 문장과 동일하다.

filedes = creat(pathname, (~mask)&mode);

여기서 mask 는 화일 생성 마스크의 현재값을 가지고, ~ 는 C 의 비트단위 부 정(bitwise negative) 명령어이고, & 는 비트단위 AND 명령어이다.


3.2. 다중이름으로 된 화일

UNIX 화일은 하나 이상의 이름으로 식별될 수 있다. 다른 말로 한 화일은 중 복하여 메모리에 저장될 필요없이 여러 UNIX 경로이름과 관련될 수 있다. 그러 한 각 이름은 링크(link)라고 불린다. 한 화일과 관련된 링크의 수는 그 화일 의 링크 계수 (link counter)라고 불린다. 새 링크는 link 시스템 호출로 생성 된다. 그리고 존재하는 링크는 unlink 시스템 호출로 제거 될 수 있다.


3.2.1. 시스템 호출 link

int retval;

char *path1, *path2;

.

.

retval = link(path1, path2);

path1 은 UNIX 경로이름을 가리키는 문자형 포인터이다. 그것은 화일의 존재 하는 링크, 즉 존재하는 이름을 지정해야 한다. path2는 화일에 대한 새로운 이름 또는 링크를 가리킨다.


3.2.2. 시스템 호출 unlink

시스템으로 부터 화일을 제거하는 간단한 방법으로 unlink 시스템 호출을 소 개한다.

unlink("/tmp/scratch");

는 /tmp/scratch 를 제거한다. 사실, unlink 호출은 단지 한 링크를 제거하 고, 화일의 링크 계수를 하나 줄인다. 링크 계수가 0 이 되고, 현재 그 화일을 개방하고 있는 프로그램이 없게 되면, 화일은 시스템으로 부터 제거된다.


3.3. 화일정보획득 : stat 와 fstat

두 시스템 호출 stat 과 fstat 는 프로그램이 존재하는 화일의 특성을 알수 있도록 하여 준다.

#include <sys/types.h>

#include <sys/stat.h>

int retval, filedes;

char *pathname;

struct stat buf;

.

.

retval = stat(pathname, &buf);

retval = fstat(filedes, &buf);

pathname 은 화일을 식별하는 경로이름을 가리키고, &buf 는 stat 구조를 가 리키는 포인터이다. 이 stat 구조는 화일에 관련된 정보를 가지고 있다.

struct stat {

dev_t st_dev;

ino_t st_ino;

ushort st_mode;

short st_nlink;

ushort st_uid;

ushort st_gid;

dev_t st_rdev;

off_t st_size;

time_t st_atime;

time_t st_mtime;

time_t st_ctime;

};

stat 구조의 각각에 대한 의미는 다음과 같다.

1. st_dev 는 화일이 들어있는 논리적 장치(logical device)를 기술하고, st_ino 는 화일의 inode 번호를 나타낸다.

2. st_mode 는 화일모드를 부여하고, 프로그래머가 화일에 관련된 허가를 계 산할 수 있도록 하여준다. st_mode 에 들어있는 값은 화일의 유형에 대한 정보로서, 허가에 관련된 것은 하위 12 비트이다.

3. st_nlink 는 화일에 관련된 링크의 수(다른말로, 서로 다른 경로이름의 수)를 부여한다. 이 값은 link 와 unlink 호출 시마다 갱신된다.

4. st_uid, st_gid 이 두 구조는 각각 화일의 사용자식별번호와 그룹식별번호 를 나타낸다. 초기치는 creat 나 open 호출로서 지정되고, chown 호출로서 변경될 수 있다.

5. st_rdev 는 화일 엔트리가 장치를 기술하는데 사용될 때만 의미를 가진다.

6. st_size 는 화일의 현재 논리적 크기로서 바이트수를 부여한다. 이것은 화 일의 끝에 쓰기명령이 있을 때마다 변경된다.

7. st_atime 는 화일의 자료가 마지막으로 읽혔던 시간을 기록한다. 초기치로 creat 호출시의 시간이 지정된다.

8. st_mtime 는 화일의 자료가 변경될 때의 시간을 기록한다. 화일에 쓰기 명 령이 있을때마다 변경된다.

9. st_ctime 는 stat 구조 자체가 변경될 때의 시간을 기록한다. 이것을 변경 하는 시스템 호출은 link(st_nlink 때문에), chmod(st_mode 때문에), write(st_mtime 과 st_size 때문에)등이 있다.


3.3.1. chmod 의 재고

chmod 는 stat 와 fstat 에 의해서 더 유용하게 사용될 수 있다. 왜냐하면, 이제 화일의 모드를 알수 있으므로 프로그램이 화일허가를 무조건 다시 지정하 지 않아도 되기 때문이다.


제4장 디레토리, 화일시스템, 특수 화일

4.1. 개요

디렉토리 : 디렉토리는 화일 이름들의 창고와 같다.

화일 시스템 : 화일시스템은 디렉토리와 화일들 의 집합으로서, UNIX 화일구 조를 구성하는 디렉토리와 화일들의 계층적인 트리구조의 일부 를 나타낸다.

특수 화일 : UNIX 는 화일의 개념을 시스템에 연결되어 있는 주변장치에까지 확장시킨다.


4.2. 디렉토리와 AT&T 시스템 V 인트페이스 정의

AT&T 의 시스템 V 인터페이스 정의(SVID)는 실제로 UNIX 디렉토리의 형식을 정의하지 않지만, 디렉토리를 다루는 것을 일반화하기 위해 ftw와 getcwd 라 불리는 두 서브루틴을 도입한다. 그러나 UNIX 의 다른버전에서는 이식이 불가 능하다.


4.3. 디렉토리 : 사용자 관점

각 사용자는 보통 자신의 홈 디렉토리(home directory)를 갖는 다. 홈 디렉토 리는 사용자가 로그인할때 들어가는 장소이고, 거기서 화일을 생성하거나 다룰 수 있다. 부디렉토리는 다시 자신의 부디렉토리를 가질 수 있고, 이러한 과정 은 무한히 반복될 수 있다.

현재 디렉토리

로그인한 사용자는 자신이 작업하기 위한 현재작업 디렉토리(current working directory) 또는 현재 디렉토리라고 불리는 화일구조의 한 특정한 장 소를 갖게 된다.

$cd /usr/keith

$pwd

/usr/keith

시스템에 있어서 현재 디렉토리는 화일의 상대적인 경로이름(relative path name)을 찾을 때 시작하는 디렉토리이다.

$cat book/chap1

$car /usr/keith/book/chap1

과 동일하고, 명령어

$cat file1

$cat /usr/keith/file1

과 동일하다.


4.4. 디렉토리의 구현

사실, UNIX 디렉토리는 단지 화일에 불과하다. 그러나 디렉토리와 보통의 화 일사이에는 몇개 의 중요한 차이점이 있다. 디렉토리는 creat 프리미티브를 사 용해서는 생성되지 않는다. 구조적으로 대부분의 UNIX 시스템에서의 디렉토리 는 16 바이트로 된 항목의 열로서 구성된다. 각 항목은 디렉토리에 포함된 각 화일이나 부 디렉토리에 대응한다. 각 항목은 디렉토리에 포함된 각 화일이나 부 디렉토리에 대응한다. 각 16 바이트는 화일의 inode 번호라고 불리는 양의 정수값을 위한 2 바이트와 화일이름을 저장하는 14 바이트로 구성된다. inode 번호는 화일을 유일하게 식별한다.(실제 로 inode 번호는 화일시스템 내에서 유일하다.) inode 번호는 inode 구조라 불리는 디스크 기초 자료구조 (disk-based data structure)를 마련하기 위해 운영체제에 의해 사용된다. 이 구조는 화일에 대한 모든 관리 정보 즉, 크기, 소유자의 사용자 식별번호, 그 룹식별번호, 허가, 생성일, 마지막 변경된 날, 화일 자료를 갖고 있는 디스크 의 블럭 주소등을 포함한다. 위의 디렉토리 표현은 편의상 나타낸 것이고, 실 제로는 2 바이트의 inode 번호가 이진수 형태로 저장된다. 이 두 바이트는 종 종 인쇄 불가능 한 값(ASC II 값이 아님)을 가진다. cat 명령을 사용하여 디렉 토리의 내용을 프린트 해보면 화면에 이상한 문자들이 나타난다. 디렉토리를 조사하는 좋은 방법은 -c 옵션을 갖는 8 진수 덤프(octal dump) od 를 사용하 는 것이다. 현재 디렉토리의 내용을 보기 위해서는

$od -c .

를 수행해야 한다. 이 명령에서 '.' 는 현재 디렉토리를 나타내는 표준기호이 다.


4.4.1. link 와 unlink 의 재고찰

각 link 는 단지 새로운 화일 이름에 원래의 화일 이름과 같은 inode 번호를 가지는 새로운 디렉토리 슬롯(slot)을 만들게 된다. 만일 화일 abc 가 새이름 xyz 를 가지도록 하는 다음의 명령문

link("abc", "xyz");

를 수행한다.

화일이 디렉토리로 부터 제거될때 16 바이트의 디렉토리 슬롯은 실제로 제거 되지 않는다. 단지 inode 번호에 화일이 제거되었다는 것을 알려주기 위해 0 을 저장한다. 반대로 디렉토리에서 새로운 화일이 생성되면, 시스템은 inode 번호와 이름을 거기에다 겹쳐 쓴다. 만일 빈 슬롯이 없으면 화일의 이름은 디 렉토리의 끝에 더해진다. 즉 디렉토리 자체가 확장된다.


4.4.2. 점과 이중점

'.' 과 '..' 은 모든 디렉토리에 항상 존재하는 화일이다. 점은 현재 디렉토 리를 가리키는 표준 UNIX 표기법으로서,

$cat ./fred

or

$ls .

등으로 사용한다. 이중점은 현재 디렉토리의 부모 디렉토리 즉, 현재 디렉토 리를 포함하는 디렉토리를 가리키는 표준 표기법이다. 사실 '.' 과 '..' 은 단 지 현재 디렉토리와 부모 디렉토리에 대한 링크이다.


4.4.3. 디렉토리의 허가

보통의 화일과 같이, 디렉토리도 다른 사용자의 접근 가능성을 제어하는 접근 허가를 가진다. 디렉토리 허가도 보통의 화일 허가처럼 소유자, 그룹, 그외 사 용자에 대해 각각 접근 특권을 명시한 'rwx' 의 세비트씩으로 구성된다.

- 디렉토리에 대한 읽기 허가는 디렉토리에 내에 있는 화일이나 부디렉토리의 이름에 대한 리스트를 볼수 있다는 것을 의미한다.

- 디렉토리에 대한 쓰기 허가는 사용자가 디렉토리 내의 화일을 제거하거나 새로운 화일을 만들수 있다는 것을 의미한다.

- 디렉토리에 대한 수행허가(또는 탐색허가)는 cd 명령 또는 프로그램안의 chdir 시스템 호출을 사용하여 사용자가 디렉토리 내부로 들어가는 것을 허용한다.


4.5. 디렉토리와 프로그래밍

표준 운영 체제 해더 화일 /usr/include/sys/dir.h 는 디렉토리 항목의 완전 한 형식(format)을 정의한 자료구조를 갖고 있다.

#define dirsiz 14

struct direct{

ino_t d_ino;

char d_name[DIRSIZ];

};

자료형 ino_t 는 헤더화일 /usr/include/sys/types.h 에 정의되어 있다. (따 라서, 이 화일은 direct 구조를 사용하는 모든 프로그램에 포함되어야 한다.) ino_t 는 unsigned short 와 동일하기 때문에, direct 구조의 총 크기는 16 바이트 즉, 디스크의 디렉토리 슬롯의 크기와 일치한다. d_ino 필드는 화일 inode 번호를 가지고, d_name 필드는 화일 이름을 가진다. 화일이름이 14 자까지 가능하고 14 자가 되지 않는 화일 이름은 null 로 끝난다. 화일 이름이 14 자인 경우는 null 로 끝나지 않기 때문에 d_name 필드에 대해서는 C 라이브러 리의 문자열 함수를 사용할 수 없다.


4.5.1. 현재 디렉토리

프로세스의 초기의 현재 디렉토리는 프로세스가 시작한 디렉토리로 지정된다. 그러나 chdir 이라 불리는 시스템 호출을 통해서 프로세스가 디렉토리를 변경 하는 것이 가능하다.


4.5.2. chdir 에 의한 디렉토리 변경

char *path;

int retval;

retval = chdir(path);

chdir 시스템 호출은 path 가 호출 프로세스의 새로운 현재 작업 디렉토리가 되게 한다. 이 변경은 단지 chdir 호출을 부른 프로세스에만 적용된다. chdir 은 실태하고 -1 을 돌려준다.


4.5.3. 디렉토리 생성

디렉토리들을 creat 시스템 호출로는 생성할 수 없다. 대신에 특별한 시스템 호출 mknod 를 사용하면 된다. mknod 는 많은 다른 기능을 갖고 있다. 특수 화 일을 만드는 데 사용될 수 있다. 프로세스간의 통신 채널인 FIFO 를 만드는 데 도 mknod 를 이용할 수 있다. mknod 에 대한 접근은 제한되어 있어, 슈퍼사용 자만이 디렉토리를 생성하는 데 이 호출을 사용할 수 있다.

int retval, mode;

char *path;

retval = mknod(path, mode);

디렉토리를 생성하고 접근허가를 설정하기 위해서는 팔진수 040000 에 원하는 접근 허가 값을 mode 에 지정하면 된다. 헤더화일 stat.h 에 이 값을 나타내는 상수 S_IFDIR 이 정의되어 있다.

- 040777 모든 사용자에게 읽기, 쓰기, 탐색허가를 허용하는 디렉토리를 생성한다.

- 040755 소유자에게는 읽기, 쓰기, 탐색 허가를 그외의 사용자에게는 읽기와 탐색허가 만을 허용하는 디렉토리를생성한다. 즉, 소유자만이 이 디 렉토리에 화일을 생성할 수 있다.

mknod 는 성공하면 0 을 실패하면 -1 을 돌려준다. 사용자가 디렉토리를 생성 할 수 있는 방법은 mkdir 명령을 사용하는 것이다.

$mkdir dname

은 디렉토리 dname 을 생성한다.


4.5.4. 루트 디렉토리 변경 UNIX 화일 시스템의 맨 꼭대기는 루트 디렉토리라 불리며 '/' 로 표기된다. UNIX 는 프로세스가 화일 시스템 계층의 출발점을 변경할 수 있도록 하는 시스 템 호출을 제공한다. 이 시스템 호출이 chroot 이다. 그러나 이것은 거의 사용 되지 않는다.

int retval;

char *path;

retval = chroot(path);

chroot 가 성공하면, path 는 '/' 로 시작하는 화일을 탐색하는 출발점이 된 다. (호출 프로세스에 대해서만 적용되고, 전 시스템에는 영향을 끼치지 않는 다). chroot 는 실패하면 -1 을 돌려주고, 루트 디렉토리는 변경되지 않는다. 이 호출은 슈퍼사용자만 이용할 수 있다.


4.5.5. 현재 디렉토리의 이름 탐색

UNIX 시스템 V 는 현재 작업 디렉토리의 이름을 알려주는 getcwd 라 불리는 서브루틴(시스템 호출이 아님)을 정의한다. 실제로는 pwd 프로그램을 수행시킴 으로써 결과를 얻는다.

int size;

char *buf, *ret, *getcwd();

ret = getcwd(buf, size);

getcwd 는 현재 디렉토리 경로이름에 대한 포인터를 돌려준다. 이상한 이유에 서 인수 size 는 복귀되는 경로이름의 길이보다 적어도 2 가 더 커야 한다. buf 가 null 포인터이면 getcwd 는 동적 메모리에서 size개 의 바이트를 할당 한다. getcwd 는 실패하고 null 값을 돌려준다.


4.5.6. 디렉토리 트리 탐색

때때로 디렉토리 계층 트리 상의 주어진 디렉토리로 부터 출발하여 그 디렉토 리의 아래에 있는 모든 화일과 부 디렉토리를 탐색하는 연산이 필요한 경우가 있다.

#include <ftw.h>

char *path;

int func();

int depth, ret;

ret = ftw(path, func, depth);

func 는 사용자가 정의한 함수로서 path 에서 출발하여 방문하는 모든 화일 이나 디렉토리에 대해 호출된다. 사용법에서 볼수 있듯이 func 는 함수 포인터 로서 ftw 루틴에 전달된다. 그래서 ftw 를 호출하기에 앞서 이 함수를 선언할 필요가 있다. depth 인수는 ftw 에 의해 사용되는 서로 다른 화일 기술어의 수 를 조절하는 기능을 한다. 일반적인 경우에는 1 이 가장 안전하다. func 는 3 개의 인수 즉, 객체(object) 이름이 저장된 null 로 끝나는 문자열, 객체에 대 한 자료가 저장된 stat 에 대한 포인터, 정수코드를 가지고 호출된다.

int func(name, statptr, type)

char *name;

struct stat *statptr;

int type;

{

/* body of function */

}

헤더 화일 ftw.h 에 정의 되어 있는 값은

FTW_F 객체가 화일이다.

FTW_D 객체가 디렉토리이다.

FTW_DNR 객체가 읽을 수 없는 디렉토리이다.

FTW_NS 객체가 stat 루틴이 성공적으로 수행할 수 없는 것이다.


4.6. UNIX 화일 시스템

지금까지, 화일들은 디렉토리라 불리는 그룹들로 편성되고, 디렉토리들은 계 층적 트리구조의 일부분을 형성한다는 것을 살펴보았다. 디렉토 리들은 그룹되 어 화일 시스템이라는 객체 (object)를 이룬다. 화일 시스템들은 보통 UNIX 시 스템의 관리자(administator)에게만 연관이 있다. 모든 화일 시스템듸은 부트 스트랩구역(bootstrap area), 화일 시스템 수퍼 블럭(file system super block), 화일 시스템 inode 구조를 위해 준비된 블럭, 특정 화일 시스템에서 화일을 구성하는 자료 블럭등을 위한 4 개의 구역으로 구성된다. 이 블력의 첫 부분(화일 시스템에서 의 논리적 블럭 0, 그러나 물리적으로는 디스크 분할이 시작하는 장소)은 부트 스트랩 블럭으로 사용된다. 즉, 이 블럭은 시스템 시작 시간에 UNIX 를 로드하기 위해 사용하는 하드웨어 지정 (hardware-specific) 부트 프로그램을 포함한다.


4.6.1. 화일 시스템 조작을 위한 시스템 호출

UNIX 에는 화일 시스템을 다루기 위한 2 개의 기본적인 시스템 호출이 있다. mount 는 사용자가 화일 시스템을 이용할 수 있도록 해주고 unmount 는 화일 시스템을 내려 사용자에게 보이지 않도록 한다.


4.6.2. 화일 시스템 탑재 : mount

char *partition, *dir;

int rwflag;

mount(partition, dir, rwflag);

mount 는 화일이 partition 으로 식별되는 디스크 분할에 있는 화일 시스템 볼륨을 디렉토리 dir 로 탑재한다. 보통 partition은 /dev/name 형태의 이름을 갖는다(name 은 다소 바뀔수 있다). /dev 는 특수화일이 저장된 디렉토리 이름 이다. 특수화일은 주변장치를 위해 사용된다. 탑재된후 partition 에 대한 디 렉토리 계층은 디렉토리 dir 을 통해 도달할 수 있다. 인수 rwflag 의 하위비 트가 1 로 지정되면(즉, rwflag 가 1 과 같으면), 화일시스템은 읽기 허가만 가지고 탑재된다. mount 는 슈퍼사용자에 의해서만 호출될수 있고, 실패한 경 우는 -1 을 성공한 경우는 0 을 돌려준다.


4.6.3. 화일 시스템 내리기 : umount

char *partition;

umount(partition);

umount 는 디스크분할 partition 에 위치한 앞서 탑재되었던 화일시스템을 디렉토리 계층으로부터 제거한다. 그러면 이 화일시스템에 있는 화일이나 디렉 토리는 이제 접근이 불가능하게 된다. 사용자가 화일시스템에 있는 화일을 사 용하고 있을 때 또는 사용자의 현재 디렉토리가 화일 시스템에 있는 디렉토리 일때는 umount 가 실패를 하게된다.

캐슁과 sync 호출

효율성을 위해서, 탑재된 화일 시스템의 수퍼 블럭의 복사가 UNIX 기계장치 의 주기억장치에 저장된다. UNIX 는 모든 주기억장치의 버퍼의 내용을 디스 크로 보내주는 시스템 호출 sync 를 제공한다.

sync();

sync 는 화일시스템의 특수화일이름을 통해 저수준(low level)에서 화일 시 스템을 조사할 필요가 있거나 기계파손에 대비하여 자료보존을 해주는 프로 그램에 의해 사용된다. 화일시스템이 너무 오랫동안 과거내용을 가지고 있 게 하지않기 위해서 UNIX 는 시스템은 다중 사용자 모드에 있는 동안 반복 하여 sync 를 호출하는 유틸리티프로그램 update 를 수행시킨다. 보통 이 기간은 30 초로 UNIX 시스템 V 에서는 시스템 관리자에 의해 조절가능하다.


4.6.4. 화일 시스템과 미래

커널이 동시에 여러 다른 배치를 갖는 화일시스템들을 제공해주도록 하는 화 일시스템 스위치(file system switch)를 도입하였다.분산화일시스템(distribu- ted file system)은 여러 다른 기계장치에 있는 사용자들이 동시에 같은 화일 에 접근할 수 있도록 해준다. RFS(Remote File Sharing)는 원격기계장치 (remote machine)의 디렉토리 계층을 국부 시스템(local system)의 디렉토리에 올릴수 있게 해 준다.


4.7. UNIX 특수 화일

UNIX 시스템에 부착된 주변장치(즉 디스크, 터미널, 프리터, 테이프 등)는 화 일 시스템 내에서 화일이름으로 표현된다. 이 화일들을 특수 화일이라 한다. 화일 시스템에 대응되는 디스크 분할도 특수화일로 표현된다. 이 특수화일들은 보통 디렉토리 /dev 에 저장된다.

/dev/tty00

/dev/tty01

/dev/tty02

는 3 개의 시스템 단말기 출입구(port)의 이름이다.

/dev/lp

/dev/rmt0

는 라인프린터와 자기테이프에 대한 이름이다. 디스크 분할에 대한 이름은 다 음과 같다.

/dev/dk0

/dev/dk1

open, close, read, write 는 모두 프로그램내에서 특수 화일에 사용될 수 있 다.

#include <fcntl.h>

main()

{

int i, fd;

fd = open("/dev/tty00", O_WRONLY);

for(i = 0; i < 100; i++)

write(fd, "x", 1);

close(fd);

}

는 100 개의 문자 x 를 단말기 출입구 tty00에 출력시킨다.


4.7.1. 블럭과 문자 특수화일

UNIX 특수화일들은 블럭장치(block device)와 문자장치(charater device)로 나눌수 있다.

1. 블럭 특수화일들은 디스크나 자기테이프같은 장치를 포함한다. 이 장치들 과 커널사이의 자료전송은 표준크기블럭(보통1024바이트)단위로 일어난다. 모든 블럭장치는 임의접근을 제공한다.

2. 문자 특수화일들은 이러한 구조화된 전송기법을 공유하지않는 단말기 라 인, 모뎀라인,프린터등과 같은 장치들을 포함한다. 임의접근은 허용될수도 않을 수도 있다.

화일 시스템들은 단지 블럭 장치에서만 존재할 수 있다는 것에 유의하라.

UNIX 는 특정 주변장치를 구동시키기 위하여 블럭장치표와 문자장치표를 갖고 있다. 주변장치와 자료를 교환하는 순서는 다음과 같다.

1. 시스템 호출 read 또는 write 는 정상적인 방법으로 특수 화일의 inode 에 접근한다.

2. 시스템은 장치가 블럭장치인지 문자장치인지를 알기위해 inode 구조의 플 래그를 검사하여 주번호(major number)를 추출한다.

3. 장치 사황표의 해당항목을 찾기 위해 주번호를 사용하고, 자료 전송을 위 해 장치 특성구동기 루틴을 호출한다.

주변장치에 대한 접근은 보통의 디스크 화일에 대한 접근과 동일하다.


4.7.2. stat 구조의 재고찰

3 장에서 언급했던 stat 구조는 특수화일정보를 저장하기 위한 두 필드를 갖 는다.

st_mode : 특수화일의 경우 st_mode 는 화일의 허가에 블럭장치에 해당하는 8 진수 060000 이나 문자장치에 해당하는 020000 을 더한 값을 갖 는다. 이 값대신에 stat.h 에서 정의된 기호상수 S_IFBLK 와 S_IFCHR 을 사용하기도 한다.

st_rdev : st_rdev 는 주장치번호돠 보조 장치번호를 갖는다.

이 정보를 알기 위해서는 ls 명령에 -l 옵션을 주면 된다. 예를 들어,

$ ls -l /dev/tty3

crw--w--w- 1 ben other 8, 3 Sep 13 10:19 /dev/tty3

결과의 첫번째 열의 문자 'c' 는 /dev/tty3 가 특수 화일이라는 것을 알려준다. 정수값 8 과 3 은 주장치번호와 보조장치번호이다. 프로그램내에서도 st_mode 를 조사해 볼 수 있다.

if((buf.st_mode & S_IFMT) == S_IFCHR)

printf("It's a character device\n");

else

printf("It's not\n");

S_IFNT 는 stat.h 에서 정의된 특수 비트 매스크(special bit mask)이다. ustat 는 가용디스크 블럭의 총 갯수와 가용 inode 의 갯수와 같은 기본 화일 시스템 정보를 얻을 수 있다.

#include <sys/types.h>

#include <ustat.h>

int dev, ret;

struct ustat buf;

ret = ustat(dev, & buf);

여기서 dev 는 stat 구조의 st_rdev 로부터 알아낸 주장치번호와 보조장치 번호를 갖는다. 인수 buf 는 헤더화일 ustat.h 에서 정의된 ustat 구조이다. ustat 구조는 다음과 같다.

struct ustat{

daddr_t f_tfree;

ino_t f_tinode;

char f_fname[6];

char f_fpack[6];

}

f_tfree 는 dev 에 의해 지정된 화일시스템에서의 가용디스크 블럭의 총갯수 를 저장하고 f_tinode 는 이 화일 시스템에 대한 가용 inode 의 갯수를 저장한다. f_name 과 f_fpack 은 시스템 관리자가 정의한 화일시스템과 팩(pack)이름 을 갖는다.


제5장. 프로세스

5.1. 프로세스 개념의 복습

UNIX 에서의 프로세스란 간단히 말하면 수행중인 프로그햄 그 자체이며, 이것 은 다른 환경에서 말하는 태스크(task) 개념에 해당한다. 쉘(shell)은 하나의 명령을 수행하기 위하여 어떤 포그램을 시작할 때마다 새로운 프로세스를 새성 한다. UNIX 프로세스 환경은 화일시스템의 디렉토리 트리와 같은 계층적인 구 조를 가진다. 프로세스 트리의 꼭대기에는 하나의 제어 프로세스가 존재하는 데, 이것은 init 라 하는 매우 중요한 프로그램의 수행이며, 궁극적으로 모든 프로세스는 이것으로 비롯된다. 프로세스 간의 통신을 위해 제공되는 시스템 호출들에 대해서는 다음장에서 설명하기로 하고, 여기서는 다음과 같은 핵심적 인 것만을 소개한다.

fork : 호출 프로세스와 똑같은 프로세스를 하나 생성한다. fork 는 가장 기본적인 프로세스 생성 프리미티브이다.

exec : 시스템 호출들의 모임으로서, 각각은 동일한 기능 즉, 한 프로세스 를 그 자신의 기억장소에 새로운 프로그램을 대치시킴으로써 변환시 키는 기능을 수행한다. exec 호출들 각각의 차이는 그들의 매개변수 목록들이 어떤방법으로 작성되는가에 있다.

wait : 프로세스의 동기화(synchronization)를 위한 초보적인 호출이다. 프 로세스를 연관된 다른 프로세스가 끝날때까지 기다리게 한다.

exit : 프로세스를 종료할때 사용된다.


5.2. 프로세스의 생성

5.2.1. fork 시스템 호출

fork 시스템 호출은 기본적인 프로세스 생성 프리미티브이다. 이것을 통하여 UNIX 는 다중처리 시스템(multitasking system)으로 전환된다.

int pid;

pid = fork();

fork 가 성공적으로 수행되면 커널은 호출하는 프로세스의 복사본을 새로운 프로세스로서 생성 한다. 새로 생성된 프로세스를 자식프로세스(child process)라 하고, fork 를 호출한 프로세스를 부모 프로세스(parent process) 라 한다. fork 의 호출로 자식프로세스가 생성된 후에는 부모프로세스와 자식 프로세스가 동시에 수행 되며, 이때 두 프로세스는 fork 호출문의 바로 다음 문장부터 수행을 계속한다.

프로세스 식별번호

fork 는 인수없이 호출되고, 정수형의 pid 를 돌려준다.

pid = fork();

부모와 자식 프로세스를 구분하는 것은 pid의 값이다. 부모 프로세스는 pid 가 0 이 아닌 양의 정수 값을 갖는 반면 자식 프로세스는 0 을 갖는다. 부모 에게로 돌려주는 pid 값을 자식 프로세스의 프로세스 식별번호(process-id)라 한다.

/* spawn -- demonstrate fork */

main()

{

int pid; /* hold process-id in parent*/

printf("Just one process so far\n");

printf("Calling fork ...\n");

pid = fork(); /* create new process*/

if(pid == 0)

printf("I'm the child\n");

else if(pid > 0)

printf("I'm the parent, child has pid %d\n", pid);

else

printf("Fork returned error code, no child\n");

}

fork 뒤의 if 문에 3 개의 분기(branch)가 존재한다. 첫번째 문기는 변수 pid 값이 0 일때, 자식 프로세스를 위한 동작을 명세하는 것이고, 두번째 문기 는 pid 값이 양수일때에 해당하는 것으로, 부모 프로세스가 해야할 동작을 나 타낸다. 그리고 세번째 분기는 pid 가 음수값(실제는 -1)을 가질 때에 해당하 는데, fork 가 자식 프로세스의 생성이 실패하였을 때의 동작을 말해준다. 이 런 상황은 부모프로세스가 다음과 같은 두종류의 제한을 깨뜨리려고 했을 때 발생한다. 첫째는 시스템 차원에서 허용되는 프로세스의 갯수이고, 둘째는 개 별 사용자가 동시에 수행시킬 수 있는 프로세스의 수에 대한 제한이다. 이런 두 종류의 상황에서 오류변수 errno 는 EAGAIN 이라는 코드를 값으로 가진다. 왜 fork 가 유용한 호출인가를 논의해 보자. 가장 핵심은 fork 가 UNIX 의 다른 기능들과 연관될때 더 유용해진다는 것이다. 예를들어 fork 로 만들어진 부모와 자식 프로세스는 UNIX 에서 제공하는 signal 이나 pipe 등과 같은 프로 세스간의 통신기능을 이용하여 서로 협조해 가면서, 서로 관련되어 있지만 서 로 다른 일들을 해 나갈 수가 있다.


5.3. exec 를 이용한 새 프로그램의 수행

5.3.1. exec 군

exec 군(family) 에 속한 어떤 호출은 새로운 프로그램의 수행을 위해 사용될 수 있다.

char *path, *file;

char *arg0, *arg1, ..., *argn;

char *argv[];

int ret;

.

.

ret = execl(path, arg0, arg1, ..., argn,

(char *)0);

ret = execv(path, argv);

ret = execlp(file, arg0, arg1, ..., argn,

(char *)0);

ret = execvp(file, argv);

exec 의 모든 변종들은 동일한 기능을 수행한다. 즉, 호출 프로세스(exec 를 호출하는)의 기억 장소에 새로운 프로그램을 로드(load)함으로써, 호출 프로세 스가 새 프로그램을 수행하도록 한다. exec 가 성공적으로 수행되면 호출 프로 그램은 완전히 새로운 프로그램으로 대치되고, 그 프로그램의 처음부터 수행은 시작하게 된다. 결과는 새로 만들어진 프로세스 하나만이 존재 하는데, 이 프 로세스는 자기를 호출한 프로세스 와 똑같은 프로세스식별번호를 가진다. exec 는 호출 프로세스와 동시에 수행하는 새로운 부프로세스를 생성하는 것이 아니 라는 점이 중요하다. exec 로 부터의 복귀값은 없다. excl 의 모든 인수는 문 자형의 포인터이다. 첫번째 인수인 path 는 새로이 수행될 프로그램이 들어있 는 화일의 이름을 가리킨다. 이것은 절대 또는 상대적인 유효한 경로이름이어 야한다. execl(혹은 execv)는 쉘명령이 들어있는 화일은 수행시키지 못한다. 두번째 인수 arg0 은 관레적으로 앞자리의 경로이름을 제거한 프로그램 또는 명령의 이름이 된다. 마지막 임을 알리는 표시로 null 포인터가 존재해야 한 다. 디렉토리를 나열하는 프로그램 ls 를 수행하기 위해 execl 을 사용하는 다 음의 프로그램을 살펴보자.

/* runls -- use "execl" to run ls */

main()

{

printf("executing lsn");

execl("/bin/ls", "ls", "0l", (char *)

0);

/* If execl returns, the call has failed, so ... */

perror("execl failed to run ls");

exit(1);

}

execl 이 성공적으로 호출되면 호출 프로그램을 제거하여 수행되지 않게 하 고, execl 이 호출되지 않아 호출 프로그램이 살아 남으면 오류가 발생되도록 하는 것이다. 이런 이유로 execl 과 그 변종들이 복귀될때는 항상 -1 을 돌려 준다.

execv, execlp 와 execvp

execv 는 2 개의 인수만을 가진다. 첫째는 수행될 프로그램의 경로이름을 가 지고 있는 문자열을 가리키는 포인터 둘째는 문자 포인터의 배열로서

char *argv[];

로서 선언되어 있다.

/* runls2 -- use execv to run ls */

main()

{

char *av[3];

av[0] = "ls";

av[1] = "-l";

av[2] = (char *)0;

execv("/bin/ls", av);

/* again - getting this far implies error */

perror("execv failed");

exit(1);

}

execlp 와 execvp 도 execl 과 execv 와 거의 비슷하다. 가장 중요한 차이는 execlp와 execvp 의 첫번째 인수가 경로이름이 아니라 단순히 화일이름을 가리 킨다는 데에 있다.


5.3.2. exec 에 의해 전달된 인수에의 접근

모든 프로그램은 자신의 main 함수로 전달된 인수를 통해서 자신을 호출한 exec 호출의 인수에 접근할 수 있다. 이 인수들은 프로그램의 main 함수를 다 음과 같이 정의함으로써 사용될 수 있다.

main(argc, argv)

int argc;

char **argv;

{

/* body of program */

}

argc 는 인수의 갯수를 나타내는 정수이고, argv 는 인수들의 배열을 가리킨 다. 자신의 첫번째 인수를 제외한 인수들을 표준 출력으로 출력하는 다음의 프 로그램을 살펴보자.

/* myecho -- echo command line arguments */

main(argc, argv)

int argc;

char ** argv;

{

while(--argc > 0)

printf("%s ", *++argv);

printf("n");

}


5.4. exec 와 fork 의 공동이용

fork 와 exec 를 함께 사용함으로써 프로그래머에게 더 많은 기능을 제공할 수 있다. fork 로 자식 프로세스를 만들고, 그자식프로세스 안에서 exec 를 이 용하면, 부모프로세스의 입장에서는 자신을 죽이지 않고도 전혀 다른 프로그램 을 부프로세스로 가질 수 있게 된다. 간단히 오류 루틴 fatal 과 wait 라는 시 스템 호출이 새로이 소개된다.

/* runls3 -- run ls in a subprocess */

main()

{

int pid;

pid = fork();

/* if parent, use wait to suspend

* execution until child finishes

*/

if(pid > 0){

wait((int*)0);

printf("ls completed\n");

exit(0);

}

/* if child then exec ls */

if(pid == 0){

execl("/bin/ls", "ls", "-l",

(char *)0);

fatal("execl failed");

}

/* getting here means pid is

* negative, so error has

* occurred

*/

fatal("fork failed");

}

fatal 은 한 메세지를 출력하기 위해서 단순히 기존의 perror 를 호출한다.

fatal(s) /* print error message and die */

char *s;

{

perror(s);

exit(1);

}

이 예에서 wait 는 fork 호출로 자식 프로세스를 생성한 직후에 호출된다. 시 스템은 이 호출로 인해 자식이 끝날때까지 부모를 sleep 상태에 둔다.


5.5. 상속된 자료와 화일 기술어

5.5.1. fork 에 있어서의 화일과 자료

fork 로 생성된 자식 프로세서는 부모 프로세스와 거의 똑같다. 특히 부모 프 로세스가 가지고 있던 변수의 값들은 자식 프로세스에게 그대로 전달된다. (fork 자신으로부터의 복귀값은 예외임). 자식에게 주어지는 변수의 값들은 부 모 프로세스가 가진 변수의 값들의 '복제'이기때문에 기억장소에서 서로 다른 위치에 놓이게 된다. 그러나 fork 이전에 개방된 화일들은 부모와 자식 프로세 스간에 매우 밀접하게 연관 된다. 이것은 각 화일의 읽기-쓰기 포인터가 부모 와 자식사이에서 공유되기 때문이다. 이러한 공유는 읽기-쓰기 포인터가 프로 그램 자체내에 명시적으로 선언되는 것이아니라 시스템이 관리하는 것이기 때 문에 가능하다. 결론적으로 한 자식 프로세스가 어떤 화일에서 정방향으로 포 인터를 전진시키면 부모 프로세스에서도 새로운 위치로 이동된다.


5.5.2. exec 와 개방된 화일

보통 개방된 화일 기술어들도 exec 를 호출했을 때 생성된 프로세스에 전달된 다. 즉, 원래의 프로그램에서 개방된 화일들은 exec 를 통해서 전혀 새로운 프 로그램이 시작될 때도 개방된 상태가 보존된다. 그런 화일들에 대한 읽기-쓰기 포인터들도 exec 호출에 의해 변화되지 않는다. fcntl 루틴을 이용하면 한 화 일과 연관된 close-on-exec 플래그를 조절할 수 있다.

#include <fcntl.h>

.

.

int fd;

fd = open("file", O_RDONLY);

.

.

/* set close-on-exec flag on */

fcntl(fd, F_SETFD, 1);

close-on-exec 플래그는 명령문

fcntl(fd, F_SETFD, 0);

에 의해 off 로 된다. 플래그의 현재 값은 다음과같이 얻어질 수 있다.

res = fcntl(fd, F_GETFD, 0);

정수형 변수 res 는 close-exec 플래그가 화일 기술어 fd 에 대해 on 일때 1 값을 가지며, 그렇지 않으면 0 을 가진다.


5.6. exit 시스템 호출

int status;

exit(status);

exit 은 이미 익숙한 것으로, 프로세스를 종료 시키고자 할때 사용된다. 물론 프로세스는 프로 그램을 수행하며 main 함수의 끝이 도달하거나, main 에서 return 문을 수행할 때에도 종료된다. exit 호출에서 가장 중요한 것은 모든 개방된 화일 기술어를 닫는 것이다.


5.7. wait 를 이용한 프로세스의 동기화

int retval, status;

retval = wait(&status);

retval = wait((int *)0);

wait 는 자식 프로세스가 수행되고 있는 동안 부모 프로세스의 수행을 일시적 으로 중단시킨다 . 자식이 수행을 마치면, 기다리던 부모는 수행을 재개한다. 하나 이사의 자식이 수행되고 있으면, wait 는 자식 프로세스들 중 하나가 최초로 종료되는 시간에 복귀된다.

pid = fork(); /* create new process */

if(pid == 0){

/* child */

/* do something..*/

}else{

/* parent, so wait for child */

wait((int *)0);

}

fork 와 wait 의 조합은 자식 프로세스가 exec를 통해 완전히 서로 다른 프로 그램을 수행 하도록 되어있을 때 이용된다. wait 가 -1 을 돌려주면 살아있는 자식 프로세스가 없다는 의미이고, 이 경우 errno 는 오류 코드 ECHILD 를 가 지게 된다.


5.8. 좀비와 불완전한 종료

1. 부모 프로세스가 wait 를 수행하지 않고 있는 상태에서 자식이 종료할때

2. 하나 이상의 자식 프로세스가 수행되고 있는 상태에서 부모가 종료할때

1 의 경우 종료하는 프로세스는 일종의 잊혀진 장소로 옮겨져서 좀비(zombie) 가 된다. 좀비 프로세스는 프로세스를 제어하기 위해 커널이 관리하고 있는 테 이블에 등록되어 있으면서 커널의 다른 자원들은 사용하지 않는다.

2 의 경우, 부모는 정상적인 종료가 허용된다. 부모 프로세스의 자식들(좀비 를 포함한)은 시스 템의 초기화 프로세스에게 맡겨진다(초기화 프로세스가 부 모의 역활을 함).


5.9. smallsh : 명령어 처리기

-- 생략


5.10. 프로세스 속성

각각의 UNIX 프로세스는 몇가지의 속송(attribute)들을 가지는데, 이 속성은 프로세스의 수행과 수행계획(scheduling), 화일 시스템의 보안 유지등을 시스 템이 조정하는데에 도움을 준다.

5.10.1. 프로세스 식별번호

시스템은 각 프로세스에게 프로세스 식별번호라는 음이 아닌 정수를 부여한 다. 프로세스 식별번호는 해당 프로세스가 종료하면 다시 사용될 수 있지만, 한 시점에서는 유일하게 프로세스를 지정한다. 프로세스 0 은 수행 계획 프로 세스(scheduler) 이고, 프로세스 1 은 /etc/init 프로그램을 수행하고 있는 초 기화 프로세스이다. 시스템 호출을 이용하여 자신의 프로세스 식별 번호를 참 조할 수 있다.

pid = getpid();

getppid 를 사용하면 호출 프로세스의 부모 프로세스의 프로세스식별번호를

얻을 수 있다.

ppid = getppid();


5.10.2. 프로세스 그룹과

프로세스 그룹식별번호

UNIX 는 프로세스들이 어떤 그룹에 속하는 것을 허용한다. 각 프로세스 그룹 은 프로세스 그룹식별번호라 불리는 정수로 표시된다. 처음에 프로세스는 fork 나 exec 를 호출할 때 자신의 프로세스 그룹식별번호를 새로운 프로세스에게로 상속한다. 그러나 한 프로세스는 setpgrp 를 호출함으로써 자신을 새로운 그룹 에 넣을 수 있다.

newpg = setpgrp();

newpg 는 새로운 프로세스 그룹식별번호인데, 실은 호출 프로세스의 프로세스 식별번호와 동일한 값이다. 한 프로세스는 자신의 현재 프로세스 그룹식별 번 호를 getpgrp 라는 시스템 호출을 이용하여 얻을 수 있다.

pgid = getpgrp();

자신의 프로세스 그룹식별번호를 수정하지 않은 프로그램이라면, pgid 의 값 은 자신의 조상중의 쉘 프로세스의 프로세스식별번호가 될 것이다.


5.10.3. 환경

프로세스의 환경(environment)은 간단히 말하면 null 로 끝나는 문자열의 모 임인데, 프로그램 안에서는 문자형 포인터의 null 로 끝나는 배열 로 표현된 다. 관레적으로, 각 환경 문자열은 다음과 같은 형태를 가진다.

name = something

프로그래머는 프로그램의 main 함수의 인수 리스트에 envp 라는 또 하나의 인 수를 첨가함으로써, 프로세스의 환경을 직접사용할 수 있다. 다음의 프로그램 은 envp 의 유형을 보여준다.

main(argc, argv, envp)

int argc;

char **argv, **envp;

{

/* do something */

}


5.10.4. 현재 작업 디렉토리

앞의 4 장에서 살펴보았듯이, 각 프로세스는 현재 작업 디렉토리와 연관된다. 현재 디렉토리 의 초기상태는 그 프로세스가 fork 나 exec 로 시작될 때 물려 받는다. 다시 말하면 한 프로세스는 그의 부모와 같은 디렉토리에 놓여진다. 자식 프로세스가 chdir 을 호출함으로써, 그 위치를 변화시켜도 부모 프로세스 의 현재 디렉토리는 변하지 않는다. 이런 이유로 표준 cd 명령어는 쉘 자체에 내장된 명령어이고, 프로그램에 대응하는 것이 아니다.


5.10.5. 사용자 식별번호와 그룹식별번호

각 프로세스는 실제 사용자식별번호와 그룹식별번호와도 연관된다. 이것들은 그 프로세스를 호출한 실제 사용자와, 그 사용자가 속한 그룹의 식별번호들이 다. 더 중요한 것은 유효 사용자식별번호와 유효그룹식별번호인데, 이것들은 어떤 사용자가 한 화일을 접근할 수 있는 지의 여부를 결정하는데 사용된다. 대부분의 경우에 유효사용자 식별 번호와 실제사용자 식별번호는 같다. 프로스 랩 화일의 set-user-id 비트가 1 이면, 그 프로그램이 exec 로 호출될 때, 그 프로세스 의 유효 사용자식별번호는 그 프로세스를 시작시킨 실제사용자가 아 니라. 프로그램 화일의 소유자가 된다. 프로세스와 연관된 사용자와 그룹의 식 별번호를 얻는데 쓰이는 시스템호출이 몇가지 있다.

int uid, euid, gid, egid;

/* get real user-id */

uid = getuid();

/* get effective user-id */

euid = geteuid();

/* get real group-id */

gid = getgid();

/* get effective group-id */

egid = getegid();

유효사용자와 그룹의 식별번호를 지정할 때에는 다음의 두가지 호출이 유용하 다.

int status, newuid, newgid;

.

.

/* set effective user-id */

status = setuid(newuid);

/* set effective group-id */

status = setgid(newgid);

두 루틴의 복귀값이 0 이면 수행의 성공을, 1 이면 실패를 나타낸다.


5.10.6. 화일크기의 제한 : ulimit

시스템 V 에는 프로세스마다 wait 시스템 호출을 이용하여 만들 수 있는 화일 의 크기에 제한이 있다. 화일 크기의 제한은 ulimit 라는 시스템 호출로 조작 된다.

long retval, newlimit, ulimit();

int cmd;

.

.

retval = ulimit(cmd, newlimit);

현재 화일의 크기 제한을 얻어내기 위하여 프로그래머는 cmd 인수를 1 로 하 고 ulimit 를 호출한다. 복귀값인 retval 은 512 바이트를 한 블럭으로 하는 단위이다. 화일 크기의 제한을 바꾸려면 cmd 를 2 로 하고, 화일의 크기에 대 한 새로운 제한을 512 바이트 블럭을 단위로 newlimit 에 저장한다.


5.10.7. 프로세스 우선 순위 : nice

시스템이 cpu 시간의 비율을 결정할 때, 특정한 프로세스는 그의 nice 값(정 수)에 의거하여 시간이 할당된다. Nice 값은 0 에서 시스템이 정하는 최대값 (보통은 39)까지이다. 큰 값을 가질수록 프로세스는 낮은 우선순위를 가진다. nice 호출은 하나의 인수를 필요로 하는데, 그것은 현재의 nice 값에다 증가시 키려는 만큼의 양의 정수값, 즉 증가분을 말한다.

nice(5);

수퍼 사용자만이 인수를 음수로하여 우선순위를 높일수가 있다.


제6장. 프로세스간의 통신 : 1

6.1. 서론

소프트웨어 시스템을 구성할때 하나의 프로그램이 아니라 여러 개의 상호보조 적인 프로세스를 이용해야 하는 경우가 있다. 두 개 이상의 프로세스가 하나의 작업을 공동으로 수행하려면 자료를 공유해야 한다. 이를 위한 한가지 방법은 화일을 공유하는 것이다. UNIX 에는 다양한 프로세스간 통신방식이 존재한다. 이장에서는 가장 많이 쓰이는 세가지 방법인 시그널, 파이프, FIFO 를 소개한다.


6.2. 시그널

6.2.1. 개관

시그널을 이용하면 UNIX 프로세스에 소프트웨어 인트럽트를 간단히 전송할 수 있다. 특성상으로 볼때 시그널은 프로세스간에 자료를 전송하는데 보다는 비정 상적인 상황을 처리하는데 더욱 적합하다.

시그널 이름

시그널 이름들은 #define 명령을 이용하여 표준헤더화일인 signal.h 에 정의 되어 있다. UNIX 에서 제공되는 시그널들은 몇개를 제외하고는 대부분 커널 에 의해 사용된다.

SIGHUP : hangup 시그널. 이 시그널은 제어단말기의 연결이 끊어졌을 때 커 널에 의하여 그 단말기에 연결된 모든 프로세스에 보내진다.

SIGINT : interrupt. 이 시그널은 사용자가 인터럽트키를 칠 때 커널에 의 하여 단말기와 연결된 모든 프로세스에 보내진다. 이것은 수행중 인 프로그램을 중지시키는 일반적인 방법이다.

SIGQUIT : quit. 이 시그널은 SIGINT 와 마찬가지로 사용자가 단말기에서 종료(quit)키를 칠때 커널에 의하여 보내진다. 종료키의 일반적인 값은 ASCII FS 또는 CTRL- 이다.

SIGILL : illegal Instruction. 이 시그널은 비정상적인 명령이 수행되려 할 때 운영체제로부터 보내진다.

SIGTRAP : trace trap. 이것은 ptrace 시스템과 함께 sdb 돠 adb 등의 디버 거에 의하여 사용되는 특별한 시그널이다.

SIGFPE : floating-point exception. 이것은 오버플로우나 언더픗로우 같은 부동 소숫점 오류가 발생했을 때 커널에 의하여 보내진다.

SIGKILL : kill. 이것은 프로세스로부터 다른 프로세스를 종료시키기 위하 여 보내지는 조금 특별한 시그널이다.

SIGSYS : bad arguments to a system call. 이것은 프로세스가 정상적인 시 스템 호출 오류 복구 방식에 의하여 처리될 수 없는 부적절한 인 수를 시스템 호출에 보냈을 때 커널에 의하여 보내진다.

SIGPIPE : write on a pipe with no-one to read it. 파이프는 6.3.절에서 다루어질 또 하나의 프로세스간 통신 방식이다.

SIGALRM : alarm clock. 이것은 타이머가 만료되었을 때 커널에의하여 프로 세스에 보내진다.

SIGTERM : software termination. 이 시그널은 보통의 프로그램에 의하여 사용될 수 있도록 제공되는 것이다.

SIGUSR1 & SIGUSR2 : SIGTERM 과 마찬가지로 이것들은 커널에 의하여 사용 되는것이 아니라 사용자가 원하는 목적을위하여 사용될 수 있다.

이 밖에도 구현된 방식에 따라 여러가지 시그널이 있는 데 이들 중 대부분은 커널이 오류상태를 나타내는 데 사용된다. 구현방법에 따라 다르게 작동하는 시그널 중 가장 중요한 것은 SIGCLD(death of a child) 시그널이다. 이것은 최 근의 UNIX 시스템에서 exit 와 wait 시스템 호출을 구현하기 위하여 사용된다. SIGCLD 는 exit 가 수행될때 그 프로세스의 부모프로세스에 보내진다.

정상과 비정상 종료

대부분의 시그널은 그 시그널이 받아졌을 때 정상종료가 된다. 이것은 프로 세스가 사전준비 없이 exit 호출을 수행한 것과 같은 효과이다. SIGQUIT, SIGILL, SIGTRAP, SIGSYS, SIGFPE 등의 시그널은 비정상 종료상태를 초래하고 이 효과는 코아덤프로 나타난다. UNIX 디버거 sdb 와 adb 는 코아화일의 형식 을 알고 있어서, 코아 덤프된 순간의 프로세스 상태를 알아보는 데 이용할 수 있다. 여기서 abort 루틴을 살펴보는 것도 의미있는 일이다.

abort();

abort 는 무엇인가 잘못됐을 때 프로세스가 현재 상태를 기록할 수 있도록 하므로 아주 유용한 디버깅 도구이다. 이것은 또한 프로세스가 자기자신에게 시그널을 보낼 수 있는 방법을 보여준다.


6.2.2. signal 시스템호출을 이용한 시그널처리

#include <signal.h>

int func(), (*was)(), sig;

.

.

was = signal(sig, func);

첫번째 인수 sig 는 대상이 되는 시그널을 지정한다. signal 이 효과를 발휘 하기 위해서는 대상이 되는 시그널이 도달하기 전에 호출이 수행되어야 한다. SIGKILL 을 제외하고는 앞에서 정의한 어떤 시그널도 sig 가 될 수 있다. SIGKILL 은 어떤 경우에도 프로세스를 종료시킬 수 있도록 하기 위하여 제외되 었다. signal 의 두번째 인수 func 는 시그널에 대한 처리를 나타낸다. 이것은 세가지 값을 가질수 있다.

1. 정수값을 돌려주는 함수의 주소.

2. SIG_IGN 시그널을 무시하라는 의미의 특별한 기호 이름. 프로세스는 sig 형의 시그널을 모두 무시한다.

3. SIG_DFL 시스템의 묵시적(default) 처리 상태로 복원하는 기호이름. 모든 표준 시그널에 대하여 이것은 정상 또는 비정상 종료를 의미한다.


6.2.3. 시그널과 시스템 호출

대부분의 경우 프로세스가 시스템 호출을 수행하는 도중에 시그널이 도착하 면, 시그널은 시스템 호출이 종료할 때까지 아무런 영향을 받지않는다. 그러나 몇몇 시스템 호출들은 다르게 처리되는데, 이들은 시그널에 으하여 인터럽트 를 받는다. 이런 호출들은 느린 장치(디스크 화일이 아니라 단말기와 같은 것 들)에 대한 read, write 또는 open, wait 또는 pause 호출이다.

if(write(tfd, buf, SIZE) < 0){

if(errno == EINTR){

warn("Write interrupted");

.

.

}

}


6.2.4. 시그널 재지정

대부분의 시그널(SIGILL 과 SIGTRAP 은 제외)에 대한 처리 함수는 시그널이 포착된 후 즉시 재지정된다.

/* reset -- signal example */

#include <signal.h>

int interrupt()

{

printf("Interrupt calledn");

sleep(10);

}

main()

{

signal(SIGINT, interrupt);

printf("Interrupt set for SIGINTN");

sleep(10);

}

프로그램은 우선 시그널 SIGINT 의 처리함수로 interrupt 를 지정하고, 메시 지를 출력한 후 10 초 동안 정지한다. 만약 사용자가 인터럽트 키를 누르면, interrupt가 수행되면서 다른 메시지가 출력되고 sleep이 두번째로 수행된다. 그러나 만약 인터럽트 키가 또 다시 눌러지면 프로세스는 종료한다. UNIX 시그 널은 누적되지 않는 다는 것에 유의하라. 다시 말하면 하나의 프로세스에 대하 여 한 순간에는 같은 종류의 시그널이 오직 하나만 존재할 수 있다. 반면에 여 러 종류의 시그널은 동시에 존재할 수 있다.


6.2.5. kill 를 이용한 시스널 전송

프로세스는 다른 프로세스가 보낸 시그널을 처리하기 위하여 signal 을 호출 한다. 시그널을 보내는 작업은 kill 시스템 호출을 통하여 이루어진다.

#include<signal.h>

int pid, sig, retval;

.

.

retval = kill(pid, sig);

첫번째 인수 pid 는 시그널 sig 가 보내질 프로세스들을 결정한다.

kill(1234, SIGTERM);

프로세스 번호가 1234 인 프로세스에 SIGTERM 시그널을 보내는 것을 의미한 다. 프로세스는 자기자신에게도 시그널을 보낼 수 있다. kill 의 pid 인수는 특별한 의미를 가지는 다른 값들이 될 수도 있다. 다음은 가능한 네가지를 나 열한 것이다.

1. pid 가 0 이면 시그널은 보내는 프로세스와 같은 프로세스 그룸에 속하는 모든 프로세스에 보내진다.

2. pid 가 -1 이고 프로세스의 유효사용자 식 별번호가 슈퍼사용자가 아니면 시그널은 실사용자 식별번호가 보내는 프로세스의 유효사용자 식별번호와 같은 모든 프로세스에 보내진다. 여기에는 보내는 프로세스도 포함된다.

3. pid 가 -1 이고 유효사용자 식별번호가 슈퍼사용자이면 시그널은 특수한 시스템프로세스를 제외한 모든 프로세스에 보내진다.(특수한 시스템 프로 세스를 제외하는 것은 프로세스의 그룹에 시그널을 보내는 경우에는 모두 해당되는데 여기서는 특히 중요하다.)

4. 마지막은 pid 가 0 보다 작으면서 -1 이 아닌 경우로 시그널은 프로세스의 그룹 식별 번호가 pid 의 절대값과 같은 모든 프로세스 보내진다. 이것은 때때로 보내는 프로세스를 포함한다.


6.2.6. alarm 시스템 호출

alarm 은 프로세스의 알람시계를 조작하는 간단하고도 유용한 시스템 호출이 다. 타이머가 종료된 것을 프로그램에 알려주기 위하여 시그널이 사용된다.

unsigned int remain, secs;

.

.

remain = alarm(secs);

secs 는 시간을 초단위로 지정한다. alarm 은 프로세스의 수행을 중지시키는 sleep 과는 다르다. alarm 은 대신에 즉시 복귀하여 SIGALRM 시그널이 도착할 때까지 정상적으로 수행을 계속한다. 작동중인 알람시계는 exec 호출이 수행되 더라도 계속해서 작동한다.(그러나, fork 를 수행한 후 에 자식 프로세스에게 알람시계는 더이상 작동 하지 않는다.) 알람은 0 을 인수로 하는 alarm 을 호 출하면 취소된다.

/* turn alarm clock off */

alarm(0);


6.2.7. pause 시스템 호출

UNIX 는 alarm 과 같은 부류인 pause 시스템 호출을 제공한다.

int retval;

retval = pause();

pause 를 호출한 프로세스는 SIGALRM 등의 시그널이 도착할 때까지 시스템 자 원을 낭비하지 않도록 수행이 중지된다.


6.2.8. setjmp 와 longjmp

때때로 시그널을 받았을 때 프로그램을 이전의 상태로 복구햐여야 할 떼가 있 다. 예를 들어 사용자가 인터럽트 키를 치면 프로그램의 주메뉴로 돌아가게 해 야 한다고 하자. 이것은 두개의 특수한 서브루틴 setjmp 와 longjmp 를 이용하 여 처리될 수 있다. setjmp 는 프로그램의 현재 상태를 저장하고 (사실 스택의 내용을 저장한다). longjmp 는 저장된 상태를 다시 복구한다.

/* example use of setjmp and longjmp */

#include <signal.h>

#include <setjmp.h>

#include <stdio.h>

jmp_buf position;

main()

{

int goback();

.

.

/* save current position */

setjmp(position);

signal(SIGINT, goback);

domenu();

.

.

}

goback()

{

signal(SIGINT, SIG_IGN);

fprintf(stderr, "nInterruptedn");

/* go back to saved position */

longjmp(position, 1);

}

signal 이 수행된 후에 사용자가 인터럽트 키를 치면, 우선 goback 이 수행된 다. goback 은 longjmp 를 호출하고, setjmp 가 저장한 프로그램 위치로 제어 를 전달한다. 따라서 프로그램은 방금 setjmp 에서 복귀한 것처럼 수행이 계속 된다. 이 경우에 setjmp 의 복귀값은 longjmp 의 두번째 인수가 된다.


6.3. 파이프를 이용한 프로세스간 통신

파이프는 프로세스를 다른 프로세스에 연결시켜 주는 일방통해의 통신채널로 UNIX 의 화일 개념 을 일반화 한 것이다. 프로세스는 write 시스템 호출을 이 용하여 자료를 파이프를 통하여 보낼 수 있고, 다른 프로세스가 read 시스템 호출을 이용하여 이 자료를 읽어들일 수 있다.


6.3.1. 명령어 수준에서의 파이프

대부분의 UNIX 사용자는 명령어 수준에서 파이프를 접해보았을 것이다.

$pr doc | lp

을 수행하면 쉘은 명령어 pr 과 lp 를 동시에 시작한다. 문자 '|' 는 pr 의 표준 출력과 lp 의 표준입력을 연결하는 파이프를 의미한다. 파이프, 특히 멸 령어 수준에서의 파이프는 UNIX 의 가장 강력하고 눈에 두드러지는 특징 중의 하나이다. 파이프를 이용하면 여러 명령어들을 간단히 하나로 연결할 수 있다. 따라서 UNIX 프로그램들은 표준입력과 표준출력을 입.출력으로 하고 잘 정의 된 하나의 작업을 처리하는 일반적인 도구로 개발될 수 있다.


6.3.2. 파이프를 이용한 프로그래밍

프로그램에서 파이프는 pipe 시스템 호출을 이용하여 만들어진다. 시스템 호 출이 성공적으로 수행되면, 두개의 화일기술어가 얻어진다. 그중 하나는 파이 프에 쓰기 위한 것이고 다른 하나는 파이프로부터 읽기 위한 것이다.

int filedes[2], retval;

retval = pipe(filedes);

filedes 는 파이프를 식별하는 화일기술어를 저장하는 크기가 2 인 정수 배열 이다. 호출이 성공적으로 수행되면, filedes[0] 는 파이프로부터 입력을 얻을 수 있도록 개방되고, filedes[1] 은 파이프에 출력할 수 있도록 개방된다. 파 이프는 자료를 first-in-first-out(FIFO)에 근거하여 처리한다. 다시 말하면, 파이프에 먼저 쓰여진 것이 파이프로부터 먼저 읽혀진다. 이 순서는 lseek 이 파이프에는 적용되지 않기 때문에 변화될 수 없다. 파이프의 지정한 가치는 fork 시스템호출과 함께 사용될 떼 발휘된다. 이때 화일기술어는 fork 가 수행 된 후에도 계속 유효하다는 특성이 이용된다.


6.3.3. 파이프의 크기

현실적으로 파이프의 크기는 유한하다. 다시 말하면 일정한 한도내의 자료만 이 읽히지 않은 상태로 파이프에 남아 있을 수 있다. 이 한도는 일반적으로 5120 바이트이다. 대부분의 경우에는 이 한도가 충분하지만, 이 최대 크기는 read 와 write 의 수행과 관계가 있다. 파이프의 용량을 초과하는 write 가 시 도되면 프로세스는 다른 프로세스에 의하여 자료가 읽혀져 파이프에 충분한 공 간이 마련될 때까지 수행이 중단된다.


6.3.4. 파이프 닫기

파이프의 한쪽 끝을 나타내는 화일기술어가 닫히는 경우에는 어떤일이 생길 까?

1. 쓰기전용 화일기술어를 닫았을 때. 다른 프로세스가 자료를 쓰기 위해 해 당 파이프를 개방한 경우에는 아무 일도 일어나지 않는다. 2. 읽기전용 화일기술어를 닫았을 때. 자료를 읽기위해 해당 파이프를 개방한 프로세스가 있는 경우에는 아무 일도 발생하지 않는다. 그러나 파이프로부 터 자료를 읽어들이는 프로세스가 더 이상 없으면 그 파이프에 자료를 쓸 수 있기를 기다리던 모든 프로세스는 커널로 부터 SIGPIPE 시그널을 받는 다.


6.3.5. 블럭되지 않는 read 와 write

파이프에 대한 read 와 write 가 어떤 경우에도 블러되지 않도록 하는 두가지 방법이 있다. 첫번째 방법은 파이프에 대한 fstat 를 사용하는 것이다. 이 방 법은 하나의 프로세스가 파이프로부터 자료를 읽어들이는 경우에 적당하다. 두 번째 방법은 fcntl 을 사용하는 것이다. fcntl 의 기능 중에는 프로세스가 화 일 기술어 의 O_NDELAY 플래그를 1 로 할 수 있도록 하는 것이다. 이것은 파이 프에 대한 read 와 write 가 블럭되지 않도록 한다.

#include <fcntl.h>

.

.

if(fcntl(filedes, F_SETFL, O_NDELAY) < 0)

perror("fcntl");


6.3.6. 파이프와 exec 시스템 호출

두 프로그램을 쉘 수준에서 연결하기 위하여 파이프를 사용할 수 있다.

$ls | ws

쉘은 개방되어 있는 화일기술어가 exec 호출을 수행한 후에도 여전히 개방되 어 있다는 것을 이용한다. exec 를 수행하기 전에 쉘은 ls 의 표준출력을 파이 프의 쓰기부분에 연결시키고, wc 의 표준 입력을 읽기부분에 연결시킨다. 이것 은 fcntl 을 이용하여 이루어지거나 dup 호출을 이용하여 이루어진다. dup 은 다음과 같이 호출된다.

dup(filedes);

이 성공적으로 수행되면 dup 는 filedes 와 같은 화일을 가리키는 새로운 화 일기술어를 돌려 준다.

/* close down standard output */

close(1);

/* dup will now return "1" */

dup(filedes);

.

.

/* program will now write its standard */

/* output to the file referred to by */

/* filedes */

.

.


6.4. FIFO 또는 명명된 파이프

파이프는 간결하고도 강력한 프로세스간 통신수단이다. 그러나 파이프에는 여 러가지 중대한 결점이 있다. 첫째로 파이프는 부모와 자식프로세스와 같이 조 상(ancestry)이 같은 프로세스들을 연결하는 데만 사용할 수 있다. 둘째, 파이 프는 영구히 존재할 수 없다. 이런 결점을 보완하기 위하여 UNIX 시스템 III 에서는 파이프의 한 변형이 도입되었다. 이 새로운 프로세스간 통신방식은 FIFO 또는 명명 된 파이프(named pipe)라 불린다. FIFO 는 영구적이고 UNIX 화 일 이름을 부여받는다. FIFO 는 또한 소유자, 크기, 접근 허가등이 지정된다. FIFO 는 다른 UNIX 화일처럼 개방되고 폐쇄되고 삭제될 수 있으나, 읽거나 쓸 때는 파이프의 성질을 나타낸다. FIFO 가 명령어 수준에서는 어떻게 사용되는 지 살펴보자. FIFO 를 만들기 위하여 mknod 명령이 사용된다.

$/etc/mknod channel p

여기서 channel 은 FIFO 의 이름이다. 인수 p는 mknod 가 FIFO 를 생성하도록 한다. 이 FIFO 를 표준 UNIX 명령을 사용하여 읽거나 쓸수 있다.

$cat < channel

이 명령어 channel 이 만들어진 직후 수행된다면, 이 명령에 대한 처리가 지 연될 것이다. 이것은 자료를 읽기 위해 FIFO 를 개방한 프로세스는 다른 프로 세스가 자료를 쓰기 위해 FIFO 를 개방할 때까지 블럭되기 때문이다. 마찬가지 로 자료를 쓰기 위해 FIFO 를 개방한 프로세스는 다른 프로세스가 자료를 읽기 위해 FIFO 를 개방할 때까지 블럭된다.


6.4.1. FIFO 를 이용한 프로그래밍

FIFO 를 이용한 프로그래밍은 일반적인 파이프를 이용한 프로그래밍과 같다. 차이점은 단지 처음에 파이프를 만드는 방법뿐이다. FIFO 는 pipe 시스템 호출 을 이용하는 것이 아니라 mknod 시스템 호출을 이용하여 만들어진다. FIFO 는 일단 만들어지면 open 을 이용하여 개방된다.

#include <fcntl.h>

.

.

fd = open("fifo", O_WRONLY);

자료를 쓰기위해 FIFO 를 개방한다. 앞 절에서 본 바와 같이 이 호출은 다른 프로세스가 자료를 쓰기 위해 FIFO 를 개방할 때까지 블럭된다(물론 FIFO 가 이미 자료를 쓰기 위해 개방되어 있다면, open 은 즉시 복귀할 것이다.) FIFO 에 대한 블럭되지 않는 open 도 가능하다. 이렇게 하기 위해서 open 은 O_RDONLY, O_WRONLY, O_ROWR 중의 하나와 비트 단위 OR 연산된 O_NDELAY 플래 그를 이용하여 호출되어야 한다.

if((fd = open("fifo", O_RDONLY|O_NDELAY)) <

0)

perror("open on fifo");

자료를 쓰기 위해 FIFO 를 개방한 프로세스가 없다면 이 open 은 블럭되는 대 신 -1 을 돌려 주고 errno 는 ENXIO 값을 가진다. 반면에 open 이 성공적으로 수행되면, 이후에 수행되는 FIFO 에 대한 read 역시 블럭되지 않는다.


제7장. 프로세스간 통신 : 2

7.1. 서론

제 6 장에서 살펴본 프로세스간의 통신방법은 대부분이 여러 해 동안 UNIX 상 에서 유용하게 사용된 것들이다. 시스템 V 에서는 몇가지 새로운 기능이 도입 되어 AT&T 의 시스템 V 인터페이스 정의에 수록되었다. 우선 가장 간단한 것은 레코드 록킹(record locking)으로서 이는 직접적인 프로세스 통신형태가 아니 라 프로세스들의 협력방법이다. 이것은 프로세스가 자신의 배타적인 사용을 위 해 화일의 일부를 입시로 보존할 수 있게 하여, 데이타 베이스관리의 몇가지 어려운 문제 를 해결하게 한다. 시스템 V 의 새로운 프로세스간 통신방법 중 다른 것들은 조금은 이색적이며, 시스템 V 의 기본설비(facility)를 확장한 것 으로 생각할수 있다. 일반적으로 이들 새기능들은 IPC(IPC 는 프소세스간 통신 을 의미한다)라 한다.

1. 메시지 전달(Message Passing)

메시지 전달 설비는 프로세스가 메시지를 주고 받을 수 있게 한다. 이때 메 시지란 입의의 문자열이나 바이트열이다.

2. 세마포어(Semaphore)

메시지 전달과 비교하면, 세마포어는 프로세스 동기화를 위한 보다 하부적 인 수단을 제공하는 것이며, 많은 양의 정보를 전달하는 것에는 적합하지 않다.

3. 공유메모리(Shared Memory)

이것은 두 개 이상의 프로세스가 특정 메모리 세그먼트에 있는 자료를 공유 할 수 있게 된다. 물론 프로세스의 자료 영역은 보통 자신만이 사용할 수 있다. 이 기능은 IPC 방법중 가장 빠르지만 약간의 하드웨어 지원을 필요로 한다.


7.2. 레코드 록킹

7.2.1. 동기

록은 화일 내용을 건드리지는 않으며, 다른 프로세스들에게 해당 데이타가 사 용중임을 나타내는 것이다. 이는 하나의 논리적 작업 또는 트랜잭션을 구성하 는 일련의 처리 과정 도중에 다른 프로세스들이 뛰어드는 것을 막는다. 이러한 유형의 방법을 레코드 록킹이라 하며, 이때의 레코드는 화일의 임의적의 일부 를 지칭한다. 완벽을 기하기 위해서는 록킹 작업이 원자화 되어야 한다. 즉, 이는 다른 프로세스에 의한 모순되는 록킹 시도와는 병행될 수 없다.


7.2.2. lockf 호출

#include <unistd.h>

int filedes, purpose, status;

long recsize;

.

.

status = lockf(filedes, purpose, recsize);

lockf 는 SVID 에 정의된 레코드 록킹 기법 중 가장 단순한 것이다. lockf(그 리고 fcntl)에 의한 록킹은 단지 권고용(advisory)이다. 이는 화일 허가만 있 으면 프로세스는 화일에서 록이 된 것으로 알려진 부분을 수정할 수 있다는 것 을 의미한다. lockf 의 첫번째 인수 filedes 는 개방된 화일 기술어라야 한다. 이는 록이 수행될 화일을 지정하고, O_WRONNLY 나 O_RDWR 를 사용하여 개방된 것이라야 한다. purpose 인수는 lockf 가 실제로 무엇을 수행할 것인가를 결정 한다. 이를 위해 허용된 값들은 화일 unistd.h 에 다름과 같이 정의되어 있다.

#define F_ULOCK 0 /*Unlock a locked record */

#define F_LOCK 1 /*lock a record for exclusive use */

#define F_TLOCK 2 /*test for lock, lock if none */

#define F_TEST 3 /*test for existing lock only */

lockf 의 마지막 인수 recsize 는 록(lock), 해제(unlock), 또는 단순히 검사 될 영역(section)의 크기를 지정한다. 이 영역의 시작위치는 화일의 읽기-쓰기 포인터의 현재위치와 같다. 이러한 이유로, lockf 는 lseek 다음에 호출된다.

F_LOCK

purpose 값을 F_LOCK 으로하여 lockf 를 호출하면, 시스템은 화일의 읽기-쓰 기 포인터와 recsize 로 정의되는 영역에 록을 하려 시도한다.

lockf(fdes, F_LOCK, 512L);

는 현재의 읽기-쓰기 포인터의 위치에서 시작하여 512 바이트에 록을 건다.

F_ULOCK

F_ULOCK 은 호출프로세스에 의해 전에 록된 영역을 해제(unlock)한다. 이는 보통 F_LOCK 이나 F_TLOCK 이 요청된 후에 사용된다. 만약 방금 해제된 영역을 록하려고 기다리던 프로세스가 있다면, 그 중 하나가 다시 시작할 수 있다.

F_TEST

F_TEST 는 다른 프로세스에 의해 해당 영역이 록 되었는 지를 검사하는 것이 다. 이영역도 역시 화일의 현재 위치와 인수 recsize 에 의해 지정된다. 록 되 어 있지 않다면 lockf는 0 을 돌려준다.

F_TLOCK

이 요청은 F_TEST 와 F_LOCK 을 결합한 것으로, 화일에서 기존에 록된 영역 을 다시 록하려 할때, 프로그램이 수행을 중지하지 않고 다른 행동을 계속하려 는 경우에 유용하다. 록 되지 않은 영역에 대해 F_TLOCK 이 사용되면, F_LOCK 과 같이 록이 적용된다. 록된 영역에 대해서는, lockf 는 -1 을 돌려주고 error 를 EACCES (UNIX 의 미래 버전에는 EAGAIN)로 한다.

recsize 인수가 0 값을 갖는 경우

만약 lockf 의 인수 recsize 의 값이 0 이라면, 록되는 화일의 영역은 화일 의 현재 위치부터 화일 크기의 최대 한계(이는 각 구현마다 다르다)까지로 정해진다.

lseek(fd, 0L, 0);

lockf(fd, F_LOCK, 0L);

이것은 간단히 화일 전체를 록한다.

교착상태

두 프로세스 PA 와 PB 가 같은 화일에서 작업중이라 하자. 또 PA 가 F_LOCK 요청이 이러한 교착상태를 유발하려 한다면, 호출은 실패하여 -1 을 돌려주고 errno 값을 EDEADLK 으로 한다.(EDEADLK 은 시스템 록 테이블이 가득찬 경우 도 의미할 수 있다.)


7.2.3. fcntl 을 사용한 레코드 록킹

우리는 이미 화일제어호출 fcntl 을 소개했다. 보통의 기능과는 별도로 fcntl 은 lockf 보다 일반적인 형태의 레코드 록킹을 수행하는데 사용될 수 있다. 이 는 두가지 형태의 록을 제공한다.

1. 읽기 록

읽기 록은 단순하게 다른 프로세스들이 쓰기 록이라는 두번째 형태의 록을 못하도록 한다.

2. 쓰기 록

쓰기 록은 다른 프로세스들이 해당구역에 읽기나 쓰기 록을 할 수 없도록 한다.

레코드 록킹을 위해 fcntl 을 다음과 같이 사용된다.

#include <fcntl.h>

struct lock ldata;

int filedes, cmd, status;

.

.

status = fcntl(filedes, cmd, &ldata);

filedes 인수는 역시 유효한 개방된 화일 기술어라야 한다. 읽기 록을 위해서 는 filedes 는 O_RDONLY 나 O_RDWR 을 이용해서 개방되어야 한다. 따라서 creat 에 의한 화일 기술어는 사용될 수 없다. 쓰기 록을 위해서는 filedes 는 O_WRONLY 나 O_RDWR 을 사용해 개방되어야 한다. 레코드 록킹에대한 인수 cmd 의 값은 다음의 3 개이다.

F_GETLK

ldata 인수를 통해 전달된 데이타에 기초해서 록 정보를 얻는다.(되돌아오는 정보는 ldata 에 기술된 록을 '블럭(block)'하는 첫번째 록을 가리킨다.

F_SETLK

화일에 록을 시도하고, 불가능하면 즉시 되돌아온다. 활동중인 록을 제거하 는 데도 사용된다.

F_SETLKW

화일에 록을 시도하고, 이것이 만약 다른 프로세스 소유의 록에 의해 블럭되 면 수행을 중지한다. lockf 에 F_LOCK 를 사용한 경우와 같이 fcntl 록에 의 해서 수행이 중단된 프로세스는 시그널에 의해 인터럽트 될 수 있다.

구조(structure) ldata 는 록 기술어이다. 이의 자료형 flock 은 fcntl.h 에 다음과 같이 정의되어 있다.

short l_type; /* descrives type of lock */

short l_whence; /*offset type, like lseek

*/

long l_start; /* offset in bytes */

long l_len; /* segment size in bytes */

short l_pid; /* set by F_GETLK command */

l_whence, l_start 그리고 l_len 은 록, 검사, 또는 해제되어절 화일의 구역 을 지정한다. l_whence 는 lseek 의 세번째 인수와 매우 유사하다. 즉 값으로 서 0,1,2 를 갖는 경우에 각각 화일의 처음, 읽기-쓰기 포인터가 가리키는 현 재의 위치, 그리고 화일의 끝을 변위(offset)의 기준점으로서 나타낸다. l_start 는 구역의 시작 위치를 l_whence 에 대한 상대위치로서 나타낸다. l_len 은 바이트 단위로서의 구역의 길이이다. lockf 의 경우와 같이 이 값이 0 이면, 세그먼트의 길이는 가능한 최대의 변위가 된다. l_type 은 적용되는 록의 형태를 지시한다. 이에 대한 값들은 fcntl.h 에 다음과 같이 정의되어 있 다.

F_RDLCK 적용되는 록이 읽기 록이다.

F_WRLCK 적용되는 록이 쓰기 록이다.

F_UNLCK 특정 구역에 대한 록을 해제한다.

l_pid 는 fcntl 의 명령으로 F_GETLK 이 선택된 경우에만 유효하다. 현재 시 도하는 록이 기존의 록에 의해 블럭되는 경우, l_pid 는 기존의 록 을 지정한 프로세스의 프로세스 식별번호값을 갖는다.


7.3. IPC 설비

7.3.1. 소개와 기본개념

UNIX 시스템 V 는 IPC 설비라는 새롭고 다양한 프로세스간 통신 방법을 제공 한다. 단위 작업들이 서로 협력하는 시스템을 프로그램할 때 다양한 종류의 방 법론적인 접근이 가능 하게 되었다. 반면에 IPC 에 대한 인터페이스는 보통의 UNIX 개념과는 맞지 않아서, 해당 부분은 시스템의 다른 부분과 부조화를 이룬 다. IPC 설비는 다음의 세 부류로 나뉜다.

1. 메시지 전달

2. 세마포어

3. 공유메모리

IPC 설비 키

가장 중요한 공통 기능은 IPC 설비 키이다. 키란 화일 이름이 화일을 지정하 는 것과 마찬가지로 UNIX 시스템의 IPC 객체를 지정하는 데 사용하는 숫자이 다. 즉 키는 여러 프로세스사이에 IPC 자원이 쉽게 공유되도록 해준다. 지정될 수 있는 객체로는 메시지 큐, 세마포어의 접합, 또는 공유 메모리 세그먼트 등 을 들 수 있다. 키의 실제 자료형은 구현에 좌우되는 자료형 key_t 에 의해 결 정된다. key_t 는 시스템 헤더화일 types.h 에서 정의된다. UNIX 의 어떤 버젼 은 화일의 경로이름을 키로 변환해주는 단순한 라이브러리 함수를 제공한다. 이 루틴의 이름은 ftok 이다.

#include <sys/types.h>

#include <sys/ipc.h>

key_t keyval, ftok();

char *path, id;

.

.

keyval = ftok(path, id);

이루틴은 화일 path 에 관련된 정보에 기초해서 키 값을 돌려준다. 같은 path 값에 대해서라도 id 값이 다르면 다른 키 값이 산출된다. ftok 가 가장 유용한 응용분야는, 명명된 화일 들을 조작하는데 IPC 기능들이 사용될 경우, 또는 필 수 불가결하고 영구적이면서 불변하는 화일에 대한 이름이 주어진 경우이다.

IPC get 연산

프로그램은 IPC 객체를 생성하거나 기존의 것에 접근하려 할 때 key 를 사용 한다. 두가지 모두 IPC get 연산으로 호출된다. 한번 생성된 IPC 설비 식별자 는 유일하다. 즉, 서로 다른 프로세스라도 동일한 IPC 객체를 위해서는 같은 값을 사용한다. 예를 들면, 다음의 문장은 IPC 호출 msgget을 사용하여 새로 운 메시지 큐를 생성한다.

msg_qid = msgget((key_t)0100, 0644|IPC_

CREAT|IPC_EXCL);

msgget 의 첫 인수는 메시지 큐의 키이다.

그밖의 IPC 연산

IPC 설비와 같이 사용되는 연산에는 다음의 두가지 유형이 있다. 하나는 상 태정보를 얻거나 제어 값을 지정하는 제어 연산이다. 이에 속하는 호출로는 msgctl, semctl, shmctl 등이 있다. 다른 하나는 좀 더 특수한 연산으로서, 비록 각각의 호출은 서로 다른 이름을 갖고 있지만, 표준형 매뉴얼에서는 이들 을 msgop, oemop, shmop 이라는 표제하에 모아놓고 있다. 예를 들면, msgop 에 는 다음의 두 함수가 있다. msgsnd 는 메시지 큐에 메시지를 전달하고, msgrcv 는 메시지 큐에서 메시지를 읽어들인다.

상태자료구조

IPC 객체가 생성되면 시스템은 IPC 설비 상태 구조를 만들어서 해당 객체와 관련된 각종 관리 정보를 수록한다. 상태 자료 구조의 자료 형은 메시지, 세마 포어, 공유 메모리마다 하나씩 존재한다. 세가지의 상태 구조 자료형은 모두 공통적으로 허가 구조를 포함한다. 이 허가구조는 다음과 같이 구성되며 태그 (tag) ipc_perm 으로 식별된다.

ushort cuid; /*user-id of creator of IPC object*/

ushort cgid; /* group-id of creator */

ushort uid; /* effective user-id */

ushort gid; /* effective group-id */

ushort umode; /* permissions */

이 상태 자료 구조는 사용자가 IPC 객체를 읽어서 해당 정보를 얻을 수 있는 지, 또는 그 객체에 정보를 쓸 수 있는지를 결정한다.


7.3.2. 메시지 전달

메시지란 본질적으로 문자나 바이트의 열이다.(반드시 널 문자로 끝날 필요는 없다.) 메시지는 프로세스 간에 메시지 큐를 통해 전달되며,메시지큐는 msgget 프리미티브에 의해 생성되고 접근된다. 일단 큐가 만들어지면, 적당한 권리를 가진 프로세스가 msgrcv 에 의해 이 메시지를 큐에 남길 수 있다. 다른 프로세 스는 msgrcv 에 의해 이 메시지를 큐로부터 제거한뒤 읽어들일 수 있다.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msg_qid, permflags;

key_t key;

.

.

msg_qid = magget(key, permflags);

의 호출은 두 기능 open 과 creat 를 동시에 수행하는 것과 같이 생각되어질 수 있다. permflags 인수는 msgget 이 수행해야 할 작업을 정확히 결정한다. 이돠 관련이 있는 두개의 상수가 ipc.h 화일에 정의되어 있는데, 이들은 독립 적으로 사용될 수도 있고, 비트끼리 OR 될 수도 있다.

IPC_CREAT

이것은 key 에 해당하는 메시지 큐가 존재하지 않는 경우에 msgget 가 이를 생성하도록 지시한다.

IPC_EXCL

IPC_CREAT 와 이것이 동시에 지정된 경우의 호출은 단지 하나의 메시지 큐 를 생성한다.

msgop 루틴들 : msgsnd 와 msgrcv

일단 큐가 생성되면, 두 개의 msgop 프리미티브를 사용하여 이를 다룰수 있 다. 첫번 것은 msgsnd 이다.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msg_qid, size, flags, retval;

struct my_msg{

long mtype;

char mtext[SOMEVALUE];

}message;

.

.

retval = msgsnd(msg_qid, &message, size, flags);

msgsnd 는 msgget 으로부터 얻은 msg_qid 가 가리키는 큐에 메시지를 보낸다. 메시지 자체는 놀라웁게도 사용자가 제공하는 구조로 선언된 변수 message 에 담겨져 있다.

struct my_msg{

long mtype;

char mtext[SOMEVALUE];

};

msgsnd 의 인수 flags 는 단 하나의 의미있는 값을 갖는데 이는 IPC_NOWAIT 이다. IPC_NOWAIT 가 0 인 경우에는 메시지 전달에 필요한 시스템 자원이 없을 때 호출 프로세스의 수행이 중단된다. msgop 유형에 속하는 또 하나의 함수는 msgrcv 이다.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msg_qid, size, flags, retval;

struct my_msg{

long mtype;

char mtext[SOMEVALUE];

}message;

long msg_type;

.

.

retval = msgrcv(msg_qid, &message, size, msg_type, flags);

msgrcv 가 사용되면, 큐의 사용 허가에 저촉되지 않는 한, msg_id 가 가리키 는 큐로 부터 메시지를 읽어들인다. 메시지를 큐에서 읽게 되면 이는 큐에서 자동적으로 제거된다. 이때 message 변수는 받아들인 메시지를 저장하고, size 변수는 이 구조에 담아둘 수 있는 정보의 최대길이를 지정한다. 호출이 성공하 면 받아들인 메시지의 길이가 retval 에 지정된다. msg_type 인수는 실제로 어 떤 메시지를 받아들 일지를 결정한다. 선택을 하는 기준은 메시지의 mtype 필 드이다. msg_type 의 값이 0 인 경우는 큐의 제일 첫번째 메시지, 즉 가장 일 찍들어온 메시지를 읽어들인다. msg_type 의 값이 0 보다 큰 경우는 해당 값을 가진 메시지의 첫번째 것을 읽는다. 마지막으로 msg_type 이 0 보다 작은 경우 가 있다. 이때는 메시지의 mtype 값이 msg_type 의 절대값보다 작거나 같은 것 중에서 최소값을 갖는 첫번째 메시지를 읽어들인다. 마지막 인수 flags 는 제 어정보를 갖게된다. 유효한 값으로는 IPC_NOWAIT 와 MSG_NOERROR 값을 단독으 로 혹은 OR 해서 사용할 수 있다. MSG_NOERROR 가 지정된 경우, 메시지의 내용 이 size 보다 길면 초과분을 제거한다. 지정되지 않았을 경우에는 msgrcv 가 실패하게 된다.

msgctl 시스템 호출

msgctl 에는 세가지 목적이 있다. 이들은 각각 프로세스에게 메시지 큐의 상 태정보를 주고, 메시지 큐에 관련된 제한사항을 변경할 수 있게 하고, 시스템 상에서 큐를 제거할 수 있게 하는 것이다.

#include <sys/type.h>

#include <sys/ipc.h>

#include <sys/msg.h>

int msg_qid, command, retval;

struct msqid_ds msq_stat;

.

.

retval = msgctl(msg_qid, command, &msq_stat );

물론 msg_qid 는 유효한 메시지 큐 식별자이다. &msq_stat 는 msgid_ds 구조 를 갖는 변수의 주소이다. 이 구조는 msg.h 에 다음과 같이 정의 되어 있다.

struct

ipc_perm msg_perm; /*ownership/perms*/

ushort msg_qnum; /*no. messages on queue*/

ushort msg_qbytes; /*max. no. bytes for

queue*/

ushort msg_lspid; /*pid of last msgsnd*/

ushort msg_lrpid; /*pid of last msgrcv*/

time_t msg_stime; /*last msgsnd time*/

time_t msg_rtime; /*last msgrcv time*/

time_t msg_ctime; /*last change time*/

ipc_perm 구조는 앞에서 설명했듯이 메시지 큐와 관련된 소유권과 사용허가를 저장한다. msg_ctl 의 command 인수는 수행될 작업을 표시한다. 사용 가능한 값은 3 개가 있는데 이들은 IPC 기능 3 개에 모두 적용될 수 있다. 이 값들은 ipc.h 에 상수로 정의되어 있다.

IPC_STAT

메시지 큐의 상태정보를 msg_stat 에 담는다.

IPC_SET

msg_stat 에 있는 정보를 바탕으로 메시지 큐에 대한 제어 변수들의 값을 지정한다.

IPC_RMID

이는 메시지 큐를 시스템에서 삭제하게 한다.


7.3.3. 세마포어

이론적 형태의 세마포어

세마포어 sem 이란 다음과 같은 연산이 허용된 정수형 변수를 말한다(p 와 v 란 이름은 wait 와 signal 이란 말의 네덜란드어에서 나온 것으로 이때 signal 이란 물론 UNIX 의 signal호출과는 다른 것이다).

p(sem) or wait(sem)

if(sem != 0)

decrement sem by one

else

wait until ssem becomes no-zero

v(sem) or signal(sem)

if( queue of waiting processes not

empty)

restart first process in wait

queue

else

increment sem by one

두 연산은 모두 원자화되어야 한다. 즉 sem 을 변경할 수 있는 프로세스는 한

순간에 오직 하나 뿐이다. 세마포어의 좋은 점은 다음의 사실이 항상 만족된다

는 것이다.

(semaphore's initial value

+ number of v operations

- number of completed p operations) >= 0

이를 세마포어의 불변특성(unvatiant)이라 한다. 전산 과학자들은 이러한 불 변 특성을 선호하는데 이는 프로그램을 체계적이고 엄격하게 검정할 수 있도록 하기 때문이다.

UNIX 시스템 V 에서 구현된 세마포어는 이러한 개념에 기초했지만 보다 일반 적인(그리고 아마도 복잡한) 기능을 제공한다. 우선 semget 과 semctl 을 살펴 보자.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

key_t key;

int sem_id, nsems, permflags, command;

int retval, sem_num;

union semun{

int val;

struct semid_ds *stat;

ushort *array;

} ctl_arg;

.

.

sem_id = semget(key, nsems, permflags);

retval = semctl(sem_id, sem_num, command,ctl_arg);

semget 호출은 msgget 과 유사하다. 인수 nsems는 세마포어 집합에 필요한 세 마포어의 갯수를 나타낸다. semget 호출이 성공하면 세마포어 집합 식별자라는 메시지 큐 식별자와 유사한 역할을 하는 값이 돌아온다. C 언어의 관습을 따라 서 세마포어 집합에 대한 첩자는 0 부터 nsems-1 까지 있을 수 있다. 집합 내 의 각 세마포어는 다음과 같은 값들을 갖게 된다.

semval

세마포어의 값으로서 항상 양수가 지정된다.

sempid

세마포어에 접근했던 최근의 프로세스의 프로세스 식별번호이다.

semncnt

세마포어의 값이 현재보다 증가하기를 기다리는 프로세스의 갯수

semzcnt

세마포어의 값이 0 으로 되기까지 기다리는 프로세스의 갯수

정의에서 알 수 있듯이 함수 semctl 은 msgctl보다 훨씬 복잡하다. sem_id 는 유효한 세마포어 식별자라야 한다. command 는 msgctl 에서와 같이 수행해야 할 정확한 기능을 명시한다.

semctl 기능코드

--------------------------------------------

표준 IPC 기능(semid_ds 구조는 sem.h에 정의됨

--------------------------------------------

IPC_STAT 상태정보를 ctl_arg.stat 에 저장

IPC_SET ctl_arg.stat 에 저장된 형태로 소유 권과 사용 허가권을 지정

IPC_RMID 시스템에서 해당 세마포어의 집합을 삭제

--------------------------------------------

단일 세마포어 연산

( retval 에서 넘어온 값 sem_unm 을 사용 )

--------------------------------------------

GETVAL 세마포어의 값 semval 을 돌려준다.

SETVAL 세마포어 값을 ctl_arg.val 로 지정

GETPID sempid 의 값을 돌려준다.

GETNCNT semncnt 를 돌려준다.

GETZCNT semzcnt 를 돌려준다.

--------------------------------------------

전체 세마포어 연산

--------------------------------------------

GETALL 모든 senvals 의 값을 ctl_arg.array 에 저장한다.

SETALL ctl_arg.array 의 값을 사용하여 모든 semvals 값을 지정한다.

--------------------------------------------

세마포어 연산 : semop 호출

semop 호출 semop 은 기본적인 세마포어 연산을 실제로 수행하는 시스템호출 이다. 이때 semop 은 메뉴얼의 항목이 아니라 실제 함수이름이다.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int retval, sem_id;

struct sembuf op_array[SOMEVALUE];

.

.

retval = semop(sem_id, op_array, SOME VALUE);

sem_id 는 세마포어 집함 식별어로서 이전에 semget 호출을 통해 값이 지정되 어야한다. op_array 는 sembuf 구조의 배열로서 sembuf 구조는 sem.h 에 정의 되어 있다. SOMEVALUE 는 임의의 정수형 상수이다. 각각의 sembuf 구조 변수는 세마포어에 대해 수행할 연산을 지정한다. sembuf 구조를 좀더 자세히 보면 다 음과 같다.

short sem_num;

short sem_op;

short sem_flg;

sem_num 는 집합 내의 세마포어에 대한 첨자를 저장한다. 만약 집합의 원소가 하나뿐이라면 sem_num 의 값은 0 이어야한다. sem_op 는 함수 semop 이 수행해 야 하는 기능을 정수로서 나타낸다.

SEM_UNDO 플래그

이것은 sembuf 구조의 구성요소 sem_flg 에 있는 플래그의 하나이다. 이는 프로세스의 수행이 끝났을 때 시스템이 수행된 연산을 자동적으로 취소하도록 지시한다.


7.3.4. 공유 메모리

공유 메모리 연산은 둘 이상의 프로세스가 실제 메모리의 일부를 공유하게 해 준다(물론 보통의 경우에는 서로 다른 프로세스의 자료영역은 완전히 독립적이 다). 이 기법은 세가지 IPC 기능 중에서 가장 효율적이다. 모든 공유 메모리 연산은 특별한 하드웨어의 지원을 필요로 한다.

sbmget 와 shmop 시스템 호출

공유하는 메모리 영역은 shmget 호출을 통해 생성된다.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

key_t key;

int size, permflags, shm_id;

.

.

shm_id = shmget(key, size, permflags);

이는 msgget 이나 semget 과 대응된다. 인수 size 는 공유할 메모리 영역의 필요한 최소 크기를 바이트 단위로 나타낸 커서이다. key 는 영역을 식별하기 위한 키 값이다. permflag 는 메모리 영역에 대한 사용 허가권으로서, msgget 이나 semget 의 경우돠 같이 IPC_CREAT 와 IPC_EXCL 를 사용하여 지정할 수 있 다. 생성 되는 메모리 영역은 프로세스의 논리적 자료 영역이 아니라 실제 메 모리의 일부이다.

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

int shm_id, shmflags;

char *memptr, *daddr, *shmat();

.

.

memptr = shmat(shm_id, daddr, shmflags);

shmat 는 shm_id 가 지정하는 메모리 영역(shmget 호출에 의해 얻어짐)과 호 출 프로세스 의 유효한 주소를 연관 짓는다. daddr 는 호출에서 선택되는 주소 를 프로그래머 가 제어할 수 있게 한다. shmflags 의 값은 두 개의 플래그 SHM_RDONLY 와 SHM_RND 를 조합하 여 정해진다.

shmctl 시스템 호출

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

int shm_id, retval, command;

struct shmid_ds shm_stat;

.

.

retval = shmctl(shm_id, command, &shm_stat

);

이것은 msgctl 과 정확히 일치한다. command 가 취할 수 있는 값은 IPC_STAT, IPC_SET 그리고 IPC_RMID 이다.


7.3.5. ipcs 와 ipcrm 명령

IPC 설비를 이용하기 위해 쉘 수준에서는 두개 의 명령어를 제공한다. 첫째는 ipcs 로서 IPC 설비의 현재 상태 정보를 출력한다. 둘째는 ipcrm 명령으로서 시스템에서 IPC 객체 를 제거하기 위해 사용된다. 예를 들어,

$ipcrm -s 0

는 식별자 0 과 연관된 세마포어를 제거한다.또

$ipcrm -S 200

는 키값이 200 인 세마포어를 제거한다.


제8장. 단말기

8.1. 서론 프로그램과 사용자가 단말기를 통해 교신할 때는 언제나 보기보다 훨씬 많은 일들이 벌어진다. 예를 들어 프로그램이 단말기 장치에 문자열을 쓸 때 이 문 자열은 단말기 장치 구동기라는 커널의 한부분에 의해 우선 처리된다. 시스템 이 유지하는 상태 플래그가 갖는 값에 따라서, 문자열은 축약된 형태로 전달되 기도 하고 구동기에 의해 변형되기도 한다.

- 프로그램

이는 출력 문자열을 생성하고 입력 문자열을 해석한다.

- 단말기 장치 구동기

이는 kernel 의 일부, 즉 소프트웨어의 일종이다. 구동기의 주요부분은 컴퓨터가 단말기와 통신하도록 해 주는 특정 하드웨어와 상호작용을 한다. 단 말기 장치 구동기의 주요 기능은 프로그램과 단말기 장치 사이에 자료를 전달 하는 것이다.

- 키보드와 화면

이들 두 노드는 단말기 자체를 나타내며 단말기가 두가지 측면을 갖고 있 음을 강조한다. 단말기의 키보드는 입력 장치에 해당하고 화면은 출력장치로 작동한다.

두가지 주의할 점이 있다. 첫째, 우리는 UNIX에서 사용되는 단말기의 대부분 인 비동기식 단말기 만을 다룬다. 둘째, UNIX 나 그 변중에서의 단말기 조작은 서로 일치하지 않는 것으로 악명이 높다.


8.2. UNIX 단말기

제 4 장에서 언급했듯이 단말기는 특수화일로 인식된다. (그리고 단말기의 특 성상 문자 장치 로서 취급된다). 단말기 이름으로는 다음의 것 들이 많이 쓰인 다.

/dev/console

/dev/tty01

/dev/tty02

/dev/tty03

tty 는 단말기를 지칭하는 UNIX 용어이다.


8.2.1. 제어 단말기

프로세스의 표준 화일 기술어를 통하여 접근할 수 있는 단말기를 프로세스와 프로세스 그룹의 제어 단말기라 한다. 프로세스가 자신의 화일 기술어 상태와 무관 하게 제어 단말기에 접근하려면 /dev/tty 란 화일이름을 사용하면 된다. 이는 항상 프로세스 의 현재 제어 단말기를 가리킨다.


8.2.2. 자료전송

단말기 장치 구동기는 프로그램과 단말기 장치 사이에 문자를 전달하는 것이 주 임무이다.


8.2.3. 반향과 미리 쳐넣기

가장 기초적인 편의 기능으로는 문자의 반향(echoing)을 꼽을수 있다. 이는 사용자가 키보드 에서 문자 'A' 를 칠때 화면에 'A' 가 나타나게 하는 기능이 다. 이는 프로그램이 화면에 출력 하는 도중에 사용자가 입력을 하는 경우에 출력 의 중간에 입력의 반향이 나타나는 상황을 초래 한다. 다른 시스템의 경 우에는 프로그램이 입력 을 읽을 준비를 갖출 때까지 반향이 억제된다.


8.2.4. 규준모드, 라이편집, 특수문자.

UNIX 구동기는 단순하고 라인 중심이며 대화식인 용도에 특별히 맞춘 작동 모 드를 제공한다. 이는 규준 모드(canonical mode)라 하며 쉘이나 ed 편집기, 그 리고 기타 유사한 프로그램에서 많이 사용된다. 규준모드에서 사용되는 편집 키 중 가장 친숙한 것은 erase 문자이다. 이를 누르면 해당 라인의 앞서의 문 자가 지워진다.

$whp<erase>o

쉘 수준에서 이를 정하는 방법은 stty 를 사용하는 것이다.

$stty erase "^h"

는 erase 문자로서 CTRL-H 를 지정한다. 이는 backspase 의 또 다른 이름이 다. 이때 CTRL-H 문자를 직접 입력하지 않아도 문자열 '^h' 를 사용하면 된다 는 사실에 주목하라. erase, kill, 또는 eof 문자의 특수 의미를 제거하려면 바로 앞에 역 슬래쉬 문자()를 사용한다. 이 경우에는 특수 문자에 결부된 기 능이 수행되지 않고 문자 자체가 프로그램으 로 전달된다.

aa<erase>b<erase>c

은 단말기에서 입력을 얻는 프로그랩에게 aa<erase>c 로 전달된다.


8.3. 프로그램의 관점

지금부터는 단말기를 사용하는 프로그램의 관점에서 살펴보자.


8.3.1. open 시스템 호출

open 은 보통의 디스크 화일과 매우 유사한 방법으로 단말기를 개방하는데 사 용된다.

fd = open("/dev/tty0a", O_RDWR);


8.3.2. read 시스템 호출

화일에 접근하는 기본 호출 중에서 단말기 특수 화일을 사용하는 경우에 가장 큰 영향을 받는 것이 read 시스템 호출이다. read 호출은 항상 newline 이 입 력되어야 끝난다.

nread = read(0, buffer, 256);


8.3.3. write 시스템 호출

이는 단말기와 함깨 사용되는 한 매우 간단하게 수행된다. 단지 제한접이란 해당 단말기의 출력 큐가 모두 차 있는 경우에 write 가 블럭되는 것 뿐이다. 프로그램의 수행이 재개되려면 큐에 남아있는 문자들이 일정수준 이하로 빠져 나가야 한다.


8.3.4. ttyname 과 isatty

ttyname 은 개방된 단말기 화일 기술어의 해당 단말기 장치에 대한 이름을 돌 려준다. isatty 는 화일 기술어가 단말기 장치의 것이면 1 을, 아니면 0 을 돌 려준다.

int filedes, bool;

char *name, *ttyname();

.

.

name = ttyname(filedes);

bool = isatty(filedes);

두기지 경우 모두 filedes 는 개방된 화일 기술어를 나타낸다. filedes 가 단 말기를 나타내지 않으면 ttynam 은 NULL 을 돌려준다.


8.3.5. termio 구조를 사용하여 단말기 특성을 변경하는 법

쉘에서는 단말기의 특성을 변경하기 위하여 stty 명령을 사용했다. 프로그램 에서는 ioctl 시스템 호출과 termino 구조를 사용하여 같은 일을 할 수 있다.

- 생략


8.3.6. MIN 과 TIME 인수

인수 MIN 과 TIME 은 플래그 ICANON 이 0 일때만 효력을 갖는다. 이들은 자료 의 입력, 즉 read 호출에 대한 프로그의 제어권을 정밀하게 조정한다. MIN 은 단말기에 대한 read 호출이 끝나기 전에 단말기 구동기가 받아 들여야 하는 문 자의 최소 갯수를 나타낸다. TIME 는 또 다른 제어권으로서 시간 종료의 기간 을 나타 낸다.

MIN 과 TIME 의 값에는 다음의 4 가지 경우가 있을 수 있다.

1. MIN 과 TIME 모두 0 일때.

이 때는 read 호출이 항상 즉시 끝난다. 해당 단말기의 입력 큐에 문자가 들 어 있으면(입력은 아무때고 도착한다는 것을 상기하라), 프로그램 버퍼에 옮 겨진다.

2. MIN 은 0 보다 코고 TIME 은 0 과 같을때.

이 경우에는 타이머가 아무 역할도 하지 않는다. read 호출이 끝마쳐 지려면 읽혀지길 기다리는 문자가 MIN 만큼은 있어야 한다.

3. MIN 은 0 이고 TIME 은 0 보다 클때.

이 경우 MIN 은 아무일도 하지 못한다. 타이머는 read 가 호출되는 순간부터 작동한다.

4. MIN 과 TIME 모두 0 보다 큰 경우.

이 경우가 가장 유용하고도 유연성이 있다. 타이머가 작동되는 순간은 이제 는 read 가 호출된 순간이 아니라 첫번째 문자가 들어왔을 때이다. 시간이 종료되면 그 순간까지 입력 큐 에 있는 문자들만이 프로그램에 전달된다.


8.3.7. ioctl 시스템 호출

ioctl 은 문자형 특수 화일이 나타내는 외부장치를 제어하기 위한 다목적 호 출로서 인수의 유형이 매우 다양하다. 단말기에 대한 ioctl 호출은 크게 기본 호출과 추가호출의 두가지 유형으로 분리된다. 기본호출의 사용법은 다음과 같다.

#include <termio.h>

struct termio targ;

int ttyfd, cmd, retval;

.

.

retval = ioctl(ttyfd, cmd, &targ);

ttyfd 는 단말기에 대한 개방된 화일 기술어라야 한다. targ 는 termio 구조 의 변섭 단말기의 상태를 저장한다. 인수 cmd 는 ioctl 의 수행양식을 지정하 며 termio.h 에 정의된 바와 같이 다음과 같은 값을 가질 수 있다.

TCGETA

이는 icctl 이 단말기의 현재상태를 targ 에 복사하게 한다.

TCSETA

TCGETA 의 역으로서, targ 에 있는 값으로 단말기의 상태를 지정한다.

TCSETAW

이는 TCSETA 와 같은 기능을 하지만 새로운 값을 지정하기전에 현재의 출력 큐가 비워질 때까지 기다린다.

TCSETAF

이는 TCSETA 의 또 다른 형태로서 출력 큐가 비워질 때까지 기다린 후에 입 력 큐를 청소하고 난 다음에야 단말기의 상태를 targ 의 값으로 지정한다.

이는 기본 호출과 추가 호출 경우에 모두 적용된다. 추가적인 ioctl 호출은 프로그래머에게 단말기 구동기가 관리하는 입출력 큐에 대한 어느 정도의 제어 권을 부여한다.

#include <termio.h>

int ttyfd, cmd, arg, retval;

.

.

retval = ioctl(ttyfd, cmd, arg);

여기서 ttyfd 와 cmd 의 의미는 앞에서의 경우 동일하다. arg 는 정수형로 추 가적인 정보를 전달한다. 추가 ioctl 호출에서 사용할 수 있는 cmd 의 값은 다 음과 같다.

TCFLSH

arg 가 0 이면 입력 큐를 청소한다. 즉 입력 큐의 모든 문자를 무시한다. arg 가 1 이면 큐를 청소한다. arg 가 2 이면 입출력 큐를 모두 청소한다.

TCXONC

이것은 단말기 구동기에 대한 중단/재개를 제어한다. arg 가 0 면 출력이 중단된다. 중단된 출력을 재개하려면 arg 값을 1 로 해서 ioctl 을 다시 호 출해야 한다.

TCBRK

이것은 break 를 전송하는 데 사용된다. 이것은 1/4 초동안 0 비트들을 전 송하는 것과 같다. 이 기능은 출력 큐가 빈 다음에만 사용되어야 한다.


8.3.8. 단절 시그널

6 장에서 프로세스 그룹 리더가 제어 단말기를 갖고 있을 때 수행이 종료되면 해당 프로세스그룹의 구성원들에게 단절 시그널 SIGHUP 이 전달됨을 보았다. 여기에는 또 다른 용도가 있어서, 컴퓨터와 단말기 사이의 접속이 끊어져서 단 말기 라인의 반송파가 검출되지 않는 상황을 대변 할 수 있다. 이러한 상황은 단말기가 전화선이나 지역 네트워크를 사용하여 연결된 경우에 발생할 수 있 다. 이런 상황이 발생하면, 단말기 구동기는 해당 단말기를 제어 단말기로 하 는 모든 프로세스에게 SIGHUP 을 보낸다. (SIGINT와는 달리 SIGHUP 은 쉘을 정 상적으로 로그아웃되게 한다.) 보통의 경우 프로그래머는 SIGHUP 에 손대지 말 아야 한다. 이는 훌륭한 안전장치를 제공한다. 그러나 때에 따라서는 프로그램 이 이를 포착하여 마무리 작업을 수행해야 할 필요성이 있을 수 있다.


8.4. connect 의 예

connect 는 통신 프로그램으로서 두가지 작동모드를 갖는다. 하나는 사용자가 두 개의 UNIX 컴퓨터를 연결하게 해준다. 다른 하나는 두 컴퓨터 간에 텍스트 화일을 전송하게 해준다. 우선 헤더화일 connect.h 를 살펴보자. 이 화일은 다 시 termio.h 와 같은 시스템 헤더 화일을 포함한다. 이외에도 다수의 상수가 정의 되어 있다. 이 중에서 STARTCOM 이란 문자열은 현지 컴퓨터에서 원격 컴 퓨터로의 자료전달을 초기화 하는데 사용된다.

/* header file for connect program */

#include <stdio.h>

#include <fcntl.h>

#include <termio.h>

#include <signal.h>

#include <setjmp.h>

#define ERROR (-1)

#define SUCCESS 0

#define TRUE 1

#define FALSE 0

#define SENDFILE 1

#define EXIT 2

#define MAXTIME 30 /*1/10ths sec*/

#define STARTCOM

"mesg n; stty -opost; cat -> %s; stty

opost; mesg y"

#define CTRLD 004 /*Control-D*/

#define CTRLP 020 /*Control-P*/

#define CTRLY 031 /*Control-y*/

connect 프로그램을 호출 하려면 다음과 같이 명령을 준다.

$connect term-name speed

예를 들어서

$connect /dev/ttya 9600

main 에서 호출하는 함수로는 표준시스템 호출 과 라이브러리루틴을 제외하 면, getspeed, ttyopen, cfatal, 그리고 connect 등이 있다. 함수 getspeed 는 명령어 라인 인수의 두 번째 것을 ioctl 에 적합한 형태로 변환한다. ttyopen 은 앞서 소개했듯이 단말기를 개방하는 함수이다(ttyopen 을 수정하여 connect.h 에 정의된 상수들을 이용하는 것도 고려해보라). 함수 cfatal 은 오 류메시지를 출력하고 프로그램의 수행을 중단시킨다. connect 는 main 이 호출 하는 마지막 함수이다. 여기서는 두개의 자식 프로세스를 생성하여 화일 전송 을 담당하 는 중추적 역할을 한다. 함수 connect 는 두개의 인수가 있는데 하 나는 ttyopen 이 개방한 화일기술어이고, 다른 하나 는 getspeed 가 변환해 준 전송속도이다. 임무 는 프로세스를 두개 생성하여 부모 프로세스는 원격 시스 템에서 오는 문자들을 전달하게 하고 자식 프로세스는 원격 시스템에서 오는 문자들 을 받아들이게 하는 것이다. 필요한 경우에는 원격 시스템으로 화일 전 송을 개시할 수도 있다. 여기서 명심할 사실이 있다. 하나는 자식 프로세스가 수행하는 from 함수는 절대는 끝나지 않는다는 것이다. 따라서 부모 프로세스 가 kill 함수를 호출해야 한다. 다른 하나는 함수 to 가 돌려주는 변수 status 는 사용자가 입력 하는 명령어에 해당한다는 것이다. SENDFILE 과 EXIT 가 정 의된 곳은 물론 connect.h 이다.


8.5. 과거

UNIX 버전 7 과 같은 경우에는 termio 구조는 존재하지 않았다. 대신에 단말 기의 상태를 기술하기 위해서 보다 간다한 구조 sgttyb 를 사용했다. 이 구조 의 자료형을 사용했을때는 ioctl 의 기능이 좀 더 제한되었었다. 단말기의 특 성은 gtty 라는 시스템 호출에 의해 얻어지고 stty 에 의해 지정되었다.


8.6. 그리고 미래

스트림이란 본질적으로는 프로세싱 모듈의 선형 구조로서 쉘의 파이프 라인과 유사하지만 모듈 사이의 자료전달이 양방향으로 모두 될 수 있는 점이 다르다. 스트림의 한쪽 끝은 사용자 프로세스에게 연결되어 있고 다른쪽 끝은 스트립을 위한 장치 구동기에 연결된다. 사용자 프로세스에게 가장 가까운 모듈은 스트 림에 대한 프로그래머의 인터페이스 역할을 한다. 사용자 프로세스가 write 를 호출하면 이는 스트림에 전달되는 메시지로 변환되고, read 를 호출되면 가장 가까운 모듈에서 자료를 읽어 들이게 된다. 일관성을 유지하기 위해서 제어 정 보도 모듈간의 메시지와 같은 형태로 전달된다. 스트림이 open 호출을 통해 개 방되면 두개의 말단 모듈이 자동적으로 연결된다. 이것이 스트림의 가장 기본 적인 형태이며 프로그래머에게 매우 원시적인 수준의 인터페이스를 제공한다. 추가적인 모듈이 스트림에 삽입되려면 확장된 형태의 ioctl 을 호출해야 한 다.(단말기가 최초에 매우 원시적 상태로 개방된다는 사실은 사용자 수준의 코 드를 약간 수정하게 만든다.)


제9장. 표준 I/O 라이브러리

9.1. 서론

우선 Dennis Ritche 가 최초로 개발하고, 모든 UNIX 시스템에서 제공되는 C

라이브러리의 중요한 부분인 표준 I/O 라이브러리를 살펴본다. 표준 I/O 룬틴

들은 UNIX 의 특정한 특성에 매어 있지 않고 C 언어를 위한 ANSI 표준의 일부

분이 된다는 의미에서 이식가능하다. 제대로 된 C 컴파일러라면 운영체제에 관

계없이 표준 I/O 라이브러리의 완전한 구현을 이용할 수 있게 할 것이다. 앞으

로 라이브러리의 UNIX 구현과 특성, 시스템 프로그래머와 소프트웨어 개발자를

위한 유용성에 대하여 살펴보자.


9.2. 스트림과 FILE 구조

표준 I/O 루틴들은 스트림이라고 하는 엔티티(entity)를 통해 화일에 접근한 다. 본질적으로 하나의 스트림은 프로그램과 개방 화일사이의 자료의 흐름이 다. 프로그램내에서 스트림은 FILE 형의 구조에 대한 포인터에 의해 식별된다. FILE 의 정의는 표준 헤더 화일 stdio.h 에서 찾을 수 있다.

typedef struct _iobuf {

int _cnt;

unsigned char *_ptr;

unsigned char *_base;

char _flag;

char _file;

} FILE;

_file 은 char 로서 선언되었으나 실제로는 '작은' 한 바이트 정수로서 취급 된다. 이것은 FILE 구조에 의해 식별되는 스트림이 화일과 연결될때 화일 기술 어를 나타낼 것이다. 이것은 표준 I/O 가 궁극적으로 화일 접근 프리미티브 open, read, write, close 등을 사용한다는 사실을 명시적으로 나타내는 것이 다. 그것은 표준 I/O 라이브러리에 의한 내부적 사용을 위한 것이다. _flag 는 스트림을 위한 제어정보를 포함한다. 예를들어 이것은 표준 I/O 라이브러리가 해당 스트림이 쓰기, 읽기 또는 둘다를 위해 개방된 것인지를 나타낸다. _ptr, _cnt 와 _base 는 개방 스트림과 관련된 문자 버퍼를 나타낸다. _base 는 버퍼 의 시작점 을 가리킨다. _ptr 은 처리를 위해 사용가능한 버퍼의 다음 문자를 가리키고, 정수 _cnt 는 _ptr 가 나타내는 위치 이후에 버퍼에 남아있는 문자 의 수를 나타낸다. 버퍼의 크기는 BUFSIZ 바이트가 된다. BUFSIZ 자체는 stdio.h 에 정의 되어 있고, 2 장에서 보았듯이 호스트환경을 위한 디스크 블 럭킹 요소를 정해준다. 전형적인 값은 512 와 1024 등이다.


9.3. 스트림을 열고 닫기 : fopen 과 fclose

#include <stdio.h>

FILE *stream;

char *filename, *type;

int retval;

.

.

stream = fopen(filename, type);

retval = fclose(stream);

fopen 과 fclose 는 open 과 close 에 해당하는 표준 I/O 라이브러리이다. fopen 루틴은 filename 에 의해 식별되는 화일을 개방하고, 하나의 filename 과 관련되게 한다. 성공적으로 수행되면 fopen 은 개방스트림을 식별하기 위한 FILE 구조에 대한 포인터를 돌려준다. fclose 는 화일과 stream 에 의해 식별 된 스트림을 닫고, 스트림이 출력 스트림이면 그 스트림의 버퍼에 남아있는 모 든 자료를 출력한다. fopen 의 두번째 인수는 접근의 모드를 결정하는 문자열 이다. 이것은 다음의 기본적인 값을 가질 수 있다.

r fopen 은 읽기 전용으로 개방한다.(만약 화일이 존재하지 않으면 fopen 호출은 실패하고 NULL 을 돌려준다.)

w filename 을 생성하거나 절단(truncate)하고, 쓰기 전용으로 개방한다. a 읽기 전용으로 filename 을 개방하고 쓰여지는 모든 자료는 자동적으로 그 화일의 끝에 첨가된다. 화일이 존재하지 않으면 쓰기를 위해 화일을 생성한다.

하나의 화일은 갱신을 위해 개방될 수도 있다. 이것은 그 화일에 쓰기와 모두 할 수 있다는 것을 의미한다. 갱신모드는 open 에 전달되는 type 매개 변수에 추가적인 '+' 기호를 사용하여 나타내어진다.

r+ 갱신을 위해 filename 을 개방한다. 그 화일이 존재하지 않으면 fopen 은 실패한다.

w+ filename 을 생성 또는 절단한다. 그리고 그 화일을 갱신을 위해 개방한 다.

a+ 갱신을 위해 개방한다. 자료는 화일의 끝에 첨가된다. 횬舅 존재하지 않 으면 화일은 쓰기를 위해 생성된다.


9.4. 단일문자 I/O : getc 와 putc

#include <stdio.h>

FILE *inf, *outf;

int c;

.

.

c = getc(inf);

putc(c, outf);

표준 I/O 라이브러리에 의해 제공되는 가장 간단한 입력과 출력 루틴은 getc 와 putc 이다. getc 루틴은 입력 스트림 inf 로 부터 다음 문자(좀더 적당하게 말하면 다음 바이트)를 돌려준다. putc 는 outf 스트림에 하나의 문자를 출력

한다.


9.5. 스트림에 문자들을 되돌리기 : ungetc

#include <stdio.h>

FILE *stream;

int c, retval;

.

.

retval = ungetc(c, stream);

ungetc 는 입력 스트림에 문자 c 를 되돌려 준다. 이것은 논리적인 연산이고 입력화일 자체 는 변경되지 않는다. 한번에 오직 하나의 문자 만을 되돌릴 수 있다.


9.6. 표준 입력, 표준 출력 과 표준 오류

표준 I/O 라이브러리는 표준 입력, 표준 출력과 표준 오류에 연결된 세가지 스트림을 제공한다. 이러한 표준 스트림들은 개방될 필요가 없다. 다음의 FILE 포인터에 의해 식별된다.

stdin 표준입력의 FILE 포인터

stdout 표준출력의 FILE 포인터

stderr 표준오류의 FILE 포인터

다음 문장은 stdin 에서 다음 문자를 가져온다.

inchar = getc(stdin);

stdin 과 stdout 은 아주 자주 사용되기 때문에 getc 와 putc 의 축약형인 getchar 와 putchar 이 제공된다. getchar 는 stdin 으로 부터 다음 문자를 읽 어오고 putchar 는 stdout 으로 하나의 문자를 보낸다. 함수 getchar 와 putchar 는 매개 변수로서 FILE 포인터를 갖지 않는다.


9.7. 표준 I/O 상태 루틴

스트림으 상태를 알아보기 위한 루틴이 몇가지 있다.

#include <stdio.h>

int retval, fd;

FILE *stream;

.

.

retval = ferror(stream);

retval = feof(stream);

clearerr(stream);

fd = fileno(steam);

ferror 는 이전의 입력 또는 출력 요구 때문에 스트림에 오류가 발생했다면 0 이 아닌 값을 돌려주는 부울(boolean)함수이다.


9.8. 라인 단위 입력과 출력

자료를 라인 단위로 입력하고 출력하기 위한 루틴들은 단일문자 I/O 루틴들과 밀접하게 연관 되어 있다. 기본적인 라인 입력 루틴은 gets 와 fgets 이다.

#include <stdio.h>

char *buf, *retstring;

FILE *inf;

int nsize;

.

.

retstring = gets(buf);

retstring = fgets(buf, nsize, inf);

gets 는 표준 입력 스트림 stdin 으로 부터 일련의 문자들을 읽고 각 문자를 buf 가 가리키는 버퍼에 저장한다. 문자들은 화일의 끝 또는 newline 을 만날 때까지 읽혀지고, newline 은 무시되며 스트링을 형성하기위해 널 문자를 buf 에 넣는다. 오류가 발생하거나 화일의 끝에 도달하고 아무런 문자도 읽혀지지 않으면, NULL 이 돌아온다. fgets 는 gets 의 일반화된 형태이다.

gets 와 fgets 에 반대되는 루틴들은 각각 puts 와 fputs 이다.

#include <stdio.h>

char *string;

FILE *outf;

int retval;

.

.

retval = puts(string);

retval = fputs(string, outf);

puts 는 널 문자를 제외하고 표준 출력 스트림으로 string 에 있는 문자들을 출력한다. fputs 는 outf 가 나타내는 출력 스트림에 string 을 출력한다. 시 스템의 오래된 버전과의 호환성을 보장하기 위해 puts 는 fputs 와는 다르게 개행 문자를 첨가한다. 오류의 경우 두 함수는 EOF 를 돌려준다.


9.9. 이진 입력과 출력 : fread 와 fwrite

#include <stdio.h>

char *buffer;

int size, nitems, result;

FILE *inf, *outf;

.

.

result = fread(buffer, size, nitems, inf);

result = fwrite(buffer, size, nitems, outf);

이진 입력과 출력을 위해 아주 유용한 두개의 루틴이 제공된다. fread 는 inf 에 해당하는 입력 스트림으로부터 nitems 개의 자료를 읽어 들인다. 읽혀지는 바이트들은 문자형 배열 buffer 에 놓여진다. 읽혀진 각 윷少 길이가 size 인 바이트 열로서 나타내어진다. 복귀값 resul 는 성공적으로 읽혀진 자료의 갯수 를 나타낸다. fwrite 는 fread 의 반대이다.


9.10. 무작위화일 접근 : fseek, rewind, ftell

표준 I/O 라이브러리는 프로그래머가 스트림의 화일 포인터 위치를 재조정하 거나 화일 포인터 의 현재 위치를 찾을 수 있도록 하는 무작위 접근 루틴을 제 공한다. 이러한 루틴들은 fseek, rewind 와 ftell 이다.

#include <stdio.h>

FILE *stream;

long offset, position;

int direction, result;

.

.

result = fseek(stream, offset, direction);

rewind(stream);

position = ftell(stream);

fseek 은 하위 수준의 lseek 과 유사하고 스트림과 관련된 화일의 화일 포인 터의 위치를 지정 한다. 그러므로 fseek 은 다음의 입력 또는 출력의 위치를 다시 정의한다. direction 매개변수는 화일에서 새로운 위치가 어디에서부터 계산되는가를 결정한다. direction 이 0 으로 주어지면 화일의 처음에서 부터 새로운 위치를 계산하고, 그 값이 1 이면 현재의 위치가 사용되며, 값이 2 로 주어지면 화일의 끝에서 부터 새로운 위치를 계산한다. offset 매개변수는 이 시작 위치에 더해질 바이트의 수를 나타낸다.rewind(stream) 은 fseek(stream, 0, 0) 의 축약형이다. 즉, rewind 는 읽기-쓰기 포인터를 화일의 처음으로 지 정한다. rewind 는 아무런 값도 돌려주지 않는다(실제로, rewind 는 void 함수 로 정의된다). ftell 은 스트림에서 프로그램의 현재 위치를 돈좋娩. 현재의 위치는 화일의 처음부터 (0 에서 부터 계산)의 바이트 수로 주어진다.


9.11. 형식화된 출력 : printf

#include <stdio.h>

char *fmt, *string;

FILE *outf;

int retval;

/*NB parameters arg1 .. have arbitrary

type */

.

.

retval = printf(fmt, arg1, arg2 ... argn);

retval = fprintf(outf, fmt, arg1, arg2 ..argn);

retval = sprintf(string, fmt, arg1, arg2 ..argn);

이 루틴들은 각각 출력 문자열을 생성하기 위해 임의의 유형의 가변적인 갯수 의 매개변수(arg1, arg2 등)와 형식 지정 문자열 fmt 를 매개변수로 갖는다. 이 출력 문자열은 fmt 에 지정된 형식을 이용하 여 매개변수 arg1 부터 argn 까지의 정보를 표현한다.

정수형 변환(integer conversion)

%d 부호가 있는 정수를 위한 표준변환코드이다. 정수가 음수이면 부호가 자 동적으로 첨가된다.

%u 매개변수는 십진수 형식으로 출력되는 부호없는 정수이다.

%o 매개변수는 부호없는 팔진수 형식으로 출력되는 정수이다.

%x 매개변수는 부호없는 십육진수 형식으로 출력되는 정수이다. 문자 a, b, c, d, e, f 가 부가적인 십육진수 숫자를 나타내기 위해 사용된다. 변환 명세가 %X 로 주어지면 A, B, C, D, E, F 가 사용된다.

%ld 매개변수는 부호있는 long 정수이고 십진수 형식으로 출력된다. 프로그 래머는 %lo, %lu, %lx 와 %lX 을 사용할 수 있다.

부동 소숫점 변환(floating-point conversion)

%f 매개변수는 십진수 형식으로 출력되는 float 또는 double 형이다.

%e 매개변수는 지수형식으로 출력되는 float 또는 double 형이다. 이것은 과 학 응용에 잘 사용된다.

%g 이것은 %e 와 %f 의 혼합이다. 이것은 해당하는 매개변수가 float, double 중 하나라는 것을 나타낸다.

문자열과 문자제어

%c 매개변수는 문자 그대로 출력되는 char 형 이다. 문자에 저장된 숫자값은 정수 변환코드를 사용하여 출력할 수 있다.

%s 해당하는 매개변수는 문자열(즉, 문자형 포인터)로서 취급된다. 이 문자 열 내용이 출력 스트림으로 전달된다. 물론, 문자열은 널 문자로 끝나야 한다.

길이와 정도(precision)의 명시

변환 명세는 매개변수가 출력되는 필드의 최소 문자 길이에 관한 정보와 그 필드는 정확도에 대한 정보를 포함할 수 있다. 정수형 매개변수 의 경우정도는 최소 갯수의 숫자를 나타내고, float 또는 double 매개변수에서는 소수점 다음 에 나타날 숫자의 갯수를 나타낸다. 문자열 매개변수에서는 그 문자열에서 취 할 수 있는 문자들의 최대 갯수를 나타낸다.

%10.5d 는 해당 정수 매개변수를 10 문자 길이로 출력 하는 것을 의미한다.

%.5f 는 해당하는 float 또는 double 매개변수를 5 자리 소수점으로 출력한다.

%10s 는 길이가 최소 10 문자인 필드로 해당 문장열을 출력한다.

%-30s 는 해당 문자열 매개변수가 30 문자의 필드에서 왼쪽부터 출력되는 것을 나 타낸다.

특수부호 

출력 변환 명세는 추가적인 부호에 의해 더욱 복잡해 질수 있다. # (hash 또 는 sharp)이 그 예이다. 이것은 명세의 길이 부분 바로 앞에 나타나야 한다.

int arg;

arg = 0xFF;

printf("In octal, %#on", arg);

는 다음을 출력한다.

In octal, 0377

+ 부호는 숫자가 양수일 때 + 부호가 출력되도록 한다.

float farg;

farg = 57.88;

printf("Value of farg is %-+10.fn");

Value of farg is +57.88

을 출력한다.

sprintf 루틴

sprintf 을 출력 루틴으로 생각하지 마라. sprintf 는 가장 신축성 있는 문 자열 조작과 C 라이브러리에서 일반적인 변환 능력을 제공한다.

/*genkey -- generate key for use in data-base */

/* key will always be 20 chars long */

#include <stdio.h>

char *genkey(buf, suppcode, orderno)

char *buf; /*will hold generated key*/

char *suppcode; /*supplier code*/

long orderno; /*order number*/

{

/*is suppcode valid?*/

if(strlen(suppcode) != 10)

return (NULL);

sprintf(buf, "%s_%.9ld", suppcode,

orderno);

return(buf);

}

genkey 의 다음 호출은

printf("%sn", genkey(buf, "abcedfghij",12));

다음 문자열을 출력한다.

abcedfghij_000000012


9.12. 형식화된 입력 : scanf

#include <stdio.h>

char *fmt, *string;

FILE *inf;

int retval;

/*NB: ptr1... ptrn are all pointers.

*The type of the variable they point

*to is arbitrary.

*/

.

.

retval = scanf(fmt, ptr1, ptr2, .. ptrn);

retval = fscanf(inf, fmt, ptr1, ptr2 ..ptrn);

retval = sscanf(string, fmt, ptr1, ptr2 .. ptrn);

이들 루틴들은 printf 의 루틴들과는 반대되는 것이다. 이들은 모두 스트림으 로부터 입력(sscanf 의 경우는 문자열)을 받아 문자얼 fmt 의 형식 지정에 따 라 입력을 해독하고 결과로 얻어진 자료를 포인터 ptr1, ..., ptrn 이 나타내 는 변수에 지정한다. 스트림에 대한 화일 포인터는 처리된 문자의 갯수만 전진 한다. 일반적으로 scanf 형식지정 문자열은 다음을 포함한다.

1. 공백문자(white-space character) : 공백, 탭, 개행문자, form feed

2. 보통의 비공백문자 : 이것은 입력스트림의 해당문자와 그대로 일치된다.

3. 변환명세 : printf 에 사용되는 명세와 동일하다.

/* demo program for scanf */

#include <stdio.h>

main()

{

int i1, i2;

float flt;

char str1[10], str2[10];

scanf("%2d%2d %f %s %s", &i1, &i2, &flt, str1, str2);

.

.

}


9.13. 표준 I/O 라이브러리를 사용한 프로그램의 수행

표준 I/O 라이브러리에는 프로그램이 다른 프로그램을 수행할 수 있게 해 주 는 루틴이 몇개 있다. 이들 중에서 가장 기초적인 것은 system 이다.

#include <stdio.h>

int retval;

char *comstring;

.

.

retval = system(comstring);

시스템은 comstring 에 담겨진 명령어를 수행한다. 이를 위해서 우선 자식 프 로세스 생성한다. 자식 프로세스는 다시 exec 를 호출하여 표준 UNIX 쉘 (/bin/sh)을 수행하고 comstring 을 입력으로 준다. (SVID 에서 실제로 언급한 것은 명령어 해석기이지만 이도 역시 UNIX 쉘과 유사하게 해동해야 한다.) system 루틴에서 부모 프로세서 wait 를 호출함으로써 주어진 명령어의 수행이 완료된 다음에 수행을 재개하도록 한다. 결과적으로 돌아오는 값 retval 은 쉘 의 종료상태로서, 이 값에 의해 주어진 명령어가 성공했는지 실패했는 지를 알 아낼수 있다. 만약 fork 나 exec 호출이 실패하면 retval 의 값은 -1 이 된다. system 에는 커다란 결점이 있다. 프로그램은 자신이 수행한 명령어로 부터의 출력에 직접 접근할 길이 없다. 이접을 해결하려면 표준 I/O 라이브러리에 있 는 popen 과 pclose 루틴 을 사용해야 한다.

#include <stdio.h>

FILE *strm, *popen();

char *comstring, *typestring;

int retval;

.

.

strm = popen(comstring, type);

retval = pclose(strm);

system 의 경우와 같이 popen 도 자식 프로세스를 생성하여 comstring 이 가 리키는 명령어를 수행게 한다. 그러나 system 과 다른 점은 호 출 프로세스와 명령어 사이에 파이프를 생성하는 것이다. 이때 파이프에 스트림이 대응되게 되고, 이는 strm 에 지정된다. 이때 type 의 값이 "w" 라면 프로그램에서의 출 력이 스트림을 통해 명령어의 표준 입력으로 전달될 수 있다. type 이 "r" 이 라면 반대로 명령어의 표준 입력을 프로그램에서 받아들일 수 있게 된다. popen 으로 개방된 스트림은 항상 pclose 로 폐쇄되어야 한다.


9.14. 기타의 호출

표준 I/O 라이브러리에 있는 그 밖의 함수를 살펴보자.

9.14.1. freopen 과 fdopen

#include <stdio.h>

FILE *oldstream, *newstream;

char *type, *filename;

int filedes;

.

.

newstream = freopen(filename, type, oldstream);

oldstream = fdopen(filedes, type);

freopen 은 oldstream 이 가리키는 스트림을 폐쇄하고 filename 에서오는 입 력을 위해 이를 다시 개방한다. type 은 새로운 스트림을 접근하는 모드를 결 정한다. 두 함수 모두 오류가 발생하면 NULL 을 돌려준다.


9.14.2. 단어 입출력 : getw 와 putw

#include <stdio.h>

int word, res;

FILE *inf, *outf;

.

.

word = getw(inf);

res = putw(word, outf);

이들 두 루틴은 단어(word) 단위로 입출력을 수행한다. 단어란 C 컴파일러가 제공하는 정수 형자료로서 단어의 크기, 그리고 이들 함수의 사용법은 컴퓨터 마다 다르다. getw 는 inf 가 가리키는 스트림의 다음번 단어 (즉 이진 정수) 를 가져온다. putw 는 outf 가 가리키는 스트림에 한 단어를 써 넣는다. 두 함 수 모두 오류 발생시에는 EOF 를 돌려준다.


9.14.3. 버퍼의 제어 : setbuf setvbuf

#include <stdio.h>

FILE *stream;

char buf1[BUFSIZ], buf2[SOMEVALUE];

int type, size, res;

.

.

setbuf(stream, buf);

res = setvbuf(stream, buf2, type, size);

setbuf 는 표준 I/O 라이브러리가 보통때 할당하는 버퍼 대신 buf1 을 사용 하도록 해준다. buf1 의 크기는 stdio.h 에 정의된 상수 BUFSIZ 가 결정한다. setbuf 의 인수로 문자형 포인 NULL 을 전달하면 입출력은 버퍼에 담기지 않는 다. 이 기능은 비정상적으로 종료하는 프로그램에서 버퍼에 있는 자료를 잃어 버릴 우려가 있을 때, 이를 디버깅하기 위해 사용될 수 있다. setvbuf 는 표준 I/O 라이브러리에 새로 첨가된 함수로서 setbuf 보다 더 정밀한 제어를 가능하 게 한다. type 인수는 stream 이 버퍼링되는 방법을 지정한다. 이에 사용할 수 있는 값은 stdio.h 에 다음과 같이 세가지로 정의되어 있다.

_IOFBF

스트림은 완전히 버퍼를 사용할 수 있다. 이는 단말기에 연결되지 않은 모 든 스트림에 대한 기본 값이다. 따라서 자료를 읽고 쓸때는 효율을 극대화 하기 위해 BUFSIZ 개의 바이트를 단위로 한다. _IOLBF 출력은 라인 단위로 버퍼에 담기고 newline문자가 쓰일 때마다 버퍼를 비우 게 된다. 버퍼를 비울 경우는 이 밖에도 버퍼가 모두 찼을 때나 입력이 요 구된 경우이다. 이는 단말기를 위한 기본값이며 대화식 사용에 도움을 주기 위해 고안되었다.

_IOBNF

이때는 입출력에 버퍼를 사용하지 않게 된다. 이 경우 buf2 와 size 는 무 시된다. 이 모드는 오류기록시 유용하다.

type 이나 size 에 부당한 값이 사용되면 setv buf 는 0 이 아닌 값을 돌려줌 에 주의하라. 역으로 0 이 돌아오면 성공을 의미한다.


제10장 화면조작

10.1. 서론

CRT/VDU 단말기의 화면을 다루는 도구들을 소개한다. 화면을 다루는 도구에는 밀접하게 관련된 두개의 C 라이브러리가 있다.

1. curses 이것은 하드웨어에 의존하지 않고 화면을 조작하는 방법이다. 이 라이브러리는 단말기의 하드웨어에 무관한 자료구조인 윈도우(window)를 제공한다. curses라는 이름 역시 최적화 커서 이동 (cursor motion optimization )에서 따온것이다. 이것은 AT&T 시스템 V 인터페이스 정의 (SVID) 의 Issue 2 에 정의되어 있다.

2. terminfo 이 라이브러리는 curses 와는 달리 하드웨어와 밀접한 연관성을 가진 루틴을 제공한다. terminfo 역시 SVID 의 Issue 2 에 정의되어 있다.

curses 와 terminfo 둘다, 현재의 버전은 terminfo 라는 각각의 단말기의 특 성을 기록해 두는 데이타베이스(database)를 사용한다.

/usr/lib/terminfo/<c>/<name>

<name> 은 실제 단말기의 이름이고 <c> 는 <name> 의 첫글자이다.

/usr/lib/terminfo/v/vt100

은 단말기 vt100 의 특성들을 기록해두는 화일이다. curses 와 terminfo 의 루틴들은 먼저 환경 변수(environment variable) TERM 을 보고 단말기의 이름 을 알아낸 다음, terminfo 데이타베이스에서 그 단말기에 대응하는 화일을 찾 는다.

termcap 라이브러리

terminfo 는 AT&T 에서 새로이 만들어 낸 것이다. 어떤 UNIX 시스템에서는 terminfo 대신 termcap 이나 termlib 가 있을 수도 있다. termcap 은 단말기 명세가 /etc/termcap 이라는 화일에 기록되어있다. 새로운 단말기에 대한 명세 를 만들려면 그 화일을 사용자가 편집(edit)하면 된다. 소프트웨어 개발을 쉽 게하기 위해 terminfo 라이브러리는 termcap 라이브러리보다 상위의 호환성을 가진다.


10.2. curses 라이브러리 : 개요

프로그래머는 윈도우라는 자료구조를 통해서 모든 작업을 하게 된다. 윈도우 의 자료형은 WINDOW 인데, 이것은 표준 인크루드 화일(standard include file) 인 curses.h 에 정의가 되어있다. 프로그래머는 newwin 이라는 루틴을 사용해 서 새로운 윈도우를 만들 수 있다. 그리고 전역에서 사용할 수 있는 표준 윈도 우 stdscr 도 있다. curses 에 대해 자세하게 설명하기 전에 두가지 점을 먼저 지적하고 넘어 가려고 한다.

1. curses 의 루틴들이 자동적으로 프로그램과 링크되는 것은 아니다. 따라서 컴파일할 때, 사용자가 링크시켜야 한다.

$cc -o scrnprog scrnprog.c -lcurses

terminfo 에 대해서도 같은 방법으로 링크한다.

2. curses 는 C 언어의 매크로(macro)를 이용해서 정의되는 것이 많다. 따라 서 프로그래머는 그것들을 사용할 때 주의해야 한다. 그리고 컴파일시 예 상치 못한 오류 메시지에 대비해야 한다.


10.3. curses 의 일반적인 구조

모든 curses 프로그램은 기본적인 구조로 구성된다.

#include <curses.h>

main()

{

initscr();

/* main body */

endwin();

exit(0);

}

curses.h 는 curses 루틴을 사용하는 모든 프로그램에 항상 포함(include) 되 어야 한다. 시스템 V 에서는 curses.h 에 헤드화일 terminfo.h 가 자동적으로 포함된다. curses.h 에는 curses 의 자료구조와 중요한 매크로들이 들어있다. initscr 루틴은 모든 curses 함수에 앞서서 수행시켜야 한다. 이 루틴은 curses 의 자료구조를 초기화하고 UNIX 환경의 TERM 변수를 통하여 단말기의 종류를 결정한다. 그렇게 함으로서 원래의 단말기 상태로 돌아갈 수 있고 커서 는 맨 아래 왼쪽에 위치하게 된다.

#include <curses.h>

main()

{

initscr();

refresh();

endwin();

}

refresh 또는 더 일반적인 버전인 wrefresh 를 수행시켜야만 윈도우으 내용이 실제화면 (physical screen) 에 나타나게 된다. refresh 는 표준윈도우 stdscr 의 내용을 실제화면에 표시하고, wrefresh 는 특정 윈도우의 내용을 실제화면 에 표시한다.


10.4. 모드 지정

curses 루틴을 사용하는 프로그램에서는 initscr 을 수행시킨 다음 단말기의 입출력 모드를 지정한다.

echo(); /* enable echoing */

noecho(); /* disable echoing */

단말기의 반향(echo) 기능을 작동시키거나 정지시킬 수 있다. 디폴트 (default)로는 반향 기능을 작동시킨다.

nl(); /* enable CR-NL mappings */

monl(); /* disable mappings */

nl 은 출력시 개행문자(newline) 가 newline/ carriage-return 으로 바뀌어서 출력되거나, 입력시 newline/carriage-return 이 개행문자로 바뀌어서 입력된 다. default 는 nl 이다.

cbreak(); /* enable CBREAK mode */

nocbreak(); /* disable CBREAK mode */

cbreak 는 입력시, 인터럽트(interrupt) 와 흐름 제어 키(flow control key) 를 제외하고는 입력자료에 어떠한 작용도 가하지 않는다.

raw(); /* enable RAW mode */

noraw(); /* disable RAW mode */

raw 는 단말기를 RAW 모드로 만든다. 이 모드는 CBREAK 모드와 비슷한데, 다른것은 신호처리(signal processing)과 회㎲┥低뗌骸 작동중지시킨 상태라는 점이다.

savetty(); /* save tty state */

resetty(); /* reset tty state */

단말기의 상태를 저장하거나 복구시킬 수 있다. savetty 는 현재 단말기 상태 를 curses 의 내부버퍼에 저장해 준다. restty 는 바로 전에 savetty 로 저장 해둔 상태를 복구시킨다.


10.5. 문자와 문자열 쓰기

curses 는 윈도우 stdscr 에 문자와 문자열을 쓰는 4 개의 루틴을 제공한다. addch, mvaddch, addstr, mvaddstr 이다.

#include <curses.h>

int c, y, x;

char *string;

.

.

addch(c);

mvaddch(y, x, c);

addstr(string);

mvaddstr(y, x, string);

addch 루틴은 stdscr 상의 현재 커서 위치에 문자 c 를 쓴다. refresh 를 해 주어야만 실제 화면에 나타난다. CTRL-C 는 ^C 처럼 쓰인다. mvaddch 루틴은 커서를 세로로 y 번째 줄, 가로로 x 번째 열로 움직인 후에 글자를 쓴다. 맨 윗줄 왼쪽이 좌표(0,0)이다. addstr 과 mvaddstr 은 0 으로 끝나는 문자열 string 을 윈도우 stdscr 에 쓴다.


10.6. 형식화된 출력

curses 에는 C 언어의 printf 와 비슷한 기능을 제공하는 루틴들이 있다. 표 준 윈도우 stdscr 에 쓰이는 루틴들로는 printw 와 mvprintw 가 있다.

#include <curses.h>

char *fmt;

int y, x;

/* NB arg0, arg1 ... argn have arbitrary

type */

.

.

printw(fmt, arg0, arg1, ... argn);

mvprintw(y, x, fmt, arg0, arg1, ... argn);


10.7. 커서 이동

move 명령을 사용하여 표준 윈도우 stdscr 위에서 커서를 움직일 수 있다.

#include <curses.h>

int y, x;

.

.

move(y, x);

매개변수 y 와 x 를 사용하여 새로운 좌표에 커서를 위치시킬 수 있다.

refresh 가 수행되어야만 실제 커서 위치가 바뀐다.

getyx 루틴을 사용하여 커서의 현재 위치를 알수 있다.

#include <curses.h>

int y, x;

WINDOW *w;

.

.

getyx 는 WINDOW 에 대한 포인터가 첫번째 변수로 주어져야 한다. 첫번째 변 수로 표준 화면인 stdscr 을 사용하면 표준 화면상의 현재 커서 위치를 얻을 수 있다. 수행 결과는 y 와 x 변수 에 주어진다. getyx 가 실제 C 함수가 아니 라 매크로(macro)이기 때문에 가능한 것이다.


10.8. 키보드 입력 : getch

curses 에서 키보드로 부터 하나의 문자를 받아 들일 경우에 getch 를 사용한 다. getch 는 C 언어의 getc 루틴처럼 정수값을 돌려준다.

#include <curses.h>

int in_ch;

.

.

in_ch = getch();

기능키 입력

처음 해야하는 작업은 curses keypad 루틴을 수행시켜서 단말기의 키패드 (keypad)를 초기화시키는 것이다.

keypad(stdscr, TRUE);

특수키는 curses.h 에 정의된 값을 통해 돌려준다. ASCII 값과의 충돌을 막기 위해 이 값들은 8 진수 401 부터 시작된다.

int in_ch;

.

.

in_ch = getch();

switch(in_ch){

case KEY_DOWN;

/* down arrow key processing */

.

.

case KEY_UP;

/* up arrow key processing */

.

.

}


10.9. 화면 입력 : inch

stdscr 의 특정 위치에 어떤 문자가 있는 지 알아야 될 경우가 있다. inch 는 화면의 현재 커서 위치에 있는 문자를 돌려준다. 그리고 mvinch 는 주어진 위 치의 문자를 돌려준다.

#include <curses.h>

int in_ch;

.

.

in_ch = inch();

in_ch = mvinch(y, x);

화면상의 글자에는 하이라이트 타입(highlight type) 같은 속성(attribute)을 지닌 경우가 있다. 이때 실제 문자만을 얻고 싶으면 curses.h 에서 정의된 상 수 A_CHARTEXT 와 비트단위 AND 를 해야한다.

cvalue = ivalue & A_CHARTEXT;


10.10. 화면 편집

이미 그려진 화면을 변화시켜야 하는 경우가 종종 있다. curses 에는 이러한 경우에 유용 한 여러가지 루틴들이 있다. 이러한 루틴들을 3가지로 구분할 수 있다. 화면을 백지화(clear)시키는 것, 문자들을 지우고 화면을 재구성하는 것, 화면을 전혀 지우는 것없이 문자를 끼워 넣는 것등이 있다.

stdscr 상에서 화면의 일부분을 지우는 루틴

#include <curses.h>

.

.

erase();

clear();

clrtobot();

clrtoeol();

erase 와 clear 은 둘다 표준 윈도우 stdscr의 모든 위치에 공백(space)을 쓴다. 다른점은 clear 는 또한 다음번 refresh 가 호출될 때 화면을 자동적 으로 백지화 시킨다는 점이다. clrtobot 는 현재줄의 커서 오른쪽에 있는 모 든 문자와 커서 아래에 있는 모든 줄을 지운다. clrtoeol 은 현재줄에서 커서 의 오른쪽에 있는 모든 문자를 지운다.

화면상의 문자들을 지울 뿐만 아니라 문자를 지우면서 생긴 공백을 메꾸기 위 해서 화면을 이동시키는 루틴

#include <curses.h>

int y, x;

.

.

delch();

mvdelch(y, x);

deleteln();

delch 의 경우 현재 커서 위치에 있는 문자가 지워진다. 그리고 그 공백을 메꾸기 위해 커서의 오른쪽에 있는 문자들을 한 칸씩 왼쪽으로 이동시킨다. mvdelch 도 기능이 거의 같다. 다만 먼저 커서를 주어진 위치에 이동시킨 다 음 delch 와 같은 기능을 수행한다. deleteln 은 커서가 있는 줄을 지운다. 그 리고 나서 그 줄 밑에 있던 모든 줄을 한 칸씩 위로 이동시킨다.

문자를 끼우는 데 관련된 루틴

#include <curses.h>

int c, y, x;

.

.

insch(c);

mvinsch(y, x, c);

insertln();

insch 는 현재 위치에 문자 c 를 끼운다. 현재 커서 위치의 오른쪽에 있는 글자들은 모두 한 칸씩 오른쪽으로 이동한다. 그 줄의 마지막에 있는 문자는 잃어버리게 된다. mvinsch 는 커서의 위치를 바꾼 다음 insch 와 같은 작용을 한다. insertln 은 커서가 있는 줄의 위에 빈 줄(blank line)을 하나 집어넣는 다. 그 줄 밑에 있는 줄들은 모두 한 줄씩 밑으로 이동한다. 따라서 화면의 맨 밑에 있던 줄은 화면상에서 사라지게 된다.


10.11. 영상 속성

curses 에서는 특정 모드로 글자를 화면에 나타내고 싶으면 그 모드에 해당하 는 상수와 비트단위 OR 하면 된다.

addch(ch|A_BOLD);

는 ch 를 볼드체(bold;주위의 글들보다 더 밝은 모드) 로 나타낸다. 이외에도 여러가지 모드가 있다.

A_STANDOUT 이 모드는 글자를 집중모드로 나타낼때 쓰인다.

A_REVERSE 역전 모드

A_BOLD 글자들이 보울드로 나타난다.

A_DIM 글자들이 기본모드보다 약간 어둡게 나타난다.

A_UNDERLINE 글자 밑에 밑줄이 그어진 형태로 나타난다.

A_BLINK 글자들이 반짝인다.

#include <curses.h>

int atts;

.

.

attrset(atts);

attron(atts);

attroff(atts);

standout();

standend();

attrset 은 표준 화면상에서 모드를 작동시킬때 사용하는 루틴이다. attron 루틴은 atts 에 주어진 모드들을 작동시킨다. 이 루틴은 앞에서 지정된 모드를 바꾸지는 않는다. 마찬가지로 attroff 는 선택된 모드를 해제한다. 그리고 두 루틴 standout 와 standend 는 각각 attron(A_STANDOUT) 와 attroff (A_STANDOUT) 와 동일한 기능을 한다.


10.12. 새 윈도우 화면의 생성과 조작

새로운 윈도우를 다루는 방범을 소개한다. 윈도우를 위한 가장 기본적인 루틴 은 newwin 이다.

#include <curses.h>

WINDOW *win;

int lines, cols, startline, startcol;

.

.

win = newwin(lines, cols, startline, startcol);

이 루틴을 수행시키면 세로 크기가 lines, 가로 크기가 cols 인 윈도우가 생 긴다.

wmove(win, y, x);

이 루틴을 수행하면 윈도우 win 상의 현재 커서 위치가 좌표(x,y)로 바뀌게 된다. 이 좌표는 표준 윈도우 stdscr 표가 아니라 윈도우 win 의 맨윗줄 맨 왼쪽을 기준으로 한 좌표이다.

wrefresh(win);

은 윈도우 win 에 있는 내용들이 실제 화면에 나타난다.


10.13. curses 예 : domenu

-- 생략


10.14. 하드웨어 의존 단말기 조정 : terminfo

terminfo 라이브러리는 프로그래머가 직접 단말기의 하드웨어를 직접 제어하 는 기능을 제공한다. terminfo 데이타베이스에는 각 단말기의 특성을 얻을 수 있다. 가장 크게 문제가 발생하는 경우가 패딩(padding, 속도늦춤)을 할때다. 이단어는 단말기가 프로그램의 속도를 적당히 유지하도록 출력속도를 늦추는 것을 의미한다.

#include <curses.h>

#include <term.h>

main()

{

setupterm(0, 1, 0);

putp(clear_screen);

reset_shell_mode();

exit(0);

}

화면을 백지화시키는 프로그램이다. term.h에는 clear_screen 을 포함하여 여 러가지 기능을 나타내는 매크로들이 정의되어있다. setr-pterm 은 termino 의 초기화루틴이다. putp 을 호출하면 화면을 백지화시키기는 문자열을 화면에 내 보낸다. putp 는 좀더 일반적인 기능을 갖는 루틴 tputs 의 제한된 버전이다. reset_shell_mode 는 단말기를 원래의 상태로 되돌린다.