可执行文件只有装载到内存以后才能被CPU执行。
一、装载的方式
页映射:将内存和磁盘中的数据和指令按照页为单位划分,以后装载和操作的单位就是页。4kb
二、程序的装载运行步骤
- 创建一个独立的虚拟地址空间;(虚拟页到物理页的映射关系,此时为空即可,缺页错误时会自动设置)
- 读取可执行文件头,建立虚拟地址空间和可执行文件的映射关系;
- 动态链接的过程;
- 将CPU指令寄存器设置为可执行文件的入口地址(ELF文件头有存储),启动运行;(这个入口是运行库的入口函数)
- 入口函数对运行库和程序运行环境进行初始化,包括堆、IO、线程、全局变量构造等等;
- 入口函数完成初始化过程,调用main函数,正式开始执行程序的主体部分;
- main函数运行完毕,返回入口函数,入口函数进行清理工作:析构、堆销毁、关闭IO,然后系统调用结束进程;
三、进程虚拟空间的分布
- ELF链接视图和执行视图
ELF可执行文件引入了Segment的概念,它是一个或多个相同属性的section的合并,相当于装载时重新划分了ELF的各个段;
原因在于装载时只关心读写执行的权限,以section的粒度来映射会存在内存浪费。所以将相同权限的section合并当作一个Segment,例如.text和.init代码段; - 堆和栈
一个进程中的堆和栈分别都有一个对应的VMA。
通过proc文件系统查看进程的虚拟空间分布:
- 总结
操作系统通过给进程空间划分出一个个VMA来管理进程的虚拟空间,基本原则是将相同权限属性的、有相同映像文件的映射成一个VMA:
四、动态链接
- 静态链接和动态链接
- 静态链接的缺陷:磁盘和内存空间浪费、程序的更新部署麻烦;
- 动态链接的方法:将模块分割开,等到程序运行时再进行链接;
- 动态链接的符号
如果一个符号定义在动态共享对象中,那么连接器就会将这个符号的引用标记为宇哥动态链接的符号,不对它进行地址重定位,留到装载时再进行;zhoumeng.2019@n224-024-082:~/dynamix$ readelf -s P1 Symbol table '.dynsym' contains 13 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND foobar 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 7: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) 8: 0000000000201030 0 NOTYPE GLOBAL DEFAULT 25 _edata 9: 0000000000201038 0 NOTYPE GLOBAL DEFAULT 26 _end 10: 0000000000201030 0 NOTYPE GLOBAL DEFAULT 26 __bss_start 11: 00000000000005f8 0 FUNC GLOBAL DEFAULT 11 _init 12: 0000000000000804 0 FUNC GLOBAL DEFAULT 15 _fini
- 动态链接的过程
- 先启动动态链接器本身;
- 装载所需要的所有共享对象;
- 重定位和初始化;