• Linux内存映射函数mmap与匿名内存块


    学习系列:《APUE14.8》《CSAPP9.8.4》

    1 总结

    • memory-mapped io可以将文件映射到内存中的buffer,当我们从buffer读写数据时,其实操作的是对应文件中的数据。这样可以达到不使用READ/WRITE的IO操作。
    • mmap也可以直接映射匿名内存块,无需提供文件fd,直接申请一块内存给当前进程使用,也可以选择继承给子进程。注意匿名映射不会真的创建文件,只是拿到了一块填充0的内存。
    • 与共享内存这种传统IPC相比,mmap匿名内存更为灵活,Postgresql使用的共享内存全部是用mmap申请的,只用共享内存申请一个PGShmemHeader结构的大小。

    2 文件映射实例

    gcc -o main1 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main1.c

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    
    #define COPYINCR (1024 * 1024 * 1024) /* 1 GB */
    
    int main(int argc, char *argv[])
    {
    	int fdin, fdout;
    	void *src;
    	void *dst;
    	size_t copysz;
    	struct stat sbuf;
    	off_t fsz = 0;
    
    	if (argc != 3)
    	{
    		printf("usage: %s  \n", argv[0]);
    		exit(1);
    	}
    
    	if ((fdin = open(argv[1], O_RDONLY)) < 0)
    	{
    		printf("can't open %s for reading\n", argv[1]);
    	}
    
    	if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0)
    	{
    		printf("can't creat %s for writing\n", argv[2]);
    	}
    
    	if (fstat(fdin, &sbuf) < 0) /* need size of input file */
    	{
    		printf("fstat error\n");
    	}
    
    	if (ftruncate(fdout, sbuf.st_size) < 0) /* set output file size */
    	{
    		printf("ftruncate error\n");
    	}
    
    	while (fsz < sbuf.st_size)
    	{
    		if ((sbuf.st_size - fsz) > COPYINCR)
    		{
    			copysz = COPYINCR;
    		}
    		else
    		{
    			copysz = sbuf.st_size - fsz;
    		}
    
    		if ((src = mmap(0, copysz, PROT_READ, MAP_SHARED, fdin, fsz)) == MAP_FAILED)
    		{
    			printf("mmap error for input\n");
    		}
    		printf("src: %p\n", (char *)src);
    		if ((dst = mmap(0, copysz, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, fsz)) == MAP_FAILED)
    		{
    			printf("mmap error for output\n");
    		}
    		printf("dst: %p\n", (char *)src);
    		memcpy(dst, src, copysz); /* does the file copy */
    		munmap(src, copysz);
    		munmap(dst, copysz);
    		fsz += copysz;
    	}
    	exit(0);
    }
    
    // gcc -o main1 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main1.c
    
    • 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
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    执行结果:

    [mingjie@centos ~/proj/mmap]$ gcc -o main1 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main1.c
    [mingjie@centos ~/proj/mmap]$ ./main1 a.data b.data
    src: 0x7fde70798000
    dst: 0x7fde70798000
    [mingjie@centos ~/proj/mmap]$ cat a.data 
    aaaaaaaa
    bbb
    [mingjie@centos ~/proj/mmap]$ cat b.data 
    aaaaaaaa
    bbb
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3 mmap参数说明

    // 定义:
     void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    
    
    // 实例1中:
    mmap(0, copysz, PROT_READ, MAP_SHARED, fdin, fsz)
    mmap(0, copysz, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, fsz)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • addr:返回映射的起始地址。
      • 这个一般传0进去,让系统返回一个地址。
      • 注意映射出来的空间地址也是类似堆,是从低向高生长的。
    • length:表示需要映射多大的空间。
    • prot:读写标志位
    • flags:
      • MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
      • MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
      • MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
      • MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
      • MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
      • MAP_HUGETLB 使用内存大页。

    申请在堆和栈中间的位置:
    在这里插入图片描述

    4 匿名内存块映射(Postgresql中的mmap)

    CreateAnonymousSegment
      ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE,  PG_MMAP_FLAGS | mmap_flags, -1, 0);
    
    • 1
    • 2
    • PG_MMAP_FLAGS
      • MAP_SHARED
      • MAP_ANONYMOUS
    • mmap_flags
      • MAP_HUGETLB

    效果:

    • 每次调用都会创建一个新的映射。
    • 子进程继承父进程的映射。
    • 当共享映射的其他人在共享映射上写入时,没有fork的copy-on-write机制:写的就是一份数据。

    匿名映射的优点:

    • 没有虚拟地址空间碎片,取消映射后,内存立即归还给系统。
    • 与全局堆分开。
    • 可以给子进程继承使用。

    匿名映射的缺点:

    • 不能调整大小!
    • 每个映射的大小都是系统页面大小的整数倍,因此会导致地址空间的浪费。
    • 创建和返回映射比预分配的堆产生更多的开销。

    5 匿名内存块使用实例(Postgresql中的mmap方式实例)

    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
    	/*Pointer to shared memory region*/
    	int *addr;
    
    	addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    	if (addr == MAP_FAILED)
    	{
    		fprintf(stderr, "mmap() failed\n");
    		exit(EXIT_FAILURE);
    	}
    
    	*addr = 1;
    
    	/*Parent and child share mapping*/
    	switch (fork())
    	{
    	case -1:
    		fprintf(stderr, "fork() failed\n");
    		exit(EXIT_FAILURE);
    
    	case 0:
    		/*Child: increment shared integer and exit*/
    		printf("Child started, value = %d, ++value\n", *addr);
    		(*addr)++;
    
    		if (munmap(addr, sizeof(int)) == -1)
    		{
    			fprintf(stderr, "munmap()() failed\n");
    			exit(EXIT_FAILURE);
    		}
    		exit(EXIT_SUCCESS);
    
    	default:
    		/*Parent: wait for child to terminate*/
    		if (wait(NULL) == -1)
    		{
    			fprintf(stderr, "wait() failed\n");
    			exit(EXIT_FAILURE);
    		}
    
    		printf("In parent, value = %d\n", *addr);
    		if (munmap(addr, sizeof(int)) == -1)
    		{
    			fprintf(stderr, "munmap()() failed\n");
    			exit(EXIT_FAILURE);
    		}
    		exit(EXIT_SUCCESS);
    	}
    }
    
    // gcc -o main3 -Wall -g -ggdb -O0 -g3 -gdwarf-2 main3.c
    
    • 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
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    执行结果

    [mingjie@centos ~/proj/mmap]$ ./main3
    Child started, value = 1, ++value
    In parent, value = 2
    
    • 1
    • 2
    • 3
  • 相关阅读:
    VLAN笔记
    【手写数据库toadb】语言解析器,编程语言是这样被解析理解,解析器利器flex和bison,解析树与逆波兰式
    一窥未来:PyQt5引领下一代Python GUI开发
    从零开始学习 Java:简单易懂的入门指南之反射(三十八)
    【学习笔记17】JavaScript作用域
    你有了解过这些架构设计,架构知识体系吗?(架构书籍推荐)
    加权平均、EMD、小波等方法去噪效果对比
    解决 Github port 443 : Timed out
    基于R语言的贝叶斯网络模型、现代贝叶斯统计学方法
    怎样查看自己的chatgpt apikey可以调用哪些gpt模型?Python代码示例
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/127995492