共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进 程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个 进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。 Linux 操作系统的进程通常使用的是虚拟内存,虚拟内存空间是有由物理内存映射而来的。System V 共 享内存能够实现让两个或多个进程访问同一段物理内存空间,达到数据交互的效果。
在Linux系统中,有多种方式可以实现共享内存,其中一种是使用POSIX共享内(posix_shm)。POSIX共享内存有两种方法:
1.内存映射文件
先用open函数打开一个文件,然后调用mmap函数把得到的描述符映射到当前进程地址空间中。这种方式访问速度相对较慢,因为需要内核同步或异步更新到文件系统中。
(1)代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
int main() {
// 打开文件
int fd = open("example.txt", O_RDWR);
if (fd == -1) {
perror("Error opening file");
exit(EXIT_FAILURE);
}
// 获取文件大小
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("Error getting the file size");
exit(EXIT_FAILURE);
}
off_t length = sb.st_size; // 文件大小,单位是字节
// 映射内存到进程的地址空间
char* map = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
perror("Error mmapping the file");
exit(EXIT_FAILURE);
}
// 打印映射的内存内容,即文件内容
for (off_t i = 0; i < length; i++) {
printf("%c", map[i]); // 打印每个字符,即文件内容
}
printf("\n");
// 释放内存映射和文件描述符
if (munmap(map, length) == -1) {
perror("Error un-mmapping the file");
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
(2)注解
open
函数用于打开文件,其返回值是文件描述符。如果打开失败,则返回-1。第二个参数O_RDWR
表示以读写模式打开文件。fstat
函数用于获取文件的大小,其返回值是stat
结构体,其中st_size
成员表示文件大小(单位是字节)。如果获取失败,则返回-1。mmap
函数用于将文件映射到进程的地址空间。第一个参数是映射区域的起始地址,通常为NULL。第二个参数是映射区域的长度。第三个参数是保护标志,这里设置为读、写和共享(可读、可写、可被其他进程共享)。第四个参数是映射对象的类型,这里设置为共享内存。第五个参数是文件描述符。第六个参数是文件映射的偏移量。如果映射成功,则返回映射区域的指针;否则返回MAP_FAILED。munmap
函数用于释放内存映射。第一个参数是映射区域的指针。第二个参数是映射区域的长度。如果释放成功,则返回0;否则返回-1。
2.共享内存对象
先用shm_open打开一个Posix IPC名字(也可以是文件系统中的一个路径名),然后调用mmap将返回的描述符映射到当前进程的地址空间。
(1)代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_SIZE 1024 // 共享内存大小
int main() {
int fd;
void *map_ptr;
// 打开共享内存对象,以读写模式打开,不创建新对象,如果对象不存在则返回-1
fd = shm_open("/Posix IPC", O_RDWR | O_CREAT, 0666);
if (fd == -1) {
perror("shm_open");
exit(EXIT_FAILURE);
}
// 调整共享内存对象的大小,这里将其设置为1024字节
if (ftruncate(fd, SHM_SIZE) == -1) {
perror("ftruncate");
exit(EXIT_FAILURE);
}
// 将共享内存对象的描述符映射到当前进程的地址空间,map_ptr指向的就是这块内存的起始地址
map_ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_ptr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
// 现在可以在map_ptr指向的内存区域进行读写操作了,这就像操作普通的内存一样简单
// ... 写入数据到 map_ptr 指向的内存区域 ...
// ... 从 map_ptr 指向的内存区域读取数据 ...
// 当进程不再需要访问共享内存时,可以通过调用munmap来撤销内存映射,参数是map_ptr和映射的长度
if (munmap(map_ptr, SHM_SIZE) == -1) {
perror("munmap");
exit(EXIT_FAILURE);
}
// 关闭共享内存对象的描述符,然后删除该对象,参数是fd和0,表示删除成功返回0,否则返回-1
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
if (shm_unlink("/my_shm") == -1) {
perror("shm_unlink");
exit(EXIT_FAILURE);
}
return 0;
}
(2)注解
shm_open
函数用于打开或创建共享内存对象。第一个参数是对象名,第二个参数是打开模式(这里使用读写模式),第三个参数是权限设置(这里设置为0666,表示所有用户都可以读写这个对象)。如果对象不存在,shm_open
会创建一个新对象。如果创建成功,shm_open
会返回一个文件描述符。如果失败,返回-1。ftruncate
函数用于调整共享内存对象的大小。第一个参数是文件描述符,第二个参数是新的文件大小。这里将文件大小设置为1024字节。如果成功,ftruncate
返回0;否则返回-1。mmap
函数用于将共享内存对象的描述符映射到当前进程的地址空间。第一个参数是映射区域的起始地址(通常为NULL),第二个参数是映射区域的长度,第三个参数是保护标志(这里设置为读、写和共享),第四个参数是映射对象的类型(这里设置为共享内存),第五个参数是文件描述符,第六个参数是文件映射的偏移量。如果映射成功,mmap
返回映射区域的指针;否则返回MAP_FAILED。
3.总结
在使用共享内存时,需要注意同步问题。因为多个进程可以同时操作共享内存,可能导致数据不一致。互斥锁和信号量等同步机制可以解决这个问题。
共享内存是一种非常有效的进程间通信方式,尤其适用于大数据量的传输和频繁的通信需求。但是,使用共享内存时需要注意同步和数据一致性问题。