信號量函數(semget、semop、semctl)

信號量函數(semget、semop、semctl)及其範例

分類: Linux進程間通信 5496人閱讀 評論(2) 收藏 舉報
 

 

信號量函數由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工具與編程》

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