1. 程式人生 > >程序間通訊筆記(7)—SystemV訊號量

程序間通訊筆記(7)—SystemV訊號量

1.概述

SystemV訊號量並不如Posix訊號量那樣“好用”,但相比之下它的年代更加久遠,但是SystemV使用的卻更加廣泛(尤其是在老系統中)。在學習Posix訊號量的時候,已經大概清楚了二值訊號量計數訊號量是什麼東西。在接觸SystemV訊號量之後,這裡有一個新的概念叫做:計數訊號量集。其實就是把訊號量放入陣列中,不過都用一些特別的結構封裝。

2. systemV訊號量程式設計

函式介面就比較少了,書上介紹了三個:semgetsemctlsemop

2.1 semget函式

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h> int semget(key_t key, int nsems, int semflg);

第一個引數key通過ftok根據特定path獲取
第二個引數表示訊號量集中訊號量數(可以理解為陣列的大小)
第三個引數標誌位比如:senflg=O_CREAT|O_EXCL|0644

systemV訊號隨核心持續的,semget呼叫成功後,在核心會維護一個資訊結構(可以理解為訊號量集的表頭,或者連結串列的頭節點),裡面包含了一些資訊:

 struct semid_ds {
    struct ipc_perm sem_perm;  //例如0644,0600,也有一些巨集特別指定,不過還是數字好記
time_t sem_otime; time_t sem_ctime; unsigned long sem_nsems; struct sem * sem_base;//這一項在man page中沒有明確表示 //但man page提到訊號量集中有這樣的結構 };

struct sem表示封裝的訊號量結構

struct sem
{
    unsigned short  semval;   /* semaphore value */
    unsigned short  semzcnt;  /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */ pid_t sempid; /* ID of process that did last op */ };

根據這兩個結構體,在核心中某個特定訊號量集可以圖解為:

這裡寫圖片描述

2.2 semop函式

使用semget開啟一個訊號量後,可以對其中一個或多個訊號量操作使用semop函式來執行。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);

對於struct sembuf這個結構體來說,其結構定義如下:

struct sembuf{
    unsigned short sem_num;  /* semaphore number */
    short          sem_op;   /* semaphore operation */
    short          sem_flg;  /* operation flags */
};

sem_num指定特定訊號量的操作。
sem_op的值分為3類:
a.sem_op > 0:將值新增到semval上,對應與釋放某個資源。
b.sem_op = 0:希望等待到semval值變為0,如果已經是0,則立即返回,否則semzcnt+1,併線程阻塞。
c.sem_op < 0:希望等待到semval值變為大於或等於|sem_op|。這對應分配資源。如果已經滿足條件,則semval減去sem_op的絕對值,否則semncnt+1並且執行緒投入睡眠。

semop函式通過命令執行了訊號量操作。

2.3 semctl函式

對一個訊號量執行各種控制操作。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);

semnum指定訊號量集中的某個成員,類似於陣列下標(0,1,2…直到nsems-1)。

下面列出了所有CMD對應的巨集。
semnum值僅僅用於前5個命令。

命令 作用
GETVAL 返回semval
SETVAL 把semval設定為指定值
GETPID 返回sempid
GETNCNT 返回semncnt
GETZCNT 返回semzcnt
GETALL 返回所有semval值,由array指標返回
SETALL 設定所有semval
IPC_RMID 刪除指定id訊號量集
IPC_SET 設定uid,gid和mode
IPC_STAT 返回semid_ds結構

而對於第四個引數來說,它是如下union型別

 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 (Linux-specific) */
};

從註釋中可以看見,有些成員僅僅針對某些命令,這也正是為什麼這裡用Union而不用Struct,可以節省空間,因為假設當前命令跟某個成員沒關的時候,struct依然為這個成員分配空間。

3. systemV程式示例

使用semget建立訊號量集,建立之後可以在linux終端使用ipcs來檢視相應資訊。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#define SEM_R    0400   //使用者(屬主)讀
#define SEM_A    0200   //使用者(屬主)寫
#define SVSEM_MODE (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6)

int main(int argc,char *argv[])
{
    int   c,oflag,semid,nsems;
    oflag = SVSEM_MODE | IPC_CREAT;   //設定建立模式
    //根據命令列引數e判斷是否制定了IPC_EXCL模式
    while((c = getopt(argc,argv,"e"))!= -1)   
    {
        switch(c)
        {
            case 'e':
                oflag |= IPC_EXCL;
                break;
        }
    }
    //判斷命令列引數是否合法
    if (optind != argc -2)
    {
        printf("usage: semcreate [-e] <pathname> <nsems>");
        exit(0);
    }
    //獲取訊號量集合中的訊號量個數
    nsems = atoi(argv[optind+1]);
    //建立訊號量,通過ftok函式建立一個key,返回訊號量 識別符號
    semid = semget(ftok(argv[optind],0),nsems,oflag);
    exit(0);
}

使用semop對訊號量集合操作

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>

int main(int argc,char *argv[])
{
    int     c,i,flag,semid,nops;
    struct  sembuf *ptr;
    flag = 0;
        //根據命令列引數設定操作模式
    while( ( c = getopt(argc,argv,"nu")) != -1)
    {
        switch(c)
        {
            case 'n':
                flag |= IPC_NOWAIT;   //非阻塞
                break;
            case 'u':
                flag |= SEM_UNDO;   //不可恢復
                break;
        }
    }
    if(argc - optind < 2)
    {
        printf("usage: semops [-n] [-u] <pathname> operation...");
        exit(0);
    } 
    //開啟一個已經存在的訊號量集合
    if((semid = semget(ftok(argv[optind],0),0,0)) == -1)
    {
        perror("semget() error");
        exit(-1);
    }
    optind++;  //指向當前第一個訊號量的位置
    nops = argc - optind;   //訊號量個數
    ptr = calloc(nops,sizeof(struct sembuf));
    for(i=0;i<nops;++i)
    {
        ptr[i].sem_num = i;  //訊號量變換
        ptr[i].sem_op = atoi(argv[optind+i]);   //設定訊號量的值
        ptr[i].sem_flg = flag;   //設定操作模式
    }
    //對訊號量執行操作
    if(semop(semid,ptr,nops) == -1)  
    {
        perror("semop() error");
        exit(-1);
    }
    exit(0);
}

使用semctl對訊號量集合傳送命令

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/ipc.h>

//定義訊號量操作共用體結構
union semun
{
    int                val;
    struct semid_ds    *buf;
    unsigned short     *array;
};

int main(int argc,char *argv[])
{
    int semid,nsems,i;
    struct semid_ds seminfo;
    unsigned short *ptr;
    union semun arg;
    if(argc < 2)
    {
            printf("usage: semsetvalues <pathname>[values ...]");
            exit(0);
    }
    //開啟已經存在的訊號量集合
    semid = semget(ftok(argv[1],0),0,0);
    arg.buf = &seminfo;
        //獲取訊號量集的相關資訊
    semctl(semid,0,IPC_STAT,arg);
    nsems = arg.buf->sem_nsems;  //訊號量的個數
    if(argc != nsems + 2 )
    {
        printf("%s semaphores in set,%d values specified",nsems,argc-2);
        exit(0);
    }
    //分配訊號量
    ptr = calloc(nsems,sizeof(unsigned short));
    arg.array = ptr;
    //初始化訊號量的值
    for(i=0;i<nsems;i++)
        ptr[i] = atoi(argv[i+2]);
    //通過arg設定訊號量集合
    semctl(semid,0,SETALL,arg);
    exit(0);
}

4. 總結

雖然systemV只有三個函式,但是設計的結構比較多,感覺用起來不如posix方便吧。
相比POSIX訊號量,systemV訊號量由一組值組成,並且除了PV操作外,訊號量集中的每個成員有三個操作:測試值是否為0、加一個整數、減一個整數。等等

5.參考