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