信號(hào)通過(guò)軟件方法實(shí)現(xiàn)(軟中斷), 會(huì)導(dǎo)致延時(shí)性. 每個(gè)進(jìn)程接收到的信號(hào)都是由內(nèi)核發(fā)送處理的, 內(nèi)核作為中轉(zhuǎn).
未決: 產(chǎn)生和遞達(dá)之間的狀態(tài), 主要由于阻塞(屏蔽)導(dǎo)致.
未決信號(hào)集和阻塞信號(hào)集(信號(hào)屏蔽字)都存放在PCB中, 數(shù)據(jù)結(jié)構(gòu)是集合, 不重復(fù)但無(wú)序, 內(nèi)部存放0/1值. 正常傳遞時(shí), 未決信號(hào)集中從0變到1(未決)再?gòu)?變到0(被處理)的過(guò)程可以看作是瞬時(shí)的. 但當(dāng)阻塞信號(hào)集某編號(hào)因?yàn)槟撤N原因?yàn)?時(shí), 當(dāng)內(nèi)核發(fā)送信號(hào)后, 未決信號(hào)集中對(duì)應(yīng)的該編號(hào)也會(huì)變?yōu)?(不發(fā)送就仍為0), 所以說(shuō)阻塞信號(hào)集影響未決信號(hào)集.
信號(hào)的處理方式:
- 執(zhí)行默認(rèn)動(dòng)作
- 忽略(丟棄), 也是一種處理
- 捕捉, 調(diào)用戶處理函數(shù).
信號(hào)四要素:
- 名字 2. 編號(hào) 3. 默認(rèn)動(dòng)作 4. 對(duì)應(yīng)觸發(fā)事件
默認(rèn)動(dòng)作有5種:
- Term,終止進(jìn)程
- Core,終止進(jìn)程并產(chǎn)生core文件
- Ign,忽略
- Stop,暫停
- Cont,繼續(xù)
man 7 signal
查看信號(hào), 當(dāng)一個(gè)名字對(duì)應(yīng)多個(gè)編號(hào)時(shí), 取中間那列的, 左右兩列為其他平臺(tái)的編號(hào).
9)SIGKILL和19)SIGSTOP比較特殊, 不允許忽略和捕捉, 甚至不允許阻塞, 只能執(zhí)行默認(rèn)動(dòng)作
信號(hào)的產(chǎn)生
1. 終端按鍵產(chǎn)生信號(hào)
ctrl + c 2)SIGINT 中斷信號(hào)interrupt, 終止進(jìn)程(Term)
ctrl + z 20)SIGTSTP 暫停與終端交互進(jìn)程, 放到后臺(tái)(Stop). 注意與19)SIGSTOP區(qū)分, SIGSTOP可以暫停任何進(jìn)程(Stop).
ctrl + \ 3)SIGQUIT 退出進(jìn)程(Core)
2. 硬件異常產(chǎn)生信號(hào)
除0操作 8)SIGFPE(浮點(diǎn)數(shù)例外)
非法訪問(wèn)內(nèi)存 11)SIGSEGV(段錯(cuò)誤)
總線錯(cuò)誤 7)SIGBUS
3. kill命令/函數(shù)產(chǎn)生信號(hào)
int kill(pid_t pid, int sig)
可以調(diào)用kill函數(shù)
對(duì)于管道連接的進(jìn)程, 當(dāng)寫端進(jìn)程終止后, 寫端都關(guān)閉, 讀端也會(huì)跟著關(guān)閉, 所以只kill寫端進(jìn)程就會(huì)把管道兩端的進(jìn)程都?xì)⑺?
4. 自身調(diào)用raise()和abort()函數(shù)產(chǎn)生信號(hào)
5. 軟件條件產(chǎn)生信號(hào):alarm()和setitimer()
alarm()傳新的時(shí)間進(jìn)去后會(huì)覆蓋舊的, 所以返回的上一次剩下的秒數(shù)沒(méi)什么用, 如果alarm(0)就是關(guān)閉定時(shí)器, 直接終止進(jìn)程. 注意終止進(jìn)程只是默認(rèn)行為, 可以設(shè)置一個(gè)handler函數(shù)如signal(SIGALRM, handler)
來(lái)注冊(cè)信號(hào)的捕捉函數(shù)(真正捕捉信號(hào)的是內(nèi)核), 這樣就可以在時(shí)間到了之后不終止進(jìn)程. 信號(hào)捕捉函數(shù)是一個(gè)典型的回調(diào)函數(shù)(封裝了一個(gè)函數(shù), 但是沒(méi)有直接調(diào)用).
可以使用time命令查看程序運(yùn)行的時(shí)間, 總時(shí)間=用戶空間時(shí)間+內(nèi)核空間時(shí)間+等待時(shí)間, 等待時(shí)間I/O占了大部分.
new_value是傳入?yún)?shù), 表示要定時(shí)的時(shí)間; old_value是傳出參數(shù), 表示上次剩的時(shí)間, 相當(dāng)于alarm()函數(shù)的返回值. setitimer成功返回0, 失敗返回-1.
需要先把結(jié)構(gòu)體成員賦初值, 再把結(jié)構(gòu)體指針傳到函數(shù)里, 分別指定val和interval的秒值/毫秒值:
signal(SIGALRM, signalHandler);
struct itimerval new_value, old_value;
new_value.it_value.tv_sec = 5;
new_value.it_value.tv_usec = 0;
new_value.it_interval.tv_sec = 3;
new_value.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &new_value, &old_value);
只有第一次設(shè)定鬧鐘后5秒會(huì)接收到SIGALRM信號(hào), 之后每3秒接收到一個(gè)SIGALRM信號(hào).
信號(hào)集操作函數(shù)
sigset_t myset;
自己的信號(hào)集, 即"位圖"
影響阻塞信號(hào)集3號(hào)位的方法:
1.先sigemptyset(&myset)
再sigaddset(&myset, 3)
把自己的信號(hào)集3號(hào)位設(shè)置為1(注意信號(hào)集編號(hào)從1開始, 長(zhǎng)度為64位unsigned long int, 對(duì)應(yīng)kill -l列出來(lái)的信號(hào)宏, 一般操作前31個(gè)).
2.如果是設(shè)置阻塞, 調(diào)用sigprocmask(SIG_BLOCK, &myset, &oldset)
把阻塞集mask的3號(hào)位置為1, 其中sigset_t oldset
是傳出參數(shù), 用來(lái)保存原來(lái)的阻塞集狀態(tài), 可以傳NULL. 之后當(dāng)內(nèi)核發(fā)送3號(hào)信號(hào)(SIGQUIT)時(shí), 未決信號(hào)集的第3位也根據(jù)阻塞集變?yōu)?.
3.如果是解除阻塞, 調(diào)用sigprocmask(SIG_UNBLOCK, &myset, &oldset)
把阻塞集mask的3號(hào)位置為0, 則未決集的3號(hào)位也會(huì)從1變0. 也就是說(shuō)無(wú)論是設(shè)置阻塞還是解除阻塞, 位圖myset的對(duì)應(yīng)號(hào)位都要置為1.
類似sigaddset()的的還有sigfillset(&myset)
全置1, sigdelset(&myset, 3)
把3號(hào)位改為0(若已為0則無(wú)動(dòng)作), sigismember(&myset, 5)
判斷5號(hào)位是否為1.
阻塞信號(hào)集不可讀, 但可以使用int sigpending(sigset_t *set)
來(lái)讀取未決信號(hào)集, set是傳出參數(shù). 打印信號(hào)集要使用sigismember函數(shù).
sigaction()捕捉信號(hào)
信號(hào)捕捉函數(shù)由內(nèi)核調(diào)用, 即所謂的回調(diào)模式, 只有在確認(rèn)信號(hào)已抵達(dá)后才會(huì)被調(diào)用, 在用戶空間執(zhí)行, 執(zhí)行完通過(guò)sigreturn返回內(nèi)核進(jìn)行報(bào)道, 再?gòu)纳洗伪恢袛嗟牡胤嚼^續(xù)執(zhí)行.
注冊(cè)信號(hào)捕捉函數(shù)除了signal(signum, handler)
之外, 還可以使用int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
函數(shù), 用結(jié)構(gòu)體的sigset_t sa_mask成員設(shè)置信號(hào)處理期間要被屏蔽的信號(hào)集合(使用sigemptyset()和sigaddset()設(shè)置), 防止調(diào)用sa_handler(函數(shù)指針型成員變量)期間被打斷, sa_mask會(huì)覆蓋內(nèi)核的阻塞集mask. 另外sa_flags也要設(shè)置為0, 表示sa_handler處理某信號(hào)期間忽略相同信號(hào).
捕獲完信號(hào)后最好恢復(fù)成信號(hào)的默認(rèn)動(dòng)作sigaction(signum, &oldact, NULL)
, 且sigaction()調(diào)用前后都要對(duì)要接收的信號(hào)進(jìn)行屏蔽(設(shè)置mask), 以防止注冊(cè)信號(hào)捕捉函數(shù)的過(guò)程中信號(hào)到來(lái).
時(shí)序競(jìng)態(tài)(競(jìng)態(tài)條件)
pause()函數(shù)可以讓進(jìn)程主動(dòng)被掛起, 當(dāng)捕獲并處理完信號(hào)后返回-1, 設(shè)置errno為EINTR. 需和信號(hào)捕捉函數(shù)配合使用
如果alarm()后失去CPU, 且過(guò)了定時(shí)的時(shí)間, 當(dāng)重獲CPU時(shí)會(huì)先處理已發(fā)送并阻塞的SIGALRM信號(hào), 這樣pause()后就無(wú)法接收到本應(yīng)等待的SIGALRM, 從而一直掛起, 導(dǎo)致時(shí)序競(jìng)態(tài)問(wèn)題. 使用sigsuspend可解決該問(wèn)題.
sigsuspend()函數(shù)實(shí)質(zhì)上就是在內(nèi)核阻塞信號(hào)集mask上把SIGALRM屏蔽, 當(dāng)執(zhí)行sigsuspend()時(shí)再使用臨時(shí)阻塞集解除屏蔽. 這樣即使alarm后失去CPU, SIGALRM信號(hào)被阻塞, 等恢復(fù)執(zhí)行后也是先調(diào)用sigsuspend()并用捕捉函數(shù)處理信號(hào), 而不是先處理SIGALRM再pause()導(dǎo)致一直掛起.
全局變量異步I/O
對(duì)于進(jìn)程間通信, 全局變量存在異步I/O的情況, 應(yīng)盡量少用全局變量(使用鎖的機(jī)制可以避免這種情況). 即可能主函數(shù)信號(hào)發(fā)出后失去CPU, 當(dāng)恢復(fù)CPU后, 另一端已發(fā)送的信號(hào)會(huì)優(yōu)先被處理(信號(hào)捕捉函數(shù)里可以改變?nèi)肿兞?, 使得對(duì)全局變量的修改順序出現(xiàn)錯(cuò)誤, 導(dǎo)致進(jìn)程掛起.
使用全局變量的函數(shù)容易變成不可重入函數(shù), 一旦被信號(hào)打斷容易發(fā)生錯(cuò)誤, 執(zhí)行結(jié)果會(huì)和預(yù)期的不同(如遞歸操作). 所以信號(hào)捕捉函數(shù)要設(shè)計(jì)成可重入函數(shù), 避免使用全局變量和static變量, 避免使用malloc/free.
捕捉SIGCHLD回收子進(jìn)程
當(dāng)多個(gè)子進(jìn)程同時(shí)死亡時(shí), 雖然信號(hào)捕捉函數(shù)一次只能處理一個(gè)信號(hào), 但如果waitpid()第一個(gè)參數(shù)設(shè)為回收全部子進(jìn)程, 在這一次調(diào)用的過(guò)程中waitpid()就會(huì)把當(dāng)前剩下死亡的子進(jìn)程都回收掉, 此處信號(hào)的作用只是激活waitpid()函數(shù), 回收幾個(gè)子進(jìn)程由waitpid自己決定.
中斷慢速系統(tǒng)調(diào)用
read在讀文件時(shí)不會(huì)阻塞, 在讀網(wǎng)絡(luò)/設(shè)備/管道時(shí)可能發(fā)生阻塞, 此時(shí)收到信號(hào)就會(huì)被中斷.
中斷的慢速系統(tǒng)調(diào)用要么重啟要么執(zhí)行默認(rèn)動(dòng)作, 如果重啟需要again和goto參數(shù), 同時(shí)判斷EINTR信號(hào)