【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线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

linux解压缩 xxx.jar文件进行内部操作过程

《linux解压缩xxx.jar文件进行内部操作过程》:本文主要介绍linux解压缩xxx.jar文件进行内部操作,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、解压文件二、压缩文件总结一、解压文件1、把 xxx.jar 文件放在服务器上,并进入当前目录#

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

linux hostname设置全过程

《linuxhostname设置全过程》:本文主要介绍linuxhostname设置全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录查询hostname设置步骤其它相关点hostid/etc/hostsEDChina编程A工具license破解注意事项总结以RHE