程序間通訊之systemV訊號量(semget semop semctl相關操作)
一、什麼是訊號量?
訊號量的本質就是計數器,記錄臨界資源的數目,用來協助程序同步互斥的訪問臨界資源。為什麼不在程序中定義一個全域性變數作為計數器呢?兩個程序間的地址空間是各自獨立的,各自有各自的虛擬記憶體空間。多程序之間不能看到各自程序中的全域性變數。(程序間的虛擬記憶體https://blog.csdn.net/jane_yao/article/details/81635979)既然多個程序都能對訊號量進行操作,那訊號量本身也是臨界資源。
因為訊號量本身也是臨界資源,所以對訊號量的增加減少操作是原子的PV操作(不可中斷的)。
二、訊號量的操作
systemV版本的訊號量申請時是以訊號量集為單位申請的 ,P操作為申請資源(資源數目減一)V操作釋放資源(資源數目加一)申請不到資源就掛起等待。
1.建立: int semget(key_t key, int nsems, int semflg)
引數1key用ftok獲取key_t ftok(const char *pathname, int proj_id)引數2所申請訊號量集中的元素的個數,最少申請一個引數3為建立IPC_CREAT | IPC_EXCL。返回值是建立的訊號集標號。
2.初始化/刪除:初始化和刪除都是用的是 int semctl(int semid, int semnum, int cmd, ...);
引數1對標號semid的訊號集操作,引數2訊號集中下標為semnum的訊號量進行設定,引數三建立時使用SETVAL刪除時就直接使用IPC_RMID,初始化時有可變引數初始化就自己定義聯合體
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) */
};SETVAL)
其中int val表示初始化時設定為幾。
3.PV操作:int semop(int semid, struct sembuf *sops, unsigned nsops);
引數1對標號semid的訊號量集進行操作,引數2訊號量集中可能有多個訊號量所以使用了系統自帶的結構體sembuf
系統自帶的sembuf的內容為
unsigned short sem_num; /* semaphore number */ //對哪個訊號量進行操作 ,訊號量在陣列中的下標
short sem_op; /* semaphore operation */ //-1為P操作1為V操作
short sem_flg; /* operation flags */ //預設設定為0
引數3訊號操作結構的數量,就是1
三、使用函式建立二元訊號量實現互斥鎖
建立兩個程序,子程序列印C父程序列印P
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/ipc.h>
5 #include <sys/sem.h>
6 #define PATH_NAME "/tmp"
7 #define PROJ_ID 0x5555
8
9 union semun {
10 int val; /* Value for SETVAL */
11 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
12 unsigned short *array; /* Array for GETALL, SETALL */
13 struct seminfo *__buf; /* Buffer for IPC_INFO
14 (Linux-specific) */
15 };
16 //初始化的flg為SETVAL,後為自己定義的聯合體,聯合體的值為要設定的值
17 void initSem(int semid, int num,int val)
18 {
19 union semun arg;
20 arg.val = val;
21 semctl(semid,num,SETVAL,arg);
22 }
23
24 static void PV(int semid,int num,int op)
25 {
26 struct sembuf _sf;
27 _sf.sem_num = num;
28 _sf.sem_op = op;
29 _sf.sem_flg = 0;
30 semop(semid,&_sf,1);
31 }
32 void P(int semid,int num)
33 {
34 PV(semid,num, -1);
35 }
36
37 void V(int semid,int num)
38 {
39 PV(semid,num, 1);
40 }
41
42 int main()
43 {
44 key_t k = ftok(PATH_NAME,PROJ_ID);
45 if(k<0)
46 {
47 printf("ftok error\n");
48 return 1;
49 }
50 int semid = semget(k,1,IPC_CREAT|IPC_EXCL|0666);
51 if(semid<0)
52 {
53 return 2;
54 }
55 sleep(5);
56 //初始化和刪除都用到semctl,引數不同
57 //初始化時可變引數列表傳結構體
58 initSem(semid,0,1);//對訊號集中下標為0的訊號進行初始化,二元訊號量所以最後一個引數為1
59 //PV操作
60 pid_t id = fork();
61 if(id == 0)
62 {
63 //child
64 while(1)
65 {
66 P(semid,0);
67 //打A時不能被幹擾,實現互斥操作
68 printf("C");
69 usleep(12345);
70 fflush(stdout);
71 printf("C ");
72 usleep(32456);
73 fflush(stdout);
74 V(semid,0);
75 }
76 }
77 else
78 {
79 while(1)
80 {
81 P(semid,0);
82 printf("P");
83 usleep(10323);
84 fflush(stdout);
85 printf("P ");
86 usleep(34252);
87 fflush(stdout);
88 V(semid,0);
89 }
90 }
91 //刪除
92 semctl(semid,0,IPC_RMID);
93 }
如果不加互斥鎖則打印出的結果為:
可以看到列印P的程序可能會被打斷,去列印C
加入互斥鎖之後結果為:
就是想要的結果啦~
四、一些注意事項
和訊息佇列、共享記憶體一樣生命週期隨核心,程序結束不能自動釋放必須使用ipcrm或者在程式末尾呼叫semctl.
POSIX訊號量和systemV訊號量作用相同,都是用於同步操作,但是POSIX可以用於執行緒間同步
為了解決程序間通訊問題引入了臨界資源,多個執行緒對臨界資源的訪問又會產生問題,為了保護臨界資源引入了同步與互斥機制,為了實現同步與互斥引入訊號量(程序中)、互斥鎖(mutex_lock執行緒中)。