1. 程式人生 > >Unix進程小結(二)關於信號的一些操作

Unix進程小結(二)關於信號的一些操作

group fill 註冊 運行 void 做的 錯誤 硬件故障 upd

一、基本的概念
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命令

  1. #include<stdio.h>
  2. #include<signal.h>
  3. #include<unistd.h>
  4. #include<stdlib.h>
  5. int main(int argc,char* argv[])
  6. {
  7. if(argc==1)
  8. {
  9. printf("error cmd!\n");
  10. return -1;
  11. }
  12. int pid=atoi(argv[2]);
  13. int sig=atoi(argv[1]);
  14. kill(pid,sig);
  15. }

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信號的默認處理方式是直接退出。

下面通過一個簡單的定時操作來讓大家感受一下這幾給函數

  1. #include<stdlib.h>
  2. #include<signal.h>
  3. void sigalrm(int signum)
  4. {
  5. printf("我接受到了%d\n",signum);
  6. }
  7. int main(int argc,char* argv[])
  8. {
  9. signal(SIGALRM,sigalrm);
  10. if(argc<1)
  11. {
  12. printf("error cmd!\n");
  13. return -1;
  14. }
  15. int sec=atoi(argv[1]);
  16. alarm(sec);
  17. pause();
  18. printf("定時%d秒!\n",sec);
  19. }

此程序通過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);
功能:獲取末決狀態的信號

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <signal.h>
  4. // 更新數據
  5. void updata(void)
  6. {
  7. for(int i=0; i<10; i++)
  8. {
  9. printf("更新第%d條數據...\n",i);
  10. sleep(1);
  11. }
  12. }
  13. void sigint(int signum)
  14. {
  15. printf("收到信號%d,正在處理...\n",signum);
  16. }
  17. int main()
  18. {
  19. signal(34,sigint);
  20. printf("我是進程%d\n",getpid());
  21. // 定義信號集
  22. sigset_t set,oldset;
  23. // 初始化信號集
  24. sigemptyset(&set);
  25. // 向信號集中添加信號
  26. printf("向信號集中添加%d %s\n",34,sigaddset(&set,34)?"失敗":"成功");
  27. // 設置信號掩碼
  28. printf("設置信號掩碼%s\n",sigprocmask(SIG_SETMASK,&set,&oldset)?"失敗":"成功");
  29. updata();
  30. sigpending(&set);
  31. for(int signum=1; signum<65; signum++)
  32. {
  33. if(sigismember(&oldset,signum))
  34. {
  35. printf("默認屏蔽的的信號%d\n",signum);
  36. }
  37. }
  38. printf("還原信號掩碼%s\n",sigprocmask(SIG_SETMASK,&oldset,NULL)?"失敗":"成功");
  39. pause();
  40. }

這個代碼就是一個簡單的屏蔽信號的實例,這裏就不過多贅述!

總之:信號是一種軟中斷,是一種處理異步事件的方法。一般來說,操作系統都支持許多信號。尤其是UNIX,比較重要應用程序一般都會處理信號。

UNIX定義了許多信號,比如SIGINT表示中斷字符信號,也就是Ctrl+C的信號,SIGBUS表示硬件故障的信號;SIGCHLD表示子進程狀態改變信號;SIGKILL表示終止程序運行的信號,等等。信號量編程是UNIX下非常重要的一種技術。

Unix進程小結(二)關於信號的一些操作