1. 訊號
後臺服務程式要脫離shell執行,可用2種方法:
- 後臺執行:
demo &
- fork一個子程序,再終止父程序,從而讓子程序被系統託管(ppid=1)
終止程序時,在shell下可以
ctrl-c
、kill pid
、killall demo
。這三個都是給系統給demo程式傳送了訊號,ctrl-c是
SIGINT
訊號,kill pid和killall是SIGTERM
訊號。demo對這2個訊號的預設處理是程式終止。
在程式中,可以把訊號關聯到新的handler,那麼收到訊號後的處理將改變。
void handler(int sig); //訊號處理函式
signal(SIGINT, handler); //關聯訊號SIGINT和handler
(1)概念
-
訊號是宏定義,一共64個訊號。SIGINT值是2,SIGTERM值是15。用訊號名、訊號值都行。
-
訊號,也叫軟中斷,用來通知程序發生了事件。中斷,是說明程序哪怕在休眠sleep()也能中斷其休眠喚醒。
-
但只能給程序通知發生了某個事件,不能給程序傳遞資料。
-
程序之間可以透過kill庫函式互相傳送訊號。Linux核心也可給程序傳送訊號,通知程序發生了某個事件。
int kill(pid_t pid, int sig); pid>0,將訊號sig傳送給程序號為pid的程序 pid=0,訊號傳給和呼叫kill的程序同程序組中所有程序 pid=-1,將訊號廣播給系統所有程序。如:系統關機時,給所有登入視窗廣播關機資訊。 返回值:0表示成功,-1表示失敗,errno設定值
-
訊號遞達(Delivery)、訊號未決(Pending)
訊號遞達——訊號產生後到達程序並執行了訊號處理函式的過程
訊號未決——訊號產生但沒有遞達
(2)程序對訊號的處理,有3種方式:
-
忽略——對訊號不做任何處理,不產生中斷
signal(SIGINT, SIG_IGN); //忽略SIGINT訊號
程式中設定中斷處理函式,那麼程式收到訊號時,中斷處理函式將被呼叫
-
Linux系統預設的訊號處理
-
signal
或sigaction
函式重新設定處理函式//用這個 //功能更強大。多執行緒中只能用這個,沒有signal()函式 #include<signal.h> sigaction(int sigNum, const struct sigaction* act, struct sigaction* oldact); struct sigaction { void (*sa_handler)(int); //處理函式 void (*sa_sigaction)(int, siginfo_t*, void*); sigset_t sa_mask; //訊號集合 int sa_flags; //標誌位 } /******************************************************/ sa_flags標誌位關注:SA_RESTART 標誌位設為SA_RESTART後,被訊號中斷的系統呼叫(如scanf這種)會在訊號處理後被重新自動呼叫 不設定的話,被中斷的系統呼叫不會重新呼叫 /******************************************************/ struct sigaction stact; memset(&stact, 0, sizeof(stact)); //置0初始化 stact.sa_handler=hdfunc; //指定處理函式 sigaddset(&stact.sa_mask, 15); //訊號15設定阻塞,沒有手動解除阻塞,等2處理完畢緊接著處理15。要放在sigaction之前 sigaction(2, &stact, NULL); sigaction(15, &stact, NULL);
#include<signal.h> signal(int sigNum, sighandler); //返回值意義不大 //sighandler——訊號處理方式,可設為: SIG_IGN忽略 void handler(int)處理 SIG_DFL恢復為預設處理 /******************************************************/ signal(2, hdfunc); //訊號處理函式
(3)常用訊號
訊號名 | 訊號值 | 發出訊號 | 預設處理 |
---|---|---|---|
SIGINT | 2 | 鍵盤中斷ctrl-c | 終止程序 |
SIGKILL | 9 | kill -9 | 終止程序、訊號不能被捕獲、訊號不能被忽略 |
SIGSEGV | 11 | 無效的記憶體引用 | 記憶體越界時,Linux核心給程序發core dump,程式就報錯core dump |
SIGTERM | 15 | kill和killall | 終止程序 |
SIGCHLD | 20,17,18 | 子程序結束 | 忽略 |
(4)實際用法
訊號常用,但工作中用法不多。
-
服務程式體面、安全的退出
服務程式執行在後臺,強行kill不是好辦法,因為kill不會釋放系統資源,會影響系統穩定。而且,對端也不知道你被kill掉了,還在等待通訊等情況。
體面而安全的做法:編寫訊號處理函式,在裡面進行資源釋放(檔案IO關閉、資料庫連線關閉、通訊連線關閉等)、通知對端結束通訊等處理。程式就可以有計劃的退出。
//demo.cpp #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<signal.h> FILE* fp=0; void EXIT(int sig) { printf("get signal:%d\n",sig); if(fp!=0) fclose(fp); //釋放資源 exit(0); } int main() { signal(SIGINT, EXIT); signal(SIGTERM, EXIT); if(fork() > 0) exit(0); while(100) { printf("execute..\n"); sleep(1); } return 0; }
-
網路服務程式抓包
(5)可靠訊號、不可靠訊號
132訊號為不可靠訊號,3464為可靠訊號。
-
不可靠訊號:
存在訊號丟失問題——程序收到訊號不做佇列處理,而是相同訊號會合併成一個。
(早期版本Linux核心的signal函式會在對不可靠訊號進行一次處理後,handler會恢復成預設處理。現在的Linux核心版本。現在已經沒這個情況了。)
#include<stdlib.h> #include<unistd.h> #include<signal.h> void handler(int sig) { printf("sig=%d",sig); for(int j=0;j<10;j++) { printf("j(%d)=%d",sig,j); sleep(1); } } int main() { //signal(2, handler); struct sigaction stact; memset(&stact,0,sizeof(stact)); stact.sa_handler=handler; sigaction(2, stact, NULL); for(int i=0;i<100;i++) { printf("i=%d",i); sleep(1); } return 0; } //./demo執行 //多次killall -2 demo傳送訊號2 發現最後handler執行次數少於訊號2的傳送次數
-
可靠訊號
不會丟失
(6)訊號處理函式被中斷
一個訊號的處理函式正在執行時,若來了其他訊號,會中斷正在執行的處理函式,轉而執行新訊號的處理函式。待新的處理函式執行完,再繼續執行之前的處理。
若來的是相同的訊號,則會排隊阻塞等待當前處理函式執行完。(可靠訊號會出現合併現象)
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
void handler(int sig)
{
printf("sig=%d",sig);
for(int j=0;j<10;j++)
{
printf("j(%d)=%d",sig,j);
sleep(1);
}
}
int main()
{
//signal(2, handler);
//signal(15, handler);
struct sigaction stact;
memset(&stact,0,sizeof(stact));
stact.sa_handler=handler;
sigaction(2, stact, NULL);
sigaction(15, stact, NULL);
for(int i=0;i<100;i++)
{
printf("i=%d",i);
sleep(1);
}
return 0;
}
//demo執行
//killall -2 demo傳送訊號2
//在2的handler還未執行完之前,killall -15 demo傳送訊號15
可見訊號2的handler正在執行時,被訊號15的handler中斷
//多次killall -2 demo傳送訊號2
訊號2的handler不會被打斷,而是排隊,由於2不可靠訊號出現合併
(7)訊號忽略和阻塞
-
忽略
程序把遞達的訊號直接丟棄
signal(sig, SIG_IGN);
-
阻塞
場景:為了讓訊號不被丟棄,同時也不會中斷當前正在執行的處理函式,將其阻塞,待當前中斷處理執行完,再手動結束阻塞
sigprocmask(SIG_BLOCK, &set, NULL)
會立即執行被阻塞的訊號的處理函式。訊號被阻塞在未決狀態,不是被丟棄。
sigset_t set; //建立訊號集合 sigemptyset(&set); //將訊號集合set置空 sigfillset(&set); //把全部訊號加到訊號集合 sigaddset(&set, 15); //向集合中新增訊號15 sigdelset(&set, 15); //從集合中刪除訊號15 int sigismember(&set, 15); //判斷訊號15是否在訊號集合 sigprocmask(SIG_BLOCK, &set, NULL); //將訊號集合set中所有訊號設定為阻塞SIG_BLOCK sigprocmask(SIG_UNBLOCK, &set, NULL); //解除阻塞
//demo #include<stdio.h> #include<stdlib.h> #include<signal.h> #include<stdlib.h> #include<unistd.h> #include<signal.h> void handler(int sig) { printf("sig=%d\n",sig); for(int j=0;j<10;j++) { printf("j(%d)=%d\n",sig,j); sleep(1); } } int main() { /* signal(2, handler); signal(15, handler); sigset_t set; sigemptyset(&set); sigaddset(&set, 15); sigprocmask(SIG_BLOCK, &set, NULL); //訊號設定為阻塞SIG_BLOCK */ struct sigaction stact; memset(&stact,0,sizeof(stact)); stact.sa_handler=handler; sigaddset(&stact.sa_mask, 15); //訊號15設定阻塞,沒有手動解除阻塞,等2處理完畢緊接著處理15。要放在sigaction之前 stact.sa_flags=SA_RESTART; sigaction(2, &stact, NULL); sigaction(15, &stact, NULL); int a; scanf("%d", &a); //在使用者不輸入的情況下,若不設定stact.sa_flags=SA_RESTART,訊號遞達被處理後,scanf等待輸入就跳過了 for(int i=0;i<100;i++) { printf("i=%d\n",i); fflush(stdout); //printf 呼叫可能因為緩衝問題而不立即顯示輸出,而是迴圈完之後全部一起輸出 sleep(1); /* if(i>20) sigprocmask(SIG_UNBLOCK, &set, NULL); */ } return 0; } //demo執行 //killall -2 demo立即執行訊號2的handler //killall -9 demo時不會立即執行訊號15的handler,而是當i==21時才執行