1. 基本特點
1) 相當于計數器,用于限制多個程序對有限共享資源的通路。
2) 多個程序擷取有限共享資源的操作模式
A. 測試控制該資源的信号量;
B. 若信号量大于0,則程序可以使用該資源, 為了表示此程序已獲得該資源,需将信号量減1;
C. 若信号量等于0,則程序休眠等待該資源, 直到信号量大于0,程序被喚醒,執行步驟A;
D. 當某程序不再使用該資源時,信号量增1, 正在休眠等待該資源的其它程序将被喚醒。
3)核心維護一個semid_ds結構體
struct semid_ds {
struct ipc_perm sem_perm; // 權限資訊
time_t sem_otime; // 上次執行 semop 的時間
time_t sem_ctime; // 最後更新時間
unsigned short sem_nsems; // 在信号量集合裡的索引
};
struct ipc_perm {
key_t __key; // 鍵值
uid_t uid; // 有效屬主ID
gid_t gid; // 有效屬組ID
uid_t cuid; // 有效建立者ID
gid_t cgid; // 有效建立組ID
unsigned short mode; // 權限字
unsigned short __seq; // 序列号
};
2. 常用函數
1) 建立/擷取信号量
int semget (key_t key, int nsems, int semflg);
A. 該函數以key參數為鍵值建立一個信号量集合 (nsems參數表示集合中的信号量數 ,或擷取已有的信号量集合(nsems取0)。
B. semflg取值:
0 - 擷取,不存在即失敗。
IPC_CREAT - 建立,不存在即建立,已存在即擷取,除非...
IPC_EXCL - 排斥,已存在即失敗。
C. 成功傳回信号量集合辨別semid,失敗傳回-1。
2) 操作信号量:它的作用是改變信号量的值
int semop (int semid, struct sembuf* sops,unsigned nsops);
struct sembuf {
unsigned short sem_num; // 信号量下标
short sem_op; // 操作數
short sem_flg; // 操作标記
};
A. 該函數對semid參數所辨別的信号量集合中, 由sops參數所指向的包含nsops個元素的, 結構體數組中的每個元素,依次執行如下操作:
a) 若sem_op大于0, 則将其加到第sem_num個信号量的計數值上,以表示對資源的釋放;
b) 若sem_op小于0,則從第sem_num個信号量的計數值中減去其絕對值,以表示對資源的擷取;
c) 若第sem_num個信号量的計數值不夠減(信号量不能為負), 則此函數會阻塞,直到該信号量夠減為止,以表示對資源的等待;
d) 若sem_flg包含IPC_NOWAIT位,則當第sem_num個信号量的計數值不夠減時, 此函數不會阻塞,而是傳回-1,errno為EAGAIN,以便在等待資源的同時還可做其它處理;
e) 若sem_op等于0,則直到第sem_num個信号量的計數值為0時才傳回, 除非sem_flg包含IPC_NOWAIT位。
B. 成功傳回0,失敗傳回-1。
3) 銷毀/控制信号量
int semctl (int semid, int semnum, int cmd);
int semctl (int semid, int semnum, int cmd,union semun arg);
函數描述:semctl() 在 semid 辨別的信号量集上,或者該集合的第 semnum 個信号量上執行 cmd 指定的控制指令。(信号量集合索引起始于零。)
A. senum :信号集的索引,用來存取信号集内的某個信号
union semun {
int val; // SETVAL使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用緩存區
unsigned short *array; // GETALL,、SETALL 使用的數組
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用緩存區
};
B. cmd取值:
IPC_STAT - 擷取信号量集合的屬性,通過arg.buf輸出。
IPC_SET - 設定信号量集合的屬性,通過arg.buf輸入,僅三個屬性可設定
IPC_RMID - 立即删除信号量集合。 此時所有阻塞在對該信号量集合的,semop函數調用,都會立即傳回失敗,errno為EIDRM。
GETALL - 擷取信号量集合中每個信号量的計數值, 通過arg.array輸出。
SETALL - 設定信号量集合中每個信号量的計數值,通過arg.array輸入。
GETVAL - 擷取信号量集合中, 第semnum個信号量的計數值, 通過傳回值輸出。
SETVAL - 設定信号量集合中,第semnum個信号量的計數值, 通過arg.val輸入。
注意:隻有針對信号量集合中具體某個信号量的操作,才會使用semnum參數。針對整個信号量集合的操作,會忽略semnum參數。
C. 成功傳回值因cmd而異,失敗傳回-1。
3. 程式設計模型
這裡模拟一個簡單的借書還書的案例:
#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
int pleft (int semid) {
int val = semctl (semid, 0, GETVAL);
if (val == -1) {
perror ("semctl");
return -1;
}
printf ("還剩%d冊。\n", val);
return 0;
}
int main (void) {
printf ("建立信号量...\n");
key_t key = ftok (".", 100);
if (key == -1) {
perror ("ftok");
return -1;
}
int semid = semget (key, 1, 0644 | IPC_CREAT | IPC_EXCL);
if (semid == -1) {
perror ("semget");
return -1;
}
printf ("初始信号量...\n");
if (semctl (semid, 0, SETVAL, 5) == -1) {
perror ("semctl");
return -1;
}
int quit = 0;
while (! quit) {
printf ("--------\n");
printf ("三國演義\n");
printf ("--------\n");
printf ("[1] 借閱\n");
printf ("[2] 歸還\n");
printf ("[0] 退出\n");
printf ("--------\n");
printf ("請選擇:");
int sel = -1;
scanf ("%d", &sel);
switch (sel) {
case 0:
quit = 1;
break;
case 1: {
// printf ("請稍候...\n");
struct sembuf sops = {0, -1, /*0*/IPC_NOWAIT};
if (semop (semid, &sops, 1) == -1) {
if (errno == EAGAIN) {
printf ("暫時無書,下回再試。\n");
break;
}
else {
perror ("semop");
return -1;
}
}
printf ("恭喜恭喜,借閱成功。\n");
pleft (semid);
break;
}
case 2: {
struct sembuf sops = {0, 1, 0};
if (semop (semid, &sops, 1) == -1) {
perror ("semop");
return -1;
}
printf ("好借好還,再借不難。\n");
pleft (semid);
break;
}
default:
printf ("無效選擇!\n");
scanf ("%*[^\n]");
scanf ("%*c");
break;
}
}
printf ("銷毀信号量...\n");
if (semctl (semid, 0, IPC_RMID) == -1) {
perror ("semctl");
return -1;
}
printf ("大功告成!\n");
return 0;
}
#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
int pleft (int semid) {
int val = semctl (semid, 0, GETVAL);
if (val == -1) {
perror ("semctl");
return -1;
}
printf ("還剩%d冊。\n", val);
return 0;
}
int main (void) {
printf ("擷取信号量...\n");
key_t key = ftok (".", 100);
if (key == -1) {
perror ("ftok");
return -1;
}
int semid = semget (key, 0, 0);
if (semid == -1) {
perror ("semget");
return -1;
}
int quit = 0;
while (! quit) {
printf ("--------\n");
printf ("三國演義\n");
printf ("--------\n");
printf ("[1] 借閱\n");
printf ("[2] 歸還\n");
printf ("[0] 退出\n");
printf ("--------\n");
printf ("請選擇:");
int sel = -1;
scanf ("%d", &sel);
switch (sel) {
case 0:
quit = 1;
break;
case 1: {
// printf ("請稍候...\n");
struct sembuf sops = {0, -1, /*0*/IPC_NOWAIT};
if (semop (semid, &sops, 1) == -1) {
if (errno == EAGAIN) {
printf ("暫時無書,下回再試。\n");
break;
}
else {
perror ("semop");
return -1;
}
}
printf ("恭喜恭喜,借閱成功。\n");
pleft (semid);
break;
}
case 2: {
struct sembuf sops = {0, 1, 0};
if (semop (semid, &sops, 1) == -1) {
perror ("semop");
return -1;
}
printf ("好借好還,再借不難。\n");
pleft (semid);
break;
}
default:
printf ("無效選擇!\n");
scanf ("%*[^\n]");
scanf ("%*c");
break;
}
}
printf ("大功告成!\n");
return 0;
}
也可以結合這篇博文來一起學習 點選打開連結