信號本質上就是一個軟體中斷,它既可以作為兩個進程間的通信的方式, 更重要的是, 信號可以終止一個正常程式的執行, 通常被用於處理意外情況 , 信號是非同步的, 也就是進程並不知道信號何時會到達 $kill 9 3390 向PID為3390的進程發送編號為9的信號= 一個兩個進程間通信的方式之一 一共6 ...
- 信號本質上就是一個軟體中斷,它既可以作為兩個進程間的通信的方式, 更重要的是, 信號可以終止一個正常程式的執行, 通常被用於處理意外情況 ,* 信號是非同步的, 也就是進程並不知道信號何時會到達
- $kill -9 3390 #向PID為3390的進程發送編號為9的信號=>一個兩個進程間通信的方式之一
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
- 一共62個,不是64個, 歷史原因, 沒有32,33
- 1~31之間的信號叫做不可靠信號, 不支持排隊, 信號可能會丟失, 也叫做非實時信號
- 34~64之間的信號叫做可靠信號, 支持排隊, 信號不會丟失, 也叫做實時信號
絕大多數信號的預設處理方式都是終止進程, 另外兩種預設處理方式: 忽略處理, 自定義處理
1) | SIGHUP | 連接掛斷 | 終止(預設處理) |
2) | SIGINT | 終端中斷,Ctrl+c產生該信號 | 終止(terminate) |
3) | SIGQUIT | 終端退出,Ctrl+\ | 終止+轉儲 |
4) | SIGILL | *進程試圖執行非法指令 | 終止+轉儲 |
5) | SIGTRAP | 進入斷點 | 終止+轉儲 |
6) | SIGABRT | *進程異常終止,abort()產生 | 終止+轉儲 |
7) | SIGBUS | 硬體或對齊錯誤 | 終止+轉儲 |
8) | SIGFPE | *浮點運算異常 | 終止+轉儲 |
9) | SIGKILL | 不可以被捕獲或忽略的終止信號 | 終止 |
10) | SIGUSR1 | 用戶定義信號1 | 終止 |
11) | SIGSEGV | *無效的記憶體段訪問=>Segmentation error | 終止+轉儲 |
12) | SIGUSR2 | 用戶定義信號2 | 終止 |
13) | SIGPIPE | 向讀端已關閉的管道寫入 | 終止 |
14) | SIGALRM | 真實定時器到期,alarm()產生 | 終止 |
15) | SIGTERM | 可以被捕獲或忽略的終止信號 | 終止 |
16) | SIGSTKFLT | 協處理器棧錯誤 | 終止 |
17) | SIGCHLD | 子進程已經停止, 對於管理子進程很有用 | 忽略 |
18) | SIGCONT | 繼續執行暫停進程(用戶一般不用) | 忽略 |
19) | SIGSTOP | 不能被捕獲或忽略的停止信號 | 停止(stop) |
20) | SIGTSTP | 終端掛起,用戶產生停止符(Ctrl+Z) | 停止 |
21) | SIGTTIN | 後臺進程讀控制終端 | 停止 |
22) | SIGTTOU | 後臺進程寫控制終端 | 停止 |
23) | SIGURG | 緊急I/O未處理 | 忽略 |
24) | SIGXCPU | 進程資源超限 | 終止+轉儲 |
25) | SIGXFSZ | 文件資源超限 | 終止+轉儲 |
26) | SIGVTALRM | 虛擬定時器到期 | 終止 |
27) | SIGPROF | 實用定時器到期 | 終止 |
28) | SIGWINCH | 控制終端視窗大小改變 | 忽略 |
29) | SIGIO | 非同步I/O事件 | 終止 |
30) | SIGPWR | 斷電 | 終止 |
31) | SIGSYS | 進程試圖執行無效系統調用 | 終止+轉儲 |
*系統對信號響應應視具體情況而定
發送信號的主要方式:
- 鍵盤 //只能發送部分特殊的信號 eg:Ctrl+C可以發送SIGINT
- 程式出錯 //只能發送部分特殊的信號 eg: 出現段錯誤, 可以發送SIGSEGV
- $kill -Signal PID #能發所有信號
- 系統函數kill()/raise()/alarm()/sigqueue()
kill()
//給任何進程或進程組發任何信號,成功返回0,失敗返回-1設errno
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid
- pid>0 //指定pid
- pid=0 //GID是調用進程PID的子進程
- pid=-1 //任何子進程
- pid<-1 //GID是PID的子進程
sig
- sig == 0 //不發任何信號,但錯誤檢查還是會做,可以檢查PID是否存在
#include<sys/types.h>
#include<signal.h>
void fa(int signo){
printf("catch signal number:%d\n",signo);
}
int main(){
pid_t pid=fork();
if(0==pid){
…
while(1);
}
if(0==kill(pid,0)){ //if child exists
printf("parent starts to raise signal\n");
if(-1==kill(pid,40)) //if fail to kill child
perror("kill"),exit(-1);
}
return 0;
}.
raise()
//給調用的線程或進程發信號,如果發送的信號被handler處理了,會在handler返回後再返回,成功返回0,失敗返回非0
#include<signal.h>
int raise(int sig);
- 在單線程程式中等價於 kill(getpid,sig)
- 在多線程程式中等價於pthread_kill(pthread_self(), sig);
if(0!=raise(SIGINT))
perror("raise success"),exit(-1);
//raise.c
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void fa(int signo){
printf("catch %d success\n",signo);
}
int main(){
//設置對SIGINT自定義處理
if(SIG_ERR==signal(SIGINT,fa))
perror("signal"),exit(-1);
//5s後使用raise發送SIGINT
unsigned int second=sleep(5);
if(0==second)
printf("sleep well");
else
printf("sleep is interrupted, there are %ds left\n",second);
if(0!=raise(SIGINT))
perror("raise success"),exit(-1);
return 0;
}
alarm()
//seconds秒之後給調用進程發送SOGALRM信號,返回距離下次鬧鐘響起剩餘的描述,沒有鬧鐘返回0
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
seconds=0表示不設置新的SIGALRM的同時取消之前的鬧鐘==>專門用來取消鬧鐘
//alarm.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void fa(int signo){
printf("catch %d\n",signo);
alarm(1);
}
int main(){
// 設置對信號SIGALRM進行自定義處理;
if(SIG_ERR==signal(SIGALRM,fa))
perror("signal"),exit(-1);
// 設置5s發送SIGALRM;
unsigned int second=alarm(5);
printf("second=%u\n",second); //0
sleep(2);
//調整鬧鐘10s後響(發SIGALRM),second是3
second=alarm(10);
printf("second=%u\n",second); //3
//取消鬧鐘(不會發SIGALRM),second是3
// second=alarm(0);
// printf("second=%d\n",second); //10
while(1);
return 0;
}
sigqueue()
//將信號和數據排好發給指定進程,成功返回0,失敗返回-1設errno
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
pid //進程的編號(給誰發信號)
sig //信號值/信號的名稱(發送什麼樣的信號)
value //聯合類型的附加數據(伴隨什麼樣的數據)
union sigval {
int sival_int;
void *sival_ptr;
};
void fa(int signo,siginfo_t* info,void* pv){
printf("進程%d發來的信號是:%d,附加數據是%d\n",info->si_pid,signo,info->si_value);
}
int main(){
//設置對信號40的自定義處理
struct sigaction action={};
action.sa_sigaction=fa;
action.sa_flags=SA_SIGINFO;
int res=sigaction(40,&action,NULL);
if(-1==res)
perror("sigaction"),exit(-1);
pid_t pid=fork();
if(-1==pid)
perror("fork"),exit(-1);
if(0==pid){ //child開始, 發送1~100之間的數據發給parent
printf("child%dstarts\n",getpid());
int i=1;
for(i=1;i<=100;i++){
union sigval value;
value.sival_int=i;
sigqueue(getppid(),40,value);//這裡沒有錯誤處理
}
exit(0);
}
while(1);
return 0;
}
sleep()
//讓調用的線程睡seconds秒,除非這期間收到了不能忽略的信號,成功返回0,失敗返回剩餘沒睡的時間
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
unsigned int second=sleep(5);
if(0==second)
printf("sleep well");
else
printf("sleep is interrupted, there are %ds left\n",second);
pause()
//讓調用的線程一直睡直到接收到了一個終止進程或者引發捕獲信號函數的信號,當接受到這樣的信號時,返回-1設errno
int pause(void);
void ding(int signo){
}
int main(){
(void) signal(SIGALRM, ding); //ding是一個函數
pause();
}
Q:pause 放在signal-catching很遠的地方可以嗎
A:pause就相當於sleep(∞), 跟捕捉語句沒什麼關係,誰說一定要組合用的
Q: 寫了捕獲語句, 是不是就使整個進程有了捕獲某個信號的能力還是只是這一句???
A:當然不是, signal就像是全局變數, 使用sigaction()對某個信號進行重定向後, 此後的程式遇到這個信號就使用新的處理方式
signal()
//重新設置對信號的處理方式,將信號signum交給handler處理,成功返回之前的signal handler,失敗返回-1設errno
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
handler
- SIG_IGN(Signal ignore)
- SIG_DFL(Signal default)
- User-defined fhandler with argument signum
#include<signal.h>
void fa(int signo){
printf("catch signal:%d\n",signo);
if(SIG_ERR==signal(signo,SIG_DFL))
perror("signal"),exit(-1);
}
int main(){
if(SIG_ERR==signal(3,fa))
perror("signal"),exit(-1);
return 0;
}
信號集
信號的集合,當前系統所支持的信號範圍:1~64(共62個),就是說每個進程可以使用信號1~64,為了對信號操作方便, 我們把一些信號放在一個集合中一起處理, 很多函數(包括sigpromask,sigpending)都是對信號集操作而不是對單個信號
sigset_t
Linux中表示信號集的數據類型, 就是一個超級大的整數(128byte, 32個unsigned long int的數組), 底層採用一個二進位位來代表一個信號, 根據該二進位位為0 or 1表示該信號是否存在
typedef struct{
unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
}__sigset_t;
typedef __sigset_t sigset_t;
在當前系統中, 按照%d列印的話就是列印信號集類型中的低4個位元組的數據其實當前只需要8byte, 設計這麼大是為了未來擴展
sigemptyset() /sigfillset()/sigaddset()/sigdelset()/sigismember()
//這些都是信號集操作函數
//sigemptyset()/sigfillset()/ sigaddset()/ sigdelset() 成功返回0,失敗返回-1設errno
//如果signum是指定信號集的一員,sigismember()返回1,不是返回0,失敗返回-1設errno。
#include <signal.h>
int sigemptyset(sigset_t *set); //清空信號集
int sigfillset(sigset_t *set); //填滿信號集
int sigaddset(sigset_t *set, int signum); //添加信號到信號集
int sigdelset(sigset_t *set, int signum); //刪除信號集的信號
int sigismember(const sigset_t *set, int signum); //判斷信號是否屬於信號集
sigprocmask()
//sigprocmask()只適用與單線程進程,多線程的參考pthread_sigmask()
//在某些特殊程式的執行過程中, 是不能被信號打斷的, 此時需要信號的屏蔽技術來解決該問題
//獲取並修改調用線程的屏蔽信號集
//成功返回0,失敗返回-1設errno
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how
- SIG_BLOCK //新的屏蔽信號集是原有集合(不是oldset,oldset是用來存儲的)和set的合集
- SIG_UNBLOCK //set內的信號被從屏蔽集合oldset中移除
- SIG_SETMASK //屏蔽信號集就是setThe set of blocked signals is set to the argument set.
oldset
- 如果oldset 不是NULL,則之前的信號集被存儲在其中
- 如果set是NULL,屏蔽信號集不會變數,但現存的屏蔽信號集還是會被存儲在oldset中
//準備信號集中有:2,3
sigset_t set,oldset;
if(-1==sigemptyset(&set))
perror("sigemptyset"),exit(-1);
if(-1==sigemptyset(&oldset))
perror("sigemptyset"),exit(-1);
if(-1==sigaddset(&set,2))
perror("sigaddset"),exit(-1);
//設置屏蔽的信號集
if(-1== sigprocmask(SIG_SETMASK,&set,&oldset))
perror("sigprocmask"),exit(-1);
- 信號屏蔽並不是刪除信號, 而是將信號單獨保存起來, 等信號的屏蔽解除之後, 信號還是會被處理的
- 可靠信號: 支持排隊, mask期間有多少信號, mask解除之後就會處理多少
- 不可靠信號:不支持排隊, mask期間有多個信號時, mask解除之後只處理第一個
sigpending()
//檢查mask期間捕捉到但是沒有處理的信號, 通過形參帶出結果,成功返回0,失敗返回-1設errno
#include <signal.h>
int sigpending(sigset_t *set);
sigaction()
//是前面的信號處理的集大成者且有很多擴展,是一個非常健壯的signal處理介面,建議使用,成功返回0,失敗返回-1設errno
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
signum可以代表除了SIGSTOP和SIGKILL之外的任何一個信號
如果NULL!=act,act作為針對signal的新的action。
如果NULL!=oldact, 之前的action被存儲在oldact。
act/oldact:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void); //obsolete, should not be used.
};
如果 sa_flags 被設為SA_SIGINFO,則sa_sigaction用來作signum的handler, 否則,用sa_handler。
sa_handler表明針對signum的handler,和signal()第二個參數類型一致, 都是用於設置signal信號的處理方式SIG_DFL,SIG_IGN,user-defined handler
//sa_sigaction
struct siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /*Trap number that caused hardware-generated signal
pid_t si_pid; /* Sending process ID */ //發送信號的進程號
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */ //伴隨信號到來的附加數據
int si_int; /* POSIX.1b signal */
void* si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void* si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address(since kernel 2.6.32) */
}
sa_mask 表明在信號handler執行期間需要屏蔽的信號,此外,出發handler的信號預設是被屏蔽的,除非flag里指定的SA_NODEFE
sa_flags(Bitwise Or)
- SA_NOCLDSTOP //If signum is SIGCHLD, do not receive notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU) or resume (i.e., they receive SIGCONT) (see wait(2)). This flag is only meaningful when establishing a handler for SIGCHLD.
- SA_NOCLDWAIT //If signum is SIGCHLD, do not transform children into zombies when they terminate. See also waitpid(2). This flag is only meaningful when establishing a handler for SIGCHLD, or when setting that signal's disposition to SIG_DFL.If the SA_NOCLDWAIT flag is set when establishing a handler for SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal is generated when a child process terminates. On Linux, a SIGCHLD signal is generated in this case; on some other implementations, it is not.
- SA_NODEFER //解除對觸發信號處理函數相同信號的屏蔽
- SA_ONSTACK //Call the signal handler on an alternate signal stack provided by sigaltstack(2). If an alternate stack is not available, the default stack will be used. This flag is only meaningful when establishing a signal handler.
- SA_RESETHAND //一旦信號處理函數被調用之後, 信號的處理方式就會恢復為預設處理方式
- SA_RESTART //Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals.This flag is only meaningful when establishing a signal handler. See signal(7) for a discussion of system call restarting.
- SA_SIGINFO //使用第二個函數指針設置信號的處理方式
//sigaction.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void fa(int signo){
printf("fa1...\n");
sleep(3);
printf("fa2...\n");
}
int main(){
printf("current pid:%d\n",getpid());
//prepare struc var
struct sigaction action={};
action.sa_handler=fa;
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask,3);
action.sa_flags=SA_NODEFER; //解除對信號2的屏蔽,sigaction(2....)
int res=sigaction(2,&action,NULL);
if(-1==res)
perror("sigaction"),exit(-1);
printf("set signal successfully\n");
while(1);
return 0;
}
sigaction()那點事:
- 使用sigaction(),需要明確兩個act,一個用來存儲老act的oldact,一個用來安裝新act的act,
- 這兩個act都是struct sigaction
- 結構體有兩個函數分別指明對sig的處理方式,具體使用哪個需要看flag是否被set了SA_SIGINFO,set了,就是sa_sigaction()處理信號,沒set,就是sa_handler,前者比後者的功能更多,後者其實就是signal()
- 指明瞭用哪個函數處理了信號,我們還可以指定在signal handler執行的過程中哪些SIG被mask,註意這個是sigset_t類型,把要mask的信號放在一個信號集里,預設條件下the signal which triggered the handler是被mask的,除非在sa_flags中set了SA_NODEFER;
- 可以看到,整個act中flags主要起到了配置的作用,它決定使用哪個handler(SA_SIGINFO),也決定要不要屏蔽觸發信號(SA_NODEFER);
- 除了這兩個,flags還有其他選項
void fa(int signo,siginfo_t* info,void* pv){
printf("進程%d發來了信號%d\n",info->si_pid,signo);
}
int main(){
struct sigaction action={};
action.sa_sigaction=fa;
action.sa_flags=SA_SIGINFO;
if(-1== sigaction(2,&action,NULL))
perror("sigaction"),exit(-1);
return 0;
}
父子進程信號
- 對於fork()創建的child, child完全照搬parent對信號的處理方式:
- parent忽略=>child忽略;
- parent預設=>child預設;
- arent自定義=>child自定義;
- parent忽略=>child忽略;
- 對於vfork(),execl()啟動的child, 部分照搬parent的信號處理方式:
- parent忽略=>child忽略;
- parent預設=>child預設;
- parent自定義=>child預設=>因為execl()已經跳出的子進程, 而process.out裡面沒有fa,所以會按預設方式處理
- parent忽略=>child忽略;
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void fa(int signo){
printf("%d\n",signo);
}
int main(){
if(SIG_ERR==signal(2,fa))
perror("signal"),exit(-1);
if(SIG_ERR==signal(3,SIG_IGN))
perror("signal"),exit(-1);
pid_t pid=vfork();
if(-1==pid)
perror("vfork"),exit(-1);
if(0==pid){
printf("child's pid=%d\n",getpid());
int res=execl("process.out","process.out",NULL);
if(-1==res)
perror("execl"),exit(-1);
while(1);
}
return 0;
}