我會用幾篇博客總結一下在Linux中進程之間通信的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇博客中都打出來 進程之間通信的方式 管道 消息隊列 信號 信號量 共用存儲區 套接字(socket) 進程間通信(四)—共用存儲區傳送門:http://www.cnblogs.com/lenomire ...
我會用幾篇博客總結一下在Linux中進程之間通信的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇博客中都打出來
進程之間通信的方式
- 管道
- 消息隊列
- 信號
- 信號量
- 共用存儲區
- 套接字(socket)
進程間通信(四)—共用存儲區傳送門:http://www.cnblogs.com/lenomirei/p/5651995.html
進程間通信(二)—消息隊列傳送門:http://www.cnblogs.com/lenomirei/p/5642575.html
進程間通信(一)—管道傳送門:http://www.cnblogs.com/lenomirei/p/5636339.html
第三篇來了!前兩篇訪問量很多,真的是很感謝了
這次記下信號量的相關操作函數和方法,和以前一樣會在博文的最後把測試代碼貼出來!
學過操作系統這本書的話應該對信號量這個名詞不會感到陌生,同時信號和信號量是不同的!
信號量多用於進程間的同步與互斥,簡單的說一下同步和互斥的意思
同步:處理競爭就是同步,安排進程執行的先後順序就是同步,每個進程都有一定的個先後執行順序
互斥:互斥訪問不可共用的臨界資源,同時會引發兩個新的控制問題(互斥可以說是特殊的同步)
競爭:當併發進程競爭使用同一個資源的時候,我們就稱為競爭進程
簡單說一下信號量的工作機制(因為真的很簡單),可以直接理解成計數器(當然其實加鎖的時候肯定不能這麼簡單,不只只是信號量了),信號量會有初值(>0),每當有進程申請使用信號量,通過一個P操作來對信號量進行-1操作,當計數器減到0的時候就說明沒有資源了,其他進程要想訪問就必須等待(具體怎麼等還有說法,比如忙等待或者睡眠),當該進程執行完這段工作(我們稱之為臨界區)之後,就會執行V操作來對信號量進行+1操作。
臨界區:不是個簡單的區域!是加鎖區間的代碼!
臨界資源:只能被一個進程同時使用(不可以多個進程共用),要用到互斥
我們可以說信號量也是進程間通信的一種方式,比如互斥鎖的簡單實現就是信號量,一個進程使用互斥鎖,並通知(通信)其他想要該互斥鎖的進程,阻止他們的訪問和使用(老子正在用!@_@!)
其實信號量的操作函數和之前幾個都是類似的,都是套路
- 信號量的創建
一定會覺得熟悉的!和消息隊列十分類似
- 函數原型:int semget(key_t key, int nsems, int semflg);
- 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
- 參數解析
- key_t這個就不詳細解釋了,在消息隊列篇就講過了
- nsems這個參數是指創造出幾個信號量,準確的來說,semget這個函數創建出來一個信號量集(就是包含好多信號量的那種,可以簡單的理解為信號量數組),這就是說創建有幾個信號量的信號量集,可以選擇只創建一個
- semflg還是一樣的,用到兩個參數就夠了IPC_CREAT 和 IPC_EXCL怎麼用的話我在消息隊列篇講過了,就不多費口舌了
下麵會看到信號量的一個缺點
- 信號量的初始化
這說明信號量的初始化和創建是分開操作的,為什麼是分開的,因為創建的時候沒有給信號量的大小的參數,信號量是可以設置大的(不是只可以設置為1),當然設置為1就是互斥訪問了,比如典型的生產者和消費者問題,但是如果像是讀者寫者中的讀者信號量可不一樣(因為可以有多個讀者同時讀),這裡就不解釋這個模型問題了。
缺點就是,一旦初始化和創建分開之後,就會有線程安全的問題,進程A剛創建一信號量還沒有初始化,進程B便已經開始對該信號量進程P操作(哥哥!我還沒賦值初始化!)根本無法進行-1操作,會阻塞的,我都不知道咋回事(親身體驗該錯誤)。。。
廢話不多說了,函數還是套路
- 函數原型:int semctl(int semid, int semnum, int cmd, ...);
- 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
- 參數解析
- 第一個semid就不多說了標識信號量用的,semget成功時候的返回值就是它
- semnum這個就很有用了,剛纔說了申請的是一個信號量集,這個參數就是告訴函數要初始化的是第幾個信號量,該參數是從0開始的,0表示第一個信號量
- cmd這次用這個SETVAL表示設置變數
當SETVAL被設置之後,可變參數列表的第四個參數就可以用上了,這裡傳入一個神奇的聯合體(我在手冊里找到了,但是死活用不了它,還是自己寫了一個)
1 union semun {
2 int val; /* Value for SETVAL */
3 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
4 unsigned short *array; /* Array for GETALL, SETALL */
5 struct seminfo *__buf; /* Buffer for IPC_INFO
6 (Linux-specific) */
7 };
就是它!就是它!就看第一個吧,其他我也沒用了(英語我也看不太懂),val就是設置的初值啦,傳入的時候直接傳入就行了,參數就是一個變數(不用傳指針)
這個函數一會刪除我們還會遇到它,ctl就是control控制的簡寫,很多操作都有它
- 信號量操作
一開始就寫了操作的兩種方式,一種叫P操作,一種叫V操作,都是通過一個函數實現的,這個函數還關聯一個結構體
- 函數原型:int semop(int semid, struct sembuf *sops, unsigned nsops);
- 頭文件:#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
- 參數解析
- 第一個semid老朋友了
- 第二個就是那個結構體了,貼出來看
- 第三個表示你想同時操作幾個信號量(為什麼不是表示index of信號量呢,在結構體里有一起說),因為信號量集可能有很多信號量,每次一個一個操作不現實,這個參數就是提供一個同時操作的
1 struct sembuf
2 {
3 unsigned short sem_num; /* semaphore number */
4 short sem_op; /* semaphore operation */
5 short sem_flg; /* operation flags */
6 };
sem_num就是index of信號量,你想操作的信號量的下標,sem_op是個短整型,這裡給1或者-1分別表示V和P操作,而最後一個semflg手冊中提到了兩個
IPC_NOWAIT:一看就是IPC通用的,非等待,不讓忙等待只好去休眠(或者別的,反正不能迴圈等待)
SEM_UNDO:這個一看就是信號量獨有的,看英文也能看出來UNDO是取消之前做過的,什麼意思呢,不得不提到,當進程在臨界區工作(持有鎖)的時候,是不允許掛(掛掉更不好啦)起的!!!這很重要(又要加篇幅啦!@_@),因為一旦掛起或者休眠,有可能就醒不了啦!(掛起有可能等待事件)這樣不好,會讓等待的其他進程饑餓,尤其是掛掉的時候,肯定就更糟糕了,根本就無法執行V操作,等待的進程就會無限的等待下去,遞歸申請信號量也是不好的,尤其是互斥信號量的時候會造成死鎖。這時候UNDO就出來把之前做過的東西都取消掉,P操作就當沒執行過,信號量又從0變回了1,皆大歡喜。
- 信號量的刪除
semctl又來了,和消息隊列的刪除類似
- 函數原型:剛纔剛寫了~!
- 頭文件:剛纔剛寫了~!
- 參數解析:剛纔剛寫了~!
- 不一樣的只是把SETVAL為換成IPC_RMID就行了(當然後面的可變參數列表也不要了,可以看一下測試的例子)
事已至此,基本操作就說完了,廢話少說,show me the code
我的程式分為comm.h(公共頭文件) comm.c(封裝基本函數) server.c(採用fork完成父子進程間使用信號量) 一共3個文件
基本功能:模擬線程安全(簡單版本),父進程會列印"A""A"子進程會列印"B""B",因為執行順序的原因可能會交叉列印,但我不要這樣,我要讓AA和BB分別連著!完成這個功能
先看之前的版本
交叉列印不是我想要的
功能完成連續列印
comm.h
1 #include <stdio.h> 2 #include <sys/ipc.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys//sem.h> 6 #include <stdlib.h> 7 #include <errno.h> 8 9 10 #define _PATH_NAME_ "/tmp" 11 #define _PROJ_ID_ 0x6666 12 13 static int comm_create_sem(int flags,int num); 14 int create_sem(int num); 15 int get_sem(); 16 void P_sem(int sem_id,int index); 17 void V_sem(int sem_id,int index);
comm.c
1 #include "comm.h" 2 3 union semun { 4 int val; /* Value for SETVAL */ 5 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ 6 unsigned short *array; /* Array for GETALL, SETALL */ 7 struct seminfo *__buf; /* Buffer for IPC_INFO 8 (Linux-specific) */ 9 }; 10 11 12 static int comm_create_sem(int flags,int num) 13 { 14 key_t _key=ftok(_PATH_NAME_,_PROJ_ID_); 15 if(_key<0) 16 { 17 printf("%d:%s",errno,strerror(errno)); 18 } 19 int sem_id; 20 if((sem_id=semget(_key,num,flags))<0) 21 { 22 printf("semget errno,%d:%s",errno,strerror(errno)); 23 } 24 return sem_id; 25 } 26 27 int create_sem(int num) 28 { 29 int flags=IPC_CREAT | IPC_EXCL; 30 int sem_id=comm_create_sem(flags,num); 31 union semun v; 32 v.val=1; 33 if(semctl(sem_id,0,SETVAL,v)<0)//init 34 { 35 printf("init error,%d:%s",errno,strerror(errno)); 36 } 37 return sem_id; 38 } 39 40 int get_sem() 41 { 42 int flags=0; 43 return comm_create_sem(flags,0); 44 } 45 46 47 void P_sem(int sem_id,int index) 48 { 49 struct sembuf s; 50 s.sem_num=index; 51 s.sem_op=-1; 52 s.sem_flg=0; 53 if(semop(sem_id,&s,1)<0) 54 { 55 printf("op errro,%d:%s",errno,strerror(errno)); 56 } 57 } 58 59 void V_sem(int sem_id,int index) 60 { 61 struct sembuf s; 62 s.sem_num=index; 63 s.sem_op=1; 64 s.sem_flg=0; 65 if(semop(sem_id,&s,1)<0) 66 { 67 printf("op error,%d:%s",errno,strerror(errno)); 68 } 69 70 } 71 72 void destory_sem(int sem_id) 73 { 74 semctl(sem_id,0,IPC_RMID); 75 }
server.c
1 #include "comm.h" 2 3 4 5 int main() 6 { 7 int pid; 8 pid=fork(); 9 if(pid>0) 10 { 11 //father 12 int sem_id=create_sem(1); 13 while(1) 14 { 15 P_sem(sem_id,0); 16 printf("A"); 17 fflush(stdout); 18 sleep(1); 19 printf("A"); 20 fflush(stdout); 21 V_sem(sem_id,0); 22 } 23 } 24 else 25 { 26 //child 27 while(1) 28 { 29 int sem_id=get_sem(); 30 P_sem(sem_id,0); 31 printf("B"); 32 fflush(stdout); 33 sleep(1); 34 printf("B"); 35 fflush(stdout); 36 V_sem(sem_id,0); 37 } 38 } 39 40 return 0; 41 }