Linux進程間通訊(中)之信號、信號量實踐

上節咱們分享了Linux進程間通訊的其中兩種方式:管道、消息隊列,文章以下:linux

Linux進程間通訊(上)之管道、消息隊列實踐
web

這節咱們就來分享一下Linux的另外兩種進程間通訊的方式:信號、信號量。編程

一、信號

咱們使用過windows的都知道,當一個程序被卡死的時候無論怎樣都沒反應,這樣咱們就能夠打開任務管理器直接強制性的結束這個進程,這個方法的實現就是和Linux上經過生成信號和捕獲信號來實現類似的,運行過程當中進程捕獲到這些信號作出相應的操做使最終被終止。
windows

信號的主要來源是分爲兩部分,一部分是硬件來源,一部分是軟件來源;進程在實際中能夠用三種方式來響應一個信號:一是忽略信號,不對信號作任何操做,其中有兩個信號是不能別忽略的分別是SIGKILL和SIGSTOP。二是捕捉信號,定義信號處理函數,當信號來到時作出響應的處理。三是執行缺省操做,Linux對每種信號都規定了默認操做。注意,進程對實時信號的缺省反應是當即終止。數組

發送信號的函數有不少,主要使用的有:kill()、raise()、abort()、alarm()微信

先來熟悉下kill函數,進程能夠經過kill()函數向包括它自己在內的其它進程發送一個信號,若是程序沒有發送這個信號的權限,對kill函數的調用將會失敗,失敗的緣由一般是因爲目標進程由另外一個用戶所擁有。app

kill函數的原型爲:異步

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);

它的做用是把信號sig發送給進程號爲pid的進程,成功時返回0。kill調用失敗返回-1,調用失敗一般有三大緣由:編輯器

  • 一、給定的信號無效
  • 二、發送權限不夠
  • 三、目標進程不存在

還有一個很是重要的函數,信號處理signal函數。程序能夠用signal函數來處理指定的信號,主要經過恢復和忽略默認行爲來操做。signal函數原型以下:函數

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

咱們來看一個例程瞭解一下signal函數。signal.c

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

//函數ouch對經過參數sig傳遞進來的信號做出響應。
void ouch(int sig)
{
 printf("signal %d\n", sig);
 //恢復終端中斷信號SIGINT的默認行爲
 (void) signal(SIGINT, SIG_DFL);
}
int main()
{
  //改變終端中斷信號SIGINT的默認行爲,使之執行ouch函數
  (void) signal(SIGINT, ouch);
 
  while(1)
  {
   printf("Hello World!\n");
   sleep(1); 
  }
 return 0;
}

運行結果:


能夠看出當我按下ctrl+c的時候並不會退出,只有當再次按下ctrl+c的時候纔會退出。形成的緣由是由於SIGINT的默認行爲被signal函數改變了,當進程接受到信號SIGINT時,它就去調用函數ouch去處理,注意ouch函數把信號SIGINT的處理方式改變成默認的方式,因此當你再按一次ctrl+c時,進程就像以前那樣被終止了。

下面是幾種常見的信號:

  • SIGHUP :從終端上發出的結束信號
  • SIGINT :來自鍵盤的中斷信號 ( ctrl + c )
  • SIGKILL :該信號結束接收信號的進程
  • SIGTERM:kill 命令發出的信號
  • SIGCHLD:標識子進程中止或結束的信號
  • SIGSTOP:來自鍵盤 ( ctrl + z ) 或調試程序的中止執行信號。

信號發送主要函數有kill和raise。上面咱們知道kill函數的用法也清楚kill函數是能夠向自身發送信號和其它進程發送信號,raise與之不一樣的是隻能夠向自己發送信號。

經過raise函數向自身發送數據,使子進程暫停經過測試以下: raise.c

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
  pid_t pid;
  int ret;
  if((pid=fork())<0)
  {
   printf("Fork error\n");
   exit(1);
  }
  //子進程
  if(pid==0)
  {
   //在子進程中使用raise()函數發出SIGSTOP信號,使子進程暫停
   printf("I am child pid:%d.I am waiting for any signal\n",getpid());
   raise(SIGSTOP);
   printf("I am child pid:%d.I am killed by progress:%d\n",getpid(),getppid());
   exit(0);
  }
  //父進程
  else  
  {
   sleep(2);  
   //在父進程中收集子進程發出的信號,並調用kill()函數進行相應的操做
   if((waitpid(pid,NULL,WNOHANG))==0) 
   { 
  //若pid指向的子進程沒有退出,則返回0,且父進程不阻塞,繼續執行下邊的語句
    if((ret=kill(pid,SIGKILL))==0)
    {
     printf("I am parent pid:%d.I am kill %d\n",getpid(),pid);
    }
   }
   //等待子進程退出,不然就一直阻塞
   waitpid(pid,NULL,0);
   exit(0);
  }
}

當調用raise的時候子進程就會暫停:

信號是對終端機的一種模擬,也是一種異步通訊方式。


二、信號量

主要做爲進程間,以及同一進程不一樣線程之間的同步手段。信號量是用來解決進程之間的同步與互斥問題的一種進程之間的通訊機制,包括一個稱爲信號量的變量和在該信號量下等待資源的進程等待隊列,以及對信號量進行的兩個原子操做。信號量對應於某一種資源,取一個非負的整形值。信號量的值是指當前可用的資源數量。

因爲信號量只有兩種操做,一種是等待信號,另外一種是發送信號。即P和V,它們的行爲以下:

  • P(sv):若是sv的值大於零,就給它減1;若是它的值爲零,就掛起該進程的執行。
  • V(sv):若是有其餘進程因等待sv而被掛起,就讓它恢復運行,若是沒有進程因等待sv而掛起,就給它加1。

Linux特別提供了一組信號量接口來對信號操做,它們不僅是侷限的針對二進制信號量,下面咱們來對每一個函數介紹,須要注意的是這些函數都是用來成對組的信號量值進行操做的。

2.一、semget函數

它的做用是建立一個新信號量或取得一個已有信號量。

int semget(key_t key, int nsems, int semflg); 

第一個參數是key整數型,不相關的進程能夠經過它訪問一個信號量,它表明程序可能要使用的某個資源,程序對全部信號量的訪問都是間接的,先經過調用semget函數並提供一個鍵,再由系統生成一個相應的信號標識符(semget函數的返回值),只有semget函數才直接使用信號量鍵,全部其餘的信號量函數使用由semget函數返回的信號量標識符。若是多個程序使用相同的key值,key將負責協調工做。

第二個參數是制定須要的信號數量,一般狀況下爲1。

第三個參數是一組標誌位,當想要當信號量不存在時建立一個新的信號量,能夠和值IPC_CREAT作按位或操做。設置了IPC_CREAT標誌後,即便給出的鍵是一個已有信號量的鍵,也不會產生錯誤。而IPC_CREAT | IPC_EXCL則能夠建立一個新的,惟一的信號量,若是信號量已存在,返回一個錯誤。

semget函數成功返回一個相應信號標識符(非零),失敗返回-1。

2.二、semop函數

它的做用是改變信號量的值。

int semop(int semid, struct sembuf *sops, unsigned nsops);

sops是一個指針,它指向這樣一個數組:元素用來描述對semid表明的信號量集合中第幾個信號進行怎麼樣的操做。nops規定該數組中操做的數量。

semop函數返回0表示成功,返回-1表示失敗。

2.三、semctl函數

該函數用來直接控制信號量信息。

int semctl(int semid, int semnum, int cmd, …);

semget並不會初始化每一個信號量的值,這個初始化必須經過SETVAL命令或SETALL命令調用semctl來完成。

例程:semctl.c

#include <stdio.h>
#include <linux/sem.h>
#define NUMS 10  

int get_sem_val(int sid,int semnum)//取得當前信號量
{  
  return(semctl(sid,semnum,GETVAL,0));  
}  

int main(void)
{  
  int I ;
  int sem_id;  
  int pid;  
  int ret;  
  struct sembuf sem_op;//信號集結構
  union semun sem_val;//信號量數值

  //創建信號量集,其中只有一個信號量
  sem_id = semget(IPC_PRIVATE,1,IPC_CREAT|0600);
  //IPC_PRIVATE私有,只有本用戶使用,若是爲正整數,則爲公共的;1爲信號集的數量;
  if (sem_id==-1)
  {  
    printf("create sem error!\n");  
    exit(1);      
  }  
  printf("create %d sem success!\n",sem_id);      
  //信號量初始化
  sem_val.val=1;  
  //設置信號量,0爲第一個信號量,1爲第二個信號量,...以此類推;SETVAL表示設置
   ret = semctl(sem_id,0,SETVAL,sem_val);  
  if (ret < 0){  
    printf("initlize sem error!\n");  
    exit(1);      
   }  
   //建立進程
  pid = fork();  
  if (pid < 0)
  {  
    printf("fork error!\n");  
    exit(1);             
  }  
  else if(pid == 0)
  {
      //一個子進程,使用者
      for ( i=0;i<NUMS;i++)
      {  
        sem_op.sem_num=0;  
        sem_op.sem_op=-1;  
        sem_op.sem_flg=0;  
        semop(sem_id,&sem_op,1);//操做信號量,每次-1                  
        printf("%d 使用者: %d\n",i,get_sem_val(sem_id,0));  
      }       
  }  
  else
  {
      //父進程,製造者
     for (i=0;i<NUMS;i++)
     {  
          sem_op.sem_num=0;  
          sem_op.sem_op=1;  
          sem_op.sem_flg=0;  
          semop(sem_id,&sem_op,1);//操做信號量,每次+1                   
          printf("%d 製造者: %d\n",i,get_sem_val(sem_id,0));  
     }       
 }  
 exit(0);  
}

運行結果:

信號量的出現就是保證資源在一個時刻只能有一個進程(線程),因此例子當中只有製造者在製造(+1操做)過程當中,使用者這個進程是沒法隨sem_id進行操做的。也就是說信號量是協調進程對共享資源操做的,起到了相似互斥鎖的做用,但卻比鎖擁有更強大的功能。

往期精彩

Linux進程間通訊(上)之管道、消息隊列實踐

C語言三劍客之《C陷阱與缺陷》一書精華提煉

C語言三劍客之《C專家編程》一書精華提煉

【爲宏正名】99%人都不知道的"##"裏用法

【Linux系統編程】可重入和不可重入函數

以爲本次分享的文章對您有幫助,隨手點[在看]並轉發分享,也是對個人支持。

本文分享自微信公衆號 - 嵌入式雲IOT技術圈(gh_d6ff851b4069)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索