Linux下IPC方式之信號1

1. 信號的基本屬性

1.1 信號的特點

簡單,不能攜帶大量信息,滿足特定條件發生,信號也叫軟中斷,有可能會有延遲。

1.2 信號的機制

信號實際上是由內核發送,內核來處理收到的信號。收到信號的進程,必須對信號做出處理(忽略,捕獲,默認動作都行)

1.3 信號的產生

  1. 按鍵產生,如:Ctrl+c、Ctrl+z、Ctrl+\
  2. 系統調用產生,如:kill、raise、abort
  3. 軟件條件產生,如:定時器alarm
  4. 硬件異常產生,如:非法訪問內存(段錯誤)、除0(浮點數例外)、內存對齊出錯(總線錯誤)
  5. 命令產生,如:kill命令

1.4 信號的狀態

  • 產生
  • 遞達:信號到達並且處理完
  • 未決:產生和遞達之間的狀態。主要由於阻塞(屏蔽)導致該狀態。

1.5 信號的處理方式

  • 執行默認動作
  • 忽略(丟棄)
  • 捕捉(調用戶處理函數)

1.6 阻塞信號集和未決信號集

阻塞信號集(信號屏蔽字): 將某些信號加入集合,對他們設置屏蔽,當屏蔽x信號後,再收到該信號,該信號的處理將推後(解除屏蔽後)
未決信號集:

  • 信號產生,未決信號集中描述該信號的位立刻翻轉爲1,表信號處於未決狀態。當信號被處理對應位翻轉回爲0。這一時刻往往非常短暫。
  • 信號產生後由於某些原因(主要是阻塞)不能抵達。這類信號的集合稱之爲未決信號集。在屏蔽解除前,信號一直處於未決狀態。
    在這裏插入圖片描述
    如果我們把2號信號設置成阻塞(即在阻塞信號集的對應位置設爲1),那麼來一個2號信號,則未信號集的對應值置爲1,什麼時候阻塞信號集中的對應位置變成0了,什麼時候未決信號集才能去處理之前被阻塞的那個信號。

1.7 信號的四要素

  • 編號
  • 事件
  • 名稱
  • 默認處理動作
    1 忽略
    2 終止
    3 終止+core
    4 暫停
    5 繼續

默認處理動作有五個(linux 輸入 man 7 signal 即可查看),如圖所示:在這裏插入圖片描述
注意:9號,19號信號,不能捕捉,不能忽略,不能阻塞。
在這裏插入圖片描述

1.8 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~64 SIGRTMIN~ SIGRTMAX LINUX的實時信號,它們沒有固定的含義(可以由用戶自定義)。所有的實時信號的默認動作都爲終止進程。

2. 信號的產生

2.1 終端按鍵產生信號

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

2.2 硬件異常產生信號

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

2.3 kill函數/命令產生信號

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

 int kill(pid_t pid, int sig);	
pid > 0		要發送進程ID
pid = 0		代表當前調用進程中內所有進程
pid = -1	代表有權限發送的所有進場
pid < 0		代表 -pid 對應的組內所有進程
sig			對應的信號,不推薦直接使用數字,應使用宏名,因爲不同操作系統信號編號可能不同,但名稱一致。

練習,讓3號子進程殺死父進程

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h>
 
int main(){
	int i;
	for(i=0; i<5; i++){
		pid_t pid=fork();
		if(pid==0){
			break;
		}
	}
	//我們使3號子進程殺死父進程
	if(i==2){
		printf("I will kill my father after 5 second!\n");
		sleep(5);
		kill(getppid(),SIGKILL);
	}
	if(i==5){
		while(1){
			printf("I am father!\n");
			sleep(1);
		}
	}
	return 0;
}

運行結果:
在這裏插入圖片描述
父進程把3號子進程殺死:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
 
int main(){
	int i=0;
	pid_t pid3, pid;
	for(i=0; i<5; i++){
		pid=fork();
		if(pid==0) break;
		if(i==2) pid3=pid;
	}
	if(i<5){
		while(1){
			printf("I am child, pid=%d, ppid=%d\n", getpid(), getppid());
			sleep(3);
		}
	}
	else if(i==5){
		printf("I am father,pid=%d, I will kill xiao san pid3=%d\n", getpid(), pid3);
		sleep(5);
		kill(pid3, SIGKILL);
		while(1) sleep(1);
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

2.4 raiseabort函數

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

int raise(int sig); 
返回值:	成功:0,失敗非0
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<unistd.h>
 
int main(){
	printf("I will die!\n");
	sleep(2);
	raise(SIGKILL);//kill(getpid(), sig)
	return 0;
}

abort 函數:給自己發送異常終止信號 6) SIGABRT 信號,終止併產生core文件

void abort(void); 該函數無返回
#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<unistd.h>
#include <stdlib.h>
 
int main(){
	printf("I will die!\n");
	sleep(2);
	//raise(SIGKILL);//kill(getpid(), sig)
    abort();
	return 0;
}

在這裏插入圖片描述

2.5 時鐘產生信號

2.5.1 alarm函數

設置定時器(鬧鐘)。在指定seconds後,內核會給當前進程發送14)SIGALRM信號。進程收到該信號,默認動作終止(Term)。
每個進程都有且只有唯一個定時器。
定時給自己發送SIGALRM
幾秒後發送信號
返回值——上次鬧鐘剩餘的秒數
特別的,如果傳入參數秒爲0,代表取消鬧鐘


#include<stdio.h>
#include<unistd.h>
 
int main(){
	alarm(6);
	while(1){
		printf("hello world \n");
		sleep(1);
	}
	return 0;
}

在這裏插入圖片描述

alarm函數的返回值

#include<stdio.h>
#include<unistd.h>
 
int main(){
	int ret=0;
	ret=alarm(6);
	printf("ret=%d\n", ret);
	sleep(2);
	ret=alarm(5);
	printf("ret=%d\n", ret);
	while(1){
		printf("hello world\n");
		sleep(1);
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

2.5.2 setitimer函數

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

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);	
返回值		成功:0;失敗:-1,設置errno

參數:
which:指定定時方式
		ITIMER_REAL 	自然定時法 			SIGALRM
		ITIMER_VIRTUAL 	計算進程執行時間 	SIGVTALRM
		ITIMER_PROF 	程序執行時間+調度時間 ITIMER_VIRTUAL
		
new_value 	要設置的鬧鐘時間

old_value 	原鬧鐘時間

在這裏插入圖片描述
在這裏插入圖片描述
setitimer實現alarm功能

#include<stdio.h>
#include<sys/time.h>
#include<unistd.h>
 
int main(){
	//setitimer是一個結構體中嵌套了兩個結構體
	//兩個結構體分別代表:週期性的時間設置,下次的鬧鐘時間
	//每個結構體裏。,第一個參數是秒,第二個參數是微妙
	//這裏定義的意思就是:三秒之後發送SIGALRM信號
	struct itimerval myit={{0,0},{3,0}};
	setitimer(ITIMER_REAL, &myit, NULL);
 
	while(1){
		printf("Who can kill me!\n");
		sleep(1);
	}
	return 0;
}

運行結果:
在這裏插入圖片描述
測試周期性發送信號功能
週期性發送SIGALRM信號殺死進程,進程利用catch_sig函數來捕獲SIGALRM信號

#include<stdio.h>
#include<sys/time.h>
#include<unistd.h>
#include<signal.h>
 
void catch_sig(int num){
	printf("cat %d sig\n", num);
}
int main(){
	signal(SIGALRM, catch_sig);
	//第一次等待五秒,之後每隔三秒
	struct itimerval myit={{3,0},{5,0}};
	setitimer(ITIMER_REAL, &myit, NULL);
 
	while(1){
		printf("Who can kill me!\n");
		sleep(1);
	}
	return 0;
}

運行結果:
在這裏插入圖片描述

setitimer實現alarm

#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
 
unsigned int myalarm(unsigned int seconds){
	struct itimerval oldit,myit={{0,0},{0,0}};
	//second秒之後,給我發送一個SIGALRM信號
	myit.it_value.tv_sec=seconds;
	//old_value 原鬧鐘時間
	setitimer(ITIMER_REAL, &myit, &oldit);
	//打印秒和微秒
	printf("tv_sec=%ld, tv_microsec=%ld\n",
		oldit.it_value.tv_sec, oldit.it_value.tv_usec);
	return oldit.it_value.tv_sec;
 
}
 
int main(){
	int ret=0;
	ret=myalarm(5);
	printf("ret=%d\n", ret);
	sleep(3);
	ret=myalarm(3);
	printf("ret=%d\n", ret);
	while(1){
		printf("hello world\n");
		sleep(1);
	}
	return 0;
}

運行結果:
在這裏插入圖片描述
後接Linux下IPC方式之信號2

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