信號
- 1. 信號的概念
- 2. 信號的產生
- 2.1 硬體產生的信號
- 2.3.1 終止行程組合鍵 **ctrl + c**
- 2.3.2 暫停行程組合鍵 ctrl + z
- 2.3.3 產生核心轉儲檔案組合鍵 ctrl + |
- 2.3.3.1 核心轉儲
- 2.3.3.2 core file size設定
- 2.3.3.3 使用gdb除錯核心轉儲檔案
- 2.3.3.4 一些非法行為對應產生的信號量
- 2.3 軟體產生的信號
- 2.3.1 kill
- 2.3.1.1 kill函式
- 2.3.1.2 kill命令
- 2.3.2 abort
- 2.3.2.1 abort函式
- 3. 信號的注冊
- 3.1 行程中的未決信號集(位圖)
- 3.1.1 圖解行程與未決信號集的關系
- 3.2 非可靠信號注冊
- 3.3 可靠信號注冊
- 3.4 sigqueue原始碼
- 4. 信號的注銷
- 4.1 非可靠信號的注銷
- 4.2 可靠信號的注銷
- 5. 信號的捕捉后的處理方式
- 5.1 默認處理方式SIG_DFL
- 5.2 忽略處理方式SIG_IGN
- 5.3 自定義信號處理方式
- 5.3.1 signal函式
- 5.3.1.1 signal函式演示程式
- 5.3.2 sigaciotn函式
- 5.3.2.1 sigaction結構體詳解
- 5.3.2.2 sigaction函式程式演示
- 5.3.2.3 內核原始碼中的sigaction結構體原始碼
- 5.3.3 signal函式與sigaction函式的關系
- 6. 探究回呼函式機制的原始碼定義
- 6.1 從task_struct到回呼函式handler的結構體嵌套原始碼
- 6.1 圖解結構體嵌套關系
- 6.2 原始碼中__sighandler_t的決議
- 7. 信號的捕捉流程
- 8. 信號阻塞
- 8.1 信號阻塞概念及內核原始碼定義
- 8.2 設定阻塞位圖函式
- 8.2.1 程式演示
1. 信號的概念
信號的概念理解:
- 信號是一個程式中斷
信號的種類:使用Kill -l 命令可以查看有多少個信號
[gongruiyang@localhost TestSignal]$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
- 1 至 31 是 非可靠信號
- 34 至 64 是 可靠信號
- 非可靠信號 : 當前信號有可能丟失的,丟失就無法執行該信號
- 可靠信號:當前信號不可能會丟失的
查看信號的具體含義命令:
man 7 signal

2. 信號的產生
2.1 硬體產生的信號
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1)
{
printf("while回圈ing\n");
sleep(1);
}
return 0;
}
2.3.1 終止行程組合鍵 ctrl + c
while回圈ing
while回圈ing
while回圈ing
while回圈ing
while回圈ing
^C
- 在程式運行程序中我們按下 ctrl + c 就可以中斷行程運行
- 組合鍵ctrl + c 本質上是SIGINT 信號(2號信號),是一個終止信號,終止正在進行的這個前臺行程,該組合鍵對后臺行程沒有任何作用
2.3.2 暫停行程組合鍵 ctrl + z
[gongruiyang@localhost signalCreate]$ ./hardTestExe
while回圈ing
while回圈ing
while回圈ing
while回圈ing
^Z
[1]+ Stopped ./hardTestExe
[gongruiyang@localhost signalCreate]$ ps aux | grep ./hardTest
gongrui+ 10153 0.0 0.0 4216 348 pts/0 T 13:40 0:00 ./hardTestExe
gongrui+ 10157 0.0 0.0 112828 992 pts/0 R+ 13:40 0:00 grep --color=auto ./hardTest
-
在程式運行程序中我們按下 ctrl + z 就可以暫停行程運行,此時該行程的行程狀態是T,意為暫停狀態
-
組合鍵 ctrl + z 本質上是SIGTSTP信號(20號信號),是一個暫停信號,讓正在運行的前臺程式暫停運行
2.3.3 產生核心轉儲檔案組合鍵 ctrl + |
[gongruiyang@localhost signalCreate]$ ./hardTestExe
while回圈ing
while回圈ing
while回圈ing
^\Quit(core dumped)
[gongruiyang@localhost signalCreate]$ ls
core.10925 hardTest.c hardTestExe
-
在程式運行程序中我們按下 ctrl + | 就可以退出運行的行程,并產生一個core.XXX的核心轉儲檔案
-
組合鍵 ctrl + | 本質上是SIGQUIT信號(3號信號),是一個結束行程并產生核心轉儲檔案 的信號
2.3.3.1 核心轉儲
- 核心轉儲檔案概念:核心轉儲檔案中存盤的是例外終止行程產生的一個檔案,行程終止瞬間將行程地址空間的內容以及有關行程狀態的其他資訊寫出的這個磁盤檔案,其中資訊常用于除錯尋找錯誤原因
- 核心轉儲概念:在UNIX系統中, 核心映像(core image) 就是行程(process)執行時的記憶體內容,當行程發生錯誤或收到“信號”(signal) 而終止執行時,系統會將核心映像寫入一個檔案,以作為除錯之用,這就是所謂的核心轉儲(core dump),
2.3.3.2 core file size設定
當行程例外退出或收到信號退出時,卻沒有產生核心轉儲檔案,此時可以通以下命令查看core file size設定情況,根據列印出來的內容可以看出core file size被設定為0,我們需要修改該設定值為unlimited后,行程例外退出后才能產生核心轉儲檔案
[gongruiyang@localhost signalCreate]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14950
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
修改方式如下:
[gongruiyang@localhost signalCreate]$ ulimit -c unlimited
[gongruiyang@localhost signalCreate]$ ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14950
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
2.3.3.3 使用gdb除錯核心轉儲檔案
#include <stdio.h>
void func()
{
int* p = NULL;
*p = 10; //訪問空指標導致程式崩潰
}
int main()
{
func();
return 0;
}
- 上述程式,運行至第5行會訪問空指標,導致程式崩潰,行程例外退出,并產生一個核心轉儲檔案
[gongruiyang@localhost signalCreate]$ gcc corefiletest.c -g -o coretest
[gongruiyang@localhost signalCreate]$ ./coretest
段錯誤(吐核)
[gongruiyang@localhost signalCreate]$ ls
core.58181 corefiletest.c coretest hardTest.c hardTestExe
gdb除錯核心轉儲檔案尋找錯誤地方
[gongruiyang@localhost signalCreate]$ gdb [可執行檔案名] [核心轉儲檔案]
- 使用gdb 可執行檔案 核心轉儲檔案 進入除錯界面

- 由除錯資訊可知:在func函式中由于11號信號導致程式終止,問題出現在func函式的第五行
(gdb) bt
#0 0x00000000004004fd in func () at corefiletest.c:5
#1 0x0000000000400513 in main () at corefiletest.c:9
(gdb) f 0
#0 0x00000000004004fd in func () at corefiletest.c:5
5 *p = 10; //訪問空指標導致程式崩潰
(gdb) p p
$1 = (int *) 0x0
- 由列印的資訊可知 p變數中保存的地址為NULL,所以崩潰原因是對空指標進行了訪問
2.3.3.4 一些非法行為對應產生的信號量
| 非法行為 | 信號量 | 信號名 |
|---|---|---|
| 解參考空指標 | 11號信號并產生核心轉儲檔案 | SIGSEGV |
| 訪問越界 | 11號信號并產生核心轉儲檔案 | SIGSEGV |
| 動態分配空間free兩次 | 6號信號并產生核心轉儲檔案 | SIGABRT |
2.3 軟體產生的信號
2.3.1 kill
2.3.1.1 kill函式
int kill(pid_t pid, int sig);
功能:
頭檔案:
- sys/types.h
- signal.h
引數:
- pid : 行程識別符號,給哪一個行程發送信號
- sig : 信號值,具體發送哪一個信號
回傳值:
- 成功:回傳信號值
- 失敗:回傳 -1
測驗程式:測驗kill函式
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{
int ret_kill = kill(getpid(),2);
if(ret_kill == -1)
perror("kill");
else
printf("信號量:%d\n",ret_kill);
return 0;
}
[gongruiyang@localhost signalinterface]$ ./killTest
[gongruiyang@localhost signalinterface]$
2.3.1.2 kill命令
kill命令可以指定給具體行程發送具體信號量
kill -signal pid
- signal:信號量,可以是具體數值,也可以是信號名字
- pid : 行程識別符號
通過以下命令獲取行程pid
ps aux | grep 行程名
[gongruiyang@localhost TestSignal]$ ps aux |grep deadCircle
gongrui+ 59789 0.0 0.0 4216 352 pts/0 S+ 16:38 0:00 ./deadCircle
gongrui+ 59797 0.0 0.0 112828 984 pts/1 R+ 16:39 0:00 grep --color=auto deadC
通過kill -2干掉該行程,也可以使用9號信號量,該信號量是強殺信號,可以干掉大部分行程
[gongruiyang@localhost TestSignal]$ kill -2 59789
2.3.2 abort
2.3.2.1 abort函式
void abort(void);
功能:可以向行程發送SIGABRT信號(6號信號),使行程例外終止,并關閉重繪行程打開的流
頭檔案:
- stdlib.h
哪一個行程呼叫該函式,便向該行程傳送SIGABRT信號(6號信號)
其實abort內部封裝了kill函式
3. 信號的注冊
3.1 行程中的未決信號集(位圖)
行程的task_struct中定義了位圖的初始定義
struct task_struct {
...
struct sigpending pending;
...
}
內核原始碼的include\linux\signal.h中sigpending
struct sigpending {
struct list_head list;
sigset_t signal;
};
內核原始碼的 include\asm-generic\signal.h中定義了sigset_t
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t;
內核原始碼的 include\asm-generic\signal.h中定義了**_NSIG_WORDS**
#define _NSIG 64
#define _NSIG_BPW __BITS_PER_LONG
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
內核原始碼的 arch\alpha\include\asm\bitsperlong.h中定義了**__BITS_PER_LONG**
#define __BITS_PER_LONG 64
-
一番檢查原始碼之后發現位圖就是一個unsigned long sig[1]
-
Linux作業系統中long占8個位元組,即64位
-
每一個信號,在該位圖中存在一個與之對應的位元位
-
當信號對應的位元位為1時,表示當前行程接收到該信號
3.1.1 圖解行程與未決信號集的關系

3.2 非可靠信號注冊
前提:當前行程收到了一個非可靠信號
- 將當前行程的位圖中的對應非可靠信號位元位變成1
- 添加sigqueue節點到sigqueue佇列當中(如果該信號的sigqueue節點已經存在于sigqueue佇列中,則不添加)

3.3 可靠信號注冊
前提:當前行程收到一個可靠信號
- 將當前行程的位圖中的對應可靠信號的位元位變成1
- 添加sigqueue節點到sigqueue佇列當中(無論該信號的sigqueue節點是否存在于sigqueue佇列中,都要添加)

3.4 sigqueue原始碼
struct sigqueue {
struct list_head list;
int flags;
siginfo_t info;
struct user_struct *user;
};
4. 信號的注銷
4.1 非可靠信號的注銷
前提:信號已經處理完
- 將處理完的信號對應位圖中的位元位從1變成0
- 將該信號的sigqueue節點從sigqueue佇列中出隊
4.2 可靠信號的注銷
前提:信號已經處理完了
- 將該信號的sigqueue節點從sigqueue佇列中出隊
- 需要判斷sigqueue佇列中是否還有與出隊的該信號相同的sigqueue節點
- 還有相同的sigqueue節點:不修改位圖中的對應位元位
- 沒有了:將該信號對應位圖中的位元位從1變成0
5. 信號的捕捉后的處理方式
#define SIG_DFL ((__sighandler_t)0) /* default signal handling */
#define SIG_IGN ((__sighandler_t)1) /* ignore signal */
#define SIG_ERR ((__sighandler_t)-1) /* error return from signal */
5.1 默認處理方式SIG_DFL
#define SIG_DFL ((__sighandler_t)0) /* default signal handling */
SIG_DFL就是 __sighandler_t結構體型別 的0
5.2 忽略處理方式SIG_IGN
典型的忽略處理方式:
僵尸行程的產生原因:子行程先于父行程退出,子行程向父行程發送了一個SIGCHLD信號,父行程接收到了該SIGCHLD信號,但是選擇了忽略處理的方式,導致了子行程的退出資源未被父行程進行回收,進而導致子行程變成了僵尸行程
#define SIG_IGN ((__sighandler_t)1) /* ignore signal */
SIG_IGN就是 __sighandler_t結構體型別 的1
5.3 自定義信號處理方式
5.3.1 signal函式
自定義 信號處理方式 函式:程式員定義一個函式 去處理接收到的信號
typedef void (*sighandler_t)(int); // void handler(int)
sighandler_t signal(int signum, sighandler_t handler);
功能:當行程接收到了signum信號時,呼叫handler函式,執行handler函式中的代碼,該信號以前需要執行的任務不再執行
頭檔案:
- signal.h
引數:
- signum : 信號量值,要處理的信號
- handler : 信號處理句柄,就是一個函式指標,當行程接收到了signum這個信號時,行程需要呼叫handler函式去做一些事先規定好的事情
5.3.1.1 signal函式演示程式
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int signum)
{
printf("接收到了2信號!\n");
}
int main()
{
signal(2,handler);
sleep(10);
return 0;
}
[gongruiyang@localhost signalinterface]$ ./test
^C接收到了2信號!
在Main行程在sleep時,按下ctrl + c組合鍵,向前臺行程main發送了一個2號信號,此時Main函式呼叫Handler函式,進行對信號的處理
5.3.2 sigaciotn函式
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:自定義 信號處理方式 函式:程式員定義一個函式 去處理接收到的信號
頭檔案:
- signal.h
引數:
- signum : 信號量值
- act : 輸入型引數,保存 對signum信號 所采取的措施資訊
- oldact : 輸出型引數,保存 以前對signum信號 所采取的措施資訊
5.3.2.1 sigaction結構體詳解
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *); //自定義函式處理方式
sigset_t sa_mask; //
int sa_flags; //
void (*sa_restorer)(void); //預留資訊
};
sa_handler:函式指標,保存了內核對信號的處理方式:默認處理方式和忽略處理方式sa_sigaction:函式指標,保存的時自定義處理函式sa_mask:信號集位圖,保存收到的信號sa_flags:填入宏
| 宏 | 含義 |
|---|---|
| SA_SIGINFO | 作業系統在處理信號的時候,呼叫的就是sa_sigaction函式指標當中保存的函式 |
| 0 | 作業系統在處理信號的時候,呼叫的就是sa_handler函式指標當中保存的函式 |
5.3.2.2 sigaction函式程式演示
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signum)
{
printf("signum : %d\n",signum);
}
int main()
{
struct sigaction act;
sigemptyset(&(act.sa_mask)); //用于將位圖全部位 置為0
act.sa_flags = 0; //0表示 使用自定義函式
act.sa_handler = handler; //填寫 自定義函式
sigaction(2,&act,NULL);
while(1)
{
printf("Hello World!\n");
sleep(1);
}
return 0;
}
[gongruiyang@localhost signalinterface]$ ./sigActionExe
Hello World!
Hello World!
Hello World!
^Csignum : 2
Hello World!
Hello World!
Hello World!
^Csignum : 2
Hello World!
^Csignum : 2
Hello World!
Hello World!
Hello World!
- 程式解釋:2號信號本來執行的是將中斷前臺行程,但是通過sigaction函式將2號信號的信號處理方式修改成了handler函式中的執行命令,所以在程式運行中,按下
ctrl + c組合鍵不會將前臺程式進行中斷,只會執行handler函式
5.3.2.3 內核原始碼中的sigaction結構體原始碼
struct sigaction {
union {
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int, struct siginfo *, void *);
} _u;
sigset_t sa_mask;
int sa_flags;
};
#define sa_handler _u._sa_handler
#define sa_sigaction _u._sa_sigaction
typedef char* __user __sighandler_t;
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t;
5.3.3 signal函式與sigaction函式的關系
- signal函式只是修改了sigaction結構體中的_sa_sigaction
- sigaction函式修改了整個sigaction結構體
- signal函式內部呼叫了sigaction函式
6. 探究回呼函式機制的原始碼定義
6.1 從task_struct到回呼函式handler的結構體嵌套原始碼
- 在task_struct中找到sighand_struct結構體指標
task_struct{
...
struct sighand_struct *sighand;
...
}
- sighand_struct結構體定義
struct sighand_struct {
...
struct k_sigaction action[_NSIG];
...
};
- k_sigaction結構體定義
struct k_sigaction {
struct sigaction sa;
...
};
- sigaction結構體定義
struct sigaction {
union {
__sighandler_t _sa_handler;
void (*_sa_sigaction)(int, struct siginfo *, void *);
} _u;
sigset_t sa_mask;
int sa_flags;
};
#define sa_handler _u._sa_handler
#define sa_sigaction _u._sa_sigaction
#ifdef CONFIG_64BIT
/* function pointers on 64-bit parisc are pointers to little structs and the
* compiler doesn't support code which changes or tests the address of
* the function in the little struct. This is really ugly -PB
*/
typedef char __user *__sighandler_t;
#else
typedef void __signalfn_t(int);
typedef __signalfn_t __user *__sighandler_t;
#endif
6.1 圖解結構體嵌套關系

6.2 原始碼中__sighandler_t的決議
#ifdef CONFIG_64BIT
/* function pointers on 64-bit parisc are pointers to little structs and the
* compiler doesn't support code which changes or tests the address of
* the function in the little struct. This is really ugly -PB
*/
typedef char __user *__sighandler_t;
#else
typedef void __signalfn_t(int);
typedef __signalfn_t __user *__sighandler_t;
#endif
- 由注釋可以看出:
__sighandler_t是一個函式指標(function pointer),并且很難看出來,十分的ugly
剛開始沒有注意看這個注釋,博主就想char*咋能是函式指標呢?原始碼翻來覆去苦苦尋找__sighandler_t是否還有其它的宏定義,后來仔細讀了一下原始碼注釋,才豁然開朗,TMD,絕了,十分ugly
7. 信號的捕捉流程

- main函式呼叫了一個系統呼叫函式或者呼叫**庫函式(庫函式底層也是封裝的系統呼叫函式)**后,cpu從用戶態切換到內核態
- 內核態呼叫系統呼叫后想回到用戶態需要呼叫do_signal函式
- do_signal函式的功能是檢查行程是否接受到信號:
- 如果接受到信號,呼叫sigcb函式去處理信號(1**.默認處理方式則不需要切換到用戶態,直接在內核態進行信號處理;2.自定義處理方式需要切換到用戶態**進行信號處理),信號處理完畢后呼叫sig_return函式表明信號處理完,再呼叫do_signal函式檢查是否接受到新的信號
- 如果未接收到信號,直接呼叫sys_return函式,讓cpu從內核態切換到用戶態
8. 信號阻塞
8.1 信號阻塞概念及內核原始碼定義
task_struct原始碼中定義了信號阻塞位圖和信號注冊位圖
struct task_struct{
...
sigset_t blocked, real_blocked; // 信號阻塞位圖
struct sigpending pending; // 信號注冊位圖 位于這個結構體內部
...
}
- 當信號阻塞位圖block中對應信號的位為1,表示當前行程阻塞該信號
- 當行程進入內核狀態,準備回傳到用戶態時,呼叫do_signal函式時,接收到了一個信號,如果該信號的阻塞位圖中的對應位置為1,則不會立即去處理該信號
- 等到該信號的阻塞位圖上的該信號對應位變成1之后才會去處理該信號,可靠信號發送了幾次處理幾次,非可靠信號發送大于等于1次都是處理1次
8.2 設定阻塞位圖函式
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:設定阻塞位圖
頭檔案:
- signal.h
引數:
- how : 告訴函式應該做什么,填入宏
| 宏 | 含義 |
|---|---|
| SIG_BLOCK | 設定某個信號為阻塞 |
| SIG_UNBLOCK | 解除對某個信號的阻塞 |
| SIG_SETMASK | 替換阻塞位圖 |
- set : 新替換入的阻塞位圖,可以為NULL
- alodset : 原阻塞位圖,可以為NULL
SIG_BLOCK設定阻塞原理:按位或,便將新的要阻塞的信號加入了阻塞位圖中 (原阻塞位圖 | 新阻塞位圖)
SIG_UNBLOCK解除阻塞原理:按位與,便將要解除阻塞的信號的位元位變成0(原阻塞位圖 & 新阻塞位圖)
8.2.1 程式演示
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signum)
{
printf("signum : %d\n",signum);
}
int main()
{
signal(2,handler);
signal(40,handler);
sigset_t set;
sigfillset(&set); // 位圖全部置為1
sigprocmask(SIG_SETMASK,&set,NULL);
while(1)
{
printf("Hello!\n");
sleep(1);
}
return 0;
}
該程式運行起來后,該行程阻塞了所有信號除了 kill -9 其他信號都沒用
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/245304.html
標籤:其他
