mmap 的测试问题

Posted 安庆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mmap 的测试问题相关的知识,希望对你有一定的参考价值。

最近遇到一个mmap的问题,然后为了测试该问题,写了如下测试代码:

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)

int
main(int argc, char *argv[])
{
char *addr;
int fd;
struct stat sb;
off_t offset, pa_offset;
size_t length;
off_t file_len;
ssize_t s;
int iRet;

if (argc < 3 || argc > 4) {
fprintf(stderr, "%s file offset [length]\n", argv[0]);
exit(EXIT_FAILURE);
}

fd = open(argv[1], O_RDWR);
if (fd == -1)
handle_error("open");
#if 0
file_len = lseek(fd, 400*1024*1024, SEEK_CUR);------------lseek和ftruncate,truncate都可以达到修改文件可映射大小的结果,不过lseek可以在readonly的情况下修改,而truncate不行。
#endif
offset = 400*1024*1024;
iRet = ftruncate(fd,offset);
if (0 != iRet)
{
close(fd);
printf("ftruncate in OpenShem fail\n");
return 0;
}

if (fstat(fd, &sb) == -1) /* To obtain file size */
handle_error("fstat");

offset = atoi(argv[2]);
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
/* offset for mmap() must be page aligned */

if (offset >= sb.st_size) {
fprintf(stderr, "offset is past end of file\n");
exit(EXIT_FAILURE);
}

if (argc == 4) {
length = atoi(argv[3]);
if (offset + length > sb.st_size)
length = sb.st_size - offset;
/* Can‘t display bytes past end of file */

} else { /* No length arg ==> display to end of file */
length = sb.st_size - offset;
}

addr = mmap(NULL, length + offset - pa_offset, PROT_READ,MAP_PRIVATE, fd, pa_offset);---------map调用,进行映射,注意此处采用的是MAP_PRIVATE
if (addr == MAP_FAILED)
handle_error("mmap");

s = write(STDOUT_FILENO, addr + offset - pa_offset, length);-------------------第一次读取该map地址
if (s != length) {
if (s == -1)
handle_error("write");

fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}
/*second excute*/
s = write(STDOUT_FILENO, addr + offset - pa_offset, length);------------------第二次读取该map地址
if (s != length) {
if (s == -1)
handle_error("write");

fprintf(stderr, "partial write");
exit(EXIT_FAILURE);
}

exit(EXIT_SUCCESS);
}

测试发现,当mmap调用前,内存占用如下:

 PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND

24441 root      20   0    4156    356    264 t   0.0  0.0   0:00.00 map.o 

调用mmap之后,内存占用如下:


PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24441 root 20 0 394784 356 264 t 0.0 0.0 0:00.00 map.o

很明显,虚拟内存增长了,但res和shr并没有增长。

然后调用到s = write(STDOUT_FILENO, addr + offset - pa_offset, length),第一次的时候,内存占用如下:

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND

24441 root      20   0  394784 391072 390968 t   0.0  1.6   0:02.35 map.o

发现res和shr也增加了,但是res-shr的值,并没有显著增长。

第二次调用s = write(STDOUT_FILENO, addr + offset - pa_offset, length),内存占用不变,符合预期。

 

那么,从MAP_PRIVATE的理解来看,因为使用的是copy-on-write mapping,那么在write之后,rss才随着virt增长是正常的。

不管是使用MAP_PRIVATE还是使用MAP_SHARE,当关闭fd之后,都不受影响,这个一般一开始都认为需要保持fd打开,其实不需要,fd也就是只是用来临时用一下,MAP_ANONYMOUS的时候,fd甚至都会忽略。

关于MAP_PRIVATE的理解,测试结果表明,当使用MAP_PRIVATE的时候,map所写的内容不会影响到其他进程,也就是说你cat 对应的文件,也是看不到修改的内容的。

 

另外,lseek和ftruncate,truncate都可以达到修改文件可映射大小的结果,注意是修改可映射大小,不是文件实际大小。

事实上,lseek不改变文件实际大小,而truncate 以及 ftruncate是会改变文件实际大小的。所谓的实际大小,是指通过fstat等接口获取的文件size,不是占用的block的大小。

lseek可以在readonly的情况下修改,而truncate不行。

truncate 在缩小了文件之后,如果原来seek的位置小于缩小之后的文件大小,则保持不变,如果大于,则位于文件尾,如果truncate 放大了文件,则seek位置不变。

以上是关于mmap 的测试问题的主要内容,如果未能解决你的问题,请参考以下文章

MySQL测试环境遇到 mmap(xxx bytes) failed; errno 12解决方法

mmap /dev/fb0 因“无效参数”而失败

linux下,设备DMA数据至0xF0000000,使用mmap映射,然后memcpy,效率相当低,16M需要400ms,有办法提高?

Linux内存管理 mmap(补充)

linux中ioremap和mmap的区别

mmap 的用例