正如我們所知,運行在用戶態(tài)下的程序可以發(fā)送和接收信號。這意味著必須定義一組系統(tǒng)調(diào)用來允許這類操作。不幸的是,由于歷史原因,有些系統(tǒng)調(diào)用可能功能相同。 因此,其中一些系統(tǒng)調(diào)用永遠不會被調(diào)用。例如,sys_sigaction()和sys_rt_sigaction()幾乎相同,因此C庫中包含的sigaction()包裝函數(shù)最終會調(diào)用sys_rt_sigaction()而不是sys_sigaction()。
1 kill()
kill(pid,sig)系統(tǒng)調(diào)用用來給常規(guī)進程或多線程應用程序發(fā)送信號,相應的服務例程是sys_kill()。pid根據(jù)值的不同具有不同意義:
pid > 0:sig信號被發(fā)送給pid指定進程所屬的線程組。
pid = 0:sig信號被發(fā)送給與調(diào)用進程同一進程組內(nèi)所有進程的線程組。
pid = –1:sig信號被發(fā)送給所有進程,除了swapper(PID 0)、init(PID 1和current進程。
pid < –1:信號被發(fā)送給-pid進程組中所有進程的所屬線程組。
sys_kill()為信號建立一個最小siginfo_t表,然后調(diào)用kill_something_info():
info.si_signo=sig; info.si_errno=0; info.si_code=SI_USER; info._sifields._kill._pid=current->tgid; info._sifields._kill._uid=current->uid; returnkill_something_info(sig,&info,pid);
繼而,kill_something_info()既可以調(diào)用kill_proc_info()(通過group_send_sig_info()發(fā)送信號給單個線程組),也可以調(diào)用kill_pg_info()(掃描目標進程組所有進程并為每個進程調(diào)用send_sig_info()),還可以為系統(tǒng)中的每個進程重復調(diào)用group_send_sig_info()(pid=-1)。
kill()能夠發(fā)送任何信號,包括所謂的實時信號(32~64)。但是,正如我們在信號的產(chǎn)生過程一節(jié)中看到的,kill()系統(tǒng)調(diào)用不能確保將信號添加到目標進程的掛起信號隊列中,因此可能會丟失多個掛起信號。實時信號應該通過諸如rt_sigqueueinfo()之類的系統(tǒng)調(diào)用來發(fā)送。
System V和BSD Unix變體也有一個killpg()系統(tǒng)調(diào)用,它能夠顯式地向一組進程發(fā)送信號。在Linux中,該函數(shù)是作為使用kill()系統(tǒng)調(diào)用的庫函數(shù)實現(xiàn)的。另一種變體是raise(),它向當前進程(即執(zhí)行函數(shù)的進程)發(fā)送信號。在Linux中,raise()是作為庫函數(shù)實現(xiàn)的。
2 tkill()/tgkill()
tkill()和tgkill()向線程組中的特定進程發(fā)送信號。每個兼容POSIX的pthread庫的pthread_kill()函數(shù)調(diào)用其中的一個來向特定的輕量級進程發(fā)送信號。
tkill()需要2個參數(shù):pid,目標進程的PID;sig,信號編碼。內(nèi)核中的sys_tkill()服務例程填充siginfo表,獲取進程描述符地址,進行一些權限檢查,并調(diào)用specific_send_sig_info()發(fā)送信號。
tgkill()不同于tkill(),它需要第3個參數(shù):tgid,線程組ID,該線程組包含信號的目標進程。sys_tgkill()服務例程執(zhí)行與sys_tkill()完全相同的操作,但也檢查信號的目標進程是否屬于線程組tgid。這個額外的檢查解決了當信號被發(fā)送到一個正在被終止的進程時發(fā)生的競爭條件:如果另一個多線程應用程序創(chuàng)建輕量級進程的速度足夠快,那么信號可能會被傳遞給錯誤的進程。tgkill()解決了這個問題,因為線程組ID在多線程應用程序的生命周期內(nèi)永遠不會更改。
3 更改信號行為
sigaction(sig,act,oact)系統(tǒng)調(diào)用允許用戶為信號指定動作;當然,如果沒有定義信號動作,內(nèi)核將執(zhí)行信號相關聯(lián)的默認動作。
相應的sys_sigaction()服務例程作用于2個參數(shù):sig,信號值;act,類型為old_sigaction的表,用以指定新行為;oact,可選輸出參數(shù),用以獲取信號之前的行為動作。(old_sigaction和sigaction具有相同的數(shù)據(jù)結構,但是成員順序不一致)
該函數(shù)首先檢查act地址是否有效。然后用*act的字段填充k_sigaction類型的new_ka局部變量的sa_handler、sa_flags和sa_mask字段:
__get_user(new_ka.sa.sa_handler,&act->sa_handler); __get_user(new_ka.sa.sa_flags,&act->sa_flags); __get_user(mask,&act->sa_mask); siginitset(&new_ka.sa.sa_mask,mask);
函數(shù)調(diào)用do_sigaction()將新的new_ka表復制到current->sig->action表的第sig-1項中:
k=¤t->sig->action[sig-1]; if(act){ *k=*act; sigdelsetmask(&k->sa.sa_mask,sigmask(SIGKILL)|sigmask(SIGSTOP)); if(k->sa.sa_handler==SIG_IGN||(k->sa.sa_handler==SIG_DFL&& (sig==SIGCONT||sig==SIGCHLD||sig==SIGWINCH||sig==SIGURG))){ rm_from_queue(sigmask(sig),¤t->signal->shared_pending); t=current; do{ rm_from_queue(sigmask(sig),¤t->pending); recalc_sigpending_tsk(t); t=next_thread(t); }while(t!=current); } }
POSIX標準要求,當默認動作為ignore時,將信號動作設置為SIG_IGN或SIG_DFL會導致所有相同類型的掛起信號被丟棄。此外,請注意,無論信號處理程序請求的屏蔽信號是什么,SIGKILL和SIGSTOP都不會被屏蔽。
sigaction()系統(tǒng)調(diào)用還允許用戶初始化sigaction表中的sa_flags字段。我們在前面曾經(jīng)列出了該字段允許的值和相關含義。
舊的System V Unix變體提供了signal()系統(tǒng)調(diào)用,它仍然被程序員廣泛使用。最近的C庫通過rt_sigaction()實現(xiàn)了signal()。然而,Linux仍然支持舊的C庫,并提供sys_signal()服務例程:
new_sa.sa.sa_handler=handler; new_sa.sa.sa_flags=SA_ONESHOT|SA_NOMASK; ret=do_sigaction(sig,&new_sa,&old_sa); returnret?ret:(unsignedlong)old_sa.sa.sa_handler;
4 檢查掛起的阻塞信號
sigpending()系統(tǒng)調(diào)用允許進程檢查掛起的阻塞信號集,例如那些在阻塞時產(chǎn)生的信號。對應的sys_sigpending()服務例程作用于單個參數(shù)set,用戶變量的地址,在其中,位數(shù)組需要被拷貝:
sigorsets(&pending,¤t->pending.signal, ¤t->signal->shared_pending.signal); sigandsets(&pending,¤t->blocked,&pending); copy_to_user(set,&pending,4);
5 修改阻塞信號集
sigprocmask()系統(tǒng)調(diào)用允許進程修改阻塞信號集; 它僅適用于常規(guī)(非實時)信號。 相應的sys_sigprocmask()服務例程作用于3個參數(shù):
oset: 進程地址空間中,指向存儲先前位掩碼的位數(shù)組的指針。
set: 進程地址空間中,指向存儲新位掩碼的位數(shù)組的指針。
how:標志,可取的值如下所示:
SIG_BLOCK:set指向的位掩碼數(shù)組必須被添加阻塞信號的位掩碼數(shù)組中。
SIG_UNBLOCK:set指向的位掩碼數(shù)組必須從阻塞信號的位掩碼數(shù)組中移除。
SIG_SETMASK:set指向的位掩碼數(shù)組設定為新的阻塞信號的位掩碼數(shù)組。
該函數(shù)調(diào)用copy_from_user()將set指向的值拷貝到new_set這個局部變量中,并將current進程的阻塞的標準信號的位掩碼數(shù)組拷貝到old_set這個局部變量中。然后根據(jù)how標志設置這兩個變量:
if(copy_from_user(&new_set,set,sizeof(*set))) return-EFAULT; new_set&=~(sigmask(SIGKILL)|sigmask(SIGSTOP)); old_set=current->blocked.sig[0]; if(how==SIG_BLOCK) sigaddsetmask(¤t->blocked,new_set); elseif(how==SIG_UNBLOCK) sigdelsetmask(¤t->blocked,new_set); elseif(how==SIG_SETMASK) current->blocked.sig[0]=new_set; else return-EINVAL; recalc_sigpending(current); if(oset&©_to_user(oset,&old_set,sizeof(*oset))) return-EFAULT; return0;
6 掛起進程
在阻塞了由mask參數(shù)對應的標準信號之后,sigsuspend()系統(tǒng)調(diào)用將進程置于TASK_INTERRUPTIBLE狀態(tài)。只有當一個非忽略、非阻塞的信號被發(fā)送給進程時,進程才會被喚醒。
相應的sys_sigsuspend()服務例程執(zhí)行以下內(nèi)容:
mask&=~(sigmask(SIGKILL)|sigmask(SIGSTOP)); saveset=current->blocked; siginitset(¤t->blocked,mask); recalc_sigpending(current); regs->eax=-EINTR; while(1){ current->state=TASK_INTERRUPTIBLE; schedule(); if(do_signal(regs,&saveset)) return-EINTR; }
將進程設置為可中斷的掛起狀態(tài)后,調(diào)用schedule()函數(shù)選擇其它進程來運行。 當發(fā)出sigsuspend()系統(tǒng)調(diào)用的進程再次執(zhí)行時,sys_sigsuspend()調(diào)用do_signal()函數(shù)來傳遞喚醒進程的信號。 如果該函數(shù)返回值1,則不會忽略該信號。因此,系統(tǒng)調(diào)用通過返回錯誤代碼-EINTR來終止。
其實,這個功能完全可以通過組合sigprocmask()和sleep()來實現(xiàn)。但是,sigsuspend()解決了一個競態(tài)問題:因為進程隨時會交叉執(zhí)行,先通過系統(tǒng)調(diào)用執(zhí)行A動作,然后通過系統(tǒng)調(diào)用執(zhí)行B動作,并不等價于通過單個系統(tǒng)調(diào)用直接執(zhí)行A和B兩個動作。
這種情況下,sigprocmask()可能會在調(diào)用sleep()之前對信號解除阻塞。如果這個發(fā)生,因為喚醒信號已經(jīng)傳遞過,從而進程得不到喚醒而永遠留在TASK_INTERRUPTIBLE狀態(tài)。相反,sigsuspend()不允許在解除阻塞之后和schedule()調(diào)用之前發(fā)送信號,因為其它進程在這個時間段不可能搶占CPU時間。
7 實時信號的系統(tǒng)調(diào)用
前面介紹的系統(tǒng)調(diào)用都是針對標準信號的,對于實時信號有專門的的系統(tǒng)調(diào)用。
實時信號的系統(tǒng)調(diào)用,如rt_sigaction()、rt_sigpending()、rt_sigprocmask()、rt_sigsuspend()與前面的標準信號對應的系統(tǒng)調(diào)用類似,不再贅述。簡單介紹一下實時信號隊列相關的兩個系統(tǒng)調(diào)用:
rt_sigqueueinfo()
發(fā)送實時信號,以便將其添加目標進程的共享掛起信號隊列中。通常通過標準庫函數(shù)中的sigqueue()實現(xiàn)。
rt_sigtimedwait()
將阻塞的掛起信號從隊列中取出而不發(fā)送它,并將信號值返回給調(diào)用者;如果沒有阻塞信號掛起,則將當前進程掛起一段固定的時間。通常通過標準庫函數(shù)sigwaitinfo()和sigtimedwait()調(diào)用。
審核編輯:湯梓紅
-
內(nèi)核
+關注
關注
4文章
1427瀏覽量
42223 -
Linux
+關注
關注
88文章
11576瀏覽量
216642 -
線程
+關注
關注
0文章
508瀏覽量
20653 -
系統(tǒng)調(diào)用
+關注
關注
0文章
28瀏覽量
8580
原文標題:Linux內(nèi)核-信號相關的系統(tǒng)調(diào)用
文章出處:【微信號:嵌入式ARM和Linux,微信公眾號:嵌入式ARM和Linux】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
Linux內(nèi)核中系統(tǒng)調(diào)用詳解

linux內(nèi)核系統(tǒng)調(diào)用之參數(shù)傳遞
Linux內(nèi)核中信號的傳遞過程

Linux內(nèi)核之系統(tǒng)調(diào)用
ARM linux系統(tǒng)調(diào)用的實現(xiàn)原理
Linux內(nèi)核系統(tǒng)調(diào)用擴展研究

編譯Linux2.6內(nèi)核并添加一個系統(tǒng)調(diào)用
透了解系統(tǒng)調(diào)用助你成為Linux下編程高手

你知道Linux系統(tǒng)調(diào)用的原理

Linux下系統(tǒng)調(diào)用的技巧
Linux的系統(tǒng)調(diào)用是什么
如何區(qū)分xenomai、linux系統(tǒng)調(diào)用/服務
Linux內(nèi)核系統(tǒng)調(diào)用概述及實現(xiàn)原理

Linux系統(tǒng)調(diào)用的具體實現(xiàn)原理

評論