Signal
當特定事件發生時,會發送信號來通知 user process
- 非同步 : 可以發生在任何時間、每個信號之間沒有順序之分、也不會互相等待
- 可以被看成是另類的 IPC
- 每個信號都有自己的代號 (SIG...) 以及代碼 (positive integer defined in
<signal.h>
) - 不同的系統支援的信號種類不同 (ex: Unix version 7 只支援 15 種信號)

信號運作流程
Signal Types
- Terminal-generated signals
- SIGINT (2): sent when
^c
was pressed - SIGKILL (9): sent to terminate the process
- SIGINT (2): sent when
- Signals from hardware exceptions
- SIGFPE (8): divided-by-zero
- SIGSEGV (11): illegal memory access (==Segmentation Fault==)
- Signals generated by software conditions
- SIGPIPE (13): a process that writes to a pipe that has no reader
- SIGALRM (14): expiration of an alarm clock
more: https://dsa.cs.tsinghua.edu.cn/oj/static/unix_signal.html
Signal Disposition/Action
對每個 process 而言,可以告訴 kernel 要對不同信號做何種行為 :
- ignore 該信號
- 用 process 自身的 signal handler 來 catch 該信號
- 執行預設動作
Program Start-up
fork()
: child 會繼承 parent 的 signal dispositionsexec()
: process 會 reset 成 default handler

不同信號的預設 disposition
core
- 當 program crashes 或 exits abnormally 時,便會產生 core dump file 來記錄 program states
- 通常被用來協助 debug (ex: loaded to gdb)
- core dump file 不會產生的情況 :
- Set-UID/GID process: real UID/GID != program’s UID/GID
- No write permission to the directory
- 空間不夠
- 在 Linux 裡面該檔案名稱被 /proc/sys/kernel/core_pattern 所指定
設定 disposition : signal()
// returns the old disposition of the signal signum if OK, SIG_ERR on error
typedef void (*sighandler_t) (int); // data type of signal handlers
sighandler_t signal(int signum, sighandler_t handler);
signal() 用來設定信號 signum 的 signal handler handler
- handler : signal handler function 的指標,可以是
自定義函數 /
SIG_IGN
:ignore /SIG_DFL
":default
SIGKILL 和 SIGSTOP 只能執行預設動作,分別是 terminate process 和 stop process
Interrrupted System Calls
當 process 在做 system call 時,一個信號可能被傳遞給 process 並打斷 system call,有兩種結果 :
- 如果建立 signal handler 時設定了 SA_RESTART,則當 singanl handler 返回後,system call 會自動重做
- system call 返回錯誤,
errno
= EINTR 可以使用以下程式來重做 system call :again: if ((n = read(fd, buf, BUFFSIZE)) < 0) { if (errno == EINTR) goto again; }
Reentrant Functions
由於 signal handler 無法知道在收到信號之前 process 運行到何處,因此有可能同一個 function 在主程式被呼叫後,又被 signal handler 呼叫一次
舉例來說,getpwnam
會將 struct passwd 存在一個 static memory 裡面,並返回該 struct 的 pointer,因此有可能在連續呼叫兩個 getpwnam 時被覆蓋
static void my_alarm(int signo) {
struct passwd *rootptr;
printf("in signal handler\n");
if ((rootptr = getpwnam("root")) == NULL)
err_sys("getpwnam(root) error");
alarm(1);
}
int main(void) {
struct passwd *ptr;
signal(SIGALRM, my_alarm);
alarm(1);
for ( ; ; ) {
if ((ptr = getpwnam("sar")) == NULL)
err_sys("getpwnam error");
if (strcmp(ptr->pw_name, "sar") != 0)
printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
}
}
當一個 function 像這樣被遞迴呼叫時會產生問題,我們稱它是 non-reentrant function
怎樣會讓一個 function non-reentrant ?
- 使用 static/global variables & data 來和其他 functions 互動
- 使用
malloc()
orfree()
: 因為它們運用 static global data structure - 在沒有備份的情況下更改
errno
- 呼叫 non-reentrant functions
因此,如果一個 function 可以被遞迴呼叫而不會有問題,則稱為 reentrant function
怎樣讓一個 function reentrant ?
- 不要用 static/global variables & data,不要返回 static memory 的 pointer
- 在使用 signal handler 前先配置好相關的 memory (不要用
malloc()
) - 在更改前先儲存
errno
- 不要呼叫任何 non-reentrant functions
Signal 的操作
- a signal is pending : 以該 process 為目標的信號已被產生,但 process 還未處理該信號
- a signal is delivered to the process : process 已經在處理該信號
- process 可以選擇 blocking 信號的傳送 :
如果被 block 的信號的目標是該 process,並且該 process 對於該信號的 disposition 並非 ignore,則該信號會 pending 直到
- process unblocks the signal
- 對該信號的 disposition 變成 ignore
(可以透過
sigpending()
來確認目前該信號的狀態)
- 每個 process 都有一個 signal mask 來決定要被 blocking 的 signal set
(可以透過
sigprocmask()
來獲取或更改 signal mask)
如果相同信號在被 unblock 之前產生了很多次
- POSIX.1 允許系統可以傳遞多次信號 (信號會排隊)
- Linux 不會讓相同信號進入 queue,如果許多相同的信號在 pending,只有一個可以被 delivered to the process
如果有很多信號都準備好被 delivered to the process
- POSIX.1 沒有規定信號傳遞的順序
- POSIX.1 建議跟 current state 有關的信號 (e.g. SIGSEGV) 應該被優先傳遞
signal sets
// All return: 0 if OK, -1 on error
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
// Return 1 if true, 0 if false, -1 on error
int sigismember(const sigset_t *set, int signo);
更改 signal mask : sigprocmask()
// Returns 0 if OK, -1 on error
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
- how :
SIG_BLOCK
: set 包含要被 block 的信號 (加入現有 signal mask)SIG_UNBLOCK
: set 包含要被 unblock 的信號 (移出現有 signal mask)SIG_SETMASK
: set 就是新的 signal mask
- set : 包含要更動的信號 (如果為
NULL
則 signal mask 不會被更動) - oset : 儲存之前的 signal mask (設成
NULL
來忽略)
在呼叫 sigpromask() 之後,如果有任何正在 pending 的信號被 unblock,則在 sigpromask() 回傳之前至少有一個信號會被 delivered 給 process
獲取 pending 信號 : sigpending()
// Returns 0 if OK, -1 on error
int sigpending(sigset_t *set);
sigpending() 會將目前正在 pending 的 signal 存入 set
傳遞信號 : kill()
// Returns 0 if OK, -1 on error
int kill(pid_t pid, int signo);
kill() 會傳遞信號 signo 給 pid
- pid > 0 : 傳給 process pid
- pid == 0 : 傳給所有和 sender 有相同 gid 的 process
- pid < 0 : 傳給所有 gid == |pid| 的 process
- pid == -1 : 傳給所有 process
可以透過將 signo 設成 0 (null signal) 來測試 process 是否存在
- 不存在 : kill() return -1,errno =
ESRCH
- 存在 : kill() return 0,但實際上不會有任何信號被傳遞
這項測試並非 atomic : 當 kill() return 時,process 可能已經 exited
權限問題
- superuser 可以傳信號給任何 process
- sender’s real UID or effective UID == receiver’s real UID or effective UID
- 如果系統支援
_POSIX_SAVED_IDS
: receiver’s saved-set-UID is checked instead of effective UID
- 如果系統支援
傳遞信號給自己 : raise()
// Returns 0 if OK, -1 on error
int raise(int signo);
相當於 kill(getpid(), signo)
設定 alarm : alarm()
unsigned int alarm(unsigned int seconds);
alarm() 會設定一個 alarm,並在 seconds 秒後過期並傳遞 SIGALRM
給 caller
- 一個 process 同時間只能有一個 alarm,當 seconds 不為 0 時,舊的 alarm 會被覆蓋
- 如果 seconds 為 0,則所有在 pending 的 alarm 被取消
alarm() 會回傳當前的 alarm 還剩多少秒,如果沒有 alarm 則回傳 0
default action for SIGALRM
is terminate the process,記得在呼叫 alarm() 之前註冊 signal handler !
暫停 process : pause()
int pause(void);
pause() 會暫停 caller 直到==某個信號被 catch== (terminate process 或觸發 signal handler)
只有當 signal handler 被觸發並返回後,pause() 才會返回 -1,errno = EINTR
更改 disposition : sigaction()
// Returns 0 if OK, -1 on error
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
我們無法更改 SIGKILL 和 SIGSTOP 的 action
sigaction() 用來檢查或更改信號 signo 的 action
- act : 新的 action (如果是
NULL
則不更改) - oact : 用來儲存之前的 action
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
}
- sa_handler : 指向 signal handler function 的指標,或是
SIG_DFL
、SIG_IGN
- sa_mask : 在 signal handler 的執行過程中要 block 的其他信號
- 在呼叫 signal handler 之前 : 將 現在要被 delivered 的信號 +
sa_mask
內的信號 加入 signal mask - signal handler 結束後 : 回復成原本的 signal mask
- 在呼叫 signal handler 之前 : 將 現在要被 delivered 的信號 +
- sa_flags : 用來調整信號的行為 (詳細見 manual)
- sa_sigaction : 用來指定另外的 signal handler function,如果
sa_flags
包含SA_SIGINFO
,則使用 sa_sigaction 而非 sa_handler- siginfo : 包含了信號產生原因等資訊 (詳細見 manual)
Example of alarm() & pause()
#include <signal.h>
#include <unistd.h>
static void sig_alrm(int signo){
/* nothing to do, just return to wake up the pause */
}
unsigned int sleep1(unsigned int seconds){
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(seconds);
alarm(seconds); /* start the timer */
pause(); /* next caught signal wakes us up */
return(alarm(0)); /* turn off timer, return unslept time */
}
這段程式有三個問題 :
- 呼叫者之前的 alarm 會被
alarm()
給覆蓋掉
solution: 查看alarm()
的返回值... if((oldalarm = alarm(0)) > 0){ if(oldalarm < seconds){ alarm(oldalarm); pause(); return(alarm(0)); } else{ alarm(seconds); pause(); return(alarm(oldalarm - seconds)); } } ...
- 呼叫者之前對
SIGALRM
的 disposition 會被signal()
給覆蓋
solution: 儲存之前的 disposition 並在結束時復原... if((oldhandler = signal(SIGALRM, sig_alrm)) == SIG_ERR){ return seconds; } ... signal(SIGALRM, oldhandler);
alarm()
和pause()
之間可能發生 race condition :alarm()
在pause()
之前就過期了
solution: use signalstatic jmp_buf env; static void sig_alrm(int signo){ longjmp(env, 1); } unsigned int sleep1(unsigned int seconds){ if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(seconds); if(setjmp(env) == 0){ alarm(seconds); /* start the timer */ pause(); /* next caught signal wakes us up */ } return(alarm(0)); /* turn off timer, return unslept time */ }
fork() v.s. exec() — signal
fork() | exec() | |
---|---|---|
signal mask | inherit | remain |
pending alarms, signals | clear and not inherit | remain |
signal disposition | inherit | if the signal is not ignored, then reset to default ; if ignored, then left ignored |
Nonlocal Jump
在 c 語言裡面,我們不能 goto 位在其他 function 的 label
nonlocal jumps 讓程式可以 goto 程式的任意位置
Stack Frame
Calling Convention :
用來規範呼叫函數時,傳遞的參數要存在哪,返回值要返回到哪等資訊
同時約束 caller (呼叫其他 function 的 function) 和 callee (被呼叫的 function,正在執行的都是 callee)
Stack Frame :
複習 memory layout
當一個 function 被呼叫時,stack 裡面會保留一段空間,也就是 stack frame,給該 function,用來存放以下資訊 :
- return address (回傳給 caller)
- arguments (從 caller 傳過來的參數,舉例來說
do_line(char *ptr)
的ptr
) - saved registers
- automatic variables (在程式流進入和離開變數的範圍時,會自動 allocate 和 deallocate)
當該 function 返回後,stack frame 就會被釋放
int main(void) {
char line[MAXLINE];
while (fgets(line, MAXLINE, stdin) != NULL)
do_line(line);
exit(0);
}
char *tok_ptr; /* global pointer for get_token() */
/* process one line of input */
void do_line(char *ptr) {
int cmd;
tok_ptr = ptr;
while ((cmd = get_token()) > 0) {
switch (cmd) { /* one case for each command */
case TOK_ADD:
cmd_add();
break;
}
}
}
void cmd_add(void) {
int token;
token = get_token();
/* rest of processing for this command */
}
int get_token(void) {
/* fetch next token from line pointed to by tok_ptr */
}

執行 nonlocal jump - setjmp(), longjmp()
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
setjmp() 會建立待會要跳轉的目標
- 將目前的 calling environment (stack, CPU registers) 記錄到 env 裡面
- env 通常是 global variable,由 setjump() 建立再由 longjmp() 使用
- 被直接呼叫時會返回 0 ; 經由 longjmp() 被呼叫則返回非 0
longjmp() 會執行跳轉的動作
- 使用 env 將程式跳轉到先前 setjmp() 被呼叫的時候,並==將 environment 回復成呼叫== ==setjmp()== ==時的情況==
- 如果跳轉成功,則程式會像是 setjmp() 返回了第二次後繼續執行
- val 就會是 setjmp() 返回的值
注意
- 呼叫一次 setjmp() 後,我們可以呼叫 longjmp() 多次,並用不同的 val 來辨別從何處返回
- 不同的 setjmp() 最好使用不同的 jmp_buf,否則可能會被覆蓋掉
Problem of Nonlocal Jump
- 變數是否會回復取決於變數的儲存位置
- memory : 會維持在呼叫
longjmp()
時的狀態- static int, valotile int, global variable
- CPU, registers : 會回復到首次呼叫
setjmp()
時的狀態- int, register int
- memory : 會維持在呼叫
- 我們只能對已被呼叫但還沒結束的 function 做
longjump()
,舉例 :jmp_buf env; P1(){ P2(); P3(); } P2(){ if(setjmp(env)){ /* long jump to here */ } } P3(){ longjmp(env, 1); /* can't jump to P2 cuz it's terminate */ }
- POSIX 沒有規範 setjmp() 是否要將 signal mask 存到 env 裡面
- FressBSD 8.0 and Mac OS X 10.6.8 :
setjmp()
和longjmp()
會儲存並回復 signal mask - Linux : 不會
- FressBSD 8.0 and Mac OS X 10.6.8 :
儲存 sigmask 的 jump : sigsetjmp(), siglongjmp()
// Returns: 0 if called directly, nonzero if returning from a call to siglongjmp
int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);
基本上和 setjmp() 和 longjmp() 一樣
當 savemask ≠ 0 的時候,sigsetjmp() 會將 signal mask 也儲存到 env 裡面
Signal Blocking
這是一個錯誤的 signal blocking 範例
sigset_t newmask, oldmask;
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/* block SIGINT and save current signal mask */
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
/*
* critical region of code
* will not be interrupted by SIGINT
*/
/* restore signal mask, which unblocks SIGINT */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
/* window is open */
pause(); /* wait for signal to occur */
/* continue processing */
問題 :
如果信號在 window is open
區塊被 delivered
那 pause() 就不會接收到信號並卡死
解法 :
需要一個 atomic operation 來做
回復 signal mask 和 pause() 兩個動作
確保所有信號都在 pause() 之後才被 delivered
暫時替換 sigmask 然後 pause : sigsuspend()
\#include <signal.h>
// Returns −1 with errno set to EINTR (If it returns to the caller)
int sigsuspend(const sigset_t *sigmask);
sigsuspend() 會先將目前的 signal mask 替換成 sigmask,接著暫停 process 直到兩種情況 :
- ==caught 到某個信號== → 在 signal handler 之後返回
- ==terminate process 的信號發生== → 不返回 當 sigsuspend() 返回時,它會將 signal mask 復原成呼叫 sigsuspend() 之前
正確的範例:
// supose signal() is implemented by sigaction()
sigset_t newmask, oldmask, waitmask;
signal(SIGINT, sig_handler);
signal(SIGUSR1, sig_handler);
sigemptyset(&waitmask);
sigemptyset(&newmask);
sigaddset(&waitmask, SIGUSR1);
sigaddset(&newmask, SIGINT);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
/*
* critical region of code
* will not be interrupted by SIGINT
*/
sigsuspend(&waitmask);
/* handl SIGINT here */
sigprocmask(SIG_SETMASK, &oldmask, NULL);

正確範例的 signal blocking 順序
Example of Parent-Child Sync
之前我們用 IPC 來處理 現在我們也可以用 signal 來達成 synchronization
static volatile sig_atomic_t sigflag;
static sigset_t newmask, oldmask, zeromask;
static void sig_usr(int signo){
sigflag = 1; /* one signal handler for SIGUSR1 and SIGUSR2 */
}
void TELL_WAIT(void){
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR2) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);
/* Block SIGUSR1 and SIGUSR2, and save the current signal mask */
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}
void TELL_PARENT(pid_t pid){
/* tell parent we’re done */
kill(pid, SIGUSR2);
}
void WAIT_PARENT(void){
while (sigflag == 0)
sigsuspend(&zeromask);
sigflag = 0;
/* Reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
void TELL_CHILD(pid_t pid){
/* tell child we’re done */
kill(pid, SIGUSR1);
}
void WAIT_CHILD(void){
while (sigflag == 0)
sigsuspend(&zeromask);
sigflag = 0;
/* Reset signal mask to original value */
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
terminate process abnormally : abort()
// The function never returns
void abort(void);
abort() 會讓 process terminate abnormally
-
unblock
SIGABRT
然後對 caller process 發送SIGABRT
-
SIGABRT
的 default action 是 terminate the process- 如果
SIGABRT
被 ignored 或是被其他 handler catch,abort() 會復原SIGABRT
的 default action 然後再次發送SIGABRT
- 如果
-
POSIX.1 裡面的 abort()
void abort(void){ sigset_t mask; struct sigaction action; /* Caller can’t ignore SIGABRT; if so reset to default */ sigaction(SIGABRT, NULL, &action); if (action.sa_handler == SIG_IGN) { action.sa_handler = SIG_DFL; sigaction(SIGABRT, &action, NULL); } /* For default action, POSIX requires flushing all open stdio streams */ if (action.sa_handler == SIG_DFL) fflush(NULL); /* Caller can’t block SIGABRT; make sure it’s unblocked */ sigfillset(&mask); sigdelset(&mask, SIGABRT); /* mask has only SIGABRT turned off */ sigprocmask(SIG_SETMASK, &mask, NULL); kill(getpid(), SIGABRT); /* send the signal */ /* If we’re here, caller process has caught SIGABRT and returned */ fflush(NULL); /* flush all open stdio streams */ action.sa_handler = SIG_DFL; sigaction(SIGABRT, &action, NULL); /* reset to default */ sigprocmask(SIG_SETMASK, &mask, NULL); /* just in case ... */ kill(getpid(), SIGABRT); /* and one more time */ exit(1); /* this should never be executed ... */ }
暫停 process 一段時間 : sleep()
// Returns: 0 or the number of unslept seconds
unsigned int sleep(unsigned int seconds);
sleep() 會暫停 caller process 直到 :
- 實際時間已經過了 seconds 秒 → 返回 0
- process catch 到某個信號 → 返回 seconds left
用 pause() 來實作 sleep()
unsigned int sleep(unsigned int seconds){
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
/* set our handler, save previous information */
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);
/* block SIGALRM and save current signal mask */
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(seconds);
suspmask = oldmask;
/* make sure SIGALRM isn’t blocked */
sigdelset(&suspmask, SIGALRM);
/* wait for any signal to be caught */
sigsuspend(&suspmask);
/* some signal has been caught, SIGALRM is now blocked */
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL); /* reset previous action */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return(unslept);
}