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