欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【LeetCode】winter vacation training
目录
- 👉🏻共享内存
- 👉🏻关于共享内存的接口函数
- 👉🏻ipcs命令
- 👉🏻ipcrm命令
- 👉🏻共享内存实现进程间通信代码示例
- 👉🏻消息队列
- 概念
- 进程之间的消息队列通信
- 👉🏻信号量
- semget,semop,semctl
- 👉🏻IPC
👉🏻共享内存
共享内存(Shared Memory)是一种进程间通信(IPC)的机制,用于在不同进程之间共享数据。
在共享内存中,多个进程可以将相同的内存区域映射到它们各自的地址空间中。这意味着这些进程可以直接访问相同的物理内存位置,而无需进行复制或通过消息传递来传递数据。这种直接的内存共享方式使得进程间的数据交换更加高效和快速。
在使用共享内存进行进程间通信时,需要注意以下几点:
- 同步机制:由于多个进程可以同时访问共享内存,因此需要使用同步机制(如信号量、互斥锁)来协调进程之间对共享内存的访问,避免数据冲突和竞争条件。
- 内存管理:共享内存需要由操作系统进行管理和分配。在创建共享内存时,需要申请足够的内存空间,并在使用完毕后及时释放,以防止资源泄漏。
- 数据一致性:由于多个进程可以同时访问共享内存,需要确保数据的一致性。可以使用同步机制来保证数据的正确读写顺序,或者通过采用特定的算法和协议来处理并发访问产生的冲突。
共享内存的优势在于它可以实现高效的数据共享,避免了进程间频繁的数据拷贝;同时,由于数据直接存储在物理内存中,共享内存的访问速度也比其他进程间通信方式更快。
共享内存就是在物理内中开辟好内存空间,将地址映射到页表中,再映射到进程地址空间中。
共享内存比管道通信快的原因主要有以下几个方面:
-
数据传输方式:在共享内存中,数据直接存储在物理内存中,不需要经过内核空间,进程可以直接读写内存,因此速度较快。而在管道通信中,数据需要在内核空间和用户空间之间进行来回拷贝,增加了额外的开销和延迟。
-
数据复制次数:在共享内存中,数据只需要被复制到共享内存区域一次,之后各个进程可以直接访问这块内存,而在管道通信中,每次数据传输都需要进行一次复制,这增加了额外的开销。
-
同步机制开销:在管道通信中,由于数据传输是基于文件描述符的读写操作,需要使用系统调用来进行读写和同步,而共享内存的访问是直接基于内存地址的,避免了频繁的系统调用,因此同步机制的开销较小。
👉🏻关于共享内存的接口函数
共享内存是一种高效的进程间通信方式,Linux系统提供了一系列函数用于进行共享内存的创建、映射、卸载和控制。下面分别介绍这些函数的作用:
- shmget函数
shmget函数用于创建或打开一个共享内存段。它的原型为:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
其中,参数key是一个键值,用于标识共享内存段,size是共享内存段的大小,shmflg是一个位掩码,用于指定创建共享内存段时的权限和选项。函数返回值是一个共享内存的标识符,如果出错则返回-1。
🌛 以下是shmflg的选项:
shmflg是在使用System V共享内存函数时用于设置标志的参数。它是一个整数值,可以通过按位或(|)运算将多个选项组合在一起。
- IPC_CREAT:如果指定此选项,当共享内存不存在时将创建一个新的共享内存对象。如果共享内存已经存在,则忽略该选项。通常与IPC_EXCL选项结合使用。
- IPC_EXCL:如果指定此选项,只有在共享内存不存在时才创建新的共享内存对象(保证返回的共享内存内容一定是全新的)。如果共享内存已经存在,则返回错误。通常与IPC_CREAT选项结合使用。
- SHM_RDONLY:以只读模式打开共享内存对象,只能读取共享内存中的数据,不能修改。
- SHM_R、SHM_W、SHM_RW:表示共享内存的权限。SHM_R表示只读权限,SHM_W表示可写权限,SHM_RW表示可读可写权限。
- SHM_LOCK、SHM_UNLOCK:用于对共享内存进行锁定和解锁操作。锁定共享内存可以防止其被交换到磁盘上,提高访问效率。
- SHM_HUGETLB:使用大页面分配共享内存,可以提高性能和效率。
2 shmat函数
shmat函数用于将共享内存段映射到进程的地址空间中。它的原型为:
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
其中,参数shmid是共享内存的标识符,shmaddr是指定的映射地址,如果为NULL,则由系统自动分配一个地址;shmflg是一个位掩码,用于指定映射共享内存时的选项。函数返回值是映射后的共享内存地址,如果出错则返回-1。
- shmdt函数
shmdt函数用于将共享内存段从进程的地址空间中卸载。它的原型为:
#include <sys/shm.h>
int shmdt(const void *shmaddr);
其中,参数shmaddr是共享内存的地址,即shmat函数的返回值。函数返回值为0表示成功,如果出错则返回-1。
- shmctl函数
shmctl函数用于对共享内存进行控制操作,例如删除共享内存段等。它的原型为:
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
其中,参数shmid是共享内存的标识符,cmd是一个命令码,用于指定要执行的操作,buf是一个指向共享内存信息结构体的指针,用于传递相关参数和返回信息。函数返回值为0表示成功,如果出错则返回-1。
🌛 其中cmd参数表示要执行的操作类型,具体选项如下:
- IPC_STAT:获取共享内存的状态信息,并将其写入到共享内存区域的shmid_ds结构中。
- IPC_SET:设置共享内存的状态信息,使用shmid_ds结构中的值对共享内存进行更新。
- IPC_RMID:删除共享内存标识符所代表的共享内存段。如果在删除之前有进程仍然附加到该共享内存段,则它不会被删除,直到最后一个进程离开后才会被删除。
- SHM_LOCK:锁定共享内存区域,防止被交换到磁盘上。
- SHM_UNLOCK:解锁共享内存区域。
- SHM_STAT:获取共享内存的状态信息,并将其写入到共享内存区域的shmid_ds结构中。与IPC_STAT功能相同。
- SHM_INFO:获取系统中共享内存的统计信息,并将其写入到shminfo结构中。
- SHM_STAT_ANY:与SHM_STAT功能相同,但不需要对共享内存区域具有所有者或创建者权限。
这些选项可用于对共享内存进行各种操作,例如获取共享内存的状态信息、设置共享内存的状态信息、删除共享内存、锁定共享内存区域等。具体使用哪个选项取决于需要执行的操作类型。
5.fotk函数
ftok函数是一个用于生成System V IPC(Inter-Process Communication,进程间通信)中的键值的函数。它的原型为:
key_t ftok(const char *pathname, int proj_id);
该函数接受一个路径名和一个项目ID作为参数,并返回一个用于标识System V IPC资源的键值。
路径名指定了一个现有的文件的路径,通常选择一个存在于文件系统中的文件。该文件用于关联一个唯一的键值,以便不同进程可以通过该键值来访问同一个System V IPC资源。如果路径名对应的文件不存在或不可访问,ftok函数将返回-1。
项目ID是一个用户定义的整数,用于进一步区分不同的System V IPC资源。在使用ftok函数时,应确保不同的资源具有不同的项目ID,以避免冲突。
ftok函数根据路径名和项目ID计算出一个32位的键值,并返回该键值。该键值可用于创建共享内存、消息队列和信号量等System V IPC对象。
👉🏻ipcs命令
ipcs命令是用于查看和管理System V IPC(Inter-Process Communication,进程间通信)对象的工具。它可以列出系统中当前存在的共享内存、消息队列和信号量等IPC对象的信息。
ipcs命令的基本语法如下:
ipcs [options]
常用的选项包括:
- -m:列出所有共享内存对象的信息。
- -q:列出所有消息队列的信息。
- -s:列出所有信号量的信息。
- -a:列出所有IPC对象(共享内存、消息队列和信号量)的信息。
- -p:显示与每个IPC对象关联的进程ID和进程所有者的信息。
- -l:显示System V IPC的限制和参数信息。
ipcs命令输出的信息包括对象的ID、键值、所有者、权限、大小等。通过查看ipcs的输出,可以了解系统中当前存在的IPC对象的状态、使用情况和其他相关信息。
对于管理员或有足够权限的用户,ipcs命令还可以用于删除或清理无用的IPC对象。例如,可以使用ipcrm命令结合ipcs的输出来删除某个特定的IPC对象。
👉🏻ipcrm命令
ipcrm命令用于删除System V IPC(Inter-Process Communication,进程间通信)对象,包括共享内存、消息队列和信号量等。
ipcrm命令的基本语法如下:
ipcrm [options] <shmid>
其中,<shmid>
是要删除的IPC对象的标识符。标识符可以是IPC对象的ID、键值或名称,具体取决于IPC对象的类型。
常用的选项包括:
- -m <id>:删除指定ID的共享内存对象。
- -q <id>:删除指定ID的消息队列。
- -s <id>:删除指定ID的信号量。
需要注意的是,使用ipcrm命令删除IPC对象需要具有足够的权限。通常只有管理员或创建IPC对象的用户才能删除它们。
在使用ipcs命令查看IPC对象信息后,可以得到IPC对象的ID,然后使用ipcrm命令删除指定的IPC对象。例如,通过以下命令可以删除一个共享内存对象:
ipcrm -m <ID>
请注意,在删除IPC对象之前,请确保没有进程正在使用该对象,否则可能会导致意外行为或数据丢失。
👉🏻共享内存实现进程间通信代码示例
以下是一个使用C++语言实现的简单进程间通信的例子,使用共享内存:
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#define SHM_SIZE 1024 // 共享内存大小
int main() {
int shmid;
char *shmaddr;
key_t key = ftok(".", 'a'); // 生成key值
// 创建共享内存
if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT|0666)) == -1) {
perror("shmget error");
exit(1);
}
// 将共享内存映射到进程地址空间
if ((shmaddr = (char *)shmat(shmid, NULL, 0)) == (char *)-1) {
perror("shmat error");
exit(1);
}
// 写入数据到共享内存
std::string message = "Hello, shared memory!";
strncpy(shmaddr, message.c_str(), SHM_SIZE);
// 创建子进程
pid_t pid = fork();
if (pid < 0) { // fork失败
perror("fork error");
exit(1);
} else if (pid == 0) { // 子进程
// 从共享内存中读取数据
std::cout << "Child get message: " << shmaddr << std::endl;
// 解除共享内存映射
if (shmdt(shmaddr) == -1) {
perror("shmdt error");
exit(1);
}
} else { // 父进程
// 等待子进程结束
wait(NULL);
// 解除共享内存映射
if (shmdt(shmaddr) == -1) {
perror("shmdt error");
exit(1);
}
// 删除共享内存
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl error");
exit(1);
}
}
return 0;
}
👉🏻消息队列
概念
消息队列是一种在分布式系统中常用的通信模式,它允许应用程序之间通过异步方式传递消息。简单来说,消息队列就是一个存储消息的容器,生产者将消息发送到队列中,而消费者则从队列中获取消息进行处理。
消息队列的设计基于生产者-消费者模型。生产者负责产生消息并将其发送到队列中,而消费者则从队列中读取消息并进行相应的处理。这种模型解耦了消息的发送方和接收方,使得系统更加灵活、可扩展和可靠。
使用消息队列有多个优点。首先,它实现了异步通信,生产者和消费者之间不需要实时交互,可以独立进行处理。这对于处理高并发和处理延迟敏感任务非常有帮助。
其次,消息队列提供了解耦的能力。生产者只需将消息发送到队列中,而不需要关心具体的消费者是谁以及何时处理消息。消费者可以根据自己的需求从队列中获取消息,并且可以根据处理速度调整消费的速率。
此外,消息队列还具备削峰填谷的功能。当系统面临突发的请求压力时,消息队列可以缓冲请求,并根据系统的处理能力逐渐消化积压的消息,避免系统过载。
常见的消息队列系统有 RabbitMQ、Apache Kafka、ActiveMQ 等。它们提供了丰富的功能和灵活的配置选项,可以根据不同的场景选择合适的消息队列系统。
总结起来,消息队列是一种重要的分布式通信机制,通过解耦、异步和削峰填谷等特性,它能够帮助构建高性能、可扩展和可靠的系统。在现代应用开发中,消息队列被广泛应用于日志处理、任务调度、实时消息推送等各种场景。
进程之间的消息队列通信
在进程之间,可以使用消息队列实现通信。每个进程都可以创建一个或多个消息队列,用来存放发送和接收的消息。
在 Linux 系统中,系统调用 msgget()
可以用来创建或获取一个消息队列的标识符。创建消息队列时需要指定一些参数,如消息队列的权限、大小等。创建完成后,进程可以使用 msgsnd()
向消息队列中添加消息,使用 msgrcv()
从消息队列中读取消息。
在发送消息时,需要指定消息类型、消息正文和消息长度。消息类型是一个正整数,用于区分不同的消息。当接收消息时,也需要指定要接收的消息类型。如果消息队列中有匹配的消息,则将其从队列中删除并返回给接收进程。
使用消息队列进行进程间通信的好处是它允许异步通信,并且可以通过消息队列来解耦发送方和接收方的耦合关系。同时,由于消息队列是基于内核实现的,因此可以跨越不同的进程,甚至可以跨越不同的机器。
但是使用消息队列进行进程间通信也存在一些问题。例如,消息队列的容量有限,如果消息队列已满,则无法再向其中添加消息。此外,消息队列的消息类型只能是正整数,这种限制可能无法满足某些特定的需求。
🍬msgget
msgget
是一个系统调用函数,用于创建一个消息队列或获取一个已存在的消息队列的标识符。
函数原型如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数说明:
key
:消息队列的键值,可以使用ftok()
函数生成。这个键值在系统中必须是唯一的,不同进程通过相同的键值可以访问同一个消息队列。msgflg
:消息队列的标志位,用于指定消息队列的创建和访问权限。常见的标志位包括IPC_CREAT
(创建消息队列)和IPC_EXCL
(与IPC_CREAT
配合使用,如果消息队列已经存在则报错)。
返回值:
- 成功时,返回消息队列的标识符(非负整数),可以用于后续操作;
- 失败时,返回 -1,并设置
errno
错误码。
以下是一个示例代码,演示如何使用 msgget
创建或获取一个消息队列:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main() {
key_t key;
int msgid;
// 生成一个消息队列的键值
key = ftok("path/to/a/file", 'A');
if (key == -1) {
perror("ftok");
exit(1);
}
// 创建或获取消息队列
msgid = msgget(key, IPC_CREAT | 0666);
if (msgid == -1) {
perror("msgget");
exit(1);
}
printf("Message queue created or obtained with ID: %d\n", msgid);
return 0;
}
在上述示例中,我们通过 ftok()
函数生成一个键值,该键值与指定的文件路径和标识字符 ‘A’ 相关联。然后使用 msgget
创建或获取一个消息队列,并将其标识符存储在 msgid
变量中。最后,打印出消息队列的标识符。
需要注意的是,在实际应用中,需要确保不同进程使用相同的键值来创建或获取同一个消息队列。
👉🏻信号量
信号量是一种用于进程间同步和互斥的机制。它主要用于解决多个进程或线程之间共享资源的并发访问问题。
由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种
关系为进程的互斥
信号量的基本原理是在共享资源上设置一个计数器,用于记录可用资源的数量。当进程需要使用共享资源时,首先检查信号量的计数器。如果计数器大于零,则表示有可用的资源,进程可以继续执行并将计数器减一。如果计数器为零,则表示没有可用的资源,进程需要等待,直到其他进程释放资源并增加计数器。
在 Linux 系统中,信号量通常使用 semget()
、semop()
和 semctl()
等系统调用函数来创建、操作和控制。
semget()
:用于创建或获取一个信号量集的标识符。semop()
:用于对信号量进行操作,如增加、减少等。semctl()
:用于控制信号量,如获取当前计数器的值、设置计数器的值、删除信号量等。
信号量的操作主要包括 P 操作(等待信号量)和 V 操作(释放信号量):
- P 操作(等待信号量):如果信号量的计数器大于零,则将计数器减一,进程继续执行;否则,进程进入等待状态。
- V 操作(释放信号量):将信号量的计数器加一,唤醒等待该信号量的其他进程。
通过合理地使用 P 操作和 V 操作,可以实现对共享资源的互斥访问和进程间的同步。
以下是一个示例代码,演示如何使用信号量进行进程间互斥访问:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SEM_KEY 1234
void P(int semid) {
struct sembuf sb;
sb.sem_num = 0; // 信号量集中的第一个信号量
sb.sem_op = -1; // 执行 P 操作
sb.sem_flg = 0;
semop(semid, &sb, 1);
}
void V(int semid) {
struct sembuf sb;
sb.sem_num = 0; // 信号量集中的第一个信号量
sb.sem_op = 1; // 执行 V 操作
sb.sem_flg = 0;
semop(semid, &sb, 1);
}
int main() {
key_t key;
int semid;
// 创建或获取信号量集的标识符
key = ftok("path/to/a/file", SEM_KEY);
if (key == -1) {
perror("ftok");
exit(1);
}
semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
// 对信号量进行操作
P(semid); // 进入临界区,执行互斥操作
printf("Critical section\n");
V(semid); // 离开临界区,释放资源
// 删除信号量集
semctl(semid, 0, IPC_RMID);
return 0;
}
semget,semop,semctl
semget、semop和semctl是Linux系统中用于控制信号量的三个关键函数。
-
semget:该函数用于创建或获取一个信号量集。其原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
- key:用于标识信号量集的键值,通常通过ftok函数生成。
- nsems:指定需要创建的信号量集中的信号量数量。
- semflg:设置信号量集的访问权限和其他标志位。
-
semop:该函数用于对信号量进行操作,例如等待信号量、释放信号量等。其原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, size_t nsops);
- semid:信号量集的标识符,由semget函数返回。
- sops:指向一个sembuf结构体数组,每个sembuf结构体表示对一个信号量的操作。
- nsops:指定sops数组中sembuf结构体的数量。
-
semctl:该函数用于对信号量进行控制操作,例如获取信号量的值、设置信号量的值等。其原型如下:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...);
- semid:信号量集的标识符,由semget函数返回。
- semnum:指定操作的信号量在信号量集中的索引,一般为0表示第一个信号量。
- cmd:指定要执行的控制命令。
- …:可选参数,根据不同的控制命令可能需要提供额外参数。
这些函数允许进程间通过信号量进行同步与互斥操作,确保资源的正确共享和访问。通过semget函数创建或获取信号量集,使用semop函数进行信号量的操作,使用semctl函数进行信号量的控制。
🌍 semctl函数的cmd选项
semctl函数的cmd选项用于指定对信号量进行的控制命令。下面是一些常用的cmd选项:
-
IPC_RMID:删除信号量集。该命令将删除指定的信号量集以及相关的数据结构。
semctl(semid, semnum, IPC_RMID);
-
SETVAL:设置信号量的值。该命令将指定的信号量设置为给定的值。
union semun { int val; // value for SETVAL struct semid_ds *buf; // buffer for IPC_STAT, IPC_SET unsigned short *array; // array for GETALL, SETALL struct seminfo *__buf; // buffer for IPC_INFO (Linux-specific) }; union semun arg; arg.val = 5; // 设置信号量的值为5 semctl(semid, semnum, SETVAL, arg);
-
GETVAL:获取信号量的值。该命令将返回指定信号量的当前值。
int value = semctl(semid, semnum, GETVAL);
-
SETALL:设置所有信号量的值。该命令将指定信号量集中所有信号量的值设置为给定的数组。
unsigned short values[3] = {2, 4, 6}; semctl(semid, 0, SETALL, values);
-
GETALL:获取所有信号量的值。该命令将返回指定信号量集中所有信号量的当前值。
unsigned short values[3]; semctl(semid, 0, GETALL, values);
-
IPC_STAT:获取信号量集的状态信息。该命令将返回一个semid_ds结构体,包含有关信号量集的详细信息。
struct semid_ds buf; semctl(semid, 0, IPC_STAT, &buf);
👉🏻IPC
IPC(Inter-Process Communication)资源是指在操作系统中用于进程间通信的各种资源。它们是为了满足不同进程之间的信息交换、同步和互斥等需求而提供的机制和数据结构。
常见的IPC资源包括:
-
信号量(Semaphore):用于多个进程或线程之间进行同步和互斥操作的计数器。
-
共享内存(Shared Memory):允许多个进程共享同一块物理内存区域,使得进程可以直接读写该内存区域,实现高效的数据交换。
-
消息队列(Message Queue):进程通过将消息发送到队列中,实现进程间的异步通信。
-
管道(Pipe):一种半双工的通信方式,可以在两个相关进程之间传输数据。
-
套接字(Socket):用于在网络中进行进程间通信的接口。
-
信号(Signal):用于通知进程发生了某个事件,例如中断信号、异常信号等。
这些IPC资源提供了不同的通信方式和机制,可以满足不同场景下进程间通信的需求。通过使用这些资源,进程可以进行数据的共享、同步操作、消息的传递以及事件的通知等,从而实现了进程间的有效交互和协作。
如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长