Linux文件映射的反思
1. 思考
多个进程可以加载相同的共享链接库,比如C语言的运行库,加载运行库采用内存映射文件的方式,可以延迟对于文件内容的读入操作。
共享链接库文件,是一个elf格式的库文件,里面会包含多个不同的section,比如text/data/bss section。
不同的section的访问方式和权限都有差别,比如text section可能一般要求“只读、可以执行”的权限,而data/bss section最起码要在“可读、可写”的权限。
那么,这种不同权限的section在内存映射时,是怎样区分对待,映射到不同的内存区域中的呢?
Linux下的共享链接库可以通过dlopen函数来加载,该函数会负责将一个共享链接库的不同的section以不同的方式映射到进程的地址空间中,
1: daniel@ubuntu:/proc$ cat /proc/self/maps
2: 00262000-00280000 r-xp 00000000 08:01 525208 /lib/i386-linux-gnu/ld-2.13.so
3: 00280000-00281000 r--p 0001d000 08:01 525208 /lib/i386-linux-gnu/ld-2.13.so
4: 00281000-00282000 rw-p 0001e000 08:01 525208 /lib/i386-linux-gnu/ld-2.13.so
5: 002df000-00455000 r-xp 00000000 08:01 525221 /lib/i386-linux-gnu/libc-2.13.so
6: 00455000-00457000 r--p 00176000 08:01 525221 /lib/i386-linux-gnu/libc-2.13.so
7: 00457000-00458000 rw-p 00178000 08:01 525221 /lib/i386-linux-gnu/libc-2.13.so
8: 00458000-0045b000 rw-p 00000000 00:00 0
9: 009b8000-009b9000 r-xp 00000000 00:00 0 [vdso]
10: 08048000-08051000 r-xp 00000000 08:01 1310739 /bin/cat
11: 08051000-08052000 r--p 00008000 08:01 1310739 /bin/cat
12: 08052000-08053000 rw-p 00009000 08:01 1310739 /bin/cat
13: 08fad000-08fce000 rw-p 00000000 00:00 0 [heap]
14: b7516000-b7517000 r--p 0043a000 08:01 3021821 /usr/lib/locale/locale-archive
15: b7517000-b7557000 r--p 002bd000 08:01 3021821 /usr/lib/locale/locale-archive
16: b7557000-b7757000 r--p 00000000 08:01 3021821 /usr/lib/locale/locale-archive
17: b7757000-b7758000 rw-p 00000000 00:00 0
18: b7768000-b776a000 rw-p 00000000 00:00 0
19: bf9e6000-bfa07000 rw-p 00000000 00:00 0 [stack]
库文件/lib/i386-linux-gnu/libc-2.13.so被映射到了三个内存区域,如果想了解各个字段都代表什么意思,可以到内核源码中去查看一个/proc/[pid]/maps是怎么打印出来的。
1: static const struct seq_operations proc_pid_maps_op = {
2: .start = m_start,
3: .next = m_next,
4: .stop = m_stop,
5: .show = show_map
6: };
1: static int show_map(struct seq_file *m, void *v)
2: {
3: struct vm_area_struct *vma = v;
4: struct proc_maps_private *priv = m->private;
5: struct task_struct *task = priv->task;
6:
7: show_map_vma(m, vma);
8:
9: if (m->count < m->size) /* vma is copied successfully */
10: m->version = (vma != get_gate_vma(task->mm))
11: ? vma->vm_start : 0;
12: return 0;
13: }
1: static void show_map_vma(struct seq_file *m, struct vm_area_struct *vma)
2: {
3: struct mm_struct *mm = vma->vm_mm;
4: struct file *file = vma->vm_file;
5: vm_flags_t flags = vma->vm_flags;
6: unsigned long ino = 0;
7: unsigned long long pgoff = 0;
8: unsigned long start, end;
9: dev_t dev = 0;
10: int len;
11:
12: if (file) {
13: struct inode *inode = vma->vm_file->f_path.dentry->d_inode;
14: dev = inode->i_sb->s_dev;
15: ino = inode->i_ino;
16: pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;
17: }
18:
19: /* We don't show the stack guard page in /proc/maps */
20: start = vma->vm_start;
21: if (stack_guard_page_start(vma, start))
22: start += PAGE_SIZE;
23: end = vma->vm_end;
24: if (stack_guard_page_end(vma, end))
25: end -= PAGE_SIZE;
26:
27: seq_printf(m, "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu %n",
28: start,
29: end,
30: flags & VM_READ ? 'r' : '-',
31: flags & VM_WRITE ? 'w' : '-',
32: flags & VM_EXEC ? 'x' : '-',
33: flags & VM_MAYSHARE ? 's' : 'p',
34: pgoff,
35: MAJOR(dev), MINOR(dev), ino, &len);
36:
37: /*
38: * Print the dentry name for named mappings, and a
39: * special [heap] marker for the heap:
40: */
41: if (file) {
42: pad_len_spaces(m, len);
43: seq_path(m, &file->f_path, "\n");
44: } else {
45: const char *name = arch_vma_name(vma);
46: if (!name) {
47: if (mm) {
48: if (vma->vm_start <= mm->brk &&
49: vma->vm_end >= mm->start_brk) {
50: name = "[heap]";
51: } else if (vma->vm_start <= mm->start_stack &&
52: vma->vm_end >= mm->start_stack) {
53: name = "[stack]";
54: }
55: } else {
56: name = "[vdso]";
57: }
58: }
59: if (name) {
60: pad_len_spaces(m, len);
61: seq_puts(m, name);
62: }
63: }
64: seq_putc(m, '\n');
65: }
seq_printf(m, "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu %n",
start,
end,
flags & VM_READ ? 'r' : '-',
flags & VM_WRITE ? 'w' : '-',
flags & VM_EXEC ? 'x' : '-',
flags & VM_MAYSHARE ? 's' : 'p',
pgoff,
MAJOR(dev), MINOR(dev), ino, &len);
即[vma的起始地址]-[vma的结束地址] [读][写][执行][共享/私有] [在文件中的偏移] [主设备号]:[次设备号] [inode序号] [长度]
对于最后一个字段
const char *name = arch_vma_name(vma);
if (!name) {
if (mm) {
if (vma->vm_start <= mm->brk &&
vma->vm_end >= mm->start_brk) {
name = "[heap]";
} else if (vma->vm_start <= mm->start_stack &&
vma->vm_end >= mm->start_stack) {
name = "[stack]";
}
} else {
name = "[vdso]";
}
}
if (name) {
pad_len_spaces(m, len);
seq_puts(m, name);
}
什么是[vdso]呢?
可以将vdso看成一个shared objdect file(这个文件实际上不存在),内核将其映射到某个地址空间,被所有程序所共享。(我觉得这里用到了一个技术:多个虚拟页面映射到同一个物理页面。即内核把vdso映射到某个物理页面上,然后所有程序都会有一个页表项指向它,以此来共享,这样每个程序的vdso地址就可以不相同了)
参考:http://www.cppblog.com/hex108/archive/2010/11/22/134313.html
vdso是将内核态的调用映射到用户态的地址空间中,使得调用开销更小,路径更好
这是一个内存页,用于中转系统调用,以提高性能。有点类似于Xen中的hypercall页面。
我们看一下,被映射成三个区域的部分在文件中是分别是什么样的内容
1: daniel@ubuntu:/proc$ readelf -S /lib/i386-linux-gnu/libc-2.13.so
2: There are 35 section headers, starting at offset 0x178b50:
3:
4: Section Headers:
5: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
6: [ 0] NULL 00000000 000000 000000 00 0 0 0
7: [ 1] .note.gnu.build-i NOTE 00000174 000174 000024 00 A 0 0 4
8: [ 2] .note.ABI-tag NOTE 00000198 000198 000020 00 A 0 0 4
9: [ 3] .gnu.hash GNU_HASH 000001b8 0001b8 003c38 04 A 4 0 4
10: [ 4] .dynsym DYNSYM 00003df0 003df0 009200 10 A 5 1 4
11: [ 5] .dynstr STRTAB 0000cff0 00cff0 005acd 00 A 0 0 1
12: [ 6] .gnu.version VERSYM 00012abe 012abe 001240 02 A 4 0 2
13: [ 7] .gnu.version_d VERDEF 00013d00 013d00 0003d8 00 A 5 28 4
14: [ 8] .gnu.version_r VERNEED 000140d8 0140d8 000040 00 A 5 1 4
15: [ 9] .rel.dyn REL 00014118 014118 002a10 08 A 4 0 4
16: [10] .rel.plt REL 00016b28 016b28 000038 08 A 4 11 4
17: [11] .plt PROGBITS 00016b60 016b60 000080 04 AX 0 0 16
18: [12] .text PROGBITS 00016be0 016be0 10cba4 00 AX 0 0 16
19: [13] __libc_freeres_fn PROGBITS 00123790 123790 000f17 00 AX 0 0 16
20: [14] __libc_thread_fre PROGBITS 001246b0 1246b0 000196 00 AX 0 0 16
21: [15] .rodata PROGBITS 00124860 124860 01dbc8 00 A 0 0 32
22: [16] .interp PROGBITS 00142428 142428 000013 00 A 0 0 1
23: [17] .eh_frame_hdr PROGBITS 0014243c 14243c 0073b4 00 A 0 0 4
24: [18] .eh_frame PROGBITS 001497f0 1497f0 028c74 00 A 0 0 4
25: [19] .gcc_except_table PROGBITS 00172464 172464 0005a8 00 A 0 0 1
26: [20] .hash HASH 00172a0c 172a0c 003484 04 A 4 0 4
27: [21] .tdata PROGBITS 001761e4 1761e4 000008 00 WAT 0 0 4
28: [22] .tbss NOBITS 001761ec 1761ec 000038 00 WAT 0 0 4
29: [23] .init_array INIT_ARRAY 001761ec 1761ec 00000c 00 WA 0 0 4
30: [24] __libc_subfreeres PROGBITS 001761f8 1761f8 000070 00 WA 0 0 4
31: [25] __libc_atexit PROGBITS 00176268 176268 000004 00 WA 0 0 4
32: [26] __libc_thread_sub PROGBITS 0017626c 17626c 00000c 00 WA 0 0 4
33: [27] .data.rel.ro PROGBITS 00176280 176280 001afc 00 WA 0 0 32
34: [28] .dynamic DYNAMIC 00177d7c 177d7c 0000f0 08 WA 5 0 4
35: [29] .got PROGBITS 00177e6c 177e6c 000174 04 WA 0 0 4
36: [30] .got.plt PROGBITS 00177ff4 177ff4 000028 04 WA 0 0 4
37: [31] .data PROGBITS 00178020 178020 00099c 00 WA 0 0 32
38: [32] .bss NOBITS 001789c0 1789bc 003058 00 WA 0 0 32
39: [33] .gnu_debuglink PROGBITS 00000000 1789bc 000014 00 0 0 1
40: [34] .shstrtab STRTAB 00000000 1789d0 00017e 00 0 0 1
41: Key to Flags:
42: W (write), A (alloc), X (execute), M (merge), S (strings)
43: I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
44: O (extra OS processing required) o (OS specific), p (processor specific)
第一个区域,r_xp,是“只读、执行、私有”区域,主要映射的是.text section, .rodata section;
第二个区域,r__p,是“只读、私有”区域,主要映射的是.tdata section, .tbss section;
第三个区域,rw_p,是“读写、私有”区域,主要映射的是.data section, .bss section;
因此,对于共享链接库以及可执行文件来说,内存映射实际上只是加载过程的一部分,一个文件可以映射到内存的几个区域。
Linux文件映射的反思的更多相关文章
- linux文件映射到windows(方便用虚拟机搭建linux服务器,用本地windows代码编辑)
1,安装docker: https://docs.docker.com/install/linux/docker-ce/centos/ 2,linux上创建好需要共享的目录 /data/share(可 ...
- windows下操作linux虚拟机映射网络驱动器中文件提示chmod权限不足解决方案
为了方便操作,linux虚拟机会通过windows下连接网络驱动器的方式共享自己的文件,对于前端来说,我想把gulp放在windows磁盘,操作虚拟机中的php文件,一来节省虚拟机磁盘大小,二来解决虚 ...
- Linux文件读写机制及优化方式
导读 Linux是一个可控性强的,安全高效的操作系统.本文只讨论Linux下文件的读写机制,不涉及不同读取方式如read,fread,cin等的对比,这些读取方式本质上都是调用系统api read,只 ...
- Linux文件权限;ACL;Setuid、Setgid、Stick bit特殊权限;sudo提权
相关学习资料 http://blog.sina.com.cn/s/blog_4e2e6d6a0100g47o.html http://blog.csdn.net/aegoose/article/det ...
- 文件映射mmap
磁盘与内存的映射就是文件映射,说这个问题之前我们先说下swap,因为 这个问题让我很容易想起swap,linux swap 是交换分区的意思,在内存不 够的情况下,操作系统先把内存与磁盘的sw ...
- nand烧写分析/内核在启动过程中式如何将这个文件映射成/目录及各子目录的?
我用的是ramdisk.image.gz,烧写在flash的0x10140000处 我不太明白内核在启动过程中式如何将这个文件映射成/目录及各子目录的? 如果ramdisk.image.gz在flas ...
- 【转】漫谈linux文件IO--io流程讲的很清楚
[转]漫谈linux文件IO--io流程讲的很清楚 这篇文章写的比较全面,也浅显易懂,备份下.转载自:http://blog.chinaunix.net/uid-27105712-id-3270102 ...
- linux文件权限总结(创建root不可以删除文件、只可追加的日志文件等)
文件类型 对于文件和目录的访问权力是根据读访问,写访问,和执行访问来定义的. 我们来看一下 ls 命令的输出结果 [root@iZ28dr6w0qvZ test]# ls -l 总用量 72 -rw- ...
- Linux文件系统目录
Linux操作系统目录呈树形结构,文件系统只有一个根目录,其余文件都是从根目录下延伸出来的 上图是一个Linux文件系统目录的展现,现在我们来看一下文件系统目录下相关目录及其功能 根目录( / ) L ...
随机推荐
- 关于radio选中或者反选
关注点:一.attr()和prop()的区别 <!DOCTYPE html> <html> <head> <title>JavaScript对文字按 ...
- js匿名函数测试
js匿名函数测试 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> < ...
- 抓包工具tcpdump用法说明--1
本文目录: 1.1 tcpdump选项 1.2 tcpdump表达式 1.3 tcpdump示例 tcpdump采用命令行方式对接口的数据包进行筛选抓取,其丰富特性表现在灵活的表达式上. 不带任何选项 ...
- VMware里克隆出来的CentOS Linux device eth0 does not seem to be present, delaying initialization
解决办法:1.ifconfig eth1 确定新网卡的MAC地址.nmcli con 确定新网卡的UUIDvim /etc/udev/rules.d/70-persistent-net.rules把原 ...
- Tomcat负载均衡、调优核心应用进阶学习笔记(四):JVM调优
文章目录 概述 **JVM管理的内存段可分为两大类:线程共享内存和线程私有内存** Java Heap SIze Options The memory structure of a JVM proce ...
- ORA-06550/PLS-00103
原因是单引号‘是需要加转义字符的(即‘—>“)
- Cocos2d-x之Layer
| 版权声明:本文为博主原创文章,未经博主允许不得转载. Layer是处理玩家事件响应的Node子类.与场景不同,层通常包含的是直接在屏幕上呈现的内容,并且可以接受用户的输入事件,包括触摸,加速度 ...
- Cocos2d-x在Windows平台环境的搭建
| 版权声明:本文为博主原创文章,未经博主允许不得转载. windows平台下配置Cocos2d-X引擎所需的压缩文件和可执行文件,如下: 下载链接: adt: http://tools.a ...
- Apache和Tomcat的区别是什么?
Apache 和 Tomcat 都是web网络服务器,两者既有联系又有区别,在进行HTML.PHP.JSP.Perl等开发过程中,需要准确掌握其各自特点,选择最佳的服务器配置. Apache是web服 ...
- shell编程:expr的数学运算
运算符两种方式 方式一:expr $num1 operator $num2 方式二:$(($num1 operator $num2)) (方式二在运算符“=”时候会出错) 1+2.sh 这个代码有点问 ...