程序的加载方式 ,覆盖载入,页映射,这两种方式都是利用了 局部性原理
覆盖载入
左边的程序是main调用A和B,A和B之间没有依赖关系,于是可以在加载完A之后用B覆盖A,这样就节省了内存。mian上的是overlay manager,也就是覆盖载入管理程序
右边是一个复杂的调用图,main->A ->C/D ,mian->B->E/F,从图中可以看出他们的依赖关系
页映射是将数据和指令分成4K-4M,一般是4K大小的页,其控制粒度比覆盖方式更细
程序不用一次性将全部的页都加载到内存,根据需要加载/替换页,页也跟虚拟内存有很大关系
从操作系统角度看创建一个进程需要做三件事
1.创建一个独立的虚拟地址空间
2.读取可执行文件头,并建立虚拟空间与可执行文件的映射关系
3.将CPU的指令寄存器设置成可执行文件的入口地址,启动运行
第二步中的映射关系如下图
页错误 page fault
当CPU准备执行这段指定时发现对应的页是空的,于是产生一个页错误,产生中断并将控制权交给操作系统,操作系统根据已经分配好的数据结构,去加载对应的页,之后返回中断,cpu重新执行一遍指令,结果就正确了
section 到 segment的映射
如果一个 text的section占两个页,init占一个页,如果能将他们合并,最后就只占两个页,节省了空间
一个程序它的section如下:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
00000000000000f0 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 00000000004003a8 000003a8
0000000000000072 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 000000000040041a 0000041a
0000000000000014 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400430 00000430
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400450 00000450
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400468 00000468
00000000000000d8 0000000000000018 AI 5 12 8
[11] .init PROGBITS 0000000000400540 00000540
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400560 00000560
00000000000000a0 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400600 00000600
0000000000000354 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000400954 00000954
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400960 00000960
0000000000000078 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 00000000004009d8 000009d8
0000000000000044 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400a20 00000a20
0000000000000134 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[20] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000601000 00001000
0000000000000060 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000601060 00001060
000000000000000c 0000000000000000 WA 0 0 4
[25] .bss NOBITS 0000000000601080 0000106c
00000000000000d8 0000000000000000 WA 0 0 32
[26] .comment PROGBITS 0000000000000000 0000106c
000000000000002d 0000000000000001 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 00001099
0000000000000108 0000000000000000 0 0 1
[28] .symtab SYMTAB 0000000000000000 000011a8
0000000000000738 0000000000000018 29 50 8
[29] .strtab STRTAB 0000000000000000 000018e0
00000000000002f4 0000000000000000 0 0 1
它对应到 segment如下:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000b54 0x0000000000000b54 R E 200000
LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x000000000000025c 0x0000000000000348 RW 200000
DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x00000000000009d8 0x00000000004009d8 0x00000000004009d8
0x0000000000000044 0x0000000000000044 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
0x00000000000001f0 0x00000000000001f0 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
从Section角度看ELF文件就是链接视图 Linking View, 从Segment角度看就是执行视图 Execution View
Segment的各个参数含义如下
参数 | 含义 |
Type | Segment的类型,有LOAD,DYNAMIC,INTERP等 |
offset | segment在文件中的偏移量 |
VirtAddr | Segment的第一个字节在进程虚拟地址空间的起始位置 |
PhysAddr | segment的物理装载地址 |
FileSiz | segment在ELF文件中所站空间的长度 |
MemSiz | segment在进程虚拟地址空间中所占的长度 |
Flags | 权限属性,比如可读R,可写W,可执行X等 |
Align | 对齐属性,实际对齐等于2的align次方 |
从操作系统角度看,各个section合并后成为segment的关系如下
启动一个python 内置的http server,cat /proc/[pid]/maps 结果如下
00400000-00401000 r-xp 00000000 fd:01 1051849 /usr/bin/python2.7
00600000-00601000 r--p 00000000 fd:01 1051849 /usr/bin/python2.7
00601000-00602000 rw-p 00001000 fd:01 1051849 /usr/bin/python2.7
01561000-017b5000 rw-p 00000000 00:00 0 [heap]
7f676dd13000-7f676dd22000 r-xp 00000000 fd:01 1050073 /usr/lib64/libbz2.so.1.0.6
7f676dd22000-7f676df21000 ---p 0000f000 fd:01 1050073 /usr/lib64/libbz2.so.1.0.6
7f676df21000-7f676df22000 r--p 0000e000 fd:01 1050073 /usr/lib64/libbz2.so.1.0.6
7f676df22000-7f676df23000 rw-p 0000f000 fd:01 1050073 /usr/lib64/libbz2.so.1.0.6
7f676df23000-7f676df48000 r-xp 00000000 fd:01 1050000 /usr/lib64/liblzma.so.5.2.2
7f676df48000-7f676e147000 ---p 00025000 fd:01 1050000 /usr/lib64/liblzma.so.5.2.2
。。。。
。。。。
。。。。
7f677af34000-7f677af35000 r--p 00021000 fd:01 1049801 /usr/lib64/ld-2.17.so
7f677af35000-7f677af36000 rw-p 00022000 fd:01 1049801 /usr/lib64/ld-2.17.so
7f677af36000-7f677af37000 rw-p 00000000 00:00 0
7ffe79477000-7ffe79498000 rw-p 00000000 00:00 0 [stack]
7ffe79498000-7ffe7949a000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
各列的参数含义如下
上图中用蓝色标注了一些特殊的VMA
Heap堆,Stack栈,vdso用于跟操作系统内核交互的模块
一个进程的VMA 大致有如下几种:
名称 | 含义 |
代码VMA | 只读,可执行,有镜像文件 |
数据VMA | 可读,可写,可执行,有镜像文件 |
堆VMA | 可读写,可执行,无镜像文件,可向上扩展 |
栈VMA | 可读写,不可执行,无镜像文件,可向下扩展 |
ELF与linux进程虚拟空间映射关系
linux进程初始化栈结构如下
esp指向初始化后的栈顶,也就是程序参数的个数,c的main函数中的argc,然后是两个参数,对应c的main函数就是argv。
之后是0表示结束,然后是两个系统参数,再后面跟0结为,最后是程序的内存布局
linux执行ELF程序的过程
调用fork创建一个子进程,再调用execve()函数执行指定的ELF文件
#include
#include
#include
int main() {
char buf[1024] = {0};
printf("mini bash\n");
scanf("%s", buf);
pid_t pid = fork();
while(1) {
if(0 == pid) {
if(execlp(buf,0) < 0) {
printf("exec error\n");
}
}
else if(pid > 0) {
int status;
waitpid(pid,&status,0);
}
else {
printf("fork error %d\n",pid);
}
}
return 0;
}
参考
《程序员的自我修养-链接,装载与库》