天天看點

程序間通信——System V IPC 之程序信号量

51.1 程序信号量

51.1.1 信号量

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

51.1.2 信号量集屬性

程式間通信——System V IPC 之程式信号量

51.1.3 建立信号量集

  

程式間通信——System V IPC 之程式信号量
  • 函數參數:
  • key:使用者指定的信号量集鍵值
  • nsems:信号量集中信号量個數
  • semflg:IPC_CREAT,IPC_EXCL 等權限組合
  • 傳回值:成功,傳回信号量集 ID,出錯,傳回 -1

51.1.4 信号量集控制

程式間通信——System V IPC 之程式信号量
程式間通信——System V IPC 之程式信号量
程式間通信——System V IPC 之程式信号量
程式間通信——System V IPC 之程式信号量
  • 函數參數:
  • 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 信号量集操作

程式間通信——System V IPC 之程式信号量
程式間通信——System V IPC 之程式信号量
  • 函數參數:
  • 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)互斥操作

程式間通信——System V IPC 之程式信号量

  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 }      

  編譯運作如下:

程式間通信——System V IPC 之程式信号量

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 }