前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内存映射函数mmap与匿名内存块

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

作者头像
mingjie
发布2022-11-28 15:57:06
2.1K0
发布2022-11-28 15:57:06
举报
文章被收录于专栏:Postgresql源码分析

学习系列:《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

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#include <fcntl.h>
#include <sys/mman.h>

#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 <fromfile> <tofile>\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

执行结果:

代码语言:javascript
复制
[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

3 mmap参数说明

代码语言:javascript
复制
// 定义:
 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)
  • 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)

代码语言:javascript
复制
CreateAnonymousSegment
  ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE,  PG_MMAP_FLAGS | mmap_flags, -1, 0);
  • PG_MMAP_FLAGS
    • MAP_SHARED
    • MAP_ANONYMOUS
  • mmap_flags
    • MAP_HUGETLB

效果:

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

匿名映射的优点:

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

匿名映射的缺点:

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

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

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

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

执行结果

代码语言:javascript
复制
[mingjie@centos ~/proj/mmap]$ ./main3
Child started, value = 1, ++value
In parent, value = 2
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 总结
  • 2 文件映射实例
  • 3 mmap参数说明
  • 4 匿名内存块映射(Postgresql中的mmap)
  • 5 匿名内存块使用实例(Postgresql中的mmap方式实例)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档