学习系列:《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 <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
执行结果:
[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参数说明
// 定义:
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)
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方式实例)
#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
执行结果
[mingjie@centos ~/proj/mmap]$ ./main3
Child started, value = 1, ++value
In parent, value = 2