System IPC 與Posix IPC(semaphore信號燈)


 

POSIX下IPC主要包括三種:

posix message queue

posix semaphores

posix shared memory

sysytem v IPC包括:

system v message queue

system v semaphores

system v shared memory

 

 

1.Semaphores

在POSIX IPC中,每個IPC對象是有名稱的,而且名稱是一個很重要的概念,posix ipc使用ipc的名稱作為ipc的標識。mq_open  sem_open  shm_open三個函數的第一個參數就是這個名稱,這個名稱不一定是在文件系統中存在的名稱。 要使用IPC對象,需要創建或者打開,這與文件操作類似,主要是使用mq_open、sem_open、shm_open 函數操作。在創建或者打開ipc對象時需要指定操作的mode,例如O_RONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL 等,IPC對象是有一定權限的,與文件的權限類似.

 

System v ipc中有一個重要的類型是key_t,在msget、semget、shmget函數操作中都需要利用這個類型是參數。系統中對每個ipc對象都會有一個結構體來標識:

 

 

信號燈分為兩種:
一種是簡單信號量(Posix ),另一種是用於進程間通訊的信號量集(System);

一、簡單信號量:
屬於POSIX標准的信號量;
從信號量的命名來看,信號量又可分為命名信號量和匿名(未命名)信號量;
從信號量的值來看,信號量可分為二進制信號量和計數信號量;

1、匿名信號量和命名信號量:
  匿名信號量是在內存中分配內存、進行初始化並由系統API進行管理的,它可以在多個線程之間進行資源同步,也可以在多個進程之間進行資源同步,這主要是看在初始化的時候給pshared傳遞的參數值,為0,則在線程之間同步,非0,則在進程之間同步;
  命名信號量與匿名信號量不同,它一般只用於在進程之間進行資源同步,而且是使用文件全路徑來對信號量進行命名的;命名信號量具有屬主用戶ID、組ID和保護模式等參數;命名信號量的名稱是在文件系統的命名空間中定義的;
  匿名信號量的操作函數有:sem_init、sem_destroy、sem_wait(P操作(-1))、sem_trywait、sem_post(V操作(+1))、sem_getvalue;

   sem_init創建一個信號燈,並初始化其值為value.pshared決定了信號量能否在幾個進程 間共享.由於目前Linux還沒有實現進程間共享信號燈,所以這個值只能夠取0.

   sem_destroy是用來刪除信號燈的.sem_wait調用將阻塞進程,直到信號燈的值大於0.這個函數返回的時候自動的將信號燈的值的件一.

   sem_post和sem_wait相反,是將信號燈的內容加一同時發出信號喚醒等待的進程..sem_trywait和sem_wait相同,不過不阻塞的,當信號燈的值為0的時候返回EAGAIN,

   表示以后重試.sem_getvalue得到信號燈的值.

 

#include <semaphore.h>
#include <stdio.h>
#include < string.h>
#include <stdlib.h>


#if 0
int main( int argc, char *argv[]){
    sem_t sem;
     if(- 1==sem_init(&sem, 0, 1)){
        perror( " sem_init error\n ");
    }
     int value= 0;

     while( 1){
        sem_wait(&sem);
        sleep( 2);
        sem_post(&sem);
        sem_getvalue(&sem,&value);
        printf( " %d\n ",value);
        sem_destroy(&sem);
    }

     return  0;
}
#endif


#if 1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include < string.h>
#include <pthread.h>
#include <semaphore.h>

sem_t bin_sem;
void *thread_function1( void *arg)
{
    printf( " thread_function1--------------sem_wait\n ");
    sem_wait(&bin_sem);
    printf( " sem_wait\n ");
     while ( 1)
    {
    }
}

void *thread_function2( void *arg)
{
    printf( " thread_function2--------------sem_post\n ");
    sem_post(&bin_sem);
    printf( " sem_post\n ");
     while ( 1)
    {
    }
}



int main()
{
     int res;
    pthread_t a_thread;
     void *thread_result;

    res = sem_init(&bin_sem,  00);
     if (res !=  0)
    {
        perror( " Semaphore initialization failed ");
    }
    printf( " sem_init\n ");
    res = pthread_create(&a_thread, NULL, thread_function1, NULL);
     if (res !=  0)
    {
        perror( " Thread creation failure ");
    }
    printf( " thread_function1\n ");
    sleep ( 5);
    printf( " sleep\n ");
    res = pthread_create(&a_thread, NULL, thread_function2, NULL);
     if (res !=  0)
    {
        perror( " Thread creation failure ");
    }
     while ( 1)
    {
    }
}

#endif

 

  命名信號量的操作函數有:sem_open、sem_close、sem_wait(阻塞P操作(-1)、sem_trywait、sem_post(喚醒V操作(+1))、sem_getvalue、sem_unlink(刪除系統中的信號燈);

 

        1 #include <fcntl.h>
        2 #include <sys/stat.h>
        3 #include <semaphore.h>
        4 #include <errno.h>
        5  #define ABORT() {printf("File:%s,Line:%d",__FILE__,__LINE__);abort();}
        6  #define PATH "txt"
-       7  #if 1 
|-      8  int main(){
||      9     sem_t *sem;
||     10     sem = sem_open(PATH,O_CREAT|O_EXCL, 00666, 2);
||-    11      switch(errno){
|||    12          case EINVAL:
|||    13             ABORT();            
|||    14              break;
|||    15          case EEXIST: 
|||    16             perror( " EXIST ");
|||    17              break;
|||    18          default:
|||    19              break;
|||    20     }
||     21      int value;
||     23     sem_wait(sem);
||     24     sem_getvalue(sem,&value);
||     25     printf( " current value is %d\n ",value);
||     26     sem_close(sem);
||     27 
||     28     pause();                                                                                                                           
||     29     exit( 0);
||     30 
||     31      return  0;
||     32 }
|      33  #endif

 

意: 一般發行版的linux操作系統內核並不支持有名信號量的操作,需要在編譯內核時將 其打開

 

 

2、二進制信號量和計數信號量:
  二進制信號量的值只有兩個:0和1;這個時候信號量就相當於一個互斥鎖;它表示可用資源數量只有1個,當一個調用者擁有並訪問它的時候(值為0),其它調用者就必須阻塞並等待.知道擁有者釋放這個信號量(值為1).
  計數信號量,從概念上來講,它的值是一個非負整數,表示可用資源的數量;用於協調對共享資源的訪問:在線程中操作共享資源的時候,當一個訪問者申請使用一 個單位的資源時,信號量的值減1,表示剩余的可用資源的數量減少1;當這個訪問者使用完這個共享資源並退還這個共享資源給系統的時候,信號量的值增1,表 示可用的共享資源數量多1;而申請資源和釋放資源的操作是原子操作,即:信號量的值減1和增1操作是原子操作;也就是說,有一個資源申請動作,就必須對應 有一個資源釋放動作,有一個信號量減1操作,就必須對應有一個信號量增1的動作;如果信號量的值為0,就表示沒有資源可用,那么這個時候,如果有后續的調 用者來申請可用的共享資源,那么這個調用者就會被阻塞在這個信號量上,直到有一個調用者釋放一個共享資源為止;
 
3、當信號量作為互斥鎖使用時:
  信號量:相當於是代碼周圍的衛兵,當衛兵發現共享代碼段正在被執行,則衛兵不讓后續調用者前去執行;當共享代碼段為空閑時,衛兵允許后續調用者去執行;
  互斥量:相當於一把多人共用的鑰匙,誰擁有這把鑰匙,誰就可以訪問受保護的代碼段;
  

 

 

 
二、信號量集:
它屬於System V信號量,是一個集合;
常用於進程間的通訊;System V的IPC要求用於進程間通訊的信號量必須是一個集合;它是系統內核定義的一個數據結構;
信號量集的初始化操作由shmget函數完成;

 信號燈與其他進程間通信方式不大相同,它主要提供對進程間共享資源訪問控制機制。相當於內存中的標志,進程可以根據它判定是否能夠訪問某些共享資源,同時,進程也可以修改該標志。除了用於訪問控制外,還可用於進程同步。信號燈有以下兩種類型:

  • 二值信號燈:最簡單的信號燈形式,信號燈的值只能取0或1,類似於互斥鎖。
    注:二值信號燈能夠實現互斥鎖的功能,但兩者的關注內容不同。信號燈強調共享資源,只要共享資源可用,其他進程同樣可以修改信號燈的值;互斥鎖更強調進程,占用資源的進程使用完資源后,必須由進程本身來解鎖。
  • 計算信號燈:信號燈的值可以取任意非負值(當然受內核本身的約束)。

二、Linux信號燈

linux對信號燈的支持狀況與消息隊列一樣,在red had 8.0發行版本中支持的是系統V的信號燈。因此,本文將主要介紹系統V信號燈及其相應API。在沒有聲明的情況下,以下討論中指的都是系統V信號燈。

注意,通常所說的系統V信號燈指的是計數信號燈集。

三、信號燈與內核

1、系統V信號燈是隨內核持續的,只有在內核重起或者顯示刪除一個信號燈集時,該信號燈集才會真正被刪除。因此系統中記錄信號燈的數據結構(struct ipc_ids sem_ids)位於內核中,系統中的所有信號燈都可以在結構sem_ids中找到訪問入口。

2、下圖說明了內核與信號燈是怎樣建立起聯系的:

其中:struct ipc_ids sem_ids是內核中記錄信號燈的全局數據結構;描述一個具體的信號燈及其相關信息。



其中,struct sem結構如下:

struct sem{ int semval;		// current value int sempid		// pid of last operation } 

從上圖可以看出,全局數據結構struct ipc_ids sem_ids可以訪問到struct kern_ipc_perm的第一個成員:struct kern_ipc_perm;而每個struct kern_ipc_perm能夠與具體的信號燈對應起來是因為在該結構中,有一個key_t類型成員key,而key則唯一確定一個信號燈集;同時,結構 struct kern_ipc_perm的最后一個成員sem_nsems確定了該信號燈在信號燈集中的順序,這樣內核就能夠記錄每個信號燈的信息了。 kern_ipc_perm結構參見《Linux環境進程間通信(三):消息隊列》。struct sem_array見附錄1。

四、操作信號燈

對消息隊列的操作無非有下面三種類型:

1、 打開或創建信號燈
與消息隊列的創建及打開基本相同,不再詳述。

2、 信號燈值操作
linux可以增加或減小信號燈的值,相應於對共享資源的釋放和占有。具體參見后面的semop系統調用。

3、 獲得或設置信號燈屬性:
系統中的每一個信號燈集都對應一個struct sem_array結構,該結構記錄了信號燈集的各種信息,存在於系統空間。為了設置、獲得該信號燈集的各種信息及屬性,在用戶空間有一個重要的聯合結構與之對應,即union semun。



聯合semun數據結構各成員意義參見附錄2

信號燈API

1、文件名到鍵值

#include <sys/types.h> #include <sys/ipc.h> key_t ftok (char*pathname, char proj); 

它返回與路徑pathname相對應的一個鍵值,具體用法請參考《Linux環境進程間通信(三):消息隊列》。

2、 linux特有的ipc()調用:

int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);

參數call取不同值時,對應信號燈的三個系統調用:
當call為SEMOP時,對應int semop(int semid, struct sembuf *sops, unsigned nsops)調用;
當call為SEMGET時,對應int semget(key_t key, int nsems, int semflg)調用;
當call為SEMCTL時,對應int semctl(int semid,int semnum,int cmd,union semun arg)調用;
這些調用將在后面闡述。

注:本人不主張采用系統調用ipc(),而更傾向於采用系統V或者POSIX進程間通信API。原因已在Linux環境進程間通信(三):消息隊列中給出。

3、系統V信號燈API

系統V消息隊列API只有三個,使用時需要包括幾個頭文件:

#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> 

1)int semget(key_t key, int nsems, int semflg)
參數key是一個鍵值,由ftok獲得,唯一標識一個信號燈集,用法與msgget()中的key相同;參數nsems指定打開或者新創建的信號燈集中將 包含信號燈的數目;semflg參數是一些標志位。參數key和semflg的取值,以及何時打開已有信號燈集或者創建一個新的信號燈集與 msgget()中的對應部分相同,不再祥述。
該調用返回與健值key相對應的信號燈集描述字。
調用返回:成功返回信號燈集描述字,否則返回-1。
注:如果key所代表的信號燈已經存在,且semget指定了IPC_CREAT|IPC_EXCL標志,那么即使參數nsems與原來信號燈的數目不 等,返回的也是EEXIST錯誤;如果semget只指定了IPC_CREAT標志,那么參數nsems必須與原來的值一致,在后面程序實例中還要進一步 說明。

2)int semop(int semid, struct sembuf *sops, unsigned nsops);
semid是信號燈集ID,sops指向數組的每一個sembuf結構都刻畫一個在特定信號燈上的操作。nsops為sops指向數組的大小。
sembuf結構如下:

struct sembuf { 	unsigned short  	sem_num;		/* semaphore index in array */ 	short			sem_op;		/* semaphore operation */ 	short			sem_flg;		/* operation flags */ }; 

sem_num對應信號集中的信號燈,0對應第一個信號燈。sem_flg可取IPC_NOWAIT以及SEM_UNDO兩個標志。如 果設置了SEM_UNDO標志,那么在進程結束時,相應的操作將被取消,這是比較重要的一個標志位。如果設置了該標志位,那么在進程沒有釋放共享資源就退 出時,內核將代為釋放。如果為一個信號燈設置了該標志,內核都要分配一個sem_undo結構來記錄它,為的是確保以后資源能夠安全釋放。事實上,如果進 程退出了,那么它所占用就釋放了,但信號燈值卻沒有改變,此時,信號燈值反映的已經不是資源占有的實際情況,在這種情況下,問題的解決就靠內核來完成。這 有點像僵屍進程,進程雖然退出了,資源也都釋放了,但內核進程表中仍然有它的記錄,此時就需要父進程調用waitpid來解決問題了。
sem_op的值大於0,等於0以及小於0確定了對sem_num指定的信號燈進行的三種操作。具體請參考linux相應手冊頁。
這里需要強調的是semop同時操作多個信號燈,在實際應用中,對應多種資源的申請或釋放。semop保證操作的原子性,這一點尤為重要。尤其對於多種資 源的申請來說,要么一次性獲得所有資源,要么放棄申請,要么在不占有任何資源情況下繼續等待,這樣,一方面避免了資源的浪費;另一方面,避免了進程之間由 於申請共享資源造成死鎖。
也許從實際含義上更好理解這些操作:信號燈的當前值記錄相應資源目前可用數目;sem_op>0對應相應進程要釋放sem_op數目的共享資 源;sem_op=0可以用於對共享資源是否已用完的測試;sem_op<0相當於進程要申請-sem_op個共享資源。再聯想操作的原子性,更不 難理解該系統調用何時正常返回,何時睡眠等待。
調用返回:成功返回0,否則返回-1。

3) int semctl(int semid,int semnum,int cmd,union semun arg)
該系統調用實現對信號燈的各種控制操作,參數semid指定信號燈集,參數cmd指定具體的操作類型;參數semnum指定對哪個信號燈操作,只對幾個特殊的cmd操作有意義;arg用於設置或返回信號燈信息。
該系統調用詳細信息請參見其手冊頁,這里只給出參數cmd所能指定的操作。

IPC_STAT 獲取信號燈信息,信息由arg.buf返回;
IPC_SET 設置信號燈信息,待設置信息保存在arg.buf中(在manpage中給出了可以設置哪些信息);
GETALL 返回所有信號燈的值,結果保存在arg.array中,參數sennum被忽略;
GETNCNT 返回等待semnum所代表信號燈的值增加的進程數,相當於目前有多少進程在等待semnum代表的信號燈所代表的共享資源;
GETPID 返回最后一個對semnum所代表信號燈執行semop操作的進程ID;
GETVAL 返回semnum所代表信號燈的值;
GETZCNT 返回等待semnum所代表信號燈的值變成0的進程數;
SETALL 通過arg.array更新所有信號燈的值;同時,更新與本信號集相關的semid_ds結構的sem_ctime成員;
SETVAL 設置semnum所代表信號燈的值為arg.val;

調用返回:調用失敗返回-1,成功返回與cmd相關:

Cmd return value
GETNCNT Semncnt
GETPID Sempid
GETVAL Semval
GETZCNT Semzcnt

五、信號燈的限制

1、 一次系統調用semop可同時操作的信號燈數目SEMOPM,semop中的參數nsops如果超過了這個數目,將返回E2BIG錯誤。SEMOPM的大小特定與系統,redhat 8.0為32。

2、 信號燈的最大數目:SEMVMX,當設置信號燈值超過這個限制時,會返回ERANGE錯誤。在redhat 8.0中該值為32767。

3、 系統范圍內信號燈集的最大數目SEMMNI以及系統范圍內信號燈的最大數目SEMMNS。超過這兩個限制將返回ENOSPC錯誤。redhat 8.0中該值為32000。

4、 每個信號燈集中的最大信號燈數目SEMMSL,redhat 8.0中為250。 SEMOPM以及SEMVMX是使用semop調用時應該注意的;SEMMNI以及SEMMNS是調用semget時應該注意的。SEMVMX同時也是semctl調用應該注意的。

六、競爭問題

第一個創建信號燈的進程同時也初始化信號燈,這樣,系統調用semget包含了兩個步驟:創建信號燈;初始化信號燈。由此可能導致一種 競爭狀態:第一個創建信號燈的進程在初始化信號燈時,第二個進程又調用semget,並且發現信號燈已經存在,此時,第二個進程必須具有判斷是否有進程正 在對信號燈進行初始化的能力。在參考文獻[1]中,給出了繞過這種競爭狀態的方法:當semget創建一個新的信號燈時,信號燈結構semid_ds的 sem_otime成員初始化后的值為0。因此,第二個進程在成功調用semget后,可再次以IPC_STAT命令調用semctl,等待 sem_otime變為非0值,此時可判斷該信號燈已經初始化完畢。下圖描述了競爭狀態產生及解決方法:



實際上,這種解決方法也是基於這樣一個假定:第一個創建信號燈的進程必須調用semop,這樣sem_otime才能變為非零值。另外,因為第一個進程可能不調用semop,或者semop操作需要很長時間,第二個進程可能無限期等待下去,或者等待很長時間。

七、信號燈應用實例

本實例有兩個目的:1、獲取各種信號燈信息;2、利用信號燈實現共享資源的申請和釋放。並在程序中給出了詳細注釋。


#include <linux/sem.h>
#include <stdio.h>
#include <errno.h>
#define SEM_PATH "/unix/my_sem"
#define max_tries 3 
int semid;
main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg;        // union semun: 請參考附錄2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL| 00666;
flag2=IPC_CREAT| 00666;
key=ftok(SEM_PATH, ' a ');
// error handling for ftok here;
init_ok= 0;
semid=semget(key, 1,flag1);
// create a semaphore set that only includes one semphore.
if(semid< 0)
{
  tmperrno=errno;
  perror( " semget ");
if(tmperrno==EEXIST)
// errno is undefined after a successful library call( including perror call) 
// so it is saved  in tmperrno.
    {
    semid=semget(key, 1,flag2);
// flag2 只包含了IPC_CREAT標志, 參數nsems(這里為1)必須與原來的信號燈數目一致
    arg.buf=&sem_info;
     for(i= 0; i<max_tries; i++)
    {
       if(semctl(semid,  0, IPC_STAT, arg)==- 1)
      {  perror( " semctl error "); i=max_tries;}
       else
      { 
         if(arg.buf->sem_otime!= 0){ i=max_tries;  init_ok= 1;}
         else   sleep( 1);  
      }
    }
     if(!init_ok)
   //  do some initializing, here we assume that the first process that creates the sem
  
//   will finish initialize the sem and run semop in max_tries*1 seconds. else it will  
  
//  not run semop any more.
    {
      arg.val= 1;
       if(semctl(semid, 0,SETVAL,arg)==- 1) perror( " semctl setval error ");
    } 
  }
   else
  {perror( " semget error, process exit ");  exit();  }
}
else  // semid>=0; do some initializing   
{
  arg.val= 1;
   if(semctl(semid, 0,SETVAL,arg)==- 1)
    perror( " semctl setval error ");
}
// get some information about the semaphore and the limit of semaphore in redhat8.0
  arg.buf=&sem_info;
   if(semctl(semid,  0, IPC_STAT, arg)==- 1)
    perror( " semctl IPC STAT ");    
  printf( " owner's uid is %d\n ",   arg.buf->sem_perm.uid);
  printf( " owner's gid is %d\n ",   arg.buf->sem_perm.gid);
  printf( " creater's uid is %d\n ",   arg.buf->sem_perm.cuid);
  printf( " creater's gid is %d\n ",   arg.buf->sem_perm.cgid);
  arg.__buf=&sem_info2;
   if(semctl(semid, 0,IPC_INFO,arg)==- 1)
    perror( " semctl IPC_INFO ");
  printf( " the number of entries in semaphore map is %d \n ",  arg.__buf->semmap);
  printf( " max number of semaphore identifiers is %d \n ",    arg.__buf->semmni);
  printf( " mas number of semaphores in system is %d \n ",   arg.__buf->semmns);
  printf( " the number of undo structures system wide is %d \n ",  arg.__buf->semmnu);
  printf( " max number of semaphores per semid is %d \n ",   arg.__buf->semmsl);
  printf( " max number of ops per semop call is %d \n ",  arg.__buf->semopm);
  printf( " max number of undo entries per process is %d \n ",  arg.__buf->semume);
  printf( " the sizeof of struct sem_undo is %d \n ",  arg.__buf->semusz);
  printf( " the maximum semaphore value is %d \n ",  arg.__buf->semvmx);
  
// now ask for available resource:  
  askfor_res.sem_num= 0;
  askfor_res.sem_op=- 1;
  askfor_res.sem_flg=SEM_UNDO;    
    
     if(semop(semid,&askfor_res, 1)==- 1) // ask for resource
      perror( " semop error ");
  
  sleep( 3); 
   // do some handling on the sharing resource here, just sleep on it 3 seconds
  printf( " now free the resource\n ");  
  
// now free resource  
  free_res.sem_num= 0;
  free_res.sem_op= 1;
  free_res.sem_flg=SEM_UNDO;
   if(semop(semid,&free_res, 1)==- 1) // free the resource.
     if(errno==EIDRM)
      printf( " the semaphore set was removed\n ");
// you can comment out the codes below to compile a different version:      
   if(semctl(semid,  0, IPC_RMID)==- 1)
    perror( " semctl IPC_RMID ");
   else printf( " remove sem ok\n ");
}

注:讀者可以嘗試一下注釋掉初始化步驟,進程在運行時會出現何種情況(進程在申請資源時會睡眠),同時可以像程序結尾給出的注釋那樣,把該程序編譯成兩個不同版本。下面是本程序的運行結果(操作系統redhat8.0):

owner ' s uid is 0
owner ' s gid is 0
creater ' s uid is 0
creater ' s gid is 0
the number of entries  in semaphore map  is  32000 
max number of semaphore identifiers  is  128 
mas number of semaphores  in system  is  32000 
the number of undo structures system wide  is  32000 
max number of semaphores per semid  is  250 
max number of ops per semop call  is  32 
max number of undo entries per process  is  32 
the  sizeof of  struct sem_undo  is  20 
the maximum semaphore value  is  32767 
now free the resource
remove sem ok

Summary:信號燈與其它進程間通信方式有所不同,它主要用於進程間同步。通常所說的系統V信號燈實際上是一個信號燈的集合,可用 於多種共享資源的進程間同步。每個信號燈都有一個值,可以用來表示當前該信號燈代表的共享資源可用(available)數量,如果一個進程要申請共享資 源,那么就從信號燈值中減去要申請的數目,如果當前沒有足夠的可用資源,進程可以睡眠等待,也可以立即返回。當進程要申請多種共享資源時,linux可以 保證操作的原子性,即要么申請到所有的共享資源,要么放棄所有資源,這樣能夠保證多個進程不會造成互鎖。Linux對信號燈有各種各樣的限制,程序中給出 了輸出結果。另外,如果讀者想對信號燈作進一步的理解,建議閱讀sem.h源代碼,該文件不長,但給出了信號燈相關的重要數據結構。

附錄1: struct sem_array如下:

/*系統中的每個信號燈集對應一個sem_array 結構 */ struct sem_array {   struct kern_ipc_perm  sem_perm;    /* permissions .. see ipc.h */   time_t      sem_otime;      /* last semop time */   time_t      sem_ctime;      /* last change time */   struct sem    *sem_base;      /* ptr to first semaphore in array */   struct sem_queue  *sem_pending;    /* pending operations to be processed */   struct sem_queue  **sem_pending_last;   /* last pending operation */   struct sem_undo    *undo;      /* undo requests on this array */   unsigned long    sem_nsems;    /* no. of semaphores in array */ }; 

其中,sem_queue結構如下:

/* 系統中每個因為信號燈而睡眠的進程,都對應一個sem_queue結構*/  struct sem_queue {   struct sem_queue *  next;     /* next entry in the queue */   struct sem_queue **  prev;    /* previous entry in the queue, *(q->prev) == q */   struct task_struct*  sleeper;   /* this process */   struct sem_undo *  undo;     /* undo structure */   int   pid;             /* process id of requesting process */   int   status;           /* completion status of operation */   struct sem_array *  sma;       /* semaphore array for operations */   int  id;               /* internal sem id */   struct sembuf *  sops;       /* array of pending operations */   int  nsops;             /* number of operations */   int  alter;             /* operation will alter semaphore */ }; 

附錄2:union semun是系統調用semctl中的重要參數:

union semun { 	int val;					/* value for SETVAL */ 	struct semid_ds *buf;		/* buffer for IPC_STAT & IPC_SET */ 	unsigned short *array;		/* array for GETALL & SETALL */ 	struct seminfo *__buf;		/* buffer for IPC_INFO */   //test!! 	void *__pad; }; struct  seminfo { 	int semmap; 	int semmni; 	int semmns; 	int semmnu; 	int semmsl; 	int semopm; 	int semume; 	int semusz; 	int semvmx; 	int semaem; }; 
 
/*
 * SemServer.c
 *
 *  Created on: Sep 11, 2013
 *      Author: zsf
 
*/

#include<sys/types.h>
#include<linux/sem.h>
#include<stdlib.h>

#define MAX_RESOURCE 5
int main( void)

{
    key_t key;
     int semid;
     struct sembuf sbuf = {  0, - 1, IPC_NOWAIT };
    union semun semopts;
     // 創建SEM
     if ((key = ftok( " . "' s ')) == - 1) {
        perror( " ftok error!\n ");
        exit(- 1);
    }
     // 根據key_t 來獲取信號集,信號集中有1個信號燈
     if ((semid = semget(key,  1, IPC_CREAT |  0666)) == - 1) {
        perror( " semget error!\n ");
        exit( 1);
    }
     // 從信號集中取得操作第0個信號燈或是根據信號集(simid)中取得信號燈
    semopts.val = MAX_RESOURCE;
     if (semctl(semid,  0, SETVAL, semopts) == - 1) {
        perror( " semctl error!\n ");
        exit( 1);
    }

     while ( 1) {
         if (semop(semid, &sbuf,  1) == - 1) {
            perror( " semop error!\n ");
            exit( 1);
        }
        sleep( 3);
    }
    exit( 0);
}  
/*
 * SemClient.c
 *
 *  Created on: Sep 11, 2013
 *      Author: zsf
 
*/

#include<sys/types.h>
#include<linux/sem.h>
#include<stdlib.h>
#include<stdio.h>
int main( void) {
    key_t key;
     int semid, semval;
    union semun semopts;
     if ((key = ftok( " . "' s ')) == - 1){
        perror( " ftok error!\n ");
        exit( 1);
    }
     if ((semid = semget(key,  1, IPC_CREAT |  0666)) == - 1) {
        perror( " semget error!\n ");
        exit( 1);
    }
     while ( 1) {
         if ((semval = semctl(semid,  0, GETVAL,  0)) == - 1) {
            perror( " semctl error!\n ");
            exit( 1);
        }
         if (semval >  0) {
            printf( " Still %d resources can be used\n ", semval);
        }  else {
            printf( " No more resources can be used!\n ");
             break;
        }
        sleep( 3);
    }
    exit( 0);
}

 

 

#include<sys/types.h>
#include<linux/sem.h>
#include<stdlib.h>
#include<stdio.h>

#define PATH "./"


#define ABORT() {printf("File:%s,Line:%d",__FILE__,__LINE__);abort();}



int main(int argc,char *argv[]){
    key_t key;
    int proj_id;

    key = ftok(PATH,'a');
    if(key ==-1){
        ABORT();
        perror("ftok error");
    }

    int sem_id;
    sem_id = semget(key,1,IPC_CREAT|00666);
    if(sem_id ==-1){
        ABORT();
        perror("semget error");
    }
    union semun va;
    va.val = 5;

    struct sembuf semout,semin;
    semout.sem_flg = SEM_UNDO;
    semout.sem_num = 0;
    semout.sem_op = -1;

    semin.sem_op = 1;
    semin.sem_flg = SEM_UNDO;
    semin.sem_num = 0;

    semctl(sem_id,0,SETVAL,va);
    while(1){
        semop(sem_id,&semout,1);
        
        int value = semctl(sem_id,0,GETVAL);
        printf("current value is %d\n",value);
        if(!value){
            break;
        }
    }
}

 

對於POSIX信號量,你可以有命名的信號量,例如,信號量有一個文件關聯它們, 對於最后三個函數,被用來創建,關閉和刪除這樣一個命名的信號量。
而sem_init()和sem_destroy()僅僅供非命名信號量使用。
他們是有關信號量的兩組程序設計接口函數。POSIX信號量來源於POSIX技術規范的實時 擴展方案(POSIX Realtime Extension),常用於線程;system v信號量,常用於進程的同步。
這兩者非常相近,但它們使用的函數調用各不相同。前一種的頭文件為semaphore.h,函數調用為sem_init(),sem_wait(),sem_post(),sem_destory()等等。后一種頭文件為<sys/sem.h>,函數調用為semctl(),semget(),semop()等函數。 更詳細地請看 man sem_overview
總結:
System V的信號量一般用於進程同步, 且是內核持續的, api為
semget
semctl
semop
Posix的有名信號量一般用於進程同步, 有名信號量是內核持續的. 有名信號量的api為
sem_open
sem_close
sem_unlink

Posix的無名信號量一般用於線程同步, 無名信號量是進程持續的, 無名信號量的api為

sem_init

sem_destroy

 

 

 

 注意:

基於內存的信號燈是由sem_init初始化的。sem參數指向必須由應用程序分配的sem_t變量。如果shared為0,那么待初始化的信號燈是在同 一進程的各個線程共享的,否則該信號燈是在進程間共享的。當shared為零時,該信號燈必須存放在即將使用它的所有進程都能訪問的某種類型的共享內存 中。跟sem_open一樣,value參數是該信號燈的初始值。
   使用完一個基於內存的信號燈后,我們調用sem_destroy關閉它。
除了sem_open和sem_close外,其它的poisx有名信號燈函數都可以用於基於內存的信號燈。

注意:posix基於內存的信號燈和posix有名信號燈有一些區別,我們必須注意到這些。
1.sem_open不需要類型與shared的參數,有名信號燈總是可以在不同進程間共享的。
2.sem_init不使用任何類似於O_CREAT標志的東西,也就是說,sem_init總是初始化信號燈的值。因此,對於一個給定的信號燈,我們必須小心保證只調用一次sem_init。
3.sem_open返回一個指向某個sem_t變量的指針,該變量由函數本身分配並初始化。但sem_init的第一個參數是一個指向某個sem_t變量的指針,該變量由調用者分配,然后由sem_init函數初始化。
4.posix有名信號燈是通過內核持續的,一個進程創建一個信號燈,另外的進程可以通過該信號燈的外部名(創建信號燈使用的文件名)來訪問它。 posix基於內存的信號燈的持續性卻是不定的,如果基於內存的信號燈是由單個進程內的各個線程共享的,那么該信號燈就是隨進程持續的,當該進程終止時它 也會消失。如果某個基於內存的信號燈是在不同進程間同步的,該信號燈必須存放在共享內存區中,這要只要該共享內存區存在,該信號燈就存在。
5.基於內存的信號燈應用於線程很麻煩(待會你會知道為什么),而有名信號燈卻很方便,基於內存的信號燈比較適合應用於一個進程的多個線程。

 

void print(pid_t);
sem_t *sem;  /* 定義Posix有名信號燈 */
int val;  /* 定義信號燈當前值 */

int main( int argc, char *argv[])
{
int n= 0;

sem=sem_open(argv[ 1],O_CREAT, 0644, 3);  /* 打開一個信號燈 */
sem_getvalue(sem,&val);  /* 查看信號燈的值 */
printf(“The value have %d\n”,val);

while(n++循環創建5個子進程,使它們同步運行*/
{
if(fork()== 0
{
       sem_wait(sem);  /* 申請信號燈 */
       print(getpid());  /* 調用共享代碼段 */
       sleep( 1); 
       sem_post(sem);  /* 釋放信號燈 */
       printf(“I’m finished,my pid  is %d\n”,getpid());
        return  0
}
wait();  /* 等待所有子進程結束 */
return  0;
}

void print(pid_t pid)
{
printf(“I  get it,my pid  is %d\n”,pid);
sem_getvalue(sem,&val);
printf(“Now the value have %d\n”,val);
}

 

問題在於sem信號燈不在共享內存區中。fork出來的子進程通常不共享父進程的內存空間。子進程是在父進程內存空間的拷貝上啟動的,它跟共享內存不是一回事。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM