Unblocked signal이 발생한 경우 kernel은 software interrupt를 일으켜서 기존에 진행되던 control을 signal handler로 옮겨준다. 그리고 나서 signal handler가 다 실행되고 난 이후에 다시 control을 기존 signal handler를 부르기 전 상태로 옮긴다.
여기서 생긴 의문: 만약 오래 걸리는 코드 (하나의 명령일 뿐이지만 오래 걸리는 명령) 가 실행되고 있었고, 그 상태에서 signal이 감지되어 handler 함수가 작동하게 되었다면, handler 함수 종료 이후에 control은 기존 실행 흐름으로 돌아올 텐데, 그 위치는 정확히 어디일까?
상황에 따라 다르지만 일반적인 경우에는 진행되던 instruction의 바로 다음 instruction 부터 재개된다고 함.
process가 system call에 의해 block된 상태라면 (ex. waiting for I/O) signal이 발생했을 때 그 system call은 error code(-1)와 함께 리턴된다. 그 후에 다시 기존 흐름으로 돌아가서 process가 실행된다.
그래서 기존 흐름 이 뭔데요?
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
void handler(int sig)
{
printf("signal handled!\n");
}
int main(int argc, char **argv, char **envp) {
char buf[1024];
signal(SIGINT, handler);
read(STDIN_FILENO, buf, 5);
printf("hoho\n");
return (0);
}
찾아보니까 sigaction 함수의 sa_flags의 SA_RESTART 값이 있는데, 얘는 시스템 콜 도중에 interrupt가 생기면 시스템 콜을 다시 호출한다고 한다.
즉, 원래 실행되던 시스템 콜 -> signal catch -> signal handler -> return -> 시스템 콜은 error code를 반환 -> 다시 system call 실행 의 단계를 거치고 있었던 것이다.
그냥 signal 함수를 쓰면 시스템 콜은 기본적으로 다시 시작하는 것 같다.
하지만 여기서 sigaction 함수의 강력함이 나타나는데, sa_flags의 SA_RESTART를 0으로 만들어 버리면, 시스템 콜이 error code를 내뱉으며 종료되고 다시 실행되지 않고 다음 instruction으로 넘어가게 된다.
예제 코드
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
void handler(int sig)
{
printf("signal handled!\n");
}
int main(int argc, char **argv, char **envp) {
char buf[1024];
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sigaddset(&sa.sa_mask, SIGINT);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
read(STDIN_FILENO, buf, 5);
printf("hoho\n");
return (0);
}
사실 이 의문이 왜 들었냐하면, bash에서 here_doc 입력을 받는 도중에 SIGINT를 주게 되면, 입력 받던 부분이 모두 종료되고 원래 bash prompt로 돌아가게 되는데, 이전의 입력받던 readline을 signal을 통해 종료시킬 수 있지 않을까? 라는 의문에서 시작되었다.
TMI)