注:本文为阅读《程序员的自我修养》第六章后所作总结
内存
操作系统会给每个进程分配独立的虚拟内存空间。根据处理器的位数,每个进程能够访问的最大内存 有所不同,但总物理内存的大小不受处理器位数的限制,而取决于地址线数量。
装载方式
程序运行需要把数据和代码装入内存。为了提高内存利用效率,操作系统使用覆盖装入和页映射 的方法实现动态装入,以确保读写最频繁的数据总是在内存中。覆盖装入允许同一程序不同模块共用 一块内存空间,页映射则把数据以“页”为单位选择性装载到内存中,减少读写频率小的数据占用的内存。
虚拟内存的结构
程序被编译后,分为许多“段(Section)”。以下代码展示了一个C语言程序中包含的部分Section。
ubuntu@myserver:~$ readelf -S test.elf
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.pr[...] NOTE 0000000000400270 00000270
0000000000000030 0000000000000000 A 0 0 8
[ 2] .note.gnu.bu[...] NOTE 00000000004002a0 000002a0
0000000000000024 0000000000000000 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000004002c4 000002c4
0000000000000020 0000000000000000 A 0 0 4
[ 4] .rela.plt RELA 00000000004002e8 000002e8
0000000000000240 0000000000000018 AI 29 20 8
[ 5] .init PROGBITS 0000000000401000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[ 6] .plt PROGBITS 0000000000401020 00001020
......
一般而言,操作系统按页来映射内存,因此每个Section对应一个页。然而这样显然会造成内存的浪费,因为 Section的大小不一定是页大小的整数倍。因此实际上,操作系统会把多个Section合成一个segment进行映射。 合成的依据是这些Section的“权限”,读、写、执行等。Segment的信息保存在程序头中。
除了可执行文件本身包含的数据外,操作系统还会分配栈和堆空间给进程使用。
装载过程
Linux可执行文件
- 检测可执行文件类型
- 检测可执行文件是否有效
- 配置动态链接
- 对ELF进行映射
- 初始化并启动进程
Windows 可执行文件
Windows中可执行文件称作PE。和Linux类似,也是由Section和Segment组成的。 不同点在于:
- 无需考虑地址对其
- 使用相对虚拟地址(所谓偏移地址)