1. 共享内存的概念
共享内存是一种进程间通信的机制,它允许两个或多个进程共享同一块内存区域。在共享内存中,进程可以读取和写入内存中的数据,从而实现进程间的数据交换。
2. 共享内存的优势
- 高效:共享内存提供了最快的进程间通信方式,因为数据直接存在于内存中,不需要进行复制和数据传输。
- 灵活:共享内存可以用于实现各种类型的数据交换,从简单的变量到复杂的数据结构都可以共享。
3. 共享内存的步骤
使用共享内存进行进程间通信通常包括以下步骤:
- 创建共享内存段:通过
shmget()
函数创建一个共享内存段。 - 将共享内存连接到进程地址空间:通过
shmat()
函数将共享内存段连接到进程的地址空间。 - 访问共享内存:在进程间可以直接通过指针访问共享内存中的数据。
- 分离共享内存:通过
shmdt()
函数将共享内存从进程的地址空间分离。 - 删除共享内存段:通过
shmctl()
函数删除共享内存段。
4. 示例代码
进程1:写入共享内存的代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
int main() {
int shmid;
key_t key;
char *shm, *s;
// 生成一个key
key = ftok(".", 'x');
// 创建共享内存段
shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 将共享内存连接到进程地址空间
shm = shmat(shmid, NULL, 0);
if (shm == (char *) -1) {
perror("shmat");
exit(1);
}
// 在共享内存中写入数据
s = shm;
for (char c = 'a'; c <= 'z'; c++) {
*s++ = c;
}
*s = '\0'; // 添加字符串结束标志
// 等待一段时间,让进程2有足够的时间去读取共享内存中的数据
sleep(5);
// 分离共享内存
if (shmdt(shm) == -1) {
perror("shmdt");
exit(1);
}
// 删除共享内存段
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
exit(1);
}
return 0;
}
进程2:读取共享内存的代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
int main() {
int shmid;
key_t key;
char *shm;
// 生成一个key
key = ftok(".", 'x');
// 获取共享内存段的标识符
shmid = shmget(key, SHM_SIZE, 0666);
if (shmid == -1) {
perror("shmget");
exit(1);
}
// 将共享内存连接到进程地址空间
shm = shmat(shmid, NULL, 0);
if (shm == (char *) -1) {
perror("shmat");
exit(1);
}
// 读取共享内存中的数据并打印
printf("共享内存中的数据: %s\n", shm);
// 分离共享内存
if (shmdt(shm) == -1) {
perror("shmdt");
exit(1);
}
return 0;
}
运行进程1
运行进程2
下面我们来解读代码
首先是进程1
创建key
ftok()
函数用于将一个文件路径和一个整数值转换为一个唯一的 key,以便用于创建共享内存段、消息队列和信号量等 IPC 对象。
key_t ftok(const char *pathname, int proj_id);
参数说明:
pathname
:文件路径名。proj_id
:项目标识符,一个小整数。
ftok()
函数将 pathname
参数所指定的文件的设备号和 inode 号以及 proj_id
参数合并为一个 key 值,并返回该 key。这样就能确保对于同一个文件路径和 proj_id
,ftok()
函数始终返回相同的 key 值。
在使用 ftok()
函数时,需要注意以下几点:
pathname
参数指定的文件必须是存在的,否则会返回 -1,并设置 errno 为 ENOENT。- 不同的文件路径和
proj_id
可能会生成相同的 key 值,但是这种情况极少发生,并且不太可能。 proj_id
的范围应该在 [0, 255] 之间,通常使用 ASCII 字符作为proj_id
的值。
通常在创建共享内存段、消息队列和信号量等 IPC 对象时,都会使用 ftok()
函数来生成对应的 key 值。
然后是创建共享内存段
shmget()
函数是用于创建共享内存段或获取已存在的共享内存段的系统调用之一。它的原型如下:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
数说明:
key
:共享内存的标识符,通常使用ftok()
函数生成。size
:共享内存的大小,以字节为单位。shmflg
:共享内存的权限标志,可以是 IPC_CREAT(创建共享内存)和/或 IPC_EXCL(与 IPC_CREAT 一起使用时,如果共享内存已存在则返回错误)以及其他标志的按位或运算结果。
shmget()
函数返回一个共享内存标识符(或者是一个错误码)。这个标识符可以用于后续的共享内存操作,比如连接到进程地址空间、删除共享内存等。
使用 shmget()
函数时,通常的步骤是:
- 调用
ftok()
函数生成一个唯一的 key 值。 - 调用
shmget()
函数创建一个新的共享内存段,或者获取已存在的共享内存段。 - 如果创建成功,后续可以调用
shmat()
函数将共享内存段连接到进程的地址空间,然后就可以在进程中直接访问共享内存。 - 使用完毕后,还可以调用
shmdt()
函数将共享内存段从进程的地址空间分离,以及shmctl()
函数删除共享内存段。
这些函数共同组成了 Linux 中用于处理共享内存的一套接口。
shmat()
函数用于将共享内存段连接到调用进程的地址空间,使得进程可以直接访问共享内存中的数据。它的原型如下:
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数说明:
shmid
:共享内存标识符,即由shmget()
函数返回的共享内存段标识符。shmaddr
:指定共享内存连接到进程地址空间的位置,通常设为NULL
,由系统自动选择合适的位置。shmflg
:共享内存的访问权限标志,通常设为 0。
shmat()
函数返回一个指向共享内存段起始地址的指针,如果连接失败,则返回 (void *) -1
。
使用 shmat()
函数连接共享内存时,通常的步骤是:
- 调用
shmget()
函数创建或获取一个共享内存段,并得到共享内存标识符shmid
。 - 调用
shmat()
函数将共享内存段连接到进程的地址空间。 - 连接成功后,就可以使用返回的指针直接访问共享内存中的数据。
- 使用完毕后,调用
shmdt()
函数将共享内存段从进程的地址空间分离。
通过 shmat()
函数连接到共享内存后,进程就可以像访问普通的内存一样操作共享内存中的数据。
通常情况下,在不再需要访问共享内存时,应该及时调用 shmdt()
函数来分离共享内存,以释放相关资源并避免资源泄漏。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
如果共享内存没有被分离,它将继续留在进程的地址空间中,直到进程终止。这可能会导致一些问题:
-
资源泄漏:共享内存会占用进程的地址空间,如果不及时分离,可能会导致内存泄漏,造成系统资源浪费。
-
竞态条件:如果多个进程共享同一块共享内存,并且其中一个进程修改了共享内存中的数据,其他进程可能会在未意识到数据已经被修改的情况下继续访问它,导致数据不一致或错误。
-
内存错误:在某些情况下,操作系统可能会在进程终止时自动释放共享内存,但这不是一个可靠的假设,因为它依赖于操作系统的具体实现。
对应的读取端只需要改变标识符即可
效果如下