程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 淺談Linux中的信號機制(一),淺談linux信號機制

淺談Linux中的信號機制(一),淺談linux信號機制

編輯:C++入門知識

淺談Linux中的信號機制(一),淺談linux信號機制


     有好些日子沒有寫博客了,自己想想還是不要荒廢了時間,寫點兒東西記錄自己的成長還是百利無一害的。今天是9月17號,暑假在某家游戲公司實習了一段時間,做的事情是在Windows上用c++寫一些游戲英雄技能的邏輯實現。雖然時間不算長,但是也算學了一點東西,對團隊項目開發流程也有了一個直觀的感受,項目裡c++11新特性也有用到不少,特別是lambda表達式,STL的一些容器和算法也終於有了可以實踐的地方。由於自己比較喜歡Linux C,也就沒有做留下的打算,現在回到了學校,好好復習一段時間,准備一下校招吧。如果有朋友有工作機會的話,不妨可以推薦一下我O(∩_∩)O,我的EMAIL:[email protected]。貌似有點扯遠了,好久不寫東西了。

信號的基本概念

  談起Linux編程signal是非常重要的一塊內容。關於signal,Linux 的 manual有很詳細的介紹。具體:man 7 signal .我就不浪費篇幅貼出來了。

  信號被認為是一種軟件中斷(區別於硬件中斷)。信號機制提供了一種在單進程/線程 下處理異步事件的方法。具體過程是當進程運行到某處,接受到一個信號,保留“現場”,響應信號(注意這裡的響應是一種宏觀意義上的響應,對信號的忽略(SIG_IGN)也被以為是一種響應,後面會詳細談到信號響應的方式。),在返回到剛剛保存的地方繼續運行。我制作了一張GIF或許可以清晰的體現這樣的處理方式:

#include <signal.h> #include <stdio.h> void sigdemo(int sig) { printf("Receive a signal:%s\n",strsignal(sig)); } int main() { if(signal(SIGINT,sigdemo) == SIG_ERR)
  {
    perror("signal()");
    return ;
  }
printf("Main started.\n"); pause();//wait a signal. }

可以看到,我在main函數中並沒有主動調用sigdemo函數,可是運行程序後,當我們在中斷按下CTRL+C時(發送SIGINT信號,宏對應的值是2),出現了這樣的結果:

[baixiang ~$]a.out
 Main started.
 ^CReceive a signal:2

可見sigdemo函數得到了執行,其參數sig便是接受到的信號的值。要將信號的值,轉換為其意義string.h中提供了一個函數char* strsignal(int sig), 基本上看到該函數原型就知道這個函數怎麼用了,在此我就不再浪費篇幅贅述了。

發送信號

     上文我們通過使用CTRL+C組合鍵發送信號SIGINT給當前的進程。但是這種方法只能發送少部分信號且並不適用所有的進程比如後台進程和守護進程。守護進程不必說,連終端都沒有。交互shell (interactive shell)在啟動一個後台進程的時候,會自動把中斷和退出信號設置為忽略,關於這點我在網上看到一篇不錯的博客:http://hongjiang.info/shell-script-background-process-ignore-sigint/ 。這樣的情況下就無法使用快捷鍵的方式了。這裡我介紹幾種其他的發送信號的方式。

     首先是shell命令kill其用法如下:

kill [-s signal|-p] [-q sigval] [-a] [--] pid...

     -s  signal  signal可以是諸如SIGINT,SIGQUIT之類的宏,亦可以是1,2,3...這樣的值,可以隨意使用,你開心就好。

     -q queue  sigval是值,可以伴隨信號傳遞,但是這裡只可以是一個integer,在進程中可以使用sigaction()接收到這個值,與之對應的是另一個函數sigqueue()。這裡先不詳細介紹,下文會談到。

     pid就是目標進程的進程id,可以是一個或者多個。但是發送信號時,要確保你所使用的用戶是具有發送信號到目標進程的權限的。

     kill的選項遠不止這些,但是通常這些已經夠用了。如有興趣請自行 “man 1 kill”查看。

     和shell命令kill有一個同名的系統調用kill(),其原型是這樣的:

int kill(pid_t pid, int sig);

     相信看了上邊的shell指令,這個函數的用法就一目了然了吧。pid是目標進程的pid,sig是要發送的信號。和其他函數一樣它也是成功返回0,失敗-1。然而真的這麼簡單嗎?事實上不是。pid這個參數在這裡大有學問。它的取值不僅僅可以是進程id,它甚至可以是負的。如果你對linux下編程熟悉的話,這樣的用法肯定接觸過,獲取消息隊列時使用的msgrcv()函數,其中的msgtype參數也具有類似的用法。當然扯遠了。

    pid>0 此時正式最普通的一種情況,pid是要目標進程的pid。

    pid=0  那麼kill()會將信號發送給調用進程同組的所有進程,也包括他自己。

    pid=-1 那麼信號將被發送至所有它具有權限發送信號的每一個進程(init進程和調用進程除外)。

    pid<-1 信號會發送sig信號到組id等於該pid絕對值的進程組中的每一個進程

如果pid在以上四種情況之外,無法匹配到目標進程,那麼就會返回-1,errno被設置為ESRCH。當沒有權限發送時kill()也將失敗返回-1,errno會被設置為EPERM。關於linux上權限是如何作用的細節,我爭取再後面的博客總結一下。

     與kill()類似的還有一個函數killpg(),用法簡單多了,也不浪費篇幅了,查看manual就能搞定。

     最後一個發送信號的函數是raise(),它只接受一個參數signal,然後把該信號傳遞給調用進程:

int raise(int sig);//成功返回0,失敗返回-1

    由於這個函數不需要引用進程ID,它是被納入C99標准的函數。

    除了這幾種產生信號的shell命令和函數之外還有一些情況下可以產生信號,比如alarm(),settimer()之類的一些與時間相關的函數,以及一些常見的軟硬件錯誤都會產生信號。詳細談這些貌似就有點淡化主題了,扯遠了。

不可靠信號與可靠信號的語義

      信號的可靠與不可靠主要體現在兩個方面:

  • 對於不可靠信號,進程每次處理信號後,都會將信號的處理方式設置為默認動作。而對於可靠信號,它的處理函數執行以後,對該信號的處理方式不會發生變化。
  • 信號可能會丟失。(見下一節實時信號與非實時信號的區別)。

     由於Linux信號機制基本上從早期的UNIX系統上的信號機制移植過來的,所以Linux仍舊支持這些早期的不可靠信號。但是Linux也對不可靠信號做了(上面兩點區別的第一小點)改進,即不可靠信號處理方式,不會在處理函數執行後變成默認方式。所以,在Linux上對於不可靠信號與可靠信號的區別就在於是否支持排隊。

     關於信號是否會丟失,我們看這樣兩段代碼,首先是rcv.c

 1 #include <signal.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <string.h>
 5 #include <unistd.h>
 6 
 7 void fun(int sig)
 8 {
 9     printf("Recvive a signal:%s\n",strsignal(sig));
10 }
11 
12 int main()
13 {
14     if(signal(SIGINT,fun) == SIG_ERR)
15         perror("signal");
16     printf("%d\n",getpid());
17     while(1)
18        pause();
19 }

     這段程序先安裝SIGINT的信號處理函數fun,fun函數只是打印信號信息。之後打印出進程id同時死循環等待信號。

另一段程序是send.c:

 1 #include <signal.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 
 5 int main(int argc,char** argv)
 6 {
 7     pid_t pid = (pid_t)atol(argv[1]);
 8     printf("%d\n",pid);
 9     int i = 0;
10     for(;i<500;++i)
11         kill(pid,SIGINT);
12 }

send程序從終端接收一個參數即目標進程的PID,然後向其發送500次SIGINT信號。下面我們分別把rcv.c,send.c編譯成rcv和send。首先運行rcv,打印出了進程pid 20273然後,我們在開一個終端運行send 20273,觀察到這樣的結果:

 

send明明發送了500個SIGINT信號,而rcv中只接受處理了13個SIGINT信號,這是怎麼回事兒呢?明天再接著寫下去,今天太晚了,戰斗力不足有點犯困了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved