linux 信號1

信號的機制

A給B發送信號,B收到信號之前執行自己的代碼,收到信號後,不管執行到程序的什麼位置,都要暫停運行,去處理信號,處理完畢再繼續執行。與硬件中斷類似——異步模式。但信號是軟件層面上實現的中斷,早期常被稱爲“軟中斷”。
信號的特質:由於信號是通過軟件方法實現,其實現手段導致信號有很強的延時性。但對於用戶來說,這個延遲時間非常短,不易察覺。
每個進程收到的所有信號,都是由內核負責發送的,內核處理。

與信號相關的事件和狀態

產生信號:
1.按鍵產生,如:Ctrl+c、Ctrl+z、Ctrl+ \
2.系統調用產生,如:kill、raise、abort
3.軟件條件產生,如:定時器alarm
4.硬件異常產生,如:非法訪問內存(段錯誤)、除0(浮點數例外)、內存對齊出錯(總線錯誤)
5.命令產生,如:kill命令
遞達:遞送並且到達進程。
未決:產生和遞達之間的狀態。主要由於阻塞(屏蔽)導致該狀態。
信號的處理方式:
1.執行默認動作
2.忽略(丟棄)
3.捕捉(調用戶處理函數)
Linux內核的進程控制塊PCB是一個結構體task_struct, 除了包含進程id,狀態,工作目錄,用戶id,組id,文件描述符表,還包含了信號相關的信息,主要指阻塞信號集和未決信號集。

阻塞信號集(信號屏蔽字): 將某些信號加入集合,對他們設置屏蔽,當屏蔽x信號後,再收到該信號,該信號的處理將推後(直到解除屏蔽後)
未決信號集:
1.信號產生,未決信號集中描述該信號的位立刻翻轉爲1,表信號處於未決狀態。當信號被處理,對應位又翻轉回爲0。這一時刻往往非常短暫。
2.信號產生後由於某些原因(主要是阻塞)不能抵達。這類信號的集合稱之爲未決信號集。在屏蔽解除前,信號一直處於未決狀態。

信號的編號

可以使用kill –l命令查看當前系統可使用的信號有哪些。

  1. SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
  2. SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
  3. SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
  4. SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
  5. SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
  6. SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
  7. SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
  8. SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
  9. SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
  10. SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
  11. SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
  12. SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
  13. SIGRTMAX-1 64) SIGRTMAX
    不存在編號爲0的信號。其中1-31號信號稱之爲常規信號(也叫普通信號或標準信號),34-64稱之爲實時信號,驅動編程與硬件相關。名字上區別不大。而前32個名字各不相同。

信號4要素

與變量三要素類似的,每個信號也有其必備4要素,分別是:
1.編號 2.名稱 3.事件 4.默認處理動作
可通過man 7 signal查看幫助文檔獲取。也可查看/usr/src/linux-headers-3.16.0-30/arch/s390/include/uapi/asm/signal.h
Signal Value Action Comment
────────────────────────────────────────────
SIGHUP 1 Term Hangup detected on controlling terminal or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at terminal
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTOU 22,22,27 Stop Terminal output for background process
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
在標準信號中,有一些信號是有三個“Value”,第一個值通常對alpha和sparc架構有效,中間值針對x86、arm和其他架構,最後一個應用於mips架構。一個‘-’表示在對應架構上尚未定義該信號。
不同的操作系統定義了不同的系統信號。因此有些信號出現在Unix系統內,也出現在Linux中,而有的信號出現在FreeBSD或Mac OS中卻沒有出現在Linux下。這裏我們只研究Linux系統中的信號。
默認動作:
Term:終止進程
Ign: 忽略信號 (默認即時對該種信號忽略操作)
Core:終止進程,生成Core文件。(查驗進程死亡原因, 用於gdb調試)
Stop:停止(暫停)進程
Cont:繼續運行進程
注意從man 7 signal幫助文檔中可看到 : The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
這裏特別強調了9) SIGKILL 和19) SIGSTOP信號,不允許忽略和捕捉,只能執行默認動作。甚至不能將其設置爲阻塞。

Linux常規信號一覽表

1)SIGHUP: 當用戶退出shell時,由該shell啓動的所有進程將收到這個信號,默認動作爲終止進程
2)SIGINT:當用戶按下了<Ctrl+C>組合鍵時,用戶終端向正在運行中的由該終端啓動的程序發出此信號。默認動作爲終止進程。
3)SIGQUIT:當用戶按下<ctrl+>組合鍵時產生該信號,用戶終端向正在運行中的由該終端啓動的程序發出些信號。默認動作爲終止進程。
4)SIGILL:CPU檢測到某進程執行了非法指令。默認動作爲終止進程併產生core文件
5)SIGTRAP:該信號由斷點指令或其他 trap指令產生。默認動作爲終止里程 併產生core文件。
6)SIGABRT: 調用abort函數時產生該信號。默認動作爲終止進程併產生core文件。
7)SIGBUS:非法訪問內存地址,包括內存對齊出錯,默認動作爲終止進程併產生core文件。
8)SIGFPE:在發生致命的運算錯誤時發出。不僅包括浮點運算錯誤,還包括溢出及除數爲0等所有的算法錯誤。默認動作爲終止進程併產生core文件。
9)SIGKILL:無條件終止進程。本信號不能被忽略,處理和阻塞。默認動作爲終止進程。它向系統管理員提供了可以殺死任何進程的方法。
10)SIGUSE1:用戶定義的信號。即程序員可以在程序中定義並使用該信號。默認動作爲終止進程。
11)SIGSEGV:指示進程進行了無效內存訪問。默認動作爲終止進程併產生core文件。
12)SIGUSR2:另外一個用戶自定義信號,程序員可以在程序中定義並使用該信號。默認動作爲終止進程。
13)SIGPIPE:Broken pipe向一個沒有讀端的管道寫數據。默認動作爲終止進程。
14)SIGALRM: 定時器超時,超時的時間 由系統調用alarm設置。默認動作爲終止進程。
15)SIGTERM:程序結束信號,與SIGKILL不同的是,該信號可以被阻塞和終止。執行shell命令kill時,缺省產生這個信號。默認動作爲終止進程。
16)SIGSTKFLT:Linux早期版本出現的信號,現仍保留向後兼容。默認動作爲終止進程。
17)SIGCHLD:子進程結束時,父進程會收到這個信號。默認動作爲忽略這個信號。
18)SIGCONT:如果進程已停止,則使其繼續運行。默認動作爲繼續/忽略。
19)SIGSTOP:停止進程的執行。信號不能被忽略,處理和阻塞。默認動作爲暫停進程。
20)SIGTSTP:停止終端交互進程的運行。按下<ctrl+z>組合鍵時發出這個信號。默認動作爲暫停進程。
21)SIGTTIN:後臺進程讀終端控制檯。默認動作爲暫停進程。
22)SIGTTOU: 該信號類似於SIGTTIN,在後臺進程要向終端輸出數據時發生。默認動作爲暫停進程。
23)SIGURG:套接字上有緊急數據時,向當前正在運行的進程發出些信號,報告有緊急數據到達。如網絡帶外數據到達,默認動作爲忽略該信號。
24)SIGXCPU:進程執行時間超過了分配給該進程的CPU時間 ,系統產生該信號併發送給該進程。默認動作爲終止進程。
25)SIGXFSZ:超過文件的最大長度設置。默認動作爲終止進程。
26)SIGVTALRM:虛擬時鐘超時時產生該信號。類似於SIGALRM,但是該信號只計算該進程佔用CPU的使用時間。默認動作爲終止進程。
27)SGIPROF:類似於SIGVTALRM,它不公包括該進程佔用CPU時間還包括執行系統調用時間。默認動作爲終止進程。
28)SIGWINCH:窗口變化大小時發出。默認動作爲忽略該信號。
29)SIGIO:此信號向進程指示發出了一個異步IO事件。默認動作爲忽略。
30)SIGPWR:關機。默認動作爲終止進程。
31)SIGSYS:無效的系統調用。默認動作爲終止進程併產生core文件。
34)SIGRTMIN ~ (64) SIGRTMAX:LINUX的實時信號,它們沒有固定的含義(可以由用戶自定義)。所有的實時信號的默認動作都爲終止進程。

信號的產生

終端按鍵產生信號:

Ctrl + c → 2) SIGINT(終止/中斷) “INT” ----Interrupt
Ctrl + z → 20) SIGTSTP(暫停/停止) “T” ----Terminal 終端。
Ctrl + \ → 3) SIGQUIT(退出)

硬件異常產生信號:

除0操作 → 8) SIGFPE (浮點數例外) “F” -----float 浮點數。
非法訪問內存 → 11) SIGSEGV (段錯誤)
總線錯誤 → 7) SIGBUS

kill函數/命令產生信號

kill命令產生信號:kill -SIGKILL pid
kill函數:給指定進程發送指定信號(不一定殺死)

int kill(pid_t pid, int sig);

成功:0;失敗:-1 (ID非法,信號非法,普通用戶殺init進程等權級問題),設置errno
sig:不推薦直接使用數字,應使用宏名,因爲不同操作系統信號編號可能不同,但名稱一致。
pid > 0: 發送信號給指定的進程。
pid = 0: 發送信號給 與調用kill函數進程屬於同一進程組的所有進程。
pid < 0: 取|pid|發給對應進程組。
pid = -1:發送給進程有權限發送的系統中所有進程。
進程組:每個進程都屬於一個進程組,進程組是一個或多個進程集合,他們相互關聯,共同完成一個實體任務,每個進程組都有一個進程組長,默認進程組ID與進程組長ID相同。
權限保護:super用戶(root)可以發送信號給任意用戶,普通用戶是不能向系統用戶發送信號的。 kill -9 (root用戶的pid) 是不可以的。同樣,普通用戶也不能向其他普通用戶發送信號,終止其進程。 只能向自己創建的進程發送信號。普通用戶基本規則是:發送者實際或有效用戶ID == 接收者實際或有效用戶ID

raise和abort函數

raise 函數:給當前進程發送指定信號(自己給自己發) raise(signo) 等價 kill(getpid(), signo);

int raise(int sig);

成功:0,失敗非0值
abort 函數:給自己發送異常終止信號 6) SIGABRT 信號,終止併產生core文件

void abort(void); 

軟件條件產生信號

alarm函數

設置定時器(鬧鐘)。在指定seconds後,內核會給當前進程發送14)SIGALRM信號。進程收到該信號,默認動作終止。
每個進程都有且只有唯一個定時器。

unsigned int alarm(unsigned int seconds); 

返回0或剩餘的秒數,無失敗。
常用:取消定時器alarm(0),返回舊鬧鐘餘下秒數。
例:alarm(5) → 3sec → alarm(4) → 5sec → alarm(5) → alarm(0)
定時與進程狀態無關(自然定時法):就緒、運行、掛起(阻塞、暫停)、終止、殭屍…無論進程處於何種狀態,alarm都計時。
實際執行時間 = 系統時間 + 用戶時間 + 等待時間

setitimer函數

設置定時器(鬧鐘)。 可代替alarm函數。精度微秒us,可以實現週期定時。

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);	

成功:0;失敗:-1,設置errno
參數:which:指定定時方式
① 自然定時:ITIMER_REAL → 14)SIGLARM 計算自然時間
② 虛擬空間計時(用戶空間):ITIMER_VIRTUAL → 26)SIGVTALRM 只計算進程佔用cpu的時間
③ 運行時計時(用戶+內核):ITIMER_PROF → 27)SIGPROF 計算佔用cpu及執行系統調用的時間
在這裏插入圖片描述
例:使用setitimer函數實現alarm函數

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

unsigned int my_alarm(unsigned int sec)
{
	struct itimerval it, oldit;
    int ret;

	it.it_value.tv_sec = sec;
	it.it_value.tv_usec = 0;
	it.it_interval.tv_sec = 0;
	it.it_interval.tv_usec = 0;

	ret = setitimer(ITIMER_REAL, &it, &oldit);
    if (ret == -1) {
        perror("setitimer");
        exit(1);
    }
	return oldit.it_value.tv_sec;
}

int main(void)
{
	
	my_alarm(1);  //alarm(1);
	while(1);

	return 0;
}

在這裏插入圖片描述
接下來再看參數it_interval的用法:
先給出一個例子:

#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

void myfunc(int signo)
{
	printf("hello world\n");
}

int main(void)
{
	struct itimerval it, oldit;
	signal(SIGALRM, &myfunc);   //SIGALRM信號的捕捉處理函數(類似Qt的回調函數)

	it.it_value.tv_sec = 5;
	it.it_value.tv_usec = 0;

	it.it_interval.tv_sec = 3;
	it.it_interval.tv_usec = 0;

	if(setitimer(ITIMER_REAL, &it, &oldit) == -1){
		perror("setitimer error");
		return -1;
	}

	while(1);

	return 0;
}

在這裏插入圖片描述
執行結果:
在這裏插入圖片描述
也就是第一次過了5秒發SIGLARM,然後每隔3s就會發一次SIGLARM信號。

信號集操作函數

內核通過讀取未決信號集來判斷信號是否應被處理。阻塞信號集(信號屏蔽字)mask可以影響未決信號集。而我們可以在應用程序中自定義set來改變mask。已達到屏蔽指定信號的目的。

信號集設定

sigset_t set; // typedef unsigned long sigset_t;
int sigemptyset(sigset_t *set); 將某個信號集清0 成功:0;失敗:-1
int sigfillset(sigset_t *set); 將某個信號集置1 成功:0;失敗:-1
int sigaddset(sigset_t *set, int signum); 將某個信號加入信號集 成功:0;失敗:-1
int sigdelset(sigset_t *set, int signum); 將某個信號清出信號集 成功:0;失敗:-1
int sigismember(const sigset_t *set, int signum);判斷某個信號是否在信號集中 返回值:在集合:1;不在:0;出錯:-1
sigset_t類型的本質是位圖。但不應該直接使用位操作,而應該使用上述函數,保證跨系統操作有效。

sigprocmask函數

用來屏蔽信號。解除屏蔽也使用該函數。其本質,讀取或修改進程的信號屏蔽字(PCB中)
嚴格注意,屏蔽信號:只是將信號處理延後執行(延至解除屏蔽);而忽略表示將信號丟處理。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

成功:0;失敗:-1,設置errno
參數:
set:傳入參數,是一個位圖,set中哪個位置爲1,就表示當前進程屏蔽哪個信號。
oldset:傳出參數,保存舊的信號屏蔽集。
how參數取值:假設當前的阻塞信號集爲mask
1.SIG_BLOCK: 當how設置爲此值,set表示需要屏蔽的信號。相當於 mask = mask|set
2.SIG_UNBLOCK: 當how設置爲此,set表示需要解除屏蔽的信號。相當於 mask = mask & ~set
3.SIG_SETMASK: 當how設置爲此,set表示用於替代原始屏蔽及的新屏蔽集。相當於 mask = set若,調用sigprocmask解除了對當前若干個信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達。

sigpending函數

讀取當前進程的未決信號集

int sigpending(sigset_t *set);	

set是傳出參數。返回值:成功:0;失敗:-1,設置errno

例:編寫程序,把所有常規信號的未決狀態打印至屏幕。
在這裏插入圖片描述

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void printset(sigset_t *ped)
{
	int i;
	for(i = 1; i < 32; i++)
	{
		if((sigismember(ped, i) == 1))
		{
			putchar('1');
		} else {
			putchar('0');
		}
	}
	printf("\n");
}

int main(void)
{
	sigset_t set, ped;
#if 0
	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	sigaddset(&set, SIGQUIT);
	sigaddset(&set, SIGKILL);//SIGKILL信號是不能被阻塞的
	sigaddset(&set, SIGSEGV);
#endif
	sigfillset(&set);//將整個myset信號集全置1
	sigprocmask(SIG_BLOCK, &set, NULL);	//屏蔽信號,也就是用myset去影響阻塞信號集

	while(1)
	{
		sigpending(&ped);//獲取未決信號集
		printset(&ped); //對未決信號集裏的每一位進行判斷(調用sigismember函數)    
		sleep(1);
	}

	return 0;
}

在這裏插入圖片描述
當然,由於ctrl+c信號已經被設置爲屏蔽,所以爲了結束上面這個進程,需要另開一個終端,然後ps ajx,找到進程pid後kill掉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章