共享内存
共享内存概念
共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于
一个共享内存段会成为一个进程用户空间的一部分,因此这种 进程间通信(IPC) 机制无需内核介
入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其
他所有共享同一个段的进程可用。
◼ 与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据
从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC 技术的速度更快。
共享内存使用步骤
获取
调用 shmget() 创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其
他进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。
关联
◼ 使用 shmat() 来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。
◼ 此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,
程序需要使用由 shmat() 调用返回的 addr 值,它是一个指向进程的虚拟地址空间
中该共享内存段的起点的指针。
分离
◼ 调用 shmdt() 来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存
了。这一步是可选的,并且在进程终止时会自动完成这一步。
删除
◼ 调用 shmctl() 来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之
后内存段才会销毁。只有一个进程需要执行这一步
共享内存操作函数
ftok
ftok()
是 Unix 和类 Unix 系统中的一个函数,用于生成一个 System V IPC 键(key_t
类型),这个键通常用于 shmget()
、semget()
或 msgget()
函数来创建和访问进程间通信(IPC)资源,如共享内存、信号量或消息队列。ftok()
函数通过结合一个文件的存在和一个唯一的标识符来生成这个键,旨在创建一个尽可能唯一的键。
函数原型
ftok()
的函数原型定义在 <sys/ipc.h>
头文件中,具体如下:
key_t ftok(const char *pathname, int proj_id);
pathname
: 一个现存文件的路径名,这个文件必须对调用进程可访问,因为ftok()
需要获取文件的 inode 编号。该文件的实际内容不重要,只需保证文件存在。proj_id
: 一个非零的整数,通常是一个小整数(比如 1, 2, 3 等)。它用于与pathname
指定的文件的 inode 编号相结合,生成一个唯一的键。
返回值
- 成功: 返回一个
key_t
类型的值,该值可用于 System V IPC 函数。 - 失败: 返回
(key_t) -1
并可能设置errno
。
错误处理
ftok()
可能会因为以下原因失败:
errno = EACCES
: 调用进程没有访问指定pathname
文件的权限。errno = ENOENT
: 指定的pathname
文件不存在。
示例用法
以下是一个使用 ftok()
生成 IPC 键的示例,然后使用该键来创建共享内存:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>int main() {const char *path = "/tmp/somefile";int proj_id = 65; // 项目标识符key_t key = ftok(path, proj_id);if (key == -1) {perror("ftok failed");return 1;}int shmid = shmget(key, 1024, 0644 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");return 1;}printf("Key: %d, Shared memory segment ID: %d\n", key, shmid);return 0;
}
在这个程序中:
- 通过指定
/tmp/somefile
文件和一个项目标识符65
,ftok()
生成一个唯一的键。 - 这个键然后被用来通过
shmget()
创建一个共享内存段。
ftok()
被广泛用于生成 IPC 键,因为它可以提供一种相对简单且通常有效的方式来生成在系统范围内唯一的键。然而,生成的键并不保证绝对唯一,尤其是在大型系统中或者多个应用使用相似的 proj_id
和文件路径时可能会发生冲突。
shmget
shmget()
是 Unix 和类 Unix 系统中 System V 共享内存 API 的一个函数,用于创建新的共享内存段或获取现有的共享内存段的标识符。这个函数是进行进程间通信(IPC)中共享内存使用的**第一步,**它允许多个进程通过访问同一块物理内存来交换信息,从而实现高效的数据通信。
函数原型
shmget()
函数的原型定义在 <sys/shm.h>
头文件中,其基本语法如下:
int shmget(key_t key, size_t size, int shmflg);
-
key
: 用于唯一标识共享内存段的键。这个键可以是任意的key_t
类型的值,通常使用ftok()
函数从一个文件名和一个整数生成,以确保键的唯一性。另外,可以指定IPC_PRIVATE
作为键值来创建一个新的、无法通过键值访问的共享内存段,只能通过进程间继承或通过返回的共享内存标识符来访问。 -
size
: 共享内存段的大小,单位是字节。这个参数指定了需要分配的内存容量。 -
shmflg
: 操作标志,这个标志由一组位标志组合而成,用来设置内存段的访问权限和状态。这些标志包括权限位(如0644
或0666
等),以及可能包含的IPC_CREAT
和IPC_EXCL
。IPC_CREAT
表示如果指定的键没有对应的共享内存段,则创建一个新的内存段。如果同时指定了IPC_EXCL
,在键已经有对应的共享内存段存在的情况下,shmget()
会失败。
返回值
- 成功: 返回一个整数,这个整数是共享内存段的标识符(shmid),用于后续的
shmat()
、shmdt()
、shmctl()
等操作。 - 失败: 返回
-1
,并设置errno
以指示错误类型。常见的errno
值包括EINVAL
(参数无效)、EEXIST
(已存在具有相同键的共享内存段,且使用了IPC_EXCL
)、ENOENT
(指定的键不存在,且没有指定IPC_CREAT
)、EACCES
(权限错误)等。
示例用法
这里是一个创建共享内存段的基本示例:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>int main() {key_t key = ftok("somefile", 65); // 生成唯一键int shmid = shmget(key, 1024, 0644 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");return 1;}printf("Shared memory segment created with ID: %d\n", shmid);return 0;
}
在这个示例中,ftok()
函数用于生成一个唯一的键。shmget()
尝试使用这个键和指定的大小及权限创建一个新的共享内存段。如果成功,它会打印出共享内存段的标识符。
shmat
shmat()
函数是 Unix 和类 Unix 系统中 System V 共享内存 API 的一部分,用于将共享内存段附加到调用进程的地址空间中。这一步骤是在成功创建或获取共享内存段标识符后(通常通过 shmget()
完成)进行的。
函数原型
shmat()
的函数原型定义在 <sys/shm.h>
头文件中,具体如下:
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid
: 共享内存段的标识符,这是通过shmget()
获取的。shmaddr
: 指定共享内存附加到进程的地址空间中的具体地址。如果设置为NULL
,系统会自动选择一个合适的地址。如果非NULL
,则通常需要设置shmflg
中的SHM_RND
来使地址对齐到共享内存系统的要求。shmflg
: 操作标志,可以设置为SHM_RDONLY
表示以只读方式附加共享内存。如果不需要特别的读写权限标志,则设置为0
。
返回值
- 成功: 返回一个指针,指向共享内存段在当前进程地址空间中的起始地址。
- 失败: 返回
(void *) -1
,并设置errno
来指示错误原因。
错误处理
shmat()
可能会设置如下 errno
值:
- EINVAL:
shmid
无效,或shmaddr
没有对齐。 - EACCES: 调用进程没有适当的访问权限。
- ENOMEM: 内存不足,无法完成操作。
示例用法
下面是一个示例,演示如何将共享内存段附加到进程的地址空间,并如何使用返回的地址:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>int main() {key_t key = ftok("somefile", 65);int shmid = shmget(key, 1024, 0644 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");return 1;}char *data = (char *)shmat(shmid, NULL, 0);if (data == (void *) -1) {perror("shmat failed");return 1;}// 使用共享内存printf("Writing to shared memory...\n");sprintf(data, "Hello from process %d", getpid());// 分离共享内存if (shmdt(data) == -1) {perror("shmdt failed");return 1;}return 0;
}
在这个程序中:
ftok()
用于生成一个基于文件和项目编号的唯一键。shmget()
创建一个新的共享内存段或获取已存在的共享内存段。shmat()
将这个共享内存段附加到进程的地址空间,并返回一个指针。- 通过这个指针,我们向共享内存写入信息。
shmdt()
用于将共享内存从进程的地址空间中分离。
这个例子简单展示了如何通过 System V IPC 的共享内存机制进行进程间通信。
shmdt
shmdt()
是 Unix 和类 Unix 系统中 System V 共享内存 API 的一部分,用于将共享内存段从调用进程的地址空间中分离。这是在使用 shmat()
函数将共享内存附加到进程地址空间后的反向操作。
函数原型
shmdt()
的函数原型定义在 <sys/shm.h>
头文件中,其基本语法如下:
int shmdt(const void *shmaddr);
shmaddr
: 这是指向共享内存段在进程地址空间中的起始地址的指针,这个地址是先前通过shmat()
调用获取的。
返回值
- 成功: 返回
0
。 - 失败: 返回
-1
并设置errno
来指示出错原因。
错误处理
调用 shmdt()
可能会设置以下 errno
值:
- EINVAL: 指定的
shmaddr
无效,表示没有任何共享内存被附加到这个地址。
示例用法
这里是一个完整的示例,演示如何附加共享内存,使用它,然后将其从进程的地址空间中分离:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>int main() {key_t key = ftok("somefile", 65);int shmid = shmget(key, 1024, 0644 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");return 1;}// 将共享内存附加到进程的地址空间char *data = (char *)shmat(shmid, NULL, 0);if (data == (void *) -1) {perror("shmat failed");return 1;}// 使用共享内存printf("Writing to shared memory...\n");sprintf(data, "Hello from process %d", getpid());// 操作完成后,从进程的地址空间中分离共享内存if (shmdt(data) == -1) {perror("shmdt failed");return 1;}return 0;
}
在这个例子中:
ftok()
生成一个键,用于shmget()
。shmget()
创建或获取共享内存。shmat()
将共享内存附加到进程地址空间。- 使用指针
data
对共享内存进行操作。 shmdt()
将共享内存从进程地址空间分离,是进行清理的重要步骤,尤其是在长期运行的应用程序中,以避免资源泄漏。
shmdt()
是管理共享内存生命周期的关键部分,确保共享内存使用后能够正确地从进程中解除映射,以维护系统的健康和稳定。
shmctl
shmctl()
是 Unix 和类 Unix 系统中 System V 共享内存 API 的一部分,用于执行各种控制操作,如查询状态、设置权限、标记共享内存段的删除等。这个函数对于管理共享内存的生命周期和属性至关重要。
函数原型
shmctl()
的函数原型定义在 <sys/shm.h>
头文件中,具体如下:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid
: 共享内存段的标识符,通过shmget()
获取。cmd
: 指定要执行的命令。常用的命令包括IPC_STAT
、IPC_SET
、IPC_RMID
等。buf
: 指向shmid_ds
结构的指针,该结构用于存储共享内存段的状态信息或修改共享内存段的权限和其他设置。
命令
IPC_STAT
: 将共享内存段的当前信息复制到buf
指向的shmid_ds
结构中。IPC_SET
: 根据buf
指向的shmid_ds
结构中提供的信息设置共享内存段的参数。IPC_RMID
: 标记共享内存段以便删除。共享内存不会立即删除,而是等到所有附加的进程都已分离后才真正删除。
结构 shmid_ds
shmid_ds
结构包含了共享内存段的多种状态信息,包括权限、大小、最后访问时间等字段。具体字段可能根据不同的系统有所变化,但通常包括:
struct ipc_perm shm_perm
: 控制权限,包括所有者、群组和其他的读写执行权限。size_t shm_segsz
: 共享内存段的大小,以字节为单位。time_t shm_atime
: 最后一次附加操作的时间。time_t shm_dtime
: 最后一次分离操作的时间。time_t shm_ctime
: 最后一次修改ipc_perm
结构的时间。pid_t shm_cpid
: 创建共享内存段的进程ID。pid_t shm_lpid
: 最后操作共享内存段的进程ID。unsigned short shm_nattch
: 当前附加到共享内存段的进程数量。
示例用法
以下是一个使用 shmctl()
来删除共享内存段的例子:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>int main() {key_t key = ftok("somefile", 65);int shmid = shmget(key, 1024, 0644 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");return 1;}// 标记共享内存段以便删除if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl failed");return 1;}printf("Shared memory segment marked for deletion\n");return 0;
}
在这个示例中:
ftok()
生成一个键,用于shmget()
。shmget()
创建共享内存。shmctl()
使用IPC_RMID
命令标记共享内存段以便删除。此操作不需要shmid_ds
结构,因此第三个参数传递为NULL
。
shmctl()
提供了对共享内存进行细粒度控制的能力,使得进程可以有效地管理和协调对共享资源的访问。
共享内存相关的函数
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);- 功能:创建一个新的共享内存段,或者获取一个既有的共享内存段的标识。新创建的内存段中的数据都会被初始化为0- 参数:- key : key_t类型是一个整形,通过这个找到或者创建一个共享内存。一般使用16进制表示,非0值- size: 共享内存的大小- shmflg: 属性- 访问权限- 附加属性:创建/判断共享内存是不是存在- 创建:IPC_CREAT- 判断共享内存是否存在: IPC_EXCL , 需要和IPC_CREAT一起使用IPC_CREAT | IPC_EXCL | 0664- 返回值:失败:-1 并设置错误号成功:>0 返回共享内存的引用的ID,后面操作共享内存都是通过这个值。void *shmat(int shmid, const void *shmaddr, int shmflg);- 功能:和当前的进程进行关联- 参数:- shmid : 共享内存的标识(ID),由shmget返回值获取- shmaddr: 申请的共享内存的起始地址,指定NULL,内核指定- shmflg : 对共享内存的操作- 读 : SHM_RDONLY, 必须要有读权限- 读写: 0- 返回值:成功:返回共享内存的首(起始)地址。 失败(void *) -1int shmdt(const void *shmaddr);- 功能:解除当前进程和共享内存的关联- 参数:shmaddr:共享内存的首地址- 返回值:成功 0, 失败 -1int shmctl(int shmid, int cmd, struct shmid_ds *buf);- 功能:对共享内存进行操作。删除共享内存,共享内存要删除才会消失,创建共享内存的进行被销毁了对共享内存是没有任何影响。- 参数:- shmid: 共享内存的ID- cmd : 要做的操作- IPC_STAT : 获取共享内存的当前的状态- IPC_SET : 设置共享内存的状态- IPC_RMID: 标记共享内存被销毁- buf:需要设置或者获取的共享内存的属性信息- IPC_STAT : buf存储数据- IPC_SET : buf中需要初始化数据,设置到内核中- IPC_RMID : 没有用,NULLkey_t ftok(const char *pathname, int proj_id);- 功能:根据指定的路径名,和int值,生成一个共享内存的key- 参数:- pathname:指定一个存在的路径/home/nowcoder/Linux/a.txt/ - proj_id: int类型的值,但是这系统调用只会使用其中的1个字节范围 : 0-255 一般指定一个字符 'a'问题1:操作系统如何知道一块共享内存被多少个进程关联?- 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch- shm_nattach 记录了关联的进程个数问题2:可不可以对共享内存进行多次删除 shmctl- 可以的- 因为shmctl 标记删除共享内存,不是直接删除- 什么时候真正删除呢?当和共享内存关联的进程数为0的时候,就真正被删除- 当共享内存的key为0的时候,表示共享内存被标记删除了如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。共享内存和内存映射的区别1.共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)2.共享内存效果更高3.内存所有的进程操作的是同一块共享内存。内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。4.数据安全- 进程突然退出共享内存还存在内存映射区消失- 运行进程的电脑死机,宕机了数据存在在共享内存中,没有了内存映射区的数据 ,由于磁盘文件中的数据还在,所以内存映射区的数据还存在。5.生命周期- 内存映射区:进程退出,内存映射区销毁- 共享内存:进程退出,共享内存还在,标记删除(所有的关联的进程数为0),或者关机如果一个进程退出,会自动和共享内存进行取消关联。
读写案例
write
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>int main() { // 1.创建一个共享内存int shmid = shmget(100, 4096, IPC_CREAT|0664);printf("shmid : %d\n", shmid);// 2.和当前进程进行关联void * ptr = shmat(shmid, NULL, 0);char * str = "helloworld";// 3.写数据memcpy(ptr, str, strlen(str) + 1);printf("按任意键继续\n");getchar();// 4.解除关联shmdt(ptr);// 5.删除共享内存shmctl(shmid, IPC_RMID, NULL);return 0;
}
read
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>int main() { // 1.获取一个共享内存int shmid = shmget(100, 0, IPC_CREAT);printf("shmid : %d\n", shmid);// 2.和当前进程进行关联void * ptr = shmat(shmid, NULL, 0);// 3.读数据printf("%s\n", (char *)ptr);printf("按任意键继续\n");getchar();// 4.解除关联shmdt(ptr);// 5.删除共享内存shmctl(shmid, IPC_RMID, NULL);return 0;
}
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson27$ gcc read_shm.c -o read
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson27$ gcc write_shm.c -o write
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson27$ ls
read read_shm.c write write_shm.c 共享内存.c
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson27$ ./write
shmid : 25
按任意键继续
daic@daic:~/Linux/linuxwebserver/part02multiProgress/lesson27$ ./read
shmid : 25
helloworld
按任意键继续
共享内存操作命令
◼ ipcs 用法
ipcs -a
// 打印当前系统中所有的进程间通信方式的信息
ipcs -m
// 打印出使用共享内存进行进程间通信的信息
daic@daic:~/Linux$ ipcs -m------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 8 daic 600 134217728 2 dest
0x00000000 11 daic 600 524288 2 dest
0x00000000 14 daic 600 524288 2 dest
0x00000000 15 daic 600 524288 2 dest
0x00000000 18 daic 600 524288 2 dest
0x00000000 19 daic 600 524288 2 dest
0x00000000 20 daic 600 524288 2 dest
0x00000064 26 daic 664 4096 2
ipcs -q
// 打印出使用消息队列进行进程间通信的信息
ipcs -s
// 打印出使用信号进行进程间通信的信息
◼ ipcrm 用法
ipcrm -M shmkey // 移除用shmkey创建的共享内存段
ipcrm -m shmid
// 移除用shmid标识的共享内存段
ipcrm -Q msgkey
// 移除用msqkey创建的消息队列
ipcrm -q msqid
// 移除用msqid标识的消息队列
ipcrm -S semkey
// 移除用semkey创建的信号
ipcrm -s semid
// 移除用semid标识的信号