【Linux系统编程】信号 (下)
00. 目錄
文章目錄
- 00. 目錄
- 01. 信號集
- 02. 信號阻塞集
- 03. sigaction函數(shù)
- 04. 附錄
01. 信號集
為了方便對多個信號進(jìn)行處理,一個用戶進(jìn)程常常需要對多個信號做出處理,在 Linux 系統(tǒng)中引入了信號集(信號的集合)。這個信號集有點(diǎn)類似于我們的 QQ 群,一個個的信號相當(dāng)于 QQ 群里的一個個好友。
信號集是用來表示多個信號的數(shù)據(jù)類型(sigset_t)。
信號集相關(guān)的操作主要有如下幾個函數(shù):
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigismember(const sigset_t *set, int signum); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum);測試代碼:
#include <signal.h> #include <stdio.h>int main(int argc, char *argv[]) {sigset_t set; // 定義一個信號集變量int ret = 0;sigemptyset(&set); // 清空信號集的內(nèi)容// 判斷 SIGINT 是否在信號集 set 里// 在返回 1, 不在返回 0ret = sigismember(&set, SIGINT);if(ret == 0){printf("SIGINT is not a member of set \nret = %d\n", ret);}sigaddset(&set, SIGINT); // 把 SIGINT 添加到信號集 setsigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信號集 set// 判斷 SIGINT 是否在信號集 set 里// 在返回 1, 不在返回 0ret = sigismember(&set, SIGINT);if(ret == 1){printf("SIGINT is a member of set \nret = %d\n", ret);}sigdelset(&set, SIGQUIT); // 把 SIGQUIT 從信號集 set 移除// 判斷 SIGQUIT 是否在信號集 set 里// 在返回 1, 不在返回 0ret = sigismember(&set, SIGQUIT);if(ret == 0){printf("SIGQUIT is not a member of set \nret = %d\n", ret);}return 0; }測試結(jié)果
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out SIGINT is not a member of set ret = 0 SIGINT is a member of set ret = 1 SIGQUIT is not a member of set ret = 0 deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$02. 信號阻塞集
信號阻塞集(屏蔽集、掩碼)
信號阻塞集也稱信號屏蔽集、信號掩碼。**每個進(jìn)程都有一個阻塞集,創(chuàng)建子進(jìn)程時子進(jìn)程將繼承父進(jìn)程的阻塞集。**信號阻塞集用來描述哪些信號遞送到該進(jìn)程的時候被阻塞(在信號發(fā)生時記住它,直到進(jìn)程準(zhǔn)備好時再將信號通知進(jìn)程)。
所謂阻塞并不是禁止傳送信號, 而是暫緩信號的傳送。若將被阻塞的信號從信號阻塞集中刪除,且對應(yīng)的信號在被阻塞時發(fā)生了,進(jìn)程將會收到相應(yīng)的信號。
我們可以通過 sigprocmask() 修改當(dāng)前的信號掩碼來改變信號的阻塞情況。
#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 功能:檢查或修改信號阻塞集,根據(jù) how 指定的方法對進(jìn)程的阻塞集合進(jìn)行修改,新的信號阻塞集由 set 指定,而原先的信號阻塞集合由 oldset 保存。參數(shù): how: 信號阻塞集合的修改方法,有 3 種情況:SIG_BLOCK:向信號阻塞集合中添加 set 信號集,新的信號掩碼是set和舊信號掩碼的并集。SIG_UNBLOCK:從信號阻塞集合中刪除 set 信號集,從當(dāng)前信號掩碼中去除 set 中的信號。SIG_SETMASK:將信號阻塞集合設(shè)為 set 信號集,相當(dāng)于原來信號阻塞集的內(nèi)容清空,然后按照set中的信號重新設(shè)置信號阻塞集。set: 要操作的信號集地址。若 set 為 NULL,則不改變信號阻塞集合,函數(shù)只把當(dāng)前信號阻塞集合保存到 oldset 中。oldset: 保存原先信號阻塞集地址返回值:成功:0,失敗:-1,失敗時錯誤代碼只可能是 EINVAL,表示參數(shù) how 不合法。注意:不能阻塞 SIGKILL 和 SIGSTOP 等信號,但是當(dāng) set 參數(shù)包含這些信號時 sigprocmask() 不返回錯誤,只是忽略它們。另外,阻塞 SIGFPE 這樣的信號可能導(dǎo)致不可挽回的結(jié)果,因?yàn)檫@些信號是由程序錯誤產(chǎn)生的,忽略它們只能導(dǎo)致程序無法執(zhí)行而被終止。
測試代碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h>//int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);void fun(int signo) {printf("\033[31mcatch signal %d\033[0m\n", signo); }int main(void) {int i = 0;sigset_t set;sigset_t oldset;signal(SIGINT, fun);signal(SIGQUIT, fun);printf("================================\n");sigemptyset(&set);sigaddset(&set, SIGINT);sigaddset(&set, SIGQUIT);//設(shè)置阻塞信號 并集if (-1 == sigprocmask(SIG_BLOCK, &set, &oldset)){printf("sigprocmask failed...\n"); goto err0;}sleep(5);//還原信號集if (-1 == sigprocmask(SIG_SETMASK, &oldset, NULL)){printf("sigprocmask failed...\n"); goto err0;}return 0; err0:return 1; }測試結(jié)果:
deng@itcast:/mnt/hgfs/LinuxHome/code.bak2/4sys/6th/code$ gcc 15sigprocmask.c deng@itcast:/mnt/hgfs/LinuxHome/code.bak2/4sys/6th/code$ ./a.out ================================ ^C^C^C^C^C^Ccatch signal 2 deng@itcast:/mnt/hgfs/LinuxHome/code.bak2/4sys/6th/code$03. sigaction函數(shù)
從 UNIX 系統(tǒng)繼承過來的信號(SIGHUP~SIGSYS,前 32 個)都是不可靠信號,不支持排隊(duì)(多次發(fā)送相同的信號, 進(jìn)程可能只能收到一次,可能會丟失)。
SIGRTMIN 至 SIGRTMAX 的信號支持排隊(duì)(發(fā)多少次, 就可以收到多少次, 不會丟失),故稱為可靠信號。
可靠信號就是實(shí)時信號,非可靠信號就是非實(shí)時信號。
signal() 函數(shù)只能提供簡單的信號安裝操作,使用 signal() 函數(shù)處理信號比較簡單,只要把要處理的信號和處理函數(shù)列出即可。
signal() 函數(shù)主要用于前面 32 種不可靠、非實(shí)時信號的處理,并且不支持信號傳遞信息。
Linux 提供了功能更強(qiáng)大的 sigaction() 函數(shù),此函數(shù)可以用來檢查和更改信號處理操作,可以支持可靠、實(shí)時信號的處理,并且支持信號傳遞信息。
下面我們一起學(xué)習(xí)其相關(guān)函數(shù)的使用。
sigqueue函數(shù)
#include <signal.h>int sigqueue(pid_t pid, int sig, const union sigval value); 功能:給指定進(jìn)程發(fā)送信號。 參數(shù):pid: 進(jìn)程號。sig: 信號的編號,這里可以填數(shù)字編號,也可以填信號的宏定義,可以通過命令 kill -l ("l" 為字母)進(jìn)行相應(yīng)查看。value: 通過信號傳遞的參數(shù)。union sigval 類型如下:union sigval{int sival_int;void *sival_ptr;};返回值:成功:0失敗:-1sigaction函數(shù)
int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact );功能:檢查或修改指定信號的設(shè)置(或同時執(zhí)行這兩種操作)。 參數(shù):signum:要操作的信號。act:要設(shè)置的對信號的新處理方式(設(shè)置)。oldact:原來對信號的處理方式(設(shè)置)。如果 act 指針非空,則要改變指定信號的處理方式(設(shè)置),如果 oldact 指針非空,則系統(tǒng)將此前指定信號的處理方式(設(shè)置)存入 oldact。返回值:成功:0失敗:-1struct sigaction 結(jié)構(gòu)體
struct sigaction {/*舊的信號處理函數(shù)指針*/void (*sa_handler)(int signum) ;/*新的信號處理函數(shù)指針*/void (*sa_sigaction)(int signum, siginfo_t *info, void *context);sigset_t sa_mask;/*信號阻塞集*/int sa_flags;/*信號處理的方式*/ }; sa_handler、sa_sigaction:信號處理函數(shù)指針,和 signal() 里的函數(shù)指針用法一樣, 應(yīng)根據(jù)情況給 sa_sigaction、sa_handler 兩者之一賦值,其取值如下:SIG_IGN:忽略該信號SIG_DFL:執(zhí)行系統(tǒng)默認(rèn)動作處理函數(shù)名:自定義信號處理函數(shù)sa_mask:信號阻塞集sa_flags:用于指定信號處理的行為,它可以是一下值的“按位或”組合:SA_RESTART:使被信號打斷的系統(tǒng)調(diào)用自動重新發(fā)起(已經(jīng)廢棄)SA_NOCLDSTOP:使父進(jìn)程在它的子進(jìn)程暫停或繼續(xù)運(yùn)行時不會收到 SIGCHLD 信號。SA_NOCLDWAIT:使父進(jìn)程在它的子進(jìn)程退出時不會收到 SIGCHLD 信號,這時子進(jìn)程如果退出也不會成為僵尸進(jìn)程。SA_NODEFER:使對信號的屏蔽無效,即在信號處理函數(shù)執(zhí)行期間仍能發(fā)出這個信號。SA_RESETHAND:信號處理之后重新設(shè)置為默認(rèn)的處理方式。SA_SIGINFO:使用 sa_sigaction 成員而不是 sa_handler 作為信號處理函數(shù)。信號處理函數(shù)
信號處理函數(shù): void (*sa_sigaction)( int signum, siginfo_t *info, void *context ); 參數(shù)說明:signum:信號的編號。info:記錄信號發(fā)送進(jìn)程信息的結(jié)構(gòu)體。context:可以賦給指向 ucontext_t 類型的一個對象的指針,以引用在傳遞信號時被中斷的接收進(jìn)程或線程的上下文測試程序: 一個進(jìn)程在發(fā)送信號,一個進(jìn)程在接收信號的發(fā)送。
發(fā)送信號測試代碼:
#include <stdio.h> #include <signal.h> #include <sys/types.h> #include <unistd.h>/******************************************************* *功能: 發(fā) SIGINT 信號及信號攜帶的值給指定的進(jìn)程 *參數(shù): argv[1]:進(jìn)程號argv[2]:待發(fā)送的值(默認(rèn)為100) *返回值: 0 ********************************************************/ int main(int argc, char *argv[]) {if(argc >= 2){pid_t pid,pid_self;union sigval tmp;pid = atoi(argv[1]); // 進(jìn)程號if( argc >= 3 ){tmp.sival_int = atoi(argv[2]);}else{tmp.sival_int = 100;}// 給進(jìn)程 pid,發(fā)送 SIGINT 信號,并把 tmp 傳遞過去sigqueue(pid, SIGINT, tmp);pid_self = getpid(); // 進(jìn)程號printf("pid = %d, pid_self = %d\n", pid, pid_self);}return 0; }接收信號測試代碼:
#include <signal.h> #include <stdio.h>// 信號處理回電函數(shù) void signal_handler(int signum, siginfo_t *info, void *ptr) {printf("signum = %d\n", signum); // 信號編號printf("info->si_pid = %d\n", info->si_pid); // 對方的進(jìn)程號printf("info->si_sigval = %d\n", info->si_value.sival_int); // 對方傳遞過來的信息 }int main(int argc, char *argv[]) {struct sigaction act, oact;act.sa_sigaction = signal_handler; //指定信號處理回調(diào)函數(shù)sigemptyset(&act.sa_mask); // 阻塞集為空act.sa_flags = SA_SIGINFO; // 指定調(diào)用 signal_handler// 注冊信號 SIGINTsigaction(SIGINT, &act, &oact);while(1){printf("pid is %d\n", getpid()); // 進(jìn)程號pause(); // 捕獲信號,此函數(shù)會阻塞}return 0; }兩個終端分別編譯代碼,一個進(jìn)程接收,一個進(jìn)程發(fā)送,運(yùn)行結(jié)果如下:
04. 附錄
4.1 參考博客: 【linux系統(tǒng)編程】進(jìn)程間通信:信號中斷處理
總結(jié)
以上是生活随笔為你收集整理的【Linux系统编程】信号 (下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Linux系统编程】信号 (上)
- 下一篇: 【Linux系统编程】进程间通信之无名管