POSIX 共享内存
POSIX 共享内存是一种在 Linux 系统上使用的共享内存机制,它允许多个进程可以访问同一个内存区域,从而实现进程间的数据共享。共享内存是可用IPC机制中最快的,使用共享内存不必频繁拷贝数据。但也需要注意,由于共享内存段中的数据可以被多个进程同时访问,因此需要在程序设计中考虑好数据同步和互斥机制,以避免出现数据竞争和不一致的情况。
共享内存使用的基本步骤:
- 通过 shm_open() 函数创建了共享内存区域,此时会在 /dev/shm/ 创建共享内存文件。
- 通过 ftruncate() 函数改变共享内存的大小,一般设置为页大小 sysconf(_SC_PAGE_SIZE) 的整数倍。
- 通过 mmap() 函数将创建的共享内存文件映射到内存。
- 通过 munmap() 卸载共享内存。
- 通过 shm_unlink() 删除内存共享文件。
下面分别介绍这些函数。
创建共享内存-shm_open() 函数
shm_open() 函数是用于创建或打开一个共享内存对象,该函数定义如下:
#include <sys/mman.h> int shm_open(const char *name, int oflag, mode_t mode);
参数说明
- name:指定共享内存对象的名称,其命名规则类似于文件系统中的文件名,不同进程可以通过相同的名字来访问同一个共享内存对象。
- oflag:指定打开方式。
- mode:创建文件时的权限。
返回值
- 如果函数执行成功,返回一个文件描述符,可以用于后续操作共享内存对象。
- 如果发生错误,返回 -1。可以通过 errno 变量来获取具体的错误信息。
共享内存对象通过IPC名字描述,当成功创建共享内存对象,系统会以文件形式将其保存在 /dev/shm 目录下。
更改文件大小-ftruncate() 函数
ftruncate() 函数用于更改文件大小,该函数定义如下:
#include <unistd.h> int ftruncate(int fd, off_t length);
参数说明
- fd:一个已经打开的文件描述符,用于指定需要更改大小的文件。
- length:指定文件应当调整到的新大小,单位是字节。
返回值
- 如果函数执行成功,返回值为0。
- 如果发生错误,返回 -1。可以通过 errno 变量来获取具体的错误信息。
如果文件在调整大小后比原来更大,文件的数据将会被扩展,多出的部分以0填充。如果文件在调整大小后比原来更小,多出的部分将会被删除。
在调整文件大小时,尽量选择当前系统页大小的整数倍,可以通过 sysconf(_SC_PAGE_SIZE) 获取当前系统的页大小。
内存映射-mmap()函数
mmap() 函数用于创建内存映射区域,该函数定义如下:
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数说明
- addr:指定希望内存映射区域的起始地址,通常设为 NULL,由系统自动选择合适的地址。
- length:指定需要映射的内存区域的长度,单位是字节。
- prot:用于设置内存区域的保护权限,不能与文件的打开模式冲突。可以是如下值的组合:
- PROT_NONE:内存区域不可访问。
- PROT_WRITE:内存区域可以被写入。
- PROT_READ:内存区域可以被读取。
- PROT_EXEC:内存区域可以被执行。
- flags:指定映射对象的类型。下面列出一些常用项:
- MAP_SHARED:与文件进行共享映射,可以实现多个进程之间共享数据的操作。对映射区域的修改会反映到文件中,同样文件的修改也会反映到映射区域中。
- MAP_PRIVATE:创建一个私有的映射副本,进程之间不共享数据。对映射区域的修改不会反映到文件中,也不会影响其他映射该文件的进程。
- MAP_LOCKED:映射区域会被锁定在物理内存中,防止页面被交换出去,保证内存访问速度,但可能会导致内存资源消耗较大。
- fd:已打开文件的文件描述符,用于与内存映射区域关联。
- offset:文件中的偏移量,指定文件的起始映射位置。
返回值
- 如果函数执行成功,返回一个指向映射区域的指针。
- 如果发生错误,返回 MAP_FAILED(-1) 。可以通过 errno 变量来获取具体的错误信息。
映射方式如下:
数据同步-msync()函数
如果指定了 MAP_SHARED 标志,Linux内核会保持内存映射文件与内存映射区的同步,但这种同步可能不会立即生效,此时,可以使用 msync() 函数来确保数据已经同步完成。该函数定义如下:
#include <sys/mman.h> int msync(void *addr, size_t length, int flags);
参数说明
- addr:指向共享内存区域的指针。
- length:需要同步的共享内存区域的长度。
- flags:用来指定额外的行为。有如下取值:
- MS_SYNC:强制将修改同步到存储设备,数据量大时,可能会导致阻塞。
- MS_ASYNC:将修改排入写队列,不会等待写操作完成。
- MS_INVALIDATE:标记共享内存区域已经无效,使下次访问该区域时重新从底层存储器加载数据。
返回值
- 如果函数执行成功,函数返回 0。
- 如果函数执行失败,函数返回 -1,可以通过 errno 变量来获取具体的错误信息。
注意事项
- msync() 函数的调用可能会比较耗时,因此需要考虑性能问题。
- msync() 函数只适用于共享内存,对于普通的内存操作并不适用。
卸载共享内存-munmap()函数
munmap() 函数用于取消指定的地址空间内存映射。该函数定义如下:
#include <sys/mman.h> int munmap(void *addr, size_t length);
参数说明
- addr:要取消映射的内存区域的起始地址。是由 mmap() 函数返回的地址。
- length:要取消映射的内存区域的长度。
返回值
- 如果函数执行成功,函数返回 0。
- 如果函数执行失败,函数返回 -1,可以通过 errno 变量来获取具体的错误信息。
注意事项
- 只能取消由 mmap() 函数创建的内存映射区域,否则会导致未定义行为。
- 要确保取消映射的地址和长度是有效的,否则会导致程序崩溃或内存泄漏。
- 取消映射后,原先映射的内存区域就会被释放,程序不应再访问这部分内存。
删除共享内存文件-shm_unlink()函数
shm_unlink()函数用于删除共享内存文件,后续其他进程将无法通过这个名称打开该共享内存对象。该函数定义如下:
#include <sys/mman.h> int shm_unlink(const char *name);
参数说明
- name:要删除的 POSIX 共享内存对象的名称。
返回值
- 如果函数执行成功,函数返回 0。
- 如果函数执行失败,函数返回 -1,可以通过 errno 变量来获取具体的错误信息。
用例
数据同步
1 #include<stdio.h> 2 #include<fcntl.h> 3 #include<sys/mman.h> 4 #include<unistd.h> 5 #include<sys/stat.h> 6 7 int main(int argc, char** argv) 8 { 9 size_t mem_size = sysconf(_SC_PAGE_SIZE) * 2; 10 int fd; 11 12 AGAIN: 13 fd = shm_open("/my_shm", O_CREAT | O_RDWR | O_EXCL, 0666); 14 if(fd == -1) 15 { 16 if(errno == EEXIST) 17 { 18 shm_unlink("/my_shm"); 19 goto AGAIN; 20 } 21 perror("shm_open"); 22 return -1; 23 } 24 25 int ret = ftruncate(fd, mem_size); 26 if(ret == -1) 27 { 28 perror("ftruncate"); 29 return -1; 30 } 31 32 void *ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 33 if(ptr == MAP_FAILED) 34 { 35 perror("mmap"); 36 return -1; 37 } 38 39 int i = 0; 40 sprintf(ptr, "Data%d", i); 41 while(1); 42 43 close(fd); 44 munmap(ptr, mem_size); 45 return 0; 46 }
输出:
$ cat /dev/shm/my_shm Data1
当指定 MAP_SHARED 后,对指针 ptr 的操作,都会同步至 my_shm 文件中,数据是以明文形式存储,可以被其他进程读取。
如果将 MAP_SHARED 改为 MAP_PRIVATE,执行结果如下:
$ cat /dev/shm/my_shm
//无数据
简单用例
读端代码如下:
1 #include<stdio.h> 2 #include<fcntl.h> 3 #include<sys/mman.h> 4 #include<unistd.h> 5 #include<sys/stat.h> 6 #include<errno.h> 7 8 int main(int argc, char** argv) 9 { 10 size_t mem_size = sysconf(_SC_PAGE_SIZE) * 2; 11 int fd = shm_open("/my_shm", O_CREAT | O_TRUNC | O_RDWR, 0666); 12 if (fd == -1 && errno != EEXIST) 13 { 14 perror("shm_open"); 15 return -1; 16 } 17 18 int ret = ftruncate(fd, mem_size); 19 if(ret == -1) 20 { 21 perror("ftruncate"); 22 return -1; 23 } 24 25 void *ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 26 if(ptr == MAP_FAILED) 27 { 28 perror("mmap"); 29 return -1; 30 } 31 32 while (1) 33 { 34 printf("read data : %s\n", (char *)ptr); 35 sleep(1); 36 } 37 38 close(fd); 39 40 return 0; 41 }
写端代码如下:
1 #include<stdio.h> 2 #include<fcntl.h> 3 #include<sys/mman.h> 4 #include<unistd.h> 5 #include<errno.h> 6 #include<sys/stat.h> 7 8 int main(int argc, char** argv) 9 { 10 size_t mem_size = sysconf(_SC_PAGE_SIZE) * 2; 11 int fd; 12 AGAIN: 13 fd = shm_open("/my_shm", O_CREAT | O_TRUNC | O_RDWR, 0666); 14 if (fd == -1) 15 { 16 if (errno == EEXIST) 17 { 18 goto AGAIN; 19 } 20 perror("shm_open"); 21 return -1; 22 } 23 24 int ret = ftruncate(fd, mem_size); 25 if(ret == -1) 26 { 27 perror("ftruncate"); 28 return -1; 29 } 30 31 void *ptr = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 32 if(ptr == MAP_FAILED) 33 { 34 perror("mmap"); 35 return -1; 36 } 37 38 int i = 0; 39 while(1) 40 { 41 sprintf(ptr, "Data%d", i++); 42 printf("write data : %s\n", (char*)ptr); 43 sleep(1); 44 } 45 46 close(fd); 47 munmap(ptr, mem_size); 48 49 return 0; 50 }
标签:include,函数,int,间通信,POSIX,内存,共享内存,shm From: https://www.cnblogs.com/BroccoliFighter/p/18059660