ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Linux] signal()
    LinuxProgramming 2023. 10. 16. 19:08

    [ basic signal functions ]

    1. kill(pid_t pid, int signo)

    특정 프로세스한테 signal 보냄

     

    2. pause()

    signal 올때까지 ㄱㄷ

     

    3. raise(int signo)

    자신에게 시그널 호출. 그래서 인자가 한 개이다. 

    다른 프로세스한테도 시그널을 호출할 수 있는 kill과 다르게 자기자신한테 호출하는 것으로 이미 정해져있는 것이기 때문이다.

    raise(SIGKILL); 이면 프로세스 자살임.

     

    4. abort()

    자신에게 ABORT 시그널을 보낸다.

    프로세스를 비정상종료시키고 core dump file을 생성한다.

     


    [ 3 ways of signal handling]

    1) SIG_DFL : 해당 시그널 오면 종료 

    2) SIG_IGN : 해당 시그널 무시

    3) 사용자 정의 처리 : 해당 시그널 오면 사용자가 정의한 함수대로 처리

     

    여기서 3번을 할 줄 알아야한다.

    sigaction 함수와 3가지 인자를 통해 특정 signal 을 받으면 처리하는 custom 함수를 만들어 사용할건데

    이 함수를 다룰 줄 모르면 노답인 상황이 된다.


    [ sigaction() ]

     

     

    signal 수신 시 원하는 행동을 취할 수 있게 한다.

     

    보통 프로그램 코드 맨 위에 미리 지정하고 시작한다.

    이게 그림에서는 2번 과정이다.

    우선 프로그램한테 특정 시그널이 호출되었을때 어떤 핸들러를 호출할 것인지 알려주고 시작해야한다!

     

    handler로 시그널들을 catch해주면 비정상종료가 되지 않고 해당 handler 내 코드를 실행한다.

    그리고 handler에서 그냥 exit(0)을 할지 아니면 main으로 돌아가서 계속 코드를 실행할지 결정할 수 있다.

     

     

    1. 시그널처리함수 구조체 sigaction 생성

    2. 시그널이 받아지면 수행될 시그널 처리함수 설정. 이건 custom 함수로 사용자정의 함수이다.

    3. sigaction을 통해 어떤 시그널에 대한 것인지 지정. 여기서는 SIGUSR1이 들어오면 act 처리함수구조체를 참조한다 이 의미임.

     

     

    이 위의 3줄의 코드는 child에서 아무것도 하지 않는다.

    그냥 시그널관련설정을 해준 것이다.

     

    따라서 실제로 child process는 pause()부터 시작하게 된다.

    자신한테 특정 signal이 들어오는 것을 기다리는게 child process 가 제일 먼저 하는 일이라고 생각해도 무방함

     


    [struct sigaction]

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

     

    앞에 sa 접미사 붙는건 sigaction의 약자다.

     

    1. sa_handler : signal handler 처리함수(SIG_DFL ,SIG_IGN, catchint, SIGUSR1 등이 들어감)

    2. sa_mask : 차단할 시그널 집합.

    시그널 처리 시 블록 지정할 시그널 집합. 타입이 sigset_t 인 것으로 집합임을 알 수 있다.

    sigaction 구조체의 sa_handler가 동작 중일때 처리를 블록할 signal_set을 정한다.

     


    [ sigprocmask() - SIGNAL BLOCKING]

    signal + process + mask

     

    한 개의 process 마다 blocked signal sigset이 존재함.

    이 blocked signal에 sigprocmask를 통해 blocking할 signal들을 추가할 수 있음

    이때 sigprocmask 작업 전에 sigemptyset sigaddset sigdelset 등을 사용해서 sigset을 설정한다.

     

    1. SIG_BLOCK

    process의 blocked signal 집합에 추가

    2. SIG_UNBLOCK

    process의 blocked_signal 집합에서 제거

    3. SIG_SETMASK

    blocked_signal 집합을 아예 비우고 재설정. 추가하는게 아님!

     

    // empty and add signal you want to block
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    
    sigprocmask(SIG_BLOCK, &set, NULL);
    
    // set sigmask before certain task
    sigprocmask(SIG_SETMASK, &mask, NULL);
    
    // remove signal after the task
    sigprocmask(SIG_UNBLOCK, &mask, NULL);

    [ alarm(int sec) ]

    1. alarm은 exec()로 실행 프로그램이 변경되도 유지됨.

    2. but fork() 시에는 기존 parent의 alarm이 따라오지는 않음.

    3. alarm(0) == 알람 끄기

    4. alarm은 누적되지 않는다. 맨 마지막 알람으로 덮어씌워짐.

    5. alarm의 return값은 맨 처음 설정하면 0, 연속으로 설정하면 그 전 alarm에서 남은 값을 return

     

    인자로 초 단위 시간을 받음.

    함수로 호출된 이후부터 인자로 지정한 시간이 지나면 SIGALRM이 자동으로 생성되어 자기자신한테 전달됨.

     

    하지만 프로세스는 SIGALRM을 받으면 종료되므로 유의해야함

    다음과 같이 alarm만 호출해주면 알람시간이 되면 프로세스는 걍 바로 종료됨

    #include <stdio.h>
    #include <unistd.h>
    
    // if you just use only alarm(int sec),
    // after SIGALRM is called, the process is finished
    // to call SIGALRM periodically, you have to put alarm in custom_signal_handler
    
    // becuase signal_handler catches the SIGALRM signal,
    // OS thinks that it will handle well and doesnt end the current process
    
    int main(){
            int cnt = 0;
    
            alarm(3);
    
            while(1){
                    printf("cnt : %d\n", cnt++);
                    sleep(1);
            }
    }

     

    진짜 알람처럼 주기적으로 호출받으려면 custom signal handler를 사용해서 그 안에서 SIGALRM을 받아주면 된다.

     

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    
    void custom_handler(int signo) {
            // print signal : prints signal info
            printf("recieved signal %d\n", signo);
    }
    
    int main(){
            // designate custom_handler's signal
            // if SIGALRM is called, this handler catches it and takes care of it
            signal(SIGALRM, custom_handler);
    
            int cnt = 0;
    
            while(1){
                    // after each 3 seconds, SIGALRM is called which is handled by custom_handler
                    if(cnt%3==0){ alarm(3); }
    
                    printf("cnt = %d\n", cnt++);
                    sleep(1);
            }
    
            exit(0);
    }

     

     


    [ 강의메모 ]

    0. SIG_IGN SIG_DFL

    static struct sigaction act;
    
    act.sa_handler = SIG_IGN; // 해당 시그널 무시
    act.sa_handler = SIG_DFL; // 해당 시그널 오면 종료
    act.sa_handler = catchint; // 사용자 정의 함수
    
    sigaction(SIGINT, &act, NULL); // SIGINT 받으면 act로 처리해라

     

    1. exec() 와 fork() 시 signal 유지여부

     

    1-1) fork되면 signal table 도 복사해간다. parent에서 지정한 시그널처리를 child에서도 그대로 하게 된다.

    1-2) A가 B로 exec함.

    이래도 시그널 테이블은 그대로임. 왜냐하면 process는 동일한데 프로그램만 바뀐 것이기 때문.

    A의 process pid가 100이였으면 exec로 B로 바뀌어도 pid는 100임.

    즉 같은 프로세스이므로 프로세스 테이블 내 시그널 테이블은 동일하다.

     

    그래서 A가 fork해서 B를 생성한 후에 B에서 exec해서 새로운 프로그램을 돌려도 시그널 테이블은 동일함!

     

    2. SIGALRM

    알람은 시그널이 아님

    알람은 시그널이 오게 하는거임. SIGALRM이 오는거임.

     

    정해진 시간 후에 SIGALRM이 올거니까 당연히 SIGALRM에 대한 처리를 해놓고 알람을 울려라 

    아니면 그냥 SIGALRM 알람 시그널 오고 바로 종료하는거임.

     

    알람 -> 타이머 작동

    각 프로세스마다 자기 타이머가 있음. 따라서 exec로 프로그램이 바뀌어도 당연히 계속 진행됨.

     

    하지만 fork 후에는 작동하지 않음.

    child를 만들었다고 해서 똑같은 타이머가 작동하는 것은 아님!

     

    소스코드2번 처럼 주기적으로 어떤 코드를 반복해서 실행하고 싶을때 alarm을 사용한다.

     

    3. SIGNAL BLOCK(sigprocmask) 랑 SIG_IGN(act.sa_handler = SIG_IGN)은 아예 다른거임 

     

    sigprocmask(SIGNAL BLOCK)은 시그널 받는데 당장 처리하지 않는거임.

    지금 당장 처리를 안하는대신이 일이 끝나면 당연히 시그널 처리를 할거다. 이 얘기임.

     

    그래서 sigprocmask(SIG_UNBLOCK) 이후에 그동안 적립된 blocked signal들을 받음.

    그래서 만약 특정 signal에 대한 handler를 만들어놓았다면 이때 signal handling이 들어감.

     

    쉽게 말하면 풀기 전까지만 BLOCK하는 거지 푼 다음에는 적립된 signal들을 다 한번에 받는다는 얘기임

     

    act.sa_handler = SIG_IGN 은 진짜로 해당 signal들을 무시하는거임.

    이건 그냥 signal을 받자마자 버려버림. 적립하지를 않음. 

     

    그래서 SIG_BLOCK이랑 SIG_IGN은 아예 다른 얘기임.

     

    4. sigprocmask

    소스코드 3번을 sigprocmask 없이 해보고 scanf 아직 안했을때 ctrl+c를 보내봐라.

    scanf 할때 ctrl+c하면 interrupt가 일어나면서 출력이 됨. 

    입력이 실제로는 안되는데 출력이 되는 이유는 ctrl+c signal이 오면 대기큐에 기다리는 scanf 입출력을 기다리고 있는 프로세스를 깨우는데 이때 깨어나면서 자기가 입력을 받은 줄 알게 되는거임.

     

    그래서 signalblock이 필요함.

    scanf 앞 뒤로 해당 코드를 집어넣어 scanf 전에 sigblock해주고 scanf 후에는 sigblock 해제 해주면 된다.

     

    5. sigprocmask는 같은 종류의 signal이면 딱 1개만 접수함

    ctrl+c 여러번 해도 do not interrupt가 딱 한번만 뜬다.

    즉 인터럽트 시그널이 몇 번이 와도 OS는 딱 1개만 접수한다.

     

    6. pause wait waitpid sleep 모두 프로세스를 대기큐에 집어넣음

     

    pause -> 다른 프로세스로부터 시그널을 받으면 깨어남

    sleep -> 몇 초 후에 알아서 깨어남

    'LinuxProgramming' 카테고리의 다른 글

    [Linux] execl() and execv()  (1) 2023.10.18
    [Linux] wait() and waitpid()  (2) 2023.10.17
    [Linux] pipe()  (0) 2023.10.16
    [Linux] mmap()  (1) 2023.10.15
    [Linux] FIFO  (0) 2023.10.14