Operating System

SIGNAL

iKay 2020. 10. 29. 23:10
반응형

signal UNIX 시스템에서 어떤 이벤트가 발생한 경우 그것을 프로세스에게 알리는데 신호라고 보면된다.

 

signal의 종류는 터미널에서 다음과 같이 확인 가능하다. 아래는 docker를 사용한 것인데... 그냥 쉽게 설명하면 ubuntu image 상에서 kill -l 을 실행시키고 도커 프로세스를 종료 시킨 것이라 보면된다. 

 

# kill -l 을 입력한 것과 같다. 
$ docker container run --name ubuntu-signals --rm -i -t ubuntu kill -l

HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT
CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

 

HUN INT QUIT ... 순서대로 나오는데 1, 2, 3... 값을 갖는다고 보면된다. 흔히 사용하는게 $ kill -9 일텐데 보면 KILL 9번째에 위치해 있다.

 

signal이 같은 프로세스에서 발생하면 동기적, 프로세스 밖에서 발생해 프로세스로 전달되면 비동기적으로 처리된다. 동기적인 경우는 잘못된 메모리 접근, 어떤 값을 0으로 나누는 등의 연산을 할 때 발생하는 예가 있고, 비동기적인 경우는 [ctrl] + [c] (사실 SIGINT 이다)를 키보드로 눌러서 프로세스를 종료시키는 예가 있다.

 

signal이 발생했을 때 signal handler에 의해 의도된 동작을 할 수 있어야 한다. signal handler의 종류는 다음과 같이 두 가지가 있다.

 

1. default signal handler

 

2. user-defined signal handler

 

default signal handler는 signal이 발생했는 때 커널, 또는 애플리케이션에 의해 기본적으로 동작하는 handler이다. 이것은 애플리케이션 개발자 입장에서는 일단 크게는 신경 쓰지 않아도 될 것 같다. 우리가 필요한 것은 user-defined signal handler 이다.


user-defined signal handler
가 필요한 경우 중 하나는 web application SIGTERM을 받아서 종료되어야 하는 경우, db와 맺고 있던 connection을 종료시켜 줘야 db connection 리소스를 원활히 활용할 수 있을 것이다.

 

실제로 kubernetes pod을 종료시킬 때, pod 내의 프로세스가 gracefully terminate 되도록, SIGTERM 신호를 보낸 후 일정시간 동안 기다린 후(기본 30 ) SIGKILL을 보낸다. 프로세스가 안전하게 스스로 종료될 시간을 주는 것이다. https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination

 

이 시간 사이에 web application은 그래서 SIGTERM을 받으면 db connection들을 close하고 스스로 프로세스를 종료하도록 구현되어야 한다.  그러기 위해서는 web application에 user-defined signal handler가 필요하다.

 

예를 들어, 다음의 Node.JS 애플리케이션은 60초 후에 자동으로 종료된다. 

 

그런데 signal handler가 따로 정의되어 있기 때문에 default signal handler가 user-defined signal에 의해 override 되어, 이 애플리케이션은 SIGINT 즉, [ctrl] + [c] 키보드 이벤트를 받으면, node 프로세스가 종료되지 않고, console.log와 주석 처리된 db.close(); 와 같은 로직이 키를 누를 때마다 반복적으로 실행된다. 그리고 SIGTERM을 받아도 마찬가지이다. 하지만 SIGKILL을 받으면 user-defined signal handler가 정의되어 있지 않으므로 즉시 종료된다.

 

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function test() {
    let n = 1;
    while (true) {
        await sleep(1000)
        console.log(n++);
        if (n === 60) {
            process.exit();
        }
    }
}

test();

process.on('SIGINT', () => {
    console.log('Received SIGINT.');
    // db.close();
});


process.on('SIGTERM', () => {
    console.log('Received SIGTERM.');
    // db.close();
});

 

# -> 출력 
# <- 입력

-> 1
-> 2
...
-> 14
-> 15 
<- kill -INT 64695 
-> Received SIGINT.
-> 16
-> 17
-> 18
-> 19
<- kill -TERM 64695
-> Received SIGTERM.
-> 20
-> 21
-> 22
...
-> 25
<- kill -KILL 64695
-> [1]    64695 killed     node src/index

 

참고

[1] Abraham Silberschatz, Peter Baer Galvin, Greg Gagne. Operating System Principles 7th edition. Wiley, 2004, 135-136

반응형