本文以一个简单的hello.c程序开始,介绍了其在Linux下运行的完整生命周期,包括预处理、编译、汇编、链接、进程管理、存储管理、I/O管理这七部分,详细介绍了程序从被键盘输入、保存到磁盘,直到最后程序运行结束,悄然逝去的全过程。本文通过清晰地观察hello.c的完整周期,直观地表现其生命历程。
关键词:hello、Linux、程序、生命周期
目 录
2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式........................................................................ - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程......................................................................... - 10 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................... - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理.......................................... - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换................................................ - 11 -
7.5 三级Cache支持下的物理内存访问............................................................. - 11 -
7.6 hello进程fork时的内存映射..................................................................... - 11 -
7.7 hello进程execve时的内存映射................................................................. - 11 -
7.8 缺页故障与缺页中断处理.............................................................................. - 11 -
8.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
① P2P(From Program to Process)过程:
hello的生命周期是从一个高级C语言程序开始的,分为四个阶段:首先经过预处理器cpp进行预处理,生成文本文件hello.i,然后经过编译器ccl生成hello.s汇编程序,接着经过汇编器as生成hello.o文件,最后经过链接器ld将其与引用到的库函数链接,生成可执行文件hello。再通过系统创建一个新进程并且把程序内容加载,实现有程序到进程的转化。
② O2O(From Zero-0 to Zero-0)过程:
当程序员在shell中运行可执行目标文件hello时,shell识别出这是一个外部命令,先调用 fork函数创建了一个新的子进程(Process),然后调用execve函数在新的子进程中加载并运行hello。运行hello还需要CPU为hello分配内存、时间片。在hello运行的过程中,CPU要访问相关数据需要MMU的虚拟地址到物理地址的转化,其中 TLB和四级页表为提高地址翻译的速度做出了巨大贡献,得到物理地址后三级 Cache又帮助CPU快速得到需要的字节。系统的进程管理帮助hello切换上下文、shell的信号处理程序使得hello在运行过程中可以处理各种信号,当程序员主动地按下Ctrl+Z或者hello运行到“return 0”;时hello所在进程将被杀死,shell会回收它的僵死进程。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk以上;
软件环境:Windows10 64位; Vmware 11; Ubuntu 64位;
开发工具:CodeBlocks;vi/vim/gpedit+gcc;gdb;edb;readelf;objdump等。
1.3 中间结果
得到的中间文件:
hello.i: hello.c经预处理得到的文本文件
hello.s: hello.i 经编译程序的汇编文件
hello.o: hello.s经得到的可重定位目标文件
hello: hello.o经链接得到的可执行目标文件
1.4 本章小结
本章通过简单介绍hello.c程序一生中的P2P过程和020过程,展示了一个源程序是如何经过预处理、编译、汇编、链接等阶段,生成各种各样的中间文件,最终成为一个可执行目标文件的。本章也介绍了本次实验所用到的硬件环境、软件环境以及开发工具等。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是C语言程序从源代码变成可执行程序的第一步,主要处理程序中的预处理命令。预处理包括处理宏定义、处理特殊符号、处理条件编译等。预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。
预处理的作用主要是使得程序便于阅读、修改、移植和调试,让编译器在随后的文本进行编译的过程中更方便,也有利于模块化程序设计。
具体地说,预处理有如下功能:
①宏展开:预处理程序中的“#define”标识符文本,用实际值(可以是字符 串、代码等)替换用“#define”定义的字符串;
②文件包含复制:预处理程序中用“#include”格式包含的文件,将文件的内 容插入到该命令所在的位置并删除原命令,从而把包含的文件和当前源文件连接成一个新的源文件,这与复制粘贴类似;
③条件编译处理:根据“#if”和“#endif”、“#ifdef”和“#ifndef”后面的条件确定需要编译的源代码。
2.2在Ubuntu下预处理的命令
使用指令gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
预处理后,得到文本文件hello.i ,文本的行数增加到了3061行,原本C程序中的注释都被删除。
hello.i文件的开头部分首先是一个原c文件文件名的字符串,紧接着可以看到它额外包含了很多头文件而不仅仅是原程序中包含的#include <stdio.h>,#include <unistd.h> ,#include <stdlib.h>。
之后利用typedef定义了变量类别别名、结构体和函数声明:
文件中还包括extern引用外部符号的部分
文件的最后部分为原程序
2.4 本章小结
本章介绍了预处理的主要作用:去注释、宏替换、条件编译、头文件展开
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译是把通常为高级语言的源代码(这里指经过预处理而生成的 hello.i)到能直接被计算机或虚拟机执行的目标代码(这里指汇编文件 hello.s)的翻译过程。
作用:
1. 词法分析,词法分析也称作扫描,是编译器的第一个步骤,词法分析器读入组成源程序的字符流,并且将它们组织成为有意义的词素的序列,对于每一个词素,词法分析器产生如下形式的词法单元作为输出。
2. 语法分析,语法分析器使用词法分析器生成的各词法单元的第一个分类来 创建树形的中间表示,在词法分析的基础上将单词序列组合成各类语法短语。该中间表示给出了词法分析产生的词法单元的语法结构,常用的表示方法为语法树。
3. 语义分析,语义分析器使用语法树和符号表中的信息来检查源程序是否和 语言定义的语义一致,它同时收集类型信息,并存放在语法树或符号表中, 为代码生成阶段做准备。
4. 代码生成和优化,在源程序的语法分析和语义分析完成后,会生成一个明 确的低级的或类及其语言的中间表示。代码优化试图改进中间代码,使生成的代码执行所需要时间和空间更少。最后代码生成以中间表示形式为输入,并把它映射为目标语言。
预处理过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位--预处理记号用来支持语言特性。预处理的功能可以分为宏定义,文件包含,条件编译三方面,由预处理器解析宏定义命令、文件包含命令、条件编译命令。为进一步编译提供准备文件。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1工作伪指令
.s文件开头是指导汇编器和连接器工作的伪指令,含义如下表:
3.3.2数据格式和寄存器结构
数据存储的格式以及寄存器的存储结构如下图所示:
3.3.3 数据
立即数:
在汇编代码中通常以$符号作为标识,hello.c中的整型常量是以数字出现的,对应了hello.s中的立即数。
寄存器变量:寄存器存储的变量,通过特定的寻址方式进行引用
字符串:.LC0和.LC1作为两个字符串变量被声明。中文字符串在汇编语言中以UTF-8的形式进行编码存储,而英文则是保持原样,在汇编代码中形式如下:
3.3.4 数据传送指令
汇编代码中数据移动使用MOV类,这些指令把数据从源位置复制到目的位置,不做任何变化。
例如下述语句意思就是将双字长度的立即数1传到寄存器%edi中
3.3.5算术操作
每种算术运算指令的末尾也有b、w、l、q(例如addb)来限制数据的大小。
如下述语句就是将寄存器%rax中的数值加上32后存入%rax中
3.3.6逻辑操作
逻辑操作常见的有两类,一类是加载有效地址,一类是位移操作。加载有效地址指令类似于MOV类指令,它的作用是将有效地址写入到目的操作数,相当于C语言中大家所熟知的取址操作,可以为以后的内存引用产生指针。位移操作顾名思义就是将二进制数进行整体左移或者右移。
如:
3.3.7 条件控制
汇编语言中,一些指令会改变条件码,例如cmpl指令和setl指令。一般是将寄存器中存储的值和其他值进行比较,设置条件码CF、ZF等,然后进行跳转或者其他操作。
如:
上述语句比较立即数9和%rbp偏移四位所存储数据的大小,若小于等于9则跳转。
3.3.10 函数调用:call指令用来进行函数的调用。如以下语句:
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.4 本章小结
本章对汇编指令做了以下简单的介绍,以及查看了Hello的机器级实现。经过简单的思考,我们便可以发现这些汇编指令和C语言代码语句之间的对应关系。同时,根据一个程序的汇编代码我们也可以翻译出相应的C语言程序的大致样貌。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编程序是指把汇编语言书写的程序翻译成与之等价的机器语言程序的翻译程序。汇编程序输入的是用汇编语言书写的源程序,输出的是用机器语言表示的目标程序。也就是说,汇编器会把输入的汇编指令文件重新打包成可重定位目标文件,并将结果保存成.o文件。它是一个二进制文件,包含程序的指令编码。
作用:将汇编语言翻译为机器指令,为后续的链接和执行过程做准备。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.3.1 ELF头
查看可重定位目标文件的elf形式,使用命令readelf
-
h
hello
.
o
查看ELF头,结果如下
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中ELF头的大小,目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如X86-64)、字节头部表的文件偏移,以及节头部表中条目的大小和数量。上图中,Data表示了系统采用小端法,文件类型Type为REL(可重定位文件),节头数量Number of section headers为13个等信息。
4.3.2 Section头
节头部表中描述其他节的位置和大小,还包括包括节的名称、类型、地址、偏移量、对齐等,使用命令readelf -S hello.o查看节头:
4.3.3 符号表
.symtab一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。Name是字符串中的字节偏移,指向符号的以null结尾的字符串名字,value是据定义目标的节的起始位置偏移,size是目标的大小(以字节为单位)。Type是符号的种类,有函数、数据、文件等,Binding简写为bing,表示符号是本地的还是全局的,符号表还可以包含各个节的条目,以及对应原始源文件的路径名的条目。使用命令readelf -s hello.o查看符号表,结果如下:
4.3.4可重定位信息
使用readelf -r hello.o查看可重定位信息,结果如下:
offset是需要被修改的引用的节偏移,Sym.标识被修改引用应该指向的符号。Type告知连接器如何修改新的引用,Addend是一个有符号常数,一些类型的重定位要使用它对被修改的引用的值做偏移调整。
4.4 Hello.o的结果解析
反汇编结果如下:
反汇编得到的代码与汇编语言很相似,但是增加了汇编语言对应的机器语言。机器语言是由0/1所构成的序列,在终端显示为16进制表示的。
机器代码利用更简单的抽象模型隐藏实现的细节。他利用了两种抽象,首先是由指令集体系结构来定义机器级程序的格式和行为,第二种抽象是通过将内存地址以虚拟地址的方式实现。汇编语言接近机器代码,但是机器代码以二进制的形式存在,而汇编代码则是可读性更好的文本形式。
机器代码与汇编代码不同的地方在于:
1.分支跳转方面:汇编语言中的分支跳转语句使用的是标识符(例如je .L2)来决定跳转到哪里,而机器语言中经过翻译则直接使用对应的地址来实现跳转。
如:
2.函数调用方面:在汇编语言.s文件中,函数调用直接写上函数名。而在.o反汇编文件中,call目标地址是当前指令的下一条指令地址。这是因为hello.c中调用的函数都是共享库中的函数,需要等待链接之后才能确定响应函数的地址。因此,机器语言中,对于这种不确定地址的调用,会先将下一条指令的相对地址设置为0,然后再.rela.text节中为其添加重定位条目,等待链接时确定地址。
4.5 本章小结
本章介绍了Hello从hello.s到hello.o的过程。这一节中对hello.o的ELF头,Section头以及符号表进行了分析,可以看到Hello的跟深处的信息。本节还对hello.o的反汇编文件进行了解析,比较了相对于hello.s文件.o文件是怎么让机器更加理解的。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译、加载、运行时。
作用:使得分离编译成为可能,可以将大型应用程序分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需要简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
5.3.1 ELF 头
hello的 ELF头和hello.o的包含的信息种类相同,但是文件的Type发生了变化,从REL变成了EXEC(Executable file可执行文件),节头部数量也发生了变化,变为了27个。
5.3.2 Section头
与之前相比,因为邻接,所有的节都有了实际地址。它描述各个节的大小、偏移量和其他属性,对hello中所有信息进行了声明,包括了大小(Size)、偏移量(Offset)、起始地址(Address)以及数据对齐方式(Align)等信息。根据始地址和大小,我们就可以计算节头部表中的每个节所在的区域。
5.3.3 符号表
可以发现经过链接之后符号表的符号数量陡增,说明经过连接之后引入了许多其他库函数的符号,一并加入到了符号表中,符号表不需要加载到内存。
5.3.4 可重定位段信息
与之前的重定位节完全不同,这是链接重定位的结果。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
hello是符号解析和重定位后的结果,链接器会修改hello中数据节和代码节中对每一个符号的引用,使得他们指向正确的运行地址。
hello.o与其他库链接,hello.c中使用的库中的函数就被加载进来了,如exit、printf、sleep、getchar、atoi等函数。
hello反汇编代码中函数调用时不再仅仅储存call当前指令的下一条指令,而是已经完成了重定位,调用的相应函数已经有对应的明确的虚拟地址空间。
hello反汇编代码中多了某些节,这些节都是经过链接之后加入进来的。例如.init节就是程序初始化需要执行的代码所在的节。
重定位的过程分为两大步:
1.重定位节和符号定义:在这一步中,连接器将所有相同类型的节合并成为同一类型的新的聚合节。例如,来自所有输入模块的.data节全部被合并成一个节,这个节成为输出的可执行目标文件的.data节。
2. 重定位节中的符号引用:在这一步中,连接器修改代码节和数据节中对每个符号的引用,使得他们指向正确的运行时地址。要执行这一步,连接器依赖于可重定位条目,及5.3节中分析的那些数据。
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.6 hello的执行流程
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
动态链接,又称为迟绑定或运行时链接,指的是在程序运行时才将所需的共享库载入内存并关联到程序中的过程。这种机制允许多个程序共享同一库文件的单个副本,从而节约系统资源并便于库的更新和维护。与静态库不同,动态库(如Linux中的.so文件)在生成时设计为可在程序运行时被加载。它们通常存放在系统的指定目录(如/lib、/usr/lib等),并在需要时由动态链接器加载。
把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
.plt:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
.got:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
分析hello程序的动态链接项目,通过edb/gdb调试,分析在动态链接前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章介绍了链接的定义和作用,分析了程序链接的过程,查看ELF的信息分析链接生成可执行文件过程中程序发生的变化。使用edb分析了动态链接。
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
作用:系统中的每个程序都运行在某个进程的上下文中,进程提供一个假象,就好像我们的程序是系统中当前运行的唯一的程序的一样,我们的程序好像是独占地使用处理器和内存。处理器就好像是无间断地一条接着一条地执行我们程序中的指令。最后,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell能解释用户输入的命令,将它传递给内核,还可以调用其他程序,给其他程序传递数据或参数,并获取程序的处理结果。在多个程序之间传递数据,把一个程序的输出作为另一个程序的输入。Shell本身也可以被其他程序调用。
处理流程:
1.输出一个提示符,等待输入命令,读取从终端输入的用户命令。
2.分析命令行参数,构造传递给execve的argv向量。
3.检查第一个命令行参数是否是一个内置的shell命令,如果是立即执行。
4.否则用fork为其分配子进程并运行。
5.子进程中,进行步骤2获得参数,调用exceve()执行程序。
6.命令行末尾没有&,代表前台作业,shell用waitpid等待作业终止后返回。
7.命令行末尾有&,代表后台作业,shell返回。
6.3 Hello的fork进程创建过程
在Linux系统中,用户可以通过 ./ 指令来执行一个可执行目标文件。在程序运行时,Shell就会创建一个新的进程,并且新创建的进程更新上下文,在这个新建进程的上下文中便可以运行这个可执行目标文件。fork函数只会被调用一次,但会返回两次,在父进程中,fork返回子进程的PID,在子进程中,fork返回0。新创建的进程与父进程几乎相同但有细微的差别。子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本(代码、数据段、堆、共享库以及用户栈),并且紫禁城拥有与父进程不同的Pid。
6.4 Hello的execve过程
当Hello的进程被创建之后,会调用execve函数加载并调用程序。exevce函数在被调用时会在当前进程的上下文中加载并运行一个新程序。它被调用一次从不返回,执行过程如下:当execve被调用时,操作系统首先根据提供的filename查找并加载相应的可执行文件。接着,操作系统会创建一个新的进程映像,并将可执行文件的内容加载到这个新进程中。之后,系统会更新这个新进程的参数和环境变量,使其与调用execve时指定的argv和envp相匹配。最后,系统将控制权交给新程序的入口点,开始执行新程序的代码。
6.5 Hello的进程执行
6.5.1逻辑控制流
逻辑控制流是进程给运行程序的第一个假象,它让程序看起来独占整个CPU在运行,但实际上的情况当然不会是这样的,如下图。这三个进程的运行时间不是连续的,也就是说每个进程会交替的轮流使用处理器来进行处理。每个进程执行它的流的一部分,之后可能就会被抢占,如果被抢占了的话就会被挂起进行其他流的处理。
6.5.2并发流与时间片
两个流如果在执行的时间上有所重叠,那么我们就说这两个流是并发流,每个流执行一部分的时间就叫做时间分片。例如上图中,我们可以说进程A和进程B并发,进程A也和进程C并发,但是进程B和进程C就不是并发的。
6.5.3内核模式和用户模式
内核模式:
在内核模式下,执行代码拥有完全且不受限制地访问底层硬件的权利。它可以执行任何CPU指令并引用任何内存地址。这种模式通常保留给操作系统的核心组件,如内核本身、设备驱动程序和其他关键的系统服务。由于内核模式下的代码具有极高的权限,因此它的崩溃通常是灾难性的,可能导致整个系统瘫痪。因此,内核模式的代码需要经过严格的测试和验证,以确保其稳定性和安全性
用户模式:
与内核模式相反,用户模式下的代码受到诸多限制,不能直接访问硬件或引用所有内存地址。它必须通过系统API来间接访问硬件或内存,这确保了系统资源的安全和稳定。在用户模式中,进程不允许执行特权指令,例如发起一个I/O操作等,更重要的是不允许直接引用地址空间中内核区内的代码和数据。如果在用户模式下进行这样的非法命令执行,会引发致命的保护故障。
6.5.4上下文切换
进程在运行时会依赖一些信息和数据,包括通用目的寄存器、浮点寄存器等的状态,这些进程运行时的依赖信息成为进程的上下文。而在进程进行的某些时刻,操作系统内核可以决定抢占当前的进程,并重新开始一个新的或者之前被抢占过的进程,这一过程成为调度。而抢占进程前后由于进程发生改变依赖信息也变得不同,这个过程就是上下文切换。
6.5.5Hello的执行
从Shell执行hello程序时,会先处于用户模式运行。在运行过程中,由内核不断进行上下文切换,配合调度器,与其他进程交替运行。如果在运行过程中收到了信号,那么就会陷入到内核中进入内核模式运行信号处理程序,之后再进行返回。
6.6 hello的异常与信号处理
异常可以分为四类:中断、陷阱、故障和终止
中断:在进程运行的过程中,我们施加一些I/O输入,比如说敲键盘,就会触发中断。系统会陷入内核,调用中断处理程序,然后返回。
陷阱:陷阱和系统调用是一码事,用户模式无法进行的内核程序,便通过引发一个陷阱,陷入内核模式下再执行相应的系统调用。
故障:常见的故障就是缺页故障。在hello中如果我们使用的虚拟地址相对应的虚拟页面不在内存中,就会发生此类缺页故障。故障是可能会被修复的,例如缺页故障触发的故障处理程序,会按需调动页面,再返回到原指令位置重新执行。但对于无法恢复的故障则直接报错退出。
终止:如果遇到一个硬件错误,那对于幼小的hello来说是相当致命的,导致结果就是触发致命错误,终止hello的运行
信号:信号有很多种,图中展示的是Linux上支持的几种不同类型的信号。
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结果截屏,说明异常与信号的处理。
- ctrl-z:进程收到 SIGSTP 信号, hello 进程被挂起。
- ctrl-c: 进程收到 SIGINT 信号,终止 hello。在ps中没有它的PID,在job中也没有,可以看出hello已经被永远地停止了。
- 乱按:
- Kill,杀死进程:
- Jobs:
- Pstree:
6.7本章小结
本章介绍了hello进程的执行过程。主要是hello的创建、加载和终止,通过键盘输入。在hello运行过程中,内核有选择地对其进行管理,决定何时进行上下文切换。在hello运行过程中,接受到不同的异常信号时,异常处理程序将对异常信号做出回应,执行对应指令,每种信号有不同的处理机制,对不同的异常信号,hello有不同的处理结果
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序编译后出现在汇编代码中的地址,用来指定一个操作数或一条指令的地址,由段标识符加上偏移量表示。
线性地址:地址空间的整数是连续的
虚拟地址:为每一个进程提供的私有的、大的和一致的地址空间。
物理地址:计算机系统的主存被组织成一个由连续字节大小的单元的数组。为每个字节分配唯一的物理地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
Intel平台下,逻辑地址的格式为段标识符:段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。分段机制将逻辑地址转化为线性地址的步骤如下:
1.使用段选择符中的偏移值在GDT或LDT表中定位相应的段描述符。
2.利用段选择符检验段的访问权限和范围,以确保该段可访问。
3.把段描述符中取到的段基地址加到偏移量上,最后形成一个线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
概念上而言,虚拟内存被组织为一个由存放在磁盘上的N个连续字节大小的单元组成的数组。如下图,虚拟内存被分为一些固定大小的块,这些块称为虚拟页块。这些页块根据不同的映射状态也被划分为三种状态:未分配、为缓存、已缓存。
未分配:虚拟内存中未分配的页
未缓存:已经分配但是还没有被缓存到物理内存中的页
已缓存:分配后缓存到物理页块中的页
地址转换过程:首先,CPU或内存管理单元(MMU)使用虚拟地址的页目录索引来在页目录中查找对应的页表条目。然后使用页表索引在找到的页表中查找对应的页帧条目。最后将页帧条目中的页帧地址与页内偏移组合起来,形成完整的物理地址。
而在我们的系统中,通常使用多级页表将页表分为多个级别,每个级别包含更小的表,用于对更大的地址空间进行映射。多级页表的使用可以有效地减小页表的大小,并提高地址转换的效率。页式管理允许每个进程具有独立的地址空间,并提供了虚拟内存的概念。通过页表的使用,操作系统可以将不连续的物理页面映射到连续的线性地址空间中,从而实现更高效的内存管理和地址转换。
7.4 TLB与四级页表支持下的VA到PA的变换
页表是 PTE(页表条目)的数组,它将虚拟页映射到物理页,每个 PTE 都有一个有效位和一个 n 位地址字段,有效位表明该虚拟页是否被缓存在 DRAM 中。虚拟地址分为两个部分,虚拟页号(VPN)和虚拟页面偏移量(VPO)。其中VPN需要在PTE中查询对应,而VPO则直接对应物理地址偏移(PPO)。
TLB的存在显著减少了CPU访问内存所需的时间,因为它避免了每次都需要访问慢速的内存来获取地址转换信息。通过这种方式,TLB大大提高了系统的性能。TLB译为翻译后备缓冲器,也就是页表的缓存。TLB是一个具有较高相连度的缓存,如下图。根据VPN中的TLB索引找到缓存中相应的组,根据标记(tag)找到相应的缓存行,根据设置的有效位找到对应的位置。
当一个虚拟地址(VA)需要转换为物理地址(PA)时,首先会检查TLB是否有该地址的映射;如果没有,则会通过四级页表进行查找。这个过程涉及对四级页表的逐级访问,直到找到相应的页表条目,然后使用这个条目中的物理地址信息完成地址转换。
7.5 三级Cache支持下的物理内存访问
CPU通过物理地址访问L1,L2,L3,RAM。访问L1Cache,若命中则直接从对应的Cache块中获取指令或数据,返回给CPU,否则访问L2,若命中则从Cache中读取返回给CPU,并更新L1。若L2不命中则访问L3,若命中则从Cache中读取返回给CPU,并更新L2,L1。若L3不命中则访问RAM,读取并返回给CPU,并更新L3,L2,L1。不命中则从下级存储读取并更新当前Cache,若对应组各行已满,淘汰替换相应Cache行后进行更新。
7.6 hello进程fork时的内存映射
当调用fork被Hello这个进程调用时,内核为该新进程创建了相应的数据结构并分配给它一个唯一的PID。他会创建当前hello进程的mm_struct、区域结构和页表的原样副本。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
首先删除已存在的用户区域,随后映射到私有区域。对于一些动态链接对象,会映射到共享区域。做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:程序会选择一个牺牲页,内核从磁盘复制对应的内容到DRAM中,更新对应的PTE,随后重新启动导致缺页的指令。
缺页中断处理:当缺页程序返回时CPU重新启动引起缺页的指令。
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
本章主要介绍了hello的存储器地址空间,逻辑地址到线性地址、线性地址到物理地址的变换,接着介绍了四级页表下的线性地址到物理地址的变换,分析了hello的内存映射,及缺页故障与缺页中断处理和动态存储分配管理
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
(第8章1分)
结论
尽管“Hello”程序的生命周期转瞬即逝,它的历程却充满了辉煌。从我们开始在键盘上敲击出一行行代码,创建出“hello.c”文件的那一刻起,它便开启了自己的旅程。这个文件经过预处理阶段,转化为“hello.i”,一个经过调整的源代码版本。随后,编译器将这个中间产物转换成汇编语言形式的“hello.s”。接下来,汇编器将这个汇编代码转化为可被系统识别的目标代码“hello.o”。最终,链接器将这些片段整合,生成一个能够执行的二进制文件“hello”,它能够被系统加载到内存中。一旦“hello”文件被加载,通过在命令行界面输入“./hello”,系统就会启动一系列底层调用,如fork和execve,将程序映射到虚拟内存中。在硬件与软件的协同作用下,程序得以顺畅运行,直至完成其使命。程序运行完毕后,操作系统将回收“hello”的进程资源,清空其在内存中的足迹,一切恢复到最初的状态。
随着课程的结束,我也从《深入理解计算机系统》这本厚厚的书中学到了很多之前并不了解的知识,真正看清了一个程序从被编写出来,运行时计算机底层逻辑是怎样实现其功能的。一个最简单的程序hello.c,执行的背后经过过许多复杂历程,而那些高级的程序必然更加复杂,所以我不得不感叹计算机这一人类智慧的结晶,也让我对后面对计算机软硬件的学习有了更加浓厚的兴趣。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.i: ASCII码的中间文件(预处理器产生),用于分析预处理过程。
hello.s: ASCII汇编语言文件(预处理器产生),用于分析编译的过程。
hello.o:可重定位目标程序(汇编器产生),用于分析汇编的过程。
hello:可执行目标文件(链接器产生),用于分析链接的过程。
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
(参考文献0分,缺失 -1分)