VinSong's Blog

Back

NTU-SP 系統程式設計 Ch7 SignalBlur image

Signal#

Signal v.s. Interrupt

  • Interrupt:對 CPU 中斷,有重要的 event 來了,一定要先做
  • Signal:任何事件的發送
    • Synchronous:內部 event 出現馬上發送(e.g. divide by 0)
    • Asynchronous:外部發送的

Type of Signal#

Terminal-generated signals

  • DELETE key, Ctrl-C: SIGINT (2)

Signals from exceptions

  • divided-by-0: SIGFPE (8)
  • illegal memory access: SIGSEGV (11)

Shell command kill

  • kill –9 pid (SIGKILL)

Function kill

  • Owner or superuser

Signals because of software conditions

  • reader of the pipe terminated: SIGPIPE
  • expiration of an alarm clock: SIGALRM

Action (disposition) of Signal#

Ignore signals

  • 直接忽略

Catch signals

  • 由程式自己定義訊號處理函式(signal handler function)來處理 signal
  • SIGCHLD

Apply the default action

  • 這是作業系統預設的反應,除非程式自行改變它。
    • Terminate, ignore, stop
  • SIGKILL: 無法 Ignore 或 Catch
  • SIGSTOP 無法 Ignore 或 Catch

Example of Signal#

SIGABRT

  • Call abort()
  • 要求 OS 送一個 SIGABRT 來殺了自己,收到後 process 死亡
  • terminate with core

SIGALARM

  • Call setitimer()
    • ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF
  • terminate

SIGCHLD

  • ignore

SIGFPE

  • Divid-by-0, floating point overflow
  • terminate with core

SIGHUP

  • 以前主機跟螢幕分開,需要用來告訴 terminal (no daemons) 斷線了
  • 現在用來叫 daemon 重新讀取 configuration file
  • terminate

SIGINT

  • terminate
  • DELETE key, CTRL-C

SIGIO

  • asynchronous I/O event
  • 會讀完,但不需要等,讀完後會收到 SIGIO

SIGKILL

  • 無法 ignore

SIGPIPE

SIGPWR

  • UPS (不斷電系統),斷電時要有能力把東西從 cache 寫到 DISK
  • 快沒電時會送 SIGPWRinit,進行收尾動作

SIGQUIT

  • SIGINT 更加強制性
  • 會做 core dump

SIGSEGV

  • 不合法記憶體

SIGSTOP

  • 類似 SIGTSTP 但無法 ignore, catch

SIGTERM

  • kill command 會送

SIGTSTP

  • 暫停而已

SIGURG

  • out-of-band
  • 跟 networking 有關

SIGUSR1, SIGUSR2

  • user define

System call#

void (*signal(int signo, void (*func)(int)))(int);

  • typedef void Sigfunc(int)
    Sigfunc *signal(int, Sigfunc *); 
    c
  • SIG_IGN 就可以 ignore
  • return 值會是上次的 address setting
  • 不同的 signal number 也可以 call 同一個 signal handler

Problem of Signal#

Signal succeed#

  • fork() 會把所有註冊的行為完全繼承
  • exec() 不一定,以下是轉換圖
    • 除了 Ignore 其餘全都 Default 截圖 2025-11-06 上午10.43.53

Signal Start-up#

  • 當有些 process 我想讓他變成 background (using double fork()),有些 foreground 的 Signal 我不能讓他收到
    • disposition of SIGINT will be set to Ignore by shell

Interrupt System call#

  • 這種 Slow system call (not DISK I/O) 必須被 Interrupt
    • pause():需要一個非 ignore 的 signal 叫醒

Auto Restart#

  • 為了避免上面的 example 用法,有些 function 會設定成 auto restart
  • 但這有時候會違反 Interrupt 設計的原則
    • slow system call 應該要被 interrupt
    • signal 可能是來救這整支程式的,如果右 auto restart 就會 forever block
    • IPC funciton, wait, read, write

Reentrant Function#

  • 這種 Recursive 結構,本來可以寫,但在這種情況下,Signal 來的時間不可控,會出現問題
  • malloc() 會動到一個 linklist,是一個 global variable,上一個 malloc() 的 linklist 還沒弄好,下一個直接進來會爆炸
  • 所以有一些 function 是 non-reentrant 的
  • 以下都是 non-reentrant function
    • 雖然 printf() 是 non-reentrant 但是 debug 的時候不要複雜化問題,直接寫就好 截圖 2025-11-06 上午11.21.48

Unreliable Signals#

Action reset to Default

  • Action 會被自動 reset to Default 要解決
  • Solution 1
    • 典型的 race condition
    • 以下這種解方也會出問題
    int sig_int();
    ...
    signal(SIGINT, sig_int);
    ...
    sig_int() {
        // 如果 Signal 出現在這就會出問題
        signal(SIGINT, sig_int);
        ...
    }
    c
  • Solution 2
    • global variable 初始必定為 0
    • TOCTTOU 問題,典型 race condition
    int sig_int_flag;
    
    main() {
        ...
        signal(SIGINT, sig_int);
        ...
        while (sig_int_flag == 0)
            // 此處來了 signal 你就不會動了
            pause();
    }
    
    sig_int() {
        signal(SIGINT, sig_int);
        sig_int_flag = 1;
    }
    c

Reliable Signals#

  • 紀錄時間,把它分成兩個 phase 截圖 2025-11-06 上午11.53.04
    • signal generated,這是無法控制的情況
    • 一直到 signal delivered 前都可以改變 action
    • 在 signal generated, signal delivered 之間被 block 住就是叫 signal pending
  • 除非我兩個情況,不然 Signal 永遠 pending (block)
    • action 設定 ignore
    • unblock
  • 如果 block signal 產生不只一次,那 signal 會 queued
  • Order of signal:現今會按照嚴重程度來決定順序

sleep#

int kill(pid_t pid, int signo);

  • 需要有 permission (real/effective 其中一個對就好)
  • 送 特定 signo 給 pid 的 process
    • pid > 0 : to the process
    • pid == 0 : to “all” processes with the same gid of the sender (excluding proc 0, 1, 2)
    • pid < 0 : to “all” processes with gid == |pid|
    • pid == -1 : broadcast signals under SVR4 and 4.3+BSD
  • signo == 0 是要看他的 return 值,看他是死是活(但有缺陷,檢查了不能用)

int raise(int signo);

  • 送一個 signal 給自己

可以一次送一個 signal 給一群 process

unsigned int alarm(unsigned int secs)

  • 設定幾秒後發出 SIGALRM
  • 同一個 process 裡的會被覆蓋,若前一個 還沒響 return 剩餘秒數
  • 當 multi-tasking 要做 scheduling 的時候有可能會有 delay 所以要抓保守空間。
  • alarm(0) 關掉。

int pause(void)

  • ignore, blocked signal 無法叫醒他
  • return -1 with errno == EINTR

Stack frame#

  • Call function 的時候需要紀錄四個值(為了要保持 caller 原樣)
    • Return address
    • Passed parameter(s)
    • Saved registers
    • Local variable(s)

nonlocal jump#

  • 由於 function stack frame 要弄跳回 main() cost 很高,所以用 nonlocal jump 可以直接跳躍進去

int setjmp(jmp_buf env);

  • 功能類似 setjmp() 是 label
  • 呼叫 setjmp() 會 return 0,從 longjmp() return val
  • env 是 label name 要是 global variable
    • env 內部紀錄一些必要值(stack_frame)但是是 machine dependent

int longjmp(jmp_buf env, int val);

  • longjump()goto
  • call 一個 function 可以在另一個 return,通常在兩個 function return
  • 呼叫 longjmp()setjmp() return

Signal mask#

  • 我們不能阻止 signal 送來,但我們可以把它 block 住,讓他不被 interrupt
  • 可以把 disposition 設定成 ignore
  • 一串 bit-stream

int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int sig_no); int sigdelset(sigset_t *set, int sig_no); int sigismember(const sigset_t *set, int sig_no);

  • 設定用 system call

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

  • oset 只要不是 NULL 就可以把目前的 signal mask 顯示出來
    • oset 不為 NULL 就是要設定的那個 mask
  • set 不是 NULL 就去 check how
    • 比如 how == SIGBLOCK 就是把它設定成 1
    • SIG_BLOCK 是 on, SIG_ UNBLOCK 是 off, SIG_SETMASK 是直接設定成 set

int sigpending(sigset_t *set);

  • 被 generate 而且不被 ignore 就是在 pending
  • 檢查某個 signal 有沒有在 pending

int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);

struct  sigaction {
    void     (*sa_handler)(int);  /* signal handler */
    sigset_t sa_mask;             /* signal mask to apply */
    int      sa_flags;            /* see signal options below */
    /* alternate handler */
    void     (*__sa_sigaction)(int, siginfo_t *, void *);
};
c
  • sigaction()signal() 的 reliable 版本
    • 可以查詢舊的 handler
    • sa_mask 可以在 handler 運行的期間去 block 其他的 additional signal (原來的 signal mask 本來就 block 住會繼承,還會把自己也 block 住)
  • act 不為 NULL 則去註冊那個 action
  • oact 不為 NULL return previous handler
  • 現今的 signal() 通常都用 sigaction() 實現,但為了兼容舊系統就要改一下
  • flag
    • SA_INTERRUPT:針對 slow system call,被這個 signal 叫了就不能 auto restart
    • SA_NOCLDWAIT: 如果是 SIGCHLD 就讓他相容 SIGCLD
    • SA_RESETHAND:退化成註冊一次一次有效(兼容)
  • 新版 handler void handler(int signo, siginfo_t *info, void *context); 可以 call 很多新的功能,取用裡面資訊得知錯誤訊息 截圖 2025-11-13 上午11.30.05

int sigsetjmp(sigjmp_buf env, int savemask); void siglongjmp(sigjmp_buf env, int val);

  • sigsetjmp() 要不要存下 mask
  • siglongjmp() 就可以還原成原本 mask
  • Signal mask 是有限的變數,照理來說是應該本來就要還原,但這是個 implementation issue
  • 一定要配對

int sigsuspend(const sigset_t *sigmask)

  • sigprocmask()pause() 包成一個 atomic operation
  • 要先往上抓 sigprocmask() 把某個 signal block 住,replace 掉他的 sigmask(相等於 unblock),然後等有一個 non-ignore 的 signal 叫醒他,run 完 handler 之後 return

Signal Mask with fork(), exec()#

NOT Change

  • exec(): signal mask, pending signals 會繼承
  • fork(): signal mask, dispositions, time of SIGALRM 會繼承

Change

  • exec(): disposition of caught signals 會變 default
  • fork(): pending signals 會變 empty,ending alarms are cleared

IPC with signal#

Wrong case 截圖 2025-11-20 上午10.20.16

  • 任何 signal 都可以叫醒 sigsuspend(),所以我們可以用 sigflag 來確認,發現不是 SIGUSR1 就繼續睡。
  • 但由於我們要對紅色片段做 critical region 處理,所以必須做 sigpromask() 的動作,但在 fork()sigpromask() 前,signal 可能就來了,造成 signal get lost 的情況

Right case 截圖 2025-11-20 上午10.33.50

  • fork() 前要先做 TELL_WAIT() 弄成 critical session
  • 然後才在 child 裡面做 suspend
  • 這樣 SIGUSR1 就被 block 一定會在最後才 deliver 才 handle.

sleep#

unsigned int sleep(unsigned int secs);

  • return 沒睡飽幾秒 or 0
  • 由於共用 SIGALRM 萬一在 sleep() 前有一個 alarm()
    • alarm(10), 3 secs, sleep(5) 大部分系統可以響鬧鐘,但理論上是可以 ignore 的
    • alarm(10), 3 secs, sleep(5) 某些系統會讓 sleep(5) 睡不飽

void abort(void)

  • 送出 raise(SIGABRT)
  • 可能死不了
    • 如果先前就先 block 住 SIGABRT 那就死不了
    • Catch 到 SIGABRT 然後在 handler 裡面用 longjump() 跳走

Back to the content

NTU PJ System Programming

2025 Fall

← Back to the content


NTU-SP 系統程式設計 Ch7 Signal
https://vinsong.csie.org/blog/ntu-sp-ch07-signal
Author VinSong
Published at 2025年11月30日