51.1 程序信号量
51.1.1 信号量
- 本質上就是共享資源的數目,用來控制對共享資源的通路
- 用于程序間的互斥和同步
- 每種共享資源對應一個信号量,為了便于大量共享資源的操作引入了信号量集,可對所有信号量一次性操作。對信号量集中所有操作可以要求全部成功,也可以部分成功
- 二進制信号量(信号燈)值為 0 和 1
- 對信号量做 PV 操作2
51.1.2 信号量集屬性

51.1.3 建立信号量集
- 函數參數:
- key:使用者指定的信号量集鍵值
- nsems:信号量集中信号量個數
- semflg:IPC_CREAT,IPC_EXCL 等權限組合
- 傳回值:成功,傳回信号量集 ID,出錯,傳回 -1
51.1.4 信号量集控制
- 函數參數:
- semid:信号量集 ID
- semnum:0 表示對所有信号量操作,信号量編号從 0 開始
- cmd:控制指令,通過 cmd 參數設定對信号量集要執行的操作
- IPC_STAT:擷取信号量集的屬性 ---> buf
- IPC_SET:設定信号量集的屬性 ---> buf
- IPC_RMID:删除信号量集 ---> buf
- GETVAL:傳回信号量的值 ---> val
- SETVAL:設定 semnum 信号量的值 ---> val
- GETALL:擷取所有信号量的值 ---> arryr
- SETALL:設定所有信号量的初始值 ---> array
- arg:即 ... ,semun 聯合體變量
- val:放置擷取或設定信号量集中某個信号量的值
- buf:信号量集屬性指針
- array:放置擷取或設定信号量集中所有信号量的值
51.1.5 信号量集操作
- 函數參數:
- semid:信号集 ID
- sops:sembuf 結構體數組指針
- sem_num:信号集中信号量的編号
- sem_op:正數為 V 操作,負數為 P 操作,0 可用于對共享資源是否已用完的測試
- sem_flg:SEM_UNDO 辨別,表示在程序結束時,相應的操作将被取消。如果設定了該标志,那麼在程序沒有釋放共享資源就退出時,核心将代為釋放
- nsops:第二個參數中結構體數組的長度
- 傳回值:成功傳回 0;出錯傳回 -1
- 其他說明:
- 用于信号量集中信号量的加和減操作(PV 操作)
- 可用于程序間的互斥或同步
51.2 信号量例子
51.2.1 PV 操作
(1)PV子產品
sem_pv.h
1 #ifndef INCLUDE_SEM_PV_H_
2 #define INCLUDE_SEM_PV_H_
3
4 #include <sys/sem.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <assert.h>
8 #include <malloc.h>
9
10 union semun {
11 int val;
12 struct semid_ds *buf;
13 unsigned short *array;
14 };
15
16 /** 初始化 semnums 個信号燈/信号量值(value) */
17 extern int sem_I(int semnums, int value);
18
19 /** 對信号量集(semid)中的信号燈(semnum)作 P() */
20 extern void sem_P(int semid, int semnum, int value);
21
22 /** 對信号集(semid) 中的信号燈(semnum)作V(value)操作 */
23 extern void sem_V(int semid, int semnum, int value);
24
25 /** 銷毀信号量集(semid) */
26 extern void sem_D(int semid);
27
28 #endif /* INCLUDE_SEM_PV_H_ */
sem_pv.c
1 #include "sem_pv.h"
2
3 /** 初始化 semnums 個信号燈/信号量值(value) */
4 int sem_I(int semnums, int value)
5 {
6 /** 建立信号量集 */
7 int semid;
8 /** 建立信号量集 */
9 semid = semget(IPC_PRIVATE, semnums, IPC_CREAT | IPC_EXCL | 0777);
10 if(semid < 0){
11 return -1;
12 }
13
14 union semun un;
15 unsigned short *array = (unsigned short *)calloc(semnums, sizeof(unsigned short));
16 int i;
17 for(i = 0; i < semnums; i++){
18 array[i] = value;
19 }
20 un.array = array;
21
22 /**
23 * 初始化信号量集中所有信号燈的初值
24 * 0: 表示要初始化所有的信号燈
25 */
26 if(semctl(semid, 0, SETALL, un) < 0){
27 perror("semctl error");
28 return -1;
29 }
30 free(array);
31 return semid;
32 }
33
34 /** 對信号量集(semid)中的信号燈(semnum)作 P() */
35 void sem_P(int semid, int semnum, int value)
36 {
37 assert(value >= 0);
38
39 /** 定義 sembuf 類型的結構體數組,放置若幹個結構體變量,對應要操作的信号量、P或V操作 */
40 struct sembuf ops[] = {{semnum, -value, SEM_UNDO}};
41 if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < 0){
42 perror("semop error");
43 }
44 }
45
46 /** 對信号集(semid) 中的信号燈(semnum)作V(value)操作 */
47 void sem_V(int semid, int semnum, int value)
48 {
49 assert(value >= 0);
50
51 /** 定義 sembuf 類型的結構體數組,放置若幹個結構體變量,對應要操作的信号量、P或V操作 */
52 struct sembuf ops[] = {{semnum, value, SEM_UNDO}};
53 if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < 0){
54 perror("semop error");
55 }
56 }
57
58 /** 銷毀信号量集(semid) */
59 void sem_D(int semid)
60 {
61 if(semctl(semid, 0, IPC_RMID, NULL) < 0){
62 perror("semctl error");
63 }
64 }
編譯:
gcc -o obj/sem_pv.o -Iinclude -c src/sem_pv.c
(2)互斥操作
atm_account.h
1 #ifndef INCLUDE_ATM_ACCOUNT_H_
2 #define INCLUDE_ATM_ACCOUNT_H_
3
4 #include <malloc.h>
5 #include <assert.h>
6 #include <string.h>
7
8
9 typedef struct {
10 int code;
11 double balance;
12 int semid; ///< 在共享資源上綁定一個信号量集
13 }atm_account;
14
15 /** 取款 */
16 extern double atm_account_withdraw(atm_account *a, double amt);
17
18 /** 存款 */
19 extern double atm_account_deposit(atm_account *a, double amt);
20
21 /** 檢視賬戶餘額度 */
22 extern double amt_account_balanceGet(atm_account *a);
23
24 #endif /* INCLUDE_ATM_ACCOUNT_H_ */
atm_account.c
1 #include "sem_pv.h"
2 #include "atm_account.h"
3
4 /** 取款 */
5 double atm_account_withdraw(atm_account *a, double amt)
6 {
7 assert(a != NULL);
8
9 /** 對信号量集 semid 中的0号信号量/信号燈作 P(1) 操作 */
10 sem_P(a->semid, 0, 1);
11 if(amt < 0 || amt > a->balance){
12 /** 對信号量集 semid 中的0号信号量/信号燈作 V(1) 操作 */
13 sem_V(a->semid, 0, 1);
14 return 0.0;
15 }
16
17 double balance = a->balance;
18 sleep(1);
19 balance -= amt;
20 a->balance = balance;
21 /** 對信号量集 semid 中的0号信号量/信号燈作 V(1) 操作 */
22 sem_V(a->semid, 0, 1);
23 return amt;
24 }
25
26 /** 存款 */
27 double atm_account_deposit(atm_account *a, double amt)
28 {
29 assert(a != NULL);
30
31 /** 對信号量集 semid 中的0号信号量/信号燈作 P(1) 操作 */
32 sem_P(a->semid, 0, 1);
33 if(amt < 0){
34 /** 對信号量集 semid 中的0号信号量/信号燈作 V(1) 操作 */
35 sem_V(a->semid, 0, 1);
36 return 0.0;
37 }
38 double balance = a->balance;
39 sleep(1);
40 balance += amt;
41 a->balance = balance;
42 /** 對信号量集 semid 中的0号信号量/信号燈作 V(1) 操作 */
43 sem_V(a->semid, 0, 1);
44
45 return amt;
46 }
47
48 /** 檢視賬戶餘額度 */
49 double amt_account_balanceGet(atm_account *a)
50 {
51 assert(a != NULL);
52 /** 對信号量集 semid 中的0号信号量/信号燈作 P(1) 操作 */
53 sem_P(a->semid, 0, 1);
54 double balance = a->balance;
55 /** 對信号量集 semid 中的0号信号量/信号燈作 V(1) 操作 */
56 sem_V(a->semid, 0, 1);
57 return balance;
58 }
測試代碼:atm_account_test.c
1 #include "atm_account.h"
2 #include "sem_pv.h"
3 #include <unistd.h>
4 #include <sys/shm.h>
5 #include <sys/wait.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <string.h>
9
10 int main(void)
11 {
12 /** 在共享記憶體中建立銀行賬戶 */
13 int shmid;
14 if((shmid = shmget(IPC_PRIVATE, sizeof(atm_account), IPC_CREAT | IPC_EXCL | 0777)) < 0){
15 perror("shmget error");
16 return 1;
17 }
18
19 /** 進行共享記憶體映射(a 為映射的位址) */
20 atm_account *a = (atm_account *)shmat(shmid, 0, 0);
21 if(a == (atm_account *)-1){
22 perror("shmat error");
23 return 1;
24 }
25 a->code = 123456789;
26 a->balance = 10000;
27
28 /** 建立信号量集并初始化(1 個信号量/信号燈,初值為 1) */
29 a->semid = sem_I(1, 1);
30 if(a->semid < 0){
31 perror("sem_I(1, 1) error");
32 return 1;
33 }
34 printf("balance: %f\n", a->balance);
35
36 pid_t pid;
37 if((pid = fork()) < 0){
38 perror("fork error");
39 return 1;
40 }
41 else if(pid > 0){
42 /** 父程序執行取款操作 */
43 double amt = atm_account_withdraw(a, 10000);
44 printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code);
45 wait(0);
46
47 /** 對共享記憶體的操作要在解除映射之前 */
48 printf("balance: %f\n", a->balance);
49
50 sem_D(a->semid); ///< 銷毀信号量集
51 shmdt(a); ///< 解除共享記憶體的映射
52 shmctl(shmid, IPC_RMID, NULL);///< 釋放共享記憶體
53 }
54 else {
55 /** 子程序進行取款操作 */
56 double amt = atm_account_withdraw(a, 10000);
57 printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code);
58
59 shmdt(a); ///< 解除共享記憶體的映射
60 }
61
62 return 0;
63 }
編譯運作如下:
51.2.2 PV操作--讀者寫者案例
目的:利用程序信号量的 PV操作實作程序間的同步問題
共享記憶體中讀寫資料(讀者和寫者問題)
1 #include <sys/shm.h>
2 #include <sys/sem.h>
3 #include <sys/wait.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <assert.h>
9
10 /** 讀者和寫者的共享資源 */
11 typedef struct {
12 int val;
13 int semid;
14 }Storage;
15
16 void init(Storage *s)
17 {
18 assert(s != NULL);
19
20 /** 建立信号量集(包含 2 個信号量) */
21 if((s->semid = semget(IPC_PRIVATE, 2, IPC_CREAT | IPC_EXCL | 0777)) < 0){
22 perror("semget error");
23 exit(1);
24 }
25
26 /** 對信号量集中的所有信号量初始化 */
27 union semun{
28 int val;
29 struct semid_ds *ds;
30 unsigned short *array;
31 };
32 union semun un;
33 /** 2 個信号量的初值設定為 0 */
34 unsigned short array[2] = {0, 0};
35 un.array = array;
36 if(semctl(s->semid, 0, SETALL, un) < 0){
37 perror("semctl error");
38 exit(1);
39 }
40 }
41
42 void destroy(Storage *s)
43 {
44 assert(s != NULL);
45 if(semctl(s->semid, 0, IPC_RMID, NULL) < 0){
46 perror("semctl error");
47 exit(1);
48 }
49 }
50
51 void writer(Storage *s, int val)
52 {
53 /** 寫入資料到 Storage */
54 s->val = val;
55 printf("%d write %d\n", getpid(), val);
56
57 /** 設定信号量 0 号作 V(1) 操作 */
58 struct sembuf ops_v[1] = {{0, 1, SEM_UNDO}};
59 /** 設定信号量 1 号作 P(1) 操作 */
60 struct sembuf ops_p[1] = {{1, -1, SEM_UNDO}};
61
62 /** V(s1) */
63 if(semop(s->semid, ops_v, 1) < 0){
64 perror("semop error");
65 }
66
67 /** P(s2) */
68 if(semop(s->semid, ops_p, 1) < 0){
69 perror("semop error");
70 }
71 }
72
73 void reader(Storage *s)
74 {
75 assert(s != NULL);
76
77 /** 設定信号量 0 号作 P(1) 操作 */
78 struct sembuf ops_p[1] = {{0, -1, SEM_UNDO}};
79 /** 設定信号量 1 号作 V(1) 操作 */
80 struct sembuf ops_v[1] = {{1, 1, SEM_UNDO}};
81 /** P(s1) */
82 if(semop(s->semid, ops_p, 1) < 0){
83 perror("semop error");
84 }
85 /** 從 Storage 中讀取資料 */
86 printf("%d read %d\n", getpid(), s->val);
87 /** V(s2) */
88 if(semop(s->semid, ops_v, 1) < 0){
89 perror("semop error");
90 }
91 }
92
93 int main(void)
94 {
95 /** 将共享資源 Storage 建立在共享記憶體中 */
96 int shmid;
97 if((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | 0777)) < 0){
98 perror("shmget error");
99 exit(1);
100 }
101
102 /** 父程序進行共享記憶體映射 */
103 Storage *s = (Storage *)shmat(shmid, 0, 0);
104 if(s == (Storage *)-1){
105 perror("shmat error");
106 exit(1);
107 }
108
109 /** 建立信号量并初始化 */
110 init(s);
111
112 pid_t pid;
113 pid = fork();
114 if(pid < 0){
115 perror("fork error");
116 exit(1);
117 }
118 else if(pid > 0){
119 int i = 1;
120 for(;i <= 20; i++){
121 writer(s, i);
122 }
123 wait(0);
124 destroy(s);
125 shmdt(s);
126 shmctl(shmid, IPC_RMID, NULL);
127 }
128 else{
129 int i = 1;
130 for(;i <= 20; i++){
131 reader(s);
132 }
133 shmdt(s);
134 }
135 }