[Linux] Anonymous pipes and named pipes, simple implementation of process pools


Preface

1. Anonymous Pipeline

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数:
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

1.Pipeline principle

Insert image description here
Insert image description here

The essence is to let different processes see the same resource first, that is, both processes can operate on the buffer of the pipe file.

When we pipe here, we will use two file descriptors. The file structures stored in these two file descriptions are the same, which is the file structure of the pipeline file. The inode is stored in the file structure. And the system buffer. At this time, fork a child process. The child process has the same structure as the parent process.
There is a very important point hereAlthough the child process has its own process address space and its own pointer array to store the file structure, the contents of the array are the same as the parent process, which is the file structure pointed to by the file descriptor position corresponding to the pipe in the child process. body (pipeline file) is the same, so far our father and son processes have seen the same resource and can use this resource to communicate.

When two different processes open the same file, in the kernel, the operating system will only open one
Insert image description here

2. Four situations of pipelines

1. The reading and writing ends are normal. If the pipe is empty, the reading end will be blocked.
The reading and writing ends are normal. If the pipe is full, the writing end will be blocked. 4. Because the operating system will not do inefficient and wasteful things, I will not read it on the reading end. What is the use of writing more data into a pipe? Because the pipe does not occupy disk memory, so after the program ends , there will be no pipeline. 3. The writing end writes normally, the reading end is closed, and the operating system will kill the process that is writing at this time (killed through a signal)
2. The reading end reads normally and the writing end is closed. The reading end will read 0, indicating that the end of the pipe file has been read and will not be blocked. If we print the content read by the reading end, the display will It always displays 0

When the amount of data to be written is not greater than PIPE_BUF, Linux will guarantee the atomicity of the write.
When the amount of data to be written is greater than PIPE_BUF, Linux will no longer guarantee the atomicity of the write.

3. Characteristics of pipelines

1. It can only be used for communication between processes with a common ancestor (processes with affinity); usually, a pipe is created by a process, and then the process calls fork, and then the parent and child processes This pipeline can be used.
2. Pipes provide streaming services
3. Generally speaking, when the process exits, the pipe is released, so the life cycle of the pipe follows the process
4. Generally speaking, the kernel will synchronize and mutually exclude pipeline operations
5. The pipeline is half-duplex, and data can only flow in one direction; when communication between both parties is required, Two pipelines need to be established

2. Named pipes

1. Features

1. One limitation of pipeline application is that it can only communicate between processes with common ancestors (affinities).
2. If we want to exchange data between unrelated processes, we can use FIFO files to do this work, which is often called named pipes
3. A named pipe is a special type of file

2. Create a named pipe

1. On the command line

 mkfifo  +文件名

2. In the program

int mkfifo(const char *filename,mode_t mode);

int main(int argc, char *argv[])
{
    
    
 mkfifo("p2", 0644);
 return 0;
}

3. When a program executes to open a pipe, it will not actually clock in.

Insert image description here
Insert image description here

When we executed this program, we found that the sentence was not printed, indicating that the pipe file was not really opened. The pipe will only be really opened when we execute another file we want to communicate with.
Insert image description here

3. Simple implementation of process pool

1.makefile

ProcessPool:ProcessPool.cpp
	g++ -o $@ $^ -std=c++11  -g

.PHONY:clean
clean:
	rm -rf ProcessPool

2.Task.hpp

 #pragma once
#include<functional>
 #include<vector>
 #include<iostream>
 using namespace std;
 

void task1()
{
    
    
    std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{
    
    
    std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{
    
    
    std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{
    
    
    std::cout << "lol 用户释放技能,更新用的血量和蓝量" << std::endl;
}


void LoadTask(vector<function<void()>>*tasks){
    
    
tasks->push_back(task1);
tasks->push_back(task2);
tasks->push_back(task3);
tasks->push_back(task4);
return ;
}

3.ProcessPool.cpp

Insert image description here

We create a processnum sub-process and let the parent process write and the sub-process read. The sub-process reads the task number and performs corresponding processing.

#include <iostream>
#include "Task.hpp"
#include <assert.h>
#include <vector>
#include <string>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

using namespace std;
vector<function<void()>> tasks;
const int processnum = 10;//创建的子进程数
class channel
{
    
    
public:
    channel(  string processname,  pid_t slaverid,int cmdcode)
        : _processname(processname), _cmdfd(cmdcode), _slaverid(slaverid)
    {
    
    
    }

public:
    string _processname;//执行任务的进程名

    pid_t _slaverid;//执行任务的进程pid

    int _cmdfd;//朝几号管道去操作
};

void Menu()
{
    
    
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;
    std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;
    std::cout << "#                         0. 退出               #" << std::endl;
    std::cout << "#################################################" << std::endl;
}

void slaver()
{
    
    
    int cmdcode;
    while (true)
    {
    
    
        int n = read(0, &cmdcode, sizeof(int));//读取任务码
        if (n == sizeof(int))
        {
    
    
            cout << "slaver say get a command " << getpid() << " cmdcode:  " << cmdcode << endl;
            if (cmdcode >= 0 && cmdcode < tasks.size())
                tasks[cmdcode]();//执行任务
        }
        else if (n == 0)//为0,说明读到文件末尾,之间break
            break;
    }
}
void InitProcessPool(vector<channel> *channels)
{
    
    
    for (int i = 0; i < processnum; i++)
    {
    
    
        int pipefd[2] = {
    
    0};
        int n = pipe(pipefd);
        //使用两个文件描述符指向同一个管道文件
        assert(!n);
        pid_t id = fork();

        if (id == 0)//子进程
        {
    
    
            close(pipefd[1]);//关闭写文件
            dup2(pipefd[0], 0);//将读文件重定向到标准输入的位置
            close(pipefd[0]);//关闭当前读文件,因为我们后续用标准输入的下标就行了
            slaver();//子进程读取任务码
            exit(0);
        }
        string name = "processname " + to_string(i);//子进程名字
        channels->push_back(channel(name, id, pipefd[1]));//子进程pid,这个子进程
        //与父进程之间的管道文件描述符下标记录下来

        // father
        close(pipefd[0]);//关闭读文件
    }
}

void ctrlProcess(vector<channel> &channels)
{
    
    
    int which = 0;
//我们循环调用各个子进程,which为子进程的下标
    while (true)
    {
    
    
         Menu();
        int select = 0;
        cin >> select;
        
        cout << "Please Enter@ ";
        if (select <= 0 || select >= 5)
            break;
        int cmdcode = select - 1;

        cout << "father say task have sent to " << channels[which]._processname << "  cmdcode : " << cmdcode << endl;
        write(channels[which]._cmdfd, &cmdcode, sizeof(int));//写入指令
        which++;
        which %= channels.size();
    }
}

void QuitProcess(const vector<channel> channels)
{
    
    
    //方法一:
    for (const auto &c : channels)
        close(c._cmdfd);

    for (const auto &c : channels)
        waitpid(c._slaverid, nullptr, 0);

    //方法二:
    //for(int i=channels.size()-1;i>=0;i--){
    
    
      //  close(channels[i]._cmdfd);
        //waitpid(channels[i]._slaverid,nullptr,0);//阻塞等待
    //}
}

int main()
{
    
    

    vector<channel> channels;//管理管道的数组
    LoadTask(&tasks);//加载任务
    InitProcessPool(&channels);//初始化进程池
    ctrlProcess(channels);//输入任务命令
    QuitProcess(channels);//中止进程
    return 0;
}

If waiting and close are in a loop, blocking will occur, because although my pipe No. 1 is closed for writing in the parent process, there are still child processes 2 and 3 pointing to this pipe for writing.

Insert image description here

Guess you like

Origin blog.csdn.net/m0_74774759/article/details/134605485