與消息隊列和共享內存一樣,信號量集也有自己的數據結構:
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 表示這個信號量的資源數。