计算机系统大作业

计算机系统

大作业

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

计算机科学与技术学院
2019年12月
摘 要
为对计算机系统有更好的理解和认识,本文对简单的hello程序从代码到运行再到最后终止过程的底层实现进行了分析,描述了与之相关的计算机组成与操作系统的相关内容。基于Linux平台,通过gcc、objdump、gdb、edb等工具对一段程序代码预处理、编译、汇编、链接与反汇编的过程进行分析与比较,并且通过shell及其他Linux内置程序对进程运行过程进行了分析。经过研究,较为深入的研究了一个C语言程序生命周期内包括缓存、虚拟内存、信号在内的各种操作系统与硬件实现的机制,对汇编语言及程序编译的各个阶段都有所讨论。研究内容对理解底层程序与操作系统的实现原理具有一定指导作用。
关键词:操作系统,计算机组成原理,编译,汇编,进程,虚拟内存

(摘要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简介
hello程序的建立是由用户创建一个.c文件开始的。
a.编写与预处理:用户利用codeblocks等编译器,通过编写高级语言程序(如C语言),再由预处理器(Preprocessor)对程序源代码进行的处理(如:#include<stdio.h>预处理器读取系统头文件stdio.h中的内容,代替该行的内容)文件一般以.i后缀保存。
b.编译:对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码,文件一般以.s后缀保存。
c.汇编:汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o的目标文件中。
d.链接:经过汇编以后的目标文件还不能直接运行,为了变成能够被加载的可执行文件,文件中必须包含固定格式的信息头,还必须与系统提供的启动代码链接起来才能正常运行,这些工作都是由链接器来完成的。
e.Shell:通过编译系统将源文件编译成可执行二进制文件后,在shell中输入我们得到的可执行二进制文件名,shell将其从磁盘中加载到存储器中。
f.存储管理:由于操作系统给每个进程提供独立的虚拟内存,hello这个进程拥有自己的空间,从而不会收到其他进程的干扰。计算机存储结构层层递进,下一级作为上一级的缓存,让hello的数据能够从磁盘传输到CPU寄存器。同时,在运行过程中,页表的存在加速的数据的访问速度,信号的处理机制也有效避免了程序错误可能导致的灾难性后果。
g.I/O设备:操作系统将I/O设备映射成文件,Unix I/O是内核输出的接口,从而让hello程序能够输出到屏幕上。
h.回收:父进程(若父进程终止,则由操作系统的init回收)将其回收,并释放占用的内存
1.2 环境与工具
1.硬件环境
Intel Core i5 X64 CPU;2.5GHz;8G RAM;128G SSD + 1T HDD
2.软件环境
Windows 10 64位; VMware 17.04;Ubuntu 16.04 LTS 64位
3.开发与调试工具
GDB;EDB;OBJDUMP;READELF;CodeBlocks 64位;vim
1.3 中间结果
hello.i: hello.c预编译的结果,用于研究预编译的作用以及进行编译器的下一步编译操作。
hello.s: hello.i编译后的结果,用于研究汇编语言以及编译器的汇编操作
hello.o: hello.s汇编后的结果,可重定位目标程序,没有经过链接,用于链接器或编译器链接生成最终可执行程序。
hello.out: hello.o链接后生成的可执行目标文件,可以用来反汇编或者通过GDB等工具分析链接过程以及程序运行过程.
ans.s: 对可执行目标文件反汇编(objdump -d hello.out)得到,可以用来分析链接过程与寻址过程。
1.4 本章小结
本章简述了hello程序从产生到终止的基本过程,以及操作系统在此期间完成的对应任务;同时还介绍了研究过程中用到的软硬件环境,还有对应的开发和调试工具;最后分析了在hello程序运行过程中的中间产物以及对应的作用。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理。
作用:
1.将所有的#define删除,并且展开所有的宏定义,并且处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等。
2.处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
3.删除所有注释“//”和“/* */”。
4.添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
5.保留所有的#pragma编译器指令,后续编译过程需要使用它们。

2.2在Ubuntu下预处理的命令

第一个命令:将.c文件预编译得到.i文件

第二个命令:将.c文件预处理得到.o文件
(也可以采用如下命令:gcc hello.c -o hello.o)

2.3 Hello的预处理结果解析
处理前:

处理后:(取一小部分进行截图)

预处理实现了在编译前对代码的初步处理,对源代码进行了某些转换。
如:在原有代码的基础上,将头文件stdio.h的内容引入,若原代码中还含有#define,会进行替换。
2.4 本章小结
预处理操作是在编译前对代码进行某些转换与处理,从而使编译得以成功进行。例如实现将定义的宏进行符号替换、引入头文件的内容、根据指令进行选择性编译等。经过预处理,代码为下一步编译做好了准备。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
概念:编译是指编译器(ccl)将文本文件hello.i翻译成文本文件hello.s的过程,这个文本文件内包含了一个汇编语言程序。
作用:编译过程编译器实现了经过词法分析、语法分析、语义分析等过程,在检查无错误后将代码翻译成汇编语言。得到的汇编语言代码可供编译器进行生成机器代码、链接等操作。
3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1.全局变量和全局函数

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

3.3.2.主函数的参数

.s文件

.c文件

栈的结构:

从.c文件可以看出,主函数有两个参数,分别是:int argc和char *argv[]。通过汇编代码,我们可以看到argc这个参数应该被写入了-0x20(rbp)这个位置,而argv[]这个参数应该被写入了-0x32(rbp)这个位置

3.3.3.控制转移

这是if结构转化的汇编语言代码。先将立即数3与刚刚存入的argc进行比较,je指令用于判断argc和3是否不相等,如果不相等则跳转,以下是利用put将
LC0中存储的内容输出到屏幕上,并将argc赋值为1

3.3.4.循环结构

接下来进入for循环语句部分。该部分使用了一个局部变量i并将其初始化为0,这个参数应该被写入了-0x04(rbp)这个位置。接着使用jmp跳转无条件进入.L3。进入.L3后使用cmpl语句先进行条件判断。如果条件满足,那么进入.L4循环体部分调用printf函数和sleep函数。
在调用printf的过程中,进行了数组访问(argv[1]和argv[2])。由于argv是指针数组,所以会进行二次寻址。
接下来分析汇编代码:
1.在汇编代码.L4的第1行到第3行中,取出argv[2]对应的内容,并放入寄存器%rdx中
2.第4行到第7行取出argv[1]对应的内容,并放入寄存器%rsi中。
3.第八行将格式字符串放到一号参数寄存器%edi中,然后调用printf函数进行显示。
4.第11行到第13行读取sleepsecs全局变量并调用sleep函数。最后一行对计数量进行加一,结束循环体部分。
5. 跳出.L4后,进入.L3并调用getchar函数,将返回值设为0,主函数正常返回。
3.4 本章小结

本阶段将hello.i编译为hello.s,从而可以查看hello对应的汇编语言。此外,通过与源文件C程序代码进行比较,完成了对汇编代码的解析工作。完成该阶段转换后,可以进行下一阶段的汇编处理。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用

概念:汇编指的是汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。hello.o文件是一个二进制文件,包含hello程序执行的机器指令。
作用:当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o目标文件后,才能进入下一步的链接工作,链接后才能被计算机执行

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

终端输入指令得到可重定位目标elf格式

可重定位目标elf格式概述表

1.ELF头:

2.节头表

3.重定位节.rela.text(也可以输入指令:readelf -r hello.o得到)

由此图可以得到hello.o的重定位节信息,可以看到.rodata、puts、exit、printf、sleep、getchar等符号的偏移。链接器会依据重定向节的信息对可重定向的目标文件进行链接得到可执行文件。

4.重定位节.rela.eh frame

5.hello.o的”.symtab“节:

4.4 Hello.o的结果解析

hello.s截图

利用objdump -d -r hello.o反汇编得到的hello.o的反汇编代码:

1.分支转移:反汇编代码可以看到跳转指令的操作数已经从原来的符号(助记符)变成了具体的地址。如图中59处的内容为:callq 5e<main+0x5e>,使用的是一个具体地址而非相对位置的关系。
2.函数调用:在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,函数调用之后跟着的是应该是具体的地址,而有些库函数的调用需要在链接之后才能确定真正运行的地址,因此可以看到图中59处机器码为e8 00 00 00 00,其实际上并没有调用到库函数,而是继续执行下一条,在链接部分程序将会自动修改。
4.5 本章小结
本章介绍了程序生成过程中编译器汇编的相关内容。汇编过程将汇编语言转换为机器代码,生成可重定位的目标文件,使机器能够直接处理与执行。可以通过readelf读取其elf信息与重定位信息,得到其符号表的相关信息。虽然机器语言难以阅读,但是它也具有灵活、直接执行等特点,即使现在很多程序员已经不再学习机器语言,我们仍需要初步掌握这种语言,因为越是了解底层、知晓其本质,对学习编程越是有好处。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
1.概念
链接是指将文件中调用的各种函数跟静态库及动态库链接,并将它们打包合并形成目标文件,即可执行文件。
2.作用
通过链接可以实现将头文件中引用的函数并入到程序中。
5.2 在Ubuntu下链接的命令
输入指令:gcc hello.o -o hello.out

5.3 可执行目标文件hello的格式
5.3.1.hello.out文件的文件头:

5.3.2.hello.out的节头

由于是可执行目标文件,所以每个段的起始地址都不相同,它们的起始地址分别对应着装载到虚拟内存中的虚拟地址。这样可以直接从文件起始处得到各段的起始位置,以及各段所占空间的大小。

5.4 hello的虚拟地址空间

其中Symbols窗口中显示了hello程序中各段的名称和其起始地址对应了节头表中的名称和地址。

5.5 链接的重定位过程分析

对比先前的反汇编截图我们可以发现以下几个不同:
1.hello将hello.o中的待修改地址修改后,这些地址成了实际虚拟内存中的地址,并指向各个被调用的函数。
2.hello相对hello.o链接了许多库函数。
3.hello相对hello.o添加了节:.init,.plt等
4.hello.o中跳转以及函数调用的地址在hello中都被更换成了虚拟内存地址。

1.合并相同节:
如上图其中函数<_start>就是系统代码段(.text)与hello.o中的.text节合并得到的最后的一个单独的代码段。

2.对定义符号进行重定位(确定其地址)
比如为函数确定首地址,从而确定每条指令的地址。
3.对引用符号进行重定位。
修改.text节 和 .data节中对每个符号的引用(地址),该过程需要用到在.rel_data和.rel_text节中保存的重定位信息。

5.6 hello的执行流程
通过使用objdump查看反汇编代码,以及使用gdb单步运行,可以找出.text节中main函数前后执行的函数名称。在main函数之前执行的程序有:_start、__libc_start_main@plt、__libc_csu_init、_init、frame_dummy、register_tm_clones。在main函数之后执行的程序有:exit、cxa_thread_atexit_impl、fini。
5.7 Hello的动态链接分析
在dl_init前后的图如下:

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。
5.8 本章小结
本章介绍了链接的概念与作用、hello的ELF格式,hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。而我们的hello这个小程序终于可以被计算机读懂并且一步一步执行操作了。

(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
1.概念
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
2.作用
进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。
这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
6.2 简述壳Shell-bash的作用与处理流程
1.作用
shell是一种交互型的应用级程序。它能够接收用户命令,然后调用相应的应用程序,即代表用户运行其他程序。
2.处理流程
shell执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行。求值步骤解析命令行,并代表用户运行程序。
6.3 Hello的fork进程创建过程
在终端输入./Hello 1180300117 Lh并回车后,终端程序会对输入的命令行进行解析,而由于hello不是内置shell命令因此终端程序将执行当前目录下的可执行目标文件hello,接着终端调用fork函数创建一个新的运行的子进程,新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同的但独立一份副本,因此当父进程调用fork时,子进程可以读写父进程中打开的任何文件。
父进程与子进程之间最大的区别在于它们拥有不同的PID。

6.4 Hello的execve过程

创建进程后,在子进程中通过判断pid即fork()函数的返回值,判断处于子进程,则会通过execve函数在当前进程的上下文中加载并运行一个新程序。execve加载并运行可执行目标文件,且带参数列表argv和环境变量列表envp。只有当出现错误时,execve才会返回到调用程序。
在execve加载了可执行程序之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,即可执行程序的main函数。此时用户栈已经包含了命令行参数与环境变量,进入main函数后便开始逐步运行程序。
6.5 Hello的进程执行
多个流并发地执行的一般现象被称为并发。一个进程和其他进轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。
hello程序执行过程中同样存储时间分片,与操作系统的其他进行并发运行。并发执行涉及到操作系统内核采取的上下文交换策略。内核为每个进程维持一个上下文,上下文就是内核重新启动一个先前被抢占的进程所需的状态。
在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个过程称为调度。
在此基础上,hello程序与操作系统其他进程通过操作系统的调度,切换上下文,拥有各自的时间片从而实现并发运行。
程序在涉及到一些操作时,例如调用一些系统函数,内核需要将当前状态从用户态切换到核心态,执行结束后再及时改用户态,从而保证系统的安全与稳定。

6.6 hello的异常与信号处理
Hello在执行的过程中,可能会出现处理器外部I/O设备引起的异常,执行指令导致的陷阱、故障和终止。第一种被称为外部异常,常见的有时钟中断、外部设备的I/O中断等。第二种被称为同步异常。陷阱指的是有意的执行指令的结果,故障是非有意的可能被修复的结果,而终止是非故意的不可修复的致命错误。
在发生异常时会产生信号。例如缺页故障会导致OS发生SIGSEGV信号给用户进程,而用户进程以段错误退出。常见信号种类如下:
1.终止:SIGINT 来自键盘的中断
2.终止:SIGKILL 杀死程序
3.中断:SIGSTP 挂起程序
各类命令输入后的结果:
1.运行途中乱按
用户键盘输入的字符被读进stdin缓冲区中,当循环结束时读入一个以’\n’结尾的字符串,剩余的字符被当作指令进入shell命令行

2.运行途中中输入ctrl+z:
内核发送一个SIGSTP信号到前台进程组中的每个进程,前台作业被挂起。输入ps查看进程信息,发现hello并没有被回收。之后输入fg 1将hello调到前台继续执行,执行结束后进程被回收。

输入fg 1 后程序继续运行
3.运行途中中输入ctrl+c:
内核发送一个SIGINT信号到前台进程组中的每个进程,终止前台作业,可以看到此时hello进程已结束并被回收

6.7本章小结
本章介绍了程序在shell执行及进程的相关概念。程序在shell中执行是通过fork函数及execve创建新的进程并执行程序。进程拥有着与父进程相同却又独立的环境,与其他系统进并发执行,拥有各自的时间片,在内核的调度下有条不紊的执行着各自的指令。
程序运行中难免遇到异常,异常分为中断、陷阱、故障和终止四类,均有对应的处理方法。操作系统提供了信号这一机制,实现了异常的反馈。这样,程序能够对不同的信号调用信号处理子程序进行处理。

(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
物理地址:计算机的主存被组织成一个由M个连续的字节大小的单元组成的数组,每个字节都有一个唯一的物理地址,CPU访问内存的最自然的方式就是物理寻址
虚拟地址:程序使用的地址,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址被送到内存之前通过硬件与操作系统被转换成适当的物理地址
逻辑地址:由程序产生的与段相关的偏移地址部分,在有地址变换功能的计算机中,访存指令给出的地址叫逻辑地址,也叫相对地址。逻辑地址由段基值和偏移量组成
线性地址:逻辑地址经过段变换后的地址,即虚拟地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
Intel处理器从逻辑地址到线性地址的变换通过段式管理,介绍段式管理就必须了解段寄存器的相关知识。段寄存器对应着内存不同的段,有栈段寄存器(SS)、数据段寄存器(DS)、代码段寄存器(CS)和辅助段寄存器(ES/GS/FS)。其大体对应关系如下图:

段寄存器用于存放段选择符,通过段选择符可以得到对应段的首地址。段选择符分为三个部分,分别是索引、TI(决定使用全局描述符表还是局部描述符表)和RPL(CPU的当前特权级)

这样,Intel处理器在通过段式管理寻址时,首先通过段描述符得到段基址,然后与偏移量结合得到线性地址,从而得到了虚拟地址。至于偏移量,基址寄存器还是变址寄存器有不同的计算方法,后者需要经过乘比例因子等处理。
7.3 Hello的线性地址到物理地址的变换-页式管理
Intel处理器从线性地址到物理地址的变换通过页式管理实现。
由计算机系统处理器层次的相关知识可知,内存其实是磁盘的缓存。磁盘上的数据被分割成块,作为其主存之间的传输单元。虚拟内存系统将虚拟内存分割称为虚拟页,物理内存分割称为物理页。
虚拟页存在未分配的、缓存的、未缓存的三种状态。其中缓存的页对应于物理页。

页表是这类地址转换的另一个重要概念,它将虚拟页映射到物理页,其每一项称为页表条目(PTE),由有效位和一个n位的地址字段组成。如果设置有效位说明该页已缓存,否则未缓存,在地址字段不为空的情况下指向虚拟页在磁盘上的起始地址。
从虚拟地址到物理地址的翻译通过MMU(内存管理单元),它通过虚拟地址索引到对应的PTE,如果已缓存则命中,否则不命中称为缺页。发生缺页时,MMU会选择一个牺牲页,在物理内存将之前缺页的虚拟内存对应的数据复制到它的位置,并更新页表,然后重新触发虚拟地址翻译事件。
通过页表,MMU可以实现从虚拟地址到物理地址的映射。

CPU中的页表基址寄存器指向当前页表,n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO)和一个n- p位的虚拟页号(VPN)。
MMU利用VPN选择适当的PTE,然后将页表条目中的物理页号(PPN)与虚拟地址中的VPO串联起来,得到物理地址。
翻译的过程如果命中则完全由硬件处理,如果缺页需要操作系统内核与硬件合作完成。

7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,MMU就必须查阅相应的PTE,这显然造成了巨大的时间开销,为了消除这样的开销,MMU中存在一个关于PTE的小的缓存,称为翻译后备缓冲器(TLB)。

TLB通过虚拟地址VPN部分进行索引,分为索引(TLBI)与标记(TLBT)两个部分。这样,MMU在读取PTE时会直接通过TLB,如果不命中再从内存中将PTE复制到TLB。

在以上机制的基础上,如果所使用的仅仅是虚拟地址空间中很小的一部分,那么仍然需要一个与使用较多空间相同的页表,造成了内存的浪费。所以虚拟地址到物理地址的转换过程中还存在多级页表的机制:上一级的页表映射到下一级也表,直到页表映射到虚拟内存,如果下一级内容都未分配,那么页表项则为空,不映射到下一级,也不存在下一级页表,当分配时再创建相应页表,从而节约内存空间。

具体来讲,页表条目的格式如下:
1-3级页级条目格式:

第4级页表条目格式:

7.5 三级Cache支持下的物理内存访问
处理器对物理内存中数据的访问,同样需要经过缓存,即Cache,主流的处理器通常采用三级Cache。层与层之间按照以下原则进行读与写:
读取数据时,首先在高速缓存中查找所需字w的副本。如果命中,立即返回字w给CPU。如果不命中,从存储器层次结构中较低层次中取出包含字w的块,将这个块存储到某个高速缓存行中(可能会驱逐一个有效的行),然后返回字w。
下面具体三类Cache进行分析:
(1)直接映射高速缓存
直接映射高速缓存每个组只有一行,当CPU执行一条读内存字w的指令,它会向L1高速缓存请求这个字。如果L1高速缓存中有w的一个缓存副本,那么就会得到L1高速缓存命中,高速缓存会很快抽取出w,并将它返回给CPU。否则就是缓存不命中,当L1高速缓存向主存请求包含w的块的一个副本时,CPU必须等待。当被请求块最终从内存到达时,L1高速缓存将这个块存放在它的一个高速缓存行里,从被存储的块中抽取出字w,然后将它返回给CPU。确定是否命中然后抽取的过程分为三步:1)组选择;2)行匹配;3)字抽取。

组选择即从w的地址中间抽取出s个索引位,将其解释为一个对应组号的无符号整数,从而找到对应的组;行匹配即对组内的唯一一行进行判断,当有效位为1且标记位与从地址中抽取出的标记位相同则成功匹配,否则就得到不命中;而字选择即在行匹配的基础上通过地址的后几位得到块偏移,从而在高速缓存块中索引到数据。

2)组相联高速缓存
组相联高速缓存每个组内可以多于一个缓存行,总体逻辑类似于直接映射高速缓存,不同之处在于行匹配时每组有更多的行可以尝试匹配,遍历每一行。如果不命中,有空行时也就是冷不命中则直接存储在空行;如果没有空行也就是冲突不命中,则替换已有行,通常有LFU(最不常使用)、LRU(最近最少使用)两者替换策略。
(3)全相联高速缓存
全相联高速缓存只有一个组,且这个组包含所有的高速缓存行(即E =
C/B)。对于全相联高速缓存,因为只有一个组,组选择变的十分简单。地址中不存在索引位,地址只被划分为一个标记位和一个块偏移。行匹配和字选择同组相联高速缓存。
写入数据时,假设我们要写一个已经缓存了的字w,在高速缓存中更新了它的w的副本之后,有两种方法来更新w在层次结构中紧接着低一层中的副本。分别是直写和写回,在这里分别介绍:
(1)直写
立即将w的高速缓存块写回到紧挨着的低一层中。优点是简单,缺点则是每次写都会引起总线流量。其处理不命中的方法是非写分配,即避开高速缓存,直接将这个字写到低一层去。
(2)写回
尽可能地推迟更新,只有当替换算法要驱逐这个更新过的块时,才把它写到紧接着的低一层中。优点是能显著地减少总线流量,缺点是增加了复杂性,必须为每个高速缓存行增加一个额外的修改位,表明是否被修改过。写回处理不命中的方法是写分配,加载相应低一层中的块到高速缓存中,然后更新这个高速缓存块,利用了写的空间局部性,但会导致每次不命中都会有一个块从低一层传到高速缓存。
通过这样的Cache读写机制,实现了从CPU寄存器到L1高速缓存,再到L2高速缓存,再到L3高速缓存,再到物理内存的访问,有效的提高了CPU访问物理内存的速度。

7.6 hello进程fork时的内存映射
shell通过fork为hello创建新进程。当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给hello进程唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和样表的原样副本。它将两个进程中的每个页面都标记为只读,并将每个进程中的每个区域结构都标记为写时复制。

当fork在新进程中返回时,新进程现在的虚拟内存刚好的和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就是为每个进程保持了私有地址空间的概念。

7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
3.映射共享区域, hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
Linux将虚拟内存组织成段的集合。内核为每个进程维护一个单独的任务结构,这个任务结构的第一个条目指向mm_struct,它描述了虚拟内存的当前状态,其中的pgd字段又会指向一个区域结构的链表,每个区域结构都描述了当前虚拟地址的一个区域,或者称为一个段。一个具体的区域结构包括vm_start和vm_end等字段,记录区域的相关信息。

假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页。这个异常导致控制转移到内核的缺页处理程序,处理程序随后就执行下面的步骤:
首先判断虚拟地址A是否合法,缺页处理程序会搜索区域结构的链表,把A和每个区域结构中的vm_start和vm_end做比较。如果指令不合法则触发段错误,从而终止该进程。
然后处理程序会判断试图进行的内存访问是否合法,也就是进程是否有读写这个区域内页面的权限。如果访问不合法,那么处理程序会触发一个保护异常,终止这个进程。
最后,确保了以上两点的合法性后,根据页式管理的规则,牺牲一个页面,并赋值为需要的数据,然后更新页表并再次触发MMU的翻译过程。

7.9动态存储分配管理
动态存储分配管理由动态内存分配器完成。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。堆是一个请求二进制零的区域,它紧接在未初始化的数据区后开始,并向上生长(向更高的地址)。分配器将堆视为一组不同大小的块的集合来维护。
每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显示地被应用程序所分配。
一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
动态内存分配器从堆中获得空间,将对应的块标记为已分配,回收时将堆标记为未分配。而分配和回收的过程中,往往涉及到分割、合并等操作。
动态内存分配器的目标是在对齐块的基础上,尽可能地提高吞吐率及空间占用率,即减少因为内存分配造成的碎片。其实现常见的数据结构有隐式空闲链表、显式空闲链表、分离空闲链表,常见的放置策略有首次适配、下一次适配和最佳适配。
为了更好的介绍动态存储分配的实现思想,以隐式空闲分配器的实现原理为例进行介绍:

隐式空闲链表分配器的实现涉及到特殊的数据结构。其所使用的堆块是由一个子的头部、有效载荷,以及可能的一些额外的填充组成的。头部含有块的大小以及是否分配的信息。有效载荷用来存储数据,而填充块则是用来对付外部碎片以及对齐要求。
基于这样的基本单元,便可以组成隐式空闲链表。

通过头部记录的堆块大小,可以得到下一个堆块的大小,从而使堆块隐含地连接着,从而分配器可以遍历整个空闲块的集合。在链表的尾部有一个设置了分配位但大小为零的终止头部,用来标记结束块。
当请求一个k字节的块时,分配器搜索空闲链表,查找足够大的空闲块,其搜索策略主要有首次适配、下一次适配、最佳适配三种。
一旦找到空闲块,如果大小匹配的不是太好,分配器通常会将空闲块分割,剩下的部分形成一个新的空闲块。如果无法搜索到足够空间的空闲块,分配器则会通过调用sbrk函数向内核请求额外的堆内存。
当分配器释放已分配块后,会将释放的堆块自动与周围的空闲块合并,从而提高空间利用率。为了实现合并并保证吞吐率,往往需要在堆块中加入脚部进行带边界标记的合并。

7.10本章小结
本章介绍了存储器地址空间、段式管理、页式管理,VA到PA的变换、物理内存访问,还介绍了进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理在真正运程序的时候。同时讲述了在涉及到系统对内存的操作的时候,系统该采用什么样的方法才能优化结构,提高程序运行效率进而减少时间。同时在某种角度上也为程序员的优化给出了一个方向:即充分利用cache。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
一个Linux文件就是一个m个字节的序列,所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这个设备映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得输入和输出都能以一种统一且一致的方式的来执行。
一个应用程序通过要求内核打开相应的文件来宣告它想访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,而文件的相关信息由内核记录,应用程序只需要记录这个描述符。
Linux shell创建的每个进程开始时都包含标准输入、标准输出、标准错误三个文件,供其执行过程中使用。
对于每个打开的文件,内核保持着一个文件位置k,初始为0,即从文件开头起始的字节偏移量,应用程序能够通过执行seek操作来显式的改变其值。
至于读操作,就是从文件复制n个字节到内存,并将文件位置k增加为k + n。当k大于等于文件大小时,触发EOF条件,即读到文件的尾部。
最后,在结束对文件的访问后,会通过内核关闭这个文件,内核将释放打开这个文件时创建的数据结构,并将描述符恢复到可用的描述符池中。
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.关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
具体实现函数及其参数要求:
1.打开文件:int open(char *filename,int flags,mode_t mode); open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,而mode参数指定了新文件的访问权限位。
2.关闭文件:int close(int fd); fd是需要关闭的文件的描述符,close返回操作结果。
3.读文件:ssize_t read(int fd,void *buf,size_t n) read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
4.写文件:ssize_t write(int fd,const void *buf,size_t n);
5.write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。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; 

}
其中va_list即char va_list arg = (va_list)((char)(&fmt) + 4); 即将arg指向了第一个const参数。
而vsprintf(buf, fmt, arg),格式化,它接受确定输出格式的格式字符串fmt,用格式字符串对个数变化的参数进行格式化,产生格式化输出。它最终会返回一个长度,即要打印出来的字符串的长度。 vsprintf生成显示信息,到write系统函数,陷阱系统调用int 0x80或syscall,字符显示驱动子程序实现从ASCII到字模库到显示vram,可以存储每一个点的RGB颜色信息,显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。如此之后,屏幕上就会显示出我们输入的信息了。
8.4 getchar的实现分析
getchar函数的函数体如下:
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return(–n>=0)?(unsigned char)*bb++:EOF;
}
在需要从键盘读入一个字符时,内核中断当前进程,控制权交给键盘中断处理程序。getchar函数中调用了read系统函数,通过系统调用读取按键ascii码,保存到系统的键盘缓冲区,直到接受到回车键才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾则返回-1(EOF),且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取
8.5本章小结
本章主要介绍了Linux的IO设备管理方法、Unix IO接口及其函数,分析了printf函数和getchar函数。我们了解到hello是如何实现与外接设备的交互,从外接设备读入数据,最后将信息输出到显示屏幕上,这些过程的实现都在本章得到了明晰。

(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
hello.c通过键盘鼠标等I/O设备输入计算机,并存储在内存中。然后预处理器将hello.c预处理成为文本文件hello.i。接着编译器将hello.i翻译成汇编语言文件hello.s。汇编器将hello.s汇编成可重定位二进制代码hello.o。链接器将外部文件和hello.o连接起来形成可执行二进制文件hello.out。shell通过fork和execve创建进程,然后把hello加载到其中。shell创建新的内存区域,并加载代码、数据和堆栈。hello在执行的过程中遇到异常,会接受shell的信号完成处理。hello在执行的过程中需要使用内存,那么就通过CPU和虚拟空间进行地址访问。Hello执行结束后,shell回收其僵尸进程,从系统中消失。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
计算机系统的设计与实现,处处体现着抽象的含义。比如,程序的本质是01二进制码,也就是机器语言。而汇编语言实现了对机器语言的抽象,高级语言实现了对汇编语言的抽象。再比如,各种物理内存的实现方式各不相同,有磁盘、软盘等。使用虚拟内存的概念实现了对各种物理内存的抽象,而具体实现则交给IO设备进行处理,使得上层在使用的时候非常方便。概念上的抽象使得对概念的使用变得简单,这就是我对计算机系统设计实现的一个感悟。

(结论0分,缺失 -1分,根据内容酌情加分)

附件
hello.c c源代码
hello.i hello.c预处理后的文件
hello.s hello.i编译后的文件
hello.o hello.s汇编后的文件
hello .out hello.o链接后的文件

(附件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分)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值