文章目录
- 一、我们之前的|(竖划线)管道
- 二、自定义shell
- 三、使用管道实现一个简易的进程池
- 1.详解
- 2.代码
- 3.一个小bug
- 4.最终代码
一、我们之前的|(竖划线)管道
cat test.txt | head -10 | tail -5
如上代码所示,是我们之前所用的管道
我们拿下面这个举个例子
当我们用管道级联起这些命令的时候,每一个命令最终都会被变为一个进程。这些进程的父进程都是同一个,我们可以去看一下这个父进程是什么东西
我们可以注意到,这个其实就是bash
所以这批进程具有血缘关系
所以他们就可以进行通信,从而达到目的
二、自定义shell
如果我们想让我们的shell支持这个|管道,代码该如何写呢?
我们可以在以前的shell代码中进行适量的修改,添加这些模块的功能即可
4.0 分析输入的命令行字符串,获取有多少个|,命令打散多个子命令字符串
4.1 malloc申请空间,pipe先申请多个管道
4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始,输出重定向,指定的第一个程序的1号文件要变成第一个管道文件的写端
4.3 中间的需要进行输入输出重定向,0标准输入替换为上一个管道的读端,1标准输入替换为下一个管道的写端
4.4 最后一个要进行输入重定向, 将0标准输入替换为上一个管道的读端
4.5 分别让不同的子进程执行不同的命令 — exec* —exec* 不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向
下面是曾经的shell的代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44
#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2
char commandline[LINE_SIZE];
char* argv[ARGC_SIZE] = {NULL};
extern char** environ;
int last_code = 0;
int quit = 0;
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];
char* rdirfilename = NULL;
int rdir = NONE;
const char* getusername()
{
return getenv("USER");
}
const char* Gethostname()
{
return getenv("HOSTNAME");
}
void getpwd()
{
getcwd(pwd,sizeof(pwd));
}
void check_rdir(char* cmd)
{
char* pos = cmd;
while(*pos!='\0')
{
if(*pos == '>')
{
if(*(pos + 1) == '>')
{
*pos++ = '\0';
*pos++ = '\0';
while(isspace(*pos)) pos++;
rdirfilename = pos;
rdir=APPEND_RDIR;
break;
}
else
{
*pos = '\0';
pos++;
while(isspace(*pos)) pos++;
rdirfilename = pos;
rdir = OUT_RDIR;
break;
}
}
else if(*pos == '<')
{
*pos = '\0'; //ls -a -l < file.txt
pos++;
while(isspace(*pos)) pos++;
rdirfilename = pos;
rdir = IN_RDIR;
break;
}
else
{
// do nothing
}
pos++;
}
}
void Interact(char* cline,int size)
{
getpwd();
printf(LEFT"%s@%s %s"RIGHT""LABLE" ",getusername(),Gethostname(),pwd);
char* s = fgets(cline,size,stdin);
(void)s;
assert(s);
commandline[strlen(cline) - 1] = '\0';
check_rdir(cline);
}
int splitstring(char cline[],char* _argv[])
{
if(strcmp(cline,"") == 0) return 0;
int i = 0;
_argv[i++] = strtok(cline,DELIM);
while(_argv[i++] = strtok(NULL,DELIM));
return i - 1;
}
void NormalExcute(char* _argv[])
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return;
}
else if (id == 0)
{
int fd = 0;
if(rdir == IN_RDIR)
{
fd = open(rdirfilename,O_RDONLY);
dup2(fd,0);
}
else if(rdir == OUT_RDIR)
{
fd = open(rdirfilename,O_WRONLY|O_CREAT|O_TRUNC,0666);
dup2(fd,1);
}
else if(rdir == APPEND_RDIR)
{
fd = open(rdirfilename,O_WRONLY|O_CREAT|O_APPEND,0666);
dup2(fd,1);
}
//子进程执行命令
execvpe(_argv[0],_argv,environ);
exit(EXIT_CODE);
}
else
{
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid == id)
{
last_code = WEXITSTATUS(status);
}
}
}
int BuildCommand(char* _argv[],int _argc)
{
if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
{
chdir(_argv[1]);
getpwd();
sprintf(getenv("PWD"),"%s",pwd);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
{
strcpy(myenv,_argv[1]);
putenv(myenv);
return 1;
}
else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
{
if(strcmp(_argv[1],"$?") == 0)
{
printf("%d\n",last_code);
last_code = 0;
}
else if(*_argv[1] == '$')
{
char* val = getenv(_argv[1] + 1);
if(val) printf("%s\n",val);
}
else
{
printf("%s\n",_argv[1]);
}
return 1;
}
if(_argc > 0 && strcmp(_argv[0],"ls") == 0)
{
_argv[_argc++] = "--color";
_argv[_argc] = NULL;
}
return 0;
}
int main()
{
while(!quit)
{
rdirfilename = NULL;
rdir = NONE;
//2.交互问题,解决命令行
Interact(commandline,sizeof(commandline));
//3.子串分割问题,解析命令行
int argc = splitstring(commandline,argv);
if(argc == 0) continue;
// for(int i = 0; argv[i]; i++)
// {
// printf("[%d] : %s\n",i,argv[i]);
// }
//4.指令的判断(内建命令和普通命令)
int n = BuildCommand(argv,argc);
// 4.0 分析输入的命令行字符串,获取有多少个|,命令打散多个子命令字符串
// 4.1 malloc申请空间,pipe先申请多个管道
// 4.2 循环创建多个子进程,每一个子进程的重定向情况。最开始,输出重定向,指定的第一个程序的1号文件要变成第一个管道文件的写端
// 4.3 中间的需要进行输入输出重定向,0标准输入替换为上一个管道的读端,1标准输入替换为下一个管道的写端
// 4.4 最后一个要进行输入重定向, 将0标准输入替换为上一个管道的读端
// 4.5 分别让不同的子进程执行不同的命令 --- exec* ---exec* 不会影响该进程曾经打开的文件,不会影响预先设置好的管道重定向
//5.普通命令的执行
if(!n) NormalExcute(argv);
}
return 0;
}
三、使用管道实现一个简易的进程池
1.详解
池化技术就是因为频繁的系统调用成本也挺高的,不妨直接创建一批进程,然后在进行分配。
我们首先让父进程去创建一批进程
然后,我们让父进程和子进程之间创建管道,子进程是读端,父进程是写端
如果父进程没有往子进程里面写任何内容,那么子进程在阻塞等待,想要读取管道里面的内容
一旦写了,那就可以执行了。
我们将父进程往子进程里面写的,可以叫做一个一个的任务。我们这里简单的规定用四字节的任务码来进行写入和读取。这个数据的不同值代表着不同的任务。所以我们可以将这个父进程称作master,子进程可以称作slaver或者worker
我们这里的流程就是
- 选择任务
- 选择进程
然后就是直接执行了
如下代码是我们第一步先写出来的
#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
const int processnum = 5;
//先描述
class channel
{
public:
channel(int cmdfd, pid_t slaverid, const std::string processname)
:_cmdfd(cmdfd)
,_slaverid(slaverid)
,_processname(processname)
{}
public:
int _cmdfd; //发送任务码的文件描述符
pid_t _slaverid; //子进程的PID
std::string _processname;//子进程的名字
};
void slaver(int rfd)
{
while(1)
{
std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
sleep(100000);
}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{
for(int i = 0; i < processnum; i++)
{
int pipefd[2]; //临时空间
int n = pipe(pipefd);
assert(n == 0);
(void)n;
pid_t id = fork();
if(id == 0) //child
{
close(pipefd[1]);
slaver(pipefd[0]);
exit(0);
}
//father
close(pipefd[0]);
//添加channnel字段
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
}
}
void Debug(const std::vector<channel>& channels)
{
for(auto& e : channels)
{
std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;
}
}
int main()
{
//在组织
std::vector<channel> channels;
//1.初始化
InitProcessPool(&channels);
//test
Debug(channels);
//2.开始控制子进程
//3.清理收尾
sleep(1000);
return 0;
}
在上面的代码中,我们完成了初始化过程。
即先创建了足够多的子进程,然后分别进行初始化。不过在上面的过程中,有一个需要注意的事项是这样的,所有子进程的读端都是3号文件,这是因为,第一次我们父进程将3,4给创建好管道以后,然后父进程又将3号管道给关闭了,又接着创建了3,5的管道。如此一来,所有的子进程他的读取端都是3号文件了。这样做也没有问题。
不过我们还有一种更为优雅的做法
接下来我们完成第二步,让父进程产生任务分配给子进程
#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
const int processnum = 5;
//先描述
class channel
{
public:
channel(int cmdfd, pid_t slaverid, const std::string processname)
:_cmdfd(cmdfd)
,_slaverid(slaverid)
,_processname(processname)
{}
public:
int _cmdfd; //发送任务码的文件描述符
pid_t _slaverid; //子进程的PID
std::string _processname;//子进程的名字
};
// void slaver(int rfd)
// {
// while(1)
// {
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
// sleep(100000);
// }
// }
void slaver()
{
//0号文件拿任务即可
while(1)
{
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;
// sleep(100000);
int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待
if(n == sizeof(int))
{
//执行cmdcode对应的任务列表
std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;
}
}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{
for(int i = 0; i < processnum; i++)
{
int pipefd[2]; //临时空间
int n = pipe(pipefd);
assert(n == 0);
(void)n;
pid_t id = fork();
if(id == 0) //child
{
close(pipefd[1]);
dup2(pipefd[0], 0);
close(pipefd[0]);
slaver();
//slaver(pipefd[0]);
exit(0);
}
//father
close(pipefd[0]);
//添加channnel字段
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
}
}
void Debug(const std::vector<channel>& channels)
{
for(auto& e : channels)
{
std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;
}
}
int main()
{
srand(time(nullptr)^getpid()^1023); //种一个随机数种子
//在组织
std::vector<channel> channels;
//1.初始化
InitProcessPool(&channels);
//test
Debug(channels);
//2.开始控制子进程
for(int i = 0; i < 5; i++)
{
//1.选择任务
int cmdcode = rand() % 20;
//2.选择进程
//需要负载均衡:轮询或者随机数法
int processpos = rand() % channels.size();
std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[processpos]._slaverid << std::endl;
//3.发送任务
write(channels[processpos]._cmdfd, &cmdcode, sizeof(int));
sleep(1);
}
//3.清理收尾
sleep(1000);
return 0;
}
接下来来加上任务模块
#pragma once
#include <iostream>
#include <vector>
typedef void(*task_t)();
void task1()
{
std::cout << "原神启动!" << std::endl;
}
void task2()
{
std::cout << "王者荣耀启动!" << std::endl;
}
void task3()
{
std::cout << "三国杀启动!" << std::endl;
}
void task4()
{
std::cout << "金铲铲启动!" << std::endl;
}
void LoadTask(std::vector<task_t>* tasks)
{
tasks->push_back(task1);
tasks->push_back(task2);
tasks->push_back(task4);
tasks->push_back(task3);
tasks->push_back(task1);
}
然后是代码
#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
const int processnum = 5;
std::vector<task_t> tasks;
//先描述
class channel
{
public:
channel(int cmdfd, pid_t slaverid, const std::string processname)
:_cmdfd(cmdfd)
,_slaverid(slaverid)
,_processname(processname)
{}
public:
int _cmdfd; //发送任务码的文件描述符
pid_t _slaverid; //子进程的PID
std::string _processname;//子进程的名字
};
// void slaver(int rfd)
// {
// while(1)
// {
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
// sleep(100000);
// }
// }
void slaver()
{
//0号文件拿任务即可
while(1)
{
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;
// sleep(100000);
int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待
if(n == sizeof(int))
{
//执行cmdcode对应的任务列表
std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;
if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();
}
}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{
for(int i = 0; i < processnum; i++)
{
int pipefd[2]; //临时空间
int n = pipe(pipefd);
assert(n == 0);
(void)n;
pid_t id = fork();
if(id == 0) //child
{
close(pipefd[1]);
dup2(pipefd[0], 0);
close(pipefd[0]);
slaver();
//slaver(pipefd[0]);
exit(0);
}
//father
close(pipefd[0]);
//添加channnel字段
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
}
}
void Debug(const std::vector<channel>& channels)
{
for(auto& e : channels)
{
std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;
}
}
int main()
{
LoadTask(&tasks);
srand(time(nullptr)^getpid()^1023); //种一个随机数种子
//在组织
std::vector<channel> channels;
//1.初始化
InitProcessPool(&channels);
//test
Debug(channels);
//2.开始控制子进程
for(int i = 0; i < tasks.size(); i++)
{
//1.选择任务
int cmdcode = i;
//2.选择进程
//需要负载均衡:轮询或者随机数法
int processpos = rand() % channels.size();
std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[processpos]._slaverid << std::endl;
//3.发送任务
write(channels[processpos]._cmdfd, &cmdcode, sizeof(int));
sleep(1);
}
//3.清理收尾
sleep(1000);
return 0;
}
运行结果为
最后我们来进行一下写文件的关闭与子进程的退出
#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/stat.h>
const int processnum = 5;
std::vector<task_t> tasks;
//先描述
class channel
{
public:
channel(int cmdfd, pid_t slaverid, const std::string processname)
:_cmdfd(cmdfd)
,_slaverid(slaverid)
,_processname(processname)
{}
public:
int _cmdfd; //发送任务码的文件描述符
pid_t _slaverid; //子进程的PID
std::string _processname;//子进程的名字
};
// void slaver(int rfd)
// {
// while(1)
// {
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
// sleep(100000);
// }
// }
void slaver()
{
//0号文件拿任务即可
while(1)
{
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;
// sleep(100000);
int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待
if(n == sizeof(int))
{
//执行cmdcode对应的任务列表
std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;
if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();
}
if(n == 0) break;
}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{
for(int i = 0; i < processnum; i++)
{
int pipefd[2]; //临时空间
int n = pipe(pipefd);
assert(n == 0);
(void)n;
pid_t id = fork();
if(id == 0) //child
{
close(pipefd[1]);
dup2(pipefd[0], 0);
close(pipefd[0]);
slaver();
std::cout << "process exit :" << getpid() << std::endl;
//slaver(pipefd[0]);
exit(0);
}
//father
close(pipefd[0]);
//添加channnel字段
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
}
}
void Debug(const std::vector<channel>& channels)
{
for(auto& e : channels)
{
std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;
}
}
void ctrlSlaver(const std::vector<channel>& channels)
{
int which = 0;
for(int i = 0; i < tasks.size(); i++)
{
//1.选择任务
int cmdcode = i;
//2.选择进程
//需要负载均衡:轮询或者随机数法
//int processpos = rand() % channels.size();
std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;
//3.发送任务
write(channels[which]._cmdfd, &cmdcode, sizeof(int));
sleep(1);
which++;
which = which % channels.size();
}
}
void QuitProcess(const std::vector<channel>& channels)
{
for(const auto& c : channels)
{
close(c._cmdfd);
}
sleep(5);
for(const auto& c : channels)
{
waitpid(c._slaverid,nullptr,0);
}
sleep(5);
}
int main()
{
LoadTask(&tasks);
srand(time(nullptr)^getpid()^1023); //种一个随机数种子
//在组织
std::vector<channel> channels;
//1.初始化
InitProcessPool(&channels);
//test
Debug(channels);
//2.开始控制子进程
ctrlSlaver(channels);
//3.清理收尾
QuitProcess(channels);
//sleep(5);
return 0;
}
运行结果为
2.代码
最后我们可以将代码在稍微调整一下,使得我们的代码更加完善,可以通过菜单的方式来实现
#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/stat.h>
const int processnum = 5;
std::vector<task_t> tasks;
//先描述
class channel
{
public:
channel(int cmdfd, pid_t slaverid, const std::string processname)
:_cmdfd(cmdfd)
,_slaverid(slaverid)
,_processname(processname)
{}
public:
int _cmdfd; //发送任务码的文件描述符
pid_t _slaverid; //子进程的PID
std::string _processname;//子进程的名字
};
// void slaver(int rfd)
// {
// while(1)
// {
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
// sleep(100000);
// }
// }
void slaver()
{
//0号文件拿任务即可
while(1)
{
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;
// sleep(100000);
int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待
if(n == sizeof(int))
{
//执行cmdcode对应的任务列表
std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;
if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();
}
if(n == 0) break;
}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{
for(int i = 0; i < processnum; i++)
{
int pipefd[2]; //临时空间
int n = pipe(pipefd);
assert(n == 0);
(void)n;
pid_t id = fork();
if(id == 0) //child
{
close(pipefd[1]);
dup2(pipefd[0], 0);
close(pipefd[0]);
slaver();
std::cout << "process exit :" << getpid() << std::endl;
//slaver(pipefd[0]);
exit(0);
}
//father
close(pipefd[0]);
//添加channnel字段
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
}
}
void Debug(const std::vector<channel>& channels)
{
for(auto& e : channels)
{
std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;
}
}
void Menu()
{
std::cout << "**********************************************" << std::endl;
std::cout << "*********** 1. 原神启动! ***************" << std::endl;
std::cout << "*********** 2. 王者荣耀启动! ***************" << std::endl;
std::cout << "*********** 3. 三国杀启动! ***************" << std::endl;
std::cout << "*********** 4. 金铲铲启动! ***************" << std::endl;
std::cout << "*********** 0. 退出 ***************" << std::endl;
std::cout << "**********************************************" << std::endl;
std::cout << "Please Enter@" << std::endl;
}
void ctrlSlaver(const std::vector<channel>& channels)
{
int which = 0;
// for(int i = 0; i < tasks.size(); i++)
// {
// //1.选择任务
// int cmdcode = i;
// //2.选择进程
// //需要负载均衡:轮询或者随机数法
// //int processpos = rand() % channels.size();
// std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;
// //3.发送任务
// write(channels[which]._cmdfd, &cmdcode, sizeof(int));
// sleep(1);
// which++;
// which = which % channels.size();
// }
while(true)
{
int cmdcode = 0;
Menu();
std::cin >> cmdcode;
if(cmdcode <= 0 || cmdcode >= 5) break;
cmdcode--;
//1.选择任务
//2.选择进程
std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;
//3.发送任务
write(channels[which]._cmdfd, &cmdcode, sizeof(int));
sleep(1);
which++;
which = which % channels.size();
}
}
void QuitProcess(const std::vector<channel>& channels)
{
for(const auto& c : channels)
{
close(c._cmdfd);
}
//sleep(5);
for(const auto& c : channels)
{
waitpid(c._slaverid,nullptr,0);
}
//sleep(5);
}
int main()
{
LoadTask(&tasks);
srand(time(nullptr)^getpid()^1023); //种一个随机数种子
//在组织
std::vector<channel> channels;
//1.初始化
InitProcessPool(&channels);
//test
Debug(channels);
//2.开始控制子进程
ctrlSlaver(channels);
//3.清理收尾
QuitProcess(channels);
//sleep(5);
return 0;
}
#pragma once
#include <iostream>
#include <vector>
typedef void(*task_t)();
void task1()
{
std::cout << "原神启动!" << std::endl;
}
void task2()
{
std::cout << "王者荣耀启动!" << std::endl;
}
void task3()
{
std::cout << "三国杀启动!" << std::endl;
}
void task4()
{
std::cout << "金铲铲启动!" << std::endl;
}
void LoadTask(std::vector<task_t>* tasks)
{
tasks->push_back(task1);
tasks->push_back(task2);
tasks->push_back(task3);
tasks->push_back(task4);
}
3.一个小bug
在我们上面的代码中其实存在一个小bug
如下图所示,当我们的父进程创建子进程的时候,因为我们用的是循环的方式,所以导致了父进程的文件描述符表被拷贝的时候,前面的写端也被拷贝进去了。导致了如下的问题,第一个子进程会被后面的子进程进行写入。
这样带来的问题就是子进程之间也可以相互进行通信了。
那么我们就很好奇了,那这样的话,进程退出的时候不会出现问题吗?
其实,这个代码写的比较巧合,还真的是不会出现问题
因为最后一个管道,他只有一个写端,那么刚好将最后一个写端关闭以后,最后一个读入了0,就会退出,然后他的前一个刚好本来只剩下一个写端,还被退出来了,那么也随之没有写端了。如此循环往复,最终导致所有子进程都是进入僵尸状态了。从而使用进程等待进行回收资源。
如果我们将上面的代码给写成这样子,那么就出现问题了
当我们直接退出的时候,直接卡住了
这是因为,第一个进程由于还有其他的写入端,导致它的管道一直在read时候阻塞了。从而进程无法退出,导致卡住了
如果我们就想要在一个循环里面进行回收,我们可以使用倒着的for循环
不过这个似乎并没有从根本上解决问题,我们应该让每一个进程只有一个写端,这样才是最好的解决方案
如下所示,我们可以加上一个数组用来记录该进程需要关闭的写端文件,然后再子进程刚开始的时候,直接依次关闭即可
4.最终代码
#include <iostream>
#include "Task.hpp"
#include <string>
#include <unistd.h>
#include <vector>
#include <cassert>
#include <ctime>
#include <sys/wait.h>
#include <sys/stat.h>
const int processnum = 5;
std::vector<task_t> tasks;
//先描述
class channel
{
public:
channel(int cmdfd, pid_t slaverid, const std::string processname)
:_cmdfd(cmdfd)
,_slaverid(slaverid)
,_processname(processname)
{}
public:
int _cmdfd; //发送任务码的文件描述符
pid_t _slaverid; //子进程的PID
std::string _processname;//子进程的名字
};
// void slaver(int rfd)
// {
// while(1)
// {
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << rfd << std::endl;
// sleep(100000);
// }
// }
void slaver()
{
//0号文件拿任务即可
while(1)
{
// std::cout << "I am child: pid: " << getpid() << "I am read pipefd: " << 0 << std::endl;
// sleep(100000);
int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int)); //如果父进程不给子进程发送数据,那么阻塞等待
if(n == sizeof(int))
{
//执行cmdcode对应的任务列表
std::cout << "I am chid, pid: " << getpid() << ",I access a cmdcode : " << cmdcode << std::endl;
if(cmdcode >= 0 || cmdcode < tasks.size()) tasks[cmdcode]();
}
if(n == 0) break;
}
}
//输入: const &
//输出: *
//输入输出:&
void InitProcessPool(std::vector<channel>* channels)
{
std::vector<int> oldfds;
for(int i = 0; i < processnum; i++)
{
int pipefd[2]; //临时空间
int n = pipe(pipefd);
assert(n == 0);
(void)n;
pid_t id = fork();
if(id == 0) //child
{
std::cout << "child: " << getpid() << " close history fd: ";
for(auto fd : oldfds)
{
close(fd);
std::cout << fd << " ";
}
std::cout << std::endl;
close(pipefd[1]);
dup2(pipefd[0], 0);
close(pipefd[0]);
slaver();
std::cout << "process exit :" << getpid() << std::endl;
//slaver(pipefd[0]);
exit(0);
}
//father
close(pipefd[0]);
//添加channnel字段
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
oldfds.push_back(pipefd[1]);
}
}
void Debug(const std::vector<channel>& channels)
{
for(auto& e : channels)
{
std::cout << e._cmdfd << ", " << e._slaverid << ", " << e._processname << std::endl;
}
}
void Menu()
{
std::cout << "**********************************************" << std::endl;
std::cout << "*********** 1. 原神启动! ***************" << std::endl;
std::cout << "*********** 2. 王者荣耀启动! ***************" << std::endl;
std::cout << "*********** 3. 三国杀启动! ***************" << std::endl;
std::cout << "*********** 4. 金铲铲启动! ***************" << std::endl;
std::cout << "*********** 0. 退出 ***************" << std::endl;
std::cout << "**********************************************" << std::endl;
std::cout << "Please Enter@" << std::endl;
}
void ctrlSlaver(const std::vector<channel>& channels)
{
int which = 0;
// for(int i = 0; i < tasks.size(); i++)
// {
// //1.选择任务
// int cmdcode = i;
// //2.选择进程
// //需要负载均衡:轮询或者随机数法
// //int processpos = rand() % channels.size();
// std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;
// //3.发送任务
// write(channels[which]._cmdfd, &cmdcode, sizeof(int));
// sleep(1);
// which++;
// which = which % channels.size();
// }
while(true)
{
int cmdcode = 0;
Menu();
std::cin >> cmdcode;
if(cmdcode <= 0 || cmdcode >= 5) break;
cmdcode--;
//1.选择任务
//2.选择进程
std::cout << "father say: cmdcode:" << cmdcode << " , already sentto " << channels[which]._slaverid << std::endl;
//3.发送任务
write(channels[which]._cmdfd, &cmdcode, sizeof(int));
sleep(1);
which++;
which = which % channels.size();
}
}
// void QuitProcess(const std::vector<channel>& channels)
// {
// for(const auto& c : channels)
// {
// close(c._cmdfd);
// std::cout << "关闭了一个写端的文件" << std::endl;
// //sleep(1);
// }
// //sleep(5);
// for(const auto& c : channels)
// {
// waitpid(c._slaverid,nullptr,0);
// //sleep(1);
// }
// //sleep(5);
// }
void QuitProcess(const std::vector<channel>& channels)
{
for(const auto& c : channels)
{
close(c._cmdfd);
waitpid(c._slaverid,nullptr,0);
}
}
// void QuitProcess(const std::vector<channel>& channels)
// {
// for(int i = channels.size() - 1; i >= 0; i--)
// {
// close(channels[i]._cmdfd);
// sleep(1);
// waitpid(channels[i]._slaverid,nullptr,0);
// sleep(1);
// }
// }
int main()
{
LoadTask(&tasks);
srand(time(nullptr)^getpid()^1023); //种一个随机数种子
//在组织
std::vector<channel> channels;
//1.初始化
InitProcessPool(&channels);
//test
Debug(channels);
//2.开始控制子进程
ctrlSlaver(channels);
//3.清理收尾
QuitProcess(channels);
//sleep(5);
return 0;
}
#pragma once
#include <iostream>
#include <vector>
typedef void(*task_t)();
void task1()
{
std::cout << "原神启动!" << std::endl;
}
void task2()
{
std::cout << "王者荣耀启动!" << std::endl;
}
void task3()
{
std::cout << "三国杀启动!" << std::endl;
}
void task4()
{
std::cout << "金铲铲启动!" << std::endl;
}
void LoadTask(std::vector<task_t>* tasks)
{
tasks->push_back(task1);
tasks->push_back(task2);
tasks->push_back(task3);
tasks->push_back(task4);
}