【Linux】匿名管道

一、管道概念

管道是Linux中的最古老的通信方式,我们把一个进程链接到另一个进程的一个数据流称为一个"管道"。

linux中指令 | 这个被称为管道符。

zxw@hcss-ecs-cc58:~/lesson1$ ll
total 56
drwxrwxrwx 2 root root  4096 Feb 13 18:10 ./
drwxr-x--- 6 zxw  zxw   4096 Feb 13 17:05 ../
-rw-rw-r-- 1 zxw  zxw     77 Feb 12 19:36 Makefile
-rwxrwxr-x 1 zxw  zxw  17720 Feb 13 18:10 my*
-rwxrwxr-x 1 zxw  zxw  16720 Feb 12 19:37 mypipe*
-rw-rw-r-- 1 zxw  zxw   1447 Feb 13 18:10 mypipe.cxx
-rw-rw-r-- 1 zxw  zxw      0 Feb 12 19:01 test.c
zxw@hcss-ecs-cc58:~/lesson1$ ls | head -3
Makefile
my
mypipe

通过管道符 | 创建的管道是匿名管道,用完了就会被自动销毁。需要注意的是,匿名管道只能在具有亲缘关系(父子进程,兄弟进程,爷孙进程)的进程间使用。也就是说,匿名管道只能用于亲缘进程之间的通信

二、文件描述符角度-理解管道

三、内核角度-理解管道

四、创建管道

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

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
using namespace std;
int main()
{
    //1. 创建管道
    int fds[2] = {0};
    int n = pipe(fds);//fds:输出型参数
    if (n != 0)
    {
        cerr << "pipe error" << endl;
        return 1;
    }

    //2. 创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        cerr << "fork error" << endl;
        return 2;
    }
    else if(id == 0)
    {
        // 子进程 --> 写
        // 3.关闭不需要的fd,关闭read
        close(fds[0]);

        int cnt = 0;
        int total = 0;
        while(true)
        {
            string message = "hello world ";
            message += to_string(getpid());
            message += ", ";
            message += to_string(cnt);


            cnt++;
            //fds[1]
            ::write(fds[1],message.c_str(),message.size());

            sleep(1);
        }

        exit(0);
    }
    else
    {
        // 父进程 --> 读
        // 3. 关闭不需要的fd,关闭write
        close(fds[1]);

        char buffer[1024];
        while(true)
        {

            ssize_t n = ::read(fds[0],buffer,1024);
            if(n > 0)
            {
                buffer[n] = 0;
                cout << " child->father,message: " << buffer << endl;
            }
            else
            {
                cout << "error" << endl;
            }
            sleep(1);
        }
        pid_t rid = waitpid(id,nullptr,0);

    }
    return 0;
}

五、管道读写规则

管道四大现象:

1.管道为空&&管道正常,read数据会阻塞。[read是一个系统调用]

2.管道为满&&管道正常,write会阻塞。[write是一个系统调用]

3.管道写端关闭&&读端继续,读端读到0(read结束后返回值),表示读到文件结尾

4.管道写端正常&&读端关闭,OS会直接杀掉写入的进程!

操作系统会给目标进程发送信号:13)SIGPIPE ---> 验证一下

int main()
{
    //1. 创建管道
    int fds[2] = {0};
    int n = pipe(fds);//fds:输出型参数
    if (n != 0)
    {
        cerr << "pipe error" << endl;
        return 1;
    }

    //2. 创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        cerr << "fork error" << endl;
        return 2;
    }
    else if(id == 0)
    {
        // 子进程 --> 写
        // 3.关闭不需要的fd,关闭read
        close(fds[0]);

        int cnt = 0;
        int total = 0;
        while(true)
        {
            string message = "h";

            cnt++;
            //fds[1]
            total += ::write(fds[1],message.c_str(),message.size());
            // cnt++;
            cout << "total: " << total << endl; 
            sleep(2);
        }

        exit(0);
    }
    else
    {
        // 父进程 --> 读
        // 3. 关闭不需要的fd,关闭write
        close(fds[1]);

        char buffer[1024];
        while(true)
        {
            sleep(1);
            ssize_t n = ::read(fds[0],buffer,1024);
            if(n > 0)
            {
                buffer[n] = 0;
                cout << " child->father,message: " << buffer << endl;
            }
            else if(n == 0)
            {
                cout << "n:" << n << endl;
                cout << "child quit ??? me too" << endl;
                break;
            }
            close(fds[0]);
            break;
            cout << endl;
        }
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        cout << "father wait chid success: " << rid << " exit code "<<
        ((status<<8)&0xFF) << ", exit sig: " << (status & 0x7F) << std::endl;

    }
    return 0;
}

zxw@hcss-ecs-cc58:~/lesson1$ ./my
total: 1
 child->father,message: h
father wait chid success: 35943 exit code 0, exit sig: 13
zxw@hcss-ecs-cc58:~/lesson1$ g++ -o my mypipe.cxx -std=c++11

tips:

管道是有固定大小的,在不同内核里,大小可能有差别。在我的这台机器中管道当前环境下管道的大小是64KB

六、管道通信的场景

进程池

Makefile

BIN=processpool
CC=g++
FLAGS=-c -Wall -std=c++11
LDFLAGS=-o
# SRC=$(shell ls *.cc)
# wildcard,Makefile中函数 == 上面
SRC=$(wildcard *.cc)
OBJ=$(SRC:.cc=.o)

$(BIN):$(OBJ)
	$(CC) $(LDFLAGS) $@ $^
%.o:%.cc
	$(CC) $(FLAGS) $< 

.PHONY:clean
clean:
	rm -f $(BIN) $(OBJ)

.PHONY:test
test:
	@echo $(SRC)
	@echo $(OBJ)

ProcessPool.hpp

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdio>
#include <vector>
#include <functional>
#include <sys/wait.h>
#include "Task.hpp"
#include "Channel.hpp"
using work_t = function<void()>;
using namespace std;

enum
{
    OK = 0,
    UsageError,
    PipeError,
    ForkError,
};

class ProcessPool
{
public:
    ProcessPool(int n, work_t w)
        : num(n), work(w)
    {
    }

    // channels: 输出型参数
    // work_t work:回调
    int InitProcessPool()
    {
        // 2. 创建
        for (int i = 0; i < num; i++)
        {
            // 1. 先有管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0)
                return PipeError;
            pid_t id = fork();
            if (id < 0)
                return ForkError;

            // 建立通讯管道
            // child
            if (id == 0)
            {
                // // version 3
                // // 关闭历史write_fd_
                // for(auto &e : channels)
                // {
                //     e.Close();
                // }

                // read
                ::close(pipefd[1]);

                dup2(pipefd[0], 0);
                work();
                cout << "child->" <<getpid() <<"done ........Task" << endl;
                ::exit(0); // 执行完就say bye
            }

            // parent
            // write
            ::close(pipefd[0]);
            channels.emplace_back(pipefd[1], id);
            // Channel ch(pipefd[1],id);
            // channels.push_back(ch);
        }
        return OK;
    }

    void DispatchTask()
    {
        int who = 0;
        // 2. 派发任务
        int num = 10;

        while (num--)
        {
            // a.选择一个任务
            int task = tm.SelectTask();
            // b.选择一个子进程channel
            Channel &curr = channels[who++];
            who %= channels.size();

            cout << "-------------------------------------" << endl;
            cout << "send this " << task << " to " <<curr.Name() << ",  任务还剩: " << num << endl;
            cout << "-------------------------------------" << endl;
            cout << "                                     " << endl;
            cout << "                                     " << endl;
            cout << "                                     " << endl;
            // c.派发任务
            curr.Send(task);

            sleep(1);
        }
    }

    void CleanProcessPool()
    {
        // version 3 
        // in the InitProcessPool 


        // 为什么不能关掉一个读端,关一个进程
        // version 2 --- true
        // 管道写端正常&&读端关闭,OS会直接杀掉写入的进程!
        for (int i = channels.size() - 1; i >= 0; i--)
        {
            channels[i].Close();
            pid_t rid = ::waitpid(channels[i].who(), nullptr, 0);
            if (rid > 0)
            {
                cout << "child " << rid << "wait...success " << "quit..." << endl;
            }
        }
        // version 2 --- false
        // for (auto &c : channels)
        // {
        //     c.Close();
        //     pid_t rid = ::waitpid(c.who(), nullptr, 0);
        //     if (rid > 0)
        //     {
        //         cout << "child " << rid << "wait...success " << "quit..." << endl;
        //     }
        // }

        // version 1 ---
        // for (auto &c : channels)
        // {
        //     c.Close();
        // }

        // for (auto &e : channels)
        // {
        //     pid_t rid = ::waitpid(e.who(), nullptr, 0);
        //     if (rid > 0)
        //     {
        //         cout << "child " << rid << "wait...success " << "quit..." << endl;
        //     }
        // }
    }

private:
    vector<Channel> channels;
    int num;
    work_t work;
};

Task.hpp

#pragma once
#include <iostream>
#include <unordered_map>
#include <functional>
#include <sys/types.h>
#include <unistd.h>

using namespace std;
using task_t = function<void()>;

void DownLoad()
{
    cout << "-------------------------------------" << endl;
    cout << getpid() <<" Working DownLoad Task" << endl;
}

void Log()
{
    cout << "-------------------------------------" << endl;
    cout  << getpid() << " Working Log Task" << endl;
}

static int number = 0;
class TaskManger
{
public:
    TaskManger()
    {
        InsertTask(DownLoad);
        InsertTask(Log);
    }

    void InsertTask(task_t t)
    {
        tasks[number++] = t;
    }

    int SelectTask()
    {
        return rand() % number;
    }

    void Excute(int number)
    {
        if (tasks.find(number) == tasks.end())
            return;
        tasks[number]();
    }

    ~TaskManger()
    {
    }

private:
    unordered_map<int, task_t> tasks;
};
TaskManger tm;
void Worker()
{
    while (true)
    {
        int cmd = 0;
        int n = ::read(cmd, &cmd, sizeof(cmd));
        if (n == sizeof(cmd))
        {
            tm.Excute(cmd);
        }
        else if (n == 0)
        {
            cout << getpid() << "->pid  read nothing "  << endl;
            break;
        }
        else
        {
        }
    }
}

Channel.hpp

#ifndef __CHANNEL_HPP__
#define __CHANNEL_HPP__

#include <iostream>
#include <string>
#include <unistd.h>
using namespace std;
class Channel
{
public:
    Channel(int write_fd, pid_t who)
        : _write_fd(write_fd), _who(who)
    {
        _name = "Channel-" + to_string(_write_fd) + "-" + to_string(who);
    }

    string Name()
    {
        return _name;
    }

    void Send(int cmd)
    {
        ::write(_write_fd,&cmd,sizeof(cmd));
    }

    void Close()
    {
        ::close(_write_fd);
    }

    ~Channel()
    {
    }

    int who()
    {
        return _who;
    }
private:
    int _write_fd;
    string _name;
    pid_t _who;
};
#endif

Main.cc

#include "ProcessPool.hpp"
#include "Task.hpp"

void Usage(string proc)
{
    cout << "Usage" << proc << "prcocess-numm" << endl;
}

int main(int argc, char *argv[])
{

    if (argc != 2)
    {
        Usage(argv[0]);
        return UsageError;
    }
    int num = stoi(argv[1]);
    ProcessPool *pp = new ProcessPool(num,Worker);

    pp->InitProcessPool();
    pp->DispatchTask();
    pp->CleanProcessPool();

    // vector<Channel> channels;
    // //1. 初始化进程池
    // InitProcessPool(num,channels,Worker);


    // //2. 派发任务
    // DispatchTask(channels);
   

    // //3. 退出进程池
    // CleanProcessPool(channels);

    
    return OK;
}

这里主要流程就是

1.初始化进程池

2.派发任务

3.退出进程池

这里注意一点就是进程池的初始化

当我们创建一个子进程的时候,我们子进程会继承父进程的struct files_struct,fd[0] = 3 指向read

fd[1] = 4 指向write,我们关闭父进程的fd[0](read),关闭子进程的fd[4](write)。

当我们又创建一个子进程,父进程的fd[0] = 3 指向read,fd[1] = 5 指向write,我们子进程会继承父进程的struct files_struct,而这个struct files_struct继承了父进程的struct files_struct,其中 fd = 4指向了上一个管道,我们在进行关闭的时候发现就第一个管道会有两个read。如果创建3个管道4个管道以此类推。会发现第一个管道read端会随进程的增加而增加,第二管道相比第一个管道read端减少一个。

相关推荐
bugtraq202143 分钟前
XiaoMi Mi5(gemini) 刷入Ubuntu Touch 16.04——安卓手机刷入Linux
linux·运维·ubuntu
xmweisi1 小时前
【华为】报文统计的技术NetStream
运维·服务器·网络·华为认证
VVVVWeiYee1 小时前
BGP配置华为——路径优选验证
运维·网络·华为·信息与通信
陆鳐LuLu1 小时前
日志管理利器:基于 ELK 的日志收集、存储与可视化实战
运维·elk·jenkins
CodeWithMe1 小时前
[ Vim ] 常用命令 and 配置
linux·编辑器·vim
DC_BLOG2 小时前
Linux-GlusterFS进阶分布式卷
linux·运维·服务器·分布式
yourkin6662 小时前
TCP...
服务器·网络·tcp/ip
cookies_s_s2 小时前
Linux--进程(进程虚拟地址空间、页表、进程控制、实现简易shell)
linux·运维·服务器·数据结构·c++·算法·哈希算法
丁劲犇2 小时前
碳基生物的悲歌-DeepSeek思考实现Linux动态库递归收集工具
linux·递归·deepseek·ldd
zhouwu_linux3 小时前
MT7628基于原厂的SDK包, 修改ra1网卡的MAC方法。
linux·运维·macos