Unix進程小結(二)關於信號的一些操作
一、基本的概念
1、中斷
中止、暫停當前正在執行的進程,轉而去執行其它的任務。
硬中斷:來自硬件設備的中斷
軟中斷:來自其它程序的中斷
2、信號
信號是一種軟中斷,可以把他看作是進程與進程、內核與進程通信的一種方式,它為進程的異步執行,提供了技術支持。
3、一些常見信號
SIGINT(2) 終端中斷信號Ctrl+c
SIGQUIT(3) 終端退出信號Ctrl+/
SIGABRT(6) 調用abort函數產生的信號
SIGFPE(8) 算術信號
SIGKILL(9) 死亡信號
SIGSEGV(11) 段錯誤信號
SIGALRM(14) 鬧鐘信號
SIGCHLD(17) 子進程結束信號
SIGCONT(18) 進程繼續信號
SIGSTOP(19) 進程暫停信號
SIGTSTP(20) 終端停止信號
4、不可靠信號(非實時)
1、編號小於SIGRGMI(34)的信號都是不可靠的,這些信號是建立在早期的信號機制上的,一個事件發生可能會產生多次信號。
2、不可靠信號不支持排除,在接收信號的時候可能會丟失,如果一個發給一個進程多次,它可能只接收到一次,其它的可能就丟失了。
3、進程在處理這種信號的時候,哪怕設置的信號處理函數,當信號處理函數執行完畢後,會再次恢復成默認的信號處理方式。
5、可靠信號(實時)
1、位於[SIGRGMI(34),SIGRTMAX(64)]區間的都是可靠信號。
2、可靠信號支持排除,不會丟失。
3、無論是可靠信號還是不可靠信號都是通過:kill、signal、sigqueue、sigaction進行處理。
二、信號的捕獲和處理
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighan‐
dler_t handler);
功能:向註冊一個信號處理函數
signum:信號的編號,可以直接寫數字,也可以使用系統提供的宏。
handler:函數指針
SIG_IGN 忽略信號
SIG_DFL 恢復信號默認的處理方式
返回值:是之前信號處理方式
函數指針、SIG_IGN、SIG_DFL、SIG_ERR
(1)eg: signal(SIGINT ,SIG_ING );
//SIG_ING 代表忽略SIGINT信號,SIGINT信號代表由InterruptKey產生,通常是CTRL +C 或者是DELETE 。發送給所有ForeGround Group的進程。
下面我們寫個死循環:
#include
#include
int main(int argc , char *argv[])
{
signal(SIGINT,SIG_IGN);
for(;;);
return 0;
}
這時我們保存執行。
按下CTRL _C程序沒有反應。這就對了
如果我們想結束該程序可以按下CTRL +\來結束
其實當我們按下CTRL +\組合鍵時,是產生了SIGQUIT信號
(2)eg: signal(SIGINT ,SIG_DFL );
//SIGINT信號代表由InterruptKey產生,通常是CTRL +C或者是DELETE。發送給所有ForeGroundGroup的進程。 SIG_DFL代表執行系統默認操作,其實對於大多數信號的系統默認動作時終止該進程。這與不寫此處理函數是一樣的。
我們將上面的程序改成:
#include
#include
int main(int argc , char *argv[])
{
//signal(SIGINT,SIG_IGN);
signal(SIGINT,SIG_DFL)
for(;;);
return 0;
}
這時就可以按下CTRL +C 來終止該進程了。把signal(SIGINT,SIG_DFL);這句去掉,效果是一樣的。
(3) void ( *signal( int sig, void (* handler)( int )))( int );
int (*p)();
這是一個函數指針, p所指向的函數是一個不帶任何參數, 並且返回值為int的一個函數.
int (*fun())();
這個式子與上面式子的區別在於用fun()代替了p,而fun()是一個函數,所以說就可以看成是fun()這個函數執行之後,它的返回值是一個函數指針,這個函數指針(其實就是上面的p)所指向的函數是一個不帶任何參數,並且返回值為int的一個函數.
void (*signal(int signo, void (*handler)(int)))(int);就可以看成是signal()函數(它自己是帶兩個參數,一個為整型,一個為函數指針的函數),而這個signal()函數的返回值也為一個函數指針,這個函數指針指向一個帶一個整型參數,並且返回值為void的一個函數.
在寫信號處理函數時對於信號處理的函數也是void sig_fun(int signo);這種類型,恰好與上面signal()函數所返回的函數指針所指向的函數是一樣的.void ( *signal() )( int );
signal是一個函數, 它返回一個函數指針, 後者所指向的函數接受一個整型參數 且沒有返回值, 仔細看, 是不是siganal( int signo, void (*handler)(int) )的第2個參數了,對了,其實他所返回的就是 signal的第2個信號處理函數,指向信號處理函數,就可以執行函數了( signal內部時, signal把信號做為參數傳遞給handler信號處理函數,接著 signal函數返回指針, 並且又指向信號處理函數, 就開始執行它)
三、子進程的信號處理
1、通過fork創建的子進程會繼承父進程的信號處理方式。
2、通過vfork+exec創建的子進程不會繼承父進程的信號處理方式,會恢復成默認的。
練習:測試通過vfork+exec創建的子進程是否會繼承父進程的信號處理方式。
四、信號的發送與kill命令以及kill函數的一些操作
kill命令是使跟在它後面的進程結束,學習了信號我們知道,它應該是給進程發送了讓進程結束的信號才能完成這一功能。
如何給進程發送信號呢。這裏就要用到kill函數;
它的原型如下;
int kill(pid_ pid,int signum); 返回值含義:成功0 失敗-1
接受者 信號類型
kill命令發送的是什麽信號呢?我們知道讓進程結束的信號有SIGINT。
可是kill其實還有kill和kill -9之分。kill不一定能殺死進程,kill -9卻能100%殺死進程。這是為什麽呢?
難道還有其他信號能使進程結束?
答案是肯定的。
下面用一個簡單的程序來實現簡單的kill命令
- int main(int argc,char* argv[])
- {
- if(argc==1)
- {
- printf("error cmd!\n");
- return -1;
- }
- int pid=atoi(argv[2]);
- int sig=atoi(argv[1]);
- kill(pid,sig);
- }
atoi函數將命令行中的字符串參數轉換成整型,這樣才能傳遞給kill函數!
這個程序最簡單的操作是通過進程號殺死一個進程!
四、pause/sleep/alarm函數的一些操作
#include <unistd.h>
int pause(void);
功能:休眠
1、進程調用了pause函數後會進程睡眠狀態,直到有信號把它叫醒(不被忽略的信號)。
2、當信號來臨後,先執行信號處理函數,信號處理函數結束後pause再返回。
3、pause函數要麽不返回(一直睡眠),要麽返回-1,並且修改errno的值。
4、從功能上來講它相當於沒有時間限制的sleep函數。
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
功能:使用調用的進程睡眠seconds秒
1、調用sleep的進程如果沒有睡眠足夠的秒數,除非收到信號後才會返回。
2、sleep的返回值是0,或剩余的睡眠秒數。
3、相當於有時間限制的pause
int usleep(useconds_t usec);v
功能:睡眠usec微秒
1秒=1000毫秒=1000000微秒。
它是一種更精確的睡眠函數。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:定時一個鬧鐘信號
1、讓內核向調用它的進程,在seconds秒後發送一個SIGALRM信號。
2、SIGALRM信號的默認處理方式是直接退出。
下面通過一個簡單的定時操作來讓大家感受一下這幾給函數
- void sigalrm(int signum)
- {
- printf("我接受到了%d\n",signum);
- }
- int main(int argc,char* argv[])
- {
- signal(SIGALRM,sigalrm);
- if(argc<1)
- {
- printf("error cmd!\n");
- return -1;
- }
- int sec=atoi(argv[1]);
- alarm(sec);
- pause();
- printf("定時%d秒!\n",sec);
- }
此程序通過alarm和pause的配合簡單實現了一個定時功能!先讓程序休眠,在規定時間後alarm發送一個信號,pause也就結束了
達到了定時的目的!
五、信號集和信號屏蔽
1、信號集:
多個信號的集合,sigset_t
由128個二進制位組成,每個二進制位表示一個信號
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);
功能:測試一個信號集中是否有某個信號
返回值:有返回1,沒有返回0,失敗返回-1
2、屏蔽信號集中的信號
每個進程都有一個信號掩碼(signal mask),它就是一個信號集,裏面包含了進程所屏蔽的信號。
int sigprocmask(int how, const sigset_t
*set, sigset_t *oldset);
功能:設置進程的信號掩碼(信號屏蔽碼)
how:修改信號掩碼的方式
SIG_BLOCK:向信號掩碼中添加信號
SIG_UNBLOCK:從信號掩碼中刪除信號
SIG_SETMASK:用新的信號集替換舊的信號掩碼
newset:新添加、刪除、替換的信號集,也可以為空
oldset:獲取舊的信號掩碼
當newset為空時,就是在備份信號掩碼
當進程執行一些敏感操作時不希望被打擾(原子操作),此需要向屏蔽信號。
屏蔽信號的目的不是為了不接收信號,而是延時接收,當處理完要做的事情後,應該把屏蔽的信號還原。
當信號屏蔽時發生的信號會記錄一次,這個信號設置為末決狀態,當信號屏蔽結束後,會再發送一次。
不可靠信號在信號屏蔽期間無論信號發生多少次,信號解除屏蔽後,只發送一次。
可靠信號在信號屏蔽期間發生的信號會排隊記錄,在信號解除屏蔽後逐個處理。
在執行處理函數時,會默認把當前處理的信號屏蔽掉,執行完成後再恢復
int sigpending(sigset_t *set);
功能:獲取末決狀態的信號
- // 更新數據
- void updata(void)
- {
- for(int i=0; i<10; i++)
- {
- printf("更新第%d條數據...\n",i);
- sleep(1);
- }
- }
- void sigint(int signum)
- {
- printf("收到信號%d,正在處理...\n",signum);
- }
- int main()
- {
- signal(34,sigint);
- printf("我是進程%d\n",getpid());
- // 定義信號集
- sigset_t set,oldset;
- // 初始化信號集
- sigemptyset(&set);
- // 向信號集中添加信號
- printf("向信號集中添加%d %s\n",34,sigaddset(&set,34)?"失敗":"成功");
- // 設置信號掩碼
- printf("設置信號掩碼%s\n",sigprocmask(SIG_SETMASK,&set,&oldset)?"失敗":"成功");
- updata();
- sigpending(&set);
- for(int signum=1; signum<65; signum++)
- {
- if(sigismember(&oldset,signum))
- {
- printf("默認屏蔽的的信號%d\n",signum);
- }
- }
- printf("還原信號掩碼%s\n",sigprocmask(SIG_SETMASK,&oldset,NULL)?"失敗":"成功");
- pause();
- }
這個代碼就是一個簡單的屏蔽信號的實例,這裏就不過多贅述!
總之:信號是一種軟中斷,是一種處理異步事件的方法。一般來說,操作系統都支持許多信號。尤其是UNIX,比較重要應用程序一般都會處理信號。
UNIX定義了許多信號,比如SIGINT表示中斷字符信號,也就是Ctrl+C的信號,SIGBUS表示硬件故障的信號;SIGCHLD表示子進程狀態改變信號;SIGKILL表示終止程序運行的信號,等等。信號量編程是UNIX下非常重要的一種技術。
Unix進程小結(二)關於信號的一些操作