linux處理序間通訊——號誌

來源:互聯網
上載者:User

原文自:https://www.ibm.com/developerworks/cn/linux/l-ipc/part4/

 

簡介: 號誌與其他處理序間通訊方式不大相同,它主要提供對進程間共用資源存取控制機制。相當於記憶體中的標誌,進程可以根據它判定是否能夠訪問某些共用資源,同時,進程也可以修改該標誌。除了用於存取控制外,還可用於進程同步。

本文的標籤:  linux環境處理序間通訊, linux進程, 號誌, 核心, 編碼

 

 

一、號誌概述

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

  • 二值號誌:最簡單的號誌形式,號誌的值只能取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 valueint 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 */shortsem_op;/* semaphore operation */shortsem_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: 請參考附錄2struct 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 0owner's gid is 0creater's uid is 0creater's gid is 0the 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 resourceremove 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;};

參考資料

[1] UNIX網路編程第二卷:處理序間通訊,作者:W.Richard Stevens,譯者:楊繼張,清華大學出版社。對POSIX以及系統V號誌都有闡述,對Linux環境下的程式開發有極大的啟發意義。

[2] linux核心原始碼情景分析(上),毛德操、胡希明著,浙江大學出版社,給出了系統V號誌相關的原始碼分析,尤其在闡述保證操作原子性方面,以及闡述undo標誌位時,討論的很深刻。

[3]GNU/Linux編程指南,第二版,Kurt Wall等著,張輝譯

[4]semget、semop、semctl手冊

關於作者

鄭彥興,男,現攻讀國防科大電腦學院網路方向博士學位。您可以通過電子郵件 mlinux@163.com和他聯絡。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.