Getting started with Linux---Using anonymous pipes to implement a function

Preface

In the previous study, we knew the concept of communication and the use of anonymous pipes. Then we will use anonymous pipes to implement a function. First, we have a lot of functions that need to be executed, and then create some sub-processes to use anonymous pipes. The sub-process passes some information, and then the sub-process determines the function to be executed based on this information and executes it, such as the picture below:
Insert image description here
Then we will implement this function step by step.

Step 1: Create a subprocess and connect the pipe

First, we need to define a macro to indicate how many sub-processes are to be created to perform different tasks. Then in the main function, we first create a for loop to continuously create sub-processes. At the beginning of the loop, we first create an array to record. The writing end and reading end of the pipe, and then use the pipe function to create an anonymous pipe. Because the pipe creation may fail, create a variable to record the return value of the pipe function. If the value of the variable is equal to 0, assert it, and then You can use the fork function to create a child process, and separate the code executed by the parent and child processes based on the return value of the fork function. The code here is as follows:

#include<iostream>
#include<unistd.h>
#include<cassert>
#define PROCSS_NUM 3
using namespace std;
int main()
{
    
    
    for(int i=0;i<PROCSS_NUM;i++)
    {
    
    
        int fds[2]={
    
    0};
        int n=pipe(fds);
        assert(n==0);
        (void)n;
        pid_t id=fork();
        if(id==0)
        {
    
    
            //这里是子进程执行的代码

        }
        //这里是父进程执行的代码
    }
    return 0;
}

Then the first thing to do in the child process is to close the writing end of the pipe file and create a while loop. In the loop, the content in the pipe must be read continuously and the corresponding function shall be executed based on the read content. Then The code here is as follows:

 if(id==0)
 {
    
    
     //这里是子进程执行的代码
     close(fds[1]);
     while(true)
     {
    
    
         //读取管道里面的内容

         //根据管道的内容执行对应的函数
     }
 }

Because there are multiple sub-processes here, each sub-process must have a corresponding write-side pid and name, so in order to conveniently describe these sub-processes, we can create a structure to describe them:

class subEp // 描述子进程
{
    
    
public:
    subEp(pid_t subId, int writeFd)
        : subId_(subId), writeFd_(writeFd)
    {
    
    
        char nameBuffer[1024];
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid(%d)-fd(%d)]", num++, subId_, writeFd_);
        //给子进程创建名字
        name_ = nameBuffer;
    }

public:
    static int num;//用于区别每个子进程
    std::string name_;//记录子进程的名字
    pid_t subId_;//子进程的id
    int writeFd_;//子进程的写端
};

Then after creating the child process, the parent process has to close the reading end of the pipe and create a structure object describing the child process. Because there are multiple child processes, there will be multiple objects describing the child process, so for the convenience of management, we will You have to create a vector object to manage the structure object, then the current code is as follows:

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<string>
#include<vector>
#define PROCSS_NUM 10
using namespace std;
class subEp // 描述子进程
{
    
    
public:
    subEp(pid_t subId, int writeFd)
        : subId_(subId), writeFd_(writeFd)
    {
    
    
        char nameBuffer[1024];
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid(%d)-fd(%d)]", num++, subId_, writeFd_);
        //给子进程创建名字
        name_ = nameBuffer;
    }

public:
    static int num;//用于区别每个子进程
    std::string name_;//记录子进程的名字
    pid_t subId_;//子进程的id
    int writeFd_;//子进程的写端
};

int subEp::num = 0;
int main()
{
    
    
    vector<subEP> subs;
    for(int i=0;i<PROCSS_NUM;i++)
    {
    
    
        int fds[2]={
    
    0};
        int n=pipe(fds);
        assert(n==0);
        (void)n;
        pid_t id=fork();
        if(id==0)
        {
    
    
            //这里是子进程执行的代码
            close(fds[1]);
            while(true)
            {
    
    
                //读取管道里面的内容

                //根据管道的内容执行对应的函数
            }

        }
        //这里是父进程执行的代码
        close[fds[0]];
        subs.push_back(subEP(id,fds[1]));

    }
    return 0;
}

What we wrote above is the creation process of subprocess and pipeline, then we can write this module as a function, because this function needs to modify the subs object, and the subprocess also executes the corresponding function, so this function requires two parameters , one is a pointer to the vector object, and the other is a reference to the function collection, then the code here is as follows:

void createSubProcess(std::vector<subEp> *subs, std::vector<func_t> &funcMap)
{
    
    
     for(int i=0;i<PROCSS_NUM;i++)
    {
    
    
        int fds[2]={
    
    0};
        int n=pipe(fds);
        assert(n==0);
        (void)n;
        pid_t id=fork();
        if(id==0)
        {
    
    
            //这里是子进程执行的代码
            close(fds[1]);
            while(true)
            {
    
    
                //读取管道里面的内容

                //根据管道的内容执行对应的函数
            }

        }
        //这里是父进程执行的代码
        close[fds[0]];
        subs.push_back(subEP(id,fds[1]));
    }
}

Step 2: Create a collection of executed functions

Here we can create many functions, and in each function we can add a printout to indicate that the current function has been executed. Then the code here is as follows:

void downLoadTask()
{
    
    
    std::cout << getpid() << ": 下载任务\n"
              << std::endl;
    sleep(1);
}

void ioTask()
{
    
    
    std::cout << getpid() << ": IO任务\n"
              << std::endl;
    sleep(1);
}

void flushTask()
{
    
    
    std::cout << getpid() << ": 刷新任务\n"
              << std::endl;
    sleep(1);
}

Then we can rename the function pointer, create a vector object of the function pointer in the main function, and then create a function to load these functions into the vector object. The code here is as follows:

typedef void (*func_t)();
void loadTaskFunc(std::vector<func_t> *out)
{
    
    
    assert(out);
    out->push_back(downLoadTask);
    out->push_back(ioTask);
    out->push_back(flushTask);
}
int main()
{
    
    
    vector<func_t> funcMap;
    loadTaskFunc(&funcMap);
    vector<subEP> subs;
    //创建子进程
    createSubProcess(subs,funcmap);
    return 0;
}

At this point, our child process has been created, and the pipe has been successfully connected to the process. Then we have to let the parent process send information to the pipe.

Step 3: The parent process sends information

After the function is created, the parent process can send information to the child process. So here we can create a function specifically to send information to the child process. But before sending the information, do we have to select a child process to send it to? Because we have to send it multiple times. signal, so when sending signals, we have to ensure the randomness and fairness of the selection of sub-processes, and ensure that the number of signals received by each process is almost the same, so here we use random numbers to select sub-processes. First, design a timestamp:

srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234);

Then create a control function for the subprocess. This function requires three parameters: a collection of subprocesses, a collection of functions, and the number of signals. The function declaration is as follows:

void loadBlanceContrl(const std::vector<subEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    
    
    
}

First, we get the number of child processes and preprocessing functions, and then we make a convention here. If the value of count is designed to be 0 from the beginning, it means that signals must be sent and functions processed continuously. Then the code here is as follows :

void loadBlanceContrl(const std::vector<subEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    
    
    int processnum=subs.num();
    int tasknum=funcmap.num();
    bool forver =(count==0? true:false);
    while(true)
    {
    
    

    }
}

In the while loop, we need to obtain a random number, and then limit its range by modulating the processnum value. After obtaining the random number, we can find the corresponding subprocess through the subs object and obtain the write end of the corresponding pipe of the process, and then Data can be sent to the pipeline. The pipeline must be random while ensuring the randomness of the signal, so we have to create a random number to ensure the randomness of the signal, and then select the process to send the signal. Then here we can create a function to complete In the process of sending a signal, this function requires a structure describing the subprocess and an index representing the function set, so the code here is as follows:

void sendTask(const subEp &process, int taskNum)
{
    
    
    
}
void loadBlanceContrl(const std::vector<subEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    
    
    int processnum=subs.num();
    int tasknum=funcmap.num();
    bool forever =(count==0? true:false);
    while(true)
    {
    
    
        int subIdx=rand()%processnum;
        int taskIdx=rand()%tasknum;
        sendTask(subs[subIdx], taskIdx);
    }
}

Then in the sendTask function we can print a sentence to indicate that we have called the function, and then call the write function to write the subscript of the function into the pipe, because we write an integer every time into the pipe, The return value of the write function is the size of the written data, so we can take a parameter to receive the return value of the write function, and use the assert function to make a judgment. The code here is as follows:

void sendTask(const subEp &process, int taskNum)
{
    
    
    cout << "send task num: " << taskNum << " send to -> " << process.name_ << endl;
    int n=write(process.writeFd_,tasknum);
    assert(n==sizeof(int));
    (void)n;
}

After sending the signal, we have to judge the value of count. If the value of forever is fasle, then decrement the value of count by one. If the value of count is 0 after the reduction, use break to end the loop. The loop ends. After that, it means that all the signals have been sent, then we can close the write end corresponding to the parent process through the for loop, then the code here is as follows:

void loadBlanceContrl(const std::vector<subEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    
    
    int processnum=subs.num();
    int tasknum=funcmap.num();
    bool forever =(count==0? true:false);
    while(true)
    {
    
    
        int subIdx=rand()%processnum;
        int taskIdx=rand()%tasknum;
        sendTask(subs[subIdx], taskIdx);
    }
    if(!forever)
    {
    
    
        --count;
        if(count==0)
        {
    
    
            break;
        }
    }
    for(int i=0;i<process;i++)
    {
    
    
        close(subs[i].writeFd_);
    }
}

So this is the implementation of the relevant functions of signal sending.

Step 4: Signal reception and execution functions

After the signal is sent, we can create a function to receive the function. When we created the subprocess before, we have not yet completed the function implementation:

void createSubProcess(std::vector<subEp> *subs, std::vector<func_t> &funcMap)
{
    
    
     for(int i=0;i<PROCSS_NUM;i++)
    {
    
    
        int fds[2]={
    
    0};
        int n=pipe(fds);
        assert(n==0);
        (void)n;
        pid_t id=fork();
        if(id==0)
        {
    
    
            //这里是子进程执行的代码
            close(fds[1]);
            while(true)
            {
    
    
                //读取管道里面的内容

                //根据管道的内容执行对应的函数
            }

        }
        //这里是父进程执行的代码
        close[fds[0]];
        subs.push_back(subEP(id,fds[1]));
    }
}

Then here we can create another function to receive the signal in the pipeline. This function requires a parameter to represent the reading end of the child process, and the return value represents the subscript of the function to be executed. The function is declared as follows:

int recvTask(int readFd)
{
    
    
    
}

At the beginning of the function, a code variable is created to record the value of the signal, and then the read function is used to read the value of the signal. The first parameter of read is the read end of the corresponding pipe, the second parameter passes the address of the code variable, and the third parameter The parameter represents the size of the read data, so here it is 4. Because there may be problems with the read result, we create a variable to record the return value of the function. If it is equal to 4, the code will be returned directly. If it is less than or equal to 0, it will be returned. -1 returns 0 in other cases, then the code here is as follows:

int recvTask(int readFd)
{
    
    
    int code=0;
    ssize_t  s=read(fds[0],&code,sizeof(int));
    if(s==4)    {
    
    return code;}
    else if(s<=0)   {
    
    return -1;}
    else    {
    
    return 0}
}

Then in the createSubProcess function, we first create a variable to receive the return value of the recvTask function, and then judge if the return value is -1, then exit directly, and otherwise execute normally:

 while(true)
 {
    
    
     //读取管道里面的内容
     int commondCode=recvTask(fds[0]);
     //根据管道的内容执行对应的函数
     if(commondCode>=0&&commondCode<funcMap.size())
     {
    
    
         funcMap[commandCode]();
     }
     else if(commandCode == -1) 
     {
    
    
         break;
     }
 }

Step 5: The child process waits for the function

Let’s first improve the content of the main function:

int main()
{
    
    
    vector<func_t> funcMap;
    loadTaskFunc(&funcMap);
    vector<subEP> subs;
    //创建子进程
    createSubProcess(subs,funcmap);
    return 0;
}

First load the function and then create the child process. After the child process is created, the reader has already started waiting for the signal from the write end. Then we have to control the child process to send signals to it, so at this time we have to call the loadBlanceContrl function to send the signal. :

int main()
{
    
    
    vector<func_t> funcMap;
    loadTaskFunc(&funcMap);
    vector<subEP> subs;
    //创建子进程
    createSubProcess(subs,funcmap);
    int count=10;
    loadBlanceContrl(subs,funcMap,count);
    return 0;
}

After the loadBlanceContrl function is executed, the reading end has been closed. Once the reading end is closed, no more signals will be generated, so we only need to do one thing here, which is to use the waitpid function to recover the information of the child process. Then we can create another function for recycling:

void waitProcess(std::vector<subEp> processes)
{
    
    
    int processnum = processes.size();
    for(int i = 0; i < processnum; i++)
    {
    
    
        waitpid(processes[i].subId_, nullptr, 0);
        std::cout << "wait sub process success ...: " << processes[i].subId_ << std::endl;
    }
}
int main()
{
    
    
    vector<func_t> funcMap;
    loadTaskFunc(&funcMap);
    vector<subEP> subs;
    //创建子进程
    createSubProcess(subs,funcmap);
    int count=10;
    loadBlanceContrl(subs,funcMap,count);
    waitProcess(subs);
    return 0;
}

Program testing

The value of PROCSS_NUM is 3, and the value of count is 10, so after the current program is run, 3 sub-processes will be created and a total of 10 tasks will be executed. Then the running phenomenon is like this: you can see that three sub-processes are created here to
Insert image description here
process For different function tasks, when the number of executions of the function reaches 10, the execution of the function is stopped, and finally the three created sub-processes are recycled:
Insert image description here

Program bug

The program runs in line with our expectations, but there is a hidden bug here. When we create a pipe, we open a pipe for reading and writing. Later, we will close the reading end of the parent process and the write segment of the child process, and then the child process You can only read data from the pipe, and the parent process writes data from the pipe. When we create a child process, the child process will inherit the file descriptor table of the parent process, but there will be a problem here. When we create the first child When creating a process, the parent process will point to the write segment of pipe No. 1, and the child process will point to the read end of pipe No. 1. When we create a second child process, the child process will inherit the file descriptor table of the parent process, so the second child process will point to the write segment of pipe No. 1. The two child processes will also point to the write end of Pipe No. 1. In the same way, when we create the third child process, it will inherit the file descriptor table of the parent process, so it will also point to Pipe No. 1 and Pipe No. 2. The write end, and because we will only close the write segment of the pipe corresponding to the process during the start of the subroutine, and will not care about other pipes, this will cause when we want to individually close the write end of a main process to the pipe, Therefore, when the communication ends, the pipe cannot directly close the child process by returning 0, so there will still be data loss and the child process cannot be closed. In order to solve this problem, we can create a vector object to record the current situation. For pipes that have been created, every time a child process is created, the child process will close the write segments of these pipes one by one, and then the parent process will add the write segment tail of the newly created pipe to the vector, then The implementation of this function is placed in the createSubProcess function, so the code here is as follows:

void createSubProcess(std::vector<subEp> *subs, std::vector<func_t> &funcMap)
{
    
    
    std::vector<int> deleteFd;
    for(int i=0;i<PROCSS_NUM;i++)
    {
    
    
        int fds[2]={
    
    0};
        int n=pipe(fds);
        assert(n==0);
        (void)n;
        pid_t id=fork();
        if(id==0)
        {
    
    
            cout<<"创建了子进程"<< endl;
            for(int i = 0; i < deleteFd.size(); i++) close(deleteFd[i]);
            //这里是子进程执行的代码
            close(fds[1]);
            while(true)
            {
    
    
                //读取管道里面的内容
                int commondCode=recvTask(fds[0]);
                //根据管道的内容执行对应的函数
                if(commondCode>=0&&commondCode<funcMap.size())
                {
    
    
                    funcMap[commondCode]();
                }
                else if(commondCode == -1) 
                {
    
    
                    break;
                }
            }
            exit(0);
        }
        //这里是父进程执行的代码
        close(fds[0]);
        subEp sub(id,fds[1]);
        subs->push_back(sub);
        deleteFd.push_back(fds[1]);
    }
}

So this is the entire content of this article. The complete code is as follows:

#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <cassert>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define PROCSS_NUM 3
using namespace std;    
typedef void (*func_t)();
class subEp // 描述子进程
{
    
    
public:
    subEp(pid_t subId, int writeFd)
        : subId_(subId), writeFd_(writeFd)
    {
    
    
        char nameBuffer[1024];
        snprintf(nameBuffer, sizeof nameBuffer, "process-%d[pid(%d)-fd(%d)]", num++, subId_, writeFd_);
        //给子进程创建名字
        name_ = nameBuffer;
    }
public:
    static int num;//用于区别每个子进程
    std::string name_;//记录子进程的名字
    pid_t subId_;//子进程的id
    int writeFd_;//子进程的写端
};
int subEp ::num =0;
void downLoadTask()
{
    
    
    std::cout << getpid() << ": 下载任务\n"
              << std::endl;
    sleep(1);
}

void ioTask()
{
    
    
    std::cout << getpid() << ": IO任务\n"
              << std::endl;
    sleep(1);
}

void flushTask()
{
    
    
    std::cout << getpid() << ": 刷新任务\n"
              << std::endl;
    sleep(1);
}
int recvTask(int readFd)
{
    
    
    int code=0;
    ssize_t  s=read(readFd,&code,sizeof(int));
    if(s==4)    {
    
    return code;}
    else if(s<=0)   {
    
    return -1;}
    else    {
    
    return 0;}
}
void createSubProcess(std::vector<subEp> *subs, std::vector<func_t> &funcMap)
{
    
    
    std::vector<int> deleteFd;
    for(int i=0;i<PROCSS_NUM;i++)
    {
    
    
        int fds[2]={
    
    0};
        int n=pipe(fds);
        assert(n==0);
        (void)n;
        pid_t id=fork();
        if(id==0)
        {
    
    
            cout<<"创建了子进程"<< endl;
            for(int i = 0; i < deleteFd.size(); i++) close(deleteFd[i]);
            //这里是子进程执行的代码
            close(fds[1]);
            while(true)
            {
    
    
                //读取管道里面的内容
                int commondCode=recvTask(fds[0]);
                //根据管道的内容执行对应的函数
                if(commondCode>=0&&commondCode<funcMap.size())
                {
    
    
                    funcMap[commondCode]();
                }
                else if(commondCode == -1) 
                {
    
    
                    break;
                }
            }
            exit(0);
        }
        //这里是父进程执行的代码
        close(fds[0]);
        subEp sub(id,fds[1]);
        subs->push_back(sub);
        deleteFd.push_back(fds[1]);
    }
}

 void loadTaskFunc(std::vector<func_t> *out)
 {
    
    
     assert(out);
     out->push_back(downLoadTask);
     out->push_back(ioTask);
     out->push_back(flushTask);
 }
void sendTask(const subEp &process, int taskNum)
{
    
    
    cout << "send task num: " << taskNum << " send to -> " << process.name_ << endl;
    int n=write(process.writeFd_,&taskNum,sizeof(taskNum));
    assert(n==sizeof(int));
    (void)n;
}
void loadBlanceContrl(const std::vector<subEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    
    
    int processnum=subs.size();
    int tasknum=funcMap.size();
    bool forever =(count==0? true:false);
    while(true)
    {
    
    
        int subIdx=rand()%processnum;
        int taskIdx=rand()%tasknum;
        sendTask(subs[subIdx], taskIdx);
        sleep(1);
        if(!forever)
        {
    
    
            --count;
            if(count==0)
            {
    
    
                break;
            }
        }
    }
    for(int i=0;i<processnum;i++)
    {
    
    
        close(subs[i].writeFd_);
    }
}
void waitProcess(std::vector<subEp> processes)
{
    
    
    int processnum = processes.size();
    for(int i = 0; i < processnum; i++)
    {
    
    
        waitpid(processes[i].subId_, nullptr, 0);
        std::cout << "wait sub process success ...: " << processes[i].subId_ << std::endl;
    }
}
int main()
{
    
    
    srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234);
    vector<func_t> funcMap;
    loadTaskFunc(&funcMap); 
    vector<subEp> subs;
    //创建子进程
    createSubProcess(&subs,funcMap);
    int count=10;
    loadBlanceContrl(subs,funcMap,count);
    waitProcess(subs);
    return 0;
}

Guess you like

Origin blog.csdn.net/qq_68695298/article/details/132701353