Linux程序間通訊(四)
一、什麼是訊號量:
訊號量的本質是一種資料操作鎖,它本身不具有資料交換的功能,而是通過控制其他的通訊資源(檔案,外部裝置)來實現程序間通訊,它本身只是一種外部資源的標識。訊號量在此過程中負責資料操作的互斥、同步等功能。
當請求一個使用訊號量來表示的資源時,程序需要先讀取訊號量的值來判斷資源是否可用。大於0,資源可以請求,等於0,無資源可用,程序會進入睡眠狀態直到資源可用。
當程序不再使用一個訊號量控制的共享資源時,訊號量的值+1,對訊號量的值進行的增減操作均為原子操作,這是由於訊號量主要的作用是維護資源的互斥或多程序的同步訪問。而在訊號量的建立及初始化上,不能保證操作均為原子性。
二、為什麼要使用訊號量:
為了防止出現因多個程式同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任何時刻只能有一個執行執行緒訪問程式碼的臨界區域。臨界區域是指執行資料更新的程式碼需要獨佔式地執行。而訊號量就可以提供這樣的一種訪問機制,讓一個臨界區同一時間只有一個執行緒在訪問它,也就是說訊號量是用來調協程序對共享資源的訪問的。其中共享記憶體的使用就要用到訊號量。
三、訊號量的工作原理
由於訊號量只能進行兩種操作等待和傳送訊號,即P(sv)和V(sv),他們的行為是這樣的:
P(sv):如果sv的值大於零,就給它減1;如果它的值為零,就掛起該程序的執行⾏
V(sv):如果有其他程序因等待sv而被掛起,就讓它恢復執行,如果沒有程序因等待sv而掛起,就給它加1
舉個例子,就是兩個程序共享訊號量sv,一旦其中一個程序執行了P(sv)操作,它將得到訊號量,並可以進入臨界區,使sv減1。而第二個程序將被阻止進入臨界區,因為當它試圖執行P(sv)時,sv為0,它會被掛起以等待第一個程序離開臨界區域並執行V(sv)釋放訊號量,這時第二個程序就可以恢復執。
示例:
comm.h
1 #pragma once 2 3 #include<stdio.h> 4 #include<sys/types.h> 5 #include<sys/ipc.h> 6 #include<sys/sem.h> 7 8 #define _PATH_NAME_ "/tmp" 9 #define _PROJ_ID_ 0x6666 10 11 union semun 12 { 13 int val; 14 struct semid_ds *buf; 15 unsigned short *array; 16 struct seminfo *_buf; 17 }; 18 19 int creat_sem_set(int nums); 20 int get_sem_set(); 21 int init_sem_set(int msg_id,int which,int val); 22 int destory_sem_set(int sem_id); 23 int P(int sem_id,int num); 24 int V(int sem_id,int num);
comm.c
1 #include"comm.h"
2
3 static int comm_sem_set(int nums,int flags)
4 {
5 key_t _key=ftok(_PATH_NAME_,_PROJ_ID_);
6 if(_key<0)
7 {
8 perror("ftok");
9 return -1;
10 }
11 int sem_id=semget(_key,nums,flags);
12 if(sem_id<0)
13 {
14 perror("semget");
15 return -2;
16 }
17 return sem_id;
18 }
19 int creat_sem_set(int nums)
20 {
21 int flags=IPC_CREAT|IPC_EXCL|0666;
22 return comm_sem_set(nums,flags);
23 }
24 int get_sem_set()
25 {
26 int flags=IPC_CREAT;
27 return comm_sem_set(0,flags);
28 }
29 int destory_sem_set(int sem_id)
30 {
31 if(semctl(sem_id,0,IPC_RMID)<0)
32 {
33 perror("semctl");
34 }
35 return 0;
36 }
37 int init_sem_set(int msg_id,int which,int val)
38 {
39 union semun _un;
40 _un.val=val;
41 if(semctl(msg_id,which,SETVAL,_un)<0)
42 {
43 perror("semctl");
44 return -1;
45 }
46 return 0;
47 }
48 static int comm_op(int sem_id,int num,int op)
49 {
50 struct sembuf _sembuf;
51 _sembuf.sem_num=num;
52 _sembuf.sem_op=op;
53 _sembuf.sem_flg=0;
54 if(semop(sem_id,&_sembuf,1)<0)
55 {
56 perror("semop");
57 return -1;
58 }
59 return 0;
60 }
61 int P(int sem_id,int num)
62 {
63 int op=-1;
64 return comm_op(sem_id,num,op);
65 }
66 int V(int sem_id,int num)
67 {
68 int op=1;
69 return comm_op(sem_id,num,op);
70 }
test.c 1 #include<unistd.h>
2 #include"comm.h"
3
4 int main()
5 {
6 int sem_id=creat_sem_set(1);
7
8 init_sem_set(sem_id,0,1);
9 pid_t id=fork();
10 if(id==0)
11 {//child
12 int sem_id_child=get_sem_set();
13 while(1)
14 {
15 P(sem_id_child,0);
16 printf("A");
17 fflush(stdout);
18 usleep(rand()%300);
19 printf("A");
20 fflush(stdout);
21 usleep(rand()%12345);
22 V(sem_id_child,0);
23 }
24 }
25 else
26 {//father
27 while(1)
28 {
29 P(sem_id,0);
30 printf("B");
31 fflush(stdout);
32 usleep(rand()%300);
33 printf("B");
34 fflush(stdout);
35 usleep(rand()%12345);
36 V(sem_id,0);
37 }
38 wait(NULL);
39 destroy_sem_set(sem_id);
40 }
41 return 0;
42 }
執行結果