目录
- 1、什么是共享内存
- 2、mmap函数
- 3、mmap对比read、write
- 4、munmap 函数
- 5、msync函数
- 5.1、通过页表机制,将修改的数据同步到磁盘,那为什么还需要msync函数
- 6、示例
- 6.1、使用mmap让进程之间通信,文件作为媒介
- 6.3、修改文件,使用msync显示同步到磁盘
- 7、shmget shmat shmdt shmctl实现系统V共享内存
- 7.1、`shmget()` 函数
- 7.2 、shmat() 函数
- 7.3、shmdt() 函数
- 7.4、shmctl() 函数
- 7.5、示例
1、什么是共享内存
共享内存(Shared Memory)是一种高效的 进程间通信(IPC, Inter-Process Communication) 机制,它允许多个进程共享同一块内存区域。通过共享内存,进程可以直接读写同一块内存,避免了数据复制的开销,因此速度非常快。
2、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_READ:可读。
- PROT_WRITE:可写。
- PROT_EXEC:可执行。
- PROT_NONE:不可访问。
flags
- 控制映射的行为。可以是以下值的组合:
- MAP_SHARED:共享映射,对映射区域的修改会反映到文件中。
- MAP_PRIVATE:私有映射,对映射区域的修改不会反映到文件中。
- MAP_ANONYMOUS:匿名映射,不关联任何文件。
- MAP_FIXED:强制使用指定的起始地址。
fd
- 文件描述符,指定要映射的文件。如果使用 MAP_ANONYMOUS,则设置为 -1。
offset
- 文件中的偏移量,表示从文件的哪个位置开始映射。通常设置为 0。
返回值
- 成功时,返回映射区域的起始地址。
- 失败时,返回 MAP_FAILED(即 (void *)-1),并设置 errno。
mmap 的常见用途
1、文件映射
将文件映射到内存中,直接通过内存访问文件内容,适合处理大文件或需要频繁访问的文件。
2、共享内存
使用 MAP_SHARED 标志创建共享内存,多个进程可以共享同一块内存区域。
3、匿名映射
使用 MAP_ANONYMOUS 创建匿名映射,用于动态分配内存或实现进程间的共享内存。
3、mmap对比read、write
通过mmap()操作文件比read()、write()要快,因为read()、write()会拷贝到缓冲区中,而mmap()不用,直接写到文件中
read()
和write()
的工作流程
read()
:
- 内核从磁盘读取数据到内核缓冲区。
- 数据从内核缓冲区拷贝到用户空间缓冲区。
- 应用程序访问用户空间缓冲区中的数据。
write()
:
- 应用程序将数据写入用户空间缓冲区。
- 数据从用户空间缓冲区拷贝到内核缓冲区。
- 内核将数据从内核缓冲区写入磁盘。
在这个过程中,数据需要在内核缓冲区和用户空间缓冲区之间进行两次拷贝,这会增加额外的开销。
mmap()
的工作流程
映射文件:
- 使用 mmap() 将文件直接映射到进程的地址空间。 文件的内容被映射到虚拟内存中,进程可以通过指针直接访问。
读写操作:
- 进程直接通过指针读写映射的内存区域。 操作系统负责将修改的数据同步到磁盘(通过页表机制)。
在这个过程中,数据不需要在用户空间和内核空间之间拷贝,因此减少了额外的开销。
mmap
优势
1、减少数据拷贝:
mmap() 避免了 read() 和 write() 中的两次数据拷贝,直接通过内存映射访问文件数据。
2、高效访问大文件:
对于大文件,mmap() 可以按需加载文件内容(通过页表机制),而不需要一次性将整个文件加载到内存中。
3、简化编程:
使用 mmap() 后,文件数据可以直接通过指针访问,编程更加直观。
4、共享内存:
多个进程可以映射同一个文件,实现高效的进程间通信。
mmap
的适用场景
1、大文件处理:
当需要处理大文件时,mmap() 可以按需加载数据,减少内存占用。
2、频繁随机访问:
如果需要对文件进行频繁的随机访问,mmap() 比 read() 和 write() 更高效。
3、进程间通信:
多个进程可以通过映射同一个文件实现共享内存。
mmap
的局限性
内存映射大小:
- mmap() 映射的文件大小受限于虚拟地址空间的大小。
同步开销:
- 频繁修改映射区域时,操作系统需要将修改的数据同步到磁盘,可能会带来额外的开销。
复杂性:
- 使用 mmap() 需要处理页表、内存对齐等问题,编程复杂度较高。
4、munmap 函数
munmap
用于解除内存映射,释放映射区域。
头文件
#include <sys/mman.h>
函数定义
int munmap(void *addr, size_t length);
参数说明
- addr 映射区域的起始地址。
- length 映射区域的长度。
返回值
- 成功时,返回 0。
- 失败时,返回 -1,并设置 errno。
5、msync函数
功能:
msync
是一个在 Unix/Linux 系统中用于同步内存映射文件(memory-mapped file)与磁盘文件的系统调用函数。它的主要作用是将内存中对文件的修改写回到磁盘,或者从磁盘重新加载文件内容到内存,以确保内存和磁盘之间的数据一致性。
头文件
#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 来指示错误类型。
5.1、通过页表机制,将修改的数据同步到磁盘,那为什么还需要msync函数
一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。
msync() 函数的作用是显式地将内存映射区域(mmap()
映射的文件)的修改同步到磁盘。虽然操作系统确实会通过页表机制将修改的数据同步到磁盘,但这种同步通常是延迟的,而不是立即执行的
为什么需要 msync()?
1、延迟写入(Lazy Write):
操作系统为了提高性能,通常会延迟将修改的数据写入磁盘。修改的数据可能暂时保留在内存的页缓存中,直到满足以下条件之一:
- 内存不足,需要回收页缓存。
- 系统调用 sync() 或 fsync() 被触发。
- 文件被关闭或取消映射(munmap())。
这种延迟写入机制可能导致数据在系统崩溃或断电时丢失。
2、显式控制同步时机:
- 使用 msync() 可以让应用程序显式控制何时将修改的数据写入磁盘,确保数据的持久性。
- 例如,在关键数据修改完成后立即调用 msync(),可以避免数据丢失的风险。
3、部分同步:
msync() 允许只同步内存映射区域的某一部分(通过指定地址和长度),而不是整个映射区域。这在处理大文件时可以提高效率。
4、跨进程共享映射:
如果多个进程共享同一个文件映射,一个进程修改数据后,其他进程可能无法立即看到修改结果。调用 msync() 可以确保修改的数据对其他进程可见。
6、示例
6.1、使用mmap让进程之间通信,文件作为媒介
写入文件
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main()
{//打开/建立文件char *filepath = "hello.txt";int fd = open(filepath,O_RDWR | O_CREAT,0666);if(fd < 0){perror("open file error");return -1;}//创建空洞文件if(ftruncate(fd,1024) < 0){perror("ftruncate error");close(fd);return -1;}//创建共享内存,并关联fd文件(建立好映射关系)void *addr = mmap(NULL,1024,PROT_WRITE,MAP_SHARED,fd,0);if(addr == (void*)-1){perror("mmap error");close(fd);return -1;}char *str = "abc";strcpy(addr,str); //实现地址到地址的拷贝,比write效率更高//解除映射munmap(addr,1024);close(fd);}
从文件中读出
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>int main()
{//打开/建立文件char *filepath = "hello.txt";int fd = open(filepath,O_RDWR | O_CREAT,0666);if(fd < 0){perror("open file error");return -1;}//创建共享内存,并关联fd文件(建立好映射关系)void *addr = mmap(NULL,1024,PROT_READ,MAP_SHARED,fd,0);if(addr == (void*)-1){perror("mmap error");close(fd);return -1;}char buf[128] = "";strncpy(buf,addr,sizeof(buf));printf("buf = %s\n",buf);//解除映射munmap(addr,1024);close(fd);}
6.3、修改文件,使用msync显示同步到磁盘
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main() {int fd = open("test.txt", O_RDWR);struct stat sb;fstat(fd, &sb);// 映射文件到内存char *ptr = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);// 修改文件内容strcpy(ptr, "Hello, mmap!");// 显式同步到磁盘if (msync(ptr, sb.st_size, MS_SYNC) == -1) {perror("msync failed");}// 取消映射munmap(ptr, sb.st_size);close(fd);return 0;
}
7、shmget shmat shmdt shmctl实现系统V共享内存
7.1、shmget()
函数
用于创建或获取一个共享内存段。
#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);
参数:
- key:共享内存段的键值。通常使用 ftok() 生成,或使用 IPC_PRIVATE 创建一个新的共享内存段。
- size:共享内存段的大小(字节)。
- shmflg:标志位,指定共享内存段的创建方式和权限。常用标志:
- IPC_CREAT:如果共享内存段不存在,则创建它。
- IPC_EXCL:与 IPC_CREAT 一起使用,如果共享内存段已存在,则返回错误。
- 权限模式:如 0666,表示所有用户可读写。
返回值:
- 成功:返回共享内存段的标识符(shmid)。
- 失败:返回 -1,并设置 errno。
7.2 、shmat() 函数
将共享内存段映射到进程的地址空间。
#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
- shmid:共享内存段的标识符(由 shmget() 返回)。
- shmaddr:指定映射地址。通常为 NULL,由系统自动选择。
- shmflg:标志位。常用标志:
- SHM_RDONLY:以只读方式映射共享内存。
返回值:
- 成功:返回共享内存段的映射地址。
- 失败:返回 (void *)-1,并设置 errno。
7.3、shmdt() 函数
将共享内存段从进程地址空间中解除映射。
#include <sys/types.h>
#include <sys/shm.h>int shmdt(const void *shmaddr);
参数:
- shmaddr:共享内存段的映射地址(由 shmat() 返回)。
返回值:
- 成功:返回 0。
- 失败:返回 -1,并设置 errno。
7.4、shmctl() 函数
控制共享内存段(如删除或获取信息)。
#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
- shmid:共享内存段的标识符(由 shmget() 返回)。
- cmd:控制命令。常用命令:
- IPC_RMID:删除共享内存段。
- IPC_STAT:获取共享内存段的状态信息,并存储到 buf 中。
- IPC_SET:设置共享内存段的状态信息。
- buf:指向 shmid_ds 结构体的指针,用于存储或设置共享内存段的信息。
返回值:
- 成功:返回 0。
- 失败:返回 -1,并设置 errno。
注意:共享内存不会随着程序结束而自动消除,而且数据也在,要么调用shmctl删除,要么自己用手敲命令去删除,否则永远留在系统中。
7.5、示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <string.h>int main()
{//获取IPC标识符key_t key = ftok(".",'a');if(key < 0){perror("ftok error");return -1;}//创建共享内存int shmid = shmget(key,1024,0666 | IPC_CREAT);if(shmid < 0){perror("shmget error");return -1;}//建立共享内存的映射关系void *addr = shmat(shmid,NULL,0);if(addr == (void *)-1){perror("shmat error");return -1;}//写char *str = "hello";strcpy(addr,str);//读char buf[128] = "";strncpy(buf,addr,sizeof(buf));printf("buf = %s\n",buf);//解除映射关系if(shmdt(addr) < 0){perror("shmdt error");return -1;}//删除共享内存if(shmctl(shmid,IPC_RMID,NULL) < 0){perror("shmctl rm error");return -1;}
}