程序間通訊筆記(7)—SystemV訊號量
1.概述
SystemV訊號量並不如Posix訊號量那樣“好用”,但相比之下它的年代更加久遠,但是SystemV使用的卻更加廣泛(尤其是在老系統中)。在學習Posix訊號量的時候,已經大概清楚了二值訊號量
和計數訊號量
是什麼東西。在接觸SystemV訊號量之後,這裡有一個新的概念叫做:計數訊號量集。其實就是把訊號量放入陣列中,不過都用一些特別的結構封裝。
2. systemV訊號量程式設計
函式介面就比較少了,書上介紹了三個:semget
,semctl
、semop
。
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值變為大於或等於
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、加一個整數、減一個整數。等等