行程間通信目的
1、資料傳輸:一個行程需要將它的資料發送給另一個行程,
2、資源共享:多個行程之間共享同樣的資源,
3、通知事件:一個行程需要向另一個或一組行程發送訊息,通知它(它們)發生了某種事件(如行程終止時要通知父行程),
4、行程控制:有些行程希望完全控制另一個行程的執行(如Debug行程),此時控制行程希望能夠攔截另一個行程的所有陷入和例外,并能夠及時知道它的狀態改變,
管道
什么是管道
管道是Unix中最古老的行程間通信的形式,
我們把從一個行程連接到另一個行程的一個資料流稱為一個“管道”,
匿名管道
主要用于父行程與子行程之間,或者兩個兄弟行程之間,在linux系統中可以通過系統呼叫建立起一個單向的通信管道,且這種關系只能由父行程來建立,
站在檔案描述符角度–深入理解管道

場景一
當實際在進行讀取時,讀取條件不滿足時(管道為空),讀端會發生阻塞,
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main(){
int pipefd[2]={0};
pipe(pipefd);
pid_t id=fork();
if(id==0){
close(pipefd[0]);
const char *msg="I am child!!";
while(1){
write(pipefd[1],msg,strlen(msg));
sleep(5);
}
}else{
close(pipefd[1]);
char buf[64];
while(1){
ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
if(s>0){
buf[s]=0;
printf("father get message : %s\n",buf);
}
printf("等待中!!\n");
}
}
return 0;
}
運行結果:

場景二
當實際在進行寫入時,寫入條件不滿足(管道寫滿),寫端就要被阻塞,
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main(){
int pipefd[2]={0};
pipe(pipefd);
pid_t id=fork();
if(id==0){
close(pipefd[0]);
const char *msg="I am child.....\n";
int count=0;
while(1){
write(pipefd[1],msg,strlen(msg));
printf("CHILD : %d\n",count++);
}
}else{
close(pipefd[1]);
char buf[64];
while(1){
ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
if(s>0){
buf[s]=0;
printf("father get message : %s\n",buf);
sleep(1);
}
}
}
return 0;
}
運行結果:

場景三
如果寫端不寫入并且關閉檔案描述符,讀端在讀取完資料后會讀到檔案結尾,read回傳0
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main(){
int pipefd[2]={0};
pipe(pipefd);
pid_t id=fork();
if(id==0){
close(pipefd[0]);
const char *msg="I am child.....\n";
int count=0;
while(1){
write(pipefd[1],msg,strlen(msg));
printf("CHILD : %d\n",count++);
if(count==5){
close(pipefd[1]);
break;
}
}
exit(2);
}else{
close(pipefd[1]);
char buf[64];
while(1){
ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
if(s>0){
buf[s]=0;
printf("father get message : %s\n",buf);
sleep(1);
}
printf("child exit return : %d\n",s);
}
}
return 0;
}
運行結果:

場景四
如果讀端關閉檔案描述符,寫端行程在后續會被OS直接殺掉,OS通過13號信號將行程殺掉,
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main(){
int pipefd[2]={0};
pipe(pipefd);
pid_t id=fork();
if(id==0){
close(pipefd[0]);
const char *msg="I am child.....\n";
int count=0;
while(1){
write(pipefd[1],msg,strlen(msg));
printf("CHILD : %d\n",count++);
}
}else{
close(pipefd[1]);
char buf[64];
int count=0;
while(1){
ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
if(s>0){
buf[s]=0;
printf("father get message : %s\n",buf);
sleep(1);
}
if(count++==3){
close(pipefd[0]);
}
}
}
return 0;
}
檢測腳本:
while :; do ps -axj| grep mypipe | grep -v grep; echo "############################";sleep 1;done;
檢測結果:

當OS發現讀端已經關閉管道后,如果寫端再寫入資料是無用的因為沒有行程從管道中讀取資料,那么管道就是廢棄管道,作業系統不做任何浪費空間和低效的事情,那么只要發現就會將這個行程殺掉,
驗證OS發送信號13殺死行程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main(){
int pipefd[2]={0};
pipe(pipefd);
pid_t id=fork();
if(id==0){
close(pipefd[0]);
const char *msg="I am child.....\n";
int count=0;
while(1){
write(pipefd[1],msg,strlen(msg));
printf("CHILD : %d\n",count++);
}
}else{
close(pipefd[1]);
char buf[64];
int count=0;
while(1){
ssize_t s=read(pipefd[0],buf,sizeof(buf)-1);
if(s>0){
buf[s]=0;
printf("father get message : %s\n",buf);
sleep(1);
}
if(count++==3){
close(pipefd[0]);
break;
}
}
int status=0;
waitpid(id,&status,0);
printf("child exit get a signal , signal number : %d\n",status&0x7f);
}
return 0;
}
運行結果:

匿名管道特點
1、只能用于具有共同祖先的行程(具有親緣關系的行程)之間進行通信;通常,一個管道由一個行程創建,然后該行程呼叫fork函式,此后父子行程之間就可應用該管道,
2、管道提供流式服務,
3、一般而言,行程退出,管道釋放,所以管道的宣告周期隨行程,
4、一般而言,內核會對管道進行同步和互斥,
5、管道是半雙工的,資料只能向一個方向流動;需要雙方通信時,需要建立起兩個管道
命名管道
命名管道是建立在實際的磁盤介質或檔案系統(而不是只存在于記憶體中)上有自己名字的檔案,任何行程可以在任何時間通過檔案名或路徑名與該檔案建立聯系,為了實作命名管道,引入了一種新的檔案型別——FIFO檔案(遵循先進先出的原則),
實作一個命名管道實際上就是實作一個FIFO檔案,命名管道一旦建立,之后它的讀、寫以及關閉操作都與普通管道完全相同,雖然FIFO檔案的inode節點在磁盤上,但是僅是一個節點而已,檔案的資料還是存在于記憶體緩沖頁面中,和普通管道相同,
創建命名管道
#命令列創建
$ mkfifo filename
#程式中創建,呼叫函式mkfifo
int mkfifo(const char *filename,mode_t mode);
匿名管道與命名管道的區別
1、匿名管道由pipe函式創建并打開的,
2、命名管道由mkfifo函式創建,用open打開,
3、FIFO(命名管道)和pipe(匿名管道)之間唯一區別就是創建和打開的方式不同,
例子—用命名管道實作server&client通信
//server.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_FILE "./fifo"
int main(){
umask(0);
if(-1==mkfifo(FIFO_FILE,0666)){
perror("mkfifo failed!!\n");
}
int fd=open(FIFO_FILE,O_RDONLY);
if(fd>=0){
while(1){
char buf[64];
ssize_t s=read(fd,buf,sizeof(buf)-1);
if(s>0){
buf[s]=0;
printf("client# : %s",buf);
}else if(s==0){
printf("client quit,me too!!\n");
break;
}else{
perror("error!\n");
}
}
}
return 0;
}
//client.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_FILE "./fifo"
int main(){
int fd=open(FIFO_FILE,O_WRONLY);
if(fd>=0){
while(1){
char buf[64];
printf("Please input a message : ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
if(s>0){
buf[s]=0;
write(fd,buf,s);
}
}
}
return 0;
}
//Makefile檔案
.PHONY:all
all:server client
server:server.c
gcc $^ -o $@
client:client.c
gcc $^ -o $@
.PHONY:clean
clean:
rm -rf client server
運行結果:


共享記憶體
共享記憶體(Shared Memory)就是允許多個行程訪問同一個記憶體空間,是在多個行程之間共享和傳遞資料最高效的方式,作業系統將不同行程之間共享記憶體安排為同一段物理記憶體,行程可以將共享記憶體連接到它們自己的地址空間中,如果某個行程修改了共享記憶體中的資料,其它的行程讀到的資料也將會改變,
共享記憶體區是最快的IPC形式,一旦這樣的記憶體映射到共享它的行程的地址空間,這些行程間資料傳遞不再涉及到內核,換句話說是行程不再通過執行進入內核的系統呼叫來傳遞彼此的資料,
共享記憶體不提供任何同步和互斥機制,也就是說,在某一個行程對共享記憶體的進行讀寫的時候,不會阻止其它的行程對它的讀寫,如果要對共享記憶體的讀/寫加鎖,可以使用信號燈,
共享記憶體函式
Linux中提供了一組函式用于操作共享記憶體,程式中需要包含以下頭檔案:
#include <sys/ipc.h>
#include <sys/shm.h>
shmget函式
功能:用于創建共享記憶體
//原型:
int shmget(key_t key, size_t size, int shmflg);
//引數key是共享記憶體的鍵值,是一個整數,是共享記憶體在系統中的編號,不同共享記憶體的編號不能相同,這一點由程式員保證,可以用函式:key_t ftok(const char *pathname, int proj_id);生成唯一鍵值,
//引數size是待創建的共享記憶體的大小,以位元組為單位,通常作業系統在底層分配頁(4K)的倍數
//引數shmflg是共享記憶體的訪問權限,與檔案的權限一樣,由九個權限標志構成,它們的用法和創建檔案時使用的mode模式標志是一樣的,如果共享記憶體不存在,就創建一個共享記憶體,
//回傳值:成功回傳一個非負整數,即該共享記憶體段的標識碼;失敗回傳-1
shmctl函式
功能:控制共享記憶體,不止是洗掉,還有其他功能
//原型:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
//引數:
shm_id:由shmget回傳的共享記憶體標志碼
command:將要采取的動作(三個常用動作)IPC_RMID:洗掉共享記憶體段,其他的查看檔案
buf:指向一個保存著共享記憶體的模式狀態和訪問權限的資料結構
//注意,用root創建的共享記憶體,不管創建的權限是什么,普通用戶無法洗掉,
//回傳值:成功回傳0,失敗回傳-1
shmat函式
功能:把共享記憶體連接到當前行程的地址空間,
//原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
//引數:
shmid:由shmget回傳的共享記憶體標志碼
shmaddr:指定連接的地址,通常為空,表示讓系統來選擇共享記憶體的地址,
shmflg:是一組標志位,通常為0,它的兩個可能取值是SHM_RND和SHM_RDONLY
//回傳值:
成功回傳一個指標,指向共享記憶體第一個位元組的指標;失敗回傳-1
引數說明
shmaddr為NULL,作業系統自動選一個地址,
shmaddr不為NULL且shmflg無SHM_RND標記,則以shmaddr為連接地址,
shmflg=SHM_RDONLY,表示連接操作用來只讀共享記憶體,
shmdt函式
功能:將共享記憶體段與當前行程脫離,
//原型:
int shmdt(const void *shmaddr);
//引數
shmaddr:由shmat所回傳的指標
//回傳值
成功回傳0,失敗回傳-1
注意:將共享記憶體與當前行程脫離,不等于洗掉共享記憶體段,洗掉共享記憶體段用shmtcl函式,
實體代碼
創建兩個行程,一個客戶端,一個服務器,讓客戶端往共享記憶體中寫字串,服務器列印共享記憶體中的字串,
//server.c
#include<stdio.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include"comm.h"
#include<unistd.h>
int main(){
//生成作業系統層面唯一碼
key_t key=ftok(PATHNAME,PROJ_ID);
printf("key : %p\n",key);
//創建共享記憶體
int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);
if(shmid<0){
perror("shmget error!!\n");
return 1;
}
//將行程與共享記憶體連接
char *str=(char*)shmat(shmid,NULL,0);
while(1){
printf("%s\n",str);
sleep(1);
}
//將行程與共享記憶體取消關聯
shmdt(str);
//洗掉共享記憶體
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
//client.c
#include<stdio.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include"comm.h"
#include<unistd.h>
int main(){
//生成作業系統層面唯一碼
key_t key=ftok(PATHNAME,PROJ_ID);
int shmid=shmget(key,SIZE,IPC_CREAT);
//將行程與共享記憶體連接
char *str=(char*)shmat(shmid,NULL,0);
//往共享記憶體中寫入資料
for(int i=0;i<26;++i){
str[i]='a'+i;
sleep(5);
}
//將行程與共享記憶體取消關聯
shmdt(str);
return 0;
}
//Makefile
.PHONY:all
all:server client
server:server.c
gcc $^ -o $@
client:client.c
gcc $^ -o $@ -std=c99
.PHONY:clean
clean:
rm -rf server client
運行結果:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/273631.html
標籤:其他
