익명 사용자
로그인하지 않음
계정 만들기
로그인
youngwiki
검색
Signal 문서 원본 보기
youngwiki
이름공간
문서
토론
더 보기
더 보기
문서 행위
읽기
원본 보기
역사
←
Signal
문서 편집 권한이 없습니다. 다음 이유를 확인해주세요:
요청한 명령은 다음 권한을 가진 사용자에게 제한됩니다:
사용자
.
문서의 원본을 보거나 복사할 수 있습니다.
상위 문서: [[Signals and Nonlocal jumps]] ==개요== signal이란 시스템에서 어떤 종류의 이벤트가 발생했음을 프로세스에 알리는 작은 메시지이다. signal은 이를 위해서 kernel에서 process로 보내지며, signal type은 1~30의 정수로 식별된다. 이때 어떤 signal에 저장된 정보는 해당 ID와 그 signal이 도착했다는 사실 뿐이다. 아래는 몇가지 signal ID와 그에 대응되는 signal들을 나타낸 표이다. {| class="wikitable" |+ !ID !Name !Default Action !Corresponding Event |- |2 |SIGINT |Terminate |User typed ctrl-c |- |9 |SIGKILL |Terminate |Kill program(cannot override or ignore) |- |11 |SIGSEGV |Terminate |Segmentation violation |- |14 |SIGALRM |Terminate |Timer Signal |- |17 |SIGCHLD |Ignore |Child stopped or terminated |} ==Signal Concepts== [[파일:SignalHandling.png|섬네일|400x400픽셀|signal handler가 signal을 캐치하는 기본적인 흐름]] 시그널이 대상 프로세스로 전달되는 과정은 다음 두 가지의 명확한 단계로 이루어진다. # Sending a signal #* 커널은 destination 프로세스의 context 일부 상태를 업데이트해 해당 프로세스에 시그널을 보낸다. 다음은 signal이 전달되는 경우이다. #*# 커널이 0으로 나누기 오류나 자식 프로세스의 종료와 같은 시스템 이벤트를 감지했을 때 #*# 한 프로세스가 kill 함수를 호출하여 명시적으로 커널에게 대상 프로세스에 signal을 보내도록 요청했을 때 #* 프로세스는 자기 자신에게 시그널을 보낼 수도 있다. # Receiving a signal #* destination 프로세스는 커널이 해당 시그널의 전달에 반응하도록 강제할 때 시그널을 받는다. 프로세스는 시그널에 대해 다음 세 가지 중 하나의 반응을 할 수 있다: #*#signal을 무시(Ignore) #*# 프로세스를 종료(Terminate) #*# 사용자 수준 함수인 signal handler을 실행하여 시그널을 catch하여 처리 #Pending and Blocked Signal #* Pending signal은 전송되었지만 수신이 완료되지 않은 signal을 의미한다. #** 특정한 유형의 pending signal은 최대 하나만 존재할 수 있다. 만약 프로세스가 특정 유형(k)의 pending signal을 가지고 있다면, 그 프로세스에 대해 이후에 보내지는 동일한 유형(k)의 signal는 대기열에 쌓이지 않고 단순히 버려진다. #* 프로세스는 특정한 유형의 signal 수신을 block할 수 있다. #** 이때 signal이 전달될 수는 있으나, 해당 signal은 pending signal이 되어 프로세스가 해당 유형의 signal을 unblock하기 전까지는 수신되지 않는다. #* 커널은 각 프로세스의 context에서 pending signal과 blocked signal에 대한 bit vector를 가지고 있다. #** pending: pending signal들의 집합을 나타내는 bit vector이다. #*** 커널은 유형이 k인 signal이 전달되면 pending vector에 k bit를 설정한다. #*** 커널은 유형이 k인 signal이 수신되면 pending vector에서 k bit를 지운다. #** blocked: block된 signal들의 집합을 나타내는 bit vector이다. #*** signal mask라고도 불린다. #*** sigprocmask 함수를 통해서 특정 유형의 signal을 block하거나 unblock할 수 있다. ==Sending Signal== ===Process Group=== [[파일:ProcessGroup.png|섬네일|400x400픽셀|Process group example]] 모든 프로세스는 단 하나의 process group(프로세스 그룹)에 속한다. 이 그룹은 양의 정수인 process group ID로 식별된다. 현재 프로세스의 process group ID를 알기 위해서는 이를 반환하는 getpgrp()함수를 사용할 수 있다. <syntaxhighlight lang="c"> #include <unistd.h> pid_t getpgrp(void); </syntaxhighlight> 기본적으로 child process는 parent process와 동일한 프로세스 그룹에 속한다. 이때 프로세스는 setpgid() 함수를 사용하여 자신이나 다른 프로세스의 프로세스 그룹을 변경할 수 있다. <syntaxhighlight lang="c"> #include <unistd.h> int setpgid(pid_t pid, pid_t pgid); </syntaxhighlight> setpgid 함수는 프로세스 pid의 프로세스 그룹의 ID를 pgid로 변경한다. 만약 pid가 0이면, 현재 프로세스의 PID가 사용된다. pgid가 0이면, pid로 지정된 프로세스의 PID가 프로세스 그룹 ID로 사용됩니다. 다음은 PID가 15213인 프로세스에서 setpgid()함수를 호출한 예시이다. <syntaxhighlight lang="c"> setpgid(0, 0); </syntaxhighlight> 위 함수의 실행은 process group ID가 15213인 프로세스 그룹이 이미 존재한다면 PID가 15213인 프로세스를 해당 그룹에 추가한다. 만약 process group ID가 15213인 프로세스 그룹이 존재하지 않는다면 그러한 프로세스 그룹을 만들고 ID가 15213인 프로세스를 해당 그룹에 추가한다. ===/Bin/kill 프로그램을 사용해 signal 보내기=== /bin/kill 프로그램은 다른 프로세스나 프로세스 그룹에 임의의 신호를 보낸다. 예를 들어, <syntaxhighlight lang="perl"> linux> /bin/kill -9 15213 </syntaxhighlight> 위 명령은 process 15213에 signal -9(SIGKILL)을 보낸다. 이때 PID로 음수를 사용하면 해당 process group 15213에 존재하는 모든 프로세스에 해당 signal을 보낸다. 예를 들어, <syntaxhighlight lang="perl"> linux> /bin/kill -9 -15213 </syntaxhighlight> 위 명령은 process group 15213에 있는 모든 프로세스에 SIGKILL 신호를 보낸다. ===키보드로 signal 보내기=== [[파일:ProcessGroup.png|Process group example|섬네일|300x300픽셀]]Unix shell은 하나의 명령어를 evaluate해 생성된 process를 job이라는 abstraction으로 나타낸다. 이때 어떠한 시점에서든 하나의 foreground job과 여러 background job이 존재할 수 있다. 예를 들어, <syntaxhighlight lang="shell"> linux> ls | sort </syntaxhighlight> 이 명령은 Unix pipe<ref>두 개의 명령어를 연결하여, 첫 번째 명령어의 출력 결과를 두 번째 명령어의 입력으로 전달하는 메커니즘이다.</ref>를 통해 연결된 두 개의 process로 구성된 foreground job<ref>알파벳 순으로 정렬된 파일 목록을 얻는 job이다.</ref>을 만든다. 두 프로세스 중 하나는 ls 프로그램<ref>현재 디렉토리의 파일과 디렉토리 목록을 출력하는 명령어이다.</ref>을 실행하고, 다른 하나는 sort 프로그램<ref>sort는 정렬을 의미하는 명령어로, 텍스트 데이터를 정렬하는 데 사용한다.</ref>을 실행한다. Shell은 각 job마다 별도의 프로세스 그룹을 생성하며, 이때의 process group ID는 job 내의 parent PID 중 하나에서 따온다. 예를 들어 오른쪽의 그림은 하나의 foreground job과 두개의 background job을 가진 shell을 보여준다. 전경 잡의 부모 프로세스는 PID가 20이고, process group ID도 20이다. 부모 프로세스는 두 개의 자식 프로세스를 생성했으며, 이들 또한 프로세스 그룹 20에 속한다. 키보드에서 Ctrl+C를 누르면 커널은 foreground group에 있는 모든 프로세스에 SIGINT<ref> * SIGINT: 프로세스가 종료를 거부하거나 신호를 처리할 수 있는 기회를 제공한다. 기본적으로 사용자가 인터럽트 키인 Ctrl+C를 눌렀을 때 발생한다. * SIGKILL: 프로세스를 즉시 강제 종료시키는 신호이다. 프로세스가 이를 거부하거나 처리할 수 없으며 무조건 종료된다. </ref> 신호를 보낸다. 즉, foreground job을 종료(terminate)한다. 마찬가지로 Ctrl+Z를 누르면 커널은 foreground 프로세스 그룹에 있는 모든 프로세스에 SIGTSTP 신호를 보낸다. 즉, foreground job을 suspend한다. ====키보드로 signal 보내기 예시==== 아래는 Ctrl+C와 Ctrl+Z의 예시이다. <syntaxhighlight lang="shell"> bluefish> ./forks 17 Child: pid=28108 pgrp=28107 Parent: pid=28107 pgrp=28107 <types ctrl-z> Suspended bluefish> ps w PID TTY STAT TIME COMMAND 27699 pts/8 Ss 0:00 -tcsh 28107 pts/8 T 0:01 ./forks 17 28108 pts/8 T 0:01 ./forks 17 28109 pts/8 R+ 0:00 ps w bluefish> fg ./forks 17 <types ctrl-c> bluefish> ps w PID TTY STAT TIME COMMAND 27699 pts/8 Ss 0:00 -tcsh 28110 pts/8 R+ 0:00 ps w </syntaxhighlight> 위에서 STAT은 다음과 같이 표현된다. * First letter: ** S: sleeping ** T: stopped **R: running * Second letter: ** s: session<ref>사용자와 그 사용자에 의해 시작된 여러 프로세스를 관리하는 논리적인 단위이다.</ref> leader<ref>session을 생성하는 첫번째 프로세스이며, 해당 session 내 다른 프로세스들을 관리하는 역할을 한다.</ref> ** +: foreground proc group ===kill 함수를 통해 signal 보내기=== 프로세스는 kill 함수를 호출하여 프로세스(자기 자신을 포함)에게 signal을 보낼 수 있다. <syntaxhighlight lang="c"> #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); </syntaxhighlight> * pid > 0: kill 함수는 signal number가 sig인 signal을 process pid에 보낸다. * pid = 0: kill 함수는 호출 프로세스가 속한 프로세스 그룹에 있는 모든 프로세스(호출 프로세스 자신 포함)에게 signal number가 sig인 signal을 보낸다. * pid < 0: kill 함수는 process group |pid|(pid의 절대값)에 속한 모든 프로세스에게 signal number가 sig인 signal을 보낸다. <syntaxhighlight lang="c"> void fork12() { pid_t pid[N]; //자식 프로세스의 PID를 저장하는 배열 int i; int child_status; //자식 프로세스 생성 for (i = 0; i < N; i++) { if ((pid[i] = fork()) == 0) { while(1); //자식 프로세스는 무한 루프를 유지한다. } } for (i = 0; i < N; i++) { printf("Killing process %d\n", pid[i]); kill(pid[i], SIGINT); //자식 프로세스에 대한 종료 요청 } for (i = 0; i < N; i++) { pid_t wpid = wait(&child_status); //자식 프로세스의 종료상태를 확인한다. if (WIFEXITED(child_status)) //WIFEXITED(child_status)를 통해 정상적으로 종료되었는지 확인 //정상적으로 종료되었다면 자식 프로세스의 exit status를 출력한다. printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminated abnormally\n", wpid); } } </syntaxhighlight> <syntaxhighlight lang="shell"> > 14-ecf-procs ./forks 12 Killing process 24526 Killing process 24527 Killing process 24528 Killing process 24529 Killing process 24530 Child 24527 terminated abnormally Child 24530 terminated abnormally Child 24529 terminated abnormally Child 24528 terminated abnormally Child 24526 terminated abnormally > 14-ecf-procs </syntaxhighlight> 위는 N을 5로 설정하였을 때<ref>#define N = 5</ref> fork12() 함수를 실행한 예시 결과이다. 이때 child process가 왜 abnormally하게???? ==Receiving Signals== [[파일:ReceivingSignal.png|섬네일|300x300픽셀]] 커널이 프로세스 p를 kernel mode에서 user mode로 전환할 때<ref>system call에서 돌아오거나 context switch를 완료할 때 등</ref>, 커널은 p에 대해 unblocked이고 pending 상태인 signal의 집합을 확인한다.<ref>pnb = pending & ~blocked</ref> 이 집합이 비어 있으면<ref>if (pnb == 0)</ref>, 커널은 p의 logical control flow에서 다음 instruction(I<sub>next</sub>)로 control을 넘긴다. 그러나 이 집합이 비어 있지 않으면, 커널은 집합에서 signal k를 하나 선택하고<ref>일반적으로 가장 작은 signal number k</ref>, p가 signal k를 수신하도록한다. 또한 이를 해당 집합에 있는 모든 signal에 대해 반복한다. signal을 수신하면 해당 프로세스는 특정 작업을 수행한다. 작업을 완료한 후, p의 logical control flow에서 다음 instruction(I<sub>next</sub>)로 control을 넘긴다. 각 signal 유형은 기본 동작이 있으며, 다음은 기본 동작을 모두 나열한 것이다. * 프로세스를 terminate / ex: SIGKILL * 프로세스를 terminate, core dump<ref>프로세스가 종료될 때 해당 프로세스의 메모리, 레지스터 상태 등을 포함한 파일이다. 이 파일을 분석하면, 크래시가 발생한 지점을 추적하거나 오류를 진단할 수 있다.</ref>라는 이름의 파일을 생성 * 프로세스를 SIGCONT signal에 의해 재개될 때까지 suspend(stop) * 프로세스를 signal을 무시하도록 한다.<ref>프로세스가 해당 신호를 수신해도 아무 일도 일어나지 않게 처리된다.</ref> / ex: SIGCHLD<ref>SIGCHLD 신호는 자식 프로세스의 종료나 상태 변경을 알리는 신호이며, 기본적으로 무시된다. 즉, 부모 프로세스는 자식 프로세스가 종료되었을 때 특별히 처리할 필요가 없다면 이 신호를 무시할 수 있다.</ref> 이때 프로세스는 signal 함수를 사용하여 신호와 관련된 기본 동작을 수정할 수 있다. 단, SIGSTOP과 SIGKILL은 기본 동작을 변경할 수 없다. ===Installing Signal Handlers=== signal 함수는 프로세스가 신호를 받았을 때 해당 신호에 대한 기본 동작을 수정하는 기능을 제공한다. 아래는 signal() 함수에 대한 기본 형식과 인자에 대한 설명이다. <syntaxhighlight lang="c"> handler_t *signal(int signum, handler_t *handler); </syntaxhighlight> * signum: 처리하려는 신호의 종류를 지정합니다. ex: SIGINT, SIGSEGV, SIGTERM 등... * handler: 신호를 받았을 때 실행할 핸들러 함수의 주소에 해당한다. 핸들러 함수는 신호에 대한 사용자 정의 동작을 포함하며, 이를 통해 신호를 처리할 방법을 정의한다. ** handler가 SIG_IGN이면 해당 signum 신호는 무시된다. ** handler가 SIG_DFL이면 해당 signum 신호에 대한 동작은 기본 동작으로 되 돌아간다. ** 그 외의 경우에서는, handler는 user-level signal handler의 address이다. *** installing: 해당 handler가 프로세스가 signum 신호를 받을 때 호출되는 것이다. *** catching / handling: handler를 실행하는 것 *** handler가 return문을 실행하면, 보통 control은 signal을 받았을 때 프로세스의 control flow에서 중단된 지점으로 돌아간다. 프로세스가 signal k를 받으면, signal k에 대해 설치된 handler가 k라는 하나의 인자를 전달받으며 호출된다. 이 인수는 동일한 handler 함수가 여러 신호 유형을 처리할 수 있도록 한다. 예를 들어, <syntaxhighlight lang="c"> void sigint_handler(int sig) /* SIGINT handler */ { printf("So you think you can stop the bomb with ctrl-c, do you?\n"); sleep(2); //2초간 대기 printf("Well..."); fflush(stdout); sleep(1); //1초간 대기 printf("OK. :-)\n"); exit(0); //프로그램을 정상적으로 종료 } </syntaxhighlight> <syntaxhighlight lang="c"> int main() { // SIGINT handler를 install if (signal(SIGINT, sigint_handler) == SIG_ERR) unix_error("signal error"); //pause() 함수는 프로세스가 signal을 받을 때까지 멈추도록 한다. pause(); return 0; } </syntaxhighlight> <syntaxhighlight lang="shell"> > ecf-signals ./sigint ^CSo you think you can stop the bomb with ctrl-c, do you? Well...OK. :-) > ecf-signals </syntaxhighlight> 위에서 sigint_handler()라는 이름의 handler에서 sig라는 이름의 정수형 인자는 등록되어 있지만 사용되지는 않았다. handler는 sig와 같은 정수형 인자를 가지고 있는데, 해당 인자에는 전송된 signal의 number가 전달된다. 이를 통해서 동일한 handler를 통해서 여러 signal에 대한 처리를 할 수 있다. ===Nested Signal Handlers=== [[파일:Nested handler.png|섬네일|300x300픽셀|Nested handler]] signal handler는 다른 handler 들에 의해 중단될 수 있다. 이 예제에서는 메인 프로그램이 signal S를 처리하고 있으며, 이를 통해 메인 프로그램을 중단시키고 signal S handler로 control flow를 넘긴다. 또한 S handler가 실행되는 동안 signal t<ref><math>\ne</math>s</ref>를 받아블여 signal T handler로 다시 control flow를 넘긴다. signal T handler가 return한 후 S handler는 중단된 지점에서 실행을 재개하고, signal S handler까지 다시 return한 이후 비로소 메인 프로그램이 재개된다. ===Signals Handlers as Concurrent Flows=== [[파일:ConcurrentSignal.jpg|섬네일|300x300픽셀]] 시그널 핸들러는 별도의 logical flow를 가진다. 즉, 시그널 핸들러는 다른 프로세스와는 separate logical process를 가진다.<ref>그럼에도 프로세스로 취급되지는 않는다.</ref> 메인 프로그램과 concurrently하게 실행된다. 이를 통해 메인 프로그램의 실행이 중단되지 않은 채 signal이 처리될 수 있도록 설계된다. [[파일:SignalContextSwitching.jpg|프레임없음|300x300픽셀]] 위 그림은 signal이 전달되고 수신되는 과정에서 context switching이 발생함을 보여준다. ==Blocking and Unblocking Signals== Linux(리눅스)는 signal을 block하는 implicit한, 혹은 explicit한 메커니즘을 제공한다. ===Implicit blocking mechanism=== 기본적으로 커널은 현재 handler에 의해서 처리되고 있는 signal과 동일한 종류의 pending signal들을 block한다. 예를 들어, 현재 signal s가 프로세스가 포착해 handler에서 실행되고 있다고 하자. 이때 signal s가 추가로 프로세스에 보내지면, 해당 signal은 pending signal이 되지만, handler가 실행 종료될 때까지는 프로세스에 수신되지 않는다. ===Explicit blocking mechanism=== application은 sigpromask() 함수와 그 보조 함수들을 통해 특정 signal들을 explicit하게 block/unblock한다. 이때 sigpromask() 함수와 그 보조 함수들은 다음과 같다. <syntaxhighlight lang="c"> #include <signal.h> /* Returns: 0 if OK, −1 on error */ int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); /* Returns: 1 if member, 0 if not, −1 on error */ int sigismember(const sigset_t *set, int signum); </syntaxhighlight> sigpromask() 함수는 현재 차단된 signal들의 집합을 인자 how의 값에 따라 변경한다. 이때 how에 들어오는 인자는 다음과 같다. * SIG_BLOCK: set에 있는 signal들을 blocked<ref>block된 signal들의 집합을 나타내는 bit vector이다.</ref> set에 추가한다.(blocked = blocked | set). * SIG_UNBLOCK: set에 있는 signal들을 blocked set에서 제거한다. (blocked = blocked & ~set) * SIG_SETMASK: blocked set을 set으로 설정한다. (blocked = set) oldset 인자가 NULL이 아니면, oldset bit vector에 이전 blocked set의 값이 저장된다. 또한 set과 같은 신호 집합들은 다음 함수들을 사용하여 조작된다. * sigemptyset 함수: set을 빈 set으로 초기화한다. * sigfillset 함수: 모든 signum을 set에 추가한다. * sigaddset 함수: signum을 set에 추가한다. * sigdelset 함수: signum을 set에서 삭제한다. * sigismember 함수: signum이 set의 멤버이면 1을 반환하고, 그렇지 않으면 0을 반환한다. <syntaxhighlight lang="c"> #include <signal.h> int main() { sigset_t mask, prev_mask; sigemptyset(&mask); // Initialize mask as empty sigaddset(&mask, SIGINT); // Add SIGINT to the mask // Block SIGINT and save previous blocked set sigprocmask(SIG_BLOCK, &mask, &prev_mask); // Code region that will not be interrupted by SIGINT // ... // Restore previous blocked set (unblock SIGINT) sigprocmask(SIG_SETMASK, &prev_mask, NULL); return 0; } </syntaxhighlight> 위 코드는 SIGINT를 일시적으로 block한 다음, 특정 코드 실행 후, 다시 이전의 blocked set으로 되돌린다. ==Writing signal handler== Signal handler는 리눅스 시스템 수준의 프로그래밍에서 가장 까다로운 것중 하나이다. 왜냐하면 handler가 다음과 같은 특성을 지니기 때문이다. * Handler는 메인 프로그램과 concurrently하게 실행되며 동일한 global data structure들을 공유하므로, 메인 프로그램이나 다른 handler들과 간섭할 수 있다. * 신호가 어떻게, 그리고 언제 수신되는지에 대한 규칙은 종종 직관에 반한다. * 서로 다른 시스템들은 서로 다른 신호 처리 의미론(semantics)을 가질 수 있다. 따라서 safe signal handler를 작성하는 것은 매우 중요하다고 볼 수 있다. ==각주== [[분류:컴퓨터 네트워크]]
Signal
문서로 돌아갑니다.
둘러보기
둘러보기
대문
최근 바뀜
임의의 문서로
미디어위키 도움말
위키 도구
위키 도구
특수 문서 목록
문서 도구
문서 도구
사용자 문서 도구
더 보기
여기를 가리키는 문서
가리키는 글의 최근 바뀜
문서 정보
문서 기록