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

2022-11-28 15:57:06 浏览数 (1)

学习系列:《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 readingn", 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 writingn", argv[2]);
	}

	if (fstat(fdin, &sbuf) < 0) /* need size of input file */
	{
		printf("fstat errorn");
	}

	if (ftruncate(fdout, sbuf.st_size) < 0) /* set output file size */
	{
		printf("ftruncate errorn");
	}

	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 inputn");
		}
		printf("src: %pn", (char *)src);
		if ((dst = mmap(0, copysz, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, fsz)) == MAP_FAILED)
		{
			printf("mmap error for outputn");
		}
		printf("dst: %pn", (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() failedn");
		exit(EXIT_FAILURE);
	}

	*addr = 1;

	/*Parent and child share mapping*/
	switch (fork())
	{
	case -1:
		fprintf(stderr, "fork() failedn");
		exit(EXIT_FAILURE);

	case 0:
		/*Child: increment shared integer and exit*/
		printf("Child started, value = %d,   valuen", *addr);
		(*addr)  ;

		if (munmap(addr, sizeof(int)) == -1)
		{
			fprintf(stderr, "munmap()() failedn");
			exit(EXIT_FAILURE);
		}
		exit(EXIT_SUCCESS);

	default:
		/*Parent: wait for child to terminate*/
		if (wait(NULL) == -1)
		{
			fprintf(stderr, "wait() failedn");
			exit(EXIT_FAILURE);
		}

		printf("In parent, value = %dn", *addr);
		if (munmap(addr, sizeof(int)) == -1)
		{
			fprintf(stderr, "munmap()() failedn");
			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

0 人点赞