-
1 信號(hào)的角色
-
2 信號(hào)的響應(yīng)行為
-
3 POSIX信號(hào)和多線程程序
-
4 與信號(hào)相關(guān)的數(shù)據(jù)結(jié)構(gòu)
-
5 信號(hào)數(shù)據(jù)結(jié)構(gòu)的操作函數(shù)
-
5.1 x86架構(gòu)
-
5.2 ARM和RISC-V架構(gòu)
-
Unix
最早引入了信號(hào)機(jī)制,允許用戶進(jìn)程間進(jìn)行交互;內(nèi)核也使用信號(hào)通知進(jìn)程某些系統(tǒng)事件。信號(hào)機(jī)制已經(jīng)存在了30年,期間只有一些細(xì)微的變化。
我們首先介紹Linux
內(nèi)核如何處理信號(hào),其次討論允許進(jìn)程交換信號(hào)的系統(tǒng)調(diào)用。
1 信號(hào)的角色
信號(hào)是發(fā)送給進(jìn)程,或一組進(jìn)程的非常短的消息。通常,可能僅發(fā)送一個(gè)表示信號(hào)的編碼。標(biāo)準(zhǔn)信號(hào)沒(méi)有參數(shù)等其它信息。
信號(hào)的編碼,在Linux
中使用前綴SIG
的宏表示。如前面提到的SIGCHLD
宏,其展開的值是17
,當(dāng)子進(jìn)程停止或終止時(shí),發(fā)送給父進(jìn)程的信號(hào)。SIGSEGV
,等于11
,當(dāng)進(jìn)程發(fā)生非法內(nèi)存引用時(shí)發(fā)送給進(jìn)程的信號(hào)。
信號(hào)兩個(gè)主要作用:
- 使進(jìn)程意識(shí)到發(fā)生了某個(gè)事件
- 讓進(jìn)程執(zhí)行信號(hào)處理程序
當(dāng)然,這兩個(gè)目的不是相互排斥的,因?yàn)橥ǔ_M(jìn)程必須對(duì)某些事件做出響應(yīng)(如執(zhí)行服務(wù)例程)。
1.1 x86/64架構(gòu)信號(hào)定義
表11-1
列出了Linux/i386
的前31
個(gè)信號(hào)(Unix
系統(tǒng)定義的信號(hào),x86
架構(gòu),Linux2.6.11
,linux
后續(xù)版本中32/64
位的信號(hào)定義統(tǒng)一到了一個(gè)文件中)。某些信號(hào),比如SIGCHLD
或SIGSTOP
與架構(gòu)相關(guān);甚至,還有一些信號(hào)如SIGSTKFLT
專門為某些架構(gòu)定義的。
# |
信號(hào) |
默認(rèn)動(dòng)作 |
說(shuō)明 |
POSIX |
---|---|---|---|---|
1 |
SIGHUP |
Terminate |
掛起控制終端和進(jìn)程 |
Yes |
2 |
SIGINT |
Terminate |
鍵盤中斷 |
Yes |
3 |
SIGQUIT |
Dump |
鍵盤退出 |
Yes |
4 |
SIGILL |
Dump |
非法指令 |
Yes |
5 |
SIGTRAP |
Dump |
調(diào)試斷點(diǎn) |
No |
6 |
SIGABRT |
Dump |
異常終止 |
Yes |
6 |
SIGIOT |
Dump |
等價(jià)于SIGABRT |
No |
7 |
SIGBUS |
Dump |
總線錯(cuò)誤 |
No |
8 |
SIGFPE |
Dump |
浮點(diǎn)異常 |
Yes |
9 |
SIGKILL |
Terminate |
殺死進(jìn)程 |
Yes |
10 |
SIGUSR1 |
Terminate |
進(jìn)程可用 |
Yes |
11 |
SIGSEGV |
Dump |
非法內(nèi)存引用 |
Yes |
12 |
SIGUSR2 |
Terminate |
進(jìn)程可用 |
Yes |
13 |
SIGPIPE |
Terminate |
管道沒(méi)有讀進(jìn)程使用 |
Yes |
14 |
SIGALRM |
Terminate |
實(shí)時(shí)時(shí)鐘 |
Yes |
15 |
SIGTERM |
Terminate |
進(jìn)程終止 |
Yes |
16 |
SIGSTKFLT |
Terminate |
協(xié)處理器堆棧錯(cuò)誤 |
No |
17 |
SIGCHLD |
Ignore |
子進(jìn)程停止/終止/被跟蹤時(shí)的信號(hào) |
Yes |
18 |
SIGCONT |
Continue |
恢復(fù)執(zhí)行 |
Yes |
19 |
SIGSTOP |
Stop |
停止進(jìn)程執(zhí)行 |
Yes |
20 |
SIGTSTP |
Stop |
停止tty發(fā)起的進(jìn)程 |
Yes |
21 |
SIGTTIN |
Stop |
后臺(tái)進(jìn)程需要輸入 |
Yes |
22 |
SIGTTOU |
Stop |
后臺(tái)進(jìn)程需要輸出 |
Yes |
23 |
SIGURG |
Ignore |
套接字上的緊急條件 |
No |
24 |
SIGXCPU |
Dump |
超出CPU時(shí)間限制 |
No |
25 |
SIGXFSZ |
Dump |
超出文件大小限制 |
No |
26 |
SIGVTALRM |
Terminate |
用戶態(tài)占用CPU時(shí)間定時(shí)器 |
No |
27 |
SIGPROF |
Terminate |
用戶態(tài)和內(nèi)核態(tài)占用CPU時(shí)間定時(shí)器 |
No |
28 |
SIGWINCH |
Ignore |
窗口大小改變 |
No |
29 |
SIGIO |
Terminate |
異步IO |
No |
29 |
SIGPOLL |
Terminate |
可輪詢事件(poll) |
No |
30 |
SIGPWR |
Terminate |
電源失效/重啟動(dòng) |
No |
31 |
SIGSYS |
Dump |
無(wú)效系統(tǒng)調(diào)用 |
No |
31 |
SIGUNUSED |
Dump |
等價(jià)于SIGSYS |
No |
除了上表中的常規(guī)信號(hào)之外,POSIX
標(biāo)準(zhǔn)還引入了一類新的信號(hào),稱為實(shí)時(shí)信號(hào)
,Linux
中信號(hào)范圍是32~64
。實(shí)時(shí)信號(hào)具有以下特性:
-
增加了從
SIGRTMIN
到SIGRTMAX
的實(shí)時(shí)信號(hào),可以通過(guò)sysconf(_SC_RTSIG_MAX)
系統(tǒng)函數(shù)獲得當(dāng)前操作系統(tǒng)支持的實(shí)時(shí)信號(hào)的個(gè)數(shù)。但是要注意,一般libc
會(huì)對(duì)SIGRTMIN
進(jìn)行修改,保留幾個(gè)預(yù)設(shè)的值用于pthread
內(nèi)部,比如glibc
就保留了3
個(gè)值。所以在使用實(shí)時(shí)信號(hào)的時(shí)候,應(yīng)該使用SIGRTMIN+n
、SIGRTMAX-n
的方式,而不是直接使用數(shù)值。 -
實(shí)時(shí)信號(hào)和常規(guī)信號(hào)不一樣,它沒(méi)有明確的含義,而是由使用者自己來(lái)決定如何使用。
-
進(jìn)程可以接受多個(gè)相同的實(shí)時(shí)信號(hào),而常規(guī)信號(hào)不能,在常規(guī)信號(hào)沒(méi)有得到處理的時(shí)候,多個(gè)常規(guī)信號(hào)會(huì)被合為一個(gè)。
-
實(shí)時(shí)信號(hào)使用
sigqueue
發(fā)送的時(shí)候,可以攜帶附加的數(shù)據(jù)(int
或者pointer
)。 -
實(shí)時(shí)信號(hào)有時(shí)間順序的概念,所以同樣的實(shí)時(shí)信號(hào)會(huì)按次序被處理。
-
信號(hào)實(shí)質(zhì)上是軟中斷,中斷有優(yōu)先級(jí),信號(hào)也有優(yōu)先級(jí)。實(shí)時(shí)信號(hào)具有優(yōu)先的概念,數(shù)值越低的信號(hào)其優(yōu)先級(jí)越高,也就是數(shù)值低的實(shí)時(shí)信號(hào)優(yōu)先得到處理。實(shí)時(shí)信號(hào)和標(biāo)準(zhǔn)信號(hào)的優(yōu)先級(jí),在
POSIX
中是未定義的,一般來(lái)說(shuō)會(huì)優(yōu)先處理標(biāo)準(zhǔn)信號(hào)。 -
實(shí)時(shí)信號(hào)的默認(rèn)行為都一樣,都是結(jié)束當(dāng)前的進(jìn)程,這個(gè)和標(biāo)準(zhǔn)信號(hào)是不一樣的。
盡管Linux
內(nèi)核不使用實(shí)時(shí)信號(hào),但是它通過(guò)幾個(gè)特殊的系統(tǒng)調(diào)用完整支持POSIX
標(biāo)準(zhǔn)。
以Ubuntu 18.04
為例,查看Linux
系統(tǒng)中使用的信號(hào)方法:
$kill-l
1)SIGHUP2)SIGINT3)SIGQUIT4)SIGILL5)SIGTRAP
6)SIGABRT7)SIGBUS8)SIGFPE9)SIGKILL10)SIGUSR1
11)SIGSEGV12)SIGUSR213)SIGPIPE14)SIGALRM15)SIGTERM
16)SIGSTKFLT17)SIGCHLD18)SIGCONT19)SIGSTOP20)SIGTSTP
21)SIGTTIN22)SIGTTOU23)SIGURG24)SIGXCPU25)SIGXFSZ
26)SIGVTALRM27)SIGPROF28)SIGWINCH29)SIGIO30)SIGPWR
31)SIGSYS34)SIGRTMIN35)SIGRTMIN+136)SIGRTMIN+237)SIGRTMIN+3
38)SIGRTMIN+439)SIGRTMIN+540)SIGRTMIN+641)SIGRTMIN+742)SIGRTMIN+8
43)SIGRTMIN+944)SIGRTMIN+1045)SIGRTMIN+1146)SIGRTMIN+1247)SIGRTMIN+13
48)SIGRTMIN+1449)SIGRTMIN+1550)SIGRTMAX-1451)SIGRTMAX-1352)SIGRTMAX-12
53)SIGRTMAX-1154)SIGRTMAX-1055)SIGRTMAX-956)SIGRTMAX-857)SIGRTMAX-7
58)SIGRTMAX-659)SIGRTMAX-560)SIGRTMAX-461)SIGRTMAX-362)SIGRTMAX-2
63)SIGRTMAX-164)SIGRTMAX
1.2 ARM架構(gòu)信號(hào)定義
下圖右邊是ARM
架構(gòu)與x86
架構(gòu)信號(hào)定義的比較圖(左邊是x86
架構(gòu),右邊是ARM
架構(gòu))。通過(guò)對(duì)比發(fā)現(xiàn),ARM
架構(gòu)比x86
架構(gòu)多了一個(gè)SIGSWI
信號(hào)。在對(duì)內(nèi)核源代碼進(jìn)行進(jìn)一步調(diào)查后,發(fā)現(xiàn)唯一提到SIGSWI
(不包括聲明本身)的是文件(
Linux 5.18.18
中,位于tools/perf/trace/beauty/signum.c
。具體代碼中只有在打印信號(hào)的時(shí)候用,貌似已經(jīng)從內(nèi)核中移除。)。一些奇怪的基于ARM
的操作系統(tǒng)(RISCOS)
使用這種方式與其模擬器進(jìn)行通信。它被稱為Arthur OS
。
1.3 RISC-V架構(gòu)信號(hào)定義
RISC-V
架構(gòu)信號(hào)定義如下面所示,Linux 6.7
內(nèi)核,文件位于/include/uapi/asm-generic/signal.h
。信號(hào)的定義直接使用了標(biāo)準(zhǔn)的接口規(guī)范。
#define_NSIG64
//...省略
#defineSIGHUP1
#defineSIGINT2
//...省略
#defineSIGPWR30
#defineSIGSYS31
#defineSIGUNUSED31
/*用戶進(jìn)程不能認(rèn)為這些是常數(shù)*/
#defineSIGRTMIN32
#ifndefSIGRTMAX
#defineSIGRTMAX_NSIG
#endif
所以說(shuō),對(duì)于Linux
信號(hào)來(lái)說(shuō),不管是x86
架構(gòu),ARM
架構(gòu),還是RISC-V
,都是統(tǒng)一的,沒(méi)有什么變化。
1.4 信號(hào)的系統(tǒng)調(diào)用
Linux
提供了一些系統(tǒng)調(diào)用,允許編程者發(fā)送信號(hào),并決定如何響應(yīng)接收到的信號(hào)。下表列出了這些系統(tǒng)調(diào)用:
系統(tǒng)調(diào)用 | 描述 |
---|---|
kill() |
發(fā)送信號(hào)給線程組 |
tkill() |
發(fā)送信號(hào)給進(jìn)程 |
tgkill() |
發(fā)送信號(hào)給特定線程組中的進(jìn)程 |
sigaction() |
設(shè)定信號(hào)的行為 |
signal() |
與sigaction()類似 |
sigpending() |
檢查是否為掛起信號(hào) |
sigprocmask() |
修改阻塞信號(hào) |
sigsuspend() |
等待信號(hào) |
rt_sigaction() |
設(shè)定實(shí)時(shí)信號(hào)的行為 |
rt_sigpending() |
檢查是否為掛起的實(shí)時(shí)信號(hào) |
rt_sigprocmask() |
修改阻塞的實(shí)時(shí)信號(hào) |
rt_sigqueueinfo() |
發(fā)送實(shí)時(shí)信號(hào)給線程組 |
rt_sigsuspend() |
等待實(shí)時(shí)信號(hào) |
rt_sigtimedwait() |
與rt_sigsuspend()類似 |
1.5 信號(hào)工作原理
信號(hào)的一個(gè)重要特性是,可能會(huì)在任何時(shí)候傳遞給進(jìn)程。發(fā)送給沒(méi)有在執(zhí)行狀態(tài)的進(jìn)程,就需要保存該信號(hào),以便進(jìn)程恢復(fù)執(zhí)行時(shí)處理它。阻塞信號(hào)要求在解除阻塞之前延緩信號(hào)的傳遞。
因此,Linux
將內(nèi)核的傳遞分為了兩個(gè)階段:
-
信號(hào)產(chǎn)生
內(nèi)核更新目標(biāo)進(jìn)程的數(shù)據(jù)結(jié)構(gòu),表達(dá)一個(gè)新信號(hào)要被發(fā)送。
-
信號(hào)傳遞
內(nèi)核通過(guò)改變目標(biāo)進(jìn)程的狀態(tài),且執(zhí)行指定信號(hào)處理程序,以強(qiáng)制其響應(yīng)信號(hào),
每個(gè)信號(hào)最多傳遞一次。信號(hào)是消耗性資源:一旦它們被傳遞,所有進(jìn)程描述符中跟信號(hào)有關(guān)的數(shù)據(jù)引用都將取消。
產(chǎn)生還沒(méi)有傳遞的信號(hào),稱為掛起信號(hào)。任何時(shí)候,一個(gè)進(jìn)程只能存在一個(gè)給定類型的掛起信號(hào);同一個(gè)進(jìn)程的同類掛起信號(hào)會(huì)被拋棄。但是,實(shí)時(shí)信號(hào)與此不同:可以同時(shí)存在多個(gè)同類型的掛起信號(hào)。
信號(hào)產(chǎn)生還沒(méi)有被傳遞這段時(shí)間,通常存在于以下時(shí)間段:
-
信號(hào)通常只傳遞給當(dāng)前正在運(yùn)行的進(jìn)程(
current
)。 -
進(jìn)程可以有選擇地阻塞信號(hào)。這種情況下,進(jìn)程不會(huì)接收信號(hào),除非解除阻塞。
-
執(zhí)行信號(hào)處理程序時(shí),進(jìn)程通常屏蔽掉響應(yīng)的信號(hào)(例如,在信號(hào)處理程序執(zhí)行完之前自動(dòng)阻塞該信號(hào))。也就是說(shuō),信號(hào)處理程序不會(huì)被正在處理的信號(hào)打斷,所以,信號(hào)處理程序不需要考慮可重入的問(wèn)題。
盡管信號(hào)的概念非常簡(jiǎn)單,內(nèi)核實(shí)現(xiàn)卻相當(dāng)復(fù)雜。內(nèi)核必須:
-
記住哪些信號(hào)被哪個(gè)進(jìn)場(chǎng)阻塞。
-
當(dāng)從內(nèi)核態(tài)切換到用戶態(tài)時(shí),檢查該進(jìn)程是否有信號(hào)需要處理。這通常發(fā)生在每次定時(shí)器中斷時(shí)(大約幾個(gè)毫秒一次)。
-
判斷該信號(hào)是否被忽略。滿足忽略的條件如下:
-
目標(biāo)進(jìn)程沒(méi)有被其它進(jìn)程追蹤(也就是進(jìn)程描述符中的
PT_PTRACED
標(biāo)志等于0
)。 - 信號(hào)沒(méi)有被目標(biāo)進(jìn)程阻塞。
- 信號(hào)正在被目標(biāo)忽略。(可以是進(jìn)程顯式忽略,也可以是信號(hào)的默認(rèn)行為是忽略且進(jìn)程沒(méi)有更改它)
-
目標(biāo)進(jìn)程沒(méi)有被其它進(jìn)程追蹤(也就是進(jìn)程描述符中的
-
處理信號(hào),可能涉及到在進(jìn)程執(zhí)行的任何時(shí)候切換到信號(hào)處理程序,且需要在處理程序返回時(shí)恢復(fù)其原始執(zhí)行上下文。
此外,Linux
必須考慮到BSD
和System V
信號(hào)采用的不同語(yǔ)義,它必須遵循相當(dāng)復(fù)雜的POSIX
要求。
2 信號(hào)的響應(yīng)行為
信號(hào)的響應(yīng)方式有3
種:
- 忽略信號(hào)
-
Terminate
殺死進(jìn)程。
-
Dump
殺死進(jìn)程,如果可能的話創(chuàng)建一個(gè)包含其上下文的核心轉(zhuǎn)儲(chǔ)文件。該文件主要用于調(diào)試目的。
-
Ignore
忽略信號(hào)。
-
Stop
停止進(jìn)程。例如將進(jìn)程置為
TASK_STOPPED
狀態(tài)。 -
Continue
繼續(xù)進(jìn)程(
TASK_STOPPED
),將其置于TASK_RUNNING
狀態(tài)。
- 執(zhí)行信號(hào)的默認(rèn)行為。默認(rèn)行為是內(nèi)核預(yù)定義好的,如下所示:
- 捕獲信號(hào),執(zhí)行自定義的信號(hào)處理程序。
注意,阻塞信號(hào)不同于忽略信號(hào)。只要信號(hào)被阻塞,就不會(huì)傳遞它。而忽略信號(hào)總是傳遞它,只是不執(zhí)行響應(yīng)動(dòng)作。
SIGKILL
和SIGSTOP
信號(hào)不能被忽略,捕獲或阻塞。它們的默認(rèn)動(dòng)作總是會(huì)被執(zhí)行。因此,SIGKILL
和SIGSTOP
信號(hào)給予合適權(quán)限的用戶可以終止、停止每個(gè)進(jìn)程(這兒有兩個(gè)例外:不能向進(jìn)程0
-swapper
發(fā)送信號(hào),并且發(fā)送給進(jìn)程1
-init
的信號(hào)總是被丟棄。因此,進(jìn)程0
永遠(yuǎn)不會(huì)死亡,而進(jìn)程1
只有在init
終止時(shí)才會(huì)死亡。)。
如果信號(hào)造成內(nèi)核殺死進(jìn)程,那么對(duì)于給定進(jìn)程是非常致命的,比如SIGKILL
。默認(rèn)行為為Terminate
的信號(hào)且未被進(jìn)程捕獲,對(duì)于該進(jìn)程來(lái)說(shuō)也是致命的。但是,如果信號(hào)被捕獲,而其處理程序終止進(jìn)程則不是致命的,因?yàn)檫M(jìn)程自己選擇的終止,不是內(nèi)核殺死的。
3 POSIX信號(hào)和多線程程序
POSIX 1003.1
標(biāo)準(zhǔn)對(duì)多線程應(yīng)用程序的信號(hào)處理有嚴(yán)格的要求:
-
多線程應(yīng)用程序中所有線程共享信號(hào)處理程序;但是,每個(gè)線程必須具有自己的掛起信號(hào)和阻塞信號(hào)的位數(shù)組。
-
kill()
和sigqueue()
等POSIX
庫(kù)函數(shù)必須發(fā)送信號(hào)給整個(gè)多線程應(yīng)用,而不是某個(gè)特定的線程。內(nèi)核生成的所有信號(hào)(如SIGCHLD
、SIGINT
或SIGQUIT
)都是如此。 -
發(fā)送給多線程應(yīng)用的信號(hào)只被傳遞給一個(gè)線程,由內(nèi)核在沒(méi)有阻塞該信號(hào)的線程中任意選擇。
-
如果致命信號(hào)發(fā)送給多線程應(yīng)用,內(nèi)核將殺死應(yīng)用程序的所有線程,而不僅僅是信號(hào)傳遞給的那個(gè)線程。
為了遵循POSIX
標(biāo)準(zhǔn),Linux 2.6
內(nèi)核將多線程應(yīng)用程序?qū)崿F(xiàn)為屬于同一線程組的一組輕量級(jí)進(jìn)程。
本文中的
線程組
是廣義的,甚至可以使傳統(tǒng)意義上的單進(jìn)程。術(shù)語(yǔ)進(jìn)程
表示傳統(tǒng)意義上的進(jìn)程或輕量級(jí)進(jìn)程(線程組中的某個(gè)成員)。
此外,如果信號(hào)被發(fā)送給一個(gè)特定的進(jìn)程,則是私有的;如果發(fā)送給整個(gè)線程組,則是共享的。
4 與信號(hào)相關(guān)的數(shù)據(jù)結(jié)構(gòu)
為了追蹤進(jìn)程或線程組的信號(hào)狀態(tài),內(nèi)核在進(jìn)程描述符中提供了幾個(gè)可訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)。重要的數(shù)據(jù)結(jié)構(gòu)如下所示:
其中,進(jìn)程描述符中與信號(hào)處理相關(guān)的數(shù)據(jù)字段如下所示:
數(shù)據(jù)類型 | 名稱 | 描述 |
---|---|---|
struct signal_struct * |
signal |
指向進(jìn)程的信號(hào)描述符 |
struct sighand_struct * |
sighand |
指向進(jìn)程的信號(hào)處理程序描述符 |
sigset_t |
blocked |
阻塞信號(hào)掩碼 |
sigset_t |
real_blocked |
阻塞信號(hào)臨時(shí)掩碼(rt_sigtimedwait() )系統(tǒng)調(diào)用使用 |
struct sigpending |
pending |
私有掛起信號(hào) |
unsigned long |
sas_ss_sp |
備選信號(hào)處理程序堆棧的地址 |
blocked
存儲(chǔ)了被進(jìn)程屏蔽掉的信號(hào)。數(shù)據(jù)類型為sigset_t
,是一個(gè)位數(shù)組,每一位代表一類信號(hào):
typedefstruct{
unsignedlongsig[2];
}sigset_t;
因?yàn)?code style="color:rgb(30,107,184);font-size:14px;line-height:1.8em;letter-spacing:0em;background:rgba(27,31,35,.05) none no-repeat scroll 0% 0%;width:auto;height:auto;margin-left:2px;margin-right:2px;padding:2px 4px;border-style:none;border-width:3px;border-color:rgba(0,0,0,.4) rgba(0,0,0,.4);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">32位系統(tǒng)的unsigned long
是32
位,信號(hào)最大數(shù)量是64
(用_NSIG
宏表示)。因?yàn)闆](méi)有信號(hào)是0
,所以,信號(hào)值等于sigset_t
中位索引加1
。具體可以參考前面列出的表。
4.1 信號(hào)描述符和信號(hào)處理程序描述符
進(jìn)程描述符中的signal
字段指向信號(hào)描述符,類型為signal_struct
,用來(lái)記錄共享掛起信號(hào)。此外,信號(hào)描述符還有一些與信號(hào)處理不太相關(guān)的字段,如rlim
(進(jìn)程資源限制),或pgrp
和session
字段,分別存儲(chǔ)線程組領(lǐng)導(dǎo)者的PID
和進(jìn)程中會(huì)話領(lǐng)導(dǎo)者的PID
。事實(shí)上,我們?cè)趯W(xué)習(xí)clone(),fork(),和vfork()系統(tǒng)調(diào)用
一節(jié)時(shí)了解到,同一線程組中的所有進(jìn)程共享信號(hào)描述符,也就是說(shuō),通過(guò)clone()
系統(tǒng)調(diào)用,并設(shè)置CLONE_THREAD
標(biāo)志,創(chuàng)建的所有進(jìn)程,其信號(hào)描述符中所有字段必須相同。
信號(hào)描述符中與信號(hào)處理相關(guān)的字段,如下表所示:
類型 | 變量 | 描述 |
---|---|---|
atomic_t |
count |
信號(hào)描述符的使用計(jì)數(shù)器 |
atomic_t |
live |
線程組中活動(dòng)進(jìn)程的數(shù)量 |
wait_queue_head_t |
wait_chldexit |
wait4() 系統(tǒng)調(diào)用中休眠進(jìn)程的等待隊(duì)列 |
struct task_struct * |
curr_target |
線程組中接收到信號(hào)的最后一個(gè)進(jìn)程的描述符 |
struct sigpending |
shared_pending |
共享掛起信號(hào)的數(shù)據(jù)結(jié)構(gòu) |
int |
group_exit_code |
線程組的進(jìn)程終止碼 |
struct task_struct * |
group_exit_task |
殺死整個(gè)線程組時(shí)使用 |
int |
notify_count |
殺死整個(gè)線程組時(shí)使用 |
int |
group_stop_count |
停止整個(gè)線程組時(shí)使用 |
unsigned int |
flags |
傳遞修改進(jìn)程狀態(tài)的信號(hào)時(shí)使用的標(biāo)志 |
除了信號(hào)描述符,每個(gè)進(jìn)程還有一個(gè)信號(hào)處理描述符,數(shù)據(jù)結(jié)構(gòu)為sighand_struct
,其描述了線程組怎樣處理信號(hào)。其字段如下所示:
類型 | 變量 | 描述 |
---|---|---|
atomic_t |
count |
信號(hào)處理程序描述符的使用計(jì)數(shù)器 |
struct k_sigaction[64] |
action |
指定傳遞信號(hào)時(shí)要執(zhí)行的動(dòng)作的結(jié)構(gòu)數(shù)組 |
spinlock_t |
siglock |
包含信號(hào)和信號(hào)處理程序等描述符的自旋鎖 |
正如先前提到的,使用clone()
和CLONE_SIGHAND
標(biāo)志創(chuàng)建的進(jìn)程們共享信號(hào)處理描述符。所以,count
字段記錄了共享信號(hào)處理描述符的進(jìn)程數(shù)。在POSIX
多線程應(yīng)用中,線程組中的所有輕量級(jí)進(jìn)程引用相同的信號(hào)描述符和相同的信號(hào)處理描述符。
4.2 sigaction數(shù)據(jù)結(jié)構(gòu)
有些架構(gòu)可能會(huì)將信號(hào)的某些屬性僅對(duì)內(nèi)核可見(jiàn)。因此,存儲(chǔ)在k_sigaction
中的信號(hào)屬性,既包含了對(duì)用戶態(tài)隱藏的屬性,也包含了用戶態(tài)所有可見(jiàn)的屬性。事實(shí)上,在x86
平臺(tái)上,所有的信號(hào)屬性對(duì)用戶態(tài)都是可見(jiàn)的。
因此,k_sigaction
結(jié)構(gòu)簡(jiǎn)化為一個(gè)類型為sigaction
的sa
結(jié)構(gòu),它包含如下字段*:
用戶態(tài)應(yīng)用程序用來(lái)給
signal()
和sigaction()
系統(tǒng)調(diào)用傳遞參數(shù)的sigaction
數(shù)據(jù)結(jié)構(gòu)與內(nèi)核使用的數(shù)據(jù)結(jié)構(gòu)略有不同。
-
sa_handler
該字段指定要執(zhí)行的動(dòng)作類型;可以是信號(hào)處理程序的指針,
SIG_DFL
(值為0
,執(zhí)行默認(rèn)行為),或SIG_IGN
(值為1
,忽略信號(hào))。 -
sa_flags
如何處理處理信號(hào)的標(biāo)志,下表列出了其中的一些。
因?yàn)闅v史原因,這些標(biāo)志和
irqaction
具有一樣的前綴SA_
;然而,兩組標(biāo)志沒(méi)有任何關(guān)系。 -
sa_mask
類型為
sigset_t
,用來(lái)指定在運(yùn)行信號(hào)處理程序時(shí)屏蔽掉的信號(hào)。
表 sa_flags
值和意義
標(biāo)志名稱 | 描述 |
---|---|
SA_NOCLDSTOP |
僅適用于SIGCHLD ;當(dāng)進(jìn)程停止時(shí)不向父進(jìn)程發(fā)送SIGCHLD |
SA_NOCLDWAIT |
僅適用于SIGCHLD ;當(dāng)進(jìn)程終止時(shí)不會(huì)創(chuàng)建zombie 僵尸進(jìn)程 |
SA_SIGINFO |
向信號(hào)處理程序提供額外的信息(查看稍后的改變信號(hào)動(dòng)作 ) |
SA_ONSTACK |
為信號(hào)處理程序使用替代堆棧(查看稍后的捕捉信號(hào) 一節(jié)) |
SA_RESTART |
中斷的系統(tǒng)調(diào)用自動(dòng)重啟(查看稍后的`系統(tǒng)調(diào)用的重新執(zhí)行) |
SA_NODEFER ,SA_NOMASK |
在執(zhí)行信號(hào)處理程序時(shí)不屏蔽信號(hào) |
SA_RESETHAND ,SA_ONESHOT |
在執(zhí)行信號(hào)處理程序后重置為默認(rèn)操作 |
4.2.1 x86/Linux2.6.11的定義
#ifdef__KERNEL__
structold_sigaction{
__sighandler_tsa_handler;
old_sigset_tsa_mask;
unsignedlongsa_flags;
__sigrestore_tsa_restorer;
};
structsigaction{
__sighandler_tsa_handler;
unsignedlongsa_flags;
__sigrestore_tsa_restorer;
sigset_tsa_mask;/*masklastforextensibility*/
};
structk_sigaction{
structsigactionsa;
};
#else
/*這是為了迎合libc庫(kù)的實(shí)現(xiàn)*/
structsigaction{
union{
__sighandler_t_sa_handler;
void(*_sa_sigaction)(int,structsiginfo*,void*);
}_u;
sigset_tsa_mask;
unsignedlongsa_flags;
void(*sa_restorer)(void);
};
#definesa_handler_u._sa_handler
#definesa_sigaction_u._sa_sigaction
#endif/*__KERNEL__*/
4.2.2 x86-64/Linux2.6.11的定義
structsigaction{
__sighandler_tsa_handler;
unsignedlongsa_flags;
__sigrestore_tsa_restorer;
sigset_tsa_mask;/*masklastforextensibility*/
};
structk_sigaction{
structsigactionsa;
};
4.2.3 x86-64/linux5.18.18的定義
較高版本中的內(nèi)核中,將k_sigaction
定義到了一個(gè)統(tǒng)一的文件中(include/linux/signal_types.h
:
structsigaction{
#ifndef__ARCH_HAS_IRIX_SIGACTION
__sighandler_tsa_handler;
unsignedlongsa_flags;
#else
unsignedintsa_flags;
__sighandler_tsa_handler;
#endif
#ifdef__ARCH_HAS_SA_RESTORER
__sigrestore_tsa_restorer;
#endif
sigset_tsa_mask;/*masklastforextensibility*/
};
structk_sigaction{
structsigactionsa;
#ifdef__ARCH_HAS_KA_RESTORER
__sigrestore_tka_restorer;
#endif
};
為了兼容libc庫(kù)
,需要根據(jù)架構(gòu)進(jìn)行一些定義:
#ifndef__KERNEL__
/*這是為了迎合libc庫(kù)的實(shí)現(xiàn)*/
#ifdef__i386__
structsigaction{
union{
__sighandler_t_sa_handler;
void(*_sa_sigaction)(int,structsiginfo*,void*);
}_u;
sigset_tsa_mask;
unsignedlongsa_flags;
void(*sa_restorer)(void);
};
#definesa_handler_u._sa_handler
#definesa_sigaction_u._sa_sigaction
#else/*__i386__*/
structsigaction{
__sighandler_tsa_handler;
unsignedlongsa_flags;
__sigrestore_tsa_restorer;
sigset_tsa_mask;/*masklastforextensibility*/
};
#endif/*!__i386__*/
#endif/*!__KERNEL__*/
4.2.4 ARM/linux5.18.18的定義
ARM
架構(gòu)下內(nèi)核中數(shù)據(jù)結(jié)構(gòu)與x86
架構(gòu)相同,但是,為了兼容libc
,不得不定義一些特殊的結(jié)構(gòu):
#ifndef__KERNEL__
/*這是為了迎合libc庫(kù)的實(shí)現(xiàn)*/
structsigaction{
union{
__sighandler_t_sa_handler;
void(*_sa_sigaction)(int,structsiginfo*,void*);
}_u;
sigset_tsa_mask;
unsignedlongsa_flags;
void(*sa_restorer)(void);
};
#definesa_handler_u._sa_handler
#definesa_sigaction_u._sa_sigaction
#endif/*__KERNEL__*/
4.2.5 RISC-V/linux6.7
最新版本內(nèi)核中沒(méi)有變化,RISC-V
相關(guān)實(shí)現(xiàn)與ARM
架構(gòu)相同,只是,取消了聯(lián)合體復(fù)雜的實(shí)現(xiàn)(這就是后發(fā)優(yōu)勢(shì)):
#ifndef__KERNEL__
structsigaction{
__sighandler_tsa_handler;
unsignedlongsa_flags;
#ifdefSA_RESTORER
__sigrestore_tsa_restorer;
#endif
sigset_tsa_mask;/*masklastforextensibility*/
};
#endif
4.3 掛起信號(hào)隊(duì)列
正如前面所述,某些系統(tǒng)可以產(chǎn)生信號(hào):kill()
和rt_sigqueueinfo()
發(fā)送信號(hào)到整個(gè)線程組,而tkill()
和tgkill()
發(fā)送信號(hào)到某個(gè)特定的進(jìn)程。
為了記錄當(dāng)前哪些信號(hào)被掛起,內(nèi)核給每個(gè)進(jìn)程提供了兩個(gè)掛起信號(hào)隊(duì)列:
-
共享掛起信號(hào)隊(duì)列
,掛載到信號(hào)描述符的shared_pending
字段,存儲(chǔ)整個(gè)線程組的掛起信號(hào)。 -
私有掛起信號(hào)隊(duì)列
,掛載到進(jìn)程描述符的pending
字段,存儲(chǔ)進(jìn)程(輕量級(jí))自己的掛起信號(hào)。
掛起信號(hào)隊(duì)列的元素是類型為sigpending
的數(shù)據(jù)結(jié)構(gòu),定義如下:
structsigpending{
structlist_headlist;
sigset_tsignal;
}
signal
字段是一個(gè)位數(shù)組,每一位代表一個(gè)掛起信號(hào),而list
字段是雙向鏈表的頭,該表頭指向sigqueue
數(shù)據(jù)結(jié)構(gòu)組成的鏈表,sigqueue
字段定義如下表所示:
類型 | 變量 | 描述 |
---|---|---|
struct list_head |
list |
掛起信號(hào)隊(duì)列的鏈表鏈接 |
spinlock_t * |
lock |
信號(hào)處理描述符的siglock 字段的指針 |
int |
flags |
sigqueue 數(shù)據(jù)結(jié)構(gòu)中的標(biāo)志 |
siginfo_t |
info |
描述發(fā)送信號(hào)的事件信息 |
struct user_struct * |
user |
指向進(jìn)程擁有者的用戶數(shù)據(jù)結(jié)構(gòu) |
其中,siginfo_t
的大小為128
字節(jié),描述特定信號(hào)事件的信息;它包含以下字段:
-
si_signo
信號(hào)編碼。
-
si_errno
產(chǎn)生信號(hào)的指令的錯(cuò)誤編碼,如果是
0
則沒(méi)有錯(cuò)誤。 -
si_code
標(biāo)識(shí)發(fā)送信號(hào)方的編碼(參加表
11-8
)表
11-8
。最重要的信號(hào)發(fā)送方編碼編碼名稱
發(fā)送方
SI_USER
kill()
和raise()
(查看稍后的與信號(hào)處理相關(guān)的系統(tǒng)調(diào)用
)SI_KERNEL
通用內(nèi)核函數(shù)產(chǎn)生的信號(hào) SI_QUEUE
sigqueue()
(查看稍后的與信號(hào)處理相關(guān)的系統(tǒng)調(diào)用
)SI_TIMER
定時(shí)器到時(shí) SI_ASYNCIO
異步IO完成 SI_TKILL
tkill()
和tgkill()
(查看稍后的與信號(hào)處理相關(guān)的系統(tǒng)調(diào)用
) -
_sifields
一個(gè)聯(lián)合體數(shù)據(jù)類型,根據(jù)信號(hào)類型存儲(chǔ)信息。例如是
SIGKILL
信號(hào),siginfo_t
記錄發(fā)送進(jìn)程的PID
和UID
;如果是SIGSEGV
,則記錄產(chǎn)生信號(hào)時(shí)訪問(wèn)的內(nèi)存地址。
5 信號(hào)數(shù)據(jù)結(jié)構(gòu)的操作函數(shù)
為了方便處理信號(hào),內(nèi)核提供了幾個(gè)函數(shù)和宏,如下所示。其中,set
是指向sigset_t
變量的指針,nsig
是信號(hào)值,mask
是一個(gè)unsigned long
類型的位掩碼。
-
sigemptyset(set)/sigfillset(set)
設(shè)置
sigset_t
變量的位為0
或1
。 -
sigaddset(set,nsig)/sigdelset(set,nsig)
設(shè)置
sigset_t
變量中指定信號(hào)nsig
為1
或0
。事實(shí)上,sigaddset()
可以簡(jiǎn)化為set->sig[(nsig-1)/32]|=1UL<((nsig?-?1)%32);
而
sigdelset()
簡(jiǎn)化為set->sig[(nsig-1)/32]&=~(1UL<((nsig?-?1)%32));
-
sigaddsetmask(set,mask)/sigdelsetmask(set,mask)
設(shè)置
sigset_t
類型變量的位掩碼為1
或0
。set->sig[0]|=mask;
and to:
set->sig[0]&=~mask;
-
sigismember(set,nsig)
返回信號(hào)
nsig
在sigset_t
變量中的對(duì)應(yīng)位。實(shí)際可以簡(jiǎn)化為:return1&(set->sig[(nsig-1)/32]>>((nsig-1)%32));
-
sigmask(nsig)
產(chǎn)生信號(hào)
nsig
的位索引。換句話說(shuō),如果內(nèi)核需要設(shè)置、清除或測(cè)試sigset_t
類型變量中的信號(hào)對(duì)應(yīng)位,可以通過(guò)該宏可以導(dǎo)出正確的位。 -
sigandsets(d,s1,s2)/sigorsets(d,s1,s2)/signandsets(d,s1,s2)
對(duì)
s1
和s2
執(zhí)行邏輯AND
、OR
和NAND
操作,結(jié)果保存到d
中。 -
sigtestsetmask(set,mask)
如果變量的相應(yīng)位掩碼為
1
,則返回1
;否則返回0
。只有在信號(hào)1~32
之間使用。 -
siginitset(set,mask)
用
mask
的位初始化sigset_t
變量中1 ~ 32
信號(hào)對(duì)應(yīng)的低位,清除33 ~ 63
信號(hào)對(duì)應(yīng)位。 -
siginitsetinv(set,mask)
用
mask
的補(bǔ)碼初始化sigset_t
變量1~32
信號(hào)對(duì)應(yīng)的低位,并設(shè)置33~63
信號(hào)對(duì)應(yīng)的位。 -
signal_pending(p)
判斷由
p
指向的進(jìn)程描述符是否具有非阻塞的掛起信號(hào),如果有,返回1
(true
);如果沒(méi)有,則返回0
(false
)。該函數(shù)是通過(guò)對(duì)進(jìn)程的TIF_SIGPENDING
標(biāo)志進(jìn)行檢查實(shí)現(xiàn)的。 -
recalc_sigpending_tsk(t)/recalc_sigpending()
第一個(gè)函數(shù)檢查
t
指向的進(jìn)程描述符中是否有掛起信號(hào)(通過(guò)檢查t->pending->signal
字段實(shí)現(xiàn)),或者檢查該進(jìn)程所屬線程組是否有掛起信號(hào)(通過(guò)檢查t-> signal->shared_pending->signal
實(shí)現(xiàn))。該函數(shù)隨后設(shè)置t->thread_info->flags
中的TIF_SIGPENDING
標(biāo)志位。recalc_sigpending()
等價(jià)于recalc_sigpending_tsk(current)
。 -
rm_from_queue(mask,q)
從掛起信號(hào)隊(duì)列
q
中移除mask
位掩碼中對(duì)應(yīng)的掛起信號(hào)。 -
flush_sigqueue(q)
從掛起信號(hào)隊(duì)列
q
中移除所有掛起信號(hào)。 -
flush_signals(t)
刪除發(fā)送給進(jìn)程的所有信號(hào)(
t
指向進(jìn)程描述符)。實(shí)現(xiàn)方式是清除t->thread_info->flags
的TIF_SIGPENDING
標(biāo)志,并分別對(duì)t->pending
和t->signal->shared_ pending
隊(duì)列調(diào)用flush_sigqueue()
。
5.1 x86
架構(gòu)
較新的內(nèi)核版本(比如,v5.18.18
和v6.7
)中,這些函數(shù)都已經(jīng)作了統(tǒng)一處理,位于文件include/linux/signal.h
中。但是,x86
架構(gòu)體系的i386
它的實(shí)現(xiàn)使用了匯編指令(為了效率),比如:
staticinlineint__gen_sigismember(sigset_t*set,int_sig)
{
boolret;
asm("btl%2,%1"CC_SET(c)
:CC_OUT(c)(ret):"m"(*set),"Ir"(_sig-1));
returnret;
}
__gen_sigismember
是sigismember
的一個(gè)底層實(shí)現(xiàn),其中用到了匯編指令btl
(將寄存器的位進(jìn)行比較,如果該位被設(shè)置則置為1
,則CC_SET(c)
條件碼會(huì)被設(shè)置為真,否則為假)。所以,i386
有一部分設(shè)置信號(hào)的函數(shù)定義獨(dú)自一個(gè)文件(/arch/x86/include/asm/signal.h
)。
5.2 ARM和RISC-V架構(gòu)
x86-64
、ARM
和RISC-V
架構(gòu)的函數(shù)定義都位于include/linux/signal.h
文件中,是統(tǒng)一實(shí)現(xiàn)。
-
嵌入式
+關(guān)注
關(guān)注
5045文章
18816瀏覽量
298459 -
內(nèi)核
+關(guān)注
關(guān)注
3文章
1336瀏覽量
40083 -
Linux
+關(guān)注
關(guān)注
87文章
11123瀏覽量
207895 -
信號(hào)
+關(guān)注
關(guān)注
11文章
2739瀏覽量
76172
原文標(biāo)題:Linux內(nèi)核-信號(hào)的基本理解
文章出處:【微信號(hào):嵌入式ARM和Linux,微信公眾號(hào):嵌入式ARM和Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論