计算机科学与技术学院
2021年5月
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
关键词:关键词:hello,计算机系统 ,csapp
摘要:通过hello的编译再次回顾计算机系统课程,深入理解和进一步巩固已学过的知识点。
目 录
2.1 预处理的概念与作用........................................................................... - 5 -
2.2在Ubuntu下预处理的命令.................................................................. - 5 -
2.3 Hello的预处理结果解析...................................................................... - 5 -
3.1 编译的概念与作用............................................................................... - 6 -
3.2 在Ubuntu下编译的命令..................................................................... - 6 -
3.3 Hello的编译结果解析.......................................................................... - 6 -
4.1 汇编的概念与作用............................................................................... - 7 -
4.2 在Ubuntu下汇编的命令..................................................................... - 7 -
4.3 可重定位目标elf格式........................................................................ - 7 -
5.1 链接的概念与作用............................................................................... - 8 -
5.2 在Ubuntu下链接的命令..................................................................... - 8 -
5.3 可执行目标文件hello的格式............................................................. - 8 -
5.4 hello的虚拟地址空间........................................................................... - 8 -
5.5 链接的重定位过程分析....................................................................... - 8 -
5.7 Hello的动态链接分析.......................................................................... - 8 -
第6章 hello进程管理............................................................................. - 10 -
6.1 进程的概念与作用............................................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程.............................................. - 10 -
6.3 Hello的fork进程创建过程............................................................... - 10 -
6.4 Hello的execve过程........................................................................... - 10 -
6.6 hello的异常与信号处理..................................................................... - 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 -
第8章 hello的IO管理........................................................................... - 13 -
8.1 Linux的IO设备管理方法................................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................................. - 13 -
8.4 getchar的实现分析............................................................................. - 13 -
第1章 概述
1.1 Hello简介
在codeblocks中构造程序Hello.c,为源程序。对Hello.c这一源程序依次进行预处理,编译,汇编,链接的操作之后,生成可执行程序Hello。然后调用shell命令行输入,fork产生子进程,Hello从program成为Process,这个过程即为P2P(Program to process)。紧接着调用程序execve,通过依次进行对虚拟内存的映射,物理内存的载入,进入主函数执行代码。hello调用write等系统函数在屏幕打印信息,之后shell父进程回收Hello的进程,一切相关的数据结构被删除。即为020(From Zero to Zero)的全部过程。
1.2 环境与工具
硬件 X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件 Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64.位/优麒麟 64位
工具 gcc vim objdump gdb hexedit
1.3 中间结果
hello.i hello.c经预处理器修改后的文本文件
hello.s hello.i编译后的汇编文件
hello.o hello.s汇编后的可重定位目标文件
hello hello.o链接后的可执行目标文件
hello.out 反汇编hello得到的可重定位文件
1.4 本章小结
简单的介绍了P2P,020等程序的过程,以及简要的概括了本次实验所需要的硬件软件,及所生成的文本。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:
预处理器cpp根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的所有库展开合并成为一个完整的文本文件。
所谓预处理,就是把程序中的宏展开,把头文件的内容展开包含进来,预处理不会生成文件,所以需要重定向.预处理器不止一种,而C/C++的预处理器就是其中最低端的一种——词法预处理器,主要是进行文本替换、宏展开、删除注释这类简单工作。预处理之后,得到的仅仅是真正的源代码;
作用:
宏定义指令
如 #define a b 对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。
条件编译指令
如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
头文件包含指令
如#include "FileName"或者#include 等。 该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
特殊符号
预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
2.2在Ubuntu下预处理的命令
预处理命令为: gcc hello.c -E -o hello.i
2.3 Hello的预处理结果解析
2.4 本章小结
本章介绍了预处理的相关概念及其所进行的一些处理,例如实现将定义的宏进行符号替换、引入头文件的内容等操作
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:
编译器ccl将修改后的源程序文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序, 这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后后,把代码翻译成汇编语言
作用:
1.扫描(词法分析),2.语法分析,3.语义分析,4.源代码优化(中间语言生成),5.代码生成,目标代码优化。
1将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号
2基于词法分析得到的一系列记号,生成语法树。
3由语义分析器完成,指示判断是否合法,并不判断对错。
4中间代码(语言)使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码,目的:一个前端对多个后端,适应不同平台。
5编译器后端主要包括:代码生成器:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等
目标代码优化器:选择合适的寻址方式,左移右移代替乘除,删除多余指令。
3.2 在Ubuntu下编译的命令
命令 gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
编译结果
3.3.1数据
1,int i:
i为局部变量,存储在-4(%rbp)中
2.数组:
argc存储在-20(%rbp)中argv首地址是-32(%rbp)
3.字符串存储在只读区域
4.加法y=y+x:addq x y
3.3.2函数
函数传参需要先设定寄存器,参数的数目多于6个,则其他的则采用栈的方式传递。在通过call来跳转到调用的函数开头地址。printf转换成了puts,把.LC0段的立即值传入的%edi中,然后call跳转到puts。exit是把立即数1传入了%edi中,然后call跳转到exit。第二个printf有三个参数,然后call跳转到printf。接下来是sleep,它有一个参数传到%edi中, call跳转sleep。getchar是不需要参数的,直接call跳转
getchar
传递控制:call getchar。
函数调用:在main中被调用。
3.4 本章小结
本章介绍了预处理阶段的相关概念、定义、应用以及方法,说明了预处理过程中对头文件stdio.h的解析、对头文件stdlib.h的解析、对头文件unistd.h的解析
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:
汇编器as将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。
作用:
汇编器是将汇编代码转变成可以执行的指令,生成 目标文件。
4.2 在Ubuntu下汇编的命令
命令 gcc -c -m64 -no-pie -fno-PIC hello.s -o hello.o
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
指令如下 readelf -a hello.o
elf头
位于elf文件的头部,里面存储着一些机器和该ELF文件的基本信息。
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值
ELF头如下
节头部表如下
记录了各个节的名称,类型,地址,偏移量和权限
重定位节如下
R_X86_64_PC32 :重定位一个使用32位PC相对地址的引用。
R_X86_64_32 :重定位一个使用32位PC绝对地址的引用。
符号表条目如下
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
反汇编代码不仅包含汇编代码,还有机器代码(二进制代码)
1 反汇编分支转移中后面跟的是地址,而hello.s分支转移跟段标签。
2 .s文件中call后面是函数名的符号,反汇编call的后面是待重定位的相对地址
3 hello.s文件全局变量访问有具体地址而在反汇编代码中没有
4.5 本章小结
本章分析了hello.o的elf格式,将反汇编和.s文件进行了对比
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:
链接就是将不同部分的代码和数据收集和组合成为一个单一文件的过程,这个文件可被加载或拷贝到内存并执行。
链接可以执行于编译时,源代码被编译成机器代码时;也可以执行于加载时,在程序被加载器加载到内存并执行时;也可以执行于运行时,由应用程序来执行。
作用:
符号解析 将符号定义与引用对应起来,这里的符号可以是一个函数,一个全局变量,一个静态变量
重定位 因为链接器和汇编器生成的是从0字节开始的代码段,链接器首先对把每一个符号定义与一个具体的内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使他们指向这个内存位置,
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等列出其各段的基本信息,包括各段的起始地址,大小等信息。
ELF头
节头部表
重定位节
符号表
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
hello的虚拟地址空间开始于0x400000,结束与0x400ff0
.txt,虚拟地址开始于0x400550,大小为0x132
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
可重定位文件
其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
共享的目标文件
静态链接库和动态链接库,在生成可执行程序和其他共享代码库的时候,它们的链接方式不同。
可执行文件
它包含了一个可以被操作系统创建一个进程来执行之的文件。
hello反汇编的代码有确定的虚拟地址,也就是说已经完成了重定位,而hello.o反汇编代码中代码的虚拟地址均为0,未完成可重定位的过程
hello反汇编的代码中多了很多的节以及很多函数的汇编代码
重定位过程
重定位节和符号定义链接器将所有类型相同的节合并在一起后,把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,完成后,程序中每条指令和全局变量都有唯一运行时的地址。
链接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
当编译器遇到对最终位置未知的目标引用时会生成一个重定位条目
5.6 hello的执行流程
5.7 Hello的动态链接分析
动态链接的基本思想是:程序按照模块拆分成各个相对独立部分,在程序运行时将它们链接在一起形成一个完整的程序,不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时,还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。
延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:
PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
puts&plt函数如下
当程序调用一个动态链接库内定义的函数时,call指令并没有直接跳转到对应的函数中,取而代之的是控制流会跳转到该函数对应的PLT表,然后通过PLT表将当前将要调用的函数序号压入栈中,下一步调用动态链接器,接下来动态链接器会根据栈中信息执行重定位,将真实的printf的运行地址写入GOT表,取代GOT原来用来跳转到PLT的地址,变成了真正的函数地址。
5.8 本章小结
本章对hello可执行程序的的elf文件的各个部分进行了分析,深入分析了 hello的执行过程
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念
进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般情 况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数 据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动 过程调用的指令和本地变量。
作用:
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。
Bash是一个命令处理器,通常运行于文本窗口中,并能执行用户直接输入的命令。Bash还能从文件中读取命令,这样的文件称为脚本。和其他Unix shell 一样,它支持文件名替换(通配符匹配)、管道、here文档、命令替换、变量,以及条件判断和循环遍历的结构控制语句。
处理流程:
1: 输入命令。
2 获取命令行参数,构造传递给execve的argv向量
3 shell对用户输入命令进行解析,判断是否为内置命令。
4 若为内置命令,调用内置命令处理函数,否则调用execve函数创建一个子进程进行运行。
5 判断是否为前台运行程序,如果是,则调用等待函数等待前台作业结束;否则将程序转入后台,开始输入命令。
6.3 Hello的fork进程创建过程
终端程序通过调用fork()函数创建一个子进程,当shell运行一个程序时,父进程通过fork函数生成这个程序的进程。新创建的子进程几乎但不完全与父进程相同,包括代码、数据段、堆、共享库以及用户栈。父进程和新创建的子进程之间最大的区别在于他们有不同的PID。父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的 逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
6.4 Hello的execve过程
在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。
#include
函数定义 int execve(const char *filename, char *const argv[ ], char *const envp[ ]);
返回值 函数执行成功时没有返回值,执行失败时的返回值为-1.
函数说明 execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
具体步骤
1.删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.创建新的区域结构,代码和初始化数据映射到.text和.data区,.bss和栈堆映射到匿名文件。
3.映射共享区域。如果a.out程序与共享对象链接,那么这些对象都是动态链接到这个程序的,再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)指向代码区域的入口点。
6.5 Hello的进程执行
进程提供给应用程序的抽象:
(1) 一个独立的逻辑控制流,它提供一个假象,好像我们的进程独占的使用处理器
(2) 一个私有的地址空间,它提供一个假象,好像我们的程序独占的使用CPU内存。
hello进程的执行是依赖于进程所提供的抽象的基础上,下面阐述操作系统所提供的的进程抽象:
逻辑控制流::一系列程序计数器 PC 的值的序列叫做逻辑控制流,进程是轮流 使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占 (暂时挂起),然后轮到其他进程。
并发流:一个逻辑流的执行时间与另一个流重叠,成为并发流,这两个流成为并发的运行。多个流并发的执行的一般现象成为并发。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
私有地址空间:进程为每个流都提供一种假象,好像它是独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个内存字节是不能被其他进程读或者写的,在这个意义上,这个地址空间是私有的。
用户模式和内核模式::处理器通常使用一个寄存器提供两种模式的区分,该寄 存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中, 用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的 代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任 何命令,并且可以访问系统中的任何内存位置。
上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由 通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内 核数据结构等对象的值构成。
上下文切换:当内核选择一个新的进程运行时,则内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程:
(1) 保存以前进程的上下文
(2)恢复新恢复进程被保存的上下文,
(3)将控制传递给这 个新恢复的进程 ,来完成上下文切换。
6.6 hello的异常与信号处理
可能出现的异常
中断:外部I/O设备引起的异常。
陷阱:陷阱是有意的异常,是执行指令的结果,提供一个系统调用
故障:可能会发生缺页故障。
终止:终止时不可恢复的错误DRAM或者SRAM位损坏的奇偶错误。
正常执行
ctrl-z 发送一个SIGTSTP信号给前台进程组的每个进程,结果是停止前台作业
Ctrl+C 内核发送一个SIGINT信号给到前台进程组中的每个进程,结果是终止前台进程
随机乱按 将屏幕的输入缓存到 stdin,当 getchar 的时候读出一个’\n’结尾的字串,其他字串会当做 shell 命令行输入。
6.7本章小结
对shell,fork,exerce的详细功能进行了解释,对命令行的各种错误输入进行了分析验证
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
线性地址:
线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
逻辑地址:
程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。
物理地址:
在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址,又叫实际地址或绝对地址
虚拟地址:
虚拟地址是程序运行在保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址。虚拟地址通过cpu的转换才能对应到物理地址,而且每次运行程序时,操作系统都会从新安排虚拟地址和物理内存的对应关系。虚拟地址通过设定的内存映射机制找到对应的物理内存。
7.2 Intel逻辑地址到线性地址的变换-段式管理
1、逻辑地址=段选择符+偏移量
2、每个段选择符大小为16位,段描述符为8字节(注意单位)。
3、GDT为全局描述符表,LDT为局部描述符表。
4、段描述符存放在描述符表中,也就是GDT或LDT中。
5、段首地址存放在段描述符中。
每个段的首地址都存放在自己的段描述符中,而所有的段描述符都存放在一个描述符表中(描述符表分为全局描述符表GDT和局部描述符表LDT)。而要想找到某个段的描述符必须通过段选择符才能找到。
具体的转换步骤如下:
给定一个完整的逻辑地址[段选择符:段内偏移地址。
看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。可以得到一个数组。
取出段选择符中前13位,在数组中查找到对应的段描述符,得到Base,也就是基地址。
线性地址 = Base + offset。
7.3 Hello的线性地址到物理地址的变换-页式管理
在保护模式下,控制寄存器CR0中的最高位PG位控制分页管理机制是否生效。如果PG=1,分页机制生效,把线性地址转换为物理地址。如果PG=0,分页机制无效,线性地址就直接作为物理地址。必须注意,只有在保护方式下分页机制才可能生效。只有在保证使PE位为1的前提下,才能够使PG位为1,否则将引起通用保护故障。
分页机制把线性地址空间和物理地址空间分别划分为大小相同的块。这样的块称之为页。通过在线性地址空间的页与物理地址空间的页之间建立的映射,分页机制实现线性地址到物理地址的转换。线性地址空间的页与物理地址空间的页之间的映射可根据需要而确定,可根据需要而改变。线性地址空间的任何一页,可以映射为物理地址空间中的任何一页。
采用分页管理机制实现线性地址到物理地址转换映射的主要目的是便于实现虚拟存储器。不象段的大小可变,页的大小是相等并固定的。根据程序的逻辑划分段,而根据实现虚拟存储器的方便划分页。
系统将每个段分割为被称为虚拟页的大小固定的块来作为进行数据传输的单元,类似地,物理内存也被分割为物理页,虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
将VPN分成三段,对于TLBT和TLBI来说,可以在TLB中找到对应的PPN,但是有可能出现缺页的情况,这时候就需要到页表中去找。此时,VPN被分成了更多段(这里是4段)CR3是对应的L1PT的物理地址,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过4层寻址之后找到相应的PPN和VPO拼接起来
7.5 三级Cache支持下的物理内存访问
对于一个虚拟地址请求,首先去TLB寻找,看是否已经在TLB中缓存。如果命中的话就直接MMU获取,没有命中的话就结合多级页表得到物理地址. 将物理地址拆分成CT+CI+CO,然后一级cache内部找,先找组索引,然后与标志位对比。如果未能寻找到标记位相同并且有效位为1的字节的话就去二级和三级cache中寻找对应的字节,最后返回结果。
7.6 hello进程fork时的内存映射
hello被当前进程调用,内核为新进程创建各种数据结构,分配给它唯一的pid。为了给新内存创建虚拟内存,创建了当前进程mm_struct、区域结构和页表的原样副本。两个进程的每个页面都标记为只读页面;两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制。当需要写时,则需要再创建一个段,将需要写入的东西写入到该段中
7.7 hello进程execve时的内存映射
1删除已经存在的用户区域
2映射私有区域
3映射hello的共享区域
4设置程序计数器
具体解析
删除当前进程用户部分中的已存在的区域结构。 映射私有区域。为hello的代码、数据、bss,代码和数据区域被映射为hello 文件中的.text和.data
区。bss 区域映射到匿名文件,栈和堆区域是初始长度为零。映射共享区域,然后再映射到用户虚拟地址空间中的共享区域内。设置当前进程上下文中的程序计数器指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页异常的情况:
1线性地址不在虚拟地址空间中
2线性地址在虚拟地址空间中,但没有访问权限
3接上一条,没有与物理地址建立映射关系
4fork等系统调用时并没有映射物理页,写数据->缺页异常->写时拷贝
5映射关系建立了,但在交换分区中
6.页面访问权限不足
缺页中断
在请求分页的过程中,如果访问的页面不再内存中,会产生一次缺页中断,在外存中找到所缺的一页将其调入内存。
1保护cpu现场
保留进程上下文判断内存是否有空闲可用帧?若有,则获取一个帧号
腾出一个空闲帧,按页表中提供的缺页外存位置,启动I/O,将缺页装入空闲帧
2分析中断原因
3转入缺页中断处理函数
4恢复cpu现场,继续执行
LRU算法
缺页中断的断点压入
当CPU执行指令希望访问一个不在内存的页面时,将产生缺页中断,系统开始运行中断处理程序。
此时指令计数器(PC) 的值尚未来得及增加就被压入堆栈,因此压入的断点必然是本次被中断的指令地址,而非下一条指令的地址。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区 域,称为堆(heap)系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk(读做"break"),它指向堆的顶部。
分配器将堆视为不同大小的块来维护,,每个块都是一个连续的虚拟内存片,或已分配或空闲空袭那块用于分配,已分配给程序使用
分配器有两种基本风格,显式和隐式。显式要求程序释放所有的块,隐式要求检测程序何时不需要该块。需要实时回收垃圾。
显式分配器(explicitallocator):要求应用显式地释放任何已分配的块。例如,C标准库提供一种叫做ma耳oc程序包的显式分配器。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。C++中的new和delete操作符与C中的malloc和free相当。
隐式分配器(implicitallocator):另一方面,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器(garbagecollector),而自动释放未使用的巳分配的块的过程叫做垃圾收集(garbagecollection)。
显式空闲链表
将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。
在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。
隐式空闲链表
一个块是由一个字的头部、有效载荷,以及可能的填充组成。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。块的头最后一位指明这个块是已分配的还是空闲的。
头部后面是应用malloc时请求的有效载荷。有效载荷后面是一片不使用的填充块,其大小可以是任意的。空闲块通过头部块的大小字段隐含的连接着,所以我们称这种结构就隐式空闲链表。
7.10本章小结
本章主要介绍了hello的存储器的地址空间,介绍了四种地址空间的差别和地址的相互转换。解释了了三级cashe的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
open函数: 打开或创建一个文件
库和函数原型:
#include
int open(const char* pathname,int flag, ... /*mode_t mode*/)
返回值:
成功返回一个整型(int)的文件描述符,这个文件描述符可以看作是一个打开文件的钥匙,凡是对文件的操作都离不开它。错误返回-1
文件描述符一定是用最小的未使用的描述符数值,其中0,1,2分别是标准输入stdin,标准输出stdout和标准出错输出stderr,所以我们在不修改或者关闭其中三个的情况下,打印出的第一个描述符数值一般是3
参数:
pathname:要打开或者创建的文件名。
flag :打开方式的选项,用下来一个或多个常量进行或|运算构成的flag参数。
选项: O_RDONLY, O_WRONLY, O_RDWR 这三个常量必须有且只能选一个
O_CREAT:文件不存在则创建
O_TRUNC:若文件存在,则把数据清零。
write函数:往打开的文件里面写数据
库和函数原型:
#include
ssize_t write(int fd, const void *buf, size_t count);
解析:
返回值:成功则返回已写的字节数,错误返回-1
fd: open函数返回的文件描述符
buf: 要写入数据的地址,通常是已声明的字符串的首地址
count:一次要写几个字节,通常为strlen()个
read函数: 从打开的设备或文件中读取数据。
库和函数原型:
#include
ssize_t read(int fd, void *buf, size_t count);
返回值: 成功返回读取的字节数,出错返回-1并设置errno,0表示文件末端
如果在调read之前已到达文件末尾,则这次read返回0
如果要求读100个字节,而离文件末尾只有99个字节,则read返回99,下一次再调用read返回0
参数: 可联系write函数记忆
fd: 文件描述符
buf: 要读数据的地址,通常是已声明的字符串的首地址
count 一次要读多少个数据
lseek函数: 显式地为一个打开的文件设置偏移量,通常读写操作都是从当前文件偏移量处开始的,并使偏移量增加所读写的字节数
库和函数原型:
#include
#include
off_t lseek(int fd, off_t offset, int whence);
返回值: 成功返回新的文件偏移量注意可能负数,出错返回-1
如果文件 描述符引用的是一个管道,FIFO或者 网络套接字,则lseek返回-1,,并将error设置成ESPIPE
参数:
fd: 文件描述符
offset 的含义取决于参数 whence
whence有三个选项:
1. 如果 whence 是 SEEK_SET, 文件偏移量将被设置为 0加上 offset。
2. 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 当前值 加上 offset,offset 可以为正也可以为负。
3. 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负。
注意:
lseek 仅将文件偏移量保存于内核中,不会导致任何 I/O 操作。这个文件偏移量将被用于之后的读写操作。
文件偏移量可以大于当前文件长度,在这之上,对文件的下一次写操作就会在文件中(源文件末端到新开始的位置)构成空洞,并被读成0,且文件中的空洞并不占用磁盘的内存空间。
close(fd)函数: 关闭这个已打开的文件,
8.3 printf的实现分析
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
sprintf函数将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。
write函数将buf中的i个元素写到终端
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
介绍了I/O接口和函数 和IO 设备管理方法,分析了 printf函数和getchar 函数
(第8章1分)
结论
hello被IO设备编写,存为.c文件。
hello.c被预处理器cpp预处理为hello.i文件
hello.i被编译器ccl编译为hello.s文件
hello.s被汇编器as汇编成hello.o文件
链接器将hello.o和外部文件链接成可执行文件hello
进入shell执行
shell调用folk,execve执行hello
hello的虚拟内存转换为物理内存执行
处理执行过程中的信号
程序终止,.shell父进程回收hello子进程
通过分析hello.c的执行,对计算机对代码的执行和源程序执行生成可执行目标文件中的基本操作有了详细理解
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.c 源程序
hello.i 修改后的源程序
hello.s 汇编文件,将c语言程序转化成汇编指令
hello.o 可重定位目标文件,与其他可重定位目标文件链接生成可执行目标文件
hello 可执行目标文件,可直接复制加载到内存并执行
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)