目錄
- 1、信號
- 1.1、什么是信號
- 1.2、進程對信號的處理
- 1.3、信號的生命周期
- 1.4、信號處理流程
- 1.5、信號的發送
- 2、kill()、raise()函數 發送信號
- 3、alarm函數 鬧鐘信號
- 4、pause函數 掛起信號、暫停
- 5、singal 函數 捕獲信號
- 5.1、為什么返回值是上一次的處理方式
- 5.2、練習
- 6、sigaction 函數 捕獲信號(推薦使用)
- 7、setitimer() 鬧鐘信號
- 8、sigset_t相關函數 信號集
- 9、sigprocmask 函數 設置信號屏蔽
- 10、sigpending 函數
1、信號
1.1、什么是信號
信號(signal):信號機制是進程之間相互傳遞消息的一種方法,信號全稱為軟中斷信號,也稱作軟中斷。信號用來通知進程發生了異步事件。
信號事件的發生有兩個來源:
硬件來源,比如我們按下了鍵盤或者其它硬件;
軟件來源,最常用發送信號的系統函數是kill(), raise(), alarm()和setitimer()等函數,軟件來源還包括一些非法運算等操作。
注意
:信號只是用來通知某進程發生了什么事件,并不給該進程傳遞任何數據。
kill -l:
1.2、進程對信號的處理
進程可以通過三種方式來響應和處理一個信號:
- 1、忽略信號:忽略某個信號,對該信號不做任何處理,就像未發生過一樣。
- 2、捕捉信號:是類似中斷的處理程序,對于需要處理的信號,進程可以指定處理函數,由該函數來處理
- 3、執行缺省操作:對該信號的處理保留系統的默認值,這種缺省操作,對大部分的信號的缺省操作是使得進程終止
常見的信號:
處理動作的字母含義如下
A 缺省的動作是終止進程
B 缺省的動作是忽略此信號
C 缺省的動作是終止進程并進行內核映像轉儲(dump core)
D 缺省的動作是停止進程
E 信號不能被捕獲
F 信號不能被忽略
停止進程
:只是暫停了進程的執行,進程仍然存在于內存中,隨時可以恢復其執行1。
終止進程
:進程被徹底結束,其資源被釋放,不再占用系統內存1
在終端中停止進程
按下 Ctrl+Z 可以停止當前前臺進程。
$ sleep 100
^Z
[1]+ Stopped sleep 100
使用 jobs 命令查看停止的進程:
$ jobs
[1]+ Stopped sleep 100
使用 fg 命令恢復進程:
$ fg 1
sleep 100
1.3、信號的生命周期
從信號(Signal)發送到信號處理函數的執行是一個完整的信號生命周期
1.4、信號處理流程
1.5、信號的發送
除了內核和超級用戶,并不是每個進程都可以向其他的進程發送信號。
一般的進程只能向具有相同uid和gid的進程發送信號,或向相同進程組中的其他進程發送信號。
常用的發送信號的函數有kill()
、raise ()
、alarm()
、setitimer()
、abort()
等。
2、kill()、raise()函數 發送信號
kill函數同讀者熟知的kill系統命令一樣,可以發送信號給進程或進程組(實際上,kill系統命令只是kill函數的一個用戶接口)。
kill –l
命令查看系統支持的信號列表
命令:man 2 kill
kill
可以向指定的進程發送信號
命令:man 2 raise
raise
只能向本進程發送信號
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int i;for (i = 0; i < 3; i++){printf("i = %d\n",i);sleep(1);if (i == 1){if (kill(getpid(),SIGINT) < 0) //向指定進程發送信號{perror("kill signal error");}// if (raise(SIGINT) < 0) //只能向本進程發送信號// {// perror("raise error");// }}}}
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>void sigfun(int sig)
{printf("parent work start\n");sleep(1);printf("parent work over\n");
}int main()
{pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){sleep(1); //讓父進程先執行printf("child process start\n");kill(getppid(),SIGINT);printf("chiild process over\n");}else if(pid > 0){printf("parent process start\n");signal(SIGINT,sigfun);wait(NULL);printf("parent process over\n");}
}
3、alarm函數 鬧鐘信號
alarm()也稱為鬧鐘函數,它可以在進程中設置一個定時器。當定時器指定的時間到時,內核就向進程發送SIGALRM信號。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>int main()
{int res = alarm(5);printf("res = %d\n",res);sleep(1);res = alarm(7);printf("res = %d\n",res); //返回上一個定時器剩余時間int i = 0;while(1){printf("i = %d\n",i++);sleep(1);}
}
4、pause函數 掛起信號、暫停
pause() 進程掛起直到收到信號為止,并以缺省的方式處理此信號
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>int main()
{pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){printf("child process send signal\n");sleep(3);kill(getppid(),SIGINT); //向父進程發送信號}else if(pid > 0){printf("parent process start\n");pause();//進程掛起 (暫停),等待被信號觸發,會以系統默認方式處理信號printf("parent over\n");}
}
5、singal 函數 捕獲信號
出錯時返回SIG_ERR
函數說明:當指定的信號signum到達時就會跳轉到參數handler函數中執行。handler函數的參數接收的是信號值(比如SIGINT
是2)
附加說明
在信號發生跳轉到handler處理函數執行后,系統會自動將此處理函數換回原來系統預設的處理方式,如果要改變此操作請改用sigaction()。
比如這個例子
#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 信號處理函數
void handle_signal(int signum) {printf("接收到信號: %d\n", signum);
}int main() {// 注冊信號處理函數if (signal(SIGINT, handle_signal) == SIG_ERR) {perror("signal 注冊失敗");return 1;}printf("程序運行中,按下 Ctrl+C 發送 SIGINT 信號...\n");// 無限循環,等待信號while (1) {sleep(1);}return 0;
}
第二次 按下 ctrl + c
會以系統默認方式中止,
也有例外
如果你的程序在第二次按下 Ctrl+C
時沒有中止,是因為 signal() 的行為在 glibc 實現中不會恢復為默認行為。
如果你希望明確控制信號處理行為,建議使用 sigaction()
替代 signal()
。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigfun(int signum)
{if (signum == SIGINT){printf("is SIGINT\n");}elseprintf("is other signal\n");}
int main()
{// signal(SIGINT,SIG_IGN); //以忽略方式處理signal(SIGINT,SIG_DFL); //以缺省方式處理#if 1void (*fun_res)(int) = signal(SIGINT,sigfun); //自定義信號處理方式printf("fun_res = %p\n",fun_res); //第一次返回值為NULLfun_res = signal(SIGINT,sigfun);printf("fun_res = %p sigfun = %p\n",fun_res,sigfun); //第二次返回上一次的處理方式#endifint i = 0;while(i < 5){printf("i = %d\n",i++);sleep(1);}
}
5.1、為什么返回值是上一次的處理方式
#include <stdio.h>
#include <signal.h>
#include <unistd.h>//為什么返回值是上一次signal處理方式
// 鏈式處理(Chaining)是一種常見的技術,
// 它允許程序員在自定義信號處理函數中調用之前的處理函數。
// 這種技術通常用于在自定義處理邏輯中保留系統默認行為或之前設置的行為。// 保存之前的信號處理函數
void (*old_handler)(int);// 自定義信號處理函數
void custom_handler(int signum)
{printf("Custom handler: Received signal %d\n", signum);// 調用之前的信號處理函數// if (old_handler != SIG_ERR) {// printf("Calling old handler...\n");// old_handler(signum);// }// 調用之前的信號處理函數(如果存在且不是默認行為)if (old_handler != SIG_ERR && old_handler != SIG_DFL) {printf("Calling old handler...\n");old_handler(signum);} else {printf("Old handler is SIG_DFL or invalid, skipping...\n");}
}int main()
{// 保存之前的信號處理函數old_handler = signal(SIGINT, custom_handler);if (old_handler == SIG_ERR) {perror("signal");return 1;}printf("Press Ctrl+C to trigger the custom handler...\n");// 無限循環,等待信號while (1) {sleep(1);}return 0;
}
5.2、練習
創建一個子進程,父進程每隔2秒向子進程發送一個不同的信號(SIGINT,SIGQUIT,SIGALRM), 子進程注冊相應的信號處理
函數,處理接收到信號,在信號處理函數里面打印信號名稱,子進程接收到3個信號后結束
// 創建一個子進程,父進程每隔2秒向子進程發送一個不同的信號(SIGINT,SIGQUIT,SIGALRM),
// 子進程注冊相應的信號處理
// 函數,處理接收到信號,在信號處理函數里面打印信號名稱,子進程接收到3個信號后結束#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>void sigfun(int sig)
{printf("is SIGINT\n");
}void sigfun2(int sig)
{printf("is SIGQUIT\n");
}void sigfun3(int sig)
{printf("is SIGALRM\n");
}int main()
{signal(SIGINT,sigfun);signal(SIGQUIT,sigfun2);signal(SIGALRM,sigfun3);pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0 ){int num = 0;while (1){num++;pause();if(num == 3)exit(0);}}else if(pid > 0){int sigs[] = {SIGINT,SIGQUIT,SIGALRM};for (int i = 0; i < 3; i++){sleep(2);kill(pid,sigs[i]);}wait(NULL);}}
6、sigaction 函數 捕獲信號(推薦使用)
頭文件
#include <signal.h>
函數定義
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
參數:
signum
:要捕獲的信號編號(例如 SIGINT、SIGTERM 等)。act
:指向struct sigaction
結構體的指針,用于指定新的信號處理行為。oldact
:指向struct sigaction
結構體的指針,用于保存之前的信號處理行為(可以為 NULL)。
返回值:
成功時返回 0,失敗時返回 -1 并設置 errno。
struct sigaction
結構體
struct sigaction {void (*sa_handler)(int); // 信號處理函數void (*sa_sigaction)(int, siginfo_t *, void *); // 高級信號處理函數sigset_t sa_mask; // 信號屏蔽集int sa_flags; // 標志位void (*sa_restorer)(void); // 已棄用,通常為 NULL
};
#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT(signfun)\n");
}void sigfun2(int sig)
{printf("is SIGINT(signfun2)\n");
}int main()
{struct sigaction newact = {0};newact.sa_handler = sigfun; // 設置信號處理函數// sigemptyset(&newact.sa_mask); // 清空信號屏蔽集// newact.sa_flags = 0; // 默認標志sigaction(SIGINT,&newact,NULL);struct sigaction oldact = {0};newact.sa_handler = sigfun2;sigaction(SIGINT,&newact,&oldact);printf("oldact = %p signal = %p\n",oldact.sa_handler,sigfun);int i = 0;while (i <5){printf("i = %d\n",i++);sleep(1);}printf("-----------------------\n");//恢復信號處理方式newact.sa_handler = SIG_DFL;sigaction(SIGINT,&newact,NULL);i = 0;while (i <5){printf("i = %d\n",i++);sleep(1);}}
7、setitimer() 鬧鐘信號
alarm()只會產生一次鬧鐘信號
setitimer()循環產生鬧鐘信號
頭文件
#include <sys/time.h>
函數定義
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
參數說明
- which: 指定定時器類型,可以是
ITIMER_REAL
、ITIMER_VIRTUAL
或ITIMER_PROF
。 - new_value: 指向
struct itimerval
結構的指針,用于設置新的定時器值。 - old_value: 指向
struct itimerval
結構的指針,用于存儲舊的定時器值。如果不需要舊的定時器值,可以傳入
NULL。
ITIMER_REAL
: 真實時間定時器,觸發 SIGALRM 信號。
ITIMER_VIRTUAL
: 進程的用戶態 CPU 時間定時器,觸發 SIGVTALRM 信號。
ITIMER_PROF
: 進程的用戶態和內核態 CPU 時間定時器,觸發 SIGPROF 信號。
struct itimerval
結構
struct itimerval {struct timeval it_interval; /* 定時器間隔 */struct timeval it_value; /* 當前定時器值 */
};struct timeval {time_t tv_sec; /* 秒 */suseconds_t tv_usec; /* 微秒 */
};
返回值
- 成功時返回 0,失敗時返回 -1 并設置 errno。
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("SIGALRM\n");
}int main()
{//用于捕捉信號signal(SIGALRM,sigfun);struct itimerval newtime = {0};newtime.it_interval.tv_sec = 1; //間隔時間newtime.it_value.tv_sec = 3; //第一次發送信號時間setitimer(ITIMER_REAL,&newtime,NULL); int i;for ( i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}}
8、sigset_t相關函數 信號集
typedef struct {unsigned long __bits[__SIGSET_NWORDS];
} sigset_t;
頭文件
#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);
sigemptyset
函數
功能:初始化一個空的信號集,即清除所有信號。
參數:
set:指向信號集的指針。
返回值:
- 成功時返回 0。
- 失敗時返回 -1,并設置 errno。
sigfillse
t函數
功能:初始化一個包含所有信號的信號集。
參數:
set:指向信號集的指針。
返回值:
- 成功時返回 0。
- 失敗時返回 -1,并設置 errno。
sigaddset
函數
功能:將指定信號添加到信號集中。
參數:
set:指向信號集的指針。
signum:要添加的信號編號(如 SIGINT)。
返回值:
- 成功時返回 0。
- 失敗時返回 -1,并設置 errno。
sigdelset
函數
功能:從信號集中刪除指定信號。
參數:
set:指向信號集的指針。
signum:要刪除的信號編號(如 SIGINT)。
返回值:
成功時返回 0。
失敗時返回 -1,并設置 errno。
sigismember
函數
功能:檢查指定信號是否在信號集中。
參數:
set:指向信號集的指針。
signum:要檢查的信號編號(如 SIGINT)。
返回值:
如果信號在信號集中,返回 1。
如果信號不在信號集中,返回 0。
失敗時返回 -1,并設置 errno。
#include <stdio.h>
#include <signal.h>//打印一個字節里每一位的值
void printb(char byte)
{for (int i = 0; i < 8; i++){printf("%d",byte & 1 << 7 - i ? 1 : 0);}printf(" "); //8位空一格}//打印某一塊區域所有字節的數據
//dest區域的首地址 size區域的大小
void printmem(void *dest,size_t size)
{//從高字節向低字節輸出int i = 0;for ( i = 0; i < size; i++){printb(((char *)dest)[size -1 -i]);//每八個字節換一行if ((i + 1)%8 == 0)printf("\n"); }}int main()
{sigset_t set = {0}; //聲明一個信號集結構體//填充整個信號集if(sigfillset(&set) < 0){perror("sigfillset error");return -1;}//清空信號集if (sigemptyset(&set) < 0){perror("sigemptyset error");return -1;}//添加信號if(sigaddset(&set,SIGINT) < 0){perror("sigaddset error");return -1;}if(sigaddset(&set,SIGQUIT) < 0){perror("sigaddset error");return -1;}// printmem(&set,sizeof(set));//刪除信號if(sigdelset(&set,SIGQUIT) < 0){perror("sigdelset error");return -1;}printmem(&set,sizeof(set));//查詢信號是否存在int res = sigismember(&set,SIGQUIT);if (res == 1){printf("set have SIGINT\n");}else if (res == 0)printf("set not have SIGINT\n");else if (res < 0){perror("sigismember error");return -1;}}
9、sigprocmask 函數 設置信號屏蔽
信號屏蔽 不等于 信號忽略
信號忽略:忽略并丟掉
信號屏蔽:暫時不處理,放入隊列中,后面再從隊列中取出處理
頭文件
#include <signal.h>
函數定義
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
參數說明
-
how
:指定如何修改信號屏蔽字,可以是以下值之一SIG_BLOCK
:將 set 中的信號添加到當前信號屏蔽字中(阻塞這些信號)。SIG_UNBLOCK
:從當前信號屏蔽字中移除 set 中的信號(解除阻塞這些信號)。SIG_SETMASK
:將當前信號屏蔽字替換為 set 中的信號集。
-
set
:指向sigset_t
類型的信號集,表示要修改的信號集。如果為 NULL,則忽略此參數。 -
oldset
:指向sigset_t
類型的信號集,用于保存修改前的信號屏蔽字。如果為 NULL,則不保存。
注意
SIG_BLOCK
:用于臨時阻塞某些信號,不影響其他已阻塞的信號。
SIG_SETMASK
:用于完全替換信號屏蔽字,適合需要精確控制信號屏蔽字的場景。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT\n");
}
int main()
{//注冊對SIGINT信號的捕捉signal(SIGINT,sigfun);//初始化信號集sigset_t set = {0};sigemptyset(&set); //先清空sigaddset(&set,SIGINT); //再添加//設置信號屏蔽//這里SIG_if(sigprocmask(SIG_SETMASK,&set,NULL) < 0){perror("sigprocmask error");return -1;}int i;for(i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}//解除屏蔽,信號會被觸發if(sigprocmask(SIG_UNBLOCK,&set,NULL) < 0){perror("sigprocmask error");return -1;}}
10、sigpending 函數
頭文件
#include <signal.h>
函數定義
int sigpending(sigset_t *set);
功能
用于檢查當前進程中被阻塞(屏蔽)但尚未處理的信號的函數。
參數
set
:一個指向 sigset_t 類型的指針,用于存儲當前被阻塞且未處理的信號集。
返回值
- 成功:返回 0。
- 失敗:返回 -1,并設置 errno 以指示錯誤。
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>void sigfun(int sig)
{printf("is SIGINT\n");
}int main()
{//注冊對SIGINT信號的捕捉signal(SIGINT,sigfun);//初始化信號集sigset_t set = {0};sigemptyset(&set); //先清空sigaddset(&set,SIGINT); //再添加//設置信號屏蔽if(sigprocmask(SIG_SETMASK,&set,NULL) < 0){perror("sigprocmask error");return -1;}int i;for(i = 0; i < 5; i++){printf("i = %d\n",i);sleep(1);}//獲取屏蔽信號的信號集sigset_t PendSet = {0};if (sigpending(&PendSet) < 0){perror("sigpending error");return -1;}int res = sigismember(&PendSet,SIGINT);if(res == 1)printf("have SIGINT\n");else if(res == 0)printf("not have SIGINT\n");else if(res < 0){perror("sigismember error");return -1;}}