????????今天我們就要來一起學習信號啦!!!還記得小編在之前的文章中說過的ctrl+c嗎?之前小編沒有詳細介紹過,現在我們就要來學習啦!!!
一、信號的基本介紹
? ? ? ? 首先,小編帶領大家先一起學習一下什么是信號吧。
????????信號是系統響應某個條件而產生的事件,進程接收到信號會執行相應的操作;
????????大家要注意,我們在使用信號時,是需要添加頭文件的。與信號有關的系統調用在<signal.h>頭文件中。
1、信號的存儲位置
舊版
vim /usr/include/x86_64-linux-gnu/bits/signum.h?
新版(23版)
vim /usr/include/x86_64-linux-gnu/bits/signum-arch.h
vim /usr/include/x86_64-linux-gnu/bits/signum-generic.h
2、常見信號對應的功能
SIGBORT? ? ? *進程異常終止
SIGALRM? ? ? 超時警告
SIGFPE? ? ? ? ?*浮點運算異常
SIGHUP? ? ? ? ?連接掛斷
SIGILL? ? ? ? ? ? *非法指令
SIGINT? ? ? ? ? ? 終端中斷
SIGKILL? ? ? ? ? ?終止進程(此信號不能被捕獲或忽略)
SIGPIPE? ? ? ? ??向無讀進程的管道寫數據
SIGQUIT? ? ? ? ??終端退出
SIGSEGV? ? ? ? ?*無效內存段訪問
SIGTERM? ? ? ? 終止
SIGUSR1? ? ? ? ?用戶定義信號1
SIGUSR2? ? ? ? ?用戶定義信號2
(在這里,重點的信號用了加粗提醒大家一定要記住,在這篇文章里,小編還不會向大家介紹SIGPIPE,在后面小編介紹管道時,會結合前邊的內容和新的內容全面介紹管道)
3、信號的值
? ? ? ? ? ? 信號名稱? 信號代號
#define SIGHUP 1
#define SIGINT?2? ? ?//鍵盤按下 Ctrl+c 時,會產生終端中斷信號
#define SIGQUIT?3? //鍵盤按下 Ctrl+\時,會產生終端退出信號
#define SIGILL?4
#define SIGTRAP?5
#define SIGABRT?6
#define SIGIOT?6
#define SIGBUS?7
#define SIGFPE?8
#define SIGKILL?9? ? ?//該信號的響應方式不允許改變
#define SIGUSR1?10
#define SIGSEGV?11
#define SIGUSR2?12
#define SIGPIPE?13? ? //讀端關閉的描述符,寫端寫入時產生,該信號會終止程序(向無讀進程的管道寫數據)
#define SIGALRM?14
#define SIGTERM?15? ? //系統 kill 命令默認發送的信號
#define SIGSTKFLT?16
#define SIGCHLD?17? ? ?//子進程結束后,內核會默認給父進程發送該信號
#define SIGCONT?18
#define SIGSTOP?19
#define SIGSTP?20
#define SIGTTIN?21
#define SIGTTOU?22
#define SIGURG?23
(通過這個,小編是想告訴大家,這些信號其實對應的就是數字)
二、信號的響應方式
信號有三種響應方式:默認、忽略、自定義;
1、信號處理函數
????????在Linux系統中,我們想要了解一個新的知識必不可少的就是幫助手冊啦!!!大家還記得怎么使用嗎?
? ? ? ? 答案就是:man signal
2、?三種響應方式
(1)默認
????????如果signal函數的參數為?SIG_DFL
,則系統將使用默認的信號處理動作。
? ? ? ? 大家可以輸入命令“man 7 signal”查看默認處理方式,當然啦,小編也會為大家展示出來。
????????在上圖,小編只截取了剛剛加粗的幾個信號,想看完整的小伙伴可以自己輸入命令“man 7 signal”,往下翻就能看到啦。
(2)忽略
????????如果signal函數的參數為?SIG_IGN
,則系統將忽略該信號。
(3)自定義
????????信號自定義處理,其實是對信號進行捕捉,然后讓信號執行自定義的方法。
下面,小編向大家演示一下默認的處理方式(也就是收到信號后,進程按照信號默認的方式去處理)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>
int main()
{while(1){printf("main run\n");sleep(1);} exit(0);
}
在上述代碼中,小編寫了一個while(1)的循環,會一直執行, 當我們鍵盤按下ctrl+c時,其實就是因為該進程收到了一個信號:SIGNT——終端中斷的信號(2號信號);就是說,在鍵盤上按下ctrl+c時,會給當前終端前臺執行的進程發送SIGINT信號;
3、改變型號的響應方式
(1)將默認改為自定義
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>
#include<string.h>
void sig_fun(int sig)
{printf("sig=%d\n",sig);
}
int main()
{signal(SIGINT,sig_fun);//這里不是調用,這里是作約定while(1){printf("main run\n");sleep(1);} exit(0);
}
?那我們該如何結束進程呢?
方法一(圖上方法):我們可以通過ctrl+\這個是終端退出的信號
方法二:打開另外一個終端,通過ps -eflgrep test[test是程序名,可替換]這個命令找到該進程的pid,然后kill掉它。(這也是我們在前面學習kill時掌握的方法)
(2)將默認改為忽略
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>
#include<string.h>
int main()
{signal(SIGINT,SIG_IGN);//這里不是調用,這里是作約定while(1){ printf("main run\n");sleep(1);} exit(0);
}
4、SIGCHLD信號
(1)子進程結束,父進程會收到內核發送的SIGCHLD信號(注意:內核發送)
大家還記得我們在學習fork復制進程中的父子進程時用到的代碼嗎?
小編把代碼放到這里幫助大家回顧昂
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>int main(){char *s=NULL;int n=0;pid_t id=fork();if(pid==-1){printf("fork err\n");exit(1);}if(id == 0){s="child";n=3;}//子進程else{s="parent";n=7;}//父進程int i=0;for(;i<n;i++){printf("s=%s\n",s);sleep(1);}exit(0);
}
在學習僵死進程時,小編說過父進程沒有獲取退出碼是會產生僵死進程的;
在上面這段代碼里,其實子進程結束了,已經給父進程發送了一個信號.只不過父進程忽略了;那么,我們修改一下代碼,讓父進程收到子進程的代碼,打印一下收到的信號代號,不要忽略掉;
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>void sig_fun(int sig)
{printf("sig = %d\n",sig);printf("child over!\n");
}
int main()
{int n=0;char *s = NULL;pid_t pid=fork();if(pid == -1) { printf("fork err\n");exit(1);} if(pid==0){ n=3;s="child";} else{ signal(SIGCHLD,sig_fun);//子進程結束,內核會默認給父進程發送信號n=7;s="parent";}for(int i =0;i<n;i++){printf("s=%s\n",s);sleep(1);}exit(0);
}
????????由執行結果可以看出,子進程結束,確實是會給父進程發送17號信號SIGCHLD;只不過遇到默認情況,父進程不會理會而已;所以,這個17號信號的默認方式就是忽略;
????????再次強調一下,這個不是子進程發送的信號,是內核發送的信號;
大家還記得處理僵死進程的兩種方法嘛
(1)父進程先結束(2)父進程調用wait()方法獲取子進程的退出碼
兩個方法的本質是一樣的,但是方法二會阻塞,就是父進程在等子進程結束,才會獲取退出
碼。結合信號,如何處理,讓它不再阻塞呢?
父進程調用wait是配合信號使用的。讓我們通過下面的代碼觀察一下吧!
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<wait.h>//注意,wait的頭文件不要忘記void sig_fun(int sig)
{printf("sig = %d\n",sig);printf("child over!\n");int val;wait(&val);//我們也可以簡單寫,就是不獲取退出碼,只要不變成僵死進程就可以//wait(NULL);
}
int main()
{int n=0;char *s = NULL;pid_t pid=fork();if(pid == -1) { printf("fork err\n");exit(1);} if(pid==0){ n=3;s="child";}else{signal(SIGCHLD,sig_fun);//子進程結束,內核會默認給父進程發送信號n=7;s="parent";}for(int i =0;i<n;i++){printf("s=%s\n",s);sleep(1);}exit(0);
}
?
三、信號實例練習
1、收到SIGINT這個信號,第一次打印信號的代號,第二次按照默認形式把進程結束
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<signal.h>
void sig_fun(int sig)
{printf("sig = %d\n",sig);signal(sig,SIG_DFL);
}
int main(){signal(SIGINT,sig_fun);//這里不是調用,這里是作約定while(1){ printf("main run\n");sleep(1);} exit(0);
}
?
2、自己實現kill命令
(1)系統調用kill與kill命令
kill也是一個命令,它底層就封裝了我們的系統調用kill;
所以,man kill是1命令,man 2 kill才是系統調用;
man 2 kill得到原型:
int kill(pid_t pid,int sig);
就是向PID為pid的發送sig信號;
返回值為-1說明失敗,0表示成功.
(2)回顧kill命令
????????執行kill PID命令,這個就是系統調用,默認發送了15號信號。比如我們sleep 500,然后
打開另外一個終端kill掉它,這個kill就是默認發送了15號信號。
(3)實現kill命令
自己實現kill命令,需要PID,需要信號代號。就是我們也要寫一個類似kill-9 PID 的命令;為什么需要信號代號呢?
9號信號是一個特殊的信號,它是不允許改變響應方式的。
比如暫停進程(ctrl+Z),那么kill不掉,就需要9號信號強制結束。
寫一個類似kill-9 PID的命令;(./mykill PID SIG)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<signal.h>//./mykill pid 信號代號
int main(int argc,char *argv[])
{if(argc!=3){printf("argc error!\n");return -1; } int pid = 0;int sig = 0;sscanf(argv[1],"%d",&pid);sscanf(argv[2],"%d",&sig);if(kill(pid,sig)==-1){ perror("kill error!\n");//perror是打印出錯信息,輸出錯誤原因} exit(0);
}
?
(4)15號信號和9號信號
????????運行sleep 500這個進程,發現使用自己的mykill命令發送15號信號顯示的是“已終止(Terminated)",發現使用自己的mykill命令發送9號信號是“已殺死(killed)",和系統的kill命令是一樣的。
????????那可能有小伙伴就會說kill命令沒有傳遞信號代號,其實是一樣的,也就是mykill傳遞兩個參數即可,把信號代號也就是argv[2]定義成15,或者9即可。
? ? ? ? 在這里小編想補充一下,其實19號信號也不能被忽略,它是暫停進程。
【小編有話說】
? ? ? ??本次內容就要結束啦,截止到這篇文章,小編其實已經帶領大家自己寫了兩個命令了,分別是mycp和mykill,還有小伙伴記得嘛,mycp是在讀寫操作那里實現的。小編現在正在籌備一個關于LINUX項目的文章,大概可能會再過兩篇Linux文章就會發布啦,到時后希望小伙伴們能夠多多捧場呀!!!
????????最后還是老三樣,點贊收藏和關注~
????????喜歡小編的文章就不要忘記這三樣,點贊收藏加關注,找到小編不迷路~