Unix System Programming 6 - 김성호(moohou) :namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
_________________________________________________________________________
제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 은 제외)에 대한 처리 함수는 시그널이
포착된 후 즉시 재지정된다.
#include <signal.h>
int interrupt()
{
printf("Interrupt called\n");
sleep(10);
}
main()
{
signal(SIGINT, interrupt);
printf("Interrupt set for SIGINT\N");
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 을 호
출하면 취소된다.
alarm(0);
6.2.7. pause 시스템 호출
UNIX 는 alarm 과 같은 부류인 pause 시스템 호출을 제공한다.
int retval;
retval = pause();
pause 를 호출한 프로세스는 SIGALRM 등의 시그널이 도착할 때까지 시스템 자
원을 낭비하지 않도록 수행이 중지된다.
6.2.8. setjmp 와 longjmp
때때로 시그널을 받았을 때 프로그램을 이전의 상태로 복구햐여야 할 떼가 있
다. 예를 들어 사용자가 인터럽트 키를 치면 프로그램의 주메뉴로 돌아가게 해
야 한다고 하자. 이것은 두개의 특수한 서브루틴 setjmp 와 longjmp 를 이용하
여 처리될 수 있다. setjmp 는 프로그램의 현재 상태를 저장하고 (사실 스택의
내용을 저장한다). longjmp 는 저장된 상태를 다시 복구한다.
#include <signal.h>
#include <setjmp.h>
#include <stdio.h>
jmp_buf position;
main()
{
int goback();
.
.
setjmp(position);
signal(SIGINT, goback);
domenu();
.
.
}
goback()
{
signal(SIGINT, SIG_IGN);
fprintf(stderr, "\nInterrupted\n");
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(1);
dup(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 역시 블럭되지 않는다.'Academy I > Tech Academy' 카테고리의 다른 글
Unix System Programming 10 (0) | 2014.12.16 |
---|---|
Unix System Programming 9 (0) | 2014.12.16 |
Unix System Programming 8 (0) | 2014.12.16 |
Unix System Programming 7 (0) | 2014.12.16 |
Unix System Programming 5 (0) | 2014.12.16 |
Unix System Programming 4 (0) | 2014.12.16 |
Unix System Programming 3 (0) | 2014.12.16 |
Unix System Programming 2 (0) | 2014.12.16 |