CSAPP大作业
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机
学 号
班 级
学 生 宋祖楠
指 导 教 师 史先俊
计算机科学与技术学院
2021年5月
摘 要
关键词:hello world、编译、内存、运行
在用户眼里看似简单的“hello world”输出,在用户看不见的地方,到底经历了什么呢?经历了“预处理”、“编译”、“汇编”、“链接”等过程,也经历在bash里运行的过程,还经历了内存的分配等过程,本文介绍了“hello world”的一生,从写代码开始,到在屏幕上输出的详细过程。
目 录
第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的P2P,020的整个过程。
P2P:首先,用一个文本编辑器编写能够输出“hello world”的文件,文件保存为hello.c;得到这个文件之后,我们要先对其进行预处理,生成一个编译器能读懂的中间文件hello.i,接下来就是用编译器将其编译生成一个ASCII汇编语言文本文件hello.s;接下来就是用汇编器将其生成一个可重定位目标二进制文件hello.o,最后用一个链接器将所需要的函数库与其链接,重定位其需要重定位的数据,生成一个可执行的目标程序,其文件格式为ELF;执行该目标文件,操作系统会使用fork函数形成一个子进程,分配相应的内存资源,包括CPU的使用权限和虚拟内存等。然后使用execve函数加载进程。
020:在CPU工作时,通过取指、译码、执行等微过程,逐步执行目标文件中的程序。同时,CPU使用流水线、进程切换等工作方式实现多进程作业。
在程序的执行过程中会使用到内存中的数据。这些数据通过各级存储,包括磁盘、主存、Cache等,并使用页表等辅助存储,实现访存的加速与虚拟内存的映像。在这个过程中还涉及操作系统的信号处理,控制进程,使得系统资源得到充分利用。而IO管理与信号处理通过软硬结合,完成程序从键盘、主板、显卡,再到屏幕的工作。当进程执行结束后,操作系统进行进程回收。
即“来时什么也没有,走时所有东西都被回收”。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件:core i7 CUP,2GHz,256SSD
软件:Windows10 64位;VirtualBox6.1;Ubuntu 20.04;
开发工具:vs code;vim;gcc;
调试工具:gdb
1.3 中间结果
hello.c:编写的输出“hello world”的c文件;
Hello.i:预处理过后的文件;
Hello.s:翻译成的ASCII汇编文本文件;
Hello.o:可重定位的目标文件;
Hello:可执行目标文件
1.4 本章小结
hello.c程序从编写、预处理、编译、汇编、链接再到执行,体现了计算机系统系统各部分的具体功能,以及它们之间的的协同合作。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理指的是在编译之前进行的处理。C语言的预处理主要有三个方面的内容:宏定义、文件包含、条件编译。预处理命令以符号“#”开头。
预处理工作也叫做宏展开,将宏名替换为文本。宏定义只需将符号常量替换成后面对应的文本即可。文件包含可以在一个文件中包含另一个文件的内容,被包含的文件称为头文件。头文件的内容可以有函数原型、宏定义、结构体定义。条件编译是在条件满足时才编译某些语句。
使用条件编译可以使目标程序变小,运行时间变短。同时有利于代码的模块化。
2.2在Ubuntu下预处理的命令
cpp hello.c hello.i
应截图,展示预处理过程!
2.3 Hello的预处理结果解析
经过预处理之后,hello.c文件转化为hello.i文件。原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。打开该文件可以发现,文件的内容增加,相对于hello.c修改了预处理命令;
2.4 本章小结
本章介绍了预处理的概念、作用以及在Ubuntu下通过gcc指令进行预处理的方法,通过对hello.c进行预处理,将hello.c与hello.i进行对比说明了预处理阶段做了怎样的处理。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译是指把用高级程序设计语言书写的源程序,C语言编译器会进行词法分析、语法分析(-fsyntax-only),接着会把源代码翻译成中间语言,即翻译成等价的汇编语言格式目标文件的过程。
作用:编译器将hello.i翻译成一个ASCII汇编语言文件hello.s
3.2 在Ubuntu下编译的命令
gcc -S hello.i
3.3 Hello的编译结果解析
通过查看hello.s文件可知:该文件存入了字符串常量“用法: Hello 学号 姓名秒数!\n”、“Hello %s %s\n”
对齐要求:8字节对齐
argc(存放在rdi中):
将argc与4比较(条件码),决定是否跳转(条件转移)
char*argv(argv地址存放在rsi中):
命令行输入的参数:
变量i(存在以rbp为基准的栈中):
将i赋初值为0
将i与7比较(条件码),并决定是否跳转(继续循环)
函数(call):
输出与退出
字符串转换为int、休眠
从命令行得到参数
printf
3.4 本章小结
本阶段完成了对hello.i的编译工作。使用Ubuntu下的编译指令可以将其转换为ASCII汇编语言文件。此外,本章通过与源文件C程序代码进行比较,完成了对汇编代码的解析工作。完成该阶段转换后,可以进行下一阶段的汇编处理。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:将ASCII汇编语言文件翻译成一个可重定位目标文件(.s 转换成 .o)
作用:得到汇编语言中的相关符号,放入相应的段中,设置重定位信息,将汇编语言翻译成机器指令
4.2 在Ubuntu下汇编的命令
as -o hello.o hello.s
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.3.1ELF头:
ELF头的结构:
typedef struct {
unsigned char e_ident[16]; /* Magic,类别,数据,版本,OS/ABI,ABI */
Elf32_Half e_type; /*ELF文件类型,REL, 可执行文件,共享目标文件等 */
Elf32_Half e_machine; /* ELF的CPU平台属性 */
Elf32_Word e_version; /* ELF版本号 */
Elf32_Addr e_entry; /* ELF程序的入口虚拟地址,REL一般没有入口地址为0 */
Elf32_Off e_phoff;
Elf32_Off e_shoff; /* 段表在文件中的偏移 */
Elf32_Word e_flags; /* 用于标识ELF文件平台相关的属性 */
Elf32_Half e_ehsize; /* 本文件头的长度 */
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize; /* 段表描述符的大小 */
Elf32_Half e_shnum; /* 段表描述符的数量 */
Elf32_Half e_shstrndx; /* 段表字符串表所在的段在段表中的下标 */
} Elf32_Ehdr;
4.3.2节头:
段是程序执行的必要组成部分,每个段中,会有代码或者数据被划分为不同的节,节头表是对这些节位置和大小的描述。用于链接和调试。没有节头表程序仍可以政策执行。因为节头没有对程序的内存布局进行描述,这是程序头表的任务。用readelf可以看到节和段的关系。
.text节是保存了程序代码指令的代码节。一段可执行程序,存在Phdr,.text就会存在于text段中。由于.text节保存了程序代码,因此节的类型为SHT_PROGBITS。
.rodata 保存只读数据。类型SHT_PROGBITS。
.plt 过程链接表(Procedure Linkage Table),包含动态链接器调用从共享库导入的函数所必须的相关代码。存在于text段中,类型SHT_PROGBITS。
.bss节保存未初始化全局数据,是data的一部分。程序加载时数据被初始化成0,在程序执行期间可以赋值,未保存实际数据,类型SHT_NOBITS。
.got节保存全局偏移表。它和.plt节一起提供了对导入的共享库函数访问的入口。由动态链接器在运行时进行修改。如果攻击者获得堆或者.bss漏洞的一个指针大小写原语,就可以对该节任意修改。类型SHT_PROGBITS。
.dynsym节保存共享库导入的动态符号信息,该节在text段中,类型SHT_DYNSYM。
.dynstr保存动态符号字符串表,存放一系列字符串,代表了符号的名称,以空字符作为终止符。
.rel节保存重定位信息,类型SHT_REL。
.hash节,也称为.gnu.hash,保存一个查找符号散列表。
.symtab节,保存了ElfN_Sym类型的符号信息,类型SHT_SYMTAB。
strtab节,保存符号字符串表,表中内容被.symtab的ElfN_Sym结构中的st_name条目引用。类型SHT_SYMTAB。
.shstrtab节,保存节头字符串表,以空字符终止的字符串集合,保存了每个节节名,如.text,.data等。有个e_shsrndx的ELF文件头条目会指向.shstrtab节,e_shstrndx中保存了.shstrtab的偏移量。这节的类型是SHT_SYMTAB。
.ctors和.dtors节,前者构造器,后者析构器,指向构造函数和析构函数的函数指针,构造函数是在main函数执行前需要执行的代码,析构是main函数之后需要执行的代码。
4.3.3重定位表:
链接器在处理目标文件时,需要对目标文件中某些部位进行重定位,及代码段和数据段中那些对绝对地址引用的位置。这些重定位的信息都记录在ELF文件的重定位表里面,对于每个需要重定位的代码段或数据段,都会有相应的重定位表。
.rel.text:当链接噐把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面调用本地函数的指令则不需要修改。注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非使用者显式地指示链接器包含这些信息。
.rel.data:被模块定义或引用的任何全局变量的信息。一般而言,任何已初始化全局变量的初始值是全局变量或者外部定义函数的地址都需要被修改。
4.3.4符号表:
符号表(symbol table)存放在程序中被定义和引用的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的表目。
4.4 Hello.o的结果解析
(1) hello.s没有机器码 反汇编代码中有机器码
(2) hello.s标出了每个函数的函数名,并且以函数名为参数实施跳转指令,反汇编代码则是用地址作为操作数实施跳转指令,并且call指令的操作数都为0,表明其需要重定位
(3) 机器语言程序的是二进制的机器指令序列集合,是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。机器指令由操作码和操作数组成。汇编语言是以人们比较熟悉的词句直接表述CPU动作形成的语言,是最接近CPU运行原理的较为通俗的比较容易理解的语言。
4.5 本章小结
本阶段完成了对hello.s的汇编工作。使用Ubuntu下的汇编指令可以将其转换为.o可重定位目标文件。此外,本章通过将.o文件反汇编结果与.s汇编程序代码进行比较,了解了二者之间的差别。完成该阶段转换后,可以进行下一阶段的链接工作。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接是处理可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息(一般是虚拟内存地址)的过程。链接又分为静态链接和动态链接,前者是程序开发阶段程序员用ld(gcc实际上在后台调用了ld)静态链接器手动链接的过程,而动态链接则是程序运行期间系统调用动态链接器(ld-linux.so)自动链接的过程。
作用:链接操作是将各种机器代码和数据的片段进行收集并组合成一个单一的链接后的文件的编译过程,这个单一文件的链接可被应用程序加载到一个内存并加载和执行。
5.2 在Ubuntu下链接的命令
ld -o /home/szn1190200321/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 /home/szn1190200321/hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
5.3 可执行目标文件hello的格式
readelf -S hello
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
虚拟空间从0x400000开始。
5.5 链接的重定位过程分析
重定位:链接器在完成符号解析以后,就把代码中的每个符号引用和正好一个符号定义关联起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小。然后就可以开始重定位步骤了,在这个步骤中,将合并输入模块,并为每个符号分配运行时的地址。。然后是重定位节中的符号引用,链接器会修改hello中的代码节和数据节中对每一个符号的引用,使得他们指向正确的运行地址。
可以看到这次的汇编代码中call时的地址都变为了绝对地址,不再是最初的函数名字或相对地址。而且多了很多通过链接加进来的函数的源代码,如printf等。
5.6 hello的执行流程
ld-2.27.so!_dl_start—
ld-2.27.so!_dl_init—
hello!_start—0x400550
hello!_init—0x4004c0
hello!main—0x400582
hello!puts@plt–0x4004f0
hello!exit@plt–0x400530
hello!printf@plt–0x400500
hello!sleep@plt–0x400540
hello!getchar@plt–0x400510
libc-2.27.so!exit+0-0x40530
5.7 Hello的动态链接分析
共享链接库代码是一个动态的目标模块,在程序开始运行或者调用程序加载时,可以自动加载该代码到任意的一个内存地址,并和一个在目标模块内存中的应用程序链接了起来,这个过程就是对动态链接的重定位过程。
动态的链接器在正常工作时链接器采取了延迟绑定的链接器策略,由于静态的编译器本身无法准确预测变量和函数的绝对运行时地址,动态的链接器需要等待编译器在程序开始加载时再对编译器进行延迟解析,这样的延迟绑定策略称之为动态延迟绑定。got链接器叫做全局变量过程偏移链接表,在plt和got中分别存放着链接器的目标变量和函数的运行时地址。一个动态的链接器通过静态的过程偏移链接表plt+got链接器实现了函数的一个动态过程链接,这样一来,它就已经包含了正确的绝对运行时地址。
5.8 本章小结
本章介绍了链接的概念、作用以及在Ubuntu下通过ld指令进行链接的方法,通过对hello.o进行链接得到可执行目标文件hello,分析hello的ELF格式,并动态链接的实现进行分析。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。
6.2 简述壳Shell-bash的作用与处理流程
Shell是“提供使用者使用界面”的软件,是一个命令行解释器,作用是:保护内核同时帮助使用者向计算机传递交互信息。
处理流程:
用户输入命令或程序代码。
Shell解析输入信息。
若为内置命令,调用内置命令处理函数,否则调用fork( )创建子进程。
若为前台运行程序,调用wait()直至前台作业结束,如果为后台命令则,则不等待。
6.3 Hello的fork进程创建过程
判断./hello不是一个内置的shell指令,所以调用应用程序,找到当前所在目录下的可执行文件hello,准备执行。Shell会自动的调用fork()函数为父进程创建一个新的子进程,子进程就会因此得到与父进程虚拟地址空间相同的一段各种的数据结构的副本。父进程与子进程最大的不同在于他们分别拥有不同的PID,父进程与子进程分别是两个并发的进程,在子进程中程序运行的这个过程中,父进程在原位置等待着程序的运行完毕。
6.4 Hello的execve过程
exceve函数在当前进程的上下文中加载并运行一个新程序。exceve函数加载并运行可执行目标文件,并带参数列表和环境变量列表。只有当出现错误时,exceve才会返回到调用程序,否则,exceve调用一次且从不返回。在exceve加载了可执行目标文件后,他调用启动代码,启动代码设置栈,将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。
6.5 Hello的进程执行
新进程的创建,首先在内存中为新进程创建一个task_struct结构,然后将父进程的task_struct内容复制其中,再修改部分数据。分配新的内核堆栈、新的PID、再将task_struct 这个node添加到链表中。然后将可执行文件装入内核的linux_binprm结构体。进程调用execve时,该进程执行的程序完全被替换,新的程序从main函数开始执行。调用execve并不创建新进程,只是替换了当前进程的代码区、数据区、堆和栈。在进程调用了exit之后,该进程并非马上就消失掉,而是留下了一个成为僵尸进程的数据结构,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换。进程上下文切换由以下4个步骤组成:决定是否作上下文切换以及是否允许作上下文切换。包括对进程调度原因的检查分析,以及当前执行进程的资格和CPU执行方式的检查等。在操作系统中,上下文切换程序并不是每时每刻都在检查和分析是否可作上下文切换,它们设置有适当的时机。保存当前执行进程的上下文。这里所说的当前执行进程,实际上是指调用上下文切换程序之前的执行进程。如果上下文切换不是被那个当前执行进程所调用,且不属于该进程,则所保存的上下文应是先前执行进程的上下文,或称为“老”进程上下文。显然,上下文切换程序不能破坏“老”进程的上下文结构。使用进程调度算法,选择一处于就绪状态的进程。恢复或装配所选进程的上下文,将CPU控制权交到所选进程手中。
6.6 hello的异常与信号处理
Hello在执行的过程中,可能会出现处理器外部I/O设备引起的异常,执行指令导致的陷阱、故障和终止。第一种被称为外部异常,常见的有时钟中断、外部设备的I/O中断等。第二种被称为同步异常。陷阱指的是有意的执行指令的结果,故障是非有意的可能被修复的结果,而终止是非故意的不可修复的致命错误。
在发生异常时会产生信号。例如缺页故障会导致OS发生SIGSEGV信号给用户进程,而用户进程以段错误退出。常见X信号:SIGINT信号、SIGSTP信号按下ctrl-z后,shell父进程收到SIGSTP信号,信号处理程序将hello进程挂起,放到后台,停止hello程序按下ctrl-c后,shell父进程收到SIGINT信号,由信号处理函数结束hello,并回收hello进程。
6.7本章小结
本章介绍了进程的概念和作用,介绍了shell如何fork、execve执行hello,系统如何在用户态与内核态间切换以处理运行时信号发送带来的中断。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
虚拟地址(逻辑地址):又称相对地址,是程序运行由CPU产生的与段相关的偏移地址部分。他是描述一个程序运行段的地址。在每个进程创建加载时,内核只是为进程"创建"了虚拟内存的布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即就把虚拟内存对应位置的程序数据和代码拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射就好,等到运行到对应的程序时,才会通过缺页异常,来拷贝数据。还有进程运行过程中,要动态分配内存,比如 malloc时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。有时我们也把逻辑地址称为虚拟地址。。
物理地址:程序运行时加载到内存地址寄存器中的地址,内存单元的真正地址。他是在前端总线上传输的而且是唯一的。在 hello程序中,他就表示了这个程序运行时的一条确切的指令在内存地址上的具体哪一块进行执行。.
线性地址:是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。高端内存不能全部映射到内核空间,也就是说这些物理内存没有对应的线性地址。不过,内核为每个物理页框都分配了对应的页框描述符,所有的页框描述符都保存在mem.map,数组中,因此每个页框描述符的线性地址都是固定存在的。
7.2 Intel逻辑地址到线性地址的变换-段式管理
每个段的首地址就会被储存在各自的段描述符里面,所以的段描述符都将会位于段全局描述符表中,通过段选择符我们可以快速寻找到某个段的段全局描述符。逻辑上段地址的偏移量结构就是段选择符+偏移量。
段选择符的索引位组成和定义如下,分别指的是索引位,ti,rpl,当索引位ti=0时,段描述符表在rpgdt中,ti=1时,段描述符表在rpldt中。而索引位index就类似一个数组,每个元素内都存放一个段的描述符,索引位首地址就是我们在查找段描述符时再这个元素数组当中的索引。一个段描述符的首地址是指含有8个元素的字节,我们通常可以在查找到段描述符之后获取段的首地址,再把它与线性逻辑地址的偏移量进行相加就可以得到段所需要的一个线性逻辑地址。
在分段保护模式下,分段有两种机制:段的选择符在段的描述符表->分段索引->目标段的段描述符条目->目标段的描述符基地址+偏移量=转换为线性段的基地址。由于现代的macosx86系统内核使用的描述符是基本扁平的逻辑模型,即目标段的逻辑地址=线性段的描述符=转换为线性段的基地址,等价于描述符转换为线性地址时关闭了偏移量和分段的功能。这样逻辑段的基地址与转换为线性段的基地址就合二为一了。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址(虚拟地址)由虚拟页号VPN和虚拟页偏移VPO组成。首先,MMU从线性地址中抽取出VPN,并且检查TLB,看他是否因为前面某个内存引用缓存了PTE的一个副本。TLB从VPN中抽取出TLB索引和TLB标记,查找对应组中是否有匹配的条目。若命中,将缓存的PPN返回给MMU。若不命中,MMU需从页表中的PTE中取出PPN,若得到的PTE无效或标记不匹配,就产生缺页,内核需调入所需页面,重新运行加载指令,若有效,则取出PPN。最后将线性地址中的VPO与PPN连接起来就得到了对应的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
虚拟地址VA虚拟页号VPN和虚拟页偏移VPO组成。若TLB命中时,所做操作与7.3中相同;若TLB不命中时,VPN被划分为四个片,每个片被用作到一个页表的偏移量,CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1 PTE的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2 PTE的偏移量,依次类推。最后在L4页表中对应的PTE中取出PPN,与VPO连接,形成物理地址PA。
7.5 三级Cache支持下的物理内存访问
首先CPU发出一个虚拟地址,在 TLB里面寻找。如果命中,那么将PTE发送给L1Cache,否则先在页表中更新PTE。然后再进行Ll根据PTE寻找物理地址,检测是否命中的工作。这样就能完成Cache和TLB的配合工作。﹒
L1的寻址一般是组相联的模式,通过组索引部分可以确定在cache里的哪一组,通过标记位确定看是否与组里的某一行标记相同,如果有,通过块偏移位确定具体是哪个数据块,从而得到我们的数据。如果没有找到,则需要从主存里去找数据,并选择 cache 里的一行替换,对于L1,L2这样的组相联cache,替换策略通常有LFU,LRU。
7.6 hello进程fork时的内存映射
shell通过一个调用用fork的函数可以让进程内核自动创建一个新的进程,这个新的进程拥有各自新的数据结构,并且被内核分配了一个唯一的pid。它有着自己独立的虚拟内存空间,并且还拥有自己独立的逻辑控制流,它同样可以拥有当前已经可以打开的各类文件信息和页表的原始数据和样本,为了有效保护进程的私有数据和信息,同时为了节省对内存的消耗,进程的每个数据区域都被内核标记起来作为写时复制。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行新程序hello.out的步骤:删除已存在的用户区域,创建新的区域结构,代码和初始化数据映射到.text和.data区(目标文件提供),.bss和栈映射到匿名文件,设置PC,指向代码区域的入口点。Linux根据需要换入代码和数据页面。
7.8 缺页故障与缺页中断处理
DRAM 缓存不命中称为缺页,即虚拟内存中的字不在物理内存中。缺页导致页面出错,产生缺页异常。缺页异常处理程序选择一个牺牲页,然后将目标页加载到物理内存中。最后让导致缺页的指令重新启动,页面命中。
7.9动态存储分配管理
在程序运行时程序员使用动态内存分配器(如malloc)获得虚拟内存。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的。分配器的类型包括显式分配器和隐式分配器。前者要求应用显式地释放任何已分配的块,后者在检测到已分配块不再被程序所使用时,就释放这个块。
动态内存管理的策略包括首次适配、下一次适配和最佳适配。首次适配会从头开始搜索空闲链表,选择第一个合适的空闲块。搜索时间与总块数(包括已分配和空闲块)成线性关系。会在靠近链表起始处留下小空闲块的“碎片”。下一次适配和首次适配相似,只是从链表中上一次查询结束的地方开始。比首次适应更快,避免重复扫描那些无用块。最佳适配会查询链表,选择一个最好的空闲块,满足适配,且剩余最少空闲空间。它可以保证碎片最小,提高内存利用率。
7.10本章小结
本章分析了hello的存储管理,主要涉及内存。介绍了Intel逻辑地址到线性地址的变换-段式管理,以及TLB与多级页表支持下的VA到PA的转换,同时对三级Cache支持下的物理内存访问做了说明。简述了hello的fork和execve内存映射,了解了缺页故障与缺页中断处理程序,介绍了动态存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
文件的类型:
普通文件:包含任意数据的文件。
目录:包含一组链接的文件,每个链接都将一个文件名映射到一个文件。
套接字:用来与另一个进程进行跨网络通信的文件
命名通道
符号链接
字符和块设备
设备管理:unix io接口
打开和关闭文件
读取和写入文件
改变当前文件的位置
8.2 简述Unix IO接口及其函数
打开文件——open():open函数将file那么转换为一个文件描述符并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。
关闭文件——close():当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
读取文件——read():read函数从描述符为fd 的当前文件位置复制最多n个字节到内存位置buf。返回值一1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。
写入文件——write():write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
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的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。然后getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章节讲述了一下linux的I/O设备管理机制,了解了开、关、读、写、转移文件的接口及相关函数,简单分析了printf和getchar函数的实现方法以及操作过程。
(第8章1分)
结论
Hello的一生从写高级语言程序.c文件开始:
预处理:解析替换#开头的语句,生成代码文件hello.i
编译:将代码文件翻译成汇编语言,生成hello.s文件。
汇编:将汇编语言转换成机器语言,生成可重定位文件 hello。
链接:由链接器将可重定位文件链接,生成可执行文件 hello.
创建进程、加载程序: shell收到运行./hello的指令之后,通过 fork创建子进程,并在其中由 execve.创建虚拟内存空间映射、调用高速缓存与缺页处理将hello加载进内存与cpu。"
执行命令:cpu取指令,控制 hello的逻辑流进行运行,其间调用printf、getchar等函数调用IO设备,进行屏幕的显示和键盘读入。
异常处理:对于运行程序时键盘输入的ctrl-c、ctrl-z等指令系统中断并调用相应的信号处理程序进行处理.
Hello的一生结束: hello程序结束后由父进程回收子进程占用的资源,hello的痕迹从内存消失。挥一挥手,不带走一片云彩。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
|
中间结果文件 |
文件的作用 |
|
hello.i |
预处理后的文本文件 |
|
hello.s |
编译后汇编程序文本文件 |
|
hello.o |
汇编后的可重定位目标程序 |
|
hello |
链接后的可执行目标文件 |
|
h_elf.txt |
hello的ELF格式 |
|
ho_elf.txt |
Hello.o的ELF格式 |
|
h_fhb.txt |
hello的反汇编文件 |
|
Ho_fhb.txt |
Hello.o的反汇编文件 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 程序运行之ELF 符号表 https://www.cnblogs.com/zhanghongfeng/p/9074505.html
(参考文献0分,缺失 -1分)
浙公网安备 33010602011771号