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

2024-09-01 12:44

本文主要是介绍【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

🎁结语:

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

这篇关于【Linux修行路】进程通信——消息队列、信号量的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126975

相关文章

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

Linux编译器--gcc/g++使用方式

《Linux编译器--gcc/g++使用方式》文章主要介绍了C/C++程序的编译过程,包括预编译、编译、汇编和链接四个阶段,并详细解释了每个阶段的作用和具体操作,同时,还介绍了调试和发布版本的概念... 目录一、预编译指令1.1预处理功能1.2指令1.3问题扩展二、编译(生成汇编)三、汇编(生成二进制机器语

Rsnapshot怎么用? 基于Rsync的强大Linux备份工具使用指南

《Rsnapshot怎么用?基于Rsync的强大Linux备份工具使用指南》Rsnapshot不仅可以备份本地文件,还能通过SSH备份远程文件,接下来详细介绍如何安装、配置和使用Rsnaps... Rsnapshot 是一款开源的文件系统快照工具。它结合了 Rsync 和 SSH 的能力,可以帮助你在 li