目录
(图像由AI生成)
0.前言
在上篇博客中,我们介绍了 Linux 进程间通信的方式之一——管道。管道是一种简单、方便的进程间通信方式,但它的效率和数据持久性较低,适用于父子进程之间的数据传输。在本篇中,我们将继续深入 Linux IPC(Inter-Process Communication,进程间通信)机制,介绍另一种高效的通信方式——System V 共享内存。共享内存是一种允许多个进程直接访问内存区域的机制,能够提供比管道更快的数据交换速度,非常适合在进程间传输大量数据。
1.共享内存的数据结构
共享内存的示意图如下所示:
在 Linux 系统中,struct shmid_ds
是一个描述共享内存段的内核数据结构,用于管理每个共享内存段的状态和属性。以下是该结构体的主要成员及其作用说明:
struct shmid_ds {
struct ipc_perm shm_perm; /* 访问权限和标识符 */
int shm_segsz; /* 共享内存段的大小(字节) */
__kernel_time_t shm_atime; /* 上次连接到共享内存的时间 */
__kernel_time_t shm_dtime; /* 上次断开共享内存的时间 */
__kernel_time_t shm_ctime; /* 共享内存的上次修改时间 */
__kernel_ipc_pid_t shm_cpid; /* 创建共享内存的进程 ID */
__kernel_ipc_pid_t shm_lpid; /* 上次操作共享内存的进程 ID */
unsigned short shm_nattch; /* 当前连接到该共享内存段的进程数 */
unsigned short shm_unused; /* 兼容性字段,已废弃 */
void *shm_unused2; /* 保留字段(DIPC 使用) */
void *shm_unused3; /* 未使用的保留字段 */
};
主要字段说明:
-
shm_perm:这是一个
ipc_perm
类型的结构体,包含共享内存的权限信息,包括用户 ID、组 ID 和访问权限等。它允许系统控制对共享内存段的访问。 -
shm_segsz:共享内存段的大小,以字节为单位。在分配共享内存段时,指定该段的大小,以确定可用内存空间。
-
shm_atime、shm_dtime、shm_ctime:这些字段记录了共享内存段的时间信息。
shm_atime
表示上次连接的时间,shm_dtime
表示上次断开的时间,shm_ctime
表示最后一次更改(如调整权限)的时间。 -
shm_cpid 和 shm_lpid:
shm_cpid
是创建共享内存段的进程 ID,shm_lpid
是最后一个操作该共享内存的进程 ID,用于跟踪对共享内存的访问和管理。 -
shm_nattch:表示当前连接到此共享内存段的进程数。该值用于确定共享内存是否在使用中,通常只有当
shm_nattch
为 0 时,才可以销毁该段。 -
shm_unused、shm_unused2 和 shm_unused3:这些字段为兼容性和扩展预留,目前在大多数系统中未被使用。
这些字段的设置和更新由内核在共享内存的管理过程中自动完成,因此开发者在调用共享内存相关的 API(如 shmget
、shmat
等)时无需直接操作这些字段。
2.代码示例及共享内存函数
2.1代码示例
以下代码展示了如何使用 System V 共享内存来进行进程间的通信。此示例包括创建共享内存、写入数据、读取数据,以及在多个进程之间同步共享内存的操作,旨在演示共享内存的基本用法。每个代码片段均附有详细注释以便理解。
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define SHM_SIZE 1024 // 共享内存的大小(字节)
int main() {
// Step 1: 创建唯一的键值
key_t key = ftok("shmfile", 65); // 使用ftok生成一个唯一的key
if (key == -1) {
perror("ftok failed");
exit(1);
}
// Step 2: 创建共享内存段
int shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT); // 0666代表读写权限
if (shmid == -1) {
perror("shmget failed");
exit(1);
}
// Step 3: 创建子进程
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
// 子进程:写入数据到共享内存
char *data = (char*) shmat(shmid, NULL, 0); // 连接到共享内存
if (data == (char *)(-1)) {
perror("shmat failed");
exit(1);
}
printf("子进程:写入数据到共享内存...\n");
strcpy(data, "Hello, Shared Memory! This is child process writing.");
printf("子进程:数据写入完成,内容为:%s\n", data);
// 分离共享内存
if (shmdt(data) == -1) {
perror("shmdt failed");
exit(1);
}
exit(0); // 子进程完成写入后退出
} else {
// 父进程等待子进程完成
wait(NULL);
// 父进程:读取共享内存中的数据
char *data = (char*) shmat(shmid, NULL, 0); // 连接到共享内存
if (data == (char *)(-1)) {
perror("shmat failed");
exit(1);
}
printf("父进程:读取共享内存中的数据...\n");
printf("父进程:读取的数据为:%s\n", data);
// 分离共享内存
if (shmdt(data) == -1) {
perror("shmdt failed");
exit(1);
}
// 删除共享内存段
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl failed");
exit(1);
}
}
return 0;
}
代码说明:
-
Step 1: 使用
ftok
函数生成一个唯一的键值key
。这个键将用于识别共享内存段。ftok
可以通过指定文件路径和项目标识符生成一个独特的 key,便于共享内存段在不同进程间的访问。 -
Step 2: 使用
shmget
函数创建一个大小为SHM_SIZE
的共享内存段,并设置其权限为读写。IPC_CREAT
标志表示如果该共享内存段不存在,则创建新的。 -
Step 3: 使用
fork
创建一个子进程,演示不同进程对共享内存的访问。
子进程:
- 使用
shmat
函数将共享内存段映射到自身的地址空间,获取指向共享内存的指针data
。 - 将字符串写入共享内存,并显示写入的内容。
- 完成后使用
shmdt
函数分离共享内存。
父进程:
- 使用
wait
等待子进程完成写入。 - 使用
shmat
连接到共享内存段,读取内容并打印。 - 断开连接并使用
shmctl
删除共享内存段,确保内存资源被释放。
运行结果
- 子进程会将数据写入共享内存,而父进程读取并显示数据,展示了进程间共享内存通信的基本工作流程。
2.2 shmget
函数
功能:shmget
用于创建或获取一个共享内存段。
原型:
int shmget(key_t key, size_t size, int shmflg);
参数:
key
:共享内存段的标识符,通常通过ftok
函数生成。用于区分不同的共享内存段。size
:共享内存段的大小(字节)。这是共享内存分配的内存空间大小。shmflg
:控制共享内存段的权限和行为,通常是访问权限和创建标志的组合。- 取值
IPC_CREAT
:若共享内存段不存在,则创建新段;若已存在,则返回现有段。 - 取值
IPC_CREAT | IPC_EXCL
:若共享内存段不存在,则创建;若已存在,则返回错误。
- 取值
返回值:成功时返回共享内存段的标识符(非负整数);失败时返回 -1,并设置 errno
标记错误原因。
2.3 shmat
函数
功能:shmat
将指定的共享内存段附加到当前进程的地址空间,允许进程访问共享内存中的数据。
原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid
:共享内存段的标识符,由shmget
返回。shmaddr
:用于指定共享内存的连接地址。通常为NULL
,让系统选择合适的地址。shmflg
:附加模式,可设为SHM_RDONLY
以只读方式附加共享内存;若为 0,则为读写模式。
返回值:成功时返回共享内存段在当前进程地址空间中的首地址;失败时返回 (void *) -1
。
2.4 shmdt
函数
功能:shmdt
用于将共享内存段从当前进程的地址空间中分离。
原型:
int shmdt(const void *shmaddr);
参数:
shmaddr
:共享内存段在进程地址空间中的首地址,即shmat
返回的地址。
返回值:成功时返回 0;失败时返回 -1。
2.5 shmctl
函数
功能:shmctl
用于控制共享内存段的操作,如删除共享内存段、获取共享内存信息等。
原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid
:共享内存段的标识符。cmd
:控制命令,常用的有:IPC_RMID
:删除共享内存段。IPC_STAT
:获取共享内存段的状态信息,存储在buf
中。IPC_SET
:设置共享内存段的状态信息,如更改权限。
buf
:shmid_ds
结构体指针,用于存储或设置共享内存段的状态信息。
返回值:成功时返回 0;失败时返回 -1,并设置 errno
标记错误原因。
3.共享内存和管道的比较
特性 | 共享内存 | 管道 |
---|---|---|
数据交换速度 | 高速(直接内存访问) | 较慢(需读写操作) |
数据存储持久性 | 手动控制,需显式删除 | 随进程结束自动销毁 |
访问方式 | 允许多个进程读写,需同步控制 | 适用于父子进程之间 |
数据量 | 适合大数据量传输 | 适合小数据量传输 |
通信方向 | 双向读写 | 单向(单向管道) |
编程复杂度 | 较高(需同步机制) | 较低 |
创建方式 | 使用 shmget 创建 | 使用 pipe 或 mkfifo |
4.进一步了解systemV
System V 是 UNIX 系统的一个重要版本,其提供了一套经典的进程间通信(IPC)机制,包括 共享内存、消息队列 和 信号量,它们被称为 System V IPC。这些机制用于满足多进程间的数据共享、消息传递和同步需求,是传统 UNIX 系统中常见的 IPC 手段。
System V 其他通信方式:
-
消息队列(Message Queue):消息队列允许进程通过有序的消息进行通信。进程可以将消息放入队列,其他进程按顺序读取消息,实现了异步、结构化的数据传递。
-
信号量(Semaphore):信号量用于控制多个进程对共享资源的访问,主要解决并发情况下的同步问题。信号量可以增加或减少资源计数,常用于进程的互斥控制,防止多个进程同时操作同一资源。
这些 System V IPC 方式各自有不同的应用场景,消息队列适用于消息传递,信号量适用于同步控制,而共享内存适用于高效的数据共享。
5.结语
通过本篇博客,我们深入探讨了 Linux 中 System V 共享内存的使用方法,包括其数据结构、关键函数及其使用示例。同时,我们将共享内存与管道进行了对比,并简要介绍了 System V 的其他通信方式,如消息队列和信号量。共享内存作为一种高效的进程间通信机制,适用于大数据量的传输和多进程的资源共享,是 Linux IPC 中不可或缺的一部分。希望通过本篇博客,大家能够更好地理解和运用共享内存来优化进程间的通信效率。