HIT-CSAPP大作业

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学   号 1180300130
班   级 1803001
学 生 李思潮  
指 导 教 师 郑贵滨

计算机科学与技术学院
2019年12月
摘 要
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

关键词:程序;计算机系统;进程;存储;周期;

Hello程序从hello.c到hello的过程中,经历了许多复杂的过程。本文从hello程序在编译时的四个过程,预处理、编译、汇编和链接、相关的进程和存储管理以及IO管理展开了分析。

目 录

第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简介
Hello的代码在编辑器里保存为文件hello.c,编译器将hello进行预处理、编译、汇编、链接,最终生成可执行文件hello。在壳(Bash)里,当OS(进程管理)调用fork(Process)或者execve或者mmap时,hello可以在Hardware(CPU/RAM/IO)上运行。在hello的运行过程中,OS(存储管理)与MMU、TLB、4级页表、3级Cache,Pagefile等完成VA到PA的转化;IO管理与信号软硬结合,使hello能在键盘、主板、显卡、屏幕正常进行。
1.2 环境与工具
Intel core i7 8gram 128g ssd,win10 vmware,ubantu,visual studio等
1.3 中间结果
Hello.c 原始c程序
Hello.i 预处理后的文版文件
Hello.s 编译生成的汇编语言文件
Hello.o 汇编后生成可重定位文件
Hello 链接后的可执行文件
Hello.txt 可执行文件hello的反汇编语言代码
Helloo.txt 可重定位文件反汇编语言代码
Hello.elf 可执行文件hello的ELF文件格式
1.4 本章小结
Hello在编译的过程中经历了如下四个阶段,也就是hello的一生,这也是本文的大体结构。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:在编译之前进行的处理。
作用:1宏定义; 2 文件包含; 3.条件编译。 预处理命令以符号“#”开头。
2.2在Ubuntu下预处理的命令
gcc hello.c -E -o hello.i

2.3 Hello的预处理结果解析
对头文件stdio.h等三个头文件的解析主要是一些文件引用声明,系统级函数的调用声明,数据类型的定义。在main函数之前,预处理器读取头文件里的内容,然后对含有#开头的宏定义继续展开,最后处理完的.i程序中没有#define。而且cpp根据#if后面的条件决定要编译的代码。
2.4 本章小结
本阶段完成了对hello.c的预处理工作。使用Ubuntu下的预处理指令可以将其转换为.i文件。完成该阶段转换后,可以进行下一阶段的汇编处理。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译是将预处理之后的程序转化为特定汇编代码的过程。
编译主要分为五个阶段:词法分析、语法分析、语义分析和中间代码的产生、优化和目标代码的形成。
编译将高级语言转为为汇编语言,为转化为机器语言做准备。
3.2 在Ubuntu下编译的命令
编译命令:gcc -S hello.i -o hello.s

可看到汇编代码变成了C代码
3.3 Hello的编译结果解析

3.3.1 全局变量与全局函数
在程序hello.c中,全局变量只有一个即 int sleepsecs = 2.5;在hello.c中,sleepsecs已经被赋值,所以编译时将其放在.data节中。如下,并且将其设置为按照4字节对称,类型为对象类型,并且将long类型值设置为2.

以及一个全局函数int main(int argc,char *argv[]);。经过编译之后,sleepsecs被存放在.rodata节中。而main函数中使用的字符串常量也被存放在数据区。其中,由于sleepsecs被定义为int型,所以为其赋初值2.5后,会进行隐式的类型转换,变为2。

3.3.2 主函数的参数
主函数的参数部分给出了int argc,char *argv[]两个参数。在汇编代码中,分别将其存放在栈中rbp寄存器指向地址-20和-32处。

3.3.3 条件判断语句及分支
接着在main函数中,使用if语句进行了条件判断。cmpl语句进行判断条件的比较。如果条件满足则继续顺序执行,调用puts输出给定字符串(这里puts是对printf的优化),然后使用参数1调用exit结束程序。
3.3.4关系操作
我们在两个地方使用了关系操作

  1. if(argc!=3)
    程序判断argc是否为3,来决定继续执行的结果
    cmpl 立即数3和 -20(%rbp)也就是argc,然后如果相等,则跳转到.L2(打印学号姓名)

  2. for(i=0;i<10;i++)
    在for循环中通过比较i和10来判断循环的终止(注意到这里面其实在编译器的优化下已经变成了对于i是否小于等于9的比较)

3.4 本章小结
本阶段完成了对hello.i的编译工作,完成了对汇编代码的解析工作。完成该阶段转换后,可以进行下一阶段的汇编处理。
第4章 汇编
4.1 汇编的概念与作用
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件hello.o中。hello.o文件是一个二进制文件,它包含的是程序的指令编码。
4.2 在Ubuntu下汇编的命令
汇编命令:gcc -c hello.s -o hello.o
在本实验中为gcc -no-pie -fno-PIC -c hello.c -o hello.o

4.3 可重定位目标elf格式
1、ELF头
头部以一个16字节的序列开始,描述生成该文件的系统的字的大小和字节顺序。剩下的部分包含帮助链接器分析语法和解释目标文件的信息,其中包含ELF头大小、目标文件的类型、及其类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

2、节头部表:包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。

3、rela.text节:一个.text节中位置的列表,包含.text节中需要重定位的信息,需要使用链接器在组合时将这些位置链接。如我们的hello.o中的getchar,exit等的重定位信息。
每一列都包含:
Offset:需要重定向文件在.text或者.data中的偏移量。
Info:包含symbol和type两部分,其中symbol占前4个字节,type占后4个字节,symbol代表重定位目标在symtab中的偏移量,type代表重定位的类型。
Type:重定向的目标类型
Sym.name:重定向到目标名称
Addend:重定向位置的辅助信息

4.4 Hello.o的结果解析
指令:objdump -d -r hello.o > hello.bjdump

1、反汇编.o文件得到的汇编代码和汇编.i文件得到的汇编代码的不同之处对比:
1) 分支转移部分:hello.s对跳转指令使用的是例如.L1这样的名称,而hello.o的反汇编中的跳转则是直接使用地址。
2) 函数调用:hello.s函数调用只写了函数名称,hello.o的反汇编中则是使用了当前指令的下一个字节(下一条指令的即地址)。原因是因为该函数延迟绑定,该函数为共享库中的函数,只有运行时,动态链接器作用后才能确定相应的PLT条目地址。
3) 汇编中mov、push、sub等指令都有表示操作数大小的后缀,比如q\l等,反汇编得到的代码中则没有。
4) 汇编代码中有很多“.”开头的伪指令用来指导汇编器和链接器工作,反汇编得到的代码中则没有。
2、机器语言与汇编语言的映射关系:一条汇编语言对应一条机器指令,其中每条机器指令的长度不一定一致(因为x86-64是CISC,指令编码长度可变)
4.5 本章小结
本章分析了hello.o的elf文件格式的信息,使用Ubuntu下的汇编指令可以将其转换为.o可重定位目标文件。并将.o文件反汇编结果与.s汇编程序代码进行比较,了解了二者之间的差别。

(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行与加载时,也就是在源代码被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。

作用:链接使得分离编译成为可能,即可以将一个大项目分解为较小的、更好管理的模块,可以单独对其进行修改和变异,最后再将其链接到一起。

5.2 在Ubuntu下链接的命令
ld -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 /usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello

5.3 可执行目标文件hello的格式
readelf -a hello > helloelf.elf将elf文件翻译输出到文件中。

在ELF格式中,节头部表(Section Headers)对hello的各个节的信息做了说明,包括各段的起始地址(Address),大小(size),类型(Type),偏移(Offset)对齐要求(Align)等信息,如下图。

链接器在链接可执行文件或动态库的过程中,它会把来自不同可重定位对象文件中的相同名称的节合并起来构成同名的节。然后把带有相同属性(比方都是只读并可加载的)的节都合并成所谓段。段作为链接器的输出,常被称为输出节。一个单独的段通常会包含几个不同的部分。
节是被链接器使用的,但是 segments是被加载器所使用的。加载器会将所需要的段加载到内存空间中运行。和用程序头表来指定一个可重定位文件中到底有哪些 部分一样。在一个可执行文件或者动态库中,也需要有一种信息结构来指出包含有哪些段。这种信息结构就是程序头表,如下图:

一共有8个段
PHDR包含程序头表本身
INTERP:只包含了一个section,在这个节中,包含了动态链接过程中所使用的解释器路径和名称。
LOAD两个:第一个是代码段,第二个是数据段。在程序运行时需要映射到虚拟空间地址。
DYNAMIC:保存了由动态链接器使用的信息。
NOTE: 保存了辅助信息。
GNU_STACK:堆栈段。
GNU_RELRO:在重定位之后哪些内存区域需要设置只读。

5.4 hello的虚拟地址空间
使用edb加载hello,使用data dump查看本进程的虚拟地址空间各段信息如下:
如图所示,代码段偏移量为0,被映射到虚拟地址0x400040,可知位于hello开头的代码(ELF)被映射到了0x400000的地方,同理PFDR位于偏移0x40,被加载到了0x400040处。

5.5 链接的重定位过程分析
使用objdump -d -r hello.out得到反汇编结果。可以明显发现该结果与hello.o的反汇编结果不同。

  1. 链接增加新的函数:在hello中链接加入了在hello.c中用到的函数,如exit、printf、sleep、getchar等函数。
    2.增加的节:hello中增加了.init和.plt节,和一些节中定义的函数。
    3.函数调用:hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。
    4.地址访问:hello.o中的相对偏移地址变成了hello中的虚拟内存地址。而hello.o文件中对于.rodata和sleepsecs等全局变量的访问,是$0x0和0(%rip),是因为它们的地址也是在运行时确定的,因此访问也需要重定位,在汇编成机器语言时,将操作数全部置为0,并且添加重定位条目。

根据hello和hello.o的不同,分析出链接的过程为:链接就是链接器ld将各个目标文件组装在一起,就是把.o文件中的各个函数段按照一定规则累积在一起,比如规则:解决符号依赖,库依赖关系,并生成可执行文件。
5.6 hello的执行流程
1.调用start函数,地址0x400500
2.调用__libc_start_main函数,地址0x400532
3.调用libc-2.27.so!__cxa_atexit 0x7fce 8c889430
4. 调用libc-2.27.so!__new_exitfn
5. 调用__libc_csu_init
6. 调用__libc_csu_init 0x4005c0
7. 调用_init函数 0x400488
8. 调用libc-2.27.so!_setjmp函数 0x7fce 8c884c10
9. 调用-libc-2.27.so!_sigsetjmp函数 0x7fce 8c884b70
10. 调用–libc-2.27.so!__sigjmp_save函数 0x7fce 8c884bd0
11. 调用main 0x400532
12. 调用puts 0x4004b0
13. 调用exit 0x4004e0
14. 调用ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce 8cc4e680
15. 调用ld-2.27.so!_dl_fixup 0x7fce 8cc46df0
16. 调用–ld-2.27.so!_dl_lookup_symbol_x 0x7fce 8cc420b0
17. 调用libc-2.27.so!exit 0x7fce 8c889128
5.7 Hello的动态链接分析
在执行函数dl_init的前后,地址0x600ff0中的值由0发生了变化。

也就是说,我们在之前重定位等一系列工作中,用到的地址都是虚拟地址,而我们需要的真实的地址信息会在程序执行的过程中用动态链接的方式加入到程序中。
5.8 本章小结
链接器的两个主要任务是符号解析和重定位,符号解析就是将目标文件中的每个全局符号都绑定到一个唯一的定义,而重定位确定每个符号的最终内存地址,并修改对那些目标的引用。

(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程的经典定义就是一个执行中程序的实例。系统中每个程序都运行在某个进程的上下文中。
进程提供给程序关键的假象:
一个独立的逻辑控制流,好像程序独立地占用处理器;一个私有的地址空间,好像我们的程序独占内存系统。
6.2 简述壳Shell-bash的作用与处理流程
shell是一个应用程序,他在操作系统中提供了一个用户与系统内核进行交互的界面。他的处理过程一般是这样的:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数
3)如果是内置命令则立即执行
4)否则调用相应的程序为其分配子进程并运行
5)shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
父进程调用fork函数创建子进程,子进程会复制父进程所有的代码,并在父进程返回一个值,如果父进程fork函数的返回值不为-1,说明子进程创建成功,fork函数会在子进程中返回0.
6.4 Hello的execve过程
Exceve函数在当前进程的上下文中加载并运行一个新的程序,它会覆盖当前进程的地址空间,但并没有创建一个新的进程。

6.5 Hello的进程执行
新进程的创建,首先在内存中为新进程创建一个task_struct结构,然后将父进程的task_struct内容复制其中,再修改部分数据。分配新的内核堆栈、新的PID、再将task_struct 这个node添加到链表中。然后将可执行文件装入内核的linux_binprm结构体。进程调用execve时,该进程执行的程序完全被替换,新的程序从main函数开始执行。调用execve并不创建新进程,只是替换了当前进程的代码区、数据区、堆和栈。在进程调用了exit之后,该进程并非马上就消失掉,而是留下了一个成为僵尸进程的数据结构,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换。进程上下文切换由以下4个步骤组成:
(1)决定是否作上下文切换以及是否允许作上下文切换。包括对进程调度原因的检查分析,以及当前执行进程的资格和CPU执行方式的检查等。在操作系统中,上下文切换程序并不是每时每刻都在检查和分析是否可作上下文切换,它们设置有适当的时机。
(2)保存当前执行进程的上下文。这里所说的当前执行进程,实际上是指调用上下文切换程序之前的执行进程。如果上下文切换不是被那个当前执行进程所调用,且不属于该进程,则所保存的上下文应是先前执行进程的上下文,或称为“老”进程上下文。显然,上下文切换程序不能破坏“老”进程的上下文结构。
(3)使用进程调度算法,选择一处于就绪状态的进程。
(4)恢复或装配所选进程的上下文,将CPU控制权交到所选进程手中。
6.6 hello的异常与信号处理
Hello在执行的过程中,可能会出现处理器外部I/O设备引起的异常,执行指令导致的陷阱、故障和终止。第一种被称为外部异常,常见的有时钟中断、外部设备的I/O中断等。第二种被称为同步异常。陷阱指的是有意的执行指令的结果,故障是非有意的可能被修复的结果,而终止是非故意的不可修复的致命错误。
在发生异常时会产生信号。例如缺页故障会导致OS发生SIGSEGV信号给用户进程,而用户进程以段错误退出。
常见信号种类:SIGINT 来自键盘的终端
SIGKILL杀死程序
SIGSEGV无效的内存引用
SIGALRM来自alarm函数的定时器信号
SIGCHLD 一个子进程停止或者终止
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
正常执行:
不加任何参数:

正常输出原格式学号姓名
加入对应的学号姓名作为参数:

每间隔2秒钟输出一行,最后等待用户输入,当用户输入结束后,hello进程结束发送SIGCHLD信号,然后被shell回收掉。
不停乱按:

不停乱按的结果是程序运行情况和前面的相同,不同之处在于shell将我们刚刚乱输入的字符除了第一个回车按下之前的字符当做getchar的输入之外,其余都当做新的shell命令,在hello进程结束被回收之后,将会在命令行中尝试解释这些命令。中间没有任何对于进程产生影响的信号被产生。
使用ctrl+Z:

使用ctrl+z之后,将会发送一个SIGTSTP信号给shell,然后shell将转发给当前执行的前台进程组,使hello进程挂起。

命令行输入ps,查看当前存在的进程中还有hello
使用ctrl+C:

6.7本章小结
本阶段通过在hello.out运行过程中执行各种操作,了解了与系统相关的若干概念、函数和功能。分析了在程序运行过程中,计算机硬件、软件和操作系统之间的配合和协作的方式。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址(LogicalAddress):逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址,其实是指由程序产生的与段相关的偏移地址部分(段内偏移量)。映射到hello.o里面的相对偏移地址。
线性地址(address space):线性地址是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。此间映射到hello里面的虚拟内存地址。
虚拟地址(Virtual Address, VA) :CPU 通过生成一个虚拟地址。映射到hello里面的虚拟内存地址。
物理地址(Physical Address,PA):物理地址用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。计算机系统的主存被组织成一个由M 个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。映射到hello在运行时虚拟内存地址对应的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理:
将虚拟内存空间和物理内存空间都划分成大小相同的页面,例如4KB、8KB和16KB等。并将页作为内存空间的最小分配单位,由于DRAM缓存是全相联的,所有任意物理页都可以包含任意虚拟页,这样就可以避免内存浪费。虚拟地址由虚拟页号(VPN)和虚拟页面偏移量(VPO)两部分组成,先根据VPN去内存中寻找该虚拟地址对应的物理地址的PPN,和VPO一起形成该虚拟地址的物理地址。如果内存中
没有对应的PPN,则触发一个缺页中断,待异常处理程序处理完后,再从内存中取出PPN,读出物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
首先虚拟地址是由VPN和VPO组成的,VPN可以作为在TLB中的索引,如上图所示,TLB可以看作是一个PTE的cache,将常用的PTE缓存到TLB中,加速虚拟地址的翻译。TLB是具有高相连度的,应该是为了一次多存一些PTE。如果能够在TLB中找到与VPN对应的PTE,即为TLB hit,TLB直接给出PPN,然后PPO即为VPO,这样就构成了一个物理地址。
如果不能做到TLB hit就要到四级页表当中取寻址,在i7中VPN有36位,被分成了四段,从左往右的前三个九位的地址分别对应于在前三级页表当中的偏移,偏移在页表中所对应的页表条目指向某一个下一级页表,而下一个9位VPN就对应的是在这个页表中的偏移。最后一级页表中的页表条目存放的是PPN
比如VPN1在第一级页表中对应于一个页表条目,这个页表条目指向下一级页表中的某个页表,再依靠VPN2在这个页表中找到它对应的页表条目,同样,这个也表条目指向的是第三级页表中的某个页表,再依靠VPN3找到在这个页表中与之对应的页表条目,这个页表条目指向的是第四级页表中的某个页表,再依靠VPN4找出与之对应的页表条目,这个页表条目中存放的是PPN,在四级页表中最多可以存放512G的内存内容,显然一般是用不了那么多的。
最后再把VPO拿来当成PPO就能找到在对应的物理页上存放的内容了。
7.5 三级Cache支持下的物理内存访问
MMU发送物理地址PA给L1缓存,L1缓存从物理地址中抽取出缓存偏移CO、缓存组索引CI以及缓存标记CT。高速缓存根据CI找到缓存中的一组,并通过CT判断是否已经缓存地址对应的数据,若缓存命中,则根据偏移量直接从缓存中读取数据并返回;若缓存不命中,则继续从L2、L3缓存中查询,若仍未命中,则从主存中读取数据。
7.6 hello进程fork时的内存映射
Linux shell下fork函数如何为每个新进程提供私有的虚拟地址空间.为新进程创建虚拟内存创建当前进程的的mm_struct, vm_area_struct和页表的原样副本.两个进程中的每个页面都标记为只读两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存随后的写操作通过写时复制机制创建新页面
7.7 hello进程execve时的内存映射
主要分四步进行:
删除已存在的用户区域
创建新的私有区域(.malloc,.data,.bss,.text)
创建新的共享区域(libc.so.data,libc.so.text)
设置PC,指向代码的入口点

7.8 缺页故障与缺页中断处理
DRAM 缓存不命中称为缺页,即虚拟内存中的字不在物理内存中。缺页导致页面出错,产生缺页异常。缺页异常处理程序选择一个牺牲页,然后将目标页加载到物理内存中。最后让导致缺页的指令重新启动,页面命中。
7.9动态存储分配管理
在程序运行时程序员使用动态内存分配器(如malloc)获得虚拟内存。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的。分配器的类型包括显式分配器和隐式分配器。前者要求应用显式地释放任何已分配的块,后者在检测到已分配块不再被程序所使用时,就释放这个块。
动态内存管理的策略包括首次适配、下一次适配和最佳适配。首次适配会从头开始搜索空闲链表,选择第一个合适的空闲块。搜索时间与总块数(包括已分配和空闲块)成线性关系。会在靠近链表起始处留下小空闲块的“碎片”。下一次适配和首次适配相似,只是从链表中上一次查询结束的地方开始。比首次适应更快,避免重复扫描那些无用块。最佳适配会查询链表,选择一个最好的空闲块,满足适配,且剩余最少空闲空间。它可以保证碎片最小,提高内存利用率。

7.10本章小结
本章通过hello的内存管理,复习了与内存管理相关的重要的概念和方法。加深了对动态内存分配的认识和了解。(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有的IO设备都被模型化为文件,而所有的输入输出都被当作对相应文件的读和写来执行。
设备管理:Linux内核的简单的低级接口–unix io接口
8.2 简述Unix IO接口及其函数
1、打开和关闭文件。进程是通过调用open函数来打开一个已存在的文件或者创建一个新文件的。会调用close函数关闭一个打开的文件。Open函数将文件名转换为一个文件描述符,并且反汇描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件: O_ RDONLY:只读, O_ WRONLY只写。O_ RDWR可读可写。访问权限由unmask来实现
2、读和写文件:引用程序通过分别调用read和write函数来执行输入和输出的。
Read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf,返回值1表示一个错误,返回值0表示EOF,否则返回值表示的是实际传送的字节数量。
Write函数从内存位置buf赋值之多n个字节到描述符fd的当前文件位置。
3、查找文件,通过调用lseek函数,应用程序能够显示地修改当前文件的位置
8.3 printf的实现分析
printf函数代码如下所示:
int printf(const char fmt, …)
{
int i;
char buf[256];
va_list arg = (va_list)((char)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
(char*)(&fmt) + 4) 表示的是…可变参数中的第一个参数的地址。而vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。接着从vsprintf生成显示信息,到write系统函数,直到陷阱系统调用 int 0x80或syscall。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数的大致实现如下:
代码:
int getchar(void)
{
char c;
return (read(0,&c,1)==1)?(unsigned char)c:EOF
}

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。然后getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
Linux把所有的I/O设备模型化为文件,并提供统一的Unix I/O接口,这使得所有的输入输出都能以一种统一且一致的方式来执行,在此之下我们了解了开、关、读、写、转移文件的接口及相关函数,并简单分析了printf和getchar函数的实现方法以及操作过程。(第8章1分)
结论
1)编辑:在编辑器中输入hello的c代码,保存为hello.c文件。
2)预处理:在预处理器中,进行展开头文件、去掉注释等一系列操作,得到hello.i文件。
3)编译:在编译器中,对hello进行逐条语句分析,将hello.c变成了汇编语言文件hello.s。
4)汇编:在汇编器中,hello.s被翻译为机器语言指令,并被打包为可重定位文件,使用hexedit可以查看这个二进制文件。
5)链接:hello调用了标准C库的一些函数,链接器将这些函数合并到hello.o程序中,结果就得到了hello程序。
6)进程创建:可以调用fork或者exceve创建hello进程,fork能让hello独立成家,而exceve则让hello浴火重生。
7)进程切换:hello在运行时会调用sleep函数,这时CPU会切换到其他进程,直到sleep结束,hello发出信号,又再经过上下文切换,回到hello程序,继续执行sleep的下一条语句。
8)虚拟地址转化为物理地址:hello在执行过程中,利用TLB、页表等,将虚拟地址转化为物理地址,读出物理页的内容。
9)内存映射:内存映射有两种对象,一种是共享对象,一种是私有对象,私有对象写时复制。Fork创建进程时,会将父进程的代码和数据都标记为私有的写时复制对象,这样父进程和子进程能相互独立运行而不会造成空间浪费。
10)缺页:如果虚拟页没有缓存在内存中,这个时候就会引发缺页故障。用从磁盘读出的虚拟页替换物理内存的牺牲页以后,再次执行产生缺页的语句。
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i 预处理产生的文本文件
hello.s 编译产生的汇编文件
hello.o 汇编产生的可重定位目标文件
hello 链接之后的可执行目标文件
helloo.elf Hello.o的ELF格式
hello.objdmp Hello的反汇编代码
hello.elf Hello的ELF格式

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 《深入理解计算机系统》Randal E.Bryant,David R.O Hallron

[2] 《printf 函数实现的深入剖析》https://www.cnblogs.com/pianist/p/3315801.html

[3] https://blog.csdn.net/Gamebot/article/details/78301714
[4] https://blog.csdn.net/jltxgcy/article/details/39233689

(参考文献0分,缺失 -1分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《计算机系统:程序员的视角(第三版,全球版,2015年7月)》是作者Randal E. Bryant和David R. O'Hallaron的经典教材,介绍了计算机系统的实现和设计方面的基本概念。这本书着重于C语言和x86-64汇编语言上,这两种语言是现代计算机编程中最常用的语言之一。本书在计算机科学和工程方面的许多领域都提供了一个坚实的基础,包括操作系统、编译器、计算机体系结构和网络,可以让读者理解计算机系统的每个层面。 本书分为三部分:程序、机器级表示和系统级表示,每个部分都逐渐深入,涵盖了计算机系统的基础知识,如处理器、存储器、缓存、虚拟内存和文件系统等方面。读者可以通过学习本书中的例子和练习来学习使用工具,例如调试器、汇编器和高级语言编译器等,在实践中掌握系统级别编程的基础知识。书中还提供了许多实用技巧和编程技巧,用于更高效和更精确地编写C程序和汇编程序。 这本书的一个显著优点是它对操作系统的原理提供了深入的覆盖范围,以及在系统级别上构建应用程序的详细说明。这使得本书非常适合计算机科学和工程领域的学生,以及想要深入了解计算机系统的程序员和系统或网络管理员。通过学习本书,读者可以充分理解系统级别编程和操作系统设计的基本原理,从而提高基础编程技能并为将来的学习和职业发展打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值