【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

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

RabbitMQ 延时队列插件安装与使用示例详解(基于 Delayed Message Plugin)

《RabbitMQ延时队列插件安装与使用示例详解(基于DelayedMessagePlugin)》本文详解RabbitMQ通过安装rabbitmq_delayed_message_exchan... 目录 一、什么是 RabbitMQ 延时队列? 二、安装前准备✅ RabbitMQ 环境要求 三、安装延时队

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

聊聊springboot中如何自定义消息转换器

《聊聊springboot中如何自定义消息转换器》SpringBoot通过HttpMessageConverter处理HTTP数据转换,支持多种媒体类型,接下来通过本文给大家介绍springboot中... 目录核心接口springboot默认提供的转换器如何自定义消息转换器Spring Boot 中的消息