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