通信之道:解锁Linux进程间通信的无限可能(三)

目录

[System V消息队列](#System V消息队列)

1:消息队列的基本原理

2:消息队列中的数据结构

3:消息队列的创建

4:消息队列的释放

5:向消息队列发送数据

6:从消息队列获取数据

[二:System V信号量](#二:System V信号量)

1.信号量相关概念

2.对信号量的理论解释

3:信号量数据结构

4:信号量的相关函数

[三:System V IPC联系](#三:System V IPC联系)

四:进程间通信方式的对比


一:System V消息队列

1:消息队列的基本原理

消息队列实际上就是在系统中创建了一个队列,队列中的每一个成员都是一个数据块,这些数据块都是由类型和信息两部分构成,两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发送数据时,都在消息队列的队尾添加数据,这两个进程获取数据块时,都在消息队列的队头取数据块

其中消息队列当中的某一个数据块是由谁发送给谁的,取决于数据块的类型.

  1. 消息队列提供了一个从一个进程向另一个进程发送数据块的方法.
  2. 每个数据块都被认为是有一个类型的,接受者进程接收的数据块可以有不同的类型值.
  3. 与共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的。

2:消息队列中的数据结构

  • 当然,系统当中也可能会存在大量的消息队列,系统一定也要为消息队列维护相关的内核数据结构。
  • 消息队列的数据结构如下:
cpp 复制代码
struct msqid_ds {
	struct ipc_perm msg_perm;
	struct msg *msg_first;      /* first message on queue,unused  */
	struct msg *msg_last;       /* last message in queue,unused */
	__kernel_time_t msg_stime;  /* last msgsnd time */
	__kernel_time_t msg_rtime;  /* last msgrcv time */
	__kernel_time_t msg_ctime;  /* last change time */
	unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
	unsigned long  msg_lqbytes; /* ditto */
	unsigned short msg_cbytes;  /* current number of bytes on queue */
	unsigned short msg_qnum;    /* number of messages in queue */
	unsigned short msg_qbytes;  /* max number of bytes on queue */
	__kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
	__kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

消息队列数据结构的第一个成员是msg_perm,它和shm_perm是同一个类型的结构体变量,ipc_perm的结构体定义如下.

cpp 复制代码
struct ipc_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};

3:消息队列的创建

cpp 复制代码
int msgget(key_t key, int msgflg);
  1. 创建消息队列也需要使用ftok函数生成一个值,这个key值作为msgget函数的第一个参数
  2. msgget函数的第二个参数,与创建共享内存时使用的shmget函数的第三个参数相同
  3. 消息队列创建成功时,msgget函数返回的一个有效的消息队列标识符(用户层标识符)

4:消息队列的释放

释放消息队列我们需要用msgctl函数,msgctl函数的函数原型如下:

cpp 复制代码
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgctl函数的参数与释放共享内存时使用的shmctl函数的三个参数相同,只不过msgctl函数的第三个参数传入的是消息队列的相关数据结构.

5:向消息队列发送数据

向消息队列发送数据我们需要使用msgsnd函数,msgsnd函数的函数原型如下

cpp 复制代码
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

msgsnd函数的参数说明

  1. 第一个参数msqid,表示消息队列的用户级标识符.
  2. 第二个参数msgp,表示待发送的数据块.
  3. 第三个参数msgsz,表示所发送的数据块的大小.
  4. 第四个参数msgflg,表示发送数据块的方式,一般默认为0即可.

msgsnd函数的返回值说明

  1. msgsnd调用成功,返回0。
  2. msgsnd调用失败,返回-1。

其中msgsnd函数的第二个参数必须为如下结构

cpp 复制代码
struct msgbuf{
	long mtype;       /* message type, must be > 0 */
	char mtext[1];    /* message data */
};

注意: 该结构当中的第二个成员mtext即为待发送的信息,当我们定义该结构时,mtext的大小可以自己指定。

6:从消息队列获取数据

从消息队列获取数据我们需要用msgrcv函数,msgrcv函数的函数原型如下:

二:System V信号量

1.信号量相关概念

  1. 多个执行流,能看到同一份资源,叫做共享资源.
  2. 保护的共享资源叫做临界资源,保护共享资源的方式有:同步和互斥.
  3. 互斥:任何时刻只能有一个进程访问共享资源.
  4. 只要是资源,就要被程序员通过代码去进行访问----->代码 = 访问共享的代码 + 不访问共享资源的代码.
  5. 被保护的共享资源称之为临界资源,临界资源的本质是对访问共享资源的代码进行保护.

2.对信号量的理论解释

  1. 进程间通信通过共享资源来实现,这虽然解决了通信的问题,但也引入了新的问题,那就是通信进程间共用的临界资源,若是不对临界资源进行保护,就可能产生各个进程从临界资源获取的数据不一致等问题.
  2. 保护临界资源的本质是保护临界区, 我们将进程代码中访问临界资源的代码 称之为临界区 ,信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量.
  3. 信号量的本质是一个计数器,在二元信号量中,信号量的个数为1(相当于将临界资源看成一整块),二元信号量本质解决了临界资源的互斥问题
  • 上述代码中,当进程A申请访问共享内存资源时,如果此时sem为1(sem代表当前信号量个数),则进程A申请资源成功,此时需要将sem减减,然后进程A就可以对共享内存进行一系列操作,但在进程A在访问共享内存时,若进程B申请访问该共享内存资源,此时sem就为0了,那么这时进程B就会被挂起,直到进程A访问共享内存结束后将sem++,此时才会将进程B唤起,然后进程B再对该共享内存进行访问操作。
  • 在这种情况下,无论什么时候都只会有一个进程在对同一份共享内存进行访问,也就解决了临界资源的互斥问题。
  • 实际上,代码中计数器sem--的操作就叫做P操作,而计数器++的操作就叫做V操作,P操作就是申请信号量,而V操作就是释放信号量。

3:信号量数据结构

信号量的数据结构如下

cpp 复制代码
struct semid_ds {
	struct ipc_perm sem_perm;       /* permissions .. see ipc.h */
	__kernel_time_t sem_otime;      /* last semop time */
	__kernel_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 short  sem_nsems;      /* no. of semaphores in array */
};

信号量数据结构的第一个成员也是**ipc_perm类型** 的结构体变量ipc_perm结构体的定义如下:

cpp 复制代码
struct ipc_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};

4:信号量的相关函数

信号量的创建

创建信号量集我们需要用semget函数,semget函数的函数原型如下:

cpp 复制代码
int semget(key_t key, int nsems, int semflg);
  1. 创建信号量集也需要使用ftok函数生成一个key值,这个key值作为semget函数的第一个参数。
  2. semget函数的第二个参数nsems,表示创建信号量的个数。
  3. semget函数的第三个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
  4. 信号量集创建成功时,semget函数返回的一个有效的信号量集标识符(用户层标识符)。

信号量集的删除

cpp 复制代码
int semctl(int semid, int semnum, int cmd, ...);

删除信号量集我们需要用semctl函数,semctl函数的函数原型如下:

信号量集的操作

cpp 复制代码
int semop(int semid, struct sembuf *sops, unsigned nsops);

三:System V IPC联系

通过对system V系列进程间通信的学习,可以发现共享内存、消息队列以及信号量,虽然它们内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量。

这样设计的好处就是,在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。

也就是说,在内核当中只需要将所有的IPC资源的ipc_perm成员组织成数组的样子,然后用切片的方式获取到该IPC资源的起始地址,然后就可以访问该IPC资源的每一个成员了。

四:进程间通信方式的对比

|--------------------------|------------------------------------|-----------------------------|------------------------------|------------------|
| 通信方式 | 机制描述 | 优点 | 缺点 | 适用场景 |
| 匿名管道(Pipe) | 半双工通信,数据只能单向流动。通常指匿名管道,仅限有血缘关系的进程。 | 简单易用,不需要复杂的同步机制。 | 缓冲区有限;只能单向传输;仅限亲缘进程 | 简单的父子进程数据传递。 |
| 命名管道(FIFO) | 类似于管道,但在文件系统中有一个名字,允许无亲缘关系的进程通信 | 克服了匿名管道只能在亲缘进程间使用的限制 | 效率较低(通过内核缓冲区);依然是单向或半双工。 | 不相关的进程间交换少量数据。 |
| 信号(Signal) | 异步通知机制,用于通知接收进程某个事件已经发生。 | 开销极小,响应速度快。 | 携带信息量极少(通常只有一个信号编号);不适用于传输数据 | 异常处理、进程中断、系统报警。 |
| 消息队列 (Message Queue) | 消息的链表,存放在内核中并由消息队列标识符标识。 | 支持异步通信;解耦发送方和接收方;支持按类型检索消息。 | 内核态与用户态之间的数据拷贝开销;消息大小有限制。 | 结构化数据交换、任务异步处理。 |
| 共享内存 (Shared Memory) | 映射一段能被其他进程访问的内存,两个进程直接读写。 | 速度最快,无需内核拷贝数据,直接操作内存。 | 需要额外的同步机制(如信号量)来防止竞态条件。 | 大批量数据共享、高频数据交换。 |
| 信号量 (Semaphore) | 一个计数器,用于为多个进程提供对共享资源的访问控制。 | 有效解决进程同步与互斥问题,防止资源冲突。 | 编程复杂,使用不当容易导致死锁。 | 配合共享内存使用,控制并发访问。 |

相关推荐
光泽雨2 小时前
c#MVVM中的消息通知机制
服务器·c#
闻道且行之2 小时前
frp+Nginx 内网穿透详细配置教程
运维·网络·nginx·frp·内网穿透
ictI CABL2 小时前
Linux环境下Tomcat的安装与配置详细指南
linux·运维·tomcat
小小码农Come on2 小时前
QPainter双缓冲区实现一个简单画图软件
linux·服务器·前端
郝学胜-神的一滴2 小时前
深入理解 epoll_wait:高性能 IO 多路复用核心解密
linux·服务器·开发语言·c++·网络协议
小陈99cyh2 小时前
最新ubuntu22.04服务器上安装vmware虚拟机,附vmware的安装包
linux·运维·服务器·vmware
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B rknn-toolkit-lite2使用方法
linux·网络·人工智能·物联网·算法
张3233 小时前
Ansible实施任务控制
linux·ansible
白菜欣3 小时前
Linux权限
linux·运维·c++