티스토리 뷰

C | C++

시그널 다루기

야라바 2018. 8. 4. 14:41
728x90

☞ sigaction, raise, kill, sigemptyset, sigfillset, sigaddset, sigdelset, sigismember, sigprocmask

<signal.h>

int raise(int signum);
int kill(pid_t pid, int signum);
int sigaction(int signum, const struct sigaction *action, struct sigaction *oldaction);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

프로세스로 전달되는 시그널에 대비하여 특정 시그널을 무시하도록 설정하거나 핸들러를 설치하기도 하지만, raise()와 kill() 함수로 특정 시그널을 프로세스 자신에게 또는 특정 프로세스에 전송할 수 있습니다. raise()는 프로세스 스스로에게 signum로 지정한 시그널을 전송하고 성공하면 0을 리턴합니다. kill()은 pid로 지정하는 특정 프로세스 또는 프로세스 그룹에 시그널을 전송합니다. pid가 0이면 동일한 프로세스 그룹에 속하는 모든 프로세스를 대상으로 시그널을 전송하고 pid를 -1로 지정할 경우 권한이 있으면 특정 시스템 프로세스를 제외한 모든 프로세스에 시그널을 전송하고 권한이 없으면 해당 사용자에 속한 모든 프로세스에 시그널을 전송합니다. pid가 -1 보다 작으면 프로세스 그룹 아이디가 -pid인 프로세스들에게 시그널을 전송합니다. kill(getpid(),signum)와 같은 방식으로 kill()로도 스스로에게 시그널을 전송할 수 있습니다. 성공적으로 메시지가 전송되면 0을 리턴합니다.



☞ 예제1

static void register_fatal_signal_handler (int signo)
{
    struct sigaction act;

    act.sa_handler = NULL;
    act.sa_sigaction = crash_handler;
    sigemptyset (&act.sa_mask);
    act.sa_flags = 0;
    act.sa_flags |= SA_SIGINFO;
    sigaction (signo, &act, NULL);
}
......
int main (int argc, char **argv)
{
    char *binary_name;
    int ret_val = 0;

    register_abort_signal_handler (SIGABRT);
    register_fatal_signal_handler (SIGILL);
    register_fatal_signal_handler (SIGFPE);
    register_fatal_signal_handler (SIGBUS);
    register_fatal_signal_handler (SIGSEGV);
    register_fatal_signal_handler (SIGSYS);
......
}

위의 예제는 큐브리드 DBMS의 코드 일부 입니다. 시그널 핸들러와 처리 방법을 설정하는 signal()함수와 유사한 기능을 수행하는 것이 위의 예제에서 사용하고 있는 sigaction() 입니다. 조금 더 복잡하기는 하지만 그 만큼 다양한 옵션을 사용할 수 있습니다. sigaction()은 대상 시그널을 지정하는 signum과 함께 구조체(struct sigaction)에 대한 포인터 action와 oldaction을 지정하는데 signum으로 지정한 시그널에 대해서 action에 따라 시그널 설정을 수행하고 기존 시그널 정보는 oldaction에 저장해 줍니다. 작업이 성공하면 0을 리턴합니다. 구조체 포인터 action과 oldaction은 모두 널포인터로 전달할 수 있는데 action이 널포인터인 경우는 시그널 변경 작업을 수행하지 않겠다는 의미고 oldaction이 널포인터인 경우는 기존 정보를 전달 받지 않겠다는 의미 입니다.

구조체(struct sigaction)는 다음과 같은 항목들로 구성됩니다.

  • sighandler_t sa_handler : signal() 함수에서 사용한 시그널 핸들러 또는 처리 방법
  • sigset_t sa_mask : 시그널 마스킹 정보로 핸들러가 시그널을 받기 위해서는 여기에서 해당 시그널이 막히지 않도록 설정 해야 합니다
  • int sa_flags : 시그널 처리 방식을 지시하는 것으로 SA_NOCLDSTOP(기본적으로는 멈추거나 중단된 자 프로세스에게도 시그널을 전송하지만 플래그가 켜지면 살아있는 자 프로세스 들에게만 전송), SA_ONSTACK(시그널 전송시 시그널 스택을 사용하도록 지시), SA_RESTART(기본적인 IO 수행 과정의 처리를 지정)를 사용할 수 있습니다.



☞ 예제2

void cfg_write_directory_ex (int vdes, const DB_INFO * databases)
{
    char line[LINE_MAX], *s;
    const DB_INFO *db_info_p;
    int n;
    sigset_t new_mask, old_mask;

    sigfillset (&new_mask);
    sigdelset (&new_mask, SIGINT);
    sigdelset (&new_mask, SIGQUIT);
    sigdelset (&new_mask, SIGTERM);
    sigdelset (&new_mask, SIGHUP);
    sigdelset (&new_mask, SIGABRT);
    sigprocmask (SIG_SETMASK, &new_mask, &old_mask);

    lseek (vdes, 0L, SEEK_SET);
    n = sprintf (line, "#db-name\tvol-path\t\tdb-host\t\tlog-path\t\tlob-base-path\n");
    write (vdes, line, n);
    for (db_info_p = databases; db_info_p != NULL; db_info_p = db_info_p->next)
    {
        bool t = (strlen (db_info_p->name) < 8);
        s = line;
        s += sprintf (s, "%s%s\t%s\t", db_info_p->name, (t ? "\t" : ""), db_info_p->pathname);

        if (db_info_p->hosts != NULL && *(db_info_p->hosts) != NULL)
        {
            char **array = db_info_p->hosts;
            s += sprintf (s, "%s", *array++);
            while (*array != NULL)
            {
                s += sprintf (s, ":%s", *array++);
            }
        }
        else
        {
            s += sprintf (s, "localhost");
        }
        if (db_info_p->logpath)
        {
            s += sprintf (s, "\t%s", db_info_p->logpath);
        }
        if (db_info_p->lobpath)
        {
            s += sprintf (s, "\t%s", db_info_p->lobpath);
        }
        s += sprintf (s, "\n");
        n = (int) (s - line);
        write (vdes, line, n);
    }

    ftruncate (vdes, lseek (vdes, 0L, SEEK_CUR));

    sigprocmask (SIG_SETMASK, &old_mask, NULL);
}

sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigismember(), sigprocmask()등은 sigaction()에서 사용하는 구조체 struct sigaction의 한 항목인 시그널 마스킹 정보(sigset_t sa_mask)를 다룹니다. 주의할 점은 이들 함수는 마스킹 정보만을 다룰 뿐이지 실제로 시그널을 막는 마스킹은 sigprocmask()를 통해서 이루어 집니다.


sigemptyset()은 시그널 마스킹 정보 set에서 모든 시그널을 제외하도록 설정합니다. sigfillset()은 시그널 마스킹 정보 set에서 모든 시그널이 포함되도록 설정합니다. sigaddset()은 특정 시그널을 시그널 마스킹 정보 set에 추가하고 sigdelset()은 제외합니다. sigismember()은 시그널 마스킹 정보 set에 지정한 시그널이 설정되어 있으면 1을 리턴하고 없으면 0, 에러면 -1을 리턴합니다. sigprocmask()은 현재 프로세스에 대한 시그널 마스킹을 다루는데 how에 작업 방법을 지정하고 set에 작업 대상 시그널 정보, oldset에 기존 마스킹 정보를 담을 오브젝트의 포인터를 전달합니다. how를 SIG_BLOCK으로 설정하면 기존 마스킹에 새로운 set를 추가 반영합니다. SIG_UNBLOCK으로 설정하면 기존 마스킹에서 set으로 지정한 것들을 제외시킵니다.


SIG_SETMASK은 기존 마스킹 정보를 새로운 것으로 대치시킵니다. set과 oldset 포인터를 각각 널포인터로 전달할 수도 있는데 set이 널이면 설정 작업을 하지 않는다는 의미이고 oldset이 널이면 기존 정보 읽기를 하지 않는다는 의미입니다.




728x90

'C | C++' 카테고리의 다른 글

환경 변수 다루기  (0) 2018.08.04
프로그램 아규먼트와 옵션 처리  (0) 2018.08.04
시그널의 종류와 핸들러  (0) 2018.08.04
비지역적 점프  (0) 2018.08.04
알람과 sleep  (0) 2018.06.29