System-V共享内存和基于管道通信实现的进程池

news2024/9/22 3:54:17

在这里插入图片描述

文章目录

  • 一.进程间通信:
    • 进程间通信的本质:
  • 二.Linux管道通信
    • 匿名管道:
    • 关于管道通信的要点:
    • 基于匿名管道构建进程池:
  • 三.System-V共享内存
    • 共享内存和命名管道协同通信

参考Linux内核源码版本------linux-2.4.3

一.进程间通信:

  • 操作系统中,为了保证安全性,进程之间具有严格的独立性(独立的PCB,独立的虚拟地址空间mm_struct和页表…等各种独立的系统资源),即便是父子进程之间也通过数据的写时拷贝保证了两者之间的的数据独立性.因此要实现进程间通信和任务协作,就要让不同进程的共同读写同一份信息资源.由于违背了进程独立性的原则,要实现进程间共享资源就需要一定的技术成本.
  • 进程间的独立性:
    在这里插入图片描述

进程间通信的本质:

  • 不同的进程同一块内存资源进行的一系列的读写操作.

二.Linux管道通信

  • 同一个管道文件的文件结构体指针分别填入两个进程的文件信息列表(通过父子进程的继承关系或者open接口实现),之后两个进程便可以对管道文件的内核级读写缓冲区(本质上是一块内存)进行读写操作实现通信.
  • 管道通信是一种单向通信手段,有固定的读端进程和写端进程.

匿名管道:

  • 匿名管道通信是父子进程间通信的一种方式.
  • 匿名管道通信机制图解:
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
  • 内核视角下管道文件结构体内部结构:在这里插入图片描述

关于管道通信的要点:

  • 管道通信可用于进程间协同,提供访问控制(同步与互斥):
    • 管道读写端正常,如果管道中缓冲区为空,则读端进程进入阻塞状态
    • 管道读写端正常,如果管道中缓冲区被写满,则写端进程进入阻塞状态
    • 管道写端先关闭,管道读端read接口返回0,标识读取结束
    • 管道读端先关闭,操作系统会终止写端进程.

基于匿名管道构建进程池:

在这里插入图片描述

  • Task.hpp模拟任务列表
#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>

//重定义函数指针
typedef void (*task_t)();

void task1()
{
    std::cout << "执行任务1:矩阵计算" << std::endl;
}
void task2()
{
    std::cout << "执行任务2:pid控制算法" << std::endl;
}
void task3()
{
    std::cout << "执行任务3:图像计算" << std::endl;
}
void task4()
{
    std::cout << "执行任务4:人脸识别算法" << 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);
}
#include "Task.hpp"
using namespace std;
#define ChildNum 5

//子进程信息结构体
class Channel
{
public: 
    Channel(){}
    Channel(const string & Name,pid_t Childpid,int Pipefd)
        : _Childpid(Childpid),
          _Pipefd(Pipefd),
          _Name(Name)
    {}
    
    pid_t getpid() const {return _Childpid;}
    string PrintName() const {return _Name;}
    int Getfd() const {return _Pipefd;}
private:
    pid_t _Childpid;
    int _Pipefd;    //保存管道的写入端
    string _Name;
};


//子进程任务执行函数
void slaver(const vector<task_t>&Taskarr)
{
    //任务码
    int CommandCode = 0;
    while(true)
    {
        int check = read(0,&CommandCode,sizeof(CommandCode));
        //若管道中没有数据且写入段没有关闭,子进程就会阻塞
        assert(check!=-1);
        if(check > 0)
        {
            std::cout <<"slaver say@ get a command: "<< getpid() << " : CommandCode: " <<  CommandCode << std::endl;
            //子进程解析并执行命令
            if(CommandCode < 0 || CommandCode >= Taskarr.size())
            {
                cout << "CommandCode Error! slaver exit!" << endl;
                exit(0);
            }
            //子进程根据任务码执行任务
            Taskarr[CommandCode]();
        }
        else 
        {
            //一旦父进程关闭管道写入端,check就会接收到0,子进程退出
            break;
        }
    }
}

//父进程向子进程发送任务的接口
void ctrlSlaver(const std::vector<Channel> & channels,const vector<task_t>&Taskarr)
{
    int count = 10;
    while(count--)
    {
        sleep(1);
        //随机选择子进程发送任务码
        int choseSlaver = rand()%channels.size();
        int Task = rand()%Taskarr.size();
        cout << "父进程向子进程" << channels[choseSlaver].PrintName() << "写入命令:" << Task << endl;
        write(channels[choseSlaver].Getfd(),&Task,sizeof(Task));
    }
    sleep(1);
    cout << "\n所有任务执行完毕,系统准备退出\n" << endl;
    sleep(2);
}


//构建进程池接口
void InitProcessPool(vector<Channel>& ChildProc,const vector<task_t>&Taskarr)
{
    for(int i = 0; i < ChildNum; ++i)
    {
        int pipefd[2];
        int check = pipe(pipefd);
        assert(!check); (void)check;
        pid_t pid = fork();
        assert(pid != -1);
        //父进程写 子进程读
        if(pid == 0)
        {
            //子进程执行流
            close(pipefd[1]);
            //将stdin对应文件指针修改为管道的读入端
            dup2(pipefd[0],0);
            //将文件信息列表中对应的指针位置空
            close(pipefd[0]);
            slaver(Taskarr);
            close(0);
            exit(0);
        }
        close(pipefd[0]);
        //将管道的写入端存入channel对象中
        ChildProc.push_back(Channel(string("Process ") + to_string(pid),pid,pipefd[1]));      
    }
}


//父进程轮询等待子进程退出
void WaitChildProc(const std::vector<Channel> & channels)
{
    //先关闭各个管道的写入端,相应的子进程会自动退出
    for(auto& e : channels)
    {
        close(e.Getfd());
    }
    //等待各个子进程退出
    for(auto & e : channels)
    {
        int Status = 0;
        waitpid(e.getpid(),&Status,0);
        cout << "写入端关闭,子进程:" << e.getpid() << "退出,退出码:"<< WIFEXITED(Status) << endl;
    }
}


int main()
{
    vector<task_t>Taskarr;
    LoadTask(Taskarr);
    srand(time(nullptr)^getpid()^1023);
    vector<Channel> ChildProc;
    InitProcessPool(ChildProc,Taskarr);
    ctrlSlaver(ChildProc,Taskarr);
    WaitChildProc(ChildProc);
    return 0;
}

在这里插入图片描述

  • 命名管道和匿名管道的内核原理相同

三.System-V共享内存

  • 共享内存通信原理:
    在这里插入图片描述
  • 构建共享内存通信环境的系统接口:
    • int shmget(key_t key, size_t size, int shmflg);
      • key是用户自定义共享内存标识键,用ftok接口获取
      • size是申请共享内存的大小
      • shmflg:取IPC_CREAT时,接口可以申请共享内存并获取共享内存的key,若参数指定的共享内存已存在则直接返回共享内存的key;取IPC_CREAT | IPC_EXCL时,接口只能用于申请新的共享内存.
    • void *shmat(int shmid, const void *shmaddr, int shmflg);
      • 接口作用:在当前进程的虚拟地址空间的共享区中为指定的共享内存块编址,并建立页表映射.
    • int shmdt(const void *shmaddr);
      • 接口作用:共享内存块与当前进程的虚拟地址空间取消关联,进程将无法再访问指定的共享内存
    • int shmctl(int shmid, int cmd, struct shmid_ds *buf);
      • 接口作用:对共享内存块进行cmd码指定的控制操作(比如释放操作),也可以用于获取共享内存块在内核中的描述信息
  • 共享内存通信环境中,由于多个进程可以对同一个内存块直接进行读写操作,因此,共享内存通信缺少同步互斥机制,无法保证数据的读写安全,为此,可以借助命名管道为共享内存通信提供读写控制.

共享内存和命名管道协同通信

  • 构建通信环境的接口头文件:
#ifndef __COMM_HPP__
#define __COMM_HPP__

#include "log.hpp"
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>

using namespace std;
const int SIZE = 4096;
const string pathname = "/home/user1/LinuxLearning/sharedMEM";
const int pro_id = 0x123456;

log LOG;


//获取自定义共享内存key
key_t GetKey(const string pathname,const int pro_id)
{
    //KEY生成器
    key_t K = ftok(pathname.c_str(),pro_id);
    if(K < 0)
    {
        LOG(Fatal,"GetKey Error, message: %s\n",strerror(errno));
        exit(-1);
    }
    LOG(Info,"key generated, message: %s\n",strerror(errno));
    return K;
}

//调用系统接口申请共享内存
int GetShareMemHelper(int flag)
{
    key_t KEY = GetKey(pathname,pro_id);
    //系统调用接口shmget申请共享内存或返回已存在的共享内存id
    int shmid = shmget(KEY,SIZE,flag);
    if(shmid == -1)
    {
        LOG(Fatal,"Get ShareMem failed, message: %s\n",strerror(errno));
        exit(-1);
    }
    LOG(Info,"Get ShareMem completed, message: %s\n",strerror(errno));
    return shmid;
}

//申请新的共享内存
int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}

//获取已存在的共享内存的id
int GetShm()
{
    return GetShareMemHelper(IPC_CREAT); 
}

#define FIFO_FILE "./myfifo"
#define MODE 0664

enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR
};

class Init
{
public:
    Init()
    {
        // 创建管道
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
        //管道去链接,若引用计数为0则删除管道文件
        int m = unlink(FIFO_FILE);
        if (m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};

#endif
  • 自制日志类log:
#pragma once
#include <time.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <stdlib.h>

//日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
//日志写入方式
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"


using std :: string;
class log
{
public:
    log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }

    string LeveltoString(int level)
    {
        switch (level)
        {
        case 0:
            return string("Info");
            break;
        case 1:
            return string("Debug");
            break;
        case 2:
            return string("Warning");
            break;
        case 3:
            return string("Error");
            break;
        case 4:
            return string("Fatal");
            break;
        default:
            break;
        }
    }


    void operator()(int level,char * format,...)
    {
        //将时间格式化存入tm结构体中
        time_t t = time(nullptr);
        struct tm* ctime = localtime(&t);
        char leftbuffer[1024];
        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d %d:%d:%d]",LeveltoString(level).c_str(),
                 ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_min,ctime->tm_sec);

        //解析可变参数
        va_list vls;
        va_start(vls,format);
        char rightbuffer[1024];
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,vls);
        va_end(vls);

        //合并时间和可变参数
        char logtxt[2048];
        snprintf(logtxt,sizeof(logtxt),"%s %s\n",leftbuffer,rightbuffer);

        //执行日志记录
        printLog(level,string(logtxt));
    }

    //日志信息写出接口
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            //将日志信息打印到标准输出
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            //将日志信息存入log.txt
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            //将日志信息存入指定的分类日志文件
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    //日志信息写到log.txt中
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        //path-->日志保存路径  logname-->日志文件名
        std::string _logname = path + logname;
        //打开"log.txt"日志文件
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); 
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    //日志信息写到log.txt.level中
    void printClassFile(int level, const std::string &logtxt)
    {
        //对日志文件名进行修改,根据日志等级分出多个日志文件
        std::string filename = LogFile;
        filename += ".";
        // "log.txt.Debug(Warning)(Fatal)"
        filename += LeveltoString(level); 
        printOneFile(filename, logtxt);
    }

    ~log(){}
private:
    int printMethod;
    std::string path;
};
  • 读端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"

extern log LOG;

int main()
{
    //创建管道和共享内存
    Init pipeCreate;
    int shmid = CreateShm();
    //建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址
    char * shmaddr = (char *)shmat(shmid,NULL,0);

    //打开管道文件
    int fd = open(FIFO_FILE,O_RDONLY);
    if(fd == -1)
    {
        LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);
        exit(FIFO_OPEN_ERR);
    }

    while(true)
    {
        //借助管道进行共享内存的读写控制,若写端没有给信号,则读端保持阻塞状态
        char c;
        int RSize = read(fd,&c,sizeof(c));
        if(RSize <=0) break;

        //直接访问共享内存,实现高效通信
        cout << "client say@ " << shmaddr << endl; 
        sleep(1);
    }

    //进程与共享内存断开连接
    shmdt(shmaddr);
    //将共享内存标记为已销毁
    shmctl(shmid,IPC_RMID,nullptr);

    close(fd);
    return 0;
}
  • 写端进程示例:
#include "log.hpp"
#include "ShareMemBuild.hpp"

extern log LOG;

int main()
{   
    //获取共享内存标识
    int shmid = GetShm();
    //建立共享内存与进程虚拟地址空间之间的映射,并获取共享内存的虚拟地址
    char * shmaddr = (char *)shmat(shmid,NULL,0);

    //打开管道文件
    int fd = open(FIFO_FILE,O_WRONLY);
    if(fd == -1)
    {
        LOG(Fatal, "error string: %s, error code: %d", strerror(errno), errno);
        exit(FIFO_OPEN_ERR);
    }
    while(true)
    {
        cout << "Please Enter@ ";
        //将信息写入共享内存
        fgets(shmaddr, 4096, stdin);
        //管道写入信号,解除读端的阻塞状态
        write(fd, "c", 1); 
    }
    //进程与共享内存断开连接
    shmdt(shmaddr);

    close(fd);
    return 0;
}

在这里插入图片描述

  • 多个进程直接通过各自的虚拟地址空间同一个内存块进行访问使得共享内存通信具有很高的通信效率.管道通信过程中,数据至少要经过两次拷贝(用户读写缓冲区和内核读写缓冲区之间的拷贝),而共享内存通信不存在通信数据拷贝问题
  • 共享内存,消息队列,信号量等通信内存资源(称为ipc资源)统一由操作系统描述为各种数据结构统一进行管理,在Linux内核中,描述共享内存,消息队列,信号量的结构体形成继承体系:(C语言实现的继承体系)在这里插入图片描述
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1245123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【linux】服务器CPU占用50%,top/htop/ps却看不到异常进程?使用unhide可以查看!

问题描述 htop发现前32个核全被占满了&#xff0c;但是却找不到对应进程号 查杀 安装unhide查看隐藏进程 apt-get install unhideunhide使用 unhide proc果然发现了隐藏进程 杀死隐藏进程 kill -9 [pid]这么多pid号&#xff0c;我这边杀了其中一个&#xff0c;发现CPU…

为IP地址申请SSL证书

SSL&#xff08;Secure Sockets Layer&#xff09;是一种网络协议&#xff0c;用于在浏览器与服务器之间建立安全、加密的连接。SSL证书是用于证明您的网站身份并启用HTTPS&#xff08;超文本传输安全协议&#xff09;的安全文件。这种协议可以确保用户与您的网站之间的所有通信…

PHP预约上门回收废品系统的代码披露

PHP预约上门回收废品系统的代码披露 <?phpnamespace app\admin\controller;class Code {public function getTopDomainhuo(){error_reporting(0);$host $_SERVER["HTTP_HOST"];$matchstr "[^\\.]\\.(?:(" . $host . ")|\\w{2}|((" . $ho…

程序环境和预处理(详解版)

我们已经学到这里&#xff0c;这就是关于C语言的最后一个集中的知识点了&#xff0c;虽然它比较抽象&#xff0c;但是了解这部分知识&#xff0c;可以让我们对C代码有更深层次的理解&#xff0c;知道代码在每一个阶段发生什么样的变化。让我们开始学习吧! 目录 1.程序的翻译环…

Excel使用VLOOKUP查询数据

VLOOKUP函数在百度百科中的解释是&#xff1a; 解释一下&#xff0c;函数需要4个参数&#xff1a; 参数1&#xff08;lookup_value&#xff09;&#xff1a;需要匹配的值参数2&#xff08;table_array&#xff09;&#xff1a;在哪个区域里进行匹配参数3&#xff08;col_index…

复费率电表和预付费电表有哪些区别?

随着科技的发展和能源管理的日益严格&#xff0c;电表技术也在不断更新换代。复费率电表和预付费电表作为两种主流的智能电表&#xff0c;各自具有独特的优势和应用场景。接下来&#xff0c;小编来为大家详细解析这两种电表的区别及其应用场景。 一、复费率电表 1.定义及工作原…

【LeetCode刷题】--67.二进制求和

67.二进制求和 方法&#xff1a;模拟计算 class Solution {public String addBinary(String a, String b) {StringBuilder ans new StringBuilder();int carry 0;for(int ia.length()-1,jb.length()-1;i>0||j>0;i--,j--){int sum carry;sum i >0 ? a.charAt(i) …

Linux:VMWare启动虚拟机导致电脑蓝屏并重启问题解决

情况描述&#xff1a; 我这边安装Ubuntu系统后&#xff0c;对Ubuntu进行了汉化操作&#xff0c;重启Ubuntu时&#xff0c;直接导致Windows10蓝屏重启。 解决办法&#xff1a; 1、打开任务管理器&#xff0c;检查Windows10系统是否开启虚拟化功能。 没有开启&#xff0c;就进入…

linux上的通用拍照程序

最近因为工作需要&#xff0c;在ubuntu上开发了一个拍照程序。 为了找到合适的功能研究了好几种实现方式&#xff0c;在这里记录一下。 目录 太长不看版 探索过程 v4l2 QT opencv4.2 打开摄像头 为什么不直接打开第一个视频节点 获取所有分辨率 切换摄像头 太长不看…

2020年06月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行以下脚本后舞台上的角色将 ? A:先克隆自身,克隆体出现后被删除。 B:先克隆自身,克隆体出现后删除本体。 C:克隆出自身后本体与克隆体同时被删除。 D:克隆出自身后本体与克…

Rola详解国外住宅IP代理选择的8个方法,稳定的海外IP哪个靠谱?

一、国外住宅IP代理是什么&#xff1f; 代理服务器充当您和互联网之间的网关。它是一个中间服务器&#xff0c;将最终用户与他们浏览的网站分开。如果您使用国外代理IP&#xff0c;互联网流量将通过国外代理服务器流向您请求的地址。然后&#xff0c;请求通过同一个代理服务器…

python opencv 放射变换和图像缩放-实现图像平移旋转缩放

python opencv 放射变换和图像缩放-实现图像平移旋转缩放 我们实现这次实验主要用到cv2.resize和cv2.warpAffine cv2.warpAffine主要是传入一个图像矩阵&#xff0c;一个M矩阵&#xff0c;输出一个dst结果矩阵&#xff0c;计算公式如下&#xff1a; cv2.resize则主要使用fx&…

python之TCP的网络应用程序开发

文章目录 版权声明python3编码转换socket类的使用创建Socket对象Socket对象常用方法和参数使用示例服务器端代码客户端代码 TCP客户端程序开发流程TCP服务端程序开发流程TCP网络应用程序注意点socket之send和recv原理剖析send原理剖析recv原理剖析send和recv原理剖析图 多任务版…

【管理运筹学】背诵手册(五)| 动态规划

五、动态规划 基本概念 阶段&#xff08;Stage&#xff09;&#xff1a;将所给问题的过程&#xff0c;按时间或空间特征分解成若干相互联系的阶段&#xff0c;以便按次序去求解每阶段的解&#xff0c;常用字母 k k k 表示。 状态&#xff08;State&#xff09;&#xff1a;…

iOS APP包分析工具 | 京东云技术团队

介绍 分享一款用于分析iOSipa包的脚本工具&#xff0c;使用此工具可以自动扫描发现可修复的包体积问题&#xff0c;同时可以生成包体积数据用于查看。这块工具我们团队内部已经使用很长一段时间&#xff0c;希望可以帮助到更多的开发同学更加效率的优化包体积问题。 工具下载…

HTB Codify WriteUp

Codify 2023年11月7日 20:59:48user nmap ➜ Codify nmap -A 10.10.11.239 Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-07 21:00 CST Nmap scan report for bogon (10.10.11.239) Host is up (0.14s latency). Not shown: 997 closed ports PORT STATE SERVI…

An issue was found when checking AAR metadata

一、报错信息 An issue was found when checking AAR metadata:1. Dependency androidx.activity:activity:1.8.0 requires libraries and applications that depend on it to compile against version 34 or later of the Android APIs.:app is currently compiled against …

spark的算子

spark的算子 1.spark的单Value算子 Spark中的单Value算子是指对一个RDD中的每个元素进行操作&#xff0c;并返回一个新的RDD。下面详细介绍一些常用的单Value算子及其功能&#xff1a; map&#xff1a;逐条映射&#xff0c;将RDD中的每个元素通过指定的函数转换成另一个值&am…

009 OpenCV 二值化 threshold

一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、二值化算法 2.1、概述 在机器视觉应用中&#xff0c;OpenCV的二值化函数threshold具有不可忽视的作用。主要的功能是将一幅灰度图进行二值化处理&#xff0c;以此大幅降低图像的数…

app小程序定制的重点|软件定制开发|网站搭建

app小程序定制的重点|软件定制开发|网站搭建 App小程序定制开发是近年来快速发展的一项技术服务&#xff0c;随着移动互联网的普及和用户需求的不断升级&#xff0c;越来越多的企业和个人开始关注和需求定制化的小程序开发。那么&#xff0c;对于app小程序定制开发来说&#xf…