店招搜索栏在那个网站上可以做,做百科网站,首次做淘宝客网站要安装程序吗,做哪些网站可以赚钱的Halo#xff0c;这里是Ppeua。平时主要更新C语言#xff0c;C#xff0c;数据结构算法…感兴趣就关注我吧#xff01;你定不会失望。 本篇导航 0. 进程间通信原理1. 匿名管道1.1 通信原理1.2 接口介绍 2. 命名管道2.1 接口介绍 3. 共享内存3.1 通信原理3.2 接口介绍 0. 进…
Halo这里是Ppeua。平时主要更新C语言C数据结构算法…感兴趣就关注我吧你定不会失望。 本篇导航 0. 进程间通信原理1. 匿名管道1.1 通信原理1.2 接口介绍 2. 命名管道2.1 接口介绍 3. 共享内存3.1 通信原理3.2 接口介绍 0. 进程间通信原理
进程间的是相互独立的。那么想要让两个进程间进行通信本质是让其看到同一份资源。因为进程具有独立性所以大多时候让两个或多个进程看到同一份资源是最费力的。
根据看到资源的方式不同将进程通信划分为以下几种
匿名管道通信命名管道通信共享内存
其中匿名管道通信与命名管道通信的本质都是让进程看到同一份内存级文件 内存级文件是一个仅存储在内存中的文件.不会刷新到磁盘中 1. 匿名管道
1.1 通信原理
管道实际上是一份内存级文件,其被创建出来,通过文件的方式去访问.
内存模型如下: 其中file_r为写缓冲区,file_w为读缓冲区(缓冲区本质上也为一个内存级文件)
创建管道时,系统会为其分配两个fd.一个为读端,一个为写端.但是 管道只能进行单向通信.
为了方便控制,通常情况下,我们会手动关闭我们不需要的那个fd.(以下为了方便测试,规定由父进程写读,子进程写)
那么在匿名管道通信时如何让多个进程看到同一份内存级文件呢?
子进程会继承父进程的大多数资源,file_struct也在其中.但因为操作系统节省资源的特性,文件并不会被创建多份
所以可以通过创建子进程的方法来让多个进程看到同一份资源
所以 匿名管道的特点之一:仅能在有关系的进程中进行通信(父子进程,兄弟进程)
创建一个子进程时,内存模型如下: (通信本质是让不同进程看到同一份资源,所以资源的准备需要在进程创建之前!!!)
此时子进程也能够访问这个内存级文件了.这时双方就可以根据fd,按照访问文件的方式去访问这个内存级文件.也就是可以进行通信
1.2 接口介绍
创建匿名管道使用的函数为 int pipe(int pipefd[2]) 其中 **int pipefd[2]**为输出型参数 pipefd[0]为读端,pipefd[1]为写端
该接口创建完管道,并为用户分配所需读写端的fd,将其存入该数组后返回给用户.
如果创建成功则返回0,如果创建失败则返回-1,同时设置errno
这是一份简单的管道通代码.创建管道需要在创建子进程前才能被共享到!
#includeiostream
#includeunistd.h
#includecstdio
#includesys/types.h
#includesys/wait.h
#includestring
using namespace std;
#define N 2
#define NUM 1024void Read(int rfd)
{while (true) {char buffer[1024];int nread(rfd,buffer,sizeof(buffer));if(n0){coutwait write into pipeendl;}else {coutbuffer;}}
}
void Write(int wfd)
{string examplei am a child,hello linux communitate ;pid_t selfgetpid();exampleto_string(self);int flagexample.size();int cnt0;while(true){example.erase(flag);example to_string(cnt)\n;int nwrite(wfd,example.c_str(),example.length());if(n0){coutpipe closeendl;break;}sleep(1);}
}int main()
{int pipefd[2];pipe(pipefd);if(pipe(pipefd)0){perror(create pipe failed\n);}cout0: pipefd[0]endl;cout1: pipefd[1]endl;pid_t id fork();if(id0) //0 read 1 write{//writeclose(pipefd[0]);Write(pipefd[1]);}else {//readclose(pipefd[1]);Read(pipefd[0]); }return 0;
}完成了匿名管道的通信.
管道通信为单向的.读端会将读取的内容从管道中取走.先写入的数据会被先取走(与队列的原理相似) 父进程会随着子进程发送信息的频率而读取信息.(上文写端进行了休眠,而读端并没有)
所以:
读写端正常.当管道中没有内容时,读端会阻塞等待 如果我们重复的写入一段内容而不读取呢?
void Write(int wfd)
{string examplei am a child,hello linux communitate ;pid_t selfgetpid();exampleto_string(self);int flagexample.size();int cnt0;while(true){example.erase(flag);example to_string(cnt)\n;int nwrite(wfd,example.c_str(),example.length());if(n0){coutpipe closeendl;break;}coutcntendl;;}
}将写端逻辑做出如上更改,当写不进去时,输出 “pipe full”;
void Read(int rfd)
{while(true){};while (true) {char buffer[1024];int nread(rfd,buffer,sizeof(buffer));if(n0){coutwait write into pipeendl;}else {coutbuffer;}}
}将读端做出如上更改.手动阻塞进程 观察到写端阻塞,等待读端读取
所以
读写端正常.当管道写满时,写端会阻塞等待读端读取 将写端设置为一段时间后自动关闭.读端不会被阻塞.read返回0,可以根据这个特性做出行为
所以
写端被关闭,读端读到文件结尾,返回0.但此时不会被阻塞 将读端设置为一段时间后自动关闭.为了节省资源.写端将被操作系统关闭
void Read(int rfd)
{int cnt5;while (cnt0) {char buffer[1024];int nread(rfd,buffer,sizeof(buffer));if(n0){coutwait write into pipeendl;}else {coutbuffer;}cnt--;}coutread closeendl;
}
//main 中修改的部分//readclose(pipefd[1]);Read(pipefd[0]);close(pipefd[0]);int status0; waitpid(id,status,0);coutreceive signal : (status 0x7f)endl;收到13号信号,进程被终止.13号信号为SIGPIPE 所以
读端被关闭.写端也被关闭 综上,匿名管道通信时有四种情况:
读写端正常.当管道中没有内容时,读端会阻塞等待读写端正常.当管道写满时,写端会阻塞等待读端读取写端被关闭,读端读到文件结尾,返回0.但此时不会被阻塞读端被关闭.节省资源.写端也被关闭
所以我们可以得到匿名管道有以下特征:
具有血缘关系的进程才可以进行通信管道只能单向通信父子进程是会进程协同的,同步与互斥管道是面向字节流的管道基于内存级文件.其生命周期随进程
2. 命名管道
与匿名管道大同小异.都是基于文件级的通信,但是命名管道在指定路径下创建了一个具有名称的内存级文件.
这使得 没有血缘关系的进程也能够看到同一份资源,所以此时,不同的进程也可以进行通信了
2.1 接口介绍
我们可以使用mkfifo 依照创建文件的方法,在指定目录下创建出内存级文件. 其表示文件属性的权限位,显示其为一个管道文件.
删除这个管道文件我们通常使用unlink 在语言层面上,也为我们封装了该接口 pathname:为指定路径 mode:为权限
#pragma once
#include sys/stat.h
#include unistd.h
#includelog.hpp
#define FIFO_PATH ./myfifo
class InitPipe{
public:InitPipe(){int nmkfifo(FIFO_PATH,MODE);if(n!0){log(FATAL,create pipe failed);exit(0);}}~InitPipe(){unlink(FIFO_PATH);}
};接口使用
为了避免每次退出进程时,还要去手动释放该管道文件.所以利用RAII的方式来存储管道文件
之后使用该管道时,根据读写文件那一套来即可.
sever.cpp:
#includelog.hpp
#include fcntl.h
#include sys/types.h
#include sys/stat.h
#include unistd.h
#include create_pipe.hpp
int main(int argc,char * argv[])
{InitPipe pipe;Log log(SCREEN|CLASS_FILE);int fdopen(FIFO_PATH,O_RDONLY);if (fd 0){log(FATAL, error string: %s, error code: %d, errno, errno);}while(true){char sz[1024]{0};int xread(fd,sz,sizeof(sz));if(x 0){coutclient say# sz endl;log(INFO,sz);}else if(x0){break;}else break;}}client.cpp:
#include fcntl.h
#includeunistd.h
#includeiostream
#includestring
using namespace std;
int main()
{int fdopen(./myfifo,O_WRONLY);if (fd 0){coutfailedendl;exit(0);}string s1;while(true){coutclient say ;getline(cin,s1);write(fd,s1.c_str(),s1.length());}}(上文用到的log可以查看我下一篇博客的日志插件)
3. 共享内存
共享内存相较于前两种通信方式,速度上有明显的优势.
其不会涉及到复制写入/读取的内容.而是直接写入内存.而管道是将内容复制到文件当中,在复制出来
3.1 通信原理
共享内存的本质就是将一段真实的 物理内存,映射到PCB的共享内存当中.当多个进程映射同一个物理内存时,通过对内存数据直接的读写,就可以实现通信.
同样,我们需要使用系统调用接口去申请这段共享内存.使用系统调用接口去释放这段共享内存
共享内存没有像管道一样的同步互斥,需要用户自己去规定.
3.2 接口介绍
申请共享内存: key:可以理解为申请共享内存的一段密钥,该密钥在系统中是唯一的,就可以申请到唯一的一块共享内存,也是系统内核去校验两个共享是否相同的一个手段 通过ftok去申请: 该函数是一个算法结合两个参数去生成一个唯一的key.所以这两个参数可以根据使用情况去定制. 若申请成功,则返回key,若申请失败则返回-1,并设置errno. size:为申请的共享内存大小,一般为4096的整数倍.
shmflg具有以下两个值:
IPC_CREAT (申请一段共享内存,若不存在则创建并返回shmid,若存在则返回shmid(用户级的key))IPC_CREAT | IPC_EXEL| 八进制权限信息 (申请一段共享内存,若不存在则创建并返回shmid,若存在则创建失败) 需要带上权限信息
为什么会有第二个选项呢?用来保证你申请的共享内存是一段新的,唯一被您使用的内存
shmid是什么?与上文的key类似,内核使用key去操作控制共享内存,而用户通过shmid完成如上操作
返回值为 成功返回shmid,失败返回-1,并设置errno 获取共享内存地址.
char * address (char *)shmat(shmid,nullptr,0);**取消挂接该地址.**若成功则返回0,失败返回-1 对该共享内存进行控制,一般用来删除共享内存
cmd参数填上IPC_RMID 表示删除当前内存
下面是一个简单的示例demo:
config.hpp
#pragma once
#include cerrno
#include cstring
#include sys/ipc.h
#includesys/types.h
#include sys/shm.h
#includestdlib.h
#includeiostream
using namespace std;
string PATH /tmp;
int MODE 255;
#define SIZE 4096
class Init{
public:Init(){_keyftok(PATH.c_str(), MODE);if(_key-1){coutcreate failedendl;strerror(errno);exit(0);}}int CreateShm(){//需要加上权限 否则创建失败int shmidshmget(_key,SIZE,IPC_CREAT|IPC_EXCL|0666);if(shmid-1){coutcreatshm failedendl;strerror(errno);exit(0);}return shmid;}int GetShm(){int shmidshmget(_key,SIZE,IPC_CREAT|0666);if(shmid-1){coutgetshm failedendl;strerror(errno);exit(0);}return shmid;}void destoryShmid(int shmid){if(shmctl(shmid,IPC_RMID, nullptr)!-1)coutdestory success;}private:key_t _key;
};processaa.cpp 接受方
#include config.hpp
#include cstddef
#include sys/shm.h
#includeiostream
using namespace std;
int main()
{Init it;int shmidit.GetShm();coutget success aendl;char * address (char *)shmat(shmid,nullptr,0);while(true)coutaddressendl;
}processbb.cpp 发送方
#include config.hpp
#includeiostream
#include cstddef
using namespace std;
int main()
{Init it;int shmidit.CreateShm();coutcreate success bendl;char * address (char *)shmat(shmid,nullptr,0);coutaddress success endl;int cnt5;while (cnt--0) {fgets(address,4096,stdin);}it.destoryShmid(shmid);
}