计算机系统
大作业
题 目 程序人生-Hello’s P2P
计算机科学与技术学院
2022年5月
本文主要描述了hello在Linux系统中从出生到一步一步预处理编译汇编执行等过程的详细细节,介绍了其中每一步的原理。分析了这些过程中产生的文件的相应信息和作用。并介绍了shell的内存管理、IO管理、进程管理等相关知识,了解了虚拟内存、异常信号等相关内容。
关键词:预处理;编译;汇编;链接;shell;进程管理;虚拟内存;异常信号;IO管理;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述 - 4 -
1.1 Hello简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 5 -
2.1 预处理的概念与作用 - 5 -
2.2在Ubuntu下预处理的命令 - 5 -
2.3 Hello的预处理结果解析 - 5 -
2.4 本章小结 - 5 -
第3章 编译 - 6 -
3.1 编译的概念与作用 - 6 -
3.2 在Ubuntu下编译的命令 - 6 -
3.3 Hello的编译结果解析 - 6 -
3.4 本章小结 - 6 -
第4章 汇编 - 7 -
4.1 汇编的概念与作用 - 7 -
4.2 在Ubuntu下汇编的命令 - 7 -
4.3 可重定位目标elf格式 - 7 -
4.4 Hello.o的结果解析 - 7 -
4.5 本章小结 - 7 -
第5章 链接 - 8 -
5.1 链接的概念与作用 - 8 -
5.2 在Ubuntu下链接的命令 - 8 -
5.3 可执行目标文件hello的格式 - 8 -
5.4 hello的虚拟地址空间 - 8 -
5.5 链接的重定位过程分析 - 8 -
5.6 hello的执行流程 - 8 -
5.7 Hello的动态链接分析 - 8 -
5.8 本章小结 - 9 -
第6章 HELLO进程管理 - 10 -
6.1 进程的概念与作用 - 10 -
6.2 简述壳Shell-bash的作用与处理流程 - 10 -
6.3 Hello的fork进程创建过程 - 10 -
6.4 Hello的execve过程 - 10 -
6.5 Hello的进程执行 - 10 -
6.6 hello的异常与信号处理 - 10 -
6.7本章小结 - 10 -
第7章 HELLO的存储管理 - 11 -
7.1 hello的存储器地址空间 - 11 -
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 -
7.9动态存储分配管理 - 11 -
7.10本章小结 - 12 -
第8章 HELLO的IO管理 - 13 -
8.1 Linux的IO设备管理方法 - 13 -
8.2 简述Unix IO接口及其函数 - 13 -
8.3 printf的实现分析 - 13 -
8.4 getchar的实现分析 - 13 -
8.5本章小结 - 13 -
结论 - 14 -
附件 - 15 -
参考文献 - 16 -
第1章 概述
1.1 Hello简介
- 通过文本编辑器或者IDE等编写程序,形成hello.c文件。
- 预处理器对hello.c文件进行预处理,得到hello.i文件。
- 编译器器对hello.i文件进行编译,得到汇编语言hello.s文件。
- 汇编器对hello.s文件进行汇编,得到可重定位目标文件hello.o文件。
- 链接器对hello.o文件进行链接,得到hello的可执行目标文件。
- 在shell中输入./hello,调用execve函数加载运行hello可执行文件
图:1.1.1 hello编译流程
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:2 GHz 四核Intel Core i5;6 GB 3733 MHz LPDDR4X;Macintosh HD
软件环境:macOS Monterey 12.3 64位;Parallels Desktop;Ubuntu 20.04 LTS 64位。
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
1.4 本章小结
本章大体上介绍了hello程序的一生是怎样的流程,以及中间结果和软硬件环境等。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理中会展开以#起始的行,试图解释为预处理指令,包括#if/#ifdef/#ifndef/#else/#elif/#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令) 。
作用:预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译,将系统中文件的源码或其它宏定义等插入程序,最终形成.i文件。
2.2在Ubuntu下预处理的命令
在Ubuntu下预处理的命令是 gcc -m64 -no-pie -fno-PIC hello.c -E -o hello.i
图2.2.1:Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
hello.i的部分内容如下图
图2.3.1:hello.i的部分内容
结果解析:由图可看出,hello.i文件实际上就是hello.c的内容中插入了很多系统程序代码、对宏定义进行了展开等,依旧是原始.c文件的格式,只不过内容大大增加。
2.4 本章小结
在本章中,先是具体了解了预处理的概念和作用,并实际在Ubuntu命令行实际执行了一次,分析了结果的成因。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译就是将经过预处理之后的.i程序中的高级程序语言翻译为汇编语言,生成.s文件的过程。
作用:把源程序翻译成目标程序,生成汇编语言文件,相比高级程序语言更加简单,编译器可以对.s文件进行词法分析和语法分析,检查错误。
3.2 在Ubuntu下编译的命令
Ubuntu下编译的命令包括:gcc -m64 -no-pie -fno-PIC hello.c -S -o hello.s / gcc -m64 -no-pie -fno-PIC hello.c -S -o hello.s
图3.2.1:Ubuntu下编译的命令
3.3 Hello的编译结果解析
hello的编译结果全部内容如下:
图3.3.1:hello的编译结果全部内容
(以下格式自行编排,编辑时删除)
3.3.1头部部分:
.file 声明源文件
.text 代码节
.section.rodata 只读数据段
.align 声明对指令或者数据的存放地址进行对齐的方式
.globl 声明全局变量
.type 声明一个符号是函数类型还是数据类型
3.3.2数据:
(1)字符串
这里只有两个字符串string分别是"用法: Hello 学号 姓名 秒数!\n"和"Hello %s %s\n",在.s文件中编码保存为下图:
图3.3.2:字符串数据
(2)局部变量
在hello程序中有一个局部变量i用来做计数器,在.s文件中局部变量i被保存在栈中,如下
图3.3.3:局部变量
(3)函数参数
hello程序中全局主函数有两个函数,分别是int argc,char *argv[],其中数组argv中保存的是命令行参数字符串,argc是argv中参数的个数。如图:
图3.3.4:函数参数
3.3.3赋值
hello程序中赋值操作只出现了一处,就是将i初始化为0,如下图:
图3.3.5:赋值
3.3.4算术操作
hello中算数操作也只出现了一处,就是计数器i增1(i++)操作,如下图:
图3.3.6:算术操作
3.3.5关系操作
hello中关系操作共两处,第一处是判断命令行参数个数argc是否等于4,如下:
图3.3.7:判断argc
第二处是判断计数器i是否小于8,汇编指令判断的是i是否小于等于7,如图:
图3.3.8:判断i
3.3.4数组/指针/结构操作:
- 数组
由3.3.1部分已知,程序中唯一的数组是作为主函数参数的argv数组,并且数组首地址存在了%rbp-32处。程序中对数组索引的操作为索引argv[1],argv[2]和argv[3],索引操作分别如下:
图3.3.9:索引argv[2]
图3.3.10:索引argv[1]
图3.3.11:索引argv[3]
- 指针
这里的指针操作是指:由于argv数组存储的是argc个字符串也就是argc个字符数组的首地址,也就是每个字符串的指针,在操作字符串之前需要先进行取址操作。如下:
图3.3.12:指针操作
3.3.5函数操作:
hello程序中全局主函数中调用的函数有exit()、printf()、atoi()、sleep()和getchar()。如下图,在主函数中首先将%rbp压栈,然后mov指令更新栈顶置针%rsp,之后leaq指令将上述字符串地址赋给寄存器%rdi,之后调用系统函数puts@PLT,在屏幕中打印。函数调用结束后弹栈并返回。
3.4 本章小结
介绍了编译(从.i文件到.s文件)的基本概念以及作用,并且介绍了在Ubuntu上的编译命令。之后分析了编译产生的结果的全部内容,包括数据类型以及函数操作,对汇编语言有一定的了解。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程,汇编器将.s汇编程序翻译成机器语言并将这些指令打包成可重定目标程序的格式存放在.o目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。
汇编的作用:将汇编语言翻译成机器语言,便于其在链接后能够被机器识别并执行。
4.2 在Ubuntu下汇编的命令
gcc -m64 -no-pie -fno-PIC hello.s -c -o hello.o 或 gcc -m64 -no-pie -fno-PIC hello.c -c -o hello.o
4.3 可重定位目标elf格式
4.3.1 ELF头:
如图,ELF节头包含了整个目标文件的结构信息,最上面的16个字节的Magic,含有 ELF 文件的识别标志,作为一个数组,它的各个索引位置的字节数据有固定的含义,提供一些用于解码和解析文件内容的数据,是不依赖于具体操作系统的。接下来是文件标识、编码格式、文件版本、OS ABI识别标志、ABI 版本、目标文件的类型、系统架构、版本、入口点地址、程序头起点等信息。
图4.3.1:ELF节头
4.3.2节头:
节头表是对这些数据和程序节的位置和大小的描述,主要用于链接和调试。没有节头表并不影响程序的正常执行,因为节头表没有对程序的内存布局进行描述,对程序内存描述是程序头表的任务。如图:
图4.3.2:节头
4.3.3重定位节:
回顾重定位的含义,即在链接器完成符号解析这一步之后,它就把代码中的每个符号引用和定义联系起来。在此时,链接器就知道它的输入目标文件中的代码节和数据节的确切大小。其中,需要重定位的是外部符号,全局符号和局部符号不需要重定位,符号包括静态函数或全局变量等。
如图,重定位节记录了在重定位时需要的信息,如偏移量(依赖寻址类型来寻址,通常是偏移量加当前pc寻址)、符号名和寻址类型等。得到信息后经链接就将其它目标文件或静态库或共享库联系起来,最后组成一个单一文件。hello程序中需要重定位的如图包括printf、puts、exit、sleep、sleep、atoi、getchar、.rodata和.text。
图4.3.3:重定位节
4.3.4 符号表:
符号解析步骤处理的符号会保存在符号表中,即程序中定义和引用的函数和全局变量的信息。如图:
图4.3.4:符号表
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
如下是hello.o反汇编的全部内容:
图4.4.1:反汇编全部内容
4.4.1 赋值操作:
程序中唯一的赋值操作在反汇编中如下,可以看出,和.s文件的汇编代码相同。
图4.4.2:赋值操作
4.4.2 算术操作:
算术操作如下,可以看出,同样和.s中的相同。
图4.4.3:算术操作
4.4.3 关系操作:
由3.3.5可知,程序中有两处关系操作。第一处和第二处的反汇编如下,可以看出,不同之处,在反汇编中,je jle指令直接向目的地址跳转,而不是像.s文件中将代码块跳转。
图4.4.4:关系操作
4.4.4 数组/指针/结构操作:
对照.s文件,在反汇编中的数组操作如下,可以看出,与.s文件是完全相同的。
图4.4.5:数组/指针/结构操作
4.4.5函数操作:
反汇编中的函数操作与.s文件中的最大差别是反汇编中各个系统函数与整个程序共享一个地址空间,call操作直接向函数对应地址跳转即可;而.s文件需要到程序外调用函数。
图4.4.6:函数操作
观察反汇编可以发现,机器语言是由操作码和操作数组成的,一种操作对应一个操作码,需要说明的是,操作不简单是指令,还包括其操作的对象,对象不同则也是不同的操作。而操作数就是操作需要处理的数值等。
4.5 本章小结
本章结合hello.s与hello.o文件,介绍了汇编的概念与作用,对可重定位目标文件ELF进行了详细的分析。对比并分析了hello.s和hello.o反汇编代码的异同。本章对汇编的过程进行了详细的分析。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接的概念:链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载(或复制)到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。
链接的作用:链接使得分离编成为可能。更便于我们维护管理,我们可以独立的修改和编译我们需要修改的小的模块。
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.3.1 ELF头:
如图,整体结构其余与hello.o文件的ELF头大体相同,可以看到,出现的变化包括文件类型变为可执行文件和节头起点等。其实地址如图是0x4010f0
图5.3.1:ELF节头
5.3.2节头:
hello可执行文件的节头表相比hello.o文件的节头表数量明显增多。原因是hello.o经过链接后,文件中增加了许多外部符号及其相关程序和数据。对Hello中所有的节信息进行了声明,包括size以及偏移量offset,根据节头中的信息可以通过hexedit定位各个节所占的空间的起始位置、大小等。各个起始地址如图:
图5.3.2:节头
5.3.3重定位节:
如图,地址如图中偏移量所示:
图5.3.3:重定位节
5.3.4 符号表:
符号解析步骤处理的符号会保存在符号表中,即程序中定义和引用的函数和全局变量的信息。如图分别是.dynsym和.symtab符号表:
图5.3.4:符号表
图5.3.5:符号表
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。首先,观察edb可以看到,地址空间是从0x401000~0x401fff
图5.4.1:地址空间
接下来看ELF头的起始地址0x4010f0edb中恰好对应程序开始的位置。
图5.4.2:开始位置
然后看节头,在ELF文件中看到.text起始位置恰好也为edb中程序开始的位置,且大小为0x1f5。.rodata节起始位置为0x402000,恰为edb地址空间的下一个位置。
之后看符号表中那些被符号解析的符号,举几个例子如frame_dummy,地址为0x4011d0,edb中也为该地址;如__do_global_dtors_aux,地址为0x4011a0,edb中也同样。
图5.4.3:符号
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello 的全部结果如下:
图5.5.1:hello反汇编全部内容
与hello.o反汇编的内容比较,有很多明显的区别:
- hello.o反汇编只有.text节的内容,原因也显然,因为还没有链接,还没有完成重定位。
2.hello.o反汇编.text节地址从0x0000开始,而hello反汇编从0x4010f0开始,地址空间从0x401000开始,这是由于gcc编译选项的原因。
3.hello反汇编中多出来了很多hello.o不具有的函数等,原因是经过链接重定位,使静态库或共享库的函数等与主函数共同构成了一个文件。
重定位的过程:1.首先将不同的.o可重定位文件的相同的节合并,作为可执行文件的节,并为新的节赋予地址2.根据hello.o中重定位节的偏移对外部符号等计算地址,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。
如exit函数的例子,根据在hello.o文件中可重地位节的偏移,在hello可执行文件中计算出新的地址,由于hello可重地位节中exit符号的寻址类型是R_X86_64_JUMP_SLO,即直接寻址,所以此处的偏移量就是exit()函数的地址,在反汇编中可以得到验证。
图5.5.2:exit重定位过程
5.6 hello的执行流程
<ld-2.27.so!_dl_start >0x7f6d4795fdf0;
<ld-2.27.so!_dl_init>0x7f6d4796fc10;
<hello!start>0x4010f0
<hello!main>0x4011d6
<hello!.plt+0x70>0x401090
<hello!exit@plt>0x401070
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。对于程序的静态链接有程序有任何模块更新,整个程序就要重新链接,发布给用户这样的问题。而使用动态链接很好的避免了这个问题。
对于动态共享链接库中 PIC 函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表 PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。
在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向 PLT中的代码逻辑,GOT存放的是PLT中函数调用指令的下一条指令地址
5.8 本章小结
本章大体上完整的介绍了链接的全部过程,包括其作用和概念,以及分析了可执行文件的ELF格式的内容。然后分析了链接后可执行文件的虚拟地址,分析了链接的重定位过程。最后整体的分析了hello的执行流程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程的定义就是一个执行中程序的实例,系统中的每个程序都运行在某个进程的上下文中。
进程的作用:每次运行这个可执行程序时,就会自动创建一个新的应用进程,并在这个新的应用进程的可执行上下文中自动运行这个可执行文件,应用程序也同样自动创建新的可执行进程,并在这个新进程的可执行上下文中用户可以运行自己的可执行代码或者其他应用程序。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell-bash除了能解释用户输入的命令,将它传递给内核,还可以,调用其他程序,给其他程序传递数据或参数,并获取程序的处理结果。在多个程序之间传递数据,把一个程序的输出作为另一个程序的输入。Shell-bash本身也可以被其他程序调用。总之,Shell-bash是将内核、程序和用户连接了起来。
处理流程:1.读取从键盘输入的命令2.判断命令是否正确,且将命令行的参数改造为系统调用execve() 内部处理所要求的形式3.终端进程调用fork() 来创建子进程,自身则用系统调用wait() 来等待子进程完成4.当子进程运行时,它调用execve() 根据命令的名字指定的文件到目录中查找可行性文件,调入内存并执行这个命令5.如果命令行末尾有后台命令符号& 终端进程不执行等待系统调用,而是立即发提示符,让用户输入下一条命令;如果命令末尾没有& 则终端进程要一直等待。当子进程完成处理后,向父进程报告,此时终端进程被唤醒,做完必要的判别工作后,再发提示符,让用户输入新命令。
6.3 Hello的fork进程创建过程
在终端中输入命令:./hello 120L020528 杜贺 2 。之后shell会处理该命令,判断出如果不是内置命令,则会调用fork函数创建一个新的子进程,子进程几乎但不完全与父进程相同。通过fork函数,子进程得到与父进程用户级虚拟地址空间相同的但是虚拟地址独立、PID也不相同的一份副本。
图6.3.1:fork进程过程
6.4 Hello的execve过程
execve的功能是在当前进程的上下文中加载并运行一个新程序。
hello的execve的主要过程是execve调用启动加载器来执行hello程序。其中加载器删除子进程现有的用户虚拟内存段,并创建一组新的代码、数据、堆和栈,并将新的栈和堆初始化为0。然后加载器将PC指向hello程序的起始位置,即从下条指令开始执行hello程序。
6.5 Hello的进程执行
了解进程执行首先需要理解逻辑控制流的含义,我们已知虽然同一时刻可能执行多个进程,但内核给了进程一个自己占有全部资源的假象,在该进程中有一系列程序计数器PC值,PC值的序列就是逻辑控制流。
上下文:上下文是一个进程执行需要用到的信息包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。
时间片:一个进程执行它的控制流中的一部分的任意一个时间段叫做时间片。
Hello的进程执行:在hello执行的过程中,内核可能决定暂停hello进程的执行,转而执行其它更重要的进程。这里就涉及到了上下文切换,将原本的hello进程的上下文转换为其它进程的上下文,hello进程执行的相关信息都保存在其自己的上下文里,如.text,.rodata段等。在其它进程结束后,内核又会调度hello进程,上下文切换,转而执行hello进程。同时,上下文切换的过程中涉及到内核态与用户态的转化,具体如下图:
图6.5.1:进程执行过程
6.6 hello的异常与信号处理
进程执行的过程中可能有4中异常,分别为:
(1) 中断:在程序执行过程中可能出现外部I/O设备引起的异常,或时钟中断等。
(2) 陷阱:陷阱是执行一条指令的结果,hello执行sleep函数时会出现这个异常。
(3) 故障:可能会发生缺页故障等。
(4) 终止:终止是不可恢复的错误。
Linux常见的30种信号如下:
图6.6.1:常见信号
hello进程的信号处理:
正常执行时如下图:
图6.6.2:正常执行
不停乱按如下,显然不做任何处理:
图6.6.3:不停乱按
按回车,显然,将回程保存在了缓冲区里:
图6.6.4:回车
Ctrl+Z如下,发送SIGTSTP信号,使程序暂停。运行ps jobs pstree fg kill 等命令如图:
图6.6.5:Ctrl+Z
图6.6.6:ps命令
图6.6.7:jobs进程
图6.6.8:pstree命令
图6.6.9:fg命令
图6.6.10:kill命令
Ctrl+C如下图,直接杀死进程:
图6.6.11:Ctrl+C
6.7本章小结
本章主要阐释了进程的定义与作用,介绍了shell-bash的处理流程和作用,重点分析了调用fork()创建新的进程,调用execve函数执行hello程序,以及Hello的进程执行等,最后着重分析了hello进程的异常与信号处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址是程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或一条指令的地址。它由选择符和偏移量组成。
线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址。
虚拟地址:保护模式下程序访问存储器所用的逻辑地址。
物理地址:计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。物理地址用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。最后两位涉及权限检查。
7.2 Intel逻辑地址到线性地址的变换-段式管理
(以下格式自行编排,编辑时删除)
Intel使用段式管理将逻辑地址转换为线性地址。
线性地址由两部分组成,段标识符: 段内偏移量,段标识符存储的是哪一个段,段内偏移是确定段后的段内偏移地址。
段标识符如下,也称为段描述符,其中高13位用来确定当前描述符在描述符表中的位置;TI=0,选择全局描述符表(GDT),TI=1,选择局部描述符表(LDT);RPL表示处于哪一级的内核态(0级最高):
图7.2.1:段描述符
之后,依靠段描述符,我们已经在描述符表中找到了段的基址,然后将其与段内偏移量相加即可得到线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
这里通过也是管理将线性地址(也就是虚拟地址)转换为物理地址。首先,为了更好的效果,更方便管理,系统将线性地址分为大小相同的页(通常为4KB),所以此时一共有内存/4KB个虚拟页。并通过页表将虚拟内存与物理内存之间建立映射(物理内存同样分页)。
通过页表上的PTE有效位来判断是否虚拟内存是否命中,若不命中则执行相应的缺页处理子程序;若命中,则从对应的页表条目中得到物理页号或磁盘地址,直接寻址即可。
图7.3.1:页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
若按照7.3原理,则需要一个内存大小/4KB的大小的页表,页表过大,浪费空间且处理时间过长。于是系统在MMU中设计了一个关于PTE的小的、虚拟寻址的缓存,成为翻译后备缓冲器(TLB),也被称为快表和多级页表的方式。
MMU在读取PTE时会直接通过TLB,若不命中(概率很低)则从内存中将PTE复制给TLB。
以二级页表为例,一级页表常驻内存,每个PTE指向一个二级页表。这样就可以免去对于未分配的虚拟空间的页表的处理,无关的二级页表可以不设立,而对应的一级页表为NULL即可,这样即节省空间,也降低了不命中率。
图7.4.1:多级页表
这样,就可以根据虚拟地址的对应位置,确定对应x级页表的虚拟页号,逐级指向,最后将最后一节的物理也好和VPO相连接就可以得到物理地址。
图:7.4.2VA到PA的变换
7.5 三级Cache支持下的物理内存访问
首先,三级cache的层次结构如下:
图7.5.1:三级cache
对与每一级的cache其都会有S组,E路,每路B个字节,总容量就是S*E*B。对于访问cache的物理地址(也叫做字地址)可分为三部分:标记,组索引和块偏移。通过组索引确定组号,然后利用标记来确定是组中的哪一块,最后从块中的偏移处读取。
其中,块的识别依靠的是标记和块上的tag的匹配,若匹配,则说其命中。
图7.5.2:块匹配
7.6 hello进程fork时的内存映射
在shell输入命令行后,内核调用fork函数创建子进程,为hello进程创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同(又不完全相同)的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。
7.7 hello进程execve时的内存映射
execve的内存映射流程如下:
(1)删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
(2)映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
(3)映射共享区域, hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
(4)设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
图7.7.1:execve内存映射
7.8 缺页故障与缺页中断处理
当虚拟地址不命中时,我们称之为缺页故障。最开始,CPU将虚拟地址发送给MMU,然后MMU使用内存中的页表(高级别的页表)生成PTE地址,之后在缓存中发生不命中(PTE有效位为0),因此MMU触发缺页异常,随后缺页处理程序确定物理内存中的牺牲页(若页面已经被修改,则换出到内存),然后缺页处理程序调入新的页面,并更新内存的PTE,最后,重新回到原来进程位置,再次执行缺页的指令。
图7.8.1:缺页异常与处理
7.9动态存储分配管理
动态分配器维护着一个进程的虚拟内存区域。成为堆(heap)。分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟虚拟内存片(chunk),要么是已分配的,要么是空闲的。空闲块可用来分配。空闲块保持空间,直到它显式的被应用所分配。一个已分配的块保持已分配状态,直到他被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。分配器有两种基本风格:显式分配器和隐式分配器。
任何实际的分配器都需要一些数据结构允许他来区别块边界以及区别已分配块和空闲块,大多数分配器将这些信息嵌入块本身,主要有三种方法构造分配器:
隐式空闲链表:一个块是由一个字的头部、有效载荷以及可能的一些额外的填充组成的。头部编码了这个块的大小,以及这个快是已分配还是空闲。其数据结构如下图所示。这种结构下块与块之间是通过头部的块大小隐式连接在一起,意思是我们要找到下一个块,只需要用当前块地址加上块大小即可。
图7.9.1:隐式空闲链表
显式空闲链表:因为块分配与堆块的总数呈线性关系,所以对于通用的分配器,隐式空闲链表是不适合的。显式空间链表是将空闲块组织为某种形式的显示数据结构,这种结构比隐式多了两个指针,一个指针指向前一个空闲块,一个指针指向后一个空闲块,这样做的好处是在查找空闲块时,不需要进行很多无用的查找操作(隐式查找时需要看每一个块,这里只需要看空闲块)。
图7.9.2:显示空闲链表
分离的空闲链表:这种方法是在显式空闲链表的基础上进行的优化,把所有的块大小分为一些等价类,每个等价类里都有一个链表,我们寻找空闲块时就只需要在对应大小的链表里寻找即可。
7.10本章小结
本章简述了在计算机中的虚拟内存管理,虚拟地址、物理地址、线性地址、逻辑地址的区别以及它们之间的变换模式,以及段式、页式的管理模式,在了解了内存映射的基础上重新认识了共享对象、fork和execve,同时认识了动态内存分配的方法与原理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
(2) Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。
(3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。
(4)读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
(5)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
Unix I/O函数:
(1) int open(char* filename,int flags,mode_t mode) ,进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
(2)int close(fd),fd是需要关闭的文件的描述符,close返回操作结果。
(3)ssize_t read(int fd,void *buf,size_t n),read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
(4)ssize_t wirte(int fd,const void *buf,size_t n),write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
8.3 printf的实现分析
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章主要介绍了 Linux 的 IO 设备管理方法——将所有设备映射为文件,允许linux内核引出一个简单、低级的应用接口——Unix I/O、Unix IO 接口及其函数,主要对 printf 函数和 getchar 函数的实现进行了分析。
(第8章1分)
结论
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
hello的起点是简单的文本,可以使用文本编辑器或IDE来编辑,得到一个hello.c文件。
之后,hello.c经过预处理、编译、汇编从hello.c变为hello.i,再到hello.s再到hello.s,然后到hello.o。
hello.o文件又需要经过链接静态库共享库等得到hello可执行文件。
hello才是计算机真正可以执行的,而在执行的过程中,还要变成一个进程,先要fork,然后要execve,最后还要waitpid被回收。
再看不见的地方,hello与计算机的内存建立了精密的联系,他的一切,都保存在存储中。
最后,尽管hello可以被计算机执行,但还要通过IO设备才能和我们交互,才能被我们观察,受我们控制。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
(附件0分,缺失 -1分)
hello.i 预处理器修改后的源程序,用来分析预处理器的行为
hello.s 编译器生成的编译程序,用来分析编译器行为
hello.o 可重定位目标文件,用来分析汇编器的行为
hello 可执行文件,可以运行
hello_o_elf. txt hello.o的elf的内容文本文件
hello_elf.txt hello的elf格式
hello_o_dump.txt hello.o使用objdump工具产生的反汇编代码
hello_dump.txt hello使用objdump工具产生的反汇编代码
为完成本次大作业你翻阅的书籍与网站等
[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分)
工具产生的反汇编代码
hello_dump.txt hello使用objdump工具产生的反汇编代码
为完成本次大作业你翻阅的书籍与网站等
[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分)