计算机系统大作业 程序人生-Hello’s P2P

摘 要

本文主要介绍了一个示例程序从编写到执行到介绍的全过程。通过详细介绍各个过程中的具体状态和操作,将计算机系统各个组成部分的工作有机地结合统一起来,完成搭建知识体系、将知识融会贯通的目标。本文主要包括示例程序的预处理、编译、汇编、链接、进程管理、存储管理、I/O管理等部分的具体操作和结果,并最终得出系统性结论。

关键词:编译;内存管理;操作系统;

第1章 概述

1.1 Hello简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

首先,使用高级语言(C语言)编写得到hello.c源程序文本文件。然后对其进行预处理,形成hello.i文本文件。接着对其进行编译,形成hello.s汇编语言文本文件。接下来经过汇编程序处理,将其转化为hello.o可重定位目标程序二进制文件。最后将程序与函数库中需要使用的二进制文件进行链接,形成可执行目标程序ELF二进制文件。

执行该目标文件,操作系统会使用fork函数形成一个子进程,分配相应的内存资源,包括CPU的使用权限和虚拟内存等。然后使用execve函数加载进程。至此完成了从程序到进程的转变P2P(From Program to Process)。在CPU工作时,通过取指、译码、执行等微程序,逐步执行目标文件中的程序。同时,CPU使用流水线、进程切换等工作方式实现多进程作业。

在程序的执行过程中会使用到内存中的数据。这些数据通过各级存储,包括磁盘、主存、Cache等,并使用页表等辅助存储,实现访存的加速。在这个过程中还涉及操作系统的信号处理,控制进程,使得系统资源得到充分利用。而IO管理与信号处理通过软硬结合,完成程序从键盘、主板、显卡,再到屏幕的工作。当进程执行结束后,操作系统进行进程回收,实现所谓的O2O:From Zero-0 to Zero-0。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU;2.19GHz;4.00GB RAM
软件环境:Windows 8.1 64位;VMware Workstation 14 Player;Ubuntu 16.04 LTS
开发工具:gcc;gdb;objdump;

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

中间结果文件名 文件作用
hello.i 预处理得到的中间结果
hello.s hello.i编译后得到的汇编语言文本文件
hello.o hello.s汇编后得到的可重定位目标文件
hello.out 链接后得到的可执行目标文件

1.4 本章小结

hello.c程序从编写、预处理、编译、汇编、链接再到执行,体现了计算机系统系统各部分的具体功能,以及它们之间的的协同合作。

第2章 预处理

2.1 预处理的概念与作用

预处理指的是在编译之前进行的处理。C语言的预处理主要有三个方面的内容:宏定义、文件包含、条件编译。预处理命令以符号“#”开头。

预处理工作也叫做宏展开,将宏名替换为文本。宏定义只需将符号常量替换成后面对应的文本即可。文件包含可以在一个文件中包含另一个文件的内容,被包含的文件称为头文件。头文件的内容可以有函数原型、宏定义、结构体定义。条件编译是在条件满足时才编译某些语句。

使用条件编译可以使目标程序变小,运行时间变短。同时有利于代码的模块化。

2.2在Ubuntu下预处理的命令

gcc hello.c -E -o hello.i
图2-1 Ubuntu下预处理命令
图2-1 Ubuntu下预处理命令

计算机系统大作业 程序人生-Hello’s P2P_第1张图片
图2-2 预处理结果hello.i文件(部分)

2.3 Hello的预处理结果解析

经过预处理之后,hello.c文件转化为hello.i文件。原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。打开该文件可以发现,文件长度变为3125行。文件的内容增加,且仍为可以阅读的C语言程序文本文件。

2.4 本章小结

本阶段完成了对hello.c的预处理工作。使用Ubuntu下的预处理指令可以将其转换为.i文件。完成该阶段转换后,可以进行下一阶段的汇编处理。

第3章 编译

3.1 编译的概念与作用

编译程序也称为编译器,是指把用高级程序设计语言书写的源程序,翻译成等价的汇编语言格式目标程序的翻译程序。编译程序属于采用生成性实现途径实现的翻译程序。它以高级程序设计语言书写的源程序作为输入,而以汇编语言表示的目标程序作为输出。

编译程序的基本功能是把源程序(高级语言)翻译成目标程序。除了基本功能之外,编译程序还具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人机联系等重要功能。

3.2 在Ubuntu下编译的命令

gcc hello.i –S –o hello.s
图3-1 Ubuntu下编译命令
图3-1 Ubuntu下编译命令
计算机系统大作业 程序人生-Hello’s P2P_第2张图片
图3-2 编译结果hello.s文件(部分)

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。
计算机系统大作业 程序人生-Hello’s P2P_第3张图片
图3-3 全局变量和全局函数

3.3.2 主函数的参数

主函数的参数部分给出了int argc,char *argv[]两个参数。在汇编代码中,分别将其存放在栈中rbp寄存器指向地址-20和-32处,如下图所示。其中%edi代表argc,%rsi代表argv[]。
图3-4 对传入参数的处理
图3-4 对传入参数的处理

3.3.3 条件判断语句及分支

接着在main函数中,使用if语句进行了条件判断。cmpl语句进行判断条件的比较。如果条件满足则继续顺序执行,调用puts输出给定字符串(这里puts是对printf的优化),然后使用参数1调用exit结束程序。对应的汇编代码如下。
计算机系统大作业 程序人生-Hello’s P2P_第4张图片
图3-5 if条件语句段对应的汇编代码

3.3.4 循环结构及主函数结尾部分

接下来进入for循环语句部分。该部分使用了一个局部变量i,该变量存放在栈中rbp寄存器指向地址-4处。首先对其置零进行初始化(35行)。接着使用jump to middle模式进入.L3使用cmpl语句先进行条件判断。如果条件满足,那么进入.L4循环体部分调用printf函数和sleep函数。

在调用printf的过程中,进行了数组访问(argv[1]和argv[2])。而argv是指针数组,所以会进行二次寻址。在汇编代码中,38至40行取出argv[2]对应的内容,并放入三号参数寄存器%rdx中。41至44行取出argv[1]对应的内容,并放入二号参数寄存器%rsi中。45行将格式字符串放到一号参数寄存器%edi中,然后调用printf函数进行显示。48-50行读取sleepsecs全局变量并调用sleep函数。最后51行对计数量进行加一,结束循环体部分。

最后调用getchar函数,将返回值设为0,主函数正常返回。
计算机系统大作业 程序人生-Hello’s P2P_第5张图片
图3-6 循环结构及主函数结尾部分

3.4 本章小结

本阶段完成了对hello.i的编译工作。使用Ubuntu下的编译指令可以将其转换为.s汇编语言文件。此外,本章通过与源文件C程序代码进行比较,完成了对汇编代码的解析工作。完成该阶段转换后,可以进行下一阶段的汇编处理。

第4章 汇编

4.1 汇编的概念与作用

汇编程序是把汇编语言书写的程序翻译成与之等价的机器语言程序的翻译程序。汇编程序输入的是用汇编语言书写的源程序,输出的是用机器语言表示的目标程序。汇编语言的指令与机器语言的指令大体上保持一一对应的关系,汇编算法采用的基本策略是简单的。通常采用两遍扫描源程序的算法。第一遍扫描源程序根据符号的定义和使用,收集符号的有关信息到符号表中;第二遍利用第一遍收集的符号信息,将源程序中的符号化指令逐条翻译为相应的机器指令。

4.2 在Ubuntu下汇编的命令

gcc hello.s –c –o hello.o
图4-1 Ubuntu下汇编命令
图4-1 Ubuntu下汇编命令

4.3 可重定位目标elf格式

计算机系统大作业 程序人生-Hello’s P2P_第6张图片
图4-2 hello.o的文件头

使用readelf –h hello.o查看文件头信息。根据文件头的信息,可以知道该文件是可重定位目标文件,有13个节,如图4-2。使用readelf –S hello.o查看节头表。从而得知各节的大小,以及他们可以进行的操作,如图4-3。使用readelf –s hello.o可以查看符号表的信息,如图4-4。

计算机系统大作业 程序人生-Hello’s P2P_第7张图片
图4-3 hello.o的节头表
计算机系统大作业 程序人生-Hello’s P2P_第8张图片
图4-4 hello.o的符号表

根据图4-3可以得到各节的基本信息。由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小。同时可以观察到,代码段是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。

4.4 Hello.o的结果解析

使用objdump -d -r hello.o指令显示hello.o的反汇编结果,如图4-5。
计算机系统大作业 程序人生-Hello’s P2P_第9张图片
图4-5 hello.o的反汇编结果

将该反汇编结果与第3章的hello.s的主函数部分进行对照,可以发现其主要流程没有不同,只是对栈的使用有所差别。反汇编代码的栈空间利用率较高,而.s文件中对栈的使用有一定的浪费。

机器语言程序的是二进制的机器指令序列集合,是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。机器指令由操作码和操作数组成。汇编语言是以人们比较熟悉的词句直接表述CPU动作形成的语言,是最接近CPU运行原理的较为通俗的比较容易理解的语言。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。机器语言与汇编语言具有一一对应的映射关系,一条机器语言程序对应一条汇编语言语句,但不同平台之间不可直接移植。

4.5 本章小结

本阶段完成了对hello.s的汇编工作。使用Ubuntu下的汇编指令可以将其转换为.o可重定位目标文件。此外,本章通过将.o文件反汇编结果与.s汇编程序代码进行比较,了解了二者之间的差别。完成该阶段转换后,可以进行下一阶段的链接工作。

第5章 链接

5.1 链接的概念与作用

链接程序将分别在不同的目标文件中编译或汇编的代码收集到一个可直接执行的文件中。它还连接目标程序和用于标准库函数的代码,以及连接目标程序和由计算机的操作系统提供的资源(例如,存储分配程序及输入与输出设备)。链接工作大致包含两个步骤,一是符号解析,二是重定位。在符号解析步骤中,链接器将每个符号引用与一个确定的符号定义关联起来。将多个单独的代码节和数据节合并为单个节。将符号从它们的在.o文件的相对位置重新定位到可执行文件的最终绝对内存位置。更新所有对这些符号的引用来反映它们的新位置。

5.2 在Ubuntu下链接的命令

gcc hello.o –o hello.out
图5-1 Ubuntu下链接指令
图5-1 Ubuntu下链接指令

5.3 可执行目标文件hello的格式

计算机系统大作业 程序人生-Hello’s P2P_第10张图片
图5-2 hello.out文件的文件头

使用readelf –h hello.out查看文件头信息。根据文件头的信息,可以知道该文件是可执行目标文件,有31个节,如图5-2。使用readelf –S hello.out查看节头表。从而得知各节的大小,以及他们可以进行的操作,如图5-3。使用readelf –s hello.out可以查看符号表的信息,如图5-4。
计算机系统大作业 程序人生-Hello’s P2P_第11张图片
计算机系统大作业 程序人生-Hello’s P2P_第12张图片
图5-3 hello.out的段头表

计算机系统大作业 程序人生-Hello’s P2P_第13张图片
图5-4 hello.out的符号表

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

根据图5-3可以得到各段的基本信息。由于是可执行目标文件,所以每个段的起始地址都不相同,它们的起始地址分别对应着装载到虚拟内存中的虚拟地址。这样可以直接从文件起始处得到各段的起始位置,以及各段所占空间的大小。同时可以观察到,代码段是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间。根据5.3节的信息,可以找到各节的二进制信息。代码段的信息如下所示。代码段开始于0x400550处,大小为0x01f2。
计算机系统大作业 程序人生-Hello’s P2P_第14张图片
图5-5使用edb查看.text段

5.5 链接的重定位过程分析

使用objdump -d -r hello.out得到反汇编结果。可以明显发现该结果与hello.o的反汇编结果不同。可执行文件的反汇编结果中给出了重定位结果,即虚拟地址的确定。而hello.o的反汇编结果中,各部分的开始地址均为0。
计算机系统大作业 程序人生-Hello’s P2P_第15张图片
图5-6 hello.out的反汇编结果(主函数部分)

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的动态链接分析

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。虽然动态链接把链接过程推迟到了程序运行时,但是在形成可执行文件时(注意形成可执行文件和执行程序是两个概念),还是需要用到动态链接库。比如我们在形成可执行程序时,发现引用了一个外部的函数,此时会检查动态链接库,发现这个函数名是一个动态链接符号,此时可执行程序就不对这个符号进行重定位,而把这个过程留到装载时再进行。

5.8 本章小结

本阶段完成了对hello.o的链接工作。使用Ubuntu下的链接指令可以将其转换为.out可执行目标文件。此外,本章通过一系列细致分析,了解了链接过程中的具体细节。完成该阶段转换后,即得到了可以执行的二进制文件了。

第6章 hello进程管理

6.1 进程的概念与作用

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

6.2 简述壳Shell-bash的作用与处理流程

Linux实质上是一个操作系统内核,一般用户不能直接使用内核,而是通过外壳程序,也就是所谓的shell来与内核进行沟通。外壳程序可以保证操作系统的安全性,抵御用户的一些不正确操作。Linux的外壳程序称作shell(命令行解释器),它能够将命令翻译给内核、将内核处理结果翻译给用户。一般我们使用的shell为bash。在解释命令的时候,bash不会直接参与解释,而是创建新进程进行命令的解释,bash只用等待结果即可,这样能保证bash进程的安全。
首先 shell检查命令是否是内部命令,若不是再检查是否是一个应用程序(这里的应用程序可以是Linux 本身的实用程序,如ls 和rm;也可以是购买的商业程序,如xv;或者是自由软件,如emacs)。然后shell在搜索路径里寻找这些应用程序(搜索路径就是一个能找到可执行程序的目录列表)。如果输入的命令不是一个内部命令且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能找到命令,该内部命令或应用程序分解后将被系统调用并传给Linux 内核。

6.3 Hello的fork进程创建过程

一个进程,包括代码、数据和分配给进程的资源。fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

6.4 Hello的execve过程

使用execve就是一次系统调用,首先要做的将新的可执行文件的绝对路径从调用者(用户空间)拷贝到系统空间中。在得到可执行文件路径后,就找到可执行文件打开,由于操作系统已经为可执行文件设置了一个数据结构,就初始化这个数据结构,保存一个可执行文件必要的信息。可执行文件不是真正上能够自己运行的,需要有代理人来代理。在系统内核中有一个formats队列,循环遍历这个队列,看看现在被初始化的这个数据结构是哪个代理人可以代理的。如果没有就继续查看数据结构中的信息。按照系统配置了是否可以动态加载模块,加载一次模块,再循环遍历看是否有代理人前来认领。找到正确的代理人后,代理人首先要做的就是放弃以前从父进程继承来的资源。主要是对信号处理表,用户空间和文件大资源的处理。将父进程的信号处理表复制过来,放弃原来的用户空间。然后载入真正的程序代码和数据段,开辟堆栈,映射执行参数和环境变量。

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信号给用户进程,而用户进程以段错误退出。常见信号种类如下表所示。

ID 名称 默认行为 相应事件
2 SIGINT 终止 来自键盘的中断
9 SIGKILL 终止 杀死程序(该信号不能被捕获不能被忽略)
11 SIGSEGV 终止 无效的内存引用(段故障)
14 SIGALRM 终止 来自alarm函数的定时器信号
17 SIGCHLD 忽略 一个子进程停止或者终止

在程序运行过程中,键入Ctrl-C,会向当前进程发送一个SIGINT信号,从而使当前进程中断,如图6-1所示。而如果输入一个回车将会被忽略,如图6-2所示。
计算机系统大作业 程序人生-Hello’s P2P_第16张图片
图6-1 输入Ctrl-C的运行结果
计算机系统大作业 程序人生-Hello’s P2P_第17张图片
图6-2 输入回车的运行结果

如果在程序运行过程中输入Ctrl-Z,那么会发送一个 SIGTSTP 信号给前台进程组中的进程,从而将其挂起,如图6-3所示。
计算机系统大作业 程序人生-Hello’s P2P_第18张图片
图6-3 输入Ctrl-Z的运行结果

Ctrl-z后可以运行ps查看进程及其运行时间,结果如图6-4所示。可以运行jobs查看当前暂停的进程,结果如图6-5所示。可以运行fg使进程在前台执行,结果如图6-6所示。也可以使用kill杀死特定进程,如图6-7所示。

计算机系统大作业 程序人生-Hello’s P2P_第19张图片
图6-4 输入ps查看进程及其运行时间
图6-5  输入jobs查看当前暂停的进程
图6-5 输入jobs查看当前暂停的进程
计算机系统大作业 程序人生-Hello’s P2P_第20张图片
图6-6 输入fg使进程在前台执行
计算机系统大作业 程序人生-Hello’s P2P_第21张图片
图6-7 使用kill杀死特定进程

6.7 本章小结

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

第7章 hello的存储管理

7.1 hello的存储器地址空间

物理地址是用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。现代操作系统都提供了一种内存管理的抽像,即虚拟内存。进程使用虚拟内存中的地址,即虚拟地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。hello.s中使用的就是虚拟空间的虚拟地址。线性地址指虚拟地址到物理地址变换的中间层,是处理器可寻址的内存空间(称为线性地址空间)中的地址。程序代码会产生逻辑地址,或者说段中的偏移地址,加上相应段基址就成了一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址。而逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。它是Intel为了兼容,而将段式内存管理方式保留下来的产物。

逻辑(虚拟)地址经过分段(查询段表)转化为线性地址。线性地址经过分页(查询页表)转为物理地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。

7.3 Hello的线性地址到物理地址的变换-页式管理

CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。

7.4 TLB与四级页表支持下的VA到PA的变换

Core i7 MMU 使用四级的页表将虚拟地址翻译成物理地址。36位VPN 被划分成四个9 位VPN,分别用于一个页表的偏移量。具体结构如图7-1所示。
计算机系统大作业 程序人生-Hello’s P2P_第22张图片
图7-1 Core i7页表翻译

7.5 三级Cache支持下的物理内存访问

计算机系统大作业 程序人生-Hello’s P2P_第23张图片
图7-2 三级Cache支持下的物理内存访问

首先CPU发出一个虚拟地址,在TLB里面寻找。如果命中,那么将PTE发送给L1Cache,否则先在页表中更新PTE。然后再进行L1根据PTE寻找物理地址,检测是否命中的工作。这样就能完成Cache和TLB的配合工作。具体流程如图7-2所示。

7.6 hello进程fork时的内存映射

虚拟内存和内存映射解释了fork函数如何为每个新进程提供私有的虚拟地址空间。Fork函数为新进程创建虚拟内存。创建当前进程的的mm_struct, vm_area_struct和页表的原样副本,两个进程中的每个页面都标记为只读,两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)。在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存,随后的写操作通过写时复制机制创建新页面。

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行新程序hello.out的步骤:删除已存在的用户区域,创建新的区域结构,代码和初始化数据映射到.text和.data区(目标文件提供),.bss和栈映射到匿名文件,设置PC,指向代码区域的入口点。Linux根据需要换入代码和数据页面。

7.8 缺页故障与缺页中断处理

DRAM 缓存不命中称为缺页,即虚拟内存中的字不在物理内存中。缺页导致页面出错,产生缺页异常。缺页异常处理程序选择一个牺牲页,然后将目标页加载到物理内存中。最后让导致缺页的指令重新启动,页面命中。

7.9动态存储分配管理

在程序运行时程序员使用动态内存分配器(如malloc)获得虚拟内存。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的。分配器的类型包括显式分配器和隐式分配器。前者要求应用显式地释放任何已分配的块,后者在检测到已分配块不再被程序所使用时,就释放这个块。

动态内存管理的策略包括首次适配、下一次适配和最佳适配。首次适配会从头开始搜索空闲链表,选择第一个合适的空闲块。搜索时间与总块数(包括已分配和空闲块)成线性关系。会在靠近链表起始处留下小空闲块的“碎片”。下一次适配和首次适配相似,只是从链表中上一次查询结束的地方开始。比首次适应更快,避免重复扫描那些无用块。最佳适配会查询链表,选择一个最好的空闲块,满足适配,且剩余最少空闲空间。它可以保证碎片最小,提高内存利用率。

7.10本章小结

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

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

首先是设备的模型化。在设备模型中,所有的设备都通过总线相连。每一个设备都是一个文件。设备模型展示了总线和它们所控制的设备之间的实际连接。在最底层,Linux 系统中的每个设备由一个 struct device 代表,而Linux统一设备模型就是在kobject kset ktype的基础之上逐层封装起来的。设备管理则是通过unix io接口实现的。

8.2 简述Unix IO接口及其函数

linux 提供如下 IO 接口函数:
read 和 write – 最简单的读写函数;
readn 和 writen – 原子性读写操作;
recvfrom 和 sendto – 增加了目标地址和地址结构长度的参数;
recv 和 send – 允许从进程到内核传递标志;
readv 和 writev – 允许指定往其中输入数据或从其中输出数据的缓冲区;
recvmsg 和 sendmsg – 结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力。

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 本章小结

本章通过介绍hello中包含的函数所对应的unix I/O,大致了解了I/O接口及其工作方式,同时也了解了硬件设备的使用和管理的技术方法。

结论

用计算机系统的语言,逐条总结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设备进行处理,使得上层在使用的时候非常方便。概念上的抽象使得对概念的使用变得简单,这就是我对计算机系统设计实现的一个感悟。

附件

列出所有的中间产物的文件名,并予以说明起作用。

中间结果文件名 文件作用
hello.i 预处理得到的中间结果
hello.s hello.i编译后得到的汇编语言文本文件
hello.o hello.s汇编后得到的可重定位目标文件
hello.out 链接后得到的可执行目标文件

参考文献

为完成本次大作业你翻阅的书籍与网站等
[1] Shirlies. Vim与GCC和gdb完美组合.[OL].[2013-08-24]. http://www.cnblogs.com/Shirlies/p/3278937.html.
[2] 未成年书生. LINUX操作系统VIM的安装和配置.[OL].[2018-01-23]. https://jingyan.baidu.com/article/046a7b3efd165bf9c27fa915.html.
[3] July M. ubuntu中搜狗输入法的安装使用.[OL].[2017-08-03]. https://blog.csdn.net/Elaine_jm/article/details/76618912.
[4] 小楼一夜听春雨. linux 创建连接命令ln -s软链接.[OL].[2016-02-16]. https://www.cnblogs.com/kex1n/p/5193826.html.
[5]赵子清. vim模式与模式切换.[OL].[2015-07-03]. https://www.cnblogs.com/zzqcn/p/4619012.html.
[6] Eira_H. 64位Kali和Ubuntu安装32位运行环境和编译环境.[OL].[2018-07-05]. https://blog.csdn.net/eira_h/article/details/80925952.
[7] sky10001. linux下如何切换到root用户.[OL].[2017-01-17]. https://www.cnblogs.com/xinjie10001/p/6295020.html.

你可能感兴趣的:(计算机系统大作业 程序人生-Hello’s P2P)