茶企业网站建设模板搭建一个网站需要什么
文章目录
- 前言:
- 🎃消息队列:
- 1. **消息队列的基本概念**
- 2. **消息队列的特点**
- 3. **常见的消息队列操作(Linux IPC)**
- **1) `msgget`:创建或获取消息队列**
- **2) `msgsnd`:发送消息**
- **3) `msgrcv`:接收消息**
- **4) `msgctl`:控制消息队列**
- 4. **消息队列的基本操作示例(Linux C代码)**
- **消息发送示例**:
- **消息接收示例**:
- **删除消息队列**:
- 5. **消息队列的优缺点**
- **优点**:
- **缺点**:
- 6. **消息队列的应用场景**
- 🎃信号量:
- ❤️五个概念:
- ❤️对信号量的理解?
- 🍤对于不整体使用的资源:
- 🍚对于整体使用的资源:
- 🍗那我可不可以使用全局变量?
- OS是如何把共享内存、消息队列、信号量统一管理起来的?
前言:
关于System V下的通信方式,我们只是对共享内存做深度的处理,而对于消息队列和信号量我们并不会特别在意,一下我将粗略的介绍一下关于消息队列,但是信号量这一部分,还有一些新的知识点需要好好聊聊。
🎃消息队列:
消息队列(Message Queue)是操作系统提供的一种进程间通信(IPC,Inter-Process Communication)机制,允许进程通过消息的形式进行异步通信。消息队列为进程间提供了一个有序的、独立于进程之外的消息缓冲区,进程可以通过向消息队列发送和接收消息来进行数据交换。
1. 消息队列的基本概念
- 异步通信:消息发送进程和接收进程之间是异步的,即发送者可以发送消息后立即返回,无需等待接收者处理;同样,接收者在准备好时可以读取消息,而无需等待发送者。
- 持久性:消息队列中的消息是持久的,消息一旦被发送,它们会保留在队列中,直到被接收进程读取或队列被显式删除。
- 顺序性:消息以队列的形式存储,发送的顺序决定了消息的顺序。接收者可以按照消息的优先级或先进先出的顺序读取消息。
2. 消息队列的特点
- 异步通信:消息队列允许发送进程和接收进程以不同时执行,发送进程不需要等待接收进程处理消息。
- 持久性:消息队列是内核管理的,在消息被读取之前,它们会一直保存在队列中,即使发送者和接收者都已经终止。
- 消息有优先级:消息可以按照优先级或FIFO(先进先出)的方式传递,接收者可以按优先级顺序或按时间顺序接收消息。
3. 常见的消息队列操作(Linux IPC)
在Linux中,消息队列是通过系统调用来实现的。下面是与消息队列相关的常见系统调用及其功能:
1) msgget
:创建或获取消息队列
int msgget(key_t key, int msgflg);
key
:消息队列的唯一标识符,由用户定义。可以通过ftok()
函数生成。msgflg
:消息队列的标志位。可以是IPC_CREAT
(如果消息队列不存在则创建),以及权限标志如0666
等。- 返回值:返回消息队列的标识符(
msqid
),后续操作基于此ID。
2) msgsnd
:发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid
:消息队列标识符,由msgget()
返回。msgp
:指向消息结构体的指针,该结构体包含消息类型和消息内容。msgsz
:消息的大小。msgflg
:控制操作的标志位。如果队列满了,IPC_NOWAIT
可以避免发送者被阻塞。- 返回值:成功时返回
0
,失败时返回-1
并设置errno
。
消息结构通常定义如下:
struct msgbuf {long mtype; // 消息类型char mtext[1]; // 消息内容
};
3) msgrcv
:接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msqid
:消息队列标识符。msgp
:指向存储接收消息的结构体的指针。msgsz
:要接收的最大消息大小。msgtyp
:指定要接收的消息类型。如果为0,则接收队列中的第一条消息。msgflg
:控制操作的标志位。可以使用IPC_NOWAIT
来防止阻塞,或者使用MSG_EXCEPT
来接收非指定类型的消息。- 返回值:返回读取到的字节数,失败时返回
-1
并设置errno
。
4) msgctl
:控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
msqid
:消息队列标识符。 -
cmd
:控制操作,可以是以下值:
IPC_STAT
:获取消息队列的状态。IPC_SET
:设置消息队列的状态。IPC_RMID
:删除消息队列。
-
buf
:用于存储或设置消息队列的状态信息,msqid_ds
结构体包含权限、最后的操作时间、消息数等信息。
4. 消息队列的基本操作示例(Linux C代码)
以下是一个简单的消息队列示例代码,展示如何创建消息队列、发送消息和接收消息。
消息发送示例:
c复制代码#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>struct msgbuf {long mtype; // 消息类型char mtext[100]; // 消息内容
};int main() {key_t key;int msgid;// 生成唯一的keykey = ftok("progfile", 65);// 创建消息队列msgid = msgget(key, 0666 | IPC_CREAT);// 准备要发送的消息struct msgbuf message;message.mtype = 1; // 消息类型strcpy(message.mtext, "Hello World!");// 发送消息到队列msgsnd(msgid, &message, sizeof(message.mtext), 0);printf("Message Sent: %s\n", message.mtext);return 0;
}
消息接收示例:
c复制代码#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>struct msgbuf {long mtype; // 消息类型char mtext[100]; // 消息内容
};int main() {key_t key;int msgid;// 生成唯一的keykey = ftok("progfile", 65);// 获取消息队列msgid = msgget(key, 0666 | IPC_CREAT);// 准备接收消息struct msgbuf message;// 接收类型为1的消息msgrcv(msgid, &message, sizeof(message.mtext), 1, 0);// 显示消息内容printf("Message Received: %s\n", message.mtext);return 0;
}
删除消息队列:
c复制代码#include <sys/ipc.h>
#include <sys/msg.h>int main() {key_t key;int msgid;// 生成唯一的keykey = ftok("progfile", 65);// 获取消息队列msgid = msgget(key, 0666 | IPC_CREAT);// 删除消息队列msgctl(msgid, IPC_RMID, NULL);return 0;
}
5. 消息队列的优缺点
优点:
- 异步处理:发送进程不必等待接收进程,可以提高系统的并发性和响应能力。
- 持久性:消息在队列中保存,直到接收者取走消息或队列被删除,即使发送者和接收者的生命周期不同步。
- 易用性:消息队列通过消息类型支持不同的消息分类,允许接收进程选择性读取某一类型的消息。
缺点:
- 容量限制:消息队列有大小限制,如果消息队列已满,发送进程可能会被阻塞(除非设置非阻塞标志)。
- 管理复杂性:消息队列是内核资源,使用后需要手动清理,未清理的消息队列可能会长期占用系统资源。
- 不适合大量数据传输:消息队列更适合传递较小的数据,过大的数据块可能会影响系统性能。
6. 消息队列的应用场景
- 进程间通信:消息队列用于不同进程之间的数据交换,例如不同模块之间的数据传递。
- 任务调度:某些系统通过消息队列进行任务调度,工作进程从队列中取任务执行。
- 日志系统:系统日志可以通过消息队列集中到一个日志处理进程来进行统一的日志存储或分析。
消息队列是一种常用的进程间通信机制,支持异步数据传输和灵活的消息管理。通过msgget
、msgsnd
、msgrcv
等系统调用,进程可以方便地将消息发送到队列中并从中读取消息。虽然消息队列有一些限制,比如队列大小的限制和内核资源的消耗,但它在许多系统中仍然是一个高效的通信工具。
🎃信号量:
❤️五个概念:
- 多个进程(执行流)想要实现通信,都必须在内核中看到同一份资源,这个资源成为公共资源(共享资源)。
- 而被保护起来的资源就叫——临界资源
- 而保护的方式分为同步和互斥,而互斥是任何一个时刻只能有一个进程在访问公共资源。而以互斥方式保护的共享资源就是临界资源。
- 资源一定是要被访问的,而且是通过代码访问的。(代码又会分为“存在访问共享资源的”和“不存在访问共享资源的”。对于上述这两种情况,分别称为——临界区和非临界区。
- 所谓利用互斥保护共享资源使其变为临界资源,本质是对访问共享资源的代码进行保护。
❤️对信号量的理解?
信号量(Semaphore)是一种用于控制对共享资源的访问的同步机制,广泛应用于多线程和多进程编程中。信号量可以用来解决临界区问题,防止竞争条件,并确保资源的互斥访问。(简单来说,是用来保护临界资源的)
🍤对于不整体使用的资源:
所以对临界资源的保护其实就是设置一个计数器,每次访问前先申请访问,访问通过了才能访问,访问完成后又会有一个退出操作。因此保护临界资源的信号量本质就是一个计数器!
电影院:临界资源
买票:申请信号量 ————> 本质就是对公共资源的一种预定机制
票数:信号量的初始值步骤:
- 申请信号量
- 访问临界资源
- 释放信号量
🍚对于整体使用的资源:
🍗那我可不可以使用全局变量?
意思是,既然信号量是一个计数器,那我可不可以直接使用一个全局变量来表示计数器,想着可不可以完全替代信号量呢?
————不能!
-
全局变量不能被所有进程看到,因为进程之间具有独立性,而父子进程却很有可能发生写实拷贝。
-
不是原子的。
-
什么是原子操作?
**原子操作(Atomic Operation)**是指一个操作要么完整地执行,要么完全不执行。在多线程/多进程环境下,原子操作可以确保操作的完整性,不会被其他线程/进程打断。
而使用全局变量作为计数器时,增加和减少计数的操作通常需要多个步骤,比如:
- 读取全局变量的值
- 对值进行修改
- 将修改后的值写回全局变量
这三个步骤并不是一个原子操作。在多线程/进程环境下,如果两个线程/进程同时执行这三个步骤,就可能出现竞争条件(race condition),导致计数器的值不准确。
比如:
- 进程A读取计数器值为10
- 进程B也读取计数器值为10
- 进程A将值加1写回为11
- 进程B将值加1写回为11
此时,计数器的值应该是12,但实际只变成了11,出现了错误。
所以还得是信号量,不能是任何什么全局变量这种非原子的。
-
所以就得让多个进程看到一个信号量(计数器)
那就说明这个信号量本质就是一个公共资源!
既然你作为信号量,你的任务是保证其它临界资源的安全,那首先你得保证自己是安全的!
有很多方法保证信号量自身是安全的,那些操作我们以后讲,不过现在我们可以就原子性来讲讲。
-
信号量的核心操作 P(wait) 和 V(signal) 都是原子操作。
-
即这些操作要么完全执行,要么完全不执行,中间不会被中断,再按照上面局列举的例子就能理解如何保护临界资源的。
OS是如何把共享内存、消息队列、信号量统一管理起来的?
先组织,再描述!
struct kern_ipc_perm
是在 Linux 内核中用于管理进程间通信(IPC)对象的权限结构体。它是内核中 ipc_perm
的扩展版本,特意用于对共享内存、消息队列、信号量等 System V IPC 机制进行权限管理和控制。在用户空间中,ipc_perm
提供的是一个简化版的权限结构,而 kern_ipc_perm
则是内核用来管理这些权限的完整结构。
struct ipc_id_ary
是 Linux 内核中用于管理 System V IPC 对象(如共享内存、消息队列、信号量)的数据结构之一。它的主要作用是管理和跟踪所有的 IPC 资源,并存储这些 IPC 资源的标识符和相关信息。
IPC(进程间通信)资源在内核中通常通过索引和标识符(shmid
、msqid
、semid
等)进行管理。ipc_id_ary
是一个数组结构,用于保存 IPC 资源的 ID 和对应的数据结构,通常包括每种 IPC 对象类型(共享内存、消息队列、信号量)的标识符和状态信息。
你每创建一个共享内存或者消息队列,本质就是先进行一次struct kern_ipc_perm对象实例化,这里包含了该共享内存的key和序列号seq,而struct kern_ipc_perm每次的创建都会由struct ipc_id_ary里的柔性数组做管理,这里就是你每一次创建一个共享内存,我这里的下标就会加一,这也就是为什么你每次拿到shmid都会在上次的基础上加一,因此shmid本质就是struct ipd_id_ary里的柔性数组的下标,而每当你释放共享内存。
序列号的递增:共享内存段删除时,内核不会重新使用之前的序列号。相反,它会递增序列号,这样下次创建共享内存段时,即使使用相同的索引位置,生成的
shmid
也不同。避免重复使用相同的
shmid
:递增序列号的目的是为了防止系统意外地重新使用旧的shmid
。这可以防止进程在使用过期或无效的shmid
时产生意外行为。
现在我们可以说完成了进程间通信的话题,管道是在内核中对缓冲区实现通信,命名管道则是对文件,共享内存则是对一个数组,不过是在用户层面上的。下一章我们将开启Linux信号。