目录
[System V消息队列](#System V消息队列)
[二:System V信号量](#二:System V信号量)
[三:System V IPC联系](#三:System V IPC联系)
一:System V消息队列
1:消息队列的基本原理
消息队列实际上就是在系统中创建了一个队列,队列中的每一个成员都是一个数据块,这些数据块都是由类型和信息两部分构成,两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发送数据时,都在消息队列的队尾添加数据,这两个进程获取数据块时,都在消息队列的队头取数据块

其中消息队列当中的某一个数据块是由谁发送给谁的,取决于数据块的类型.
- 消息队列提供了一个从一个进程向另一个进程发送数据块的方法.
- 每个数据块都被认为是有一个类型的,接受者进程接收的数据块可以有不同的类型值.
- 与共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为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);

- 创建消息队列也需要使用ftok函数生成一个值,这个key值作为msgget函数的第一个参数
- msgget函数的第二个参数,与创建共享内存时使用的shmget函数的第三个参数相同
- 消息队列创建成功时,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函数的参数说明
- 第一个参数msqid,表示消息队列的用户级标识符.
- 第二个参数msgp,表示待发送的数据块.
- 第三个参数msgsz,表示所发送的数据块的大小.
- 第四个参数msgflg,表示发送数据块的方式,一般默认为0即可.
msgsnd函数的返回值说明
- msgsnd调用成功,返回0。
- 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.信号量相关概念
- 多个执行流,能看到同一份资源,叫做共享资源.
- 被保护的共享资源叫做临界资源,保护共享资源的方式有:同步和互斥.
- 互斥:任何时刻只能有一个进程访问共享资源.
- 只要是资源,就要被程序员通过代码去进行访问----->代码 = 访问共享的代码 + 不访问共享资源的代码.
- 被保护的共享资源称之为临界资源,临界资源的本质是对访问共享资源的代码进行保护.

2.对信号量的理论解释
- 进程间通信通过共享资源来实现,这虽然解决了通信的问题,但也引入了新的问题,那就是通信进程间共用的临界资源,若是不对临界资源进行保护,就可能产生各个进程从临界资源获取的数据不一致等问题.
- 保护临界资源的本质是保护临界区, 我们将进程代码中访问临界资源的代码 称之为临界区 ,信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量.
- 信号量的本质是一个计数器,在二元信号量中,信号量的个数为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);
- 创建信号量集也需要使用ftok函数生成一个key值,这个key值作为semget函数的第一个参数。
- semget函数的第二个参数nsems,表示创建信号量的个数。
- semget函数的第三个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
- 信号量集创建成功时,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) | 一个计数器,用于为多个进程提供对共享资源的访问控制。 | 有效解决进程同步与互斥问题,防止资源冲突。 | 编程复杂,使用不当容易导致死锁。 | 配合共享内存使用,控制并发访问。 |