Linux下IPC方式之信號1
1. 信號的基本屬性
1.1 信號的特點
簡單,不能攜帶大量信息,滿足特定條件發生,信號也叫軟中斷,有可能會有延遲。
1.2 信號的機制
信號實際上是由內核發送,內核來處理收到的信號。收到信號的進程,必須對信號做出處理(忽略,捕獲,默認動作都行)
1.3 信號的產生
- 按鍵產生,如:Ctrl+c、Ctrl+z、Ctrl+\
- 系統調用產生,如:kill、raise、abort
- 軟件條件產生,如:定時器alarm
- 硬件異常產生,如:非法訪問內存(段錯誤)、除0(浮點數例外)、內存對齊出錯(總線錯誤)
- 命令產生,如: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 raise
和abort
函數
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