對于 Linux來說,實際信號是軟中斷,許多重要的程序都需要處理信號。信號,為 Linux 提供了一種處理異步事件的方法。比如,終端用戶輸入了 ctrl+c 來中斷程序,會通過信號機制停止一個程序。
信號概述
信號的名字和編號:
每個信號都有一個名字和編號,這些名字都以“SIG”開頭,例如“SIGIO ”、“SIGCHLD”等等。
信號定義在signal.h頭文件中,信號名都定義為正整數。
具體的信號名稱可以使用kill -l來查看信號的名字以及序號,信號是從1開始編號的,不存在0號信號。kill對于信號0又特殊的應用。
//1)掛起 2)中斷(ctl+c) 3)退出 4)出現了問題 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP//6)丟棄 7)總線信號 9)殺死進程6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1// 14)鬧鐘信號
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
// 19)停止程序
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
// 29)IO口的訪問
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
信號的處理:
??????信號的處理有三種方法,分別是:忽略、捕捉和默認動作
??????忽略信號:大多數信號可以使用這個方式來處理,但是有兩種信號不能被忽略(分別是 SIGKILL和SIGSTOP)。因為他們向內核和超級用戶提供了進程終止和停止的可靠方法,如果忽略了,那么這個進程就變成了沒人能管理的的進程,顯然是內核設計者不希望看到的場景
??????捕捉信號:需要告訴內核,用戶希望如何處理某一種信號,說白了就是寫一個信號處理函數,然后將這個函數告訴內核。當該信號產生時,由內核來調用用戶自定義的函數,以此來實現某種信號的處理。
??????系統默認動作:對于每個信號來說,系統都對應由默認的處理動作,當發生了該信號,系統會自動執行。不過,對系統來說,大部分的處理方式都比較粗暴,就是直接殺死該進程。(kill -9 pid 或者 kill -SIGKILL pid)殺死進程
具體的信號默認動作可以使用man 7 signal來查看系統的具體定義。
信號處理函數的注冊
信號處理函數的注冊不只一種方法,分為入門版和高級版
(1)入門版:函數signal
(2)高級版:sigqueue
信號發送函數也不止一個,同樣分為入門版和高級版
(1)入門版:kill
(2)高級版:sigqueue
signal
#include <signal.h>
typedef void (*sighandler_t)(int);//這個是函數指針,沒有返回數,參數是int型,sighandler_t是函數名
sighandler_t signal(int signum, sighandler_t handler);//返回值是sighandler_t,第一個參數signum是要捕捉哪些信號,第二個參數handler是一種類型,指向函數指針void (*sighandler_t)(int)
代碼演示
#include <signal.h>
#include<stdio.h>
// typedef void (*sighandler_t)(int);// sighandler_t signal(int signum, sighandler_t handler);void handler(int signum)//信號處理函數,捕捉信號
{printf("get signal=%d\n",signum);switch(signum){case 2:printf("SIGINT\n");break;case 9:printf("SIGKILL\n");case 10:printf("SIGUSER1\n");}printf("never quite\n");
}
int main()
{signal(SIGINT,handler);//如果將handler改為SIG_ICN即可忽略函數signal(SIGKILL,handler);signal(SIGUSR1,handler);while(1);
}
kill發送消息——低級版
#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);
代碼實戰
#include <signal.h>
#include<stdio.h>
#include <stdlib.h>
#include <sys/types.h>int main(int argc,char**argv)
{int signum;int pid;char cmd[128]={0};signum=atoi(argv[1]);pid=atoi(argv[2]);//atoi將阿斯科碼轉化為整數printf("num=%d,pid=%d\n",signum,pid);
// kill(pid,signum);//用來發信號sprintf(cmd,"kill -%d %d",signum,pid);//cmd是目標字符串,第二個參數是想要的目標字符串的長相system(cmd);//用system調用腳本發信號printf("send signal ok\n");return 0;
}
sigaction原型(安裝信號處理程序)
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//第一個參數是信號,第二個是sigaction結構體用來綁定某些參數,第三個參數也是sigaction結構體,用來備份原有的信號操作,如不需要則設為NULL
struct sigaction {void (*sa_handler)(int); //信號處理程序,不接受額外數據,SIG_IGN 為忽略,SIG_DFL 為默認動作void (*sa_sigaction)(int, siginfo_t *, void *); //信號處理程序,能夠接受額外數據和sigqueue配合使用,第一個參數是信號處理函數,第三個是指針空無數據,非空有數據sigset_t sa_mask;//阻塞關鍵字的信號集,可以再調用捕捉函數之前,把信號添加到信號阻塞字,信號捕捉函數返回之前恢復為原先的值。int sa_flags;//影響信號的行為SA_SIGINFO表示能夠接受數據};
//回調函數句柄sa_handler、sa_sigaction只能任選其一
關于void (*sa_sigaction)(int, siginfo_t *, void );處理函數來說還需要有一些說明。void 是接收到信號所攜帶的額外數據;而struct siginfo這個結構體主要適用于記錄接收信號的一些相關信息。
siginfo_t {int si_signo; /* Signal number */int si_errno; /* An errno value */int si_code; /* Signal code */int si_trapno; /* Trap number that causedhardware-generated signal(unused on most architectures) */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 */int si_band; /* Band event */int si_fd; /* File descriptor */
}
其中的成員很多,si_signo 和 si_code 是必須實現的兩個成員。可以通過這個結構體獲取到信號的相關信息。
信號發送函數——高級版
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);//第一個是發給誰,第二個是發的什么信號,第三個是消息
union sigval {int sival_int;void *sival_ptr;};
接收端代碼實戰
#include <signal.h>
#include<stdio.h>// int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
void handler(int signum, siginfo_t * info,void * context)
{printf("get signum:%d\n",signum);if(context!=NULL){printf("get data=%d\n",info->si_int);printf("get data=%d\n",info->si_value.sival_int);printf("from: %d\n",info->si_pid);}}
int main()
{struct sigaction act;printf("ps=%d\n",getpid());act.sa_sigaction=handler;//收到信號后調用handle處理信號act.sa_flags=SA_SIGINFO;//SA_SIGINFO表示能接收數據sigaction(SIGUSR1,&act,NULL);while(1);return 0;
}
發送端代碼實戰
#include<stdio.h>
#include <signal.h>
//int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char**argv)
{int signum;int pid;signum=atoi(argv[1]);pid=atoi(argv[2]);union sigval value;value.sival_int=100sigqueue(pid,signum,value);printf("pid =%d\n",getpid());printf("over\n"); return 0;
}
臨界資源:
多道程序系統中存在許多進程,它們共享各種資源,然而有很多資源一次只能供一個進程使用。一次僅允許一個進程使用的資源稱為臨界資源。許多物理設備都屬于臨界資源,如輸入機、打印機、磁帶機等。
信號量
信號量(semaphore)與已經介紹過的 IPC 結構不同,它是一個計數器。信號量用于實現進程間的互斥與同步,而不是用于存儲進程間通信數據。
特點
1、 信號量用于進程間同步,若要在進程間傳遞數據需要結合共享內存。
2、信號量基于操作系統的 PV 操作,程序對信號量的操作都是原子操作。
3、 每次對信號量的 PV 操作不僅限于對信號量值加 1 或減 1,而且可以加減任意正整數。
4、 支持信號量組。
原型:
??????最簡單的信號量是只能取 0 和 1 的變量,這也是信號量最常見的一種形式,叫做二值信號量(Binary Semaphore)。而可以取多個正整數的信號量被稱為通用信號量。
??????Linux 下的信號量函數都是在通用的信號量數組上進行操作,而不是在一個單一的二值信號量上進行操作。
#include <sys/sem.h>
// 創建或獲取一個信號量組:若成功返回信號量集ID,失敗返回-1int semget(key_t key, int num_sems,int sem_flags);
// 對信號量組進行操作,改變信號量的值:成功返回0,失敗返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops); // 控制信號量的相關信息int semctl(int semid, int sem_num, int cmd, ...);
當semget創建新的信號量集合時,必須指定集合中信號量的個數(即num_sems),通常為1; 如果是引用一個現有的集合,則將num_sems指定為 0 。
代碼實戰
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<stdio.h>
// int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);//取鑰匙的函數,第一個參數是信號量ID,第二個是配置信號量的個數,第三個是第二項的個數
union semun {int val; /* Value for SETVAL */struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */unsigned short *array; /* Array for GETALL, SETALL */struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */};
void vPutBackKey(int id)
{struct sembuf set;set.sem_num=0;set.sem_op=+1;set.sem_flg=SEM_UNDO;semop(id,&set,1);printf("put back key\n");
}
void pGetKey(int id)
{struct sembuf set;set.sem_num=0;//信號量的編號set.sem_op=-1;//拿鑰匙后原來的鑰匙減一set.sem_flg=SEM_UNDO;//SEM_UNDO表示等待,IPC_NOWAIT表示不等待semop(id,&set,1);printf("get key\n");
}
int main (int argc,char**argv)
{int semid;key_t key=ftok(".",2);semid=semget(key,1,IPC_CREAT|0666);//第二個參數代表信號量集所含信號量個數,flag表示如果沒有信號怎么做。IPC_CREAT表示若信號量不存在則創建它,0666表示信號量的權限union semun initsem;initsem.val=0;//剛開始里面是沒有鑰匙的狀態semctl(sempid,0,SETVAL,initsem);//將信號量初始化,第一個參數是信號量集的ID,第二個參數是代表要操作第幾個信號量 ,0代表第0個。第三個參數是要對信號量如何操作,SETVAL表示設置信號量的值設置為initsem,initsem.val=0表示有一把鑰匙。pid_t pid=fork();if(pid>0){pGetKey(semid);printf("this is father\n");vPutBackKey(semid);semctl(semid,0,IPC_RMID);//銷毀信號量}else if(pid==0){//pGetKey(semid);printf("this is child\n");vPutBackKey(semid);}else{printf("fork error\n");}return 0;
}
本文參考以下兩篇博文編寫:
信號量
信號