system call 在某些條件下結果可能會不如預期
what is signal
無論是自己或是他人要求的,都是透過 OS 送的
像是 Ctrl + C 或是 SIGPIPE 都是 signal 的一種
也算一種 IPC 的方式
signal v.s. interrupt
interupt(狹義)
對 CPU(Hardware)
內部指令可能會間接或者本身就想要去直接中斷,或是外部硬體接腳 如 PIC(Programable interrurpt controller) 這邊就是連到很多周邊 device 並接收 request 再接收 IRQ 去中斷 CPU
CPU 被中斷會 run ISR(interupt service routine) 是用來記錄 event
signal
對 process
會去處理 ISR 記錄的 event,由 OS 去解釋 event(不一定會是單一個 signal
signal == 0 是 NULL signal
signal 會是正整數
不同系統會有不同 signal number
OS 等有空會去處理紀錄的 Event 然後轉換成 signal 傳送到 process
signal 的定義是來自 kernel,也是由 kernel 送給 process,不論 event 是什麼,解釋權都在 signal
Synchronous v.s. asynchronous signal
Synchronous 同步
因為執行某個 instruction 就發生
只要一發生就會產生,如 divided by zero 或 segmentation fault
Asynchronous 非同步
不知道 run 到什麼會發生
如 killed by other process, SIGCHILD(child termination)
Generate Signal
Terminal-generated signals
DELETE key
SIGINT(2)
Ctrl-C
Signals from exceptions
SIGFPE(8)
divided by 0
SIGSEGV(11)
illegal memory access
Fuction kill
(need owner or superuser)
Shell command kill
SIGKILL(9)
kill -9 pid
Signals because of software conditions
SIGPIPE
reader of the pipe terminated
SIGALRM
expiration of an alarm clock
對 Signal 的反應(action)
SIGKILL and SIGSTOP 不能被設定反應
設定 Ingore signals
SIGKILL and SIGSTOP 不能被 ignored
有些 signal 沒有設定對 ignore 的反應,所以當設定成 ignore 的後果要自負,如 SIGFPE
設定 Catch signals
SIGKILL and SIGSTOP 不能被 catch
當 signal 來要 call 哪個 function
call 的 function 就是 signal hadler
如 waitpid 就是
Apply the default action
Terminate(多數) or ignore or stop
沒有設定就會去變成 default
Example of Signals
w/core 指到是 core dump 就是終止後會把 memory copy 一份下來
|
|
SIGABRT
- terminate w/core
call
abort()
來自己終止自己,會送 SIGABORT 給自己
SIGALRM
- terminate
Call
setitimer()
,alarm()
這兩個是用來設定每幾秒就會送一個 SIGALRM 訊號過來,如果沒設為 catch 去處理,會使得程式終止
SIGBUS
- terminate w/core Hardware 去 implement 的情形
SIGCHLD
- ignore sent to the parent whenever a child process terminates or stops
SIGCONT
- continue/ignore Continue a stopped process
SIGFPE
- terminate w/core Divid-by-0, floating point overflow, etc.
SIGHUP
- terminate
以前是實體接線斷掉,會接到 SIGHUP
通常 deamon(background process) 會有 comunication file(組態檔)來根 process 溝通
現在是對 deamons 更改組態檔後通知 deamons 要進行重新讀取組態檔有關(因為通常不會隨邊停止)
SIGINT
- terminate DELETE key or CTRL-C
SIGIO
- terminate/ignore
跟做 asynchronous I/O 的 event 有關
比如說要一直讀,讀到 buffer 去,作業系統在讀完把 data copy 過來後就會收到
SIGPIPE
- terminate reader of the pipe/socket terminated
SIGPROF
- terminate A profiling timer expires (setitimer)
SIGPWR
- ignore (SVR4) 當沒電 UPS 運作時會 call 這個 signal 給 init
SIGQUIT
- terminate w/core CTRL-\ triggers the terminal driver to send the signal to all foreground processes
SIGSEGV
- terminate w/core Invalid memory access
SIGSTOP
- stop process (like SIGTSTP) Can not be caught or ignored
SIGSYS
- terminate w/core Invalid system call
SIGTERM
- terminate Termination signal sent by kill command
SIGTSTP
- stop process Terminal stop signal (CTRL-Z) to all foreground processes
SIGURG
- ignore Urgent condition (e.g., receiving of out-of-band data on a network connection)
SIGUSR1
- terminate User-defined
SIGUSR2
- terminate User-defined
system call of set signal
|
|
以前註冊只有一次有效
設定 signo
的對應要用哪一個 function 處理
return 會是回傳之前的 function address
輸入輸出的 function 都要是 void func(int signo)
的格式func
有由系統 define address 是 -1 是 SIG_ERR
0 是 default SIG_DFL
1 是 ignore SIG_IGN
,因為不會有 address 那麼前面
因為可以很多個 signal id 共用同一個 function 所以會接去判斷接到的 id ,會大概長得像
|
|
pause()
會等到收到沒有被設定為 ignore 的 signal
可以使用 signal()
去確定設定的種類,就是設定成想確認的結果就會知道之前是什麼
|
|
exec
呼叫 exec 會把所有 signal 的設定條回 default,除非該項被設為 ignore
tips of signal
系統會自動把 backgroup process 的只有 foregroud 接收到相關資訊的 signal 設為 ignore
interrupt
可以在 slow systemcall 會被 forever block 的情況可以被 interrupted
errno 會是 EINTR
所以可以用這點來打斷 forever block
BSD 有設計被 interuptted 就會自動 restart
不同系統有些可以設定,或是寫死,或是不會 autorestart
reentrant function
有正面表列出 reentrant 的 function
call twice
有時候處理 signal 的 function 用到被 interrupt 的 function,裡面又有 static 出現,會出現問題
static 會被 compiler 放在 global variable,但存取會被 compiler 擋住
non-reentrant function
不能被 call twice 的 function,如裡面有 static, global, heap
也就是不會在 call twice 遇到被存取到相同位置
Example of non-reentrant function
|
|
signal 去對 errno 的防止 reentrant 的方法是,會先去存 errno,並在 handler return 前回復
longjump 也是 non-reentrant
如 printf 是 non-reentrant 但因為發生機率極低,所以還是用它來 debug
reentrant function solution
把要用到的變數由使用者提供 pointer 來存取,這樣在 reentrant 時就不會改到另一個 call 這個 function 那邊的變數
reliable signals
unreliable signals
signal could get lost
指的是感覺沒有 catch 到
像是因為以前 signal 只有註冊一次有效,而在處理前再次設定前又收到一個,所以就以預設處理
或是先把收到訊號用 global variable 去存起來,之後再處理,會遇到當開始去確認有沒有訊號時還沒有訊號,但是此時 context switch 收到,然後接回等待 signal(pause) 時,就等不到 signal 造成 deadlock
也就是 non-atomic 有 Race condition 造成問題
reliable signal 的做法
signal generated 時會先 set flag 然後等排到 CPU 才會去把 signal deliver 給 process,中間的時間就被稱為 pending
通常是在 context switch 間的時間去 generate
可以有很多 process 認領 signal 但至少要一個
reliable signal 的處理方式是在 deliver 的時候 block 住,等到 unblock 或 ignore
sigpending()
可以去看哪些 signal 被 block
signal mask
和 cwd 跟 umask 一樣在 process 中記錄
deliver times
標準沒說多個相同的 signal 會怎麼在 unblock 時去 deliver,通常實作是只會一次
多個不同的則會去把最嚴重的先 deliver
kill and raise
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int signo)
會把 signal 送給指定 processpid
== 0 會給所以有 process 有相同 gidpid
< 0 則是送給所有 gid = |pid| 的pid
== -1 broadcast signals under SVR4 and 4.3+BSD
如 Worktation shutdown 前可以送 signal 到所有線上 User
r-UID 或 e-UID 要一樣才能送,也有些系統會去確認 r-UID 或 saved set-UIDsigno
== 0 (NULL signal) 是用來確認知道 process 是不是 alive 但會有 TTC != TTU 的問題,如果不是 alive 會 return -1
signal 送給自己且沒被 block 會在 return 前去 check 有沒有被 pending 的狀態(包含剛剛送的 signal),直到至少一個 deliver 才會 return
int raise(int signo)
送 signal 給自己
alarm and pause
#include <unistd.h>
unsigned int alarm(unsigned int secs)
幾秒要送一個 SIGALRM 給自己
會把之前設定的 alarm 取消掉
會把前面鬧鐘剩餘秒數 return
會比設定的多一些時間,所以設定要保守些
alarm(0) 可以 reset
要設定接收的反應,因為預設是 termination
int pause(void)
會暫停直到收到 沒被 blocking 也非 ignore 的 signal
都會 return -1,errno = EINTR
setjump and longjump
#include <setjmp.h>
可以 non local goto
Stack Frame
fucntion 的 那塊區域
每 call 一次 funtion 就會出現
會存 return address, passed parameters, save registers(CPU 之前的狀態), local variable
return adress 的概念基本上就是會落在 call fuction 的下一行
裡面的 local variable 如果有做初始化,會由 compiler 產生的組合語言把它填成設定的值
compiler 會去優化跟預測接下來的行為來最佳化,所以亂用 goto 會讓 compiler 的預測效率降低
沒有 initialized 的 global variable 在 exec 會把整塊清零
設定 local variable 是 compiler
return 完 pop 掉其實不會被真正刪掉,只會動 pointer,但在重新 push(不論是不是同一個)時會重新設定
所以當 set file pointer 的 buffer 在 function 中宣告,會出現又 call function 時會出問題,但是不會有 error
jmp_buf
是 machine-dependent
通常會包含 CPU registers, stack pointer 以及 return address
int setjmp(jmp_buf env)
會 return 0 當 call 自己,當被 longjmp 會 return longjmp 的 val
通常會把 env 設為 global variable\
int longjmp(jmp_buf env, int val)
jump 到 有 setjmp 為 env 的 function 並在那邊 return val
可以用來一次跳回很多層,但是 stack 的 local vairable 基本上是不會回復
不能回到已經 return 的 function(要在 VM 的 stack 中)
sleep2 實驗
|
|
先註冊 signal 然後當 alarm 響了會跳到 sig_alrm
,不管 pasue 是否執行到,會到 if 所以不會被 pause 影響到(不會從 pause return)
可以解決 race condition,因為不論是什麼情況都會跳回 return
假設收到 SIGINT 也會中斷
但會造成中間假設有在處理其他的 signal hadler,會被中斷,然後假設處理到一半,因為處理 SIGALRM 的 longjmp 使得另一邊的 signal hadler 還沒跑完就結束了。
還是有 open source 會用,因為以邏輯上沒出錯,然後對於這個問題不重視(如 error number 沒被還原)
slow system call 遇到 signal
read 遇到 alarm
如果還沒 read 到東西,收到 SIGALRM 會使得 read return -1,errno == EINTR
auto restart
BSD 有提出一些 slow system call 被特定 signal interrupt 會 auto restart
所以遇到需要自己設定 timeout 會需要確定系統沒有對用到的 system call 沒有 auto restart
Signal Mask
每個 process 有自己的 signal mask
是一個類似 bit string 的東西
希望有一段時間可以把 signal block 住,因為那時處理的事情會跟 signal handler 衝突
預設都是 0
signal sets
#include <signal.h>
sigset_t
是用來存 signal numbers 的變數型態
|
|
int sigprocmask(int how,const sigset_t *set, sigset_t *oset)
如果 oset 不是 NULL 會先把目前的 sigset 複製到這
再來 set 如果不是 NULL 會去 check how 是什麼,set 是用來傳參數的 mask
然後會把 return 前所有 unblock 的 pending signal 至少 deliver 一個後再去把 set 設定到 sigset 中然後 return
所以如果 unblock 的話會立即去處理 signal
SIG_BLOCK
把 set 的跟 sigset 做聯集後去設定到 sigset
SIG_UNBLOCK
做交集
SIG_SETMASK
直接 replace
int sigpending(sigset_t *set)
會把 set 設定成正在 pending 的 signal
不一定會是 block 的 signal,可能是沒被 block 單純只是還沒被 deliver
但通常單純的 pending 不會太久,所以通常還是 block
inherited
fork
signal mask 跟 dispositions(signal 的 signal handlers)會繼承
peding signal 跟 alarm 因為對象是 process 所以不會繼承
exec
signal mask, pending signals, alarm 的 time left 會繼承
diposition(signal handler) 因為會把原本的程式區塊清掉所以不會清掉
sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
用來取代 signal()
,改正了不能確認之前的設定跟在處理時額外 block 的效果,也可以一次註冊多次有效
如果 act 不是 NULL 會改 signal hadler
如果 oact 不是 NULL 會回傳現在註冊的狀態
現在 signal 會用 sigaction implement
struct sigaction
|
|
sa_mask
是設定把在跑 sigaction 時會額外 block 的 signal,也會再去 block 住自己的 signalsa_flag
是可以設定要怎麼處理sa_sigaction
是可以用不同參數的 signal handler 可以用特定 sa_flag
啟用\
sa_flag
SA_INTERRUPT | SA_NOCLDSTOP | SA_NOCLDWAIT | SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_RESTART | SA_SIGINFO |
---|---|---|---|---|---|---|---|
被 interrupt 的 sysytem call 不會 auto restart | 不會去接收產生的 SIGCHILD | 自己的 child 不會變 zombie,用 wait 時,要所有的 child 才會 return,等同 SIGCLD | 不會在呼叫時把自己用 mask block 起來 | 呼叫一次 sigaction 一次有效 | 會 auto restart 被 signo interrupt 的 system call | 使用 sa_sigaction 當作 signal handler |
System V 有一個 SIGCLD 是可以不用 wait,然後 wait return 是所有 child 的結束才會 return
sa_sigaction
接收的 siginfo_t
可以接收資訊
每個版本不一樣,但可以列出一些大家都有的
|
|
signal implement by sigaction
後面很多 signal 就是使用 sigaction impement 的
|
|
sigsetjmp & siglongjmp
|
|
會可以設定之後恢復 sigmask 不管是不是 empty
把 sigmask 存在 env
當 savemask != 0
jmp 的處理
當 signal handler 設定會 jmp 到 setjmp 但此時收到時 setjmp 還沒設定好會出問題
有時候就會設定 global variable 來去在 longjmp 前確認 set 了沒,然後在 setjmp 完設定
int sigsuspend(const sigset_t *sigmask)
把 sigprocmask 跟 pause 做成 atomic
也就是會把 sigmask 設定成輸入的值然後 wait
一定會 return -1 然後 errno == EINTR
return 會恢復成之前的 sigmask
所以如果在 sigsuspend 前設定了 sigprocmask 來擋 critical reigion 的話(sigsuspend 開始等),要記得用在 sigsuspend 後把原本怕被 call 的 signal 從 sigmask 中移除後重新設定
critical reigion
signal 絕對不能被 deliver 的區塊
signal IPC
如果有需要在一開始先去 block 住 signal 的話,要在 parent 的 sigmask 設定好後再 fork 使得 child 繼承,才不會出現在剛 fork 完就收到 signal 的狀況
sleep
因為要不要保留前面的 alarm 秒數是 implementation issue,所以這邊不處理
會把前面的 signal handler 暫時存起來,再去 set alarm 然後處理,當中間被叫醒就把回傳值設定成剩下秒數,最後再把把之前的 signal handler 恢復,也就是假設有之前的 alarm 的 hadler 會不處理
|
|
abort
會送一個 SIGABRT
當 user 有註冊 SIGABRT 的 signal handler 會先執行然後還不會 return,然後再換成 default call SIGABRT 來結束程式(不 return)
會出現問題在 longjmp 到其他地方(因為就不會繼續往下跑)、exit(因為要異常 terminate 變正常 terminate)
ANSI C 是說 file stream flush 跟是否要砍掉暫檔是 implementation issue
POSIX 是定義不允許 block 跟 ignore SIGABRT,且會 flush file stream
abort 在原先 signal handler 有 longjmp 的話會逃掉
|
|
ANSI C
如果成功跑完會 flush buffer 然後 delete 所有 temp file
POSIX
如果成功終止程式,會 fclose 所有 file
implement
如果原本 SIGABRT 是 ignore 會把它設定成 default 然後 fflush
variable type(補充)
normal
就是一般的變數,在最佳化可以被操作
static
在 function 內部時會把值在跳出時依然存下來(用全域變數實踐)
在 module(c 檔)中作為非 body(function) 變數不能被外部存取(區域變數)
const
常數,只可讀不可寫
register
推薦放 variable 到 register,但只有不要放在 register 的才能絕對確定
當 register 有空會放到 register,但就會沒有 address
volatile
宣告說不論在哪邊改數字,跟判斷中間可能都會被更改(不要放到 regiter 去)
也就是讓 compiler 知道部會只看 function 直接跑
一定有 address,且不能被 compiler 自行判斷有關這個的結果因爲前面結果而取消
|
|
可能會被 compiler 優化改成
|
|
如果是中間接到 signal 改了 foo 會出問題
restrict
指的 source 是不能被用兩次,目前是給 programmer 看,compiler 不一定要實作 也就是不能有 overlaping