程序员的自我修养-可执行文件的加载和进程

程序员的自我修养-可执行文件的加载和进程

内存映射方式

程序的加载方式 ,覆盖载入,页映射,这两种方式都是利用了 局部性原理
覆盖载入

左边的程序是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

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的各个参数含义如下

参数含义
TypeSegment的类型,有LOAD,DYNAMIC,INTERP等
offsetsegment在文件中的偏移量
VirtAddrSegment的第一个字节在进程虚拟地址空间的起始位置
PhysAddrsegment的物理装载地址
FileSizsegment在ELF文件中所站空间的长度
MemSizsegment在进程虚拟地址空间中所占的长度
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]
                                                                                                           

各列的参数含义如下
1.VMA的地址范围
2.VMA的权限,r表示读 w为写 x执行 p是私有 s是共享
3.偏移量,表示VMA对应的Segment在映像文件中的偏移
4.表示映像文件所在设备的之主设备号和次设备号
5.映像文件的节点号
6.映像文件的路径
上图中用蓝色标注了一些特殊的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 <stdio.h>
#include <sys/types.h>
#include <unistd.h>

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;
}
                                                            
0 次阅读

发表评论

电子邮件地址不会被公开。