宁波建设厅网站,汕头市交通建设网站,汉口企业制作网站的,14亿有多少中国人打了新冠疫苗文章目录 #x1f490;专栏导读#x1f490;文章导读#x1f427;进程间通信的目的#x1f427;如何进行进程间通信#x1f427;进程间通信的分类#x1f427;管道#x1f426;什么是管道#x1f426;管道原理 #x1f427;实例代码#x1f427;管道的特点#x1f4… 文章目录 专栏导读文章导读进程间通信的目的如何进行进程间通信进程间通信的分类管道什么是管道管道原理 实例代码管道的特点代码拓展 - 通过管道实现进程控制 专栏导读 作者简介花想云 在读本科生一枚C/C领域新星创作者新星计划导师阿里云专家博主CSDN内容合伙人…致力于 C/C、Linux 学习。 专栏简介本文收录于 Linux从入门到精通本专栏主要内容为本专栏主要内容为Linux的系统性学习专为小白打造的文章专栏。 相关专栏推荐C语言初阶系列、C语言进阶系列 、C系列、数据结构与算法。 文章导读
本章我们将深入学习如何通过管道进行进程间通信。 进程间通信的目的
数据传输一个进程需要将它的数据发送给另一个进程资源共享多个进程之间共享同一份资源通知事件一个进程需要向另一个或一组进程发送消息通知它们发生了某种事件如进程终止时要通知父进程进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截一个进程所有陷入和异常并能够及时知道它的状态改变
如何进行进程间通信
首先进程间进行通信有最大的一个难题
进程是具有独立性的
也就是说我们无法做到让两个进程之间访问彼此的资源。因此进程间通信的首要任务是
让两个进程看到同一份资源然后让一方写入另一方进行读取
这也是主流的我们将要的介绍的几种进程间通信的方法的核心思想。
进程间通信的分类
进程间通信经过漫长的发展逐渐衍生出以下几种方式
管道
匿名管道pipe命名管道
System V IPC
System V 消息队列System V 共享内存System V 信号量
POSIX IPC
消息队列共享内存信号量互斥量条件变量读写锁
本章节我们将介绍管道的进程间通信方式。
管道
什么是管道
在不久之前我们应该都见过Linux中的管道例如
在 myfile.txt 中写入五行文字
$ echo hello world myfile.txt
$ echo hello world myfile.txt
$ echo hello world myfile.txt
$ echo hello world myfile.txt
$ echo hello world myfile.txt
$ cat myfile.txt
hello world
hello world
hello world
hello world
hello world当我们想统计出 myfile.txt 中有几行文字时
$ cat myfile.txt | wc -l
5wc -l 用来统计文本中的行数
在Linux中管道是一种机制允许将一个命令的输出直接传递给另一个命令作为输入。这个机制通过竖线符号|来实现。通过使用管道你可以将一个命令的输出作为另一个命令的输入从而实现多个命令的协同工作形成一个命令链。
例如假设你有两个命令command1 和 command2。你可以使用管道将它们连接在一起使 command2 处理 command1 的输出。命令的形式如下
$ command1 | command2这会将 command1 的输出传递给 command2而不是将其打印到终端。这种机制使得在Linux系统上可以轻松地组合和重用命令从而实现更复杂的操作。
在计算机领域中管道是一个比较大的概念Linux指令中的 | 是管道的一种形式。
管道原理
我们首先要明确管道是一个文件。要做到让两个进程看到同一份资源说明这一份资源不独属于任何一个进程。
当我们创建一个进程该进程会拥有自己的tast_struct结构体在这个结构体中管理着 struct files_struct字段files_struct中又管理着该进程的文件描述符表 struct file* fd_array[]如下图所示 在该进程中分别用读方式与写方式打开一个文件然后 fork 创建子进程此时子进程会继承来自父亲的与父亲相同的文件描述符表 此时父进程与子进程指向了同一个文件接下来结合实际场景例如我们想让父进程进行写入让子进程负责读取这时父子进程需要关闭不需要的文件描述符父进程将 4 关闭子进程将 3 关闭这时父子进程就可以根据需要来收发数据了。
实例代码
现在有一个需求子进程向管道中发送数据父进程每隔一秒读取一次管道中的内容。
在代码的编写过程中我们需要用到一个接口——pipe为我们生成管道文件。它的基本原型如下
#include unistd.hint pipe(int pipefd[2]);这里pipefd 是一个整型数组有两个元素分别表示管道的两个端口。pipefd[0] 代表读取端口pipefd[1] 代表写入端口。成功调用 pipe 函数后这两个文件描述符将被用于在两个相关进程之间传递数据。
#include iostream
#include iostream
#include string
#include cerrno
#include cassert
#include cstring
#include sys/types.h
#include unistd.h
#include sys/wait.husing namespace std;int main()
{int pipefd[2] {0};// 1. 创建管道int n pipe(pipefd);if (n 0){cout pipe error errno : strerror(errno) endl;return 1;}cout pipfd[0] pipefd[0] endl;cout pipfd[1] pipefd[1] endl;// 2.创建子进程pid_t id fork();assert(id 0);if(id 0) // 子进程{// 3.关闭不需要的fdclose(pipefd[0]);// 4.开始通信const string namestr 我是子进程;int cnt 1;char buffer[1024];while(true){snprintf(buffer, sizeof buffer, %s : %d, pid : %d\n, namestr.c_str(), cnt, getpid());write(pipefd[1], buffer, strlen(buffer));}close(pipefd[1]);exit(0);}// 父进程close(pipefd[1]);// 4.开始通信char buffer[1024];int cnt 0;while(true){int n read(pipefd[0], buffer, sizeof(buffer) - 1);if(n 0){cout 我是父进程我读到了信息\n buffer endl;}else if(n 0){cout 我是父进程我读到了文件末尾 buffer endl;break;}else{cout 我是父进程读取异常 buffer endl;break;} sleep(1);}return 0;
}管道的特点
在管道的使用中我们经常能遇到以下四种场景
当我们read读取完毕所有的管道数据如果写端不再继续写入数据那读端只能等待当写端将管道全部写满了之后管道文件的容量是有上限的就不能在继续写入数据了当写端将数据全部写入并退出后读端读取完毕管道数据后read就会返回0代表读到了文件末尾当写端一直写入数据而读端关闭这时写端所作的事情是无意义的OS此刻会杀死一直写入数据的进程OS会通过发送信号来终止该进程
由四种场景我们可以总结出管道具有的特点
管道是单向通信的半双工。管道并不能读取和写入同时进行管道的生命周期随进程当进程退出时管道释放管道通信经常是由具有“血缘关系”的进程间进行的例如父子进程在管道通信中读与写的次数并不是强相关的因为管道提供的是流式服务管道具有一定的系统能力让读端与写端能够按照一定的步骤进行通信
代码拓展 - 通过管道实现进程控制
接下来我们就基于管道进行一个简单的设计——父进程向不同的子进程写入特定的消息唤醒子进程并让子进程去执行特定的命令。
/* ctrlProcess.cpp */#include Task.hppusing namespace std;Task t;
const int gnum 3;class EndPonit
{
public:EndPonit(int id, int fd): _child_id(id),_write_fd(fd){}~EndPonit(){}public:pid_t _child_id; // 子进程idint _write_fd; // 写端fd
};// 子进程要执行的方法
//void WaitCommand(int _write_fd)
void WaitCommand()
{while (true){int command 0;int n read(_write_fd, command, sizeof(int));if (n sizeof(int)){t.Execute(command);}else if (n 0){break;}else{break;}}
}void creatProcess(vectorEndPonit *end_points)
{for (int i 0; i gnum; i){// 1.创建管道int pipefd[2] {0};int n pipe(pipefd);assert(n 0);(void)n;// 2.创建进程pid_t id fork();assert(id ! -1);// 子进程if (id 0){// 3.关闭不要的fdclose(pipefd[1]);// 输入重定向dup2(pipefd[0], 0);// 子进程等待获取命令WaitCommand();//WaitCommand(pipefd[0]);close(pipefd[0]);exit(0);}// 父进程// 关闭不要的fdclose(pipefd[0]);// 4.将新的子进程和它的管道写端构建对象end_points-push_back(EndPonit(id, pipefd[1]));}
}
int main()
{vectorEndPonit end_points;creatProcess(end_points);int num 0;while (true){// 1.选择任务int command COMMAND_FUNC1;// 2.选择进程int index rand() % end_points.size();// 3.下发任务write(end_points[index]._write_fd, command, sizeof(int));sleep(1);}return 0;
}/* Task.hpp */
#pragma once#include iostream
#include vector
#include sys/wait.h
#include sys/types.h
#include unistd.h
#include assert.h
using namespace std;typedef void (*func_t)();void Func1()
{cout pid: getpid() Func1.... endl;
}void Func2()
{cout pid: getpid() Func2.... endl;
}void Func3()
{cout pid: getpid() Func3.... endl;
}#define COMMAND_FUNC1 0
#define COMMAND_FUNC2 1
#define COMMAND_FUNC3 2class Task
{
public:Task(){_tasks.push_back(Func1);_tasks.push_back(Func2);_tasks.push_back(Func3);}void Execute(int command){if (command 0 command _tasks.size())_tasks[command]();}~Task(){}private:vectorfunc_t _tasks;
};运行效果展示 本章的内容到这里就结束了如果觉得对你有所帮助的话欢迎三连~