【Linux】进程间通信 —— 管道与 System V 版本通信方式

目录

为什么有进程间通信?进程间通信的目的是什么?

管道

匿名管道

父子进程共享管道

命名管道

共享内存

概念

原理

共享内存和内存映射(文件映射)的区别

使用

消息队列

概念

使用

信号量

概念

使用

IPCS 命令

System V 版本进程通信内核表示


为什么有进程间通信?进程间通信的目的是什么?

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

管道

什么是管道?管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

管道怎么实现的?内核环形缓冲区

优点:实现简单,管道通信不需要像消息队列、共享内存等进程通信方式那样对系统资源进行复杂管理,实现起来较为简单方便。

缺点:

  1. 通信方式效率低,不适合进程间频繁地交换数据。
  2. 数据是无格式的流且大小受限,Ubuntu 20.04下管道大小为 65536 B = 64 KB
  3. 通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道。

匿名管道

特点:

  1. 自带同步机制。
  2. 匿名管道是只能用于存在父子关系的进程间通信。
  3. pipe 是面向字节流的。
  4. 没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中。
  5. 生命周期随着进程创建而建立,随着进程终止而消失。父子进程退出,管道自动释放,因为管道文件的生命周期是随进程的
  6. 管道只能单向数据通信的,这就意味着管道是半双工的一种特殊情况。

匿名管道使用的四种情况:

  1. 管道内部没有数据 && 子进程(写端)不关闭自己的写端文件fd,读端(父进程)就要阻塞等待,直到pipe有数据。
  2. 管道内部被写满 && 父进程(读端)不关闭自己的读端文件fd,写端(子进程)写满之后就要阻塞等待。这个大小在Ubuntu 20.04为 65536 B = 64 KB。
  3. 写端不写了 && 关闭了pipe,读端会将pipe中的数据读完,最后就会读取到返回值0,表示读结束。
  4. 读端不读了 && 关闭了pipe,写端再写就无意义了,OS会直接终止写入的进程(子进程),通过信号13) SIGPIPE杀掉进程。

此外,pipe 内部定义了 PIPE_BUF = 4 KB,PIPE_BUF 的定义见 <limits.h>

  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性;
  • 当要写入的数据量大于PIPE_BUF时,linux将不保证写入的原子性。

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

父子进程共享管道

使用 pipe 创建匿名管道,然后父进程调用 close(fd[1]) 关闭写段,子进程调用 close(fd[0]) 关闭读端,就可以实现父子进程通信。

示例代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>void writer(int wfd)
{const char *str = "hello father, I am child";char buffer[128];int cnt = 0;pid_t pid = getpid();while(1){snprintf(buffer, sizeof(buffer), "message: %s, pid: %d, count: %d\n", str, pid, cnt);write(wfd, buffer, strlen(buffer));cnt++;sleep(1);}
}void reader(int rfd)
{char buffer[1024];while(1){ssize_t n = read(rfd, buffer, sizeof(buffer)-1);(void)n;printf("father get a message: %s", buffer);}
}int main()
{// 1. int pipefd[2];int n = pipe(pipefd);if(n < 0) return 1;printf("pipefd[0]: %d, pipefd[1]: %d\n", pipefd[0]/*read*/, pipefd[1]/*write*/);// 2. pid_t id = fork();if(id == 0){//child: wclose(pipefd[0]);writer(pipefd[1]);exit(0);}// father: rclose(pipefd[1]);reader(pipefd[0]);wait(NULL);return 0;
}

命名管道

在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。

调用接口

#include <sys/types.h>

#include <sys/stat.h>

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

#include <fcntl.h>           /* Definition of AT_* constants */

#include <sys/stat.h>

int mkfifoat(int dirfd, const char *pathname, mode_t mode);

#include <unistd.h>

int unlink(const char *pathname);

共享内存

概念

共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存可以存在很多个,OS必须对这些共享内存进行管理 --- 先描述,再组织!

共享内存数据结构如下:

struct shmid_ds 
{struct ipc_perm		shm_perm;	/* operation perms */int			shm_segsz;	/* size of segment (bytes) */__kernel_time_t		shm_atime;	/* last attach time */__kernel_time_t		shm_dtime;	/* last detach time */__kernel_time_t		shm_ctime;	/* last change time */__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */unsigned short		shm_nattch;	/* no. of current attaches */unsigned short 		shm_unused;	/* compatibility */void 			*shm_unused2;	/* ditto - used by DIPC */void			*shm_unused3;	/* unused */
};

原理

  1. 拿出一块虚拟地址空间来,映射到相同的物理内存中。

  2. 解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问。

  3. 带来新的问题,多进程竞争同个共享资源会造成数据的错乱。解决方式:1. 信号量 2. 条件变量 + 锁。

共享内存和内存映射(文件映射)的区别

共享内存:利用共享内存完成进程间通信,两个进程通过虚拟内存到用户级页表,然后通过用户级页表最后访问同一块物理内存,实现进程间通信。

mmap:

  • mmap 是在磁盘上建立一个文件,每个进程地址空间中开辟出一块空间进行映射。而 shm 共享内存,每个进程最终会映射到同一块物理内存
  • mmap 会把数据同步到文件中(会有缓存机制),shm不会把数据写入文件,这意味着 mmap 重启不会丢失共享数据。
  • mmap 共享数据需要通过文件,io 效率要比 shm 的内存读写肯定要低很多。
  • 内存空间比磁盘空间小很多,所以使用 mmap 可以共享的数据比 shm 大很多。

关于 mmap 以及 malloc / free 可查看以下文章:【C语言】一文详解 malloc / free 分配内存和释放内存相关问题-CSDN博客

使用

头文件

#include <sys/ipc.h>
#include <sys/shm.h>

shmget

功能:用来创建共享内存
原型
        int shmget(key_t key, size_t size, int shmflg);
参数
        key:这个共享内存段名字
        size:共享内存大小
        shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat

功能:将共享内存段连接到进程地址空间
原型
        void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
        shmid: 共享内存标识
        shmaddr:指定连接的地址
        shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

说明

        shmaddr为NULL,核心自动选择一个地址
        shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
        shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
        shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt

功能:将共享内存段与当前进程脱离
原型
        int shmdt(const void *shmaddr);
参数
        shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl

功能:用于控制共享内存
原型
        int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
        shmid:由shmget返回的共享内存标识码
        cmd:将要采取的动作(有三个可取值),见下图
        buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

消息队列

概念

实际上是保存在内核的消息链表,消息队列的消息体是可以用户自定义的数据类型。

优点

  1. 可以频繁通信了
  2. 可以独立于读写进程存在,避免了 FIFO 中同步管道打开和关闭可能产生的困难
  3. 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法
  4. 读进程可以根据消息消息类型有选择的接受消息,而不是像 FIFO 那样默认接受

缺点

  1. 通信不及时,每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。
  2. 消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。

消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在。

使用

头文件

#include <sys/types.h>
#include <sys/ipc.h>

#include <sys/msg.h>

msgget

功能:获取消息队列标识符

原型

         int msgget(key_t key, int msgflg);

msgctl

功能:用于控制消息队列

原型

       int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgsnd

功能:用于发送数据给消息队列

原型

       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

msgrcv

功能:用于从消息队列接收数据       

原型

        ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

信号量

概念

对于共享资源进行保护,是一个多执行流场景下,一个比较常见和重要的话题。

互斥:在访问一部分共享资源的时候,任何时刻只有一个进程访问。

同步:访问资源在安全的前提下,具有一定的顺序性。

临界资源:被保护起来的,任何时刻只允许一个进程进行访问的公共资源。

临界区:访问临界资源的代码。

信号量本质上是一个描述临界资源数量的计数器,对公共局部临界资源的预定机制,用来保护临界资源

假设用 count 描述临界资源的数量。

可以将信号量申请看作预定资源,if (count > 0) count--; else wait;

将信号量释放看作释放资源,此临界资源可以被其他进程申请,count++;

但是,用 int 类型定义 count 不能实现信号量的效果。

原因:

1. 无法在进程间共享,对 count 的操作会触发写时拷贝,于是就要让进程提前看到一部分资源 --- 信号量这种计数器资源。

2. count++,count-- 的操作不是原子的。

所有的资源要访问临界资源,都必须先申请信号量,那么所有进程都得先看到同一个信号量,同时,信号量本身就是共享资源!信号量的申请和释放就必须是原子性的。信号量申请是P操作,信号量释放是V操作,信号量的申请释放称为PV操作。

如果信号量初始值是1呢?代表将临界资源看作整体,来实现互斥,二元信号量就是一把锁。

使用

头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

功能:创建信号量集标识符

int semget(key_t key, int nsems, int semflg);

功能:op是operation的缩写,对指定信号量进行操作

int semop(int semid, struct sembuf *sops, size_t nsops);
 

功能:对指定信号量进行控制

int semctl(int semid, int semnum, int cmd, ...);

该函数参数数量为 3 或 4,取决于 cmd 参数,第 4 个参数是一个 union 类型变量,如下图:

IPCS 命令

ipcs [options]

options:

-m

查看系统中的共享内存

-q

查看系统中的消息队列

-s

查看系统中的信号量

-a

查看当前使用的共享内存、消息队列及信号量所有信息

-p

得到与共享内存、消息队列相关进程之间的消息

-u

查看各个资源的使用总结信息

-l

查看各个资源的系统限制信息

ipcrm [options] <id>

options:

-m <shmid>

删除 shmid 对应的共享内存

-q <msgid>

删除 msgid 对应的消息队列

-s <semid>

删除 semid 对应的信号量

-a

删除所有进程间通信的资源

System V 版本进程通信内核表示

/* used by in-kernel data structures */
struct kern_ipc_perm
{spinlock_t	lock;int		deleted;key_t		key;uid_t		uid;gid_t		gid;uid_t		cuid;gid_t		cgid;mode_t		mode; unsigned long	seq;void		*security;
};struct ipc_id_ary {int size;struct kern_ipc_perm *p[0];
};struct shmid_kernel /* private to the kernel */
{	struct kern_ipc_perm	shm_perm;struct file *		shm_file;int			id;unsigned long		shm_nattch;unsigned long		shm_segsz;time_t			shm_atim;time_t			shm_dtim;time_t			shm_ctim;pid_t			shm_cprid;pid_t			shm_lprid;struct user_struct	*mlock_user;
};struct msg_queue {struct kern_ipc_perm q_perm;time_t q_stime;			/* last msgsnd time */time_t q_rtime;			/* last msgrcv time */time_t q_ctime;			/* last change time */unsigned long q_cbytes;		/* current number of bytes on queue */unsigned long q_qnum;		/* number of messages in queue */unsigned long q_qbytes;		/* max number of bytes on queue */pid_t q_lspid;			/* pid of last msgsnd */pid_t q_lrpid;			/* last receive pid */struct list_head q_messages;struct list_head q_receivers;struct list_head q_senders;
};struct sem_array {struct kern_ipc_perm	sem_perm;	/* permissions .. see ipc.h */time_t			sem_otime;	/* last semop time */time_t			sem_ctime;	/* last change time */struct sem		*sem_base;	/* ptr to first semaphore in array */struct sem_queue	*sem_pending;	/* pending operations to be processed */struct sem_queue	**sem_pending_last; /* last pending operation */struct sem_undo		*undo;		/* undo requests on this array */unsigned long		sem_nsems;	/* no. of semaphores in array */
};

共享内存、消息队列、信号量的部分源码如上,可以看到:

共享内存、消息队列、信号量 --- 存在共性,这是操作系统故意为之的,用于 system V 进程间通信的,因为操作系统注定要对 IPC 资源进行管理,如何管理?先描述,再组织

内核中,所有描述管理 IPC 资源的结构体,第一个成员都一样 --- kern_ipc_perm,然后定义出了一个与文件描述符表毫无关系的 ipc_id_ary 结构体,里面包含申请到的 kern_ipc_perm 和 对应的个数,用于管理 IPC 资源,而这种设计方式与文件操作进行了隔离,有悖于 Linux 里的一切皆文件所以这种技术后来逐渐被替代了

这种设计方式也有优点,用C语言实现了高级语言里的多态!!

(shmid_kernel*)ipc_array[0]->shm_nattch;
(msg_queue*)ipc_array[1]->q_stime;
(sem_array*)ipc_array[2]->sem_pending;

那么我怎么知道 ipc_array 里 i 号下标的是什么资源呢?

#define IPC_SHM_TYPE 1 << 0
#define IPC_MSG_TYPE 1 << 1
#define IPC_SEM_TYPE 1 << 2auto GetKernIpcPerm(kern_ipc_perm* p)
{if (p->mode & IPC_SHM_TYPE)return (shmid_kernel*) p;else if (p->mode & IPC_MSG_TYPE)return (msg_queue*) p;else if (p->mode & IPC_SEM_TYPE)return (sem_array*) p;
}

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

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

相关文章

Docker Compose方式部署Ruoyi-前后端分离版本

目录 一. 环境准备 二. 制作一个jdk8u202环境的镜像 三. 制作nginx镜像 四. 对项目文件做修改 五. 项目打包 1. 前端打包 2. 后端打包 六. 编写docker-compose.yml 一. 环境准备 主机名IP系统软件版本配置信息localhost192.168.226.25Rocky_linux9.4 git version 2.…

码农职场:一本专为IT行业求职者量身定制的指南

目录 写在前面 推荐图书 推荐理由 写在后面 写在前面 本期博主给大家推荐一本专为IT行业求职者量身定制的指南&#xff1a;《码农职场》。 推荐图书 https://item.jd.com/14716160.html 内容简介 这是一本专为广大IT 行业求职者量身定制的指南&#xff0c;提供了从职前…

黑马JavaWeb后端案例开发(包含所有知识点!!!)

目录 1.准备工作 环境搭建 开发规范 REST&#xff08;REpresentation State Transfer&#xff09;,表述性状态转换&#xff0c;它是一种软件架构风格 注意事项 统一响应结果 2.部门管理功能 查询部门 删除部门 新增部门 RequestMapping 3.员工管理功能 分页查询 批…

单细胞|MEBOCOST·基于代谢物的细胞通讯预测(一)

import os,sys import scanpy as sc import pandas as pd import numpy as np from matplotlib import pyplot as plt import seaborn as sns from mebocost import mebocost 1. 创建 mebocost 对象 adata sc.read_h5ad(data/demo/raw_scRNA/demo_HNSC_200cell.h5ad) ## che…

开发无人带货直播插件

在当今快速发展的电商行业中&#xff0c;直播带货已成为推动销售增长的重要力量&#xff0c;然而&#xff0c;随着直播市场的日益饱和和消费者需求的不断变化&#xff0c;如何在保持直播互动性的同时&#xff0c;实现高效、低成本的运营成为许多商家关注的焦点。 无人带货直播…

springboot 微信消息推送 springboot sse推送

目录 一、springboot 微信消息推送 springboot sse推送 1、在 Spring 框架中实现 2、传统的 Servlet 中实现 一、springboot 微信消息推送 springboot sse推送 关于 SSE SSE 全程 Server Send Event&#xff0c;是 HTTP 协议中的一种&#xff0c;Content-Type 为 text/event…

Android 自定义圆形进度条样式

效果 代码 主要是设置属性indeterminateDrawable <ProgressBarandroid:id"id/iv_progress"android:layout_width"20dp"android:layout_height"20dp"android:layout_gravity"center"android:layout_marginStart"15dp"and…

清爽简洁!这可能是开源界功能最强大的项目开发管理系统

&#x1f482; 个人网站: IT知识小屋&#x1f91f; 版权: 本文由【IT学习日记】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 文章目录 写在前面项目简介项目特点设计思想技术栈项目展示项目获取 写在前…

❤️‍FlyFlow:新增表格布局表单

FlyFlow 介绍 官网地址&#xff1a;www.flyflow.cc ElementPlus演示网址&#xff1a;pro.flyflow.cc AntDesign演示网址&#xff1a;ant.flyflow.cc FlyFlow 借鉴了钉钉与飞书的界面设计理念&#xff0c;致力于打造一款用户友好、快速上手的工作流程工具。相较于传统的基于 …

21. Hibernate 性能之数据库连接池

1. 前言 从本节课程开始&#xff0c;和大家一起聊聊 Hibernate 中的性能问题&#xff0c;面对开发者&#xff0c;Hibernate 表现出卓越的数据库操作能力。 使用框架最大的优势就是带来操作的快捷、便利。同时&#xff0c;因为框架的封装性&#xff0c;其性能往往比原生开发要…

【PostGresql】---- pgSql 将列中合并字符串拆分为多行 实例代码

-- 将 AQY_ID,AQY 中的字符串拆分为多行 SELECT"ID","AQY_ID","AQY",UNNEST ( string_to_array( "AQY_ID", , ) ) AS "AQY_ID_1",UNNEST ( string_to_array( "AQY", , ) ) AS "AQY_1" FROM"JF_SGC…

30.jdk源码阅读之ReentrantReadWriteLock

1.写在前面 ReentrantReadWriteLock 是 Java 并发包中的一个读写锁实现&#xff0c;它允许多个读线程同时访问共享资源&#xff0c;但在写线程访问时&#xff0c;所有的读线程和其他写线程都会被阻塞。不知道大家在日常工作中这个类使用的多不多&#xff0c;对于它的底层实现有…

Tooltip 文字提示

在偶然维护前端开发时,遇到页面列表中某个字段内容太长,且该字段使用了组件显示,导致不能使用纯文本得那个省略号代替显示得css样式效果,如下 所以只能另辟溪路了, 1、最开始想到是使用横向滚动得效果来实现,但是实现后,感觉还是不太理想,因为用户注意不到你这里有滚动…

【基础篇】Docker 容器操作 FOUR

嘿&#xff0c;小伙伴们&#xff01;我是小竹笋&#xff0c;一名热爱创作的工程师。在上一篇文章中&#xff0c;我们探讨了 Docker 镜像管理的相关知识。今天&#xff0c;让我们一起深入了解一下 Docker 容器的操作吧&#xff01; &#x1f4e6; 运行、停止和删除容器 Docker…

一个私有化的中文笔记工具个人知识库,极空间Docker部署中文版『Trilium Notes』

一个私有化的中文笔记工具&个人知识库&#xff0c;极空间Docker部署中文版『Trilium Notes』 哈喽小伙伴们好&#xff0c;我是Stark-C~ 最近被很多小伙伴问到NAS上的笔记工具&#xff0c;虽说之前也出过Memos&#xff0c;刚开始用起来还不错&#xff0c;但是用了一段时间…

【JKI SMO】框架讲解(六)

接JKI SMO 框架讲解&#xff08;五&#xff09;&#xff0c;现在对代码进行一个扩展&#xff0c;当前代码仅有一路电压采集&#xff0c;现在需要扩展一路电流采集通道。 下面是对应的步骤&#xff1a; 1.打开项目&#xff0c;在工具里打开SMO Editor。 2.之前创建的SMO会自动加…

快速收集地图商户信息_百度|高德|腾讯|google

数字化营销中企业名录和商家电话号码的采集已成为营销人员日常工作的首要一环。地图平台以其海量的商家信息和实时更新的特性&#xff0c;成为我们获取数据的宝贵渠道。如何快速利用百度、高德、腾讯这三大地图平台高效采集商家联系方式是每个营销人员的必备技能。 我们整理了…

记录某次“有趣的“挖矿木马排查

挖矿木马是什么&#xff1f; 挖矿木马是一种恶意软件&#xff0c;它在用户不知情或未经同意的情况下&#xff0c;利用受害者的计算机资源进行加密货币挖矿。这类软件通过执行大量运算来挖掘数字货币&#xff0c;如比特币或门罗币等。挖矿木马通常通过漏洞利用、弱口令爆破或非…

微软蓝屏事件:全球网络安全与系统稳定性的警示

文章目录 每日一句正能量前言探讨软件更新流程中的风险管理和质量控制机制软件更新的风险风险管理策略质量控制措施测试流程缺陷识别实施质量控制结论 提供预防类似大规模故障的最佳方案或应急响应对策设计冗余系统实施灾难恢复计划建立高可用架构应急响应对策利用自动化工具和…

豆瓣9.9分!大名鼎鼎的深度学习入门书“鱼书”更新第二版了!带你深刻理解神经网络!

大家应该都知道这本非常有名的著作吧&#xff1a;《深度学习入门&#xff0c;基于python的理论与实现》。 这是更新的第二版&#xff0c;是由日本作者斋藤康毅所著的一本关于深度学习的书籍。这本书的出版社是人民邮电出版社&#xff0c;出品方是图灵教育。这本书的原名是“ゼロ…