與消息隊列和共享內存一樣,信號量集也有自己的數據結構:
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned short sem_nsems; /* No. of semaphores in set */
};
同樣地,第一個條目也是共有的ipc 對象內核結 構,剩下的是私有成員。
Each semaphore in a semaphore set has the following associated values:
unsigned short semval; /* semaphore value */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
pid_t sempid; /* process that did last op */
即每一個在信號集中的信號量都有上述4個相關的變量。
1、semval :當前某信號量的資源數目
2、semzcnt:當sem_op(見 struct sembuf)為0,且semop 函數沒有設置IPC_NOWAIT 標志,且當前semval 不為0 ,此時進程會阻塞等待直到4個事件發生,具體可man 2 semop 一下,然後semzcnt 會加1,表示等待這個信號量的資源變為 0的進程數加1。
3、semncnt:當sem_op(見 struct sembuf)< 0,且semop 函數沒有設置IPC_NOWAIT 標志,且 當前semval < |sem_op| ,此時進程會阻塞等待直到4個事件發生,具體可man 2 semop 一下,然後semncnt 會加1,表 示等待這個信號量的資源增加的進程數加1。
4、當正確執行了semop 函數,則每個信號量的sempid 參數都被設置為 改變此信號量的進程pid。
以下是幾個信號量集操作函數:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
int semctl (int semid, int semnum, int cmd, ...);
int semop(int semid, struct sembuf *sops, unsigned nsops);
功 能:用來創建和訪問一個信號量集
原型int semget(key_t key, int nsems, int semflg);
參數
key: 信號集的名 字
nsems:信號集中信號量的個數
semflg: 由九個權限標志構成,它們的用法和創建文件時使用的mode模式標志是一樣 的
返回值:成功返回一個非負整數,即該信號集的標識碼;失敗返回-1
功能:用於控制信號量集
原型int semctl(int semid, int semnum, int cmd, ...);
參數
semid:由semget返回的信號集標識碼
semnum:信號集中信號 量的序號,從0開始編號
cmd:將要采取的動作(有三個可取值)
最後一個參數是 union semun,具體成員根據cmd 的 不同而不同
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) */
};
返回值:成功返回0;失敗返回-1
cmd 取值如下:
SETVAL 設置信號量集中的信號量的計數值
GETVAL 獲取信號量集中的信號量的計數值
IPC_STAT把semid_ds結 構中的數據設置為信號集的當前關聯值
IPC_SET在進程有足夠權限的前提下,把信號集的當前關聯值設置為semid_ds數據 結構中給出的值
IPC_RMID刪除信號集
功能:用來創建和訪問一個信號量集
原型int semop(int semid, struct sembuf *sops, unsigned nsops);
參數
semid:是該信號量集的標識碼,也就是semget函數的返回值
sops:是個指向 一個結構體的指針
nsops:信號量的個數
返回值:成功返回0;失敗返回-1
struct sembuf
{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
sem_num: 是信號量的編號。
sem_op:是信號量一次PV操作時加減的數值,一般只會用到兩個值,一個是“-1”,也就是P操作,等 待信號量變得可用;另一個是“+1”,也就是我們的V操作,發出信號量已經變得可用。當然+-n 和0 都是允許的。需要注 意的是只有+n 才確保將semval +n 後馬上返回,而-n 和 0 很可能是會阻塞的,見文章上面的分析,+-n 需要進程對信號 集有寫的權限,而0 只需要讀的權限。
sem_flag:的兩個取值是IPC_NOWAIT或SEM_UNDO,設為前者如果當某個信號 量的資源為0時進行P操作,此時不會阻塞等待,而是直接返回資源不可用的錯誤;設為後者,當退出進程時對信號量資源的 操作撤銷;不關心時設置為0即可。
當要對一個信號量集中的多個信號量進行操作時,sops 是結構體數組的指針, 此時nsops 不為1。此時對多個信號量的操作是作為一個單元原子操作,要麼全部執行,要麼全部不執行。
下面來封 裝一個信號量集操作函數的工具:
semtool.c
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
union semun
{
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* array for GETALL, SETALL */
/* Linux specific part: */
struct seminfo *__buf; /* buffer for IPC_INFO */
};
int sem_create(key_t key)
{
int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
if (semid == -1)
ERR_EXIT("semget");
return semid;
}
int sem_open(key_t key)
{
int semid = semget(key, 0, 0);
if (semid == -1)
ERR_EXIT("semget");
return semid;
}
int sem_p(int semid)
{
struct sembuf sb = {0, -1, /*IPC_NOWAIT*/SEM_UNDO};
int ret = semop(semid, &sb, 1);
if (ret == -1)
ERR_EXIT("semop");
return ret;
}
int sem_v(int semid)
{
struct sembuf sb = {0, 1, /*0*/SEM_UNDO};
int ret = semop(semid, &sb, 1);
if (ret == -1)
ERR_EXIT("semop");
return ret;
}
int sem_d(int semid)
{
int ret = semctl(semid, 0, IPC_RMID, 0);
if (ret == -1)
ERR_EXIT("semctl");
return ret;
}
int sem_setval(int semid, int val)
{
union semun su;
su.val = val;
int ret = semctl(semid, 0, SETVAL, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("value updated...\n");
return ret;
}
int sem_getval(int semid)
{
int ret = semctl(semid, 0, GETVAL, 0);
if (ret == -1)
ERR_EXIT("semctl");
printf("current val is %d\n", ret);
return ret;
}
int sem_getmode(int semid)
{
union semun su;
struct semid_ds sem;
su.buf = &sem;
int ret = semctl(semid, 0, IPC_STAT, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("current permissions is %o\n", su.buf->sem_perm.mode);
return ret;
}
int sem_setmode(int semid, char *mode)
{
union semun su;
struct semid_ds sem;
su.buf = &sem;
int ret = semctl(semid, 0, IPC_STAT, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("current permissions is %o\n", su.buf->sem_perm.mode);
sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);
ret = semctl(semid, 0, IPC_SET, su);
if (ret == -1)
ERR_EXIT("semctl");
printf("permissions updated...\n");
return ret;
}
void usage(void)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "semtool -c\n");
fprintf(stderr, "semtool -d\n");
fprintf(stderr, "semtool -p\n");
fprintf(stderr, "semtool -v\n");
fprintf(stderr, "semtool -s <val>\n");
fprintf(stderr, "semtool -g\n");
fprintf(stderr, "semtool -f\n");
fprintf(stderr, "semtool -m <mode>\n");
}
int main(int argc, char *argv[])
{
int opt;
opt = getopt(argc, argv, "cdpvs:gfm:");
if (opt == '?')
exit(EXIT_FAILURE);
if (opt == -1)
{
usage();
exit(EXIT_FAILURE);
}
key_t key = ftok(".", 's');
int semid;
switch (opt)
{
case 'c':
sem_create(key);
break;
case 'p':
semid = sem_open(key);
sem_p(semid);
sem_getval(semid);
break;
case 'v':
semid = sem_open(key);
sem_v(semid);
sem_getval(semid);
break;
case 'd':
semid = sem_open(key);
sem_d(semid);
break;
case 's':
semid = sem_open(key);
sem_setval(semid, atoi(optarg));
break;
case 'g':
semid = sem_open(key);
sem_getval(semid);
break;
case 'f':
semid = sem_open(key);
sem_getmode(semid);
break;
case 'm':
semid = sem_open(key);
sem_setmode(semid, argv[2]);
break;
}
return 0;
}
首先來介紹一個getopt 函數, int getopt(int argc, char * const argv[],const char *optstring);
可以解析命令行選項參數,前兩個參數由main 函數傳遞,第三個參數是一個字符串集,即解析命令行參數看是否存在這些 字符。如./semtool -s 3 則s為選項,3為選項參數,optarg 是一個全局指針變量 extern char *optarg; 通 過atoi(optarg) 可以獲取數字3。
"cdpvs:gfm:" 表示選項s 和 m 後面可接參數,我們未使用一個while 循環去解析命令行參數,即這些選項只能同時出現一個,當未使用選項時打印輸出使用方法。
根據解析到的選項來 調用不同的函數,這些函數內部都調用了原始的信號量集操作函數,參照函數解釋都不難理解。
需要注意一點是, 這裡為了只創建一個信號量集,只對這個信號量集的信號量進行操作,在sem_create 中指定了IPC_EXCL 選項,即當key 已 存在時返回錯誤,不再創建信號量集,而我們使用了ftok 函數產生一個唯一的key,傳入的參數一定,則每次產生的key 值 一樣,當第二次次執行./semtool -c ,會返回file exist 的錯誤,當然先刪除當前信號量集,再create 是可以的,此時 雖然key 還是一樣的,但返回的semid 是不同的。
且這個唯一的信號量集中只有唯一的一個信號量,即0號信號量, 我們只對這個信號量進行PV操作。
使用舉例如下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s
------ Semaphore Arrays ---- ----
key semid owner perms nsems
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool
usage:
semtool -c
semtool -d
semtool -p
semtool -v
semtool -s <val>
semtool - g
semtool -f
semtool -m <mode>
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -c
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x730135db 98304 simba 666 1
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -v
current val is 1
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -v
current val is 1
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -s 3
value updated...
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -g
current val is 3
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -p
current val is 2
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -m 600
current permissions is 666
permissions updated...
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool - d
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s
------ Semaphore Arrays ------- -
key semid owner perms nsems
因為我們在PV操作中指定了SEM_UNDO 選項,當進程退出時撤銷操作,所以連續執行兩 次V操作後信號量的資源還是為0(創建後信號量默認資源為0,不一定所有系統實現都會如此,應該顯式地初始化為0)。通 過-s 可以設置信號量的資源數。ipcs -s 輸出中的nsems 表示信號量的個數,當前只有一個;./semtool -v 輸出中的 current value 表示這個信號量的資源數。