sigaction

Development/Linux 2009. 8. 1. 15:35

1장. sigaction(2)

차례
1.1. 사용법
1.2. 설명
1.3. 반환값
1.4. 에러
1.5. 예제
1.6. 참고문헌

시그널 처리관련 함수


1.1. 사용법

#include <signal.h>
int sigaction(int signum,  const  struct  sigaction  *act,
	struct sigaction *oldact);
int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
		


1.2. 설명

sigaction() 시스템 호출은 특정 시그널의 수신에 대해서 취할 액션을 설정하거나 변경하기 위해서 사용된다.

signum는 시그널을 명시한다. SIGKILLSIGSTOP를 제외한 모든 시그널이 타당한 시그널이 될 수 있다.

만약 actnull이 아니라면 signum번호를 가지는 시그널에 대해서 act함수가 설치된다. 만약 oldact가 null이 아니라면 이전의 액션은 oldact에 저장된다.

sigaction구조체는 다음과 같이 정의되어 있다.

struct sigaction 
{
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
		

sa_handler

signum번호를 가지는 시그널이 발생했을 때 실행된 함수를 설치한다. 함수외에도 SIG_DFL과 SIG_IGN을 지정할 수 있다. 전자는 시그널에 대한 기본행동을 후자는 시그널을 무시하기 위해서 사용한다.

sa_mask

sa_handler에 등록된 시그널 핸들러 함수가 실행되는 동안 블럭되어야 하는 시그널의 마스크를 제공한다. SA_NOMASK가 적용되어 있지 않다면

sa_flags

시그널 처리 프로세스의 행위를 수정하는 일련의 플래그들을 명시한다. 다음중 하나 이상의 것들에 의해서 만들어 진다.

SA_NOCLDSTOP

만약 signum이 SIGCHLD라면, 자식 프로세스가 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU등을 받아서 중단되었을 때 이를 통지 받을 수 없게 된다.

SA_ONESHOT, SA_RESETHAND

일단 시그널 처리기가 호출되면, 기본 상태에 대한 시그널 액션을 재 저장한다. 이는 signal(2)호출에 대한 기본 행위이다.

SA_RESTART

일부 시스템 호출들이 시그널을 통해 재시작할 수 있도록 함으로서 BSD 시그널과 호환되도록 한다.

SA_NOMASK, SA_NODEFER

시그널이 자체 시그널 처리기로부터 수신 받지 않도록 한다.

SA_SIGINFO

시그널 처리기가 하나가 아닌 3개의 인자를 취할경우 sa_handler대신 sigaction의 siginfo_t를 이용할 수 있다. siginto_t는 다음과 같이 정의된 구조체이다.

siginfo_t {
    int      si_signo;  /* 시그널 넘버 */
    int      si_errno;  /* errno 값 */
    int      si_code;   /* 시그널 코드 */
    pid_t    si_pid;    /* 프로세스 ID 보내기 */
    uid_t    si_uid;    /* 프로세스를 전송하는 실제 사용자 ID */
    int      si_status; /* Exit 값 또는 시그널 */
    clock_t  si_utime;  /* 소모된 사용자 시간 */
    clock_t  si_stime;  /* 소모된 시스템 시간 */
    sigval_t si_value;  /* 시그널 값 */
    int      si_int;    /* POSIX.1b 시그널 */
    void *   si_ptr;    /* POSIX.1b 시그널 */
    void *   si_addr;   /* 실패를 초래한 메모리 위치 */
    int      si_band;   /* 밴드 이벤트 */
    int      si_fd;     /* 파일 기술자 */
}
						
SIGCHLD 시그널은 si_pid, si_uid, si_status, si_utime, si_stime를 채운다. si_int, si_ptr은 시그널의 송신자에 의해서 명시될 수 있다.

si_code는 왜 시그널이 보내졌는지를 지시한다.

sigprocmask()는 현재 블록된 시그널들을 변경시키기 위해서 사용한다. 호출의 행위는 how 값들에 대해서 의존적이 된다. how는 다음중 하나를 선택할 수 있다.

SIG_BLOCK

set에 설정된 시그널을 블럭 시그널셋에 추가시킨다.

SIG_UNBLOCK

시그널 셋set의 시그널을 현재의 블럭된 시그널에서 삭제한다.

SIG_SETMASK

시그널 셋set의 시그널을 블럭화된 시그널로 지정한다.

oldset이 null이 아니면, 시그널 마스크의 이전 값은 oldset에 저장된다.

sigpending()함수는 전달 시그널들에 대한 검사를 허용한다. 전달 시그널들의 마스크는 set에 저장된다.

sigsuspend()는 프로세스의 시그널 마스크를 일시적으로 mask로 대체하고, 시그널이 수신될때까지 프로세스를 중지시킨다.


1.3. 반환값

성공하면 0을 실패하면 -1을 리턴한다.


1.4. 에러

EINVAL

부적절한 시그널이 지정되거나. 무시할 수 없는 SIGKILL SIGSTOP에 대한 액션을 변경하고자 할 경우

EFAULT

act, oldact, set, oldset 이 타당하지 않은 메모리 영역을 가리킬 경우

EINTR

시스템 호출이 인터럽트 되었다.


1.5. 예제

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

void sig_int(int signo);
void sig_usr(int signo);

int main()
{
    int i = 0;
    struct sigaction intsig, usrsig;

    usrsig.sa_handler = sig_usr;
    sigemptyset(&usrsig.sa_mask);
    usrsig.sa_flags = 0;

    intsig.sa_handler = sig_int;
    sigemptyset(&intsig.sa_mask);
    intsig.sa_flags = 0;

	// SIGINT에 대해서 sig_int를 등록한다. 
    if (sigaction(SIGINT, &intsig, 0) == -1)
    {
        printf ("signal(SIGINT) error");
        return -1;
    }    

	// SIGUSR2에 대해서 usrsig를 등록한다. 
    if (sigaction(SIGUSR2, &usrsig, 0) == -1)
    {
        printf ("signal(SIGUSR2) error");
        return -1;
    }    

    while(1)
    {
        printf("%dn", i);
        i++;
        sleep(1);
    }
}

void sig_int(int signo)
{
    sigset_t sigset, oldset;

	// 핸들러가 수행되는 동안 수신되는 모든 시그널에 대해서
	// 블럭한다.  
    sigfillset(&sigset);
    if (sigprocmask(SIG_BLOCK, &sigset, &oldset) < 0)
    {
        printf("sigprocmask %d error n", signo);
    }
    fprintf(stderr, "SIGINT !!!!n");
    sleep(5);
}

void sig_usr(int signo)
{
    printf("sig_usr2n");
}
		

Posted by 까 치
,

Linux daemon

Development/Linux 2009. 7. 6. 21:19
아래 글은 Nick lib 의 푸우님께서 작성해주신 글입니다.
문제시 삭제 조치하겠습니다.
Ref  http://www.nicklib.com/bbs/board.php?bo_table=bbs_column&wr_id=9


요즘 통 글을 안 올렸네요.
요즘 회사일로 정신이 없네요.
죄송하구요. ^^; 
아무튼 이번에는 UNIX/LINUX의 Deamon 프로세스 만드는 것에 대해 정리해 보도록 하겠습니다.
 
1. Daemon이란?
 
뭐 이글을 관심이 있어서 읽고 계시는 분 중에 Daemon이 뭔지를 모르시는 분은 없겠지만...
그래도 한번 정리해 봅니다.
Daemon의 뜻 자체는 "악마", "악령", "귀신"과 같이 좀 무시무시 합니다.
하지만 Daemon의 어원은 신화에서 신과 인간사이의 초자연적인 영적존재로서의 수호신에서 나왔다고 합니다. 뭐 좋은쪽으로 생각하죠.
컴퓨터 상에서 Daemon은 겉으로는 보이지 않지만 항상 뒤에서 뭔가를 계속해서 수행해 주는 프로그램을 말합니다. 예로 httpd, ftpd, telnetd 등...
이런면에서 왜 Daemon이라는 이름 지었는지 이해가 가네요.
윈도우즈에서는 Daemon이라는 용어가 마음에 들지 않았는지 아니면 뭔가 꼭 튀게 해야한다고 생각했는지 몰라도... UNIX/LINUX의 Daemon과 같은 역할을 하는 프로그램을 Service 라고 합니다.
"데몬"은 이름에 뭔가 철학을 담아 지은것 같은데... "서비스"... 참 단순해서 좋긴 좋네요. ㅋㅋ  ^^;
 
Daemon 프로그램이 되려면 몇가지 특징을 갖추어야 하지만 가장 중요한 것은 "OS가 부팅되면 사용자의 개입없이 실행될 수 있을 것"입니다.
이 말은 다시 말해 누군가가 로그인을 하여 굳이 실행하지 않아도 OS의 init프로세스에 의해 실행될 수 있어야 한다는 이야기 이고 이럴려면 프로그램을 제어하는 터미널(tty)를 갖지 말아야 한다는 것 입니다. 반대로 말하면 누군가 로그인 해서 데몬을 시작시켰다면 로그아웃할 때 해당 데몬은 계속해서 살아 있어야 한다는 말도 됩니다.
 
이제 이러한 특징을 갖는 프로그램을 만드는 방법을 단계별로 알아보도록 하죠.
 
 
2. 로그 아웃시 프로그램 종료되지 않게 하기
 
우선 데몬은 아니더라도 데몬과 비슷하게 수행하면 계속해서 작업을 수행 할 수 있도록 하는 방법에 대해서 알아 보겠습니다.
 
기본 소스는 다음과 같습니다.
 
#include <stdio.h>

int work()
{
        FILE *fp;
        unsigned long count=0;

        fp=fopen("/tmp/daemon.out""wt");
        if(fp==NULL) {
                printf("File Open Errorn");
                return -1;
        }

        while(1){
                sleep(1);
                fprintf(fp, "%lun", count);
                fflush(fp);
                fprintf(stdout, "%lun", count++);
        }
        fclose(fp);

        return 0;
}

int main(int argc, char *argv[])
{
        work();

        return 0;
}
 
 
위의 프로그램은 간단하니깐 보시면 아시겠지만 main()함수가 수행되면 work()라는 함수를 수행하게 됩니다.
work()함수는 무한루프를 돌면서 1초마다 "/tmp/daemon.out"파일에 카운팅을 기록합니다.
또 표준출력으로도 같은 값을 출력하는 프로그램입니다.
위의 소스를 컴파일하여 daemon이라는 실행파일을 생성했다고 하고 다음의 글들을 작성토록 하겠습니다.
 
자 그럼 로그아웃이 될때 어떤 현상이 일어날지에 대해 한번 알아보도록 하죠.
 
로그아웃하거나 해당 터미널이 끊기게 되면 OS 해당 터미널을 제어단말로 사용하던 모든 프로그램에게 특별한 신호를 보내게 됩니다.
이 신호가 SIGHUP, 즉, Hangup 시그널입니다.
일반적인 프로그램은 이 Hangup 시그널을 받으면 정상종료를 하게 됩니다.
뭐 프로세스가 알아서 정상종료 한다기 보다는 이 시그널을 어떻게 처리하라는 루틴을 구현하지 않은 프로세스는 조용히 OS가 죽이는 것이죠.
 
OS에 SIGHUP을 무시하게 해주는 'nohup'이라는 쉘명령어가 있습니다.
일반적인 사용방법은 다음과 같습니다.
 
#nohup daemon&
 
위의 명령어 중 daemon은 데몬 프로그램의 이름이고 마지막 &는 이 글의 주제와는 상관없지만 백그라운드로 실행해서 프롬프트를 다시 얻기 위함입니다.
이렇게 daemon을 실행한 뒤 로그아웃하거나 단말기를 끊고 다시 접속한 다음 "ps -ef | grep daemon" 명령을 통해 보시면 daemon이 살아 있음을 보실 수 있으실 것 입니다.
또한 "tail -f /tmp/daemon.out" 해서 보시면 살아 있을 뿐 더러 원래의 기능도 정상적으로 수행 하고 있음을 알 수 있습니다.
 
그런데 여기서 원래 daemon은 파일 뿐만 아니라 화면에도 카운팅 번호를 출력하도록 되어 있었는데 위의 명령의 수행 후에는 아래와 같은 메시지만 남기고 나타나지 않습니다.
 
appending output to `nohup.out'
 
이는 nohup 명령어가 daemon의 표준 출력을 현재 경로의 'nohup.out'이라는 파일로 리다이렉트 시켰기 때문입니다.
 
어찌되었건 쉘에서 제공하는 명령어를 통해 데몬과 유사한 기능을 하도록 해 보았네요. 
 
자 이번에는 'nohup'과 같은 기능을 시스템 함수로 구현해 보도록 하겠습니다.
 
골자는 프로그램 내에서 SIGHUP시그널을 무시할 수 있도록 코딩하는 것입니다.
 
 위 함수에 #include <signal.h> 를 포함하고 main함수를 다음과 같이 고칩니다.
 
 
int main(int argc, char *argv[])
{
        signal(SIGHUP, SIG_IGN);

        work();

        return 0;
}
 
 
지금 부터는 work()함수의 표준 출력이 귀찮아 지므로 표준 출력으로 내보는 문장은 적절히 삭제하셔도 좋습니다.  
위의 main()함수는 signal()함수를 통해 SIGHUP시그널을 무시하도록 설정 한 뒤 work()함수를 수행한 것입니다.
 
컴파일 후 테스트는 여러분의 몫으로 하구요.
 
SIGHUP을 무시하는 방법은 아니지만 또 다른 방법이 있는데 이는 프로세스 내에서 자신의 그룹아이디를 바꾸는 것입니다.
 
이는 로그 아웃시 현재 터미널을 제어 단말기로 하고 있는 프로세스에 OS가 SIGHUP시그널을 보낼 때 쉘프로세스의 아이디를 그룹아이디로 갖는 모든 프로세스에게 보낸다는 것에 착안한 것입니다.
 
즉, 프로그램은 특정 쉘프로세스에서 수행되기 때문에 일반적으로 프로세스 그룹아이디는 수행 환경이 되었던 쉘프로세스 아이디를 갖게 되는데 이를 프로세스 내에게 바꾸는 것입니다. 
 
int main(int argc, char *argv[])
{
        int newpgid;

        newpgid = setpgrp();

        work();

        return 0;
}
 
위의 소스에 보이는 setpgrp()함수는 자신의 프로세스 그룹을 자신의 프로세스아이디로 바꿔줍니다.
이렇게 하면 로그아웃시 이 프로세스는 아예 SIGHUP시그널을 받지도 않으므로 종료되지 않습니다.
 
자 여기까지는 데몬 프로그램을 만드는 법이라기 보다는 일반적으로 프로세스에서 일어나는 일과 이들을 처리하는 방법, 그리고 간단히 데몬과 유사한 효과 내기라고 생각하시면 될 듯 싶습니다.
 
 
3. fork에 의한 Daemon만들기
 
 
위에서 이야기 한 방법들도 뭐 잘못된 방법들은 아니지만 일반적으로는 이제부터 이야기 하는 방법으로 주로 데몬을 만듭니다.
main()함수를 다음과 같이 바꾸세요.
 
int main(int argc, char *argv[])
{
        int pid;

        pid=fork();
        switch(pid){
        case -1:
                fprintf(stderr, "Fork Errorn");
                break;
        case 0:         // child
                break;
        default:        // parent
                return 0;
        }

        work();

        return 0;
}
 
위의 main()함수는 프로그램이 시작되자 마자 fork()를 수행해서 자식프로세스는 work()함수를 수행하고 부모프로세스는 종료하게 합니다.
그러면 사실 자식프로세스는 아직 살아 있는데 부모프로세스가 터미널과 연결되어 있었는데 사라지게 됩니다. 고로 자식 프로세스는 제어단말기를 가지지 않게 되고 부모가 없으므로 고아프로세스가 되었네요.
이렇게 고아프로세스가 발생하면 OS는 이 고아 프로세스를 init프로세스의 자식으로 만들어 줍니다.
부모,자식,고아를 이야기 했으니 입양이라고 해야 할까요? ㅋㅋㅋ
하지만 원래 init프로세스는 1번 프로세스로서 조상 프로세스에 해당됨으로 입양이라고 하기는 좀 그렇네요.
 
아무튼 위의 프로그램은 아무 문제가 없습니다. 좀비가 발생하지도 않구요.
 
하지만 이런 케이스를 생각해 보죠.
daemon도 프로세스이므로 현재 작업디렉토리를 갖습니다.
그래서 이 daemon이 실행된 후 daemon을 실행 시켰던 디렉토리가 속한 파일시스템을 "umount" 시키려 한다면 OS는 해당 파일시스템이 사용되고 있다고 umount 하지 못한다고 할 것 입니다.
이런 경우 해당 데몬을 내려야 하는데...
더욱 나쁜 경우는 어떤 데몬이 이 파일시스템을 사용하고 있는지를 알기가 힘들다는 것이죠.
그래서 데몬은 데몬이 되기 전에 현재 경로를 "/'와 같이 "umount"될 수 없는 곳으로 옮기는 것이 좋습니다.
 
또 한가지 고려해야 할 것은 현재 daemon은 뭐 파일에 숫자만 적고 있지만 여러분이 만드는 deamon은 어떤 일을 하게 될 지 사실 모릅니다.
예를 들어 daemon이 또 fork를 해서 자식 프로세스들을 갖을 수도 있고 exec()함수나 system()함수에 의해 아예 다른 프로그램을 실행 시킬 수도 있을 것입니다.
이때 새로 생성되는 프로세스를 현재 daemon이 제어 할 수 없다면 이 또한 문제일 수 있겠죠?
그래서 데몬프로세스는 데몬이 된 직 후 자신이 속한 프로세스 그룹의 리더가 아니라면 새로운 프로세스 세션을 만들고 자신이 리더가 된 후 다음 작업들을 하게 됩니다.  이러한 기능을 하는 함수가 바로 setsid()라는 함수 입니다. (참조: http://teamblog.joinc.co.kr/yundream/226)
 아래 소스는 chdir()과 setsid()함수를 호출하여 위에서 이야기한 내용을 반영하고 있습니다.
 
int main(int argc, char *argv[])
{
        int pid;

        pid=fork();
        switch(pid){
        case -1:
                fprintf(stderr, "Fork Errorn");
                break;
        case 0:         // child
                break;
        default:        // parent
                return 0;
        }

        chdir("/");
        setsid();

        work();

        return 0;
}
 
 
자 여기까지 하면 뭐 데몬이 잘 만들어 진 것 입니다.
 
추가로 실제 데몬을 프로그래밍 하실 때 주의하실 점은 시그널에 대한 처리를 꼭 하시라는 것 입니다.
위의 샘플 소스는 뭐 특별하게 시그널 처리를 할께 딱히 없지만 예를 들어 웹서버와 같이 통신 프로그램인 경우 SIG_PIPE와 같은 시그널을 처리해야 합니다.
 
 
4. 오뚜기 같은 Daemon만들기
 
마지막으로 이야기 하는 것은 일반적인 데몬에 대한 이야기는 아니구요 팁같은 거라고 할까요?
데몬은 서비스 제공하는 프로세스이다 보니깐...
시스템이 시작되면 시작되서 끝날때 까지 살아 있어야 하는데...
우리가 짜는 프로그램이 문제가 없다고 장담할 수는 없겠죠? (저만 그런가?)
 
만약 데몬으로 띄운 프로그램이 죽으면 타격이 크죠.
뭐 그 타격은 타격이더라도 세션이나 트랜잭션이 중요한 서비스가 아닌 경우라면 빨리 다시 시작이라도 시켜서 정상화를 시키고 싶은 경우가 있습니다.
 
이런 경우에 다음과 같은 방법으로 할 수 있을 것입니다.
먼저 일반 데몬을 하나 만들기 위해 일반 fork하시구요.
그리고 다시 fork를 해서 자식프로세스는 서비스를 수행하구요 부모프로세스는 자식이 종료되기를 기다립니다.
그런데 자식프로세스는 무한루프일 거니깐...
부모프로세스 입장에서 자식이 종료되었다면 실제로는 뭔가 문제가 생긴 것이겠죠?
이때 다시 fork를 해서 위의 과정을 반복합니다.
 
소스는 다음과 같습니다.  
 
int main(int argc, char *argv[])
{
        int pid;
        int ret;

        if (( pid = fork()) < 0) {
                fprintf(stderr, "Main Fork Errorn");
                return 0;
        } else if(pid > 0) {
                return 0;
        }

        chdir("/");
        setsid();

        while(1) {
                if (( pid = fork()) < 0) {
                        fprintf(stderr, "Sub Fork Errorn");
                        return 0;
                } else if(pid == 0) {
                        break;
                } else if(pid > 0) {
                        wait(&ret);
                }
        }

        work();

        return 0;
}
 
 
만약 자식이 종료될때 상태를 알고자 한다면 wait()함수의 인자인 ret값을 조사하면 됩니다.
 
자 이렇게 해서 UNIX/LINUX에서 Daemon만들기에 대한 이야기를 마치도록 하겠습니다. 
Posted by 까 치
,
































06년 새해가 밝았습니다.
조카들의 재롱을 볼 나이가 되었고, 이젠 복돈이랍시고 은행에서 신권을 교환해서 조카들 손에 쥐어 줄 나이가 되었나 봅니다.(아직 방년 25살 6년차인데..ㅠ.ㅠ)
명절이 되면 그래도 맘 맞는 사람은 형님밖에 없어선지 항상 고도리도 치고, 간혹 끽연도 해가며 보냈던 것 같은데,
이번 구정은 외롭더군요.

그래도 명절인데 아들 딸 얼굴도 못보고 부모님 할머니 할아버지도 뵙지 못해 아쉬워 할 형을 생각하니, 그렇게 나쁜것 만은 아닌 것 같네요.
언능 돈 많이 벌어오라고, 금의환양하라고 응원을 보냅니다. (좀 부담슬럽겠지만, 그래도 될 듯하여 ^^ㅋ)


'Pictures > Snap' 카테고리의 다른 글

SW멤버십 사진 모음  (0) 2013.05.13
광주 멤버십 스키장 가다......I  (0) 2012.08.20
설날 정훈이와 가현이 ...  (0) 2008.03.22
Let's happy~  (0) 2008.03.22
06년 하반기 OT 단체사진  (0) 2008.03.22
Posted by 까 치
,