《程序员的自我修养》读书笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012927281/article/details/50596494

本读书笔记从第六章开始,之前的内容会陆续补上。内容上主要对认为重要的内容进行记录,《程序员的自我修养》确实是一本好书,欢迎大家一起对书中的内容进行讨论。第六章的6.1-6.3节的内容总结如下:

书中前三节已经基本阐述清楚了可行性文件、进程虚拟地址空间与物理地址空间三者之间的关系。在此简单做一总结。程序要被运行,其代码与数据应被装载进入内存方可被执行。因此操作系统首先应建立可执行文件与虚拟地址空间之间的映射关系,即操作系统应该清晰代码与数据被放在了虚拟地址空间中的何处,此所谓可执行程序与虚拟地址空间之间的映射关系。而操作系统中同时运行多个程序,为使他们之间运行不互相干扰,因此不能让进程直接访问物理内存,而是由操作系统代为管理物理资源,当然这只是建立虚拟地址空间的原因之一,其他几点原因不清楚的同学,可参考操作系统理论的有关书籍。书归正传,正由于虚拟地址空间与物理地址的不等同,虚拟地址空间与物理地址空间同样存在某种映射关系,这两者这间的关系涉及操作系统的内存管理,在此不再展开赘述。

6.4 进程虚存空间分布

6.4.1 ELF文件链接视图与执行视图

6.4小节的主要内容就是阐述如何建立可执行文件与进程虚拟地址空间之间的关系,凭我们最简单的想法,由于可执行文件已经被组织成了一个个不同的段,因此可执行文件可以以段为单位直接与虚拟地址空间建立一一对应的关系,但由于虚拟地址空间是按照页进行管理,因此直接建立映射关系,会造成大量的内碎片。如果换个调度思考问题,操作系统装载可执行文件其实不需要关心可执行文件的内容,仅了解段的权限即可,即保证程序在运行过程中不被错误访问即可。基于以上想法,可采用的方法是将相同权限的“段(section)”合并为一,进而组织成为“节(segment)”。借用书中的一句话:“从链接的角度看,ELF文件是按'Section'存储的,从装载的角度看,ELF文件是按'Segment'划分的”。换句话说elf的文件内容就在那里不多、不少。在此还应理清一个概念就是虚拟内存区域(VMA,Virtual Memory Area),所谓VMA就是指可执行文件与进程虚拟地址空间的映射关系,上述映射关系作为一个数据结构保存在操作系统中。操作系统就是在此基础上,将权限相同的节映射进入一个VMA中。即“节”的组织是静态的,是存在于硬盘中的,而VMA的组织的动态的,是存在于内存中的。但上述概念不是绝对的,有关于这一部分内容的解释请见下一小节。

我们可通过实验的方式进一步理解这一概念,采用书中给出的实验,使用readelf -S与readelf -h命令分别查看可执行文件的内容。在此要补充一点知识,不可使用objdump工具查看elf文件的内容,objdump -h与readelf -s输出内容基本一致,但前者无法输出.rel开头的段和.shstrtab,.symtab,.strtab。readelf与objdump的详细对比请见这篇博客:http://my.oschina.net/kangchunhui/blog/152682

同时由于我的机器是64位,因此虚拟地址空间不是从08048000开始,通过实验对比可进一步验证“segment”与“section”是从不同角度来划分同一个ELF文件。

可执行文件与共享库文件均有程序头表(Program Header Table),由于目标文件不需要被装载,因此不需要程序头表。结构体Elf32_Phdr 中记载了程序头表中的内容,上述结构体同样存在于/usr/inclde/elf.h中。

6.4.2-6.4.3

在操作系统中VMA除了被用来映射可执行文件中的各个“Segment”外,操作系统通过使用VMA来对进程的地址空间进行管理。进程在执行过程中还需要堆、栈空间,事实上堆栈空间在进程的虚拟空间中的表现也是以VMA的形式存在的,很多情况下,一个进程中的栈和堆分别对应一个VMA。在linux下,可通过命令cat /proc/PID/maps 查看进程的虚拟空间分布。其中PID指进程ID,可通过命令./elf &命令查看,其中elf为可执行文件名。

关于进程虚拟地址空间的概念总结如下:

操作系统通过给进程空间划分出一个个VMA来管理进程的虚拟空间;基本原则是将相同权限属性的、有相同映像文件的映射成一个VMA;一个进程基本上可以分为如下几种VMA区域:

代码VMA,权限只读、可执行;有映像文件。

数据VMA,权限可读写、可执行;有映像文件。

堆VMA,权限可读写、可执行;无映像文件,匿名,可向上扩展。

栈VMA,权限可读写、不可执行;无映像文件,匿名,可向下扩展。

上一下节中提到的不绝对的概念是指,Linux的进程虚拟空间管理的VMA的概念并非与“Segment”完全对应,linux规定一个VMA可以映射到某个文件的一个区域,或者是没有映射到任何文件。

6.4.4 段地址对齐

通过前三小节的分析,我们已经基本了解了可执行文件与进程虚拟地址空间之间的关系。但仅靠上述映射关系还不够,因为虚拟地址空间是按照页进行管理的,而可执行文件仍然是按照节进行管理的,因此就需要把一个个不同的“节”装入内存“页”中。还是从最简单的想法出发,直接以节为单位,映射进入虚拟地址空间中的“页”。但虚拟地址空间是以页作为管理单元,因此直接映射的方法肯定会造成内碎片问题,虚拟地址空间中的内碎片映射到物理地址空间就造成了资源的浪费。因此为解决上述问题,有些UNIX系统采用了一个很取巧的方法,就是让那些各个段接壤部分共享一个物理页面,然后将该物理页面在虚拟地址空间中映射两次。上述方法的采用虽然造成了虚拟地址空间的浪费,但将可执行文件看作是一个整体,以“页”为单位对其进行划分。但在虚拟地址空间向物理地址空间进行映射时,由于减少了内碎片,因此就减少了对物理内存的浪费。

6.4.5 进程栈初始化 内容不多在此就不总结了。

6.5 Linux内核装载ELF过程简介

本小节主要介绍linux是如何装载并执行ELF文件的。

首先在用户层面,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行指定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束,然后继续等待用户输入命令。

1)execve()系统调用的入口是sys_execve()。

2)sys_execve()调用do_execve()。do_execve()的主要功能是查找被执行文件,如果找到文件,则读取文件的前128个字节以判断可执行文件类型。

3)调用search_binary_handle()去搜索和匹配合适的可执行文件装载处理工程。比如ELF可执行文件的装载处理过程为load_elf_binary()。

4)以load_elf_binary()为例,该函数最为重要的一步是将系统调用的返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点就是动态连接器。

5)按照调用顺序逐级返回load_elf_binary()->do_execve()->sys_execve()。

由于已经将系统调用的返回地址改成了被装载的ELF程序的入口地址了。所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到了ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成。


展开阅读全文

没有更多推荐了,返回首页