1 `specific_send_sig_info()`函數(shù)
2 send_signal()函數(shù)
3 group_send_sig_info()函數(shù)
許多內(nèi)核函數(shù)產(chǎn)生信號(hào):它們完成信號(hào)處理的第一階段,也就是更新一個(gè)或多個(gè)進(jìn)程描述符。她們不會(huì)直接執(zhí)行第二階段的信號(hào)處理,也就是傳遞信號(hào);但是,依賴信號(hào)類型和目標(biāo)進(jìn)程的狀態(tài),可能會(huì)喚醒某些進(jìn)程并強(qiáng)制它們接收信號(hào)。
信號(hào)的發(fā)送,可以是內(nèi)核,也可以是其它進(jìn)程,內(nèi)核使用下表中的函數(shù)產(chǎn)生信號(hào)。
表11-9 為進(jìn)程產(chǎn)生信號(hào)的內(nèi)核函數(shù)
函數(shù)名稱 | 描述 |
---|---|
send_sig() | 給單個(gè)進(jìn)程發(fā)送信號(hào) |
send_sig_info() | 與send_sig()類似,在siginfo_t中帶有擴(kuò)展信息 |
force_sig() | 發(fā)送不能被進(jìn)程顯式忽略或阻塞的信號(hào) |
force_sig_info() | 類似force_sig(),在siginfo_t中帶有擴(kuò)展信息 |
force_sig_specific() | 類似force_sig(),但是針對(duì)SIGSTOP和SIGKILL信號(hào)進(jìn)行了優(yōu)化 |
sys_tkill() | tkill()系統(tǒng)調(diào)用處理程序 |
sys_tgkill() | tgkill()系統(tǒng)調(diào)用處理程序 |
表格11-9中所有函數(shù)最后都會(huì)調(diào)用specific_send_sig_info()函數(shù),后面會(huì)介紹。
發(fā)送到整個(gè)線程組的信號(hào),可以來自內(nèi)核或其它進(jìn)程,產(chǎn)生信號(hào)的函數(shù)如下表所示。
表11-10 為線程組產(chǎn)生信號(hào)的內(nèi)核函數(shù)
函數(shù)名稱 | 描述 |
---|---|
send_group_sig_info() | 發(fā)送信號(hào)給線程組,由線程組中的某個(gè)進(jìn)程描述符標(biāo)識(shí) |
kill_pg() | 發(fā)送信號(hào)給進(jìn)程組中所有線程組(參加第1章的進(jìn)程管理一節(jié)) |
kill_pg_info() | 類似kill_pg(),只是siginfo_t帶有擴(kuò)展信息 |
kill_proc() | 發(fā)送信號(hào)給線程組,由線程組中的某個(gè)進(jìn)程PID標(biāo)識(shí) |
kill_proc_info() | 類似kill_proc(),只是siginfo_t帶有擴(kuò)展信息 |
sys_kill() | kill()系統(tǒng)調(diào)用的處理程序(參見后面與信號(hào)處理有關(guān)的系統(tǒng)調(diào)用) |
sys_rt_sigqueueinfo() | rt_sigqueueinfo()系統(tǒng)調(diào)用的處理程序 |
上表中的函數(shù)最終調(diào)用group_send_sig_info()更新進(jìn)程描述符,函數(shù)會(huì)在group_send_sig_info()函數(shù)一節(jié)中介紹。
1 specific_send_sig_info()函數(shù)
specific_send_sig_info()函數(shù)可以發(fā)送信號(hào)到具體的進(jìn)程。作用于3個(gè)參數(shù):
sig
信號(hào)編號(hào)。
info
既可以是siginfo_t表的地址,也可以是3個(gè)特殊值:0意味著用戶進(jìn)程發(fā)送的信號(hào);1意味著內(nèi)核發(fā)送的信號(hào);2意味著內(nèi)核發(fā)送的信號(hào),且信號(hào)是SIGSTOP或SIGKILL。
t
目標(biāo)進(jìn)程描述符的指針。
specific_send_sig_info()必須在本地中斷禁止且申請(qǐng)了t->sighand->siglock自旋鎖的情況下調(diào)用。執(zhí)行步驟如下:
檢查進(jìn)程是否忽略信號(hào);如果是,則返回0(不用產(chǎn)生信號(hào))。滿足下面3個(gè)條件,信號(hào)即會(huì)被忽略:
進(jìn)程沒有被跟蹤(t->ptrace中PT_PTRACED標(biāo)志清除)
信號(hào)沒被阻塞sigismember(&t->blocked, sig) returns 0
信號(hào)顯式忽略(t->sighand->action[sig-1]中的sa_handler字段等于SIG_IGN)或隱式忽略(sa_handler字段等于SIG_DFL并且信號(hào)是SIGCONT、SIGCHLD、SIGWINCH或SIGURG)
檢查信號(hào)是否是非實(shí)時(shí)(sig<32),相同信號(hào)是否已經(jīng)在進(jìn)程的私有掛起信號(hào)隊(duì)列(sigismember(&t->pending.signal,sig) returns 1):如果確定,什么也不做,然后返回0。
調(diào)用send_signal(sig, info, t, &t->pending)將信號(hào)添加到進(jìn)程的掛起信號(hào)集中;詳細(xì)描述如下所示:
如果send_signal()成功終止,且信號(hào)也沒有被阻塞(sigismember(&t->blocked,sig) returns 0),然后調(diào)用signal_wake_up()函數(shù)通知進(jìn)程新的掛起信號(hào)。因此,函數(shù)執(zhí)行如下步驟:
在t->thread_info->flags中設(shè)置TIF_SIGPENDING標(biāo)志。
調(diào)用try_to_wake_up()喚醒進(jìn)程(這些進(jìn)程處于TASK_INTERRUPTIBLE或TASK_STOPPED狀態(tài),且信號(hào)是SIGKILL),具體可以參考第7章的try_to_wake_up()函數(shù)一節(jié)。
如果try_to_wake_up()返回0,進(jìn)程喚醒并可運(yùn)行:如果是,檢查該進(jìn)程是否在其它CPU上正在運(yùn)行,這種情況下,發(fā)送一個(gè)核間中斷給那個(gè)CPU,強(qiáng)制重新調(diào)度當(dāng)前進(jìn)程。(參考第4章的核間中斷處理一節(jié))因?yàn)楫?dāng)從schedule()函數(shù)返回時(shí),每個(gè)進(jìn)程都會(huì)檢查掛起信號(hào),核間中斷確保目標(biāo)進(jìn)程快速注意到新的掛起信號(hào)。
信號(hào)產(chǎn)生成功,則返回1。
2 send_signal()函數(shù)
send_signal()負(fù)責(zé)插入掛起信號(hào)隊(duì)列中。接收參數(shù):信號(hào)sig,數(shù)據(jù)結(jié)構(gòu)siginfo_t中info的地址(或具體編碼值,參考前面的specific_send_sig_info()描述,目標(biāo)進(jìn)程描述符地址t,掛起信號(hào)隊(duì)列signals的地址。
函數(shù)執(zhí)行如下內(nèi)容:
如果info等于2,信號(hào)是SIGKILL或SIGSTOP且是內(nèi)核通過force_sig_specific()函數(shù)產(chǎn)生的:這種情況直接跳轉(zhuǎn)到第9步。與這些信號(hào)相對(duì)應(yīng)的動(dòng)作由內(nèi)核立即強(qiáng)制執(zhí)行,因此該函數(shù)可能會(huì)跳過將信號(hào)添加到掛起信號(hào)隊(duì)列中。(如果是特殊信號(hào),比如殺死、停止進(jìn)程,則直接執(zhí)行,不再走信號(hào)處理的通用流程)
如果進(jìn)程擁有者的掛起信號(hào)的數(shù)量(t->user->sigpending)小于當(dāng)前進(jìn)程資源限制(t->signal->rlim[RLIMIT_SIGPENDING].rlim_cur),函數(shù)就會(huì)為新信號(hào)分配sigqueue數(shù)據(jù)結(jié)構(gòu):
q=kmem_cache_alloc(sigqueue_cachep,GFP_ATOMIC);
如果掛起信號(hào)的數(shù)量太多或前一步內(nèi)存分配失敗,則跳轉(zhuǎn)第9步。
掛起信號(hào)數(shù)量(t->user->sigpending)和每個(gè)用戶數(shù)據(jù)結(jié)構(gòu)的引用計(jì)數(shù)器(t->user)增加。
添加sigqueue數(shù)據(jù)結(jié)構(gòu)到掛起信號(hào)隊(duì)列(signals)中:
list_add_tail(&q->list,&signals->list);
完善sigqueue數(shù)據(jù)結(jié)構(gòu)中的siginfo_t表:
if((unsignedlong)info==0){ q->info.si_signo=sig; q->info.si_errno=0; q->info.si_code=SI_USER; q->info._sifields._kill._pid=current->pid; q->info._sifields._kill._uid=current->uid; }elseif((unsignedlong)info==1){ q->info.si_signo=sig; q->info.si_errno=0; q->info.si_code=SI_KERNEL; q->info._sifields._kill._pid=0; q->info._sifields._kill._uid=0; }else copy_siginfo(&q->info,info);
copy_siginfo()函數(shù)將調(diào)用者傳遞的siginfo_t表進(jìn)行拷貝。
設(shè)置隊(duì)列位掩碼中信號(hào)對(duì)應(yīng)的位:
sigaddset(&signals->signal,sig);
信號(hào)成功添加到掛起信號(hào)隊(duì)列中,返回0。
該步驟主要是處理信號(hào)無法添加到信號(hào)掛起隊(duì)列中的情況,比如,已經(jīng)有太多掛起信號(hào),或沒有內(nèi)存分配sigqueue,或者信號(hào)由內(nèi)核立即強(qiáng)制執(zhí)行。如果信號(hào)是實(shí)時(shí)的,且是有內(nèi)核函數(shù)發(fā)送并明確要求添加到隊(duì)列中時(shí),該函數(shù)返回錯(cuò)誤碼-EAGAIN:
if(sig>=32&&info&&(unsignedlong)info!=1&& info->si_code!=SI_USER) return-EAGAIN;
設(shè)置隊(duì)列位掩碼中信號(hào)對(duì)應(yīng)的位:
sigaddset(&signals->signal,sig);
返回0:即使信號(hào)沒有被添加到隊(duì)列中,對(duì)應(yīng)的位也已經(jīng)在掛起信號(hào)隊(duì)列中位掩碼中設(shè)置相應(yīng)位。
即使掛起的信號(hào)隊(duì)列中沒有空間容納相應(yīng)的項(xiàng),仍然讓目標(biāo)進(jìn)程接收信號(hào)是很重要的。例如,假設(shè)一個(gè)進(jìn)程正在消耗過多的內(nèi)存。內(nèi)核必須確保kill()成功,即使沒有可用內(nèi)存;否則,系統(tǒng)管理員沒有任何機(jī)會(huì)通過終止違規(guī)進(jìn)程來恢復(fù)系統(tǒng)。
3 group_send_sig_info()函數(shù)
group_send_sig_info()函數(shù)發(fā)送信號(hào)給整個(gè)線程組。它有三個(gè)參數(shù):信號(hào)sig,siginfo_t表地址(或者具體值0,1或2),和進(jìn)程描述符的地址p。
該函數(shù)執(zhí)行的大概步驟如下:
檢查sig是否正確:
if(sig0?||?sig?>64) return-EINVAL;
如果信號(hào)是由用戶進(jìn)程發(fā)送的,則檢查該操作是否被允許。只有滿足以下條件之一,信號(hào)才會(huì)被發(fā)送:
如果用戶進(jìn)程不被允許發(fā)送信號(hào),則返回-EPERM。
發(fā)送進(jìn)程的所有者具有適當(dāng)?shù)臋?quán)限(通常,這僅僅意味著信號(hào)是由系統(tǒng)管理員發(fā)出的,參見第20章)。
信號(hào)是SIGCONT,目標(biāo)進(jìn)程與發(fā)送進(jìn)程處于相同的登錄會(huì)話中。
兩個(gè)進(jìn)程屬于同一個(gè)用戶。
如果sig等于0,則立即返回,不會(huì)產(chǎn)生任何信號(hào):
if(!sig||!p->sighand) return0;
因?yàn)?不是有效的信號(hào)數(shù)字,所以它用于允許發(fā)送進(jìn)程檢查它是否具有向目標(biāo)線程組發(fā)送信號(hào)所需的特權(quán)。如果目標(biāo)進(jìn)程被殺死(通過檢查其信號(hào)處理程序是否被釋放進(jìn)行判斷),該函數(shù)也會(huì)返回。
申請(qǐng)p->sighand->siglock自旋鎖并禁止本地中斷。
調(diào)用handle_stop_signal()函數(shù),檢查某些類型的信號(hào),這些信號(hào)可能使目標(biāo)線程組中的其它掛起信號(hào)失效。
該函數(shù)執(zhí)行如下步驟:
a. 如果線程組被殺死(信號(hào)描述符中flags字段的SIGNAL_GROUP_EXIT標(biāo)志被設(shè)置),立即返回。
b. 如果sig是SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信號(hào),該函數(shù)會(huì)調(diào)用rm_from_queue()函數(shù)從共享掛起信號(hào)隊(duì)列p->signal->shared_pending和線程組中所有成員的私有隊(duì)列中移除SIGCONT信號(hào)。
c. 如果sig是SIGCONT,該函數(shù)會(huì)調(diào)用rm_from_queue()函數(shù)從共享掛起信號(hào)隊(duì)列p->signal->shared_pending中移除;然后,將相同的信號(hào)從線程組進(jìn)程的私有掛起信號(hào)隊(duì)列中移除,并喚醒他們:
rm_from_queue(0x003c0000,&p->signal->shared_pending); t=p; do{ rm_from_queue(0x003c0000,&t->pending); try_to_wake_up(t,TASK_STOPPED,0); t=next_thread(t); }while(t!=p);
掩碼0x003c0000選擇了四個(gè)停止信號(hào)。每次迭代,next_thread宏返回線程組中一個(gè)不同的輕量級(jí)進(jìn)程的描述符地址(參考第3章的進(jìn)程之間的關(guān)系)。
實(shí)際的代碼要遠(yuǎn)比上面的代碼片段復(fù)雜,因?yàn)閔andle_stop_signal()還處理捕獲SIGCONT信號(hào)的異常情況,以及在線程組中的所有進(jìn)程都停止時(shí)由于SIGCONT信號(hào)發(fā)生而導(dǎo)致的競(jìng)態(tài)條件。
檢查線程組是否忽略該信號(hào),如果忽略,則返回成功(0)。滿足忽略信號(hào)的三個(gè)條件即可,可以參考specific_send_sig_info()函數(shù)的介紹。
檢查信號(hào)是否為非實(shí)時(shí)信號(hào),且同一個(gè)信號(hào)是否已經(jīng)在線程組的共享掛起信號(hào)隊(duì)列中掛起:如果掛起,什么也不用做,返回成功即可(0):
if(sig<32?&&?sigismember(&p->signal->shared_pending.signal,sig)) return0;
通過以上檢查,則調(diào)用send_signal()將信號(hào)添加到共享掛起信號(hào)隊(duì)列中。如果send_signal()返回非零錯(cuò)誤碼,則將該錯(cuò)誤碼返回并終止執(zhí)行。
調(diào)用__group_complete_signal()函數(shù)喚醒線程組中的一個(gè)輕量級(jí)進(jìn)程。
釋放p->sighand->siglock自旋鎖,且使能本地中斷。
返回成功(0)。
__group_complete_signal()函數(shù)會(huì)掃描線程組中的進(jìn)程,查找可以接受新信號(hào)的進(jìn)程。前提是滿足一下條件:
該進(jìn)程不會(huì)阻塞信號(hào)
該進(jìn)程沒有處于EXIT_ZOMBIE、EXIT_DEAD、TASK_TRACED或TASK_STOPPED(特例是,如果該信號(hào)是SIGKILL,則進(jìn)程可以處于TASK_TRACED和TASK_STOPPED狀態(tài)中)
進(jìn)程沒有被殺死,也就是沒有設(shè)置PF_EXITING標(biāo)志
進(jìn)程當(dāng)前正在某個(gè)CPU核上執(zhí)行,或者它的TIF_SIGPENDING標(biāo)志尚未設(shè)置。(事實(shí)上,喚醒一個(gè)有掛起信號(hào)的進(jìn)程沒有意義的:一般來說,這個(gè)操作已經(jīng)由設(shè)置了TIF_SIGPENDING標(biāo)志的內(nèi)核控制路徑執(zhí)行了。另一方面,如果進(jìn)程當(dāng)前處于執(zhí)行中,它應(yīng)該收到新的掛起信號(hào)的通知。
線程組可能包含許多滿足條件的進(jìn)程。該函數(shù)選擇其中一個(gè):
如果進(jìn)程(由函數(shù)group_send_sig_info()傳遞的進(jìn)程描述符參數(shù)p標(biāo)識(shí))滿足前述所有條件并可以接收信號(hào),則函數(shù)選擇它。
否則,該函數(shù)從接收到最后一個(gè)線程組信號(hào)的進(jìn)程開始(p->signal->curr_target),通過掃描線程組的成員選擇一個(gè)合適的進(jìn)程。
如果__group_complete_signal()成功找到一個(gè)合適的進(jìn)程,它將設(shè)置信號(hào)傳遞到的進(jìn)程。首先,該函數(shù)會(huì)檢查信號(hào)是否致命:這種情況下,向線程組中的每個(gè)輕量級(jí)進(jìn)程發(fā)送SIGKILL信號(hào)來殺死整個(gè)線程組。如果信號(hào)不是致命的:則該函數(shù)調(diào)用signal_wake_up()來通知所選進(jìn)程它有一個(gè)新的掛起信號(hào)(參見前面章節(jié)的specific_send_sig_info()函數(shù)中的第4步)。
審核編輯:劉清
-
信號(hào)處理
+關(guān)注
關(guān)注
48文章
988瀏覽量
103112 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
315瀏覽量
21580
原文標(biāo)題:Linux內(nèi)核-信號(hào)的產(chǎn)生過程
文章出處:【微信號(hào):嵌入式ARM和Linux,微信公眾號(hào):嵌入式ARM和Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論