mmap函数详解_ydt_lwj的博客-程序员秘密

技术标签: Linux 内核  access  file  c  null  磁盘  thread  

mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来做而不需要read/write函数。

#include <sys/mman.h>

 void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off);
 int munmap(void *addr, size_t len);

该函数各参数的作用图示如下:

图 28.4. mmap函数

mmap函数

如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的映射首地址通过返回值可以得到。len参数是需要映射的那一部分文件的长度。off参数是从文件的什么位置开始映射,必须是页大小的整数倍(在32位体系统结构上通常是4K)。filedes是代表该文件的描述符。

prot参数有四种取值:

  • PROT_EXEC表示映射的这一段可执行,例如映射共享库

  • PROT_READ表示映射的这一段可读

  • PROT_WRITE表示映射的这一段可写

  • PROT_NONE表示映射的这一段不可访问

flag参数有很多种取值,这里只讲两种,其它取值可查看mmap(2)

  • MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。

  • MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。

如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。

下面做一个简单的实验。

$ vi hello
(编辑该文件的内容为“hello”)
$ od -tx1 -tc hello 
0000000 68 65 6c 6c 6f 0a
          h   e   l   l   o  \n
0000006

现在用如下程序操作这个文件(注意,把fd关掉并不影响该文件已建立的映射,仍然可以对文件进行读写)。

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

int main(void)
{
	int *p;
	int fd = open("hello", O_RDWR);
	if (fd < 0) {
		perror("open hello");
		exit(1);
	}
	p = mmap(NULL, 6, PROT_WRITE, MAP_SHARED, fd, 0);
	if (p == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	close(fd);
	p[0] = 0x30313233;
	munmap(p, 6);
	return 0;
}

然后再查看这个文件的内容:

$ od -tx1 -tc hello
 0000000 33 32 31 30 6f 0a
           3   2   1   0   o  \n
 0000006

请读者自己分析一下实验结果。

mmap函数的底层也是一个系统调用,在执行程序时经常要用到这个系统调用来映射共享库到该进程的地址空间。例如一个很简单的hello world程序:

#include <stdio.h>

int main(void)
{
	printf("hello world\n");
	return 0;
}

strace命令执行该程序,跟踪该程序执行过程中用到的所有系统调用的参数及返回值:

$ strace ./a.out 
execve("./a.out", ["./a.out"], [/* 38 vars */]) = 0
brk(0)                                  = 0x804a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fca000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=63628, ...}) = 0
mmap2(NULL, 63628, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fba000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\260a\1"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=1339816, ...}) = 0
mmap2(NULL, 1349136, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7e70000
mmap2(0xb7fb4000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x143) = 0xb7fb4000
mmap2(0xb7fb7000, 9744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7fb7000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7e6f000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7e6f6b0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7fb4000, 4096, PROT_READ)   = 0
munmap(0xb7fba000, 63628)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fc9000
write(1, "hello world\n", 12hello world
)           = 12
exit_group(0)                           = ?
Process 8572 detached

可以看到,执行这个程序要映射共享库/lib/tls/i686/cmov/libc.so.6到进程地址空间。也可以看到,printf函数的底层确实是调用write


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ydt_lwj/article/details/7732405

智能推荐

GO语言基础进阶教程:ioutil包_qfliweimin的博客-程序员秘密

除了io包可以读写数据,Go语言中还提供了一个辅助的工具包就是ioutil,里面的方法虽然不多,但是都还蛮好用的。import "io/ioutil"该包的介绍只有一句话:Package ioutil implements some I/O utility functions。一、ioutil包的方法下面我们来看一下里面的方法:// Discard 是一个 io.Writer 接口,调用它的 Write 方法将不做任何事情// 并且始终成功返回。var Discard io.Wr

Fatal error compiling: 无效的标记: -parameters -> [Help 1] [ERROR]_孟夏草木长的博客-程序员秘密

[INFO] ------------------------------------------------------------------------[INFO] BUILD FAILURE[INFO] ------------------------------------------------------------------------[INFO] Total time:...

app审核被拒:App Tracking Transparency permission request when reviewed on iOS 15.0_碧羽化屏的博客-程序员秘密

app审核被拒:App Tracking Transparency permission request when reviewed on iOS 15.0被拒理由:Guideline 2.1 - Information NeededWe’re still looking forward to completing our review, but we need moreinformation to continue. Your app uses the AppTrackingTransparen

深度学习笔记~感受野(receptive field)的计算_zlibo丶的博客-程序员秘密

以前对CNN中的感受野(receptive field)已经有了一些认识,基本上是从概念理解上得到的。本篇文章给出了receptive field的计算过程和相应的python代码,对receptive的理解和计算有较好的帮助作用。转载:https://medium.com/mlreview/a-guide-to-receptive-field-arithmetic-for-convolu...

KaliLinux2 网络渗透测试实践指南第二版(基础一)_gdhck的博客-程序员秘密_kalilinux2网络渗透测试实践指南pdf

一、Kali Linux2简介Kali Linux是基于Debian 的Linux发行版,设计用于数字取证操作系统。Back Track是他们之前写的用于取证的Linux发行版。Kali Linux预装了许多渗滲透测试软件,包括nmap 、 Wireshark 、 John the Ripper, Kali Linux。以及Aircrack-ng.用户可通过硬盘、Iive CD或Iive USB运行,Kali Linux是专业的渗透測试系统,预装了许多渗透测试软件。二、Kali Linux2的安装

随便推点

修改SqlServer默认的1433端口 _Jaron的博客-程序员秘密

打开Microsoft SQL Server--服务器网络实用工具--启用的协议--TCP/IP--属性--默认端口    

避免出现空指针异常方法总结(java篇)_风筝Lee的博客-程序员秘密_防止空指针异常

Java应用中抛出的空指针异常是解决空指针的最好方式,也是写出能顺利工作的健壮程序的关键。避免Java中的空指针异常的常用技巧(同时避免大量的非空检查):1) 从已知的String对象中调用equals()和equalsIgnoreCase()方法,而非未知对象。总是从已知的非空String对象中调用equals()方法。因为equals()方法是对称的,调用a.equals(b)和调...

Django的安全防护-Django在安全问题上的处理详解_盖世英雄Zz的博客-程序员秘密_django安全防护

跨站脚本 (XSS) 防护¶XSS攻击允许用户注入客户端脚本到其他用户的浏览器里。 这通常是通过存储在数据库中的恶意脚本,它将检索并显示给其他用户,或者通过让用户点击一个链接,这将导致攻击者的 JavaScript 被用户的浏览器执行。 然而,XSS 攻击可以来自任何不受信任的源数据,如 Cookie 或 Web 服务,任何没有经过充分处理就包含在网页中的数据。使用 Django 模板保护你免受多数

解决问题:java.lang.RuntimeException: 在系统中发现了多个分页插件,请检查系统配置!_总要有一个梦想或大或小的博客-程序员秘密_在系统中发现多个分页插件

在使用springboot配置mybatis PageHelper时由于版本出现的问题mybatis-config.xml中配置了一次PageHelper&amp;lt;plugin interceptor=&quot;com.github.pagehelper.PageInterceptor&quot;&amp;gt; &amp;lt;!-- 设置数据库类型Oracle,Mysql,MariaDB,SQLite,Hsqldb,Po...

docker 拉取oracle_Docker 下拉取oracle 11g镜像配置-Go语言中文社区_weixin_39667041的博客-程序员秘密

1、拉取镜像docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11g 由于镜像我已经拉取,所以此处显示已存在,查看镜像信息docker iamges 2、创建并容器信息docker run -d -p 1521:1521 --name oracle_11g registry.aliyuncs.com/helowin/ora...

推荐文章

热门文章

相关标签