目录
一.什么是匿名管道
匿名管道是linux中一种非常古老的进程间通信方式,本质上就是一个内存级的文件。
一般用于父子进程间通信。概念上就是父进程与子进程共同使用一个管道文件来传输数据。
虽然父子进程都有对管道的读和写功能,但在使用时只能读或者写,因此管道是单向通信,半双工模式。
父进程把数据写入管道,子进程从管道中读取:
二.如何使用匿名管道
(一).pipe原理
linux为我们提供了系统接口pipe,用于创建管道进行通信。
参数是长度为2的整形数组,pipefd[0]代表读端文件描述符,pipefd[1]代表写端文件描述符。
返回值是int,创建成功返回0,失败返回-1,同时记录进errno。
pipe的使用原理上,就是首先父进程创建一个管道文件,但同时赋予管道文件两个文件描述符。
一个是以读方式打开即pipefd[0],另一个是以写方式打开即pipefd[1]。
之后创建子进程,由于子进程会对父进程进行拷贝,会把父进程的fd_array同时拷贝一份,也就拥有了管道对应的两个文件描述符(读端&写端)。
图示如下:
(二).pipe使用
以下面代码为例:
父进程使用write接口将字符串给管道,子进程从管道中接收字符串并打印。
同时,子进程的read系统接口会阻塞,直到父进程往管道中写完数据,read一次性将此时管道内数据读取完并清空管道。
当父进程关闭管道后,若管道中还有数据时read函数会一次性读取完并在下一次读取时返回0,没有数据时直接返回0。
-
#include<iostream>
-
#include<cstdio>
-
#include<unistd.h>
-
#include<string>
-
#include<assert.h>
-
using
namespace std;
-
int main()
-
{
-
int pfd[
2] = {
0 };
-
int ret =
pipe(pfd);
-
assert(ret ==
0);
-
pid_t id = fork();
-
assert(id >=
0);
-
if(id ==
0)
-
{
-
close(pfd[
1]);
//关闭写端
-
char GetStr[
1024] = {
0 };
-
ssize_t i =
read(pfd[
0], GetStr,
sizeof GetStr);
//接收数据
-
GetStr[i] =
'\0';
-
cout << GetStr << endl;
-
exit(
0);
-
}
-
//父进程
-
close(pfd[
0]);
-
char str[
1024] =
"hello world";
-
write(pfd[
1], str,
sizeof str);
//发送数据
-
-
return
0;
-
}
三.命名管道概念及区别
(一).什么是命名管道
顾名思义,这是有名字的管道。它以文件的形式存在于系统中,在磁盘中有对应的节点但没有为其分配数据块(block)。
换一种说法,它是能被我们看到(看到名字)且有inode编号的文件,但是inode中记录的block数量为0,使用时只能使用内存空间。
(二).与匿名管道的联系和区别
①和匿名管道一样,本质上都是利用内存空间进行通信。
②都是半双工通信,任意时刻只能向一端发送数据
③管道生命周期不同,匿名管道伴随进程,命名管道只要不删除一直存在。
④通信对象不同,匿名管道一般用于父子进程通信,命名管道可用于任意进程间通信。
⑤创建管道的方式不同,匿名管道使用pipe,命名管道使用mkfifo
四.命名管道的使用
(一).系统指令
系统指令方式:$mkfifo 路径+管道名
演示:
(二).mkfifo
代码方式:int mkfifo(const char *pathname, mode_t mode)
自制翻译:int mkfifo(文件路径+管道名,文件权限);
头文件:<sys/types.h>、<sys/stat.h>
返回值int:成功返回0,失败返回-1并在errno中记录。
文件权限:用于规定管道文件读写权限, 实际权限=设定权限&~umask。
比如0666实际上就是:0666 & ~0002(umask == 0002)即0661。
因为命名管道是一个文件,使用方式与文件的使用方式相同,但如果只打开了读端或写端,该端口就会阻塞,直到写端或读端打开为止。
使用方式:
-
//读端
-
int i =
mkfifo(
"./fifo.ipc",
0666);
//创建命名管道
-
assert(i >=
0);
-
int fd =
open(
"./fifo.ipc", O_RDONLY);
//打开管道读端,O_RDONLY:只读方式打开
-
. . .
-
char buf[
1024] = {
'\0' };
-
ssize_t s =
read(fd, buf,
sizeof buf);
//读取数据
-
. . .
-
close(fd);
//关闭读端管道
-
-
-
//写端
-
int fd =
open(
"./fifo.ipc", O_WRONLY);打开管道写端,O_WRONLY:只写方式打开
-
. . .
-
std::string buf;
-
std::
getline(std::cin, buf);
-
ssize_t s =
write(fd, buf.
c_str(), buf.
size());
//向管道写入数据
-
. . .
-
close(fd);
//关闭写端管道
五.进程池
(一).概念与原理
进程池可以简单理解为父进程调度多个子进程完成不同的任务。类似于人脑与四肢的关系。
我们可以自制一个简易的进程池。
首先利用父进程fork多个子进程,每次fork之前都先使用pipe创建与这个子进程用于联系的管道。
同时记录子进程的pid和其对应的读端文件描述符(用于向其中传输数据与回收子进程)。
之后调度某个子进程传输相关数据。
(二).代码原理与分析
在这份代码中,模拟实现5个函数,父进程会随机选择任意一个子进程调用任意一个函数。
子进程调用系统read函数接收数据并调用对应的模拟函数。
为了提高英语能力,小编选择使用英文注释😂。
-
//"Command.h"
-
#pragma once
-
#include<iostream>
-
#include<cstdio>
-
#include<vector>
-
#include<functional>
-
using
namespace std;
-
typedef function<
void()> func;
-
vector<func> callCommand;
-
void Running()
-
{
-
cout <<
"Running now" << endl;
-
}
-
void Writing()
-
{
-
cout <<
"Writing now" << endl;
-
}
-
void Eating()
-
{
-
cout <<
"Eating now" << endl;
-
}
-
void Sleeping()
-
{
-
cout <<
"Sleeping now" << endl;
-
}
-
void Testing()
-
{
-
cout <<
"Testing now" << endl;
-
}
-
-
void CommandInit()
-
{
-
callCommand.
push_back(Running);
-
callCommand.
push_back(Writing);
-
callCommand.
push_back(Eating);
-
callCommand.
push_back(Sleeping);
-
callCommand.
push_back(Testing);
-
}
-
void ShowAllCommand()
-
{
-
//... if you want you can write one ^-^
-
}
-
#include<cstdlib>
-
#include<iostream>
-
#include<cstdio>
-
#include<vector>
-
#include<unistd.h>
-
#include<sys/wait.h>
-
#include<sys/fcntl.h>
-
#include<assert.h>
-
#include"Command.h"
-
using
namespace std;
-
#define PROCESS_NUM 5 //number of process is 5
-
-
int main()
-
{
-
CommandInit();
//init for Command function
-
-
vector<pair<
pid_t,
int>> KvPidFd;
//record children process pid & read file fd
-
-
for(
int i =
0; i < PROCESS_NUM; i++)
//creat child process
-
{
-
int pipefd[
2] = {
0 };
-
int k =
pipe(pipefd);
-
assert(k ==
0);
//-1 : creat false
-
pid_t id = fork();
-
-
if(id ==
0)
//child
-
{
-
close(pipefd[
1]);
-
while(
1)
-
{
-
uint32_t accept =
-1;
//uint32_t : unsigned int in 32bit
-
cout <<
"*********" <<
getpid() << endl;
-
-
int s =
read(pipefd[
0], &accept,
sizeof accept);
-
if(s ==
0)
break;
//if s is zero, that's mean child read zero word in pipe
-
-
assert(accept >=
0);
-
cout <<
"I am child " <<
getpid() <<
" now accept command : ";
-
callCommand[accept]();
//invoke function
-
}
-
-
cout <<
"child " <<
getpid() <<
"finished work !" << endl;
-
close(pipefd[
0]);
-
exit(
0);
-
}
-
-
//only father process can go to this step
-
//take chiid pid & it's write fd as a mapping
-
close(pipefd[
0]);
-
KvPidFd.
push_back(
make_pair(id, pipefd[
1]));
-
}
-
-
srand((
unsigned
int)
time(
nullptr) *
getpid() *
131);
-
uint32_t command =
0;
-
int proc =
-1;
-
int count =
0;
-
-
while(
1)
//father
-
{
-
sleep(
1);
-
//random distribute function which will be commanded
-
command =
rand() % callCommand.
size();
-
assert(command >=
0);
-
proc =
rand() % PROCESS_NUM;
//random distribute process which will be used
-
assert(proc >=
0);
-
write(KvPidFd[proc].second, &command,
sizeof(command));
-
count++;
-
if(count ==
5)
break;
//stop this loop when calling child five times
-
}
-
-
-
//close pipe files & revoke children
-
for(
auto kv : KvPidFd)
-
{
-
close(kv.second);
-
}
-
-
for(
auto kv : KvPidFd)
-
{
-
waitpid(kv.first,
nullptr,
0);
-
}
-
-
return
0;
-
}
(三).进程池管道陷阱
有人可能有疑惑,这里有什么陷阱呢?
嗯。。如果父进程在关闭写端管道同时关闭相应子进程呢?
看似没有问题?
-
//close pipe files & revoke children
-
for(
auto kv : KvPidFd)
-
{
-
close(kv.second);
-
pid_t id =
waitpid(kv.first,
nullptr,
0);
-
assert(id >
0);
-
cout <<
"From father : child " << id <<
"finish work" << endl;
-
}
但是程序直接夯住了!
不要着急,我们来仔细梳理一下父进程与子进程的管道关系就能得到答案。
当父进程创建子进程1前,先创建了管道1,子进程1与父进程的fd_array都会记录管道1的fd。
当父进程创建子进程2前,先创建了管道2,子进程2与父进程fd_array都会记录管道2的fd。但是,子进程2的fd_array中也会记录管道1的fd。
是的,后面创建的子进程不仅会记录自己管道的fd,也会记录之前创建的管道fd,准确来讲是继承之前管道的写端。
于是,当父进程关闭管道写端后,管道写端并没有被全部关闭,read函数会一直阻塞,进而根本无法执行到waitpid的步骤。
因此,只有将父进程所有管道的写端都关闭后,首先是最后创建的子进程完成read返回0的操作,因为对它而言,有它管道的写端的只有父进程。当最后一个子进程关闭后,倒数第二个开始关闭,...直到第一个关闭。
所以,我们这是使用关闭管道和回收子进程分开的方式进行。
图示如下:
如果你是房间里最聪明的人,那么你走错房间了——未名
如有错误,敬请斧正
转载:https://blog.csdn.net/weixin_61857742/article/details/127975539