LINUX進程信號量

 首先了解一下,信號量機概念是由荷蘭科學家Dijkstr引入,值得一提的是,它提出的Dijksrtr算法解決了最短路徑問題。
      信號量又稱爲信號燈,它是用來協調不同進程間的數據對象的,而最主要的應用是共享內存方式的進程間通信。本質上,信號量是一個計數器,它用來記錄對某個資源(如共享內存)的存取狀況,信號量是一個特殊的變量,並且只有兩個操作可以改變其值:等待(wait)與信號(signal)。
因爲在Linux與UNIX編程中,"wait"與"signal"已經具有特殊的意義了(暫不知這特殊意義是啥),所以原始概念: 
     用於等待(wait)的P(信號量變量) ; 
     用於信號(signal)的V(信號量變量) ; 
這兩字母來自等待(passeren:通過,如同臨界區前的檢測點)與信號(vrjgeven:指定或釋放,如同釋放臨界區的控制權)的荷蘭語。
P操作 負責把當前進程由運行狀態轉換爲阻塞狀態,直到另外一個進程喚醒它。

操作爲:申請一個空閒資源(把信號量減1),若成功,則退出;若失敗,則該進程被阻塞;

V操作 負責把一個被阻塞的進程喚醒,它有一個參數表,存放着等待被喚醒的進程信息。
操作爲:釋放一個被佔用的資源(把信號量加1),如果發現有被阻塞的進程,則選擇一個喚醒之。 
補充:查看共享信息的內存的命令是ipcs [-m|-s|-q] (全部的話是ipcs -a) ;查看共享信息的內存的命令是ipcs [-m|-s|-q]。
(一)系統調用函數semget()
函數原型:int semget(key_t key,int nsems,int semflg);
功能描述: 創建一個新的信號量集,或者存取一個已經存在的信號量集。
當調用semget創建一個信號量時,他的相應的semid_ds結構被初始化。ipc_perm中各個量被設置爲相應 
值: 
        sem_nsems被設置爲nsems所示的值;     
        sem_otime被設置爲0;  
        sem_ctime被設置爲當前時間
參數介紹: 
         key:所創建或打開信號量集的鍵值,鍵值是IPC_PRIVATE,該值通常爲0,創建一個僅能被進程進程給我的信號量, 鍵值不是IPC_PRIVATE,我們可以指定鍵值,例如1234;也可以一個ftok()函數來取得一個唯一的鍵值。 
         nsems:創建的信號量集中的信號量的個數,該參數只在創建信號量集時有效。 
         semflg:調用函數的操作類型,也可用於設置信號量集的訪問權限,兩者通過or表示:
                有IPC_CREAT,IPC_EXCL兩種:
IPC_CREAT如果信號量不存在,則創建一個信號量,否則獲取。
IPC_EXCL只有信號量不存在的時候,新的信號量才建立,否則就產生錯誤。
返回值說明: 
如果成功,則返回信號量集的IPC標識符,其作用與信息隊列識符一樣。 
如果失敗,則返回-1,errno被設定成以下的某個值 
EACCES:沒有訪問該信號量集的權限 
EEXIST:信號量集已經存在,無法創建 
EINVAL:參數nsems的值小於0或者大於該信號量集的限制;或者是該key關聯的信號量集已存在,並且nsems 
大於該信號量集的信號量數 
ENOENT:信號量集不存在,同時沒有使用IPC_CREAT 
ENOMEM :沒有足夠的內存創建新的信號量集 
ENOSPC:超出系統限制
每個信號量都有一些相關值:
      semval 信號量的值,一般是一個正整數,它只能通過信號量系統調用semctl函數設置,程序無法直接對它進行修改。
      sempid 最後一個對信號量進行操作的進程的pid.
      semcnt 等待信號量的值大於其當前值的進程數。
      semzcnt 等待信號量的值歸零的進程數。
(二)信號量的控制 semctl()
原型:int semctl(int semid,int semnum,int cmd,union semun ctl_arg);  
參數介紹: semid爲信號量集引用標誌符,即semget 的返回值。  
               semnum第二個參數是信號量數目;
               cmd表示調用該函數執行的操作,其取值和對應操作如下:
標準的IPC函數
(注意在頭文件<sys/sem.h>中包含semid_ds結構的定義)
IPC_STAT 把狀態信息放入ctl_arg.stat中
IPC_SET 用ctl_arg.stat中的值設置所有權/許可權
IPC_RMID 從系統中刪除信號量集合
單信號量操作
(下面這些宏與sem_num指定的信號量合semctl返回值相關)
GETVAL 返回信號量的值(也就是semval)
SETVAL 把信號量的值寫入ctl_arg.val中
GETPID 返回sempid值
GETNCNT 返回semncnt(參考上面內容)
GETZCNT 返回semzcnt(參考上面內容)
全信號量操作
GETALL 把所有信號量的semvals值寫入ctl_arg.array
SETALL 用ctl_arg.array中的值設置所有信號量的semvals
參數arg代表一個union的semun的實例。semun是在linux/sem.h中定義的:
union semun { 
int val; //執行SETVAL命令時使用 
struct semid_ds *buf; //在IPC_STAT/IPC_SET命令中使用 
unsigned short *array; //使用GETALL/SETALL命令時使用的指針 
}
聯合體中每個成員都有各自不同的類型,分別對應三種不同的semctl 功能,如果semval 是SETVAL.則使用的將是ctl_arg.val.
功能:smctl函數依據command參數會返回不同的值。它的一個重要用途是爲信號量賦初值,因爲進程無法直接對信號量的值進行修改。
(三)信號量操作semop函數
在 Linux 下,PV 操作通過調用semop函數來實現,也只有它能對PV進行操作
調用原型:int semop(int semid,struct sembuf*sops,unsign ednsops); 
返回值:0,如果成功。-1,如果失敗:errno=E2BIG(nsops大於最大的ops數目) 
EACCESS(權限不夠) 
EAGAIN(使用了IPC_NOWAIT,但操作不能繼續進行) 
EFAULT(sops指向的地址無效) 
EIDRM(信號量集已經刪除) 
EINTR(當睡眠時接收到其他信號) 
EINVAL(信號量集不存在,或者semid無效) 
ENOMEM(使用了SEM_UNDO,但無足夠的內存創建所需的數據結構) 
ERANGE(信號量值超出範圍)
參數介紹:
第一個參數semid 是信號量集合標識符,它可能是從前一次的semget調用中獲得的。
第二個參數是一個sembuf結構的數組,每個 sembuf 結構體對應一個特定信號的操作,sembuf結構在,<sys/sem.h>中定義
struct sembuf{ 
usign short sem_num;/*信號量索引*/ 
short sem_op;/*要執行的操作*/ 
short sem_flg;/*操作標誌*/ 
}
sem_num 存放集合中某一信號量的索引,如果集合中只包含一個元素,則sem_num的值只能爲0。
Sem_op取得值爲一個有符號整數,該整數實際給定了semop函數將完成的功能。包括三種情況:
      如果sem_op是負數,那麼信號量將減去它的值,對應於p()操作。這和信號量控制的資源有關。如果沒有使用IPC_NOWAIT,那麼調用進程將進入睡眠狀態,直到信號量控制的資源可以使用爲止。
      如果sem_op是正數,則信號量加上它的值。對應於v()操作。這也就是進程釋放信號量控制的資源。
      最後,如果sem_op是0,那麼調用進程將調用sleep(),直到信號量的值爲0。這在一個進程等待完全空閒的資源時使用。

sem_flag是用來告訴系統當進程退出時自動還原操作,它維護着一個整型變量semadj(信號燈的計數器),可設置爲 IPC_NOWAIT 或 SEM_UNDO 兩種狀態。只有將 sem_flg 指定爲 SEM_UNDO 標誌後,semadj (所指定信號量針對調用進程的調整值)纔會更新,即減去減去sem_num的值。 此外,如果此操作指定SEM_UNDO,系統更新過程中會撤消此信號燈的計數(semadj)。此操作可以隨時進行---它永遠不會強制等待的過程。調用進程必須有改變信號量集的權限。

實驗代碼:

pv.h pv.c文件介紹:可以封裝成一個塊

pv.h

#ifndef __PV_H__
#define __PV_H__
#include <sys/types.h>
int I(int semnums,int value);
int I_2(key_t key,int semnums,int value);
void P(int semid,int semnum,int value);
void V(int semid,int semnum,int value);
void D(int semid);

#endif

pv.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "pv.h"
union semun{
   int val;
   struct semid_ds *buf;
   unsigned short *array;
};
int I(int semnums,int value)
{
   int semid;
   if((semid=semget(IPC_PRIVATE,semnums,IPC_CREAT|IPC_EXCL|0777))<0)//創建一個信號量
   {
     perror("semget error");
     return -1;
   }
   union semun un;
   unsigned short *array=(unsigned short*)calloc(semnums,sizeof(unsigned short));
   int i=0;
   for(i=0;i<semnums;i++)
   {
       array[i]=value;
   }
   un.array=array;
   if(semctl(semid,0,SETALL,un)<0)//SETALL 用ctl_arg.array中的值設置所有信號量的semvals
   {
     perror("semctl error");
     return -1;
   }
   free(array);
   return semid;
}
int I_2(key_t key,int semnums,int value)
{
   int semid;
   if((semid=semget(key,semnums,IPC_CREAT|IPC_EXCL))<0)
   {
     perror("semget error");
     return -1;
   }
   union semun un;
   unsigned short *array=(unsigned short*)calloc(semnums,sizeof(unsigned short));
   int i=0;
   for(i=0;i<semnums;i++)
   {
       array[i]=value;
   }
   un.array=array;
   if(semctl(semid,0,SETALL,un)<0)
   {
     perror("semctl error");
     return -1;
   }
   free(array);
   return semid;
}
void P(int semid,int semnum,int value)
{
   assert(value>=0);
   struct sembuf buf[]={semnum,-value,SEM_UNDO};
   if(semop(semid,buf,sizeof(buf)/sizeof(struct sembuf))<0)
   {
      perror("P:semop error");
   }
   else 
   {
     printf("semop P operation success\n");
   }
}
void V(int semid,int semnum,int value)
{
   assert(value>=0);
   struct sembuf buf[]={semnum,value,SEM_UNDO};
   if(semop(semid,buf,sizeof(buf)/sizeof(struct sembuf))<0)
   {
      perror("V:semop error");
   }
   else
   {
     printf("semop P operation success\n");
   }
}
void D(int semid)
{
   if(semctl(semid,0,IPC_RMID,NULL)<0)
   {
     perror("D operation error");
   }
   else 
   {
     printf("D operation success\n");
   }

}

可以調用命令:gcc -o pv.o -c pv.c 鏈接成一個.o文件

測試文件:test_sem.c

#include "pv.h"
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
void handlesem(key_t skey,int semid);
int main(void)
{
  key_t semkey=0x200;
  int i;
  int semid;
  semid=I_2(semkey,1,1);
  for (i=0;i<3;i++)
  {
    if (fork()==0)           //父進程負責產生3個子進程
      handlesem(semkey,semid);  //子進程中才執行handlesem,做完後就exit。
  }
  D(semid);
  exit(0);
}
void handlesem(key_t skey,int semid)
{
  pid_t pid=getpid(); 
  printf("進程 %d 在臨界資源區之前 \n",pid);
  P(semid,0,1);                                      //進程進入臨界資源區,信號量減少1
  printf("進程 %d 在使用臨界資源時,停止10s \n",pid);
  /*in real life do something interesting */
  sleep(10);
  printf("進程 %d 退出臨界區後 \n",pid);
  V(semid,0,1);                                //進程退出臨界資源區,信號量加1
  printf("進程 %d 完全退出\n",pid);
  exit(0);

}

執行結果:

進程 30562 在臨界資源區之前 
semop P operation success
進程 30562 在使用臨界資源時,停止10s 
進程 30561 在臨界資源區之前 
進程 30560 在臨界資源區之前 
進程 30562 退出臨界區後 
semop V operation success
semop P operation success
進程 30561 在使用臨界資源時,停止10s 
進程 30562 完全退出
進程 30561 退出臨界區後 
semop V operation success
semop P operation success
進程 30560 在使用臨界資源時,停止10s 
進程 30561 完全退出
進程 30560 退出臨界區後 
semop V operation success
進程 30560 完全退出
D operation success
 

線程信號量和進程信號量的區別:

信號量分爲有名與無名 
信號量在進程是以有名信號量進行通信的,在線程是以無名信號進行通信的,因爲線程LINUX還沒有實現進程間的通信,所以在sem_init的第二個參數要爲0,而且在多線程間的同步是可以通過有名信號量也可通過無名信號,但是一般情況線程的同步是無名信號量,無名信號量比較簡單,有名信號量必須由LINUX 內核管理,由內核結構struct ipc_ids 存儲,是隨內核持續,系統關閉,信號量則刪除,當然也可以顯示刪除,通過系統調用刪除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章