一,管道PIPE
二,FIFO通信
三,mmap通信
四,信號的概念
信號的特點:簡單,但不能攜帶大量的信息,滿足特定條件就會發生
信號的機制:進程B發送信號給進程A。信號是由內核來處理的。
信號的產生:
- 按鍵產生:ctrl+c,ctrl+z,ctrl+\
- Ctrl + c → 2) SIGINT(終止/中斷) "INT" ----Interrupt
- Ctrl + z → 20) SIGTSTP(暫停/停止) "T" ----Terminal 終端。
- Ctrl + \ → 3) SIGQUIT(退出)
- 調用系統函數后產生:kill,raise,abort等
- 定時器產生:alarm,setitimer
- 命令產生:kill等
- 由於硬件錯誤產生:非法訪問內存(段錯誤),除0(浮點數錯誤),內存對齊錯誤(總線錯誤),管道破裂(fd已經關閉了還調用write函數時)。
- 除0操作 → 8) SIGFPE (浮點數例外) "F" -----float 浮點數。
- 非法訪問內存 → 11) SIGSEGV (段錯誤)
- 總線錯誤 →7) SIGBUS
- 管道破 → 13)SIGPIPE
信號的狀態:
- 產生
- 遞達:信號到達了,並且處理完了
- 未決:產生和遞達之間的狀態。主要由於阻塞(屏蔽)導致該狀態。
信號的默認處理方式:
- 忽略
- 執行默認動作
- 捕獲
信號的4要素:
-
編號
-
事件
-
名稱
-
默認處理動作
-
忽略
-
終止
-
終止並產生core文件
-
暫停
-
繼續
-
下面是英語:
Term Default action is to terminate the process. Ign Default action is to ignore the signal. Core Default action is to terminate the process and dump core (see core(5)). Stop Default action is to stop the process. Cont Default action is to continue the process if it is currently stopped.
-
用man 7 signal可以查看詳細的信息
下面是從man 7 signal里考出來的linux常規信號一覽表,value里有3列的,看中間的數字,中間的數字代表linux
Signal Value Action Comment
──────────────────────────────────────────────────────────────────────
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating-point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no
readers; see pipe(7)
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at terminal
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTOU 22,22,27 Stop Terminal output for background process
中文解釋:
1) SIGHUP: 當用戶退出shell時,由該shell啟動的所有進程將收到這個信號,默認動作為終止進程
2) SIGINT:當用戶按下了<Ctrl+C>組合鍵時,用戶終端向正在運行中的由該終端啟動的程序發出此信號。默認動
作為終止進程。
3) SIGQUIT:當用戶按下<ctrl+\>組合鍵時產生該信號,用戶終端向正在運行中的由該終端啟動的程序發出些信
號。默認動作為終止進程。
4) SIGILL:CPU檢測到某進程執行了非法指令。默認動作為終止進程並產生core文件
5) SIGTRAP:該信號由斷點指令或其他 trap指令產生。默認動作為終止里程 並產生core文件。
6) SIGABRT: 調用abort函數時產生該信號。默認動作為終止進程並產生core文件。
7) SIGBUS:非法訪問內存地址,包括內存對齊出錯,默認動作為終止進程並產生core文件。
8) SIGFPE:在發生致命的運算錯誤時發出。不僅包括浮點運算錯誤,還包括溢出及除數為0等所有的算法錯誤。默認動作為終止進程並產生core文件。
9) SIGKILL:無條件終止進程。本信號不能被忽略,處理和阻塞。默認動作為終止進程。它向系統管理員提供了可以殺死任何進程的方法。
10) SIGUSE1:用戶定義 的信號。即程序員可以在程序中定義並使用該信號。默認動作為終止進程。
11) SIGSEGV:指示進程進行了無效內存訪問。默認動作為終止進程並產生core文件。
12) SIGUSR2:另外一個用戶自定義信號,程序員可以在程序中定義並使用該信號。默認動作為終止進程。
13) SIGPIPE:Broken pipe向一個沒有讀端的管道寫數據。默認動作為終止進程。
14) SIGALRM: 定時器超時,超時的時間 由系統調用alarm設置。默認動作為終止進程。
15) SIGTERM:程序結束信號,與SIGKILL不同的是,該信號可以被阻塞和終止。通常用來要示程序正常退出。執行shell命令Kill時,缺省產生這個信號。默認動作為終止進程。
16) SIGSTKFLT:Linux早期版本出現的信號,現仍保留向后兼容。默認動作為終止進程。
17) SIGCHLD:子進程結束時,父進程會收到這個信號。默認動作為忽略這個信號。
18) SIGCONT:如果進程已停止,則使其繼續運行。默認動作為繼續/忽略。
19) SIGSTOP:停止進程的執行。信號不能被忽略,處理和阻塞。默認動作為暫停進程。
20) SIGTSTP:停止終端交互進程的運行。按下<ctrl+z>組合鍵時發出這個信號。默認動作為暫停進程。
21) SIGTTIN:后台進程讀終端控制台。默認動作為暫停進程。
22) SIGTTOU: 該信號類似於SIGTTIN,在后台進程要向終端輸出數據時發生。默認動作為暫停進程。
23) SIGURG:套接字上有緊急數據時,向當前正在運行的進程發出些信號,報告有緊急數據到達。如網絡帶外數據到達,默認動作為忽略該信號。
24) SIGXCPU:進程執行時間超過了分配給該進程的CPU時間 ,系統產生該信號並發送給該進程。默認動作為終止進程。
25) SIGXFSZ:超過文件的最大長度設置。默認動作為終止進程。
26) SIGVTALRM:虛擬時鍾超時時產生該信號。類似於SIGALRM,但是該信號只計算該進程占用CPU的使用時間。默認動作為終止進程。
27) SGIPROF:類似於SIGVTALRM,它不公包括該進程占用CPU時間還包括執行系統調用時間。默認動作為終止進程。
28) SIGWINCH:窗口變化大小時發出。默認動作為忽略該信號。
29) SIGIO:此信號向進程指示發出了一個異步IO事件。默認動作為忽略。
30) SIGPWR:關機。默認動作為終止進程。
31) SIGSYS:無效的系統調用。默認動作為終止進程並產生core文件。
34) SIGRTMIN ~ (64) SIGRTMAX:LINUX的實時信號,它們沒有固定的含義(可以由用戶自定義)。所有的實時信號的默認動作都為終止進程。
信號的編號
可以使用kill –l命令查看當前系統可使用的信號有哪些。
信號集合
- 阻塞信號集(信號屏蔽字): 將某些信號加入集合,對他們設置屏蔽,當屏蔽x信號后,再收到該信號,該信號的處理將推后(解除屏蔽后)
- 未決信號集:
- 信號產生,未決信號集中描述該信號的位立刻翻轉為1,表信號處於未決狀態。當信號被處理對應位翻轉回為0。這一時刻往往非常短暫。
- 信號產生后由於某些原因(主要是阻塞)不能抵達。這類信號的集合稱之為未決信號集。在屏蔽解除前,信號一直處於未決狀態。

系統函數產生的信號
-
kill函數
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);- sig:信號。不推薦直接使用數字,應使用宏名,因為不同操作系統信號編號可能不同,但名稱一致。
- pid > 0: 發送信號給指定的進程。
- pid = 0: 發送信號給 與調用kill函數進程屬於同一進程組的所有進程。
- pid < 0: 取|pid|發給對應進程組。
- pid = -1:發送給進程有權限發送的系統中所有進程。
- 返回值:成功(0),失敗(-1)。
例子:在子進程中調用kill函數,殺死父進程
#include <sys/types.h> #include <signal.h> #include <unistd.h> #include <stdio.h> int main(){ int proIdx = 0; for(proIdx = 0; proIdx < 5; ++proIdx){ pid_t pid = fork(); if(pid == 0){ break; } } //在第三個子進程中殺死父進程 if(proIdx == 2){ printf("kill parent process after 5s\n"); sleep(5); int ret = kill(getppid(), SIGKILL); while(1){ sleep(1); } } //parent process if(proIdx == 5){ while(1){ printf("father\n"); sleep(1); } } }例子:在父進程中調用kill函數,殺死子進程
#include <sys/types.h> #include <signal.h> #include <unistd.h> #include <stdio.h> int main(){ pid_t pid3, pid; int proIdx = 0; for(proIdx = 0; proIdx < 5; ++proIdx){ pid = fork(); if(pid == 0){ break; } //parent process if(proIdx == 2){ pid3 = pid; } } if(proIdx < 5){ while(1){ printf("pid=%d\n", getpid()); sleep(3); } } //在父進程中殺死第三個子進程 if(proIdx == 5){ sleep(5); int ret = kill(pid3, SIGKILL); while(1){ sleep(1); } } } -
raise函數:給調用raise函數的進程發送信號,實際調用的是kill(getpid(), sig)函數
#include <signal.h> int raise(int sig);- sig:信號。不推薦直接使用數字,應使用宏名,因為不同操作系統信號編號可能不同,但名稱一致。
例子:
#include <signal.h> #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main(){ printf("will kill myself\n"); sleep(2); raise(SIGKILL); return 0; } -
abort函數:給調用abort函數的進程發送信號,並生成core文件(生成core文件的前提是:ulimit -c unlimited)
#include <stdlib.h> void abort(void);#include <signal.h> #include <sys/types.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> int main(){ printf("will kill myself\n"); sleep(2); abort(); return 0; } -
alarm函數:在指定參數的秒數后,給調用alarm函數的進程發送SIGALRM信號。SIGALRM信號的默認處理的Term,所以就會終止當前的進程。
Signal Value Action SIGALRM 14 Term#include <unistd.h> unsigned int alarm(unsigned int seconds);- seconds:指定秒數后發送SIGALRM信號
- 返回值:第一次返回0。返回第一次和第二次之間的秒數。(returns the number of seconds remaining until any previously scheduled alarm was due to be delivered, or zero if there was no previously scheduled alarm.)
例子:
#include <stdio.h> #include <unistd.h> int main() { int ret = 0; ret = alarm(2); printf("ret:%d\n", ret); sleep(1); ret = alarm(5); printf("ret:%d\n", ret); while(1){ printf("aaaaaaaaaa\n"); sleep(1); } } -
setitimer函數:功能強大的alarm函數,可以設置周期性的鬧鍾。
#include <sys/time.h> int getitimer(int which, struct itimerval *curr_value); int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);-
which:
- ITIMER_REAL:按自然時鍾的方式計時。鬧鍾響了后,產生SIGALRM信號。
- ITIMER_VIRTUAL:按進程執行時間的方式計時。鬧鍾響了后,產生SIGVTALRM信號。
- ITIMER_PROF:按進程執行時間的方式和CPU時鍾的方式計時。鬧鍾響了后,產生SIGPROF信號。
Signal Value Action Comment SIGALRM 14 Term Timer signal from alarm(2) SIGVTALRM 26,26,28 Term Virtual alarm clock (4.2BSD) SIGPROF 27,27,29 Term Profiling timer expired可以看出來,這3個信號的默認處理都是Term,所以就會終止當前的進程。
-
new_value和old_value的結構體
struct itimerval { struct timeval it_interval; /* Interval for periodic timer */ 周期定時用 struct timeval it_value; /* Time until next expiration */ 單次定時用 }; struct timeval { time_t tv_sec; /* seconds */ 秒 suseconds_t tv_usec; /* microseconds */ 微妙 }; -
返回值:成功0,失敗-1。
例子:周期性的鬧鍾,第一次鬧鍾在5秒后,然后每3秒鬧鍾響一次。鬧鍾響后調用函數cap,並把信號SIGALRM傳給函數。
#include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <signal.h> void cap(int num){ printf("cap:%d\n", num); } int main(){ signal(SIGALRM, cap);//當本進程收到SIGALRM信號時,調用函數cap,並把信號SIGALRM對應的數字傳給cap struct itimerval it = {{3,0}, {5,0}};//第一次鬧鍾在5秒后,然后每3秒鬧鍾響一次。 setitimer(ITIMER_REAL, &it, NULL); while(1){ printf("aaaaa\n"); sleep(1); } } -
信號集函數
1,操作信號集合
#include <signal.h>
//把對應的標識位全部設置成0
int sigemptyset(sigset_t *set);
//把對應的標識位全部設置成1
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);
2,設置阻塞信號
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- how
- SIG_BLOCK:設置想要阻塞的信號
- SIG_UNBLOCK:設置想要解除阻塞的信號
- SIG_SETMASK:讓新的set替換掉原來的信號集合
3,取得當前未決信號集
#include <signal.h>
int sigpending(sigset_t *set);
- set:未決的信號集
4,例子:設置ctrl+c的信號為阻塞。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(){
sigset_t setu;
//清空信號集合setu
sigemptyset(&setu);
//把信號SIGINT加入信號集合setu
sigaddset(&setu, SIGINT);
//把信號SIGQUIT加入信號集合setu
sigaddset(&setu, SIGQUIT);
//把集合里面的信號設置為阻塞。
sigprocmask(SIG_BLOCK, &setu, NULL);
while(1){
sigset_t set1;
sigpending(&set1);
for(int i = 1; i < 31; ++i){
//判斷信號i是否在信號集合set1里面。
if(sigismember(&set1, i) == 1){
printf("1");
}
else {
printf("0");
}
}
printf("\n");
sleep(1);
}
}
信號捕捉
1,信號捕捉函數signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- signum:要捕捉的信號
- handler:捕捉到信號后,調用的函數指針。返回值為void,參數為int(信號的值)
2,信號捕捉函數sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
-
signum:要捕捉的信號
-
act:捕捉到信號后,執行的動作定義在結構體里。
struct sigaction { void (*sa_handler)(int);//當sa_flags為0時,調用的函數指針 void (*sa_sigaction)(int, siginfo_t *, void *);//當sa_flags為SA_SIGINFO時,調用的函數指針,這個函數可以傳進去很多參數,但是信號本身的目的就是簡單並攜帶少量信息,所以一般不使用這個參數 sigset_t sa_mask;//在執行函數期間,指定的臨時的阻塞信號集合。 int sa_flags;//一般為0,根據這個flag來決定調用哪個函數。SA_RESTART的作用是,比如某些阻塞函數(read)在阻塞的時候,突然來了個信號,執行完信號捕捉函數后,會繼續執行阻塞的函數(read),不設置sa_flags為SA_RESTART的話,信號捕捉函數信息后,不繼續執行阻塞的函數(read)。 void (*sa_restorer)(void); }; -
oldact:原來對應這個信號的動作。目的是,方便還原回去。
例子1:
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
void catch(int num){
printf("catch %d signal\n", num);
}
int main()
{
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
struct itimerval it = {{3, 0}, {5, 0}};
setitimer(ITIMER_REAL, &it, NULL);
while(1){
printf("kill me plz\n");
sleep(1);
}
}
4,信號捕捉函數執行期間的特性
- X信號捕捉函數的執行期間,X信號被自動屏蔽
- 阻塞的常規信號不支持排隊,即使產生多次,函數只被調用一次。(后32個實時信號支持排隊)
- 進程運行時,有一個屏蔽的信號集合,假定為集合A。對於函數sigaction來說,在信號捕捉函數執行期間,屏蔽的信號集合不是集合A,而是集合sa_mask。
驗證的例子:
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
void catch(int num){
printf("begin catch %d signal\n", num);
sleep(5);
printf("end catch %d signal\n", num);
}
int main(){
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
sigaction(SIGINT, &act, NULL);
while(1){
printf("kill me\n");
sleep(1);
}
}
- 在函數catch執行期間,即便多次按ctrl c,最后也只執行一次。
- 在函數catch執行期間,即使按ctrl \,也沒有效果,因為ctrl \產生的信號(SIGQUIT)被屏蔽了。
- 不在函數catch執行期間,按ctrl \后,進程就立即結束了。因為信號(SIGQUIT)沒有被屏蔽。
用信號機制回收子進程。
原理:當子進程被暫停或者終止的時候,會給父進程發送信號SIGCHLD,信號SIGCHLD的默認處理為Ign,也就是什么都不做。所以我們可以通過捕捉信號SIGCHLD,在信號捕捉函數里調用wait或者waitpid函數來回收子進程。
Signal Value Action Comment
───────────────────────────────────────────────────────────
SIGCHLD 20,17,18 Ign Child stopped or terminated
1,錯誤的粗略版,不能夠回收全部的子進程。理由是,信號SIGCHLD是不支持排隊的,所以當waitpid函數執行的同時,又有一個子進程結束了,也就是又來了一個SIGCHLD信號,所以這個信號就被忽略了。
重點看最后一行的sleep函數。如果把最后一行的sleep函數的注釋打開,就能夠全部回收子進程了。理由是睡了1秒,在waitpid函數執行的同時,沒有別的SIGCHLD信號進來,所以就都回收了。
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void catch(int num){
pid_t pid = waitpid(-1, NULL, WNOHANG);
printf("%d is ok\n", pid);
}
int main(){
int i = 0;
for(; i < 10; ++i){
pid_t p = fork();
if(p == 0)break;
}
if(i == 10){
//parent process
struct sigaction act, old;
act.sa_flags = 0;
act.sa_handler = catch;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);
while(1){
sleep(1);
}
}
else {
//child process
printf("child %d %d\n", i, getpid());
//sleep(1);
}
}
2,改進版,即使注釋掉最后一行的sleep函數,也能夠回收全部的子進程。理由是虛幻調用waitpid函數了。在一次信號捕捉函數里,盡量多的回收子進程,但是也不一定是絕對的穩定,極端時候也會有回收不全的情況。每次執行的時候就會發現,打印的【begin】和【end】不一定是幾次。也就是說在一次捕捉函數里,調用了多次waitpid函數。而且還有一個致命的弱點,如果父進程的信號捕捉函數sigaction還沒有調用,子進程就全部結束了的話,所以的子進程都不能夠被回收了。
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void catch(int num){
printf("begin\n");
pid_t pid;
while((pid = waitpid(-1, NULL, WNOHANG)) > 0){
printf("%d is ok\n", pid);
}
printf("end\n");
}
int main(){
int i = 0;
for(; i < 10; ++i){
pid_t p = fork();
if(p == 0)break;
}
if(i == 10){
//parent process
struct sigaction act, old;
act.sa_flags = 0;
act.sa_handler = catch;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);
while(1){
sleep(1);
}
}
else {
//child process
printf("child %d %d\n", i, getpid());
}
}
3,最終版。解決改進版的問題。
原理:在父進程里阻塞信號SIGCHLD,這樣一來,即使子進程在父進程調用sigaction之前就結束了,子進程發送過來的信號SIGCHLD是被阻塞的狀態,也就是信號處於未決狀態。在調用sigaction后的下一行代碼,把信號SIGCHLD從阻塞狀態恢復到原來的非阻塞狀態后,這個時點所有被阻塞的子進程的信號SIGCHLD,就一次(【begin】和【end】只被打印一次)都被捕捉函數處理掉了。
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void catch(int num){
printf("begin\n");
pid_t pid;
while((pid = waitpid(-1, NULL, WNOHANG)) > 0){
printf("%d is ok\n", pid);
}
printf("end\n");
}
int main(){
sigset_t st, oldset;
sigemptyset(&st);
sigaddset(&st, SIGCHLD);
//把信號SIGCHLD設置為阻塞,並取得當前的信號集合oldset
sigprocmask(SIG_BLOCK, &st, &oldset);
int i = 0;
for(; i < 10; ++i){
pid_t p = fork();
if(p == 0)break;
}
if(i == 10){
//parent process
sleep(2);
struct sigaction act, old;
act.sa_flags = 0;
act.sa_handler = catch;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);
//用信號集合oldset,恢復到之前的狀態。
sigprocmask(SIG_SETMASK, &oldset, NULL);
while(1){
sleep(1);
}
}
else {
//child process
printf("child %d %d\n", i, getpid());
//sleep(1);
}
}
父子進程通過信號SIGUSR1和SIGUSR2來通信,但是執行不成功。執行結果是子進程直接結束了,變成了僵屍進程,求指點!!!
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
void fcat(int num){
sleep(1);
printf("aa");
}
void fcat2(int num){
sleep(1);
printf("aa1");
}
int main(){
pid_t pid = fork();
if(pid == 0){
sigset_t st, oldset;
sigemptyset(&st);
sigaddset(&st, SIGUSR2);
if((sigprocmask(SIG_BLOCK, &st, &oldset)) == -1){
perror("sigprocmask");
}
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = fcat2;
sigaction(SIGUSR2, &act, NULL);
sigprocmask(SIG_SETMASK, &oldset, NULL);
int cnt = 1;
while(1){
printf("child:%d\n", getpid());
kill(getppid(), SIGUSR1);
sleep(1);
}
}
else{
sigset_t st, oldset;
sigemptyset(&st);
sigaddset(&st, SIGUSR1);
sigprocmask(SIG_BLOCK, &st, &oldset);
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = fcat;
sigaction(SIGUSR1, &act, NULL);
sigprocmask(SIG_SETMASK, &oldset, NULL);
int cnt = 0;
while(1){
printf("parent:%d\n", getpid());
kill(pid, SIGUSR2);
sleep(1);
}
}
}
c/c++ 學習互助QQ群:877684253

