Linux中的進(jìn)程間通信之匿名管道解讀
一、基本概念
我們知道多個(gè)進(jìn)程之間是互相獨(dú)立的,但是有時(shí)候我們需要將一個(gè)進(jìn)程的數(shù)據(jù)傳遞到另一個(gè)進(jìn)程,實(shí)現(xiàn)數(shù)據(jù)傳輸的效果,有的時(shí)候多個(gè)進(jìn)程之間要共享同樣的資源,有的時(shí)候一個(gè)進(jìn)程要對(duì)其他進(jìn)程發(fā)送消息,實(shí)現(xiàn)通知事件,還有的時(shí)候一個(gè)進(jìn)程要完全控制另一個(gè)進(jìn)程的執(zhí)行,實(shí)現(xiàn)進(jìn)程控制
因?yàn)檫M(jìn)程間相互獨(dú)立,所以進(jìn)程通信是有較高成本的
進(jìn)程間通信的本質(zhì)就是讓不同的進(jìn)程看到同一份資源,這份資源一定是由操作系統(tǒng)提供的第三方空間,不能是某個(gè)進(jìn)程的,因?yàn)檫@樣會(huì)破壞進(jìn)程獨(dú)立性,我們進(jìn)程訪問第三方空間本質(zhì)上就是訪問操作系統(tǒng)
一般操作系統(tǒng)會(huì)有一個(gè)獨(dú)立的通信模塊,隸屬于文件系統(tǒng),它被制定者制定了兩個(gè)標(biāo)準(zhǔn)system V 和 posix ,其中system V 是本機(jī)內(nèi)部進(jìn)程間的通信,分為消息隊(duì)列、共享內(nèi)存、信號(hào)量,posix 是網(wǎng)絡(luò)進(jìn)程通信,分為消息隊(duì)列、共享內(nèi)存、信號(hào)量、互斥量、條件變量、讀寫鎖
在進(jìn)程間通信的規(guī)則指定之前,還沒有system V 和 posix 的時(shí)候,我們是通過管道進(jìn)行進(jìn)程間通信的,這是一種基于文件的通信方式
二、管道
1、溫故知新
我們?cè)谥暗膶W(xué)習(xí)命令行的過程中學(xué)習(xí)過管道,那里的管道與這里的管道是一致的,本質(zhì)上就是一個(gè)管子,在兩頭位置處有兩種處理方式,在進(jìn)入管道前處理一次,在管道中的內(nèi)容就是已經(jīng)被處理過一次的內(nèi)容,然后離開管道后再處理一次,得出的結(jié)果就是一個(gè)數(shù)據(jù)被前面的命令處理一次的結(jié)果被后面的命令處理
當(dāng)時(shí)學(xué)習(xí)的時(shí)候只浮于表面,實(shí)際上管道就是起到一個(gè)傳遞數(shù)據(jù)流的作用,兩邊為兩個(gè)進(jìn)程,進(jìn)程A發(fā)出的信息可以通過管道到達(dá)進(jìn)程B,管道本身沒有處理數(shù)據(jù)的功能,只有傳遞數(shù)據(jù)的功能
2、實(shí)現(xiàn)方式
我們說管道是一個(gè)基于文件的通信方式,我們來看一下我們文件管理的內(nèi)容

進(jìn)程中的PCB中有一個(gè)struct files_struct指針,指向結(jié)構(gòu)體files_struct,files_struct結(jié)構(gòu)體中存在一個(gè)文件描述符指針數(shù)組,指向?qū)?yīng)的struct file對(duì)象,每個(gè)struct file都有inode描述文件屬性,file_operators定義操作文件的函數(shù)接口,文件緩沖區(qū)緩沖文件,硬盤當(dāng)中的文件如果要加載到內(nèi)存中需要先加載到文件緩沖區(qū),如果我們的管道文件在硬盤上,那么IO的速度將非常慢,不利于我們進(jìn)行進(jìn)程間的快速通信,那什么地方既速度快又能存放文件呢?答案就是內(nèi)存

我們把寫入或者讀取硬盤的IO操作去掉,將管道文件保存在緩沖區(qū),其他進(jìn)程再通過文件描述符讀取緩沖區(qū)的內(nèi)容,就可以實(shí)現(xiàn)進(jìn)程間的管道通信,這里的管道文件就是匿名管道
管道文件的存放問題我們解決了,下一個(gè)問題就是其他進(jìn)程怎么通過文件描述符讀取緩沖區(qū)的內(nèi)容
我們知道子進(jìn)程被父進(jìn)程創(chuàng)建后,如果不做修改,相當(dāng)于是淺拷貝,父進(jìn)程的PCB復(fù)制一份,files_struct也復(fù)制一份,那么它們就同時(shí)指向已經(jīng)同一個(gè)struct file,如果父進(jìn)程fd==3以讀方式打開管道文件,fd==4以寫方式打開管道文件,那么子進(jìn)程也一樣,然后父進(jìn)程close(3)子進(jìn)程close(4)實(shí)現(xiàn)父寫子讀,父進(jìn)程close(4)子進(jìn)程close(3)實(shí)現(xiàn)父讀子寫
因?yàn)橐粋€(gè)文件是沒法進(jìn)行讀寫交替一起的,所以匿名管道其實(shí)是一種半雙工的通信方式,即單向通信,當(dāng)然我們可以通過建立多個(gè)匿名管道來實(shí)現(xiàn)雙向通信
管道通信常用于父子進(jìn)程通信,可用于兄弟進(jìn)程、爺孫進(jìn)程等有"血緣"的進(jìn)程進(jìn)行通信
3、匿名管道
#include <unistd.h> int pipe(int pipefd[2]); //pipefd:文件描述符數(shù)組,其中pipefd[0]表示讀端,pipefd[1]表示寫端,值為對(duì)應(yīng)的文件描述符 //返回值:成功返回0,失敗返回錯(cuò)誤代碼
在pipe函數(shù)中,int fd[2]是一個(gè)輸出型參數(shù)
我們來實(shí)現(xiàn)一個(gè)父讀子寫這樣一個(gè)管道通信
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstring>
#include <cstdlib>
#define N 2
#define NUM 1024
using namespace std;
void Writer(int wfd)
{
//定義要發(fā)送的字符串
string s = "this is your child";
//獲取當(dāng)前進(jìn)程的pid
pid_t myid = getpid();
int number = 0;
char buffer[NUM];
while(1)
{
//此處相當(dāng)于buffer[0] = '\0';意思是將整個(gè)數(shù)組當(dāng)做字符串用并且清空字符串
buffer[0] = 0;
//將字符串、pid、以及計(jì)數(shù)器number按照"%s-%d-%d"格式寫到buffer當(dāng)中
snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),myid,number++);
//這里傳過來的wfd為對(duì)應(yīng)的文件描述符,然后將buffer中的有效內(nèi)容寫到管道文件緩沖區(qū)中
write(wfd,buffer,strlen(buffer));
sleep(5);
}
}
void Reader(int rfd)
{
char buffer[NUM];
while(1)
{
//同上
buffer[0] = 0;
//將文件描述符rfd讀取的內(nèi)容存儲(chǔ)到buffer中,并返回讀取到的字符個(gè)數(shù)n
ssize_t n = read(rfd,buffer,sizeof(buffer));
//如果有內(nèi)容則打印出來
if(n > 0)
{
buffer[n] = 0;
cout << "parent get a message[" << getpid() << "]# " << buffer << endl;
}
//沒有內(nèi)容即讀取完成
else if(n == 0)
{
printf("parent read file done!\n");
break;
}
//其他情況就是有bug了
else break;
}
}
int main()
{
//pipefd用來存放輸出型參數(shù)
int pipefd[N] = {0};
//成功驗(yàn)證
int n = pipe(pipefd);
if(n < 0)
{
return 1;
}
//創(chuàng)建子進(jìn)程
pid_t id = fork();
//錯(cuò)誤情況
if(id < 0)
{
return 2;
}
//子進(jìn)程執(zhí)行段,把讀寫函數(shù)打包一下,寫到一個(gè)函數(shù)里,立體分明
else if(id == 0)
{
//child
//子進(jìn)程要寫不讀,關(guān)掉pipefd[0],寫pipefd[1],寫完再關(guān)掉pipefd[1],然后退出
close(pipefd[0]);
Writer(pipefd[1]);
close(pipefd[1]);
exit(0);
}
//父進(jìn)程執(zhí)行段
else{
//parent
//父進(jìn)程要讀不寫,關(guān)掉pipefd[1],讀pipefd[0],等待子進(jìn)程結(jié)束再關(guān)掉pipefd[0]
close(pipefd[1]);
Reader(pipefd[0]);
pid_t rid = waitpid(id,NULL,0);
if(rid < 0)
{
return 3;
}
close(pipefd[0]);
}
return 0;
}
這里父進(jìn)程只在子進(jìn)程寫入的時(shí)候才讀取,沒有出現(xiàn)子進(jìn)程寫一半父進(jìn)程就讀取的情況,所以父子進(jìn)程直接是會(huì)進(jìn)行協(xié)同的,有同步和互斥性
(一)管道中的四種情況
對(duì)管道中可能出現(xiàn)的四種情況做說明:
- 讀寫端正常,如果管道為空,讀端就要被阻塞(上面印證)
- 讀寫端正常,如果管道被寫滿,寫端就要被阻塞(在管道特性這里印證)
- 讀端正常,寫端關(guān)閉,讀端可以讀到0,表明讀到了文件結(jié)尾,不堵塞
- 寫端正常,讀端關(guān)閉,操作系統(tǒng)會(huì)殺死正在寫入的進(jìn)程,用信號(hào)
SIGPIPE,也就是kill -13
注釋掉main函數(shù)中子進(jìn)程中的Writer函數(shù),它會(huì)讀到文件結(jié)尾并打印done信息

寫端一秒寫入一次,讀端一秒讀一次,讀端讀5秒后退出讀模式,關(guān)閉讀端,然后靜待5秒,等待子進(jìn)程結(jié)束,然后打印它的退出碼和收到的信號(hào)
int main()
{
//......
if(id < 0)
{
return 2;
}
else if(id == 0)
{
//child
close(pipefd[0]);
Writer(pipefd[1]);
close(pipefd[1]);
exit(0);
}
else{
//parent
close(pipefd[1]);
Reader(pipefd[0]);
close(pipefd[0]);
cout << "father close read fd: " << pipefd[0] << endl;
sleep(5);
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid < 0)
{
return 3;
}
cout << "wait child success: " << rid << " exit code: " << ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) << endl;
sleep(5);
cout << "parent quit" << endl;
}
return 0;
}
(二)管道的特性
//子進(jìn)程一直寫
void Writer(int wfd)
{
string s = "this is your child";
pid_t myid = getpid();
int number = 0;
char buffer[NUM];
while(1)
{
//buffer[0] = '\0';
buffer[0] = 0;
snprintf(buffer,sizeof(buffer),"%s-%d-%d",s.c_str(),myid,number++);
write(wfd,buffer,strlen(buffer));
}
}
//父進(jìn)程5秒讀一次數(shù)據(jù)
void Reader(int rfd)
{
char buffer[NUM];
while(1)
{
sleep(5);
buffer[0] = 0;
ssize_t n = read(rfd,buffer,sizeof(buffer));
if(n > 0)
{
buffer[n] = 0;
cout << "parent get a message[" << getpid() << "]# " << buffer << endl;
}
else if(n == 0)
{
printf("parent read file done!\n");
break;
}
else break;
}
}
我們發(fā)現(xiàn)它的讀取是雜亂無(wú)章的,說明管道是面向字節(jié)流的,這里與前面并不矛盾,有人說這里不是沒寫完就讀取嗎,你看這個(gè)句子一段一段的,其實(shí)這里是緩沖區(qū)寫滿了,寫不下了,寫入端堵塞導(dǎo)致的,在讀取端讀取之后寫入端才繼續(xù)寫入,正好也印證了上面的說法
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Ubuntu16.04搭建NFS 文件共享服務(wù)器的方法
這篇文章主要介紹了Ubuntu16.04搭建NFS 文件共享服務(wù)器的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04
Ubuntu 17.04系統(tǒng)下源碼編譯安裝opencv的步驟詳解
這篇文章主要給大家介紹了在Ubuntu 17.04系統(tǒng)下源碼編譯安裝opencv的相關(guān)資料,文中將一步步的步驟介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08

