共享内存 概念 共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数 malloc 分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。可以用来做进程间通信。
System V 接口共享内存 参考文档: https://man7.org/linux/man-pages/man7/sysvipc.7.html
相关函数 1 2 3 4 5 6 7 8 9 10 11 #include <sys/shm.h> int shmget (key_t key, size_t size, int shmflg) ;void *shmat (int shmid, const void *_Nullable shmaddr, int shmflg) ; int shmdt (const void *shmaddr) ; void *shmat (int shmid, const void *_Nullable shmaddr, int shmflg) ; int shmdt (const void *shmaddr) ; int shmctl (int shmid, int cmd, struct shmid_ds *buf) ;
示例代码 写入 system v 共享内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> int main (int argc, char * argv[]) { if (argc < 2 ) { printf ("Usage: ./sv_shm_write <msg>\n" ); exit (EXIT_FAILURE); } int shm_size = 100 ; int shmid = shmget(IPC_PRIVATE, shm_size, IPC_CREAT | 0600 ); if (shmid < 0 ){ perror("shmget" ); return 1 ; } char * shm_addr = (char *)shmat(shmid, NULL , 0 ); printf ("shm addr %p\n" , shm_addr); if (atoi(shm_addr) == -1 ){ perror("shmat" ); return -1 ; } printf ("wirte data to shmid %d: %s\n" , shmid, argv[1 ]); strcpy (shm_addr, argv[1 ]); shmdt(shm_addr); return 0 ; }
写入后,通过 ipcs -m
命令可以看到共享内存的状态
读取、删除 system v 共享内存内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <stdio.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> int main (int argc, char * argv[]) { if (argc < 2 ) { printf ("Usage: ./sv_shm_read <shmid>\n" ); exit (EXIT_FAILURE); } int shmid = atoi(argv[1 ]); char * shm_addr = (char *)shmat(shmid, NULL , 0 ); if (atoi(shm_addr) == -1 ) { perror("shmat" ); return 1 ; } printf ("string from shmid %d: %s\n" , shmid, shm_addr); shmdt(shm_addr); shmid_ds sds; if (shmctl(shmid, IPC_RMID, &sds) != 0 ){ perror("shmctl" ); return 1 ; } return 0 ; }
POSIX 接口共享内存 相关函数 POSIX 接口提供了一组函数来操作共享内存,并使用 fd 的方式来进行管理。从 Linux 2.4 和 glibc 2.2 版本开始支持。
POSIX 共享内存有更好的兼容性,但是早期程序中 system v 共享内存使用广泛。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <sys/mman.h> #include <sys/stat.h> /* For mode constants */ #include <fcntl.h> /* For O_* constants */ int shm_open (const char *name, int oflag, mode_t mode) ;int shm_unlink (const char *name) ;#include <unistd.h> int truncate (const char *path, off_t length) ;int ftruncate (int fd, off_t length) ;#include <sys/mman.h> void *mmap (void addr[.length], size_t length, int prot, int flags, int fd, off_t offset) ;int munmap (void addr[.length], size_t length) ;
示例代码 写入 posix 共享内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> int main (int argc, char * argv[]) { if (argc < 2 ) { printf ("Usage: ./posix_shm_write <msg>\n" ); exit (EXIT_FAILURE); } const char * name = "/posix_shm" ; int shm_size = 1024 ; int fd = shm_open(name, O_CREAT | O_RDWR, 0666 ); if (fd == -1 ){ perror("shm_open" ); return 1 ; } if (ftruncate(fd, shm_size) == -1 ){ perror("ftruncate" ); return 1 ; } char * ptr = (char *)mmap(0 , shm_size, PROT_WRITE, MAP_SHARED, fd, 0 ); if (ptr == MAP_FAILED){ perror("mmap" ); return -1 ; } printf ("write data to posix_shm: %s\n" , argv[1 ]); sprintf (ptr, "%s" , argv[1 ]); munmap(ptr, shm_size); close(fd); return 0 ; }
执行写入程序后,可以在共享内存文件中看到写入的内容,需要注意的是
只能按 ‘/name’ 路径指定共享内存名,不能指定其它子路径
此文件的内容并不是存储在磁盘上,而是在内存中,除非做了额外的持久化逻辑,系统重启后会被清空。
读取、删除 posix 共享内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> int main () { const char * name = "/posix_shm" ; int shm_size = 1024 ; int fd = shm_open(name, O_RDONLY, 0666 ); if (fd == -1 ){ perror("shm_open" ); return 1 ; } char * ptr = (char *)mmap(0 , shm_size, PROT_READ, MAP_SHARED, fd, 0 ); if (ptr == MAP_FAILED){ perror("mmap" ); return -1 ; } printf ("string from posix_shm: %s\n" , ptr); munmap(ptr, shm_size); close(fd); if (shm_unlink(name) == -1 ){ perror("shm_unlink" ); return 1 ; } return 0 ; }
文件内存映射 mmap 在 posix 共享内存的部分,其实已经有使用过 mmap 函数来将共享内存文件附加到进程的虚拟内存空间了,网上很多文章也是将 mmap 作为一种共享内存的手段来描述。这里为什么还要单独把它拿出来说呢?
其实 mmap 并不一定只能映射共享内存和匿名的内存片段,它提供将文件映射到进程的虚拟内存空间,并自动完成内存页和磁盘数据的同步操作。posix 共享内存也是提供一个 fd 并支持的文件相关操作,所以可以使用 mmap 将共享内存映射到进程虚拟内存空间,但不代表 mmap 仅能用于映射虚拟内存,它也可以基于一个普通磁盘文件实现 IPC。
概念 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
相关函数 1 2 3 4 5 6 #include <sys/mman.h> void *mmap (void addr[.length], size_t length, int prot, int flags, int fd, off_t offset) ;int munmap (void addr[.length], size_t length) ;
示例代码 下面是基于 mmap 使用普通文件内存映射实现 ipc 的示例代码。
使用 mmap 映射普通文件到内存后写入文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> int main (int argc, char * argv[]) { if (argc < 2 ) { printf ("Usage: ./mmap_write <msg>\n" ); exit (EXIT_FAILURE); } const char * filepath = "./mmapfile" ; int shm_size = 1024 ; int fd = open(filepath, O_CREAT | O_RDWR, 0666 ); if (fd == -1 ){ perror("open" ); return 1 ; } if (ftruncate(fd, shm_size) == -1 ){ perror("ftruncate" ); return 1 ; } char * ptr = (char *)mmap(0 , shm_size, PROT_WRITE, MAP_SHARED, fd, 0 ); if (ptr == MAP_FAILED){ perror("mmap" ); return -1 ; } int i = 0 ; while (i < 100 ) { printf ("write data to mmapfile: %s-%d\n" , argv[1 ], i); sprintf (ptr, "%s-%d" , argv[1 ], i); i++; sleep(1 ); } munmap(ptr, shm_size); close(fd); return 0 ; }
另一个进程将文件映射到内存,并不断读取其内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> int main () { const char * filepath = "./mmapfile" ; int shm_size = 1024 ; int fd = open(filepath, O_RDONLY, 0666 ); if (fd == -1 ){ perror("open" ); return 1 ; } char * ptr = (char *)mmap(0 , shm_size, PROT_READ, MAP_SHARED, fd, 0 ); if (ptr == MAP_FAILED){ perror("mmap" ); return -1 ; } int i = 0 ; while (i < 100 ) { printf ("string from mmapfile: %s\n" , ptr); i++; sleep(1 ); } munmap(ptr, shm_size); close(fd); return 0 ; }
常规文件操作需要从磁盘到页缓存再到用户内存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户内存的一次数据拷贝过程。mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。
参考文档:
本文链接:https://www.zoucz.com/blog/2022/07/14/cadbe410-a2db-11ee-bb9f-1566042b6386/