本文內容:
1.進程通信的目的
2.介紹Linux下進程間的4種通信方式:管道,消息隊列,共享內存,信號量
ps:套接字也可以用於進程間的通信,不過是不同物理機器上的進程通信,本章討論是是同一台物理機器上的通信,套接字本章暫不討論
一.進程間通信的目的
1)數據的傳輸
2)數據的共享
3)事件的通知
4)資源的共享
5)進程的控制
二.進程間的通信方式
1.管道
概念:管道是一種兩個進程間進行單向通信的機制,因為管道傳遞數據的單向性,管道又稱之為半雙工管道
分類:匿名管道和有名管道
特點:
1)數據只能由一個進程流向另一個進程,如果要進行雙工通信,則需要建立兩個管道(一個讀管道,一個寫管道)
2)匿名管道只能用於父子進程或者兄弟進程間的通信,有名管道則不受這個限制
3)管道緩沖區的大小是有限制的
4)管道傳輸的是無格式的字節流,需要傳輸雙方事先約定傳輸格式
5)管道也是一種文件,但這個文件只存在於內存中
管道創建:
pipe()函數
int pipe(int fd[2])
1.管道兩端分別用fd[0]和fd[1]描述
2.管道兩端是固定了任務的,即一端只能用來讀,一端只能用來寫,規定fd[0]為讀端,fd[1]為寫端
樣例程序:建立一個子進程讀,父進程寫的匿名管道
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> using namespace std; #define input 0 #define output 1 int main() { int fd[2]; pid_t pid; pipe(fd); pid=fork(); if(pid<0) { cout<<"fork error"<<endl; return 0; }else if(pid==0) { cout<<"in child process"<<endl; close(fd[input]); char str[]="hello,my parents"; write(fd[output],str,strlen(str)); exit(0); }else { cout<<"in parent process"<<endl; close(fd[output]); char str[256]; int num=read(fd[input],str,sizeof(str)); str[num]='\0'; cout<<num<<" bytes,"<<str<<endl; exit(0); } return 0; }分析:要求子進程寫,父進程讀,那么關閉父進程的寫端,打開父進程讀端,關閉子進程的讀端,關閉子進程寫端,這樣一條子進程寫父進程讀的匿名管道就建立好了,然后直接用read,write函數傳遞進行讀寫就好了
值得注意的是,匿名管道是存在於內核中的,而有名管道則是以FIFO的文件形式存在於文件系統中,這樣即使是不具有親緣關系的進程,只有可以訪問文件系統,都可以進行通信
有名管道的特點:
1)可以使互不相關的兩個進程實現彼此通信
2)該命名管道可以通過路徑名來指出,並且在文件系統中是可見的,在建立了有名管道之后,兩個進程就可以把它當作普通文件一樣進行讀寫操作,使用方便
3)數據嚴格遵循FIFO先進先出原則
有名管道的創建:
int mkfifo(const char* pathname,mode_t mode)
第一個參數為路徑名,也就是其創建后的名字
第二個參數為文件權限掩碼,表示那些用戶對此文件具有讀寫權限
樣例程序:兩個毫無關系的進程通過有名管道進行通信:
進程1:讀端
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> using namespace std; #define P_FIFO "/tmp/p_fifo" #define max_v 105 int main() { char str[max_v]; if(access(P_FIFO,F_OK)==0)//管道文件存在 { execlp("rm","-f",P_FIFO,NULL);//刪除 cout<<"the FIFO file have existed,delete it"; } if(mkfifo(P_FIFO,0777)<0)//創建命名管道,fifo文件 { cout<<"create fifo file error"<<endl; return 0; } int fd=open(P_FIFO,O_RDONLY|O_NONBLOCK);//打開fifo文件 while(1) { memset(str,0,sizeof(str));//清空 int num=read(fd,str,max_v);//讀取數據 if(num==0) { cout<<"no data"<<endl; }else { str[num]='\0'; cout<<str<<endl; } sleep(1);//sleep 1s繼續讀 } close(fd);//關閉fd return 0; }進程二:寫端
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> using namespace std; #define P_FIFO "/tmp/p_fifo" #define max_v 105 int main() { int fd=open(P_FIFO,O_WRONLY|O_NONBLOCK);//打開fifo文件 char str[]="give you data"; str[strlen(str)]='\n'; write(fd,str,strlen(str));//寫入數據 sleep(1); close(fd); return 0; }分析:先運行讀端程序,然后多次運行寫端程序向命名管道中寫入數據,寫入的數據的讀端的終端中會有顯示
2.消息隊列
定義:用於運行於同一台機器上的進程通信,和管道很相似,是在一個系統的內核中用來保存消息的隊列,它在系統內核中是以消息鏈表的形式出現的
相關函數:
1)創建新消息隊列或者取得已經存在的消息隊列
int msgget(key_t key,int msgflag)
key:鍵值,一個公共的標識來標識一個通訊通道,當標識和消息隊列綁定之后,則內核可以通過改標識找到對應的那個隊列,key值可以由ftok函數生成
【函數ftok將一個已經存在的路徑和一個整數標識轉化為一個key鍵】
msgflag:
若msgflag=IPC_CREAT,若沒有消息隊列,則創建一個,若存在在返回原標識符
若msgflag=IPC_EXCL,若沒有該隊列,則返回-1.若已存在則返回0
2)向消息隊列讀取/寫入消息
讀取消息:
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,int msgflag)
寫入消息
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflag)
字段含義:
# msqid:消息隊列標識符
# msgp:指向消息緩沖區的指針,用來暫時存儲和發送消息,是一個用戶可定義的通用結構
struct msgstru { long type;//大於0 char mtext[512]; };# msgsz:消息的大小
# msgtyp:消息的形態,等於0則標識消息隊列中所有的消息都會被讀取
# msgflag:
1.等於0表示阻塞式接收/發送消息,沒有該類型的消息/消息隊列滿了,那么一直阻塞等待
2.等於IPC_NOWAIT,在發送消息時若果隊列滿了,則不阻塞,直接返回-1,在接收消息時,如果消息隊列為空,則不阻塞等待,直接返回-1
3)設置消息隊列屬性
int msgctl(int msgqid,int cmd,struct msqid_ds *buf)
# msqid:消息隊列標識符
# cmd=IPC_STAT:獲取消息隊列對應的msqid_ds(消息隊列屬性)數據結構,並且將其存放到buf指定的看見中
# cmd=IPC_SET:設置消息隊列的屬性,要設置的屬性放在buf中
# cmd=IPC_RMID:刪除消息隊列
樣例程序:使用消息隊列來進行兩個進程間的數據傳輸
接收消息端:
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/msg.h> using namespace std; #define max_v 512 struct msg_st { long type;//>0 char mtext[max_v]; }; int main() { int msgid=-1; struct msg_st data; long int msgtype=0; //建立消息隊列 msgid=msgget((key_t)1234,0666|IPC_CREAT); if(msgid==-1) { cout<<"msgget function faild with error"<<endl; exit(EXIT_FAILURE); } //從隊列中獲取消息,直到遇到end while(1) { if(msgrcv(msgid,(void*)&data,max_v,msgtype,0)==-1) { cout<<"msgrcv failed with error"<<endl; exit(EXIT_FAILURE); } //打印一下 cout<<"you get message:"<<data.mtext<<endl; if(strncmp(data.mtext,"end",3)==0) { return 0; } } //刪除消息隊列 if(msgctl(msgid,IPC_RMID,0)==-1) { cout<<"msgctl IPC_RMID faild error"<<endl; exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }寫入消息端:
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/msg.h> using namespace std; #define max_v 512 struct msg_st { long type;//>0 char mtext[max_v]; }; int main() { struct msg_st data; char buff[max_v]; int msgid=-1; //建立消息隊列,若已經存在則直接獲取 msgid=msgget((key_t)1234,0666|IPC_CREAT); if(msgid==-1) { cout<<"msgget faild with error"; exit(EXIT_FAILURE); } //向消息隊列中寫入信息,知道寫入end while(1) { cout<<"enter some text:"<<endl; //輸入信息 fgets(buff,max_v,stdin); data.type=1; strcpy(data.mtext,buff); //寫入信息 if(msgsnd(msgid,(void*)&data,max_v,0)==-1) { cout<<"msgsnd faild with error"<<endl; exit(EXIT_FAILURE); } //end跳出 if(strncmp(buff,"end",3)==0) { break; } sleep(1); } exit(EXIT_SUCCESS); }和命名管道相比,消息隊列的優勢在於:
1)消息隊列也可以獨立於發送和接收進程而存在,從而消除了在同步命名管道的打開和關閉時可能產生的困難
2)可以同時發通過發送消息來避免命名管道的同步和阻塞問題,而不需要進程自己提供同步方法
3)接收程序可以通過消息類型有選擇的接收數據,而不是像命名管道那樣只能默認接收
3.共享內存
定義:允許兩個不相關的進程訪問同一個邏輯內存
1.共享內存是兩個正在運行的進程之間傳遞和共享數據的一種非常有效的方式
2.不同進程之間共享的內存通常安排在同一段物理內存中
3.進程可以將同一段共享內存鏈接到自己的地址空間中
4.共享內存沒有提供同步機制!所以通過需要其他的機制來進行同步
1)創建共享內存:shmget函數
int shmget(key_t kry,int size,int flag)
# key:共享內存段的id
# size:共享內存的容量,單位字節
# flag:權限標志
成功則返回共享內存標識符,失敗則返回-1
2)鏈接共享內存到自身的地址空間:shmat函數
void *shmat(int shmid,void *addr,int flag)
# shmid:共享存儲標識符
# *addr和flag:決定以什么方式來確定鏈接的地址
成功則返回進程數據段所連接的實際地址
3)將共享內存從當前進程的地址空間分離:shmdt函數
int shmdt(const void *shmaddr);
# shmaddr:為shmat函數返回的地址指針
成功返回0,失敗返回-1
樣例程序:使用共享內存進行進程間通信
寫入端:
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/msg.h> #include<sys/shm.h> using namespace std; #define max_v 2048 struct shared_use_st { int flag;//0代表可寫,1代表可讀 char text[max_v]; }; int main() { //創建共享內存 int shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT); if(shmid==-1) { cout<<"shmget failed with error"<<endl; exit(EXIT_FAILURE); } void *share_memory=(void*)0; //將共享內存鏈接到本進程的地址空間 share_memory=shmat(shmid,(void *)0,0); if(share_memory==(void*)-1) { cout<<"shmat failed with error"<<endl; exit(EXIT_FAILURE); } //打印共享內存在進程數據段的地址 cout<<"memory attached at "<<(long)share_memory<<endl; struct shared_use_st *shared_stuff=(struct shared_use_st *)share_memory; shared_stuff->flag=0; char buff[max_v]; while(1) { //當前共享內存處於不可讀寫狀態,等待 if(shared_stuff->flag==1) { sleep(1); cout<<"wait for client..."<<endl; } //輸入 cout<<"exter some text"<<endl; fgets(buff,max_v,stdin); strncpy(shared_stuff->text,buff,max_v); shared_stuff->flag=1; //遇到end跳出 if(strncmp(buff,"end",3)==0) { break; } } //將共享內存從本進程地址空間分離 if(shmdt(share_memory)==-1) { cout<<"shmdt failed with error"<<endl; exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }讀取端:
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/msg.h> #include<sys/shm.h> using namespace std; #define max_v 2048 struct shared_use_st { int flag;//0代表可寫,1代表可讀 char text[max_v]; }; int main() { //創建共享內存 int shmid=shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT); if(shmid==-1) { cout<<"shmget failed with error"<<endl; exit(EXIT_FAILURE); } void *share_memory=(void*)0; //將共享內存鏈接到本進程的地址空間 share_memory=shmat(shmid,(void *)0,0); if(share_memory==(void*)-1) { cout<<"shmat failed with error"<<endl; exit(EXIT_FAILURE); } //打印共享內存在進程數據段的地址 cout<<"memory attached at "<<(long)share_memory<<endl; struct shared_use_st *shared_stuff=(struct shared_use_st *)share_memory; shared_stuff->flag=0; while(1) { //可寫 if(shared_stuff->flag) { cout<<"you wrote :"<<shared_stuff->text<<endl; sleep(1); shared_stuff->flag=0; //遇到end跳出 if(strncmp(shared_stuff->text,"end",3)==0) { break; } } } //將共享內存從本進程地址空間分離 if(shmdt(share_memory)==-1) { cout<<"shmdt failed with error"<<endl; exit(EXIT_FAILURE); } //刪除共享內存 if(shmctl(shmid,IPC_RMID,0)) { cout<<"shmctl IPC_RMID faild with error"; exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }共享內存優劣分析:
優點:
1.方便,函數接口簡單
2.數據不用進行傳送,可以之間訪問內存,加快了效率
3.對通信的進程沒有要求,不像匿名管道一樣,要求父子/兄弟進程才能進行通信
缺點:
1.沒有提供同步機制!導致我們在通信時需要通過其他機制來實現同步
四.信號量
用於多線程的信號量是POSIX信號量,而用於進程的信號量是SYSTEM_V信號量
相關函數
1)創建和打開信號量:semget函數
int semget(key_t key,int nsems,int semflag)
# key:鍵值
# nsems:創建信號量的個數,一旦創建了該信號量,就不能改變信號量的個數,只有不刪除該信號量,就可以重新調用該函數創建該鍵值的信號量
# semflag:指定該信號量的讀寫權限
成功則返回信號量的標識符,失敗則返回-1
2)改變信號量的值:semop函數
int semop(int semid,struct sembuf *sops,unsigned nsops);
# sem_id:信號量標識符
# *sops:指向存儲信號操作結構的數組指針
# nsops:信號操作結構的數量,>=1
共享內存是進程間最快的通信方式,但是共享內存的同步問題共享內存無法解決,可以采用信號量解決共享內存的同步問題
在進程訪問臨界資源之前,需要測試信號量,如果為正數,則信號量-1並且進程可以進入臨界區,若為非正數,則進程掛起放入等待隊列,直至有進程退出臨界區,釋放資源並+1信號量,此時喚醒等待隊列的進程。
樣例程序:信號量+共享內存,共享內存解決進程間的通信,而信號量解決共享內存的同步問題
讀取端:
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/msg.h> #include<sys/shm.h> #include<sys/sem.h> #include<sys/types.h> #include<sys/ipc.h> using namespace std; #define sem_key 4001 #define shm_key 5678 union semnu { int val; }; int main() { //創建共享內存 int shmid=shmget(shm_key,sizeof(int),IPC_CREAT|0666); if(shmid<0) { cout<<"create shm error"; return -1; } //將共享內存鏈接到當前進程的地址空間 void *shmptr=shmat(shmid,NULL,0); if(shmptr==(void*)-1) { cout<<"shmat error:"<<strerror(errno)<<endl; return -1; } int *data=(int*)shmptr; int semid=semget(sem_key,2,0666);//創建信號量 union semnu s; struct sembuf sbuf; struct sembuf sb[1] = {{0, 1, 0}}; while(1) { //sleep(1); cin>>*data;//往共享內存寫入數據 semop(semid, sb, 1);//信號量+1 } return 0; }讀取端:
#include <iostream> #include<pthread.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<errno.h> #include<semaphore.h> #include<sys/wait.h> #include<sys/types.h> #include<fcntl.h> #include<sys/stat.h> #include<sys/msg.h> #include<sys/shm.h> #include<sys/sem.h> #include<sys/types.h> #include<sys/ipc.h> using namespace std; #define sem_key 4001 #define shm_key 5678 union semnu { int val; }; int main() { //創建共享內存 int shmid=shmget(shm_key,sizeof(int),IPC_CREAT|0666); if(shmid<0) { cout<<"create shm error"; return -1; } //將共享內存鏈接到當前進程的地址空間 void *shmptr=shmat(shmid,NULL,0); if(shmptr==(void*)-1) { cout<<"shmat error:"<<strerror(errno)<<endl; return -1; } int *data=(int*)shmptr; int semid=semget(sem_key,2,IPC_CREAT|0666);//創建信號量 union semnu s; struct sembuf sbuf; struct sembuf sb[1] = {{0, -1, 0}}; while(1) { semop(semid, sb, 1);//信號量減1,當信號量<0則會阻塞等待寫入端寫入數據,>0則輸出數據 cout<<"the NUM="<<*data<<endl; } return 0; }分析:信號負責共享內存的同步,而共享內存負責進程間的通信!將共享內存和信號量結合的方法非常好,因為共享內存是進程間通信最快的方式,而信號量又可以很好的解決共享內存的同步問題!
寫入數據后,將信號量+1
將信號量-1,然后讀取數據
因為信號量<0的話當前進程會阻塞
總結:進程間的四種通信方式總結完畢:管道(匿名管道和命名管道),消息隊列,共享內存,信號量