消息队列与信号量
消息队列
msgget()
msgget()
是一个用于获取 System V 消息队列标识符的系统调用。让我为您解释一下它的用法和参数。
-
msgget(key_t key, int msgflg);
key
: 指定的键值,用于关联消息队列。msgflg
: 控制行为的标志。
msgget()
可以用于两个目的:
- 获取已创建消息队列的标识符:当
msgflg
为零且key
不是IPC_PRIVATE
时,可以获取已创建消息队列的标识符。 - 创建新的消息队列:如果
key
的值为IPC_PRIVATE
或者key
不是IPC_PRIVATE
,但不存在具有给定key
的消息队列,并且msgflg
中指定了IPC_CREAT
,则会创建一个新的消息队列。
例如,以下代码片段演示了如何使用 msgget()
创建一个新的消息队列:
#include <sys/msg.h>
int main() {
key_t key = ftok("/tmp", 'A'); // 创建一个唯一的键
int msqid = msgget(key, 0666 | IPC_CREAT); // 获取或创建消息队列
if (msqid != -1) {
printf("Message queue created with identifier %d\n", msqid);
} else {
perror("msgget");
}
return 0;
}
创建一个消息队列,如果该队列不存在,则会创建一个新的队列。
msgget()函数的返回值为消息队列的标识符(一个非负整数),如果出错,返回值为 -1,同时 errno
会被设置以指示错误原因
msgbuf结构体
msgbuf
是一个用于在消息队列中传递消息的结构体,通常用于 msgsnd()
和 msgrcv()
系统调用。让我为您解释一下它的结构和用法。
msgbuf
结构体的定义如下:
struct msgbuf {
long mtype; // 消息类型,必须大于 0
char mtext[1]; // 消息数据
};
mtype
字段表示消息类型,必须是一个严格正整数值。接收进程可以根据消息类型来选择接收哪些消息。mtext
字段是一个数组(或其他结构),其大小由msgsz
参数指定,是一个非负整数值。允许发送长度为零的消息(即没有mtext
字段)。
例如,如果您要发送一条消息到消息队列,可以这样使用 msgbuf
结构体:
#include <sys/msg.h>
struct my_message {
long mtype; // 消息类型
char mtext[256]; // 消息文本
};
int main() {
key_t key = ftok("/tmp", 'A'); // 创建一个唯一的键
int msqid = msgget(key, 0666 | IPC_CREAT); // 获取或创建消息队列
struct my_message msg;
msg.mtype = 1; // 设置消息类型
snprintf(msg.mtext, sizeof(msg.mtext), "Hello, world!"); // 设置消息文本
msgsnd(msqid, &msg, sizeof(msg.mtext), 0); // 发送消息
return 0;
}
这段代码创建了一个消息队列,将一条消息发送到队列中。您可以根据自己的需求修改消息的类型和文本
msgsnd()
函数的返回值为 0 表示成功,-1 表示出错。如果出错,您可以通过检查 errno
来获取详细的错误信息
msgctl()
-
函数签名:
#include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);
-
参数:
msqid
: 消息队列的标识符。cmd: 要执行的控制操作。
cmd的有效值包括:
IPC_STAT
:将与msqid
关联的内核数据结构的信息复制到指向buf
的msqid_ds
结构体中。这提供了有关消息队列的详细信息。IPC_SET
:基于buf
中的值修改消息队列的属性。IPC_RMID
:删除消息队列。
msgctl
可以用于以下两个目的:
- 获取已创建消息队列的标识符:当
cmd
为零且key
不是IPC_PRIVATE
时,可以获取已创建消息队列的标识符。 - 创建新的消息队列:如果
key
的值为IPC_PRIVATE
或者key
不是IPC_PRIVATE
,但不存在具有给定key
的消息队列,并且msgflg
中指定了IPC_CREAT
,则会创建一个新的消息队列。
例如,以下代码片段演示了如何使用 msgctl
获取消息队列的属性:
#include <sys/msg.h>
int main() {
key_t key = ftok("/tmp", 'A'); // 创建一个唯一的键
int msqid = msgget(key, 0666 | IPC_CREAT); // 获取或创建消息队列
struct msqid_ds info;
msgctl(msqid, IPC_STAT, &info); // 获取消息队列属性
printf("Message queue permissions: %o\n", info.msg_perm.mode);
printf("Number of messages in the queue: %ld\n", info.msg_qnum);
return 0;
}
msgctl 函数的返回值为成功时为 0,出错时为 -1,错误原因存储在 errno
中
ipc_perm
ipc_perm
是一个在 Linux 中用于描述进程间通信(IPC)对象权限和所有者的结构体。让我为您详细解释一下它的作用和成员。
-
结构体定义:
struct ipc_perm { key_t __key; // IPC 对象的键值 uid_t uid; // 所有者的用户 ID gid_t gid; // 所有者的组 ID uid_t cuid; // 创建者的用户 ID gid_t cgid; // 创建者的组 ID mode_t mode; // 权限模式 unsigned short seq; // 序列号 };
-
成员说明:
__key
:IPC 对象的键值,用于关联 IPC 对象。uid
:所有者的用户 ID。gid
:所有者的组 ID。cuid
:创建者的用户 ID。cgid
:创建者的组 ID。mode
:权限模式,表示对象的读写执行权限。seq
:序列号,用于处理并发操作。
ipc_perm
结构体用于保存 IPC 对象的权限信息,例如消息队列、信号量和共享内存。每个 IPC 对象都有一个关联的 ipc_perm
结构体,用于确定对该对象的访问权限
所以像shmid之类的的本质就是数组下标
struct ipc_perm* array[];
信号量
本质:就是一个计数器
用来描述临界资源中资源数量的多少
信号量是一种用于进程间同步和互斥的机制,通常用于控制对共享资源的访问。信号量可以看作是一个计数器,用来记录某个资源的可用数量或者某个临界区域的使用情况。
在操作系统中,信号量通常有两种类型:二进制信号量和计数信号量。
- 二进制信号量(Binary Semaphore):
- 二进制信号量的取值范围只能是 0 和 1,通常用于实现互斥锁。
- 当二进制信号量的值为 1 时,表示资源可用,进程可以访问该资源;当值为 0 时,表示资源不可用,进程需要等待。
- 二进制信号量常用于保护临界资源,确保只有一个进程可以访问该资源,从而避免竞争条件和数据不一致性。
- 计数信号量(Counting Semaphore):
- 计数信号量的取值范围不限于 0 和 1,可以是任意非负整数。
- 计数信号量通常用于表示某个资源的可用数量,例如共享内存区域的可用空间数、生产者消费者模型中的缓冲区剩余空间等。
- 当计数信号量的值大于 0 时,表示资源可用数量;当值为 0 时,表示资源已经全部被占用,进程需要等待。
信号量操作通常包括两个基本操作:P(Produce)
和 V(Vacate)
。
P
操作(也称为原语wait
或者down
)用于请求使用资源,如果资源不可用,则进程阻塞等待。V
操作(也称为原语signal
或者up
)用于释放资源,将资源标记为可用,并唤醒等待的进程。
PV操作是原子的:要么不做,要么做完,没有正在做这个概念。
临界资源
临界资源是一次仅允许一个进程使用的共享资源。这些资源在一段时间内只允许一个进程访问,也被称为独占资源。常见的临界资源包括系统中的大多数物理设备,例如打印机、磁带机,以及栈、变量和表格)当多个进程需要访问同一个资源时,它们采取互斥的方式来实现对这种资源的共享。
此外,临界区是每个进程中访问临界资源的那段代码,也被称为临界区(critical section)。在临界区内,每次只允许一个进程进入,其他进程不允许进入。无论是硬件临界资源还是软件临界资源,多个进程必须互斥地对其进行访问。使用临界区时,一般不允许其运行时间过长,因为其他进入此临界区的线程都会被挂起,从而影响程序的运行性能](h
总之,临界资源和临界区是操作系统中用于实现进程同步和资源共享的重要概念。通过合理管理临界资源和临界区,可以避免竞态条件和数据不一致等问题。