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

【Linux修行路】进程通信——消息队列、信号量

目录

⛳️推荐

一、消息队列

1.1 实现原理

1.2 消息队列接口

1.2.1 msgget——创建、获取一个消息队列

1.2.2 msgctl——释放消息队列、获取消息队列属性

1.2.3 msgsnd——发送数据

1.2.4 msgrcv——从消息队列中检索数据块

1.3 消息队列的指令操作

二、信号量

2.1 数据不一致问题、互斥、临界资源、临界区

2.2 理解信号量

2.3 二元信号量

2.4 信号量也是共享资源

2.5 PV操作

2.6 总结

2.7 信号量的接口

2.8 信号量凭什么是进程通信的一种?

三、内核中对 IPC 资源的管理

3.1 三种描述 IPC 资源的结构体

3.2 操作系统对 IPC 资源的管理


⛳️推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站【Linux修行路】动静态库详解点击跳转到网站

一、消息队列

1.1 实现原理

由操作系统在内核中创建一个队列,A、B两个进程以数据块的形式将需要发送的数据链接到队列中,为了区分消息队列中的数据块是谁发送的,因此数据块本质上是一个结构体,它里面有一个类型字段用来该标识数据块是由哪个进程发送的。

image-20240307090201559

1.2 消息队列接口

1.2.1 msgget——创建、获取一个消息队列

image-20240307090903999

其中第一个参数 key 和共享内存中的那个 key 一样,也是通过 ftok 函数来获取。

1.2.2 msgctl——释放消息队列、获取消息队列属性

image-20240307091149778

该函数用来控制一个消息队列,可以用它删除一个消息队列、获取消息队列的属性。

用户层描述消息队列的结构体 struct msqid_ds

image-20240307091319979

1.2.3 msgsnd——发送数据

消息队列不需要挂接到进程的地址空间,直接通过系统调用向消息队列中写入数据,或者从消息队列中读取数据。

image-20240307091846161

  • msqid:用户层唯一标识一个消息队列的 ID,就是 msgget 函数的返回值。
  • msgp:发送的数据块,是一个 struct msgbuf结构体类型,需要用户自己定义。
  • msgsz:数据块的大小。
  • msgflg:一般设置为0,表示消息队列如果满了,就阻塞式等待;IPC_NOWAIT 表示消息队列如果为满,不阻塞,立即返回-1。

数据块的类型:

image-20240307092804576

  • mtype:标识数据块的类型。
  • mtext:用发送的信息。
1.2.4 msgrcv——从消息队列中检索数据块

image-20240307092950508

  • msgtyp:表示要检索的数据块类型。
  • msgflag:用来设置当消息队列为空的时候,阻塞还是非阻塞,选项和上面的一样。

1.3 消息队列的指令操作

  • ipcs -q:查看当前操作系统中所有的消息队列。
  • ipcrm -q msqid:释放一个消息队列。

二、信号量

2.1 数据不一致问题、互斥、临界资源、临界区

共享内存没有同步与互斥机制,可能会出现 A 进程正在向共享内存中写入,还没有写完,B 进程就来读取,导致发方和收方的数据不完整,这就是数据不一致问题。

  • A B看到的同一份资源,共享资源,如果不加保护,会导致数据不一致问题。
  • 可以通过加锁来实现互斥访问,在任何时候,只允许一个执行流访问共享资源。
  • 把共享的,任何时刻只允许一个执行流访问(执行访问代码)的资源称为——临界资源。
  • 临界资源一般是操作系统或者用户维护的一段内存空间
  • 把访问临界资源的代码叫做临界区

2.2 理解信号量

信号量是一种用于控制多个进程或线程对共享资源的访问的同步机制,它的本质是一把计数器,用于记录资源的可用数量。

image-20240307140658029

在临界资源充足的情况下,如果出现多个执行流访问同一个临界资源,那属于编码 Bug。信号量保证的是,假设只有 n 个临界资源,不会出现 n+1 个执行流来访问临界资源,如果出现就出导致数据不一致问题。

  • 申请计数器成功,就表示当前执行流具有访问临界资源的权限了。
  • 申请到了计数器,并没有去访问临界资源,申请计数器是对资源的一种预定机制。
  • 计数器可以有效的保证访问临界资源的执行流的数量。
  • 所以,每个执行流想要访问临界资源的时候,不是直接访问,而是先申请计数器资源(信号量)

2.3 二元信号量

如果临界资源只有一份,那么这个计数器的值只能是 1 或者 0,并且,在任何时候都只允许一个执行流访问共享资源。我们把这种只能为 1、0 两态的计数器就叫做——二元信号量二元信号量本质是一把锁。计数器最大为 1,本质上是资源只有一份,也就是不要将临界资源分成很多块,而是当做一个整体,整体申请,整体释放,这样就能实现互斥

总结:二元信号量主要用于实现对临界资源的互斥访问。

2.4 信号量也是共享资源

所有执行流想要使用临界资源,必须先来申请信号量,所以信号量也是一种共享资源。可能出现多个执行流同时来申请同一个信号量,信号量是用来保护临界资源的,前提是信号量得保证自身的安全。而我们的 -- 和 ++ 操作是不安全的,他们转成汇编,一般会对应三条汇编指令:从内存中读取数据到 CPU 中;CPU 内进行操作;CPU 将结果写回内存。进程在运行的时候,随时可能被切换,这就导致在多进程共享信号量的前提下, -- 和 ++ 操作可能会导致信号量的值发生错乱。

2.5 PV操作

PV操作:申请信号量,本质是对计数器 --,称为 P 操作;释放共享资源,本质是对计数器 ++,称为 V 操作。PV 操作一定是原子的。所谓原子性,就是一件事情,要么不做,要做就做完,只有两态,没有正在做这样的概念。站在技术角度来理解原子性就是:该操作只对应一条汇编指令,那么该操作就是原子的。

2.6 总结

image-20240307145723292

2.7 信号量的接口

创建、获取一个信号量(集)——semget

image-20240307152340080

  • nsems:表示创建几个信号量。

控制信号量——semctl

image-20240307152551386

  • semnum:信号量的编号。

image-20240307153504246

可以通过将 cmd 设置为 SETVAL,再传递 union semun 来设置信号量的初始值。

申请释放信号量(PV操作)——semop

image-20240307152820489

image-20240307152908488

  • sem_num:信号量的编号。
  • sem_op:1 表示 V 操作;-1 表示 P 操作。

2.8 信号量凭什么是进程通信的一种?

  • 通信不仅仅是数据传输,相互协同也是,告诉某个执行流接下来可以干什么了,或者不可以干什么。
  • 要协同,本质也是通信,信号量首先要被所有的通信进程看到。

三、内核中对 IPC 资源的管理

共享内存、消息队列、信号量,统称为操作系统中的 IPC 资源。为了管理这些资源,操作系统创建了三个结构体,分别用来描述这三种 IPC 资源。

3.1 三种描述 IPC 资源的结构体

struct shmid_kernel:

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 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 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 */
};

这三个结构体都有一个共性,结构体中的第一个数据类型都是 struct kern_ipc_perm 类型。

struct kern_ipc_perm:

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;
};

3.2 操作系统对 IPC 资源的管理

无论是什么类型的 IPC 资源,一定都有一个属于自己的 struct kern_ipc_perm 结构。所以,操作系统就通过一个 structkern_ipc_perm* 类型的数组将所有的 IPC 资源管理了起来。

struct ipc_ids:

struct ipc_ids {int in_use;int max_id;unsigned short seq;unsigned short seq_max;struct semaphore sem;	struct ipc_id_ary nullentry;struct ipc_id_ary* entries;
};

struct ipc_id_arry:

struct ipc_id_ary {int size;struct kern_ipc_perm *p[0];
};

其中 p 就是维护当前操作系统中所有 IPC 资源的一个柔性数组。如何通过这个数组里存的 struct ipc_id_ary* 找到某一个具体的 IPC 对象呢(如何找到 struct shmid_kernelstruct msg_queuestruct sem_array)?答案是通过强制类型转换,因为 kern_ipc_perm 是这三个结构体中的第一个成员,我们只要知道了一个 kern_ipc_perm 的地址,就相当于知道了某个具体 IPC 对象的起始地址,然后通过强制类型转换就可以访问到该 IPC 对象中的所有成员属性,这样就实现了对一个具体 IPC 对象的访问。例如:(struct shmid_kernel*)p[0]->q_stime。那操作系统是如何知道要将其强制转化成什么类型呢?答案是,在 kern_ipc_perm 中一定有字段来标识该 kern_ipc_perm 是属于那种 IPC 资源的,这就是多态的雏形。我们在用户层面上使用的:shmid、msqid、semid本质上就是内核中 p 数组的下标。

ipc_id_arry 这个数组隶属于操作系统,不属于任何进程,数组下标是一直线性递增的,不会因为 IPC 资源的释放而改变它的递增属性,即当前操作系统中最后一个 IPC 资源的下标是 12,释放掉这个 IPC 资源,下一次再创建 IPC 资源,因为有递增属性,所以它的下标是13,而不是12,当递增到一定值的时候,会回绕到0。

image-20240307113515929

🎁结语:

        今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是我前进的动力!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 计算之魂:持续于正确的因果链(一)
  • MySQL用户管理:用户管理、用户授权、用户权限撤销
  • 兴业证券基于Apache DolphinScheduler的应用实践
  • Linux 系统入门:高级系统管理与文本处理
  • 29 路由工作原理
  • 大二必做项目贪吃蛇超详解之中篇游戏设计与分析
  • sqlserver数据库常见操作
  • 力扣1353.最多可以参加的会议数目
  • Webpack打包常见问题及优化策略
  • langchain结合searXNG实现基于搜索RAG
  • 浅谈C#事件
  • sqli-libs第四关详解
  • Python脚本阿里云服务监控脚本
  • 考研--数学(相关公式)
  • 【王树森】BERT:预训练Transformer模型(个人向笔记)
  • 【译】理解JavaScript:new 关键字
  • 5、React组件事件详解
  • C学习-枚举(九)
  • exif信息对照
  • IndexedDB
  • java 多线程基础, 我觉得还是有必要看看的
  • js中forEach回调同异步问题
  • Laravel5.4 Queues队列学习
  • overflow: hidden IE7无效
  • php面试题 汇集2
  • Python - 闭包Closure
  • unity如何实现一个固定宽度的orthagraphic相机
  • Webpack 4x 之路 ( 四 )
  • webpack4 一点通
  • 大快搜索数据爬虫技术实例安装教学篇
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 人脸识别最新开发经验demo
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 微信开源mars源码分析1—上层samples分析
  • Android开发者必备:推荐一款助力开发的开源APP
  • 阿里云服务器如何修改远程端口?
  • 组复制官方翻译九、Group Replication Technical Details
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • # centos7下FFmpeg环境部署记录
  • # Redis 入门到精通(一)数据类型(4)
  • #QT(一种朴素的计算器实现方法)
  • $refs 、$nextTic、动态组件、name的使用
  • (1)虚拟机的安装与使用,linux系统安装
  • (done) 两个矩阵 “相似” 是什么意思?
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (蓝桥杯每日一题)love
  • (最新)华为 2024 届秋招-硬件技术工程师-单板硬件开发—机试题—(共12套)(每套四十题)
  • ******之网络***——物理***
  • .NET MAUI Sqlite数据库操作(二)异步初始化方法
  • .net 调用php,php 调用.net com组件 --