当前位置: 首页 > news >正文

Linux的进程间通信

目录

前言:

1、进程间通信的介绍

1.1 进程间通信的目的

1.2 进程间通信的发展

2、管道

2.1 管道通信的四种情况

2.2 匿名管道

2.3 基于匿名管道的进程池

2.3.1 Process.hpp

2.3.2 Task.hpp

2.4 命名管道

2.5 管道的特性

3、System V IPC

3.1 System V 共享内存

3.1.1 shmget()

3.1.2 shmat()&&shmdt()

3.1.3 shmctl()

3.2 内核中System V IPC资源的组织管理


前言:

  1. 作者的环境切换为Ubuntu 20.04,创建用户后,root用户vim /etc/sudoers,
  2. 用户需要sudo usermod -s /bin/bash 用户名。
  3. 通过Linux的基础开发工具,把远程仓库克隆到本地
  4. 语言使用C/C++,因为系统调用是C写的。
  5. VSCode下载Remote - SSH插件,远程连接服务器ssh 用户名@IP地址,再选择第一个配置文件。
  6. 使用VSCode的代码编辑器代替vim,并通过VSCode的bash终端(ctrl+`)和Xshell的bash终端进行操作。

1、进程间通信的介绍

1.1 进程间通信的目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或组组进程发送消息,通知它(它们)发生了某种事件(如子进程退出时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

注意:

进程间通信前提让不同的进程先看到同一份资源("内存")

1.2 进程间通信的发展

进程间通信(IPC,Inter-Process Communication)

管道:

  • 匿名管道。
  • 命名管道。

System V IPC

  • System V 消息队列。
  • System V 共享内存。
  • System V 信号量。

POSIX IPC:

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

本篇文章,重点介绍 管道 和 system V 共享内存。

2、管道

基于文件

2.1 管道通信的四种情况

  1. 写慢,读快。读端阻塞。
  2. 写快,读慢。满了,写端阻塞。
  3. 写关闭,读继续。读到文件结尾,返回0。
  4. 读关闭,写继续。无意义,OS信号杀死。

2.2 匿名管道

  • int pipe(int pipefd[2]);建立一个匿名管道文件,以读和写的方式打开,将匿名管道文件的读写描述符分别写到pipedf[0],pipefd[1]中,成功返回0,失败返回-1。
  • 匿名管道文件是内存级的(没有路径,没有文件名,不需要保存到磁盘)。
  • 匿名管道文件只能用于具有血缘关系的进程间通信(通常用于父子间通信)。
  • 匿名管道文件的生命周期,是随进程的。所有进程关闭了该管道的文件描述符(引用计数降为 0),释放资源

2.3 基于匿名管道的进程池

父子间通信为例,父进程写子进程读完成指定任务

注意:

代码中想以,父进程写端关闭,子进程读继续,子进程读到文件结尾,返回0,子进程退出,回收子进程。

但是,继承下来的子进程,也会有哥哥进程的匿名管道文件的写端描述符,只要存在写端,对应子进程读段就会一直阻塞,所以子进程关闭自己写端时,也要关闭哥哥的写端。才能实现父进程写端关闭,子进程退出

2.3.1 Process.hpp
#pragma once#include <iostream>
#include <cstdio>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include "Task.hpp"#define CHILD_PROCESS_NUM 3
#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)class Channel // 子进程的pid,父进程对应该子进程的匿名管道的写端
{
public:Channel(pid_t chpid, int wfd): _chpid(chpid), _wfd(wfd){}pid_t getpid(){return _chpid;}int getwfd(){return _wfd;}void Close(){close(_wfd);}void Wait(){waitpid(_chpid,nullptr,0);}private:pid_t _chpid;int _wfd;
};class ChannelManager
{
public:void InsertChannel(pid_t pid, int wfd){channels.emplace_back(pid, wfd);}int wfd(int index){return channels[index % channels.size()].getwfd();}int pid(int index){return channels[index % channels.size()].getpid();}void Printf(){for (auto e : channels)printf("child pid : %d , wfd : %d\n", e.getpid(), e.getwfd());}void Close(){for (auto e : channels)e.Close();}void Wait(){for (auto e : channels)e.Wait();}private:std::vector<Channel> channels;
};class ProcessPool
{
public:ProcessPool(int num): _child_process_num(num){_tm.RegisterTask(func1);_tm.RegisterTask(func2);_tm.RegisterTask(func3);}void Work(int rfd){while (true){int code = 0;int n = read(rfd, &code, sizeof(int));if (n == 0)break;else if (n == 4){_tm.ExecuteTask(code);}else{continue;}}}void Create(){for (int i = 0; i < _child_process_num; ++i){int fds[2] = {0};int n = pipe(fds);if (n != 0)ERR_EXIT("pipe");int pid = fork();if (pid == -1)ERR_EXIT("fork");else if (pid == 0){// childclose(fds[1]); // 关闭自己的写端_cm.Close(); // 关闭哥哥进程的写端Work(fds[0]);close(fds[0]);exit(0);}else{// parentclose(fds[0]);_cm.InsertChannel(pid, fds[1]);}}std::cout << "Create Success" << std::endl;_cm.Printf();}void Run(){int i = 0;while (true){sleep(1);int code = _tm.Code();std::cout << "发送child " << _cm.pid(i) << " 一个任务码" << code << std::endl;write(_cm.wfd(i), &code, sizeof(int));i++;}}~ProcessPool(){// 关闭父进程的写端即可,子进程都读到结尾,退出。_cm.Close();// 回收子进程。_cm.Wait();}private:int _child_process_num;ChannelManager _cm;TaskManager _tm;
};
2.3.2 Task.hpp
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <functional>void func1()
{std::cout<<"***** 打开数据库 *****"<<std::endl;
}void func2()
{std::cout<<"***** 打开日志 *****"<<std::endl;
}void func3()
{std::cout<<"***** 运行Hello World *****"<<std::endl;
}class TaskManager
{
public:TaskManager(){srand(time(nullptr));}void RegisterTask(std::function<void()> f){_tasks.push_back(f);}int Code(){return rand() % _tasks.size();}void ExecuteTask(int code){if(code >= 0 && code < _tasks.size())_tasks[code]();}private:std::vector<std::function<void()>> _tasks;
};

2.4 命名管道

  • int mkfifo(const char *filename,mode_t mode);创建一个命名管道文件filename(可指定路径),mode指定文件的权限,成功返回0,失败返回-1。
  • unlink(const char *filename);会立即移除命名管道在文件系统中的路径(即filename不再可见),但不会影响已经打开该管道的进程。成功返回0,失败返回-1。
  • 命名管道文是内存级的(有路径,有文件名,但是数据不需要刷新到磁盘)。
  • 命名管道文件用于不同的进程间通信(通常用于父子间通信)。
  • unlink后,所有进程关闭了该管道的文件描述符(延迟到引用计数降为 0),释放资源

2.5 管道的特性

  • 管道文件用于单向通信,属于半双工(一发一收)。通常收发在一开始就确定了,不能改。
  • 管道文件,自带同步机制(一发一收,有顺序)。
  • 管道文件,面向字节流(没有按特定的序列读写)。
  • 管道文件,大小通常为64KB
  • 管道文件的写入的数据 <= PIPE_BUF(通常为4KB) 时,具有原子性(数据要么全部写入,要么完全不写入,不会被其他进程的写入穿插)。

    3、System V IPC

    3.1 System V 共享内存

    共享内存区最快的IPC形式。因为不需要系统调用。但是没有“保护机制”,数据读取随意

    3.1.1 shmget()

    int shmget(key_t key, size_t size, int shmflg);

    • key用户层共享共享内存标识符,在用户层,让不同进程打开同一个共享内存。因为内核的共享内存的shmid是运行后才有的,此时不能通信,给不了id,不能指向同一个共享内存。共享内存是内存级,没有名字,用key标识。一般使用ftok,创建一个唯一的key。
    • size共享内存的大小。共享内存的大小为4KB的整数倍,但是申请了多少,就给多少,如:申请4097B,共享内存在内核中为8KB,但是只给你4097B。
    • shmflgIPC_CREAT,不存在,就创建,存在,就打开。用于打开。;IPC_CREAT|IPC_EXCL|权限,不存在,就创建,存在,就报错。用于创建
    • 成功返回shmid,失败返回-1。

    注意:

    共享内存的其他操作都使用内核共享内存的shmid,不使用key

    3.1.2 shmat()&&shmdt()
    • void *shmat(int shmid, const void *shmaddr, int shmflg);将共享内存挂接到进程的虚拟地址空间,成功返回起始虚拟地址,失败返回(void*)-1。shmaddr是设置其实虚拟地址的位置,一般用不上,传NULL就行。shmflg当前进程的映射行为(仅自身有效),通常传0,可读可写。
    • int shmdt(const void *shmaddr);去共享内存的关联。共享内存的引用计数为0,释放资源。
    3.1.3 shmctl()
    • int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    命令用途使用频率
    IPC_STAT获取共享内存的状态信息(如 shm_segszshm_nattch)。高(调试/监控)
    IPC_SET修改共享内存的某些属性(如权限 shm_perm)。中(需谨慎)
    IPC_RMID标记删除共享内存实际释放需等待引用计数归零)。高(资源清理)
    • ipcs -m shmid,查看共享内存。
    • ipcrm -m shmid,删除指定的共享内存。

    3.2 内核中System V IPC资源的组织管理

    • System V 是一个标准,Linux支持了这种标准,并专门设计了一个IPC模块。
    • 共享内存,消息队列,信号量(本质是一个计数器,描述的是临界(共享)资源中,资源的数量),是System V IPC的三种核心机制,接口使用类似都有struct ipc_perm* -> 独立的数据结构 struct shmid_ds*, struct msqid_ds*, struct semid_ds*(通过强制类型转换,实现多态)key区分唯一(不能使用相同的key)。
    http://www.lryc.cn/news/613317.html

    相关文章:

  1. 嵌入式学习硬件(一)ARM体系架构
  2. 简单手写Transformer:原理与代码详解
  3. Java中的反射机制
  4. 土壤盐分传感器与土壤电导率传感器直接的关系
  5. 深入理解String类:揭秘Java字符串常量池的优化机制
  6. 【2025最新版】火狐浏览器(官方版)安装-附教程
  7. 飞算JavaAI深度解析:Java开发者的智能革命
  8. AUTOSAR进阶图解==>AUTOSAR_EXP_BSWDistributionGuide
  9. 损耗对信号质量的影响
  10. Java 八大经典排序算法全解析
  11. 数组指针-函数指针-回调函数
  12. 人工智能——自动微分
  13. Docker容器部署harbor-小白级教学
  14. Dlib库是什么?白话,详细介绍版
  15. python中用xlrd、xlwt读取和写入Excel中的日期值
  16. GIT操作卡顿
  17. 机器学习核心算法与实践要素(全篇)
  18. java excel转图片常用的几种方法
  19. 玳瑁的嵌入式日记D14-0807(C语言)
  20. NVIDIA/k8s-device-plugin仓库中GPU无法识别问题的issues分析报告
  21. Linux学习记录 DNS
  22. LocalSqueeze(图片压缩工具) v1.0.4 压缩
  23. nlp-句法分析
  24. ClickHouse数据迁移
  25. Redis持久化存储
  26. 【网络运维】Linux:NFS服务器原理及配置
  27. ansible-playbook之获取服务器IP存储到本地文件
  28. Linux---第三天---权限
  29. Idea打包可执行jar,MANIFEST.MF文件没有Main-Class属性:找不到或无法加载主类
  30. 3a服务器的基本功能1之身份认证