在写操作系统共享内存实验的时候,用到了Linux C中的mmap
函数,觉得比较有意思,在此记录一下。
mmap
函数的作用是将磁盘上文件的一块区域映射到内存中,使得进程可以直接访问内存来获取和修改文件内容,而无须通过read
和write
。其具体的加载过程使用了lazy loading策略,在建立的时候并没有直接将内容拷贝到内存,甚至都没有建立虚拟内存到物理内存的映射,而是在访问的时候使用缺页异常来载入,可以在下面链接的博文中看到详细内容。
而要使用mmap
实现共享内存,从我的理解看,其原理是对同一个文件,不同的进程建立映射后,映射区在物理内存中的地址是相同的,尽管在每个进程看到的虚拟内存中的地址是不一样的。因为虚拟内存是按页分配的,所以mmap
要求映射区的大小是页大小的整数倍(这些都是个人理解)。
Linux环境进程间通信(五)共享内存(上)
3、进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。
5、所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。
以下是使用mmap
实现文件复制的代码。
#include
#include
#include
#include
#include
#include
#include
#include
#include
void mmapcopy(int, int, size_t);
int main(int argc, char *argv[])
{
if (argc != 3) {
printf("Usage: %s SOURCE DEST\n", argv[0]);
return 0;
}
int src_fd, dst_fd;
struct stat stat;
if ((src_fd = open(argv[1], O_RDONLY)) == -1) {
perror("open src");
return -1;
}
fstat(src_fd, &stat);
if ((dst_fd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0664)) == -1) {
perror("open dst");
return -1;
}
ftruncate(dst_fd, stat.st_size); // 如果没有该语句会报Bus error
mmapcopy(src_fd, dst_fd, stat.st_size);
close(src_fd);
close(dst_fd);
return 0;
}
void mmapcopy(int src_fd, int dst_fd, size_t len)
{
void *src, *dst;
int rt;
if ((src = mmap(0, len, PROT_READ, MAP_PRIVATE, src_fd, 0)) == MAP_FAILED) {
printf("src mmap error: %s\n", strerror(errno));
exit(-1);
}
if ((dst = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, dst_fd, 0)) == MAP_FAILED) {
printf("src mmap error: %s\n", strerror(errno));
exit(-1);
}
memcpy(dst, src, len); // 如果dst和src相反会出现Segmentation fault(因为src是MAP_PRIVATE)
if (munmap(src, len) == -1) {
perror("munmap src");
exit(-1);
}
if (munmap(dst, len) == -1) {
perror("munmap dst");
exit(-1);
}
}
有趣的是malloc
函数在分配大于128KB的内存空间时使用的是mmap
。
0x951d008 // 1KB
0x951d818 // 128KB
0xb7530008 // 256KB
打印一下malloc
分配不同大小内存返回的地址,可以看到大于128KB的内存地址和前两个都很不一样,因为mmap
分配的内存位于堆和栈空间之间。
除此之外,动态链接库的共享也是用mmap
实现的。
linux下共享内存mmap()方法和shmget()方法的疑问?
当malloc()分配内存时,在其中会调用brk()或mmap()向系统申请1块内存,小于128KB的内存空间,实际通过brk()方法申请;大于128KB的内存空间,实际通过mmap()方法申请。
mmap()相当于在页表中注册这块内存的虚地址,使该虚地址空间有效。在首次访问该虚地址空间时会触发缺页异常。
在linux系统的缺页异常中,会测试该内存块的虚地址是否合法,若为合法的虚地址空间,则说明是新分配的内存块尚未被映射到物理地址空间,则在缺页异常中完成地址映射;若给出的内存块虚地址不存在,则说明本次访问非法,系统抛出Segmentation fault错误。