🔔 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다. 혹여나 틀린 부분이 있다면 지적해주시면 감사드리겠습니다.
#include <signal.h>
int sigaction(int signo, const struct sigaction * act, struct sigaction *oact);
sigaction
을 이용하여 signal에 대한 action을 수정할 수 있다. act
에는 새로 등록할 handler에 대한 정보가 담겨있으며, oact
에 기존 action을 저장한다.
Argument
signo
: signal numberact
: signal에 대한 actionoact
: 기존 action
Return
- 성공 시 0 return
- error 발생 시 -1 return
위에서 act
와 oact
의 구조체는 아래와 같다.
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction) (int, siginfo_t *, void *);
};
sigaction
sa_handler
:int
를 인자로 받는 function의 포인터로, handler에 해당함.
-SIG_IGN
이 오면 해당 signal을 무시,SIG_DFL
가 오면 기존 action으로 복원함.sa_mask
: signal handler가 종료될때까지 block될 signal의 집합.
- 해당 signal들은 무시되는 것이 아니라 blocking됨.sa_flag
: signal을 handling할때의 option에 해당함.
-SA_RESTART
: interrupt되지 않고 다시 시작
-SA_SIGINFO
:sa_sigaction
으로 handler를 지정
sa_sigaction
SA_SIGINFO
flag가 설정되었을 경우에 대체로 사용하는 signal handlersa_handler
와 둘 중에 하나만 선택되어 사용되며, 인수로 전달되는info
는 해당 signal에 대한 추가 정보를 담고 있음.
#include <signal.h>
void catchint(int signo) {
printf(“\nCATCHINT: signo=%d\n”, signo);
printf(“CATCHINT: returning\n\n”);
}
int main() {
static struct sigaction act;
act.sa_handler = catchint;
sigfillset(&(act.sa_mask)); // 모든 signal block
sigaction(SIGINT, &act, NULL);
printf(“sleep call #1\n”);
sleep(1);
printf(“sleep call #2\n”);
sleep(1);
printf(“sleep call #3\n”);
sleep(1);
printf(“sleep call #4\n”);
sleep(1);
printf(“Exiting\n”);
return 0;
}
해당 코드는 SIGINT
에 대하여 handler를 sigaction
을 이용해 설정해주고, signal이 들어오는 것을 기다리기 위해 총 4번 sleep한다. 실행 중간데 ^C
가 입력되어 SIGINT
signal이 발생하면 catchint
가 이를 handling하여 해당 signo
를 출력하게 된다.
#include <signal.h>
static struct sigaction act, oact;
sigaction(SIGTERM, NULL, &oact);
act.sa_handler = SIG_IGN;
sigaction(SIGTERM, &act, NULL);
...
sigaction(SIGTERM, &oact, NULL);
위 코드는 sigaction
을 이용해 handler를 수정한 이후, 기존 action으로 복원해준다. act
대신 NULL
을 이용하여 oact
에 기존 action을 저장하고, 다시 act
를 이용하여 handler를 변경한다. 특정 코드 실행 이후, 다시 oact
를 이용하여 기존 action으로 handler를 지정하는 것을 볼 수 있다.
#include <stdio.h>
#include <stdlib.h>
void g_exit(int s) {
unlink(“tempfile”);
fprintf(stderr, “Interrupted – exiting\n”);
exit(1);
}
SIGINT
와 같은 interrupt가 발생하여 file이 닫히지 않은 경우를 위해서 위와 같이 signal handler를 사용하여 닫히지 않은 상태로 유지되지 않도록 처리할 수 있다.
struct siginfo_t {
int si_signo; /* signal number */
int si_errno; /* if nonzero, errno value from <errno.h> */
int si_code; /* additional info (depends on signal) */
pid_t si_pid; /* sending process ID */
uid_t si_uid; /* sending process real user ID */
void *si_addr; /* address that caused the fault */
int si_status; /* exit value or signal number */
long si_band; /* band number for SIGPOLL */
...
};
alternate signal handler
, 다시 말해 sa_sigaction
에서의 siginfo_t
는 error number
, signal을 보낸 process의 pid와 real user id
, error가 발생한 code 주소
등 위와 같은 정보를 담고 있다. 다시 말해 signal이 왜 발생했는지에 대한 정보를 담고 있는 것이다.
#include <signal.h>
void sig_handler(int sig, siginfo_t *siginfo, void* param2) {
printf(“[Parent:%d]: receive a signal from child %d\n”, getpid(), siginfo->si_pid);
}
int main() {
pid_t pid;
static struct sigaction act;
act.sa_sigaction = sig_handler;
act.sa_flags = SA_SIGINFO;
sigfillset(&act.sa_mask);
sigaction(SIGUSR1, &act, 0);
int i = 0;
while(pid=fork()) {
printf(“[Parent:%d]: create child %d\n\n”, getpid(), pid);
if(i++==3) break;
}
if (pid>0)
getchar();
else
kill(getppid(), SIGUSR1);
return 0;
}
위 코드는 sa_sigaction
에 대한 예제이며, sig_handler
의 인자로 siginfo
를 전달받아 이에 대한 pid
를 출력하는 것을 볼 수 있다.
⭐ 출력 관련해서는 이후에 공부 이후 추가 예정
signal은 보통 system call이 끝나기 전까지 block된다. 하지만 slow system call
일 경우에는 system call이 interrupt되고, -1을 return
한다. 이때, errno = EINTR
이다.
Slow system calls
- pipe, terminal or network device
- pause, wait
- interprocess communication function
device
의 경우 언제 끝날지 예측이 쉽지 않기 때문에 slow
에 해당된다. (그냥 file에 대한 I/O는 이에 해당되지 않는다.) process간의 통신 function
의 경우도 마찬가지다.
system call이 중단됨에 따라 error return을 명시적으로 해주어야 할 필요가 있다. 만일 device에 대한 I/O의 진행 중에 interrupt가 발생해 system call이 interrupt 되었다면, 어느 시점부터 I/O가 잘못된 것인지 판단이 불가능하다.
if ((write(tfd, buf, size) < 0) {
if (errno == EINTR) {
warn(“Write interrupted”);
}
}
따라서 아래와 같이 수정하여 다시 처음부터 처리할 필요가 있다.
again:
if ((write(tfd, buf, size) < 0) {
if (errno == EINTR) {
warn(“Write interrupted”);
goto again;
}
}
이와는 다른 방법으로 sigaction
을 이용하여 SA_RESTART
flag를 설정해주는 것이다. 해당 flag가 설정되면, interrupt되는 것이 아니라 다시 시작되기 때문에 errno
도 설정되지 않고 다시 시작하게 된다.
process에도 signal mask
가 존재하며, sigaction
의 act에서의 mask
가 process의 signal mask
에 포함되어 작동된다. handling된 signal 역시 signal mask
에 포함되게 되며, handler가 종료되면 기존 mask 값으로 재설정된다.
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);
sigsetjmp
는 특정 순간을 타임스탬프처럼 지정하며, siglongjmp
는 지정된 위치로 다시 돌아가는 역할을 한다.
Argument
sigjmp_buf env
:sigsetjmp
는 여기에 정보를 저장하며,siglongjmp
는 이를 이용해 해당 위치로 이동int savemask
: process의 signal mask를 같이 저장할지에 대한 여부를 결정int val
:sigsetjmp
의 return value
Return
sigsetjmp
: 직접적으로 call되었을 경우에는 0을 return,siglongjmp
를 통해 call되었을 경우에는val
을 return
#include <sys/types.h>
#include <signal.h>
#include <setjmp.h>
#include <stdio.h>
sigjmp_buf position;
// 출력 이후 jump
void goback(void) {
fprintf(stderr, “\nInterrupted\n”);
siglongjmp(position, 1); /* Go back to the position */
}
int main() {
static struct sigaction act;
...
// 처음 호출될 경우 실행
if (sigsetjmp(position, 1) == 0) {
act.sa_handler = goback;
sigaction(SIGINT, &act, NULL);
}
// jump 이후 실행
domenu();
...
}
위 코드는 sigsetjmp
와 siglongjmp
의 활용 예제이다. 여기서 save mask
를 true
로 설정해주었는데, 이를 설정해주지 않으면 siglongjmp
에 의해 goback
함수가 끝까지 실행되지 않아 해당 signal은 block된 상태가 지속되게 된다. 이를 1로 설정해주면 이러한 경우에도 mask
를 원래대로 복원해주기 때문에 설정하였다.
중요한 코드를 실행하는 과정에서 interrupt가 되지 않도록, 즉 signal에 의해 blocking되지 않도록 하는 것이 가능하다. 이는 해당 작업을 완료할때까지 신호를 처리하지 않으며, signal을 무시하는 것은 아니다.
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
sigprocmask
는 해당 process로 전달된 signal들이 block되도록 한다.
Argument
int how
:sigprocmask
를 어떻게 수행할지 결정
-SIG_BLOCK
: 기존에 있는 set에 추가
-SIG_UNBLOCK
: 기존에 있는 set에서 제거
-SIG_SETMASK
: 해당 set으로 지정const sigset_t *set
: 전달할 signal maskconst sigset_t *oset
: 현재 signal mask를 저장
Return
- 성공 시 0 return
- error 발생 시 -1 return
#include <signal.h>
int main () {
sigset_t set1, set2;
// set1은 전체
sigfillset(&set1);
// set2는 2개 제외 전체
sigfillset(&set2);
sigdelset(&set2, SIGINT);
sigdelset(&set2, SIGQUIT);
// set1로 대체 = 전부 block
sigprocmask(SIG_SETMASK, &set1, NULL);
// set2를 제외 = 2개만 block
sigprocmask(SIG_UNBLOCK, &set2, NULL);
// 전체를 unblock
sigprocmask(SIG_UNBLOCK, &set1, NULL);
}
위 코드는 sigprocmask
에 대한 각 how
의 활용 예제이다.
process는 자기 자신에게, 다른 process에게 혹은 process group에게 signal
을 보낼 수 있다.
#include <signal.h>
int kill(pid_t pid, int signo);
kill
은 process나 process group에게 signal을 보낸다. sender process는 signal을 받을 receiver process의 pid를 알 필요가 있으며, 이는 주로 child와 parent와 같이 밀접하게 관련된 process 사이에서 활용한다.
또한 sender의 real or effective uid
가 receiver의 effective uid
와 동일해야 signal을 보낼 수 있다. super user
는 누구에게나 signal을 보낼 수 있다.
Return
- 성공 시
return 0
- error 발생 시
return -1
Arguments
pid
: pid에 따라 보낼 대상이 달라짐
-> 0
: process ID가 pid인 process에게
-== 0
: sender의 process group ID와 동일한 process group ID를 가진 모든 process에게
-< 0
: pid의 절댓값과 동일한 process group ID를 가지는 모든 process에게
-== -1
: sender가 signal을 보낼 권한이 있는 모든 process에게signo
: 보낼 signal의 signal number
/* synchro */
#include <unistd.h>
#include <signal.h>
int ntimes = 0;
void p_action (int), c_action (int);
int main() {
pid_t pid, ppid;
static struct sigaction pact, cact;
pact.sa_handler = p_action;
sigaction(SIGUSR1, &pact, NULL);
switch (pid = fork()) {
case -1:
perror (“synchro”);
return 1;
case 0:
cact.sa_handler = c_action;
sigaction (SIGUSR1, &cact, NULL);
ppid = getppid();
for (;;) {
sleep(1);
kill(ppid,SIGUSR1);
pause();
}
default:
for (;;) {
pause();
sleep(1);
kill(pid,SIGUSR1);
}
}
return 0;
}
void p_action (int sig) {
printf (“Parent caught signal #%d\n”, ++ntimes);
}
void c_action (int sig) {
printf (“Child caught signal #%d\n”, ++ntimes);
}
위 코드는 kill
을 활용하는 예제이며, p_action
과 c_action
은 handler로 몇 번째인지를 출력하는 역할을 한다. parent와 child가 서로 signal을 주고받는 것을 반복하는 코드이며, handler는 fork시 자식에게 유전된다는 것을 유의해야 한다.
#include <signal.h>
int raise(int sig);
raise
는 자기 자신에게 signal을 보낸다.
Return
- 성공 시
retur 0
- error 발생 시
return -1
#include <unistd.h>
unsigned int alarm(unsigned int secs);
alarm
은 말 그대로 특정 시간 이후에 SIGALRM
signal을 스스로에게 보내는 역할을 한다. 기본 동작은 process 종료이며, alarm
은 하나만 존재한다. 즉, 다시 alarm
을 call하면 처음에 call한 alarm
을 대신하게 된다. alarm(0)
은 만료되지 않은 alarm
을 종료시킨다.
Return
- 이전에 설정된
alarm
의 초를 return- 이전
alarm
이 없는 경우return 0
#include <unistd.h>
int pause(void);
pause
는 signal이 전달될 때까지 process를 sleep하도록 한다.
Return
- signal이 catch되고, catch function이 return된 경우에만 return
return -1
과 함께errno = EINTR
signal에 대한 action이 process 종료일 경우에는 pause
는 return하지 않으며, signal을 catch하는 function을 실행하는 것이라면 해당 function이 return한 이후에 pause
가 return된다.