天天看點

程序間通信方式(四)----------------信号量

信号量是3個XSI IPC機制中的最後一個個。如想了解XSI IPC可點選XSI IPC

信号量與已經介紹過的IPC機構(無名管道、有名管道、消息隊列、共享記憶體)不同。它是一個計數器,用于多程序對共享資料對象的通路。

為了擷取共享資源,程序需要進行下列操作:

(1)測試控制該資源的信号量。

(2)若此信号量的值為正,則程序可以使用該資源。程序将信号量的值減1,表示它使用了一個資源機關。

(3)若此信号量的值為0,則程序進入休眠狀态,直至信号量的值大于0.程序被喚醒後,他傳回至第(1)步。

當程序不再使用由一個信号量控制的共享資源時,該信号量的值增1.如果有程序正在休眠等待此信号量,則喚醒他們。

為了正确地實作信号量,信号量值的測試及減1操作應當是原子操作。為此,信号量通常是在核心中實作的。

常用信号量被稱為二進制信号量或雙态信号量。它控制單個資源,初始值為1.但是一般而言,信号量的初值可以是任一正值,該值說明有多少個共享資源機關可供使用。

但是,XSI的信号量與此相比要複雜的多。三種特性造成了這種并非必要的複雜性:

(1)信号量并非是單個非負值,而必須将此信号量定義為含有一個或多個信号量值的集合。當建立一個信号量時,要指定該集合中信号量值的數量。

(2)建立信号量(semget)與對其賦初值(semctl)分開。這是一個緻命的弱點,因為不能原子地建立一個信号量集合,并且對該集合中的各個信号量值賦初值。

(3)即使沒有程序正在使用各種形式的XSI IPC,它們仍然是存在的。有些程式在終止時并沒有釋放已經配置設定給它的信号量,是以我們不得不為這種程式擔心。

核心為每個信号量集合設定了一個semid_ds結構:

struct semid_ds{
struct ipc_perm     sem_perm;
unsigned short      sem_nsems;
time_t              sem_otime;
time_t              sem_ctime;
.
.
.};
           

每個信号量由一個無名結構表示,它至少包含下列成員:

struct{
unsigned short semval;
pid_t          sempid;
unsigned short semncnt;
unsigned short semzcnt;
.
.
.};
           

下表列出了影響信号量集的系統限制:

程式間通信方式(四)----------------信号量

建立或擷取信号量

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

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

key:和信号燈集關聯的key值,可以用宏定義IPC_PRIVATE,也可以用ftok()函數

nsems:  信号燈集中包含的信号燈數目

flag:信号燈集的通路權限

函數傳回值

          成功:     信号量ID
          出錯:     -1
           

當建立一個新的信号量集合時,對semid_ds結構的下列成員賦初值:

按XSI IPC的規則對ipc_perm結構賦初值。該結構中的mode被設定為flag中的相應權限位。

sem_otime被設定為0

sem_ctime設定為目前時間

sem_nsems設定為nsems

nsems是該集合中的信号量數。如果是建立新集合(一般是在伺服器程序中),則必須指定nsems。如果引用一個現存的集合(一個客戶程序),則将nsems指定為0。

操作信号量(賦初值、銷毀)

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

int semctl ( int semid, int semnum,  int cmd,/*…union semun arg(不是位址) */);

semid:    信号量ID

semnum: 要修改的信号量編号

cmd:
           GETVAL:擷取信号燈的值

           SETVAL:設定信号燈的值

           IPC_RMID:從系統中删除信号燈集合

函數傳回值
              成功:0
              出錯:-1
           

依賴于所請求的指令,第四個參數是可選的,如果使用該參數,則其類型是semun,它是多個特定指令參數的聯合(union)

union{
int                    val;
struct    semid_ds    *buf;
unsigned  short       *array;
 };
           

注意,這是一個聯合,而非指向聯合的指針。

cmd參數指定下列10種指令中的一種,在semid指定的信号量集合上執行此指令。其中有5條指令是針對一個特定的信号量值的,它們用semnum指定該型号量集合中的一個成員。semnum值在0~nsems-1之間(包括0和nsems-1)。

IPC_STAT    對此集合取semid_ds結構,并存放在arg.buf指向的結構中。

IPC_SET      按由arg.buf指向結構中的值設定與此集合相關結構中的下列三個字段值:

                     sem_perm.uid、sem_perm.gid、sem_perm.mode。

                     此指令隻能由下列兩種程序執行:

                     一種是其有效使用者ID等于sem_perm.cuid或sem_perm.uid的程序

                      另一種是具有超級使用者權限的程序

IPC_RMID    從系統中删除該信号量集合。這種删除是立即發生的。仍然在使用此信号量集合的其他程序在它們下次試圖對此信号量集合進行操作時,将出錯傳回EIDRM。此指令隻能由下列兩種程序執行:

                                                                    一種是其有效使用者ID等于sem_perm.cuid或sem_perm.uid的程序

                                                                     另一種是具有超級使用者權限的程序

GETVAL                  傳回成員semnum的semval值

SETVAL                   設定成員semnum的semval值。該值由arg.val指定。

GETPID                  傳回成員semnum的sempid的值

GETNCNT               傳回成員semnum的semncnt的值

GETZCNT                傳回成員semnum的semzcnt的值

GETALL                   取該集合中所有信号量的值,并将他們存放在由arg.array指定的數組中

SETALL                     按arg.array指向的數組中的值,設定該集合中所有信号量的值

對于除GETALL以外的所有GET指令,semctl函數都傳回相應的值。其他指令的傳回值為0。

信号量的操作

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

int semop ( int semid, struct sembuf  semoparray[],  size_t  nops);

semid:         信号燈量ID

 參數semoparray是一個指針,指向一個信号量操作函數,信号量操作由sembuf結構表示:

struct sembuf {

   unsigned short  sem_num;  // 要操作的信号燈的編号

   short           sem_op;   //  0 :  等待,直到信号燈的值變成0
 
                             // 1 :  釋放資源,V操作

                             //   -1 :  配置設定資源,P操作                    

   short  sem_flg;    // 0,  IPC_NOWAIT,  SEM_UNDO
};

nops:  要操作的信号量的個數

傳回值:                       成功傳回   0
                              出錯傳回   -1
           

對集合中每個成員的操作由相應的sem_op值規定。此值可以為負值、0或正值。

下面将講述信号量的undo标志。此标志對應于相應sem_flg成員的SEM_UNDO位。

(1)最容易處理的情況是sem_op為正。這對應于程序釋放占用的資源數。sem_op值加到信号量的值上。如果指定了undo标志,則也從該程序的此信号量調整值中減去sem_op。

(2)若sem_op為負,則表示要擷取由該信号量控制的資源。

如果該信号量的值大于或等于sem_op的絕對值(具有所需的資源),則從信号量值中減去sem_op的絕對值。這保證信号量的結果值大于或等于0。如果指定了undo标志,則sem_op的絕對值也加到該程序的此信号量調整值上。

如果信号量值小于sem_op的絕對值(資源不能滿足要求),則:

                    (a)若指定了IPC_NOWAIT,則semop出錯傳回EAGAIN。

                    (b)若未指定IPC_NOWAIT,則該信号量的semncnt值增1(因為調用程序将進入休眠狀态),然後調用程序被挂起直至下列事件之一發生:

                    (i)此信号量變成大于或等于sem_op的絕對(即某個程序已經釋放了某些資源)。此信号量的semncnt值減1(因為等待結束),并且從信号量值中減去sem_op的絕對值。如果指定了undo标志,則sem_op的絕對值也加到該程序的此信号量的調整值上。

                   (ii)從系統中删除此信号量。在這種情況下,函數出錯則傳回EIDRM。

                   (iii)程序捕捉到一個信号,并從信号處理程式傳回。在這種情況下,此信号量的semncnt值減1(因為調用程序不再等待),并且函數出錯傳回EINTR。

(3)若sem_op為0,這表示調用程序希望等待到該信号量值變為0。

如果信号量值目前為0,則函數立即傳回。

如果信号量值非0,則:

        (a)若指定了IPC_NOWAIT,則出錯傳回EAGAIN。

        (b)若未指定IPC_NOWAIT,則該信号量的semzcnt值增1(因為調用程序将進入休眠狀态),然後調用程序被挂起直至下列事件之一發生:

                   (i)此信号量值變為0.此信号量的semzcnt值減1(因為調用程序已結束等待)

                   (ii)從系統中删除了此信号量。在此情況下,函數出錯傳回EIDRM。

                   (iii)程序捕捉到了一個信号,并且從信号處理程式傳回。在此情況下此信号量的semzcnt值減1(因為調用程序1不再等待),并且函數出錯傳回EINTR。

semop函數具有原子性,它或者執行數組中的所有操作,或者什麼也不做。

exit 時信号量調整

正如前面提到的,如果在程序終止時,它占用了經由信号量配置設定的資源,那麼就會成為一個問題。無論如何,隻要為信号量操作指定了SEM_UNDO标志,然後配置設定資源(sem_op值小于0),那麼核心就會記住對于該信号量,配置設定給調用程序多少資源(sem_op的絕對值)。當該程序終止時,無論自願或者不自願,核心都将檢驗該程序是否還有尚未處理的信号量調整值,如果有,則按調整值對相應量值進行處理。

如果用帶SETVAL或SETALL指令的semctl設定一信号量的值,則在所有程序中,對于該信号的調整值都設定為0。

繼續閱讀