本文以认知的角度,从文件的执行过程来了解内存分配过程,由于这个流程太过复杂,仅作为抛砖引玉。希望能够说明: 一个可执行文件,从磁盘加载到内存以及cpu执行过程的概貌
一.文件是什么?如何被存储?
一切文件的本质是二进制流,根据编码形成不同的文件格式。
我们常见的字符串是经过一些编辑器如notepad++/sublime识别出编码后,按照编码协议解码出来的结果
比如我们打开一个普通的文本文件,可能是utf-8,gbk,ascii等编码,sublime会识别二进制流中的格式解码成正常的字符串。
如果我们打开的不是一个文本文件,如一个音视频文件,那sublime显示出来的就是一堆乱码,乱码其实就是无法被打印显示的字符串。
二.可执行文件有什么特别的?
可执行文件是一些列预先编译好的指令和数据的集合,不同平台的指令集不一样。 linux中可执行文件格式为elf,windows下为exe,它们都是指按照一定的规范定义的文件。以elf文件为例:它是一系列的段组合文件,包含数据段,字符串段,代码段,符号表段等。
整个文件结构比较复杂,我们不深入分析每个段的具体作用,关注其中比较重要的两点:
- elf代码段中包含了cpu能够执行的指令和数据
- elf文件在链接阶段确定好了程序指令的偏移地址
我们可以通过objdump -d xxx 获取指令的偏移地址
objdump -d test
------------
Disassembly of section __TEXT,__text:
00000001000037a0 _main:
1000037a0: 55 pushq %rbp
1000037a1: 48 89 e5 movq %rsp, %rbp
1000037a4: b8 f0 11 00 00 movl $4592, %eax
1000037a9: e8 28 06 00 00 callq 1576 <dyld_stub_binder+0x100003dd6>
1000037ae: 48 29 c4 subq %rax, %rsp
1000037b1: 31 d2 xorl %edx, %edx
1000037b3: c7 45 fc 00 00 00 00 movl $0, -4(%rbp)
...
objdump -h test
------------
Sections:
Idx Name Size VMA Type
0 __text 00000635 00000001000037a0 TEXT
1 __stubs 00000060 0000000100003dd6 TEXT
2 __stub_helper 000000b0 0000000100003e38 TEXT
3 __cstring 000000c3 0000000100003ee8 DATA
4 __unwind_info 00000048 0000000100003fac DATA
5 __got 00000008 0000000100004000 DATA
6 __la_symbol_ptr 00000080 0000000100008000 DATA
7 __data 00000008 0000000100008080 DATA
复制代码
三.elf文件如何执行?
elf文件是一堆指令和数据的集合,它在运行时,由系统装载程序将elf的指令装载到内存中执行。
elf文件的执行过程大致包含三个部分:
- 创建一个独立的虚拟地址空间
- 读取可执行文件头(从第二部分我们已经知道elf是一种结构化数据),建立虚拟地址与elf文件的映射关系
- cpu寄存器设置为程序的入口函数地址,启动运行
文件执行过程就是进程的创建过程。指令只有cpu才能读取运行,为了提高效率,最简单的方式自然是把整个elf加载到内存中,但是由于内存条的随机高速访问特性,导致它造价非常昂贵,无法做到内存自由,就像无法左右爆米花自由一样。因此如何提高内存的使用率是一个长期命题。
这个问题根据程序的局部性原理得到解决方案,程序的局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。据此程序执行过程如下:
- 1.将程序的数据与指令按照页的形式组织,每个页的大小是4k个字节,同时物理内存也按照页大小划分为多个块。简单理解就是东西太大,切片处理。
- 2.装载程序将入口函数所在的p0块装载到物理内存f0,运行中依次将p1,p2装载到物理内存块f1,f2
- 3.运行到p3时,物理内存不够,发生缺页中断,根据lru算法将p3装载到F1
- 4.循环3的过程直到程序执行结束。这样一个1G的程序,运行期间可能只占用了实际100m的物理内存
这里大家肯定要骂娘,你刚才还跟我扯编译时已经确定了虚拟内存地址了,现在又说装载的时候才确定物理内存地址,而且还会替换,那cpu还能找到具体的指令在哪里吗?可以的,请继续看。
四.为什么需要虚拟内存?
我们知道现在的操作系统基本都是32位或者64位的,以32位为例,2^32 = 4G ,最大内存大小能支持4G。 如果是64位,那就是2^64=256T。实际的内存大小往往小于能够支持的内存大小。
为了解决这个问题,操作系统提供了虚拟内存的概念,虚拟内存让每个进程看起来拥有比实际物理意义内存大的多的内存空间,同时由于物理内存的访问由系统程序mmu控制,间接的保证了程序的安全性。
为了能将elf的虚拟内存地址转化为实际的内存地址,操作系统在进程创建期间,采用页表的形式存储了虚拟地址与实际地址的映射关系,页表是一个数据结构,可以通过虚拟地址找到实际物理地址。
负责查找的系统程序叫做mmu.它负责把页码转化为页框码。更加深入的分析不在本篇范围。有这个mmu之后,我们神奇的发现,我们传统意义上理解的物理内存,居然神奇般的变成了高速缓存。
在多任务操作系统中,每个进程都只会将部分的代码装载到内存中以备执行,大部分的代码还是在磁盘中
既然内存已经沦为高速缓存了,那我们是否可以全部用内存,不用磁盘呢?也是不行的。
五.磁盘与物理内存的本质区别?
磁盘与物理内存同属于存储介质,有如下两个显著的区别:
- 磁盘可持久化存储,内存是电驱动的,断电内容会消失。
- 磁盘是顺序访问的,随机访问速度较慢;内存随机访问速度是磁盘的大约100000倍。因此内存适合用于切片访问,而磁盘不可以。
六.总结
- 1.文件本质上是一堆二进制流。不同的文件格式以不同的协议保存二进制流。
- 2.可执行文件是一堆指令与数据的结合。它在链接期间已经决定了指令的偏移地址
- 3.elf文件在执行过程会创建程序所使用的虚拟地址空间,并将虚拟地址与实际物理地址的映射关系记录到页表中,页表存放在进程pcb块中。
- 4.mmu解决了cpu从虚拟地址获取物理地址的转化过程。