Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1136688
  • 博文数量: 300
  • 博客积分: 37
  • 博客等级: 民兵
  • 技术积分: 772
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-26 04:46
文章分类
文章存档

2017年(4)

2016年(7)

2015年(19)

2014年(72)

2013年(71)

2012年(127)

分类: LINUX

2016-08-16 23:22:38

1程序编译

编译驱动程序实现从源代码到可执行文件的预处理、编译、汇编、链接过程。

1.1预处理

预处理执行时将源代码.cpp翻译成ASCII码的中间文件.i。

1.2、#include

在源代码中形如#include或#include”x.h”的行都被替换为文件名指定的文件的内容。

1.3、编译

编译执行时将.i文件翻译为汇编语言文件.s。

1.4、汇编

汇编执行时将.s文件翻译成一个可重定位目标文件.o。

1.5、链接

将.o文件和必要的系统目标文件组合起来,创建可执行目标文件。

1.6、符号解析

在每个可重定位目标文件中都有一个符号表,其存放文件定义和引用的全部变量和函数信息。
符号表中的符号分3种:
在本可重定位文件定义全局符号、在其他可重定位文件定义并被本文件引用的全局符号(外部符号)、只被本模块定义和引用的本地符号。即带有static的C函数和全局变量。
符号解析的基本原则是将每个引用和目标文件符号表的一个符号定义联系起来。
由于编译器确保本地符号只有一个定义。因此本地符号解析很直接。
当编译器遇到一个不在本可重定位文件的符号(函数或变量时)时生成一个链接器符号条目表交给连接器去处理。则链接器在所有可重定位文件中去找此符号。
当在其他重定位文件中找到则链接成功,否则链接失败报错。
当外部符号出现在多个可重定位文件时适用以下规则:
其中已经初始化的变量为强符号,未初始化的为弱符号。

根据Unix连接器使用下面的规则来处理多重定义的符号:
●规则1:不允许有多个强符号
●规则2:如果有一个强符号和多个弱符号,那么选择强符号

●规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个(多么可怕啊)

2程序启动

2.1 ELF文件加载


程序源代码编译后的机器指令经常被放在代码段(Code Section)里,代码段常见的名字有“.code”或是“.text”;全局变量和局部静态变量数据放在数据段(Data Section),数据段名字为“.data”,未初始化的全局变量和局部静态变量默认值都为0,本在他们可以放在.data段,但是因为都是0,所以为 他们在.data段分配空间并且存放数据0是没有必要的,所以放在.bss段,.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有 内容,所以它在文件中也不占据空间。
ELF文件的开头是一个“文件头”,它描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接及入口地址(如果是可执行文件)、目标硬件、目标操作系统等信息,文件头还包括一个段表(Section Table),总体来说,程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。
ELF文件头描述了映射加载关系,可由OBJDUMP查看objdump -h 我的程序

当外壳运行一个进程时,父进程外壳生成一个子进程,它是父进程的复制品。其后Exec删除子进程现有的虚拟存储段,并创建新的代码、数据和堆、栈段。除头部信息外在加载过程中没有任何从存储器到存储器的数据拷贝,直到cpu引用一个被映射的虚拟页时才进行拷贝,后续通过页面调度机制自动将页面从磁盘传送到内存。
ELF文件结构:通过 readelf -S a.out


然后加载段地址到虚拟内存地址,映射如下:

然后另一部分段映射到数据区,关系如下:

通过 cat /proc/进程ID/maps查看进程内存布局

上述每列的名称如下:
地址:库在进程里地址范围
权限:虚拟内存的权限,r=读,w=写,x=,s=共享,p=私有;
偏移量:库在进程里地址范围
设备:映像文件的主设备号和次设备号;
节点:映像文件的节点号;
路径: 映像文件的路径
每项都与一个vm_area_struct结构成员对应。
64位下的代码段起始地址将从0x400000开始
其中1为代码段、2为数据段

2.2 X86-64进程布局


在这256TB的虚拟内存空间中, 0000000000000000 - 00007fffffffffff(128TB)为用户空间, ffff800000000000 - ffffffffffffffff(128TB)为内核空间。
Memory Mapping Region 任何应用程序都可以通过Linux的mmap()系统调用(实现)请求这种映射。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。‘大块’意味着比MMAP_THRESHOLD还大,缺省是128KB。

2.3 虚拟内存VM


虚拟存储器的作用:
1、 进程对内存访问采用“写时拷贝”,提高了对内存的使用效率---缓存工具
2、 保证每个进程的地址空间不被其他进程破坏—保护工具
3、 采用一致的内存空间,简化程序加载等操作—管理工具

2.3.1 地址

逻辑地址:段中的偏移地址,偏移地址指明了从段开始的地方到实际地址之间的距离。在C语言指针中,读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址。
虚拟地址(线性地址):段中的偏移地址(逻辑地址),加上相应段的基地址就生成了一个线性地址。
物理地址:真正的内存地址。

2.3.2 虚拟页

虚拟页状态:
? 未分配——就是没有和主存中的任何物理页对应,同时,也没有和磁盘上的任何空间对应。
? 缓存的——和主存的某一物理页对应了。
? 未缓存的——要把一个可执行文件加载到一个进程中,只要设置这个进程的虚拟地址空间的一个虚拟页,将这个虚拟页标志为未缓存的,然后指向磁盘上的可执行文件即可。真实使用的时候,cpu会负责让虚拟存储器将实际文件从磁盘换入到主存

2.3.2 缺页异常与牺牲页

DARM缓存的命中称为页命中,不命中称为缺页。举个例子来说,
1. CPU要访问的一个虚拟地址在虚拟页3上(VP3),通过地址翻译硬件从页表的3号页表条目中取出内容,发现有效位0,即没有缓存,就产生一个缺页异常
2. 缺页异常调用内核的缺页异常处理程序,它会根据替换算法选择一个DRAM中的牺牲页,比如PP3。PP3中已经缓存了VP4对应的磁盘文件的内容,如果VP4的内容有改动,就刷新到磁盘中去。然后把VP3对应的磁盘文件内容加载到PP3中。然后更新页表条目,把PTE3指向PP3,并修改PTE4,不再指向PP3.
3. 缺页异常处理程序返回后重新启动缺页异常前的指令,这时候虚拟地址对应的内容已经缓存在主存中了,页命中也可以让地址翻译硬件正常处理了。具体参考:深入理解计算机系统9.3.3、9.3.4
调用malloc函数后,OS会马上分配实际的内存空间吗?
答:不会,只会返回一个虚拟地址,待用户要使用内存时,OS会发出一个缺页异常,此时,内存管理模块才会为程序分配真正的内存。

2.3.4 PT

页表(page table)是一个页表条目(page table entry,PTE)数组。PT常储在内存中,每次地址转换时都会访问页表,操作系统负责维护页表的内容。
操作系统为每个进程维护一个独立的页表。同时多个虚拟页面可以映射到同一个共享物理页面。

同时PTE也包含多个许可条件如果一条指令违反许可条件则触发CPU保护故障即段错误(segmentation info)。

2.4 地址转换

Linux多级页表机制

以二级页表为例

在32位linux系统中一个PGD索引负责映射一个PTE基地址。
每个PTE基地址又负责映射1024个的大小为4K的虚拟存储器页面。
页面偏移指示在虚拟存储器页面内的偏移,由于虚拟存储器页面大小为4K因此页内偏移为12b(2**12=4K)。


阅读(1557) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~