Wednesday, May 14, 2008

Signals, basic manipulation

아주 기초, 기본적인 signal 처리함수는 아래와 같다.

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signo, sighandler_t handler);

signo에 해당하는 signal이 발생할 때 handler 함수를 호출하겠다는 얘기다.

#include <stdio.h>
#include <signal.h>

void handle_sigtstp(int signal)
{
printf("ctrl-z pressed. "
"You shouldn't use printf!\n");
return;
}

int main()
{
signal(SIGTSTP, handle_sigtstp);

sleep(10);
}

위 예제 처럼 간단하게 등록, 사용할 수 있다. printf 같은 함수를 호출하면 안되는 이유는 이 함수가 reentrancy를 보장하기 않기 때문인데, 일단 나중에 얘기하자.

사용자 handler 대신에 default로 동작하기를 원하는경우 handler 대신 'SIG_DFL'을 주면 되고, signal을 무시하고자 하는 경우에는 SIG_IGN을 주면된다. signal()은 등록된 handler의 포인터나 SIG_DFL, SIG_IGN 을 주는데, 실패한 경우 SIG_ERR을 준다(errno를 설정하지는 않는다)

signal을 테스트할 때 편한 함수로 pause()가 있다(signal을 받을 때 까지 기다린다). 이러한 부분을 고려하면 코드는 아래와 같아진다.

if(signal(SIGINT, handler) == SIG_ERR) {
fprintf(stderr, "Can't handle SIGINT\n");
}

프로세스가 실행(exec)되면 Signal handle은 default로 잡힌다. 부모 프로세스가 특정 signal을 무시하고 있었다면, exec된 프로세스도 무시하게 된다. 그리고 부모 프로세스가 잡아서 처리하던 signal은 default action으로 바뀐다(부모 프로세스의 주소공간을 공유하고 있지 않아서 handler도 공유/상속되지 않기 때문이다).

쉘에서 특정 job을 bg로 돌리는 경우 bg로 돌아가는 job은 SIGINT, SIGQUIT 등을 무시해야 한다. 그래서 쉘에서 bg job들을 실행하기 전에 아래처럼 SIGINT, SIGQUIT을 무시하는 코드를 수행한다(그래야 bg job들도 무시하게 되니까)

if(signal(SIGINT, SIG_IGN) != SIG_IGN) {
if(signal(SIGINT, sight_handler) == SIG_ERR) {
fprintf(stderr, "Can't handle SIGINT\n");
}
}
/* SIGQUIT도 마찬가지 */

그런데 fork()의 경우에는 좀 다르다. 부모의 주소공간을 공유하기 때문에 signal handler 까지 모두 받아서 똑같이 signal을 처리한다.

특정 signal이 발생했을 때 SIGINT인지 SIGKILL인지 숫자로 되어있어 햇갈릴 수 있다. 그래서 sys_siglist[] 를 통해서 각각 signal을 string으로 찍어볼 수 있다. linux에서 char *strsignal(int signo)를 제공하지만 표준은 아니다(thread-safe하지도 않다). sys_siglist[]를 추천한다.

#include <signal.h>
#include <stdio.h>

void sig_handler(int signo)
{
printf("signal caught : %s\n", sys_siglist[signo]);
return;
}

int main()
{
if (signal(SIGINT, sig_handler) == SIG_ERR) {
fprintf(stderr, "SIGINT handle can't be registered\n");
}
pause();
return;
}

signal을 보내는 방법은 아래처럼 매우 간단하다. 혹은 kill로 바로 보내도 된다(like, kill -HUP [pid]).

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int signo);

kill()은 세가지 값을 반환하는데, invalid signal인 경우 EINVAL, 권한이 없는 경우 EPERM, pid가 없거나 좀비인 경우 ESRCH이다.

다른 user나 그룹등...에 signal을 보내고자 하는 경우에는 프로세스가 CAP_KILL capability가 있어야 한다. 내가 다른 프로세스에 signal을 보낼 권한이 있는지 체크하는 좋은 방법으로는 signal로 '0'을 보내보는 것이다(실제 signal을 보내지는 않으므로 err만 체크할 수 있다).

자기자신에게 signal을 보낼 때는 간단히 raise()를 사용한다(like, int raise(int signo)). 이것은 kill(getpid(), signo) 와 같다. 전체 프로세스 그룹에 보내는 경우에는 killpg(int pgrp, int signo)를 사용한다(kill을 사용해서 kill(int -pgrp, signo)처럼 해도된다). pgrp = 0 이면 자신이 띄운 프로세스들에 모두 보낸다.

No comments:

Post a Comment