1. 程式人生 > >訊號量機制

訊號量機制

轉自  http://blog.csdn.net/guoping16/article/details/6584043

訊號量函式由semget、semop、semctl三個函式組成。下面的表格列出了這三個函式的函式原型及具體說明。

1.   semget函式原型

semget(得到一個訊號量集識別符號或建立一個訊號量集物件)

所需標頭檔案

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函式說明

得到一個訊號量集識別符號或建立一個訊號量集物件並返回訊號量集識別符號

函式原型

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

函式傳入值

key

0(IPC_PRIVATE):會建立新訊號量集物件

大於0的32位整數:視引數semflg來確定操作,通常要求此值來源於ftok返回的IPC鍵值

nsems

建立訊號量集中訊號量的個數,該引數只在建立訊號量集時有效

msgflg

0:取訊號量集識別符號,若不存在則函式會報錯

IPC_CREAT:當semflg&IPC_CREAT為真時,如果核心中不存在鍵值與key相等的訊號量集,則新建一個訊號量集;如果存在這樣的訊號量集,返回此訊號量集的識別符號

IPC_CREAT|IPC_EXCL:如果核心中不存在鍵值與key相等的訊號量集,則新建一個訊息佇列;如果存在這樣的訊號量集則報錯

函式返回值

成功:返回訊號量集的識別符號

出錯:-1,錯誤原因存於error中

附加說明

上述semflg引數為模式標誌引數,使用時需要與IPC物件存取許可權(如0600)進行|運算來確定訊號量集的存取許可權

錯誤程式碼

EACCESS:沒有許可權

EEXIST:訊號量集已經存在,無法建立

EIDRM:訊號量集已經刪除

ENOENT:訊號量集不存在,同時semflg沒有設定IPC_CREAT標誌

ENOMEM:沒有足夠的記憶體建立新的訊號量集

ENOSPC:超出限制

如果用semget建立了一個新的訊號量集物件時,則semid_ds結構成員變數的值設定如下:

Ÿ        sem_otime設定為0。

Ÿ        sem_ctime設定為當前時間。

Ÿ        msg_qbytes設成系統的限制值。

Ÿ        sem_nsems設定為nsems引數的數值。

Ÿ        semflg的讀寫許可權寫入sem_perm.mode中。

Ÿ        sem_perm結構的uid和cuid成員被設定成當前程序的有效使用者ID,gid和cuid成員被設定成當前程序的有效組ID。

2.   semop函式原型

semop(完成對訊號量的P操作或V操作)

所需標頭檔案

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函式說明

對訊號量集識別符號為semid中的一個或多個訊號量進行P操作或V操作

函式原型

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

函式傳入值

semid:訊號量集識別符號

sops:指向進行操作的訊號量集結構體陣列的首地址,此結構的具體說明如下:

struct sembuf {

    short semnum; /*訊號量集合中的訊號量編號,0代表第1個訊號量*/

    short val;/*若val>0進行V操作訊號量值加val,表示程序釋放控制的資源 */

/*若val<0進行P操作訊號量值減val,若(semval-val)<0(semval為該訊號量值),則呼叫程序阻塞,直到資源可用;若設定IPC_NOWAIT不會睡眠,程序直接返回EAGAIN錯誤*/

  /*若val==0時阻塞等待訊號量為0,呼叫程序進入睡眠狀態,直到訊號值為0;若設定IPC_NOWAIT,程序不會睡眠,直接返回EAGAIN錯誤*/

    short flag;  /*0 設定訊號量的預設操作*/

/*IPC_NOWAIT設定訊號量操作不等待*/

/*SEM_UNDO 選項會讓核心記錄一個與呼叫程序相關的UNDO記錄,如果該程序崩潰,則根據這個程序的UNDO記錄自動恢復相應訊號量的計數值*/

  };

nsops:進行操作訊號量的個數,即sops結構變數的個數,需大於或等於1。最常見設定此值等於1,只完成對一個訊號量的操作

函式返回值

成功:返回訊號量集的識別符號

出錯:-1,錯誤原因存於error中

錯誤程式碼

E2BIG:一次對訊號量個數的操作超過了系統限制

EACCESS:許可權不夠

EAGAIN:使用了IPC_NOWAIT,但操作不能繼續進行

EFAULT:sops指向的地址無效

EIDRM:訊號量集已經刪除

EINTR:當睡眠時接收到其他訊號

EINVAL:訊號量集不存在,或者semid無效

ENOMEM:使用了SEM_UNDO,但無足夠的記憶體建立所需的資料結構

ERANGE:訊號量值超出範圍

sops為指向sembuf陣列,定義所要進行的操作序列。下面是訊號量操作舉例。

struct sembuf sem_get={0,-1,IPC_NOWAIT}; /*將訊號量物件中序號為0的訊號量減1*/

struct sembuf sem_get={0,1,IPC_NOWAIT};  /*將訊號量物件中序號為0的訊號量加1*/

struct sembuf sem_get={0,0,0};           /*程序被阻塞,直到對應的訊號量值為0*/

flag一般為0,若flag包含IPC_NOWAIT,則該操作為非阻塞操作。若flag包含SEM_UNDO,則當程序退出的時候會還原該程序的訊號量操作,這個標誌在某些情況下是很有用的,比如某程序做了P操作得到資源,但還沒來得及做V操作時就異常退出了,此時,其他程序就只能都阻塞在P操作上,於是造成了死鎖。若採取SEM_UNDO標誌,就可以避免因為程序異常退出而造成的死鎖。

3.   semctl函式原型

semctl (得到一個訊號量集識別符號或建立一個訊號量集物件)

所需標頭檔案

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函式說明

得到一個訊號量集識別符號或建立一個訊號量集物件並返回訊號量集識別符號

函式原型

int semctl(int semid, int semnum, int cmd, union semun arg)

函式傳入值

semid

訊號量集識別符號

semnum

訊號量集陣列上的下標,表示某一個訊號量

cmd

見下文表15-4

arg

union semun {

   short val;          /*SETVAL用的值*/

   struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds結構*/

   unsigned short* array; /*SETALL、GETALL用的陣列值*/

   struct seminfo *buf;   /*為控制IPC_INFO提供的快取*/

  } arg;

函式返回值

成功:大於或等於0,具體說明請參照表15-4

出錯:-1,錯誤原因存於error中

附加說明

semid_ds結構見上文訊號量集核心結構定義

錯誤程式碼

EACCESS:許可權不夠

EFAULT:arg指向的地址無效

EIDRM:訊號量集已經刪除

EINVAL:訊號量集不存在,或者semid無效

EPERM:程序有效使用者沒有cmd的許可權

ERANGE:訊號量值超出範圍

表15-4 semctl函式cmd形參說明表

命令

解   釋

IPC_STAT

從訊號量集上檢索semid_ds結構,並存到semun聯合體引數的成員buf的地址中

IPC_SET

設定一個訊號量集合的semid_ds結構中ipc_perm域的值,並從semun的buf中取出值

IPC_RMID

從核心中刪除訊號量集合

GETALL

從訊號量集合中獲得所有訊號量的值,並把其整數值存到semun聯合體成員的一個指標陣列中

GETNCNT

返回當前等待資源的程序個數

GETPID

返回最後一個執行系統呼叫semop()程序的PID

GETVAL

返回訊號量集合內單個訊號量的值

GETZCNT

返回當前等待100%資源利用的程序個數

SETALL

與GETALL正好相反

SETVAL

用聯合體中val成員的值設定訊號量集合中單個訊號量的值

4. 訊號量應用程式舉例

sem.c原始碼如下:

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

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 */

        };

/***對訊號量陣列semnum編號的訊號量做P操作***/

int P(int semid, int semnum)

{

        struct sembuf sops={semnum,-1, SEM_UNDO};

        return (semop(semid,&sops,1));

}

/***對訊號量陣列semnum編號的訊號量做V操作***/

int V(int semid, int semnum)

{

        struct sembuf sops={semnum,+1, SEM_UNDO};

        return (semop(semid,&sops,1));

}

int main(int argc, char **argv)

{

        int key ;

        int semid,ret;

        union semun arg;

        struct sembuf semop;

        int flag ;

        key = ftok("/tmp", 0x66 ) ;

        if ( key < 0 )

        {

            perror("ftok key error") ;

            return -1 ;

        }

        /***本程式建立了三個訊號量,實際使用時只用了一個0號訊號量***/

        semid = semget(key,3,IPC_CREAT|0600);

        if (semid == -1)

        {

                perror("create semget error");

                return ;

        }

        if ( argc == 1 )

        {

            arg.val = 1;

            /***對0號訊號量設定初始值***/

            ret =semctl(semid,0,SETVAL,arg);

            if (ret < 0 )

            {

                    perror("ctl sem error");

                    semctl(semid,0,IPC_RMID,arg);

                    return -1 ;

            }

        }

        /***取0號訊號量的值***/

        ret =semctl(semid,0,GETVAL,arg);

        printf("after semctl setval  sem[0].val =[%d]\n",ret);

        system("date") ;

        printf("P operate begin\n") ;

        flag = P(semid,0)  ;

        if ( flag )

        {

            perror("P operate error") ;

            return -1 ;

        }

        printf("P operate end\n") ;

        ret =semctl(semid,0,GETVAL,arg);

        printf("after P sem[0].val=[%d]\n",ret);

        system("date") ;

        if ( argc == 1 )

        {

            sleep(120) ;

        }

        printf("V operate begin\n") ;

    if (V(semid, 0) < 0)

        {

            perror("V operate error") ;

            return -1 ;

        }

        printf("V operate end\n") ;

        ret =semctl(semid,0,GETVAL,arg);

        printf("after V sem[0].val=%d\n",ret);

        system("date") ;

        if ( argc >1 )

        {

            semctl(semid,0,IPC_RMID,arg);

        }

        return 0 ;

}

①    編譯 gcc sem.c –o sem。

②    在一視窗執行./sem,執行結果如下:

after semctl setval  sem[0].val =[1]

2011年 01月 11日 星期二 10:08:11 CST

P operate begin

P operate end

after P sem[0].val=[0]

2011年 01月 11日 星期二 10:08:11 CST

V operate begin

V operate end

after V sem[0].val=0

2011年 01月 11日 星期二 10:10:11 CST

③    然後在另一視窗中執行./sem test1,執行結果如下:

after semctl setval  sem[0].val =[0]

2011年 01月 11日 星期二 10:08:36 CST

P operate begin

P operate end

after P sem[0].val=[0]

2011年 01月 11日 星期二 10:10:11 CST

V operate begin

V operate end

after V sem[0].val=1

2011年 01月 11日 星期二 10:10:11 CST

摘錄自《深入淺出Linux工具與程式設計》