计算机科学与技术学院

2021年5月

摘  要

本文介绍并详细分析了hello程序的完整的一生:预处理,编译,汇编,链接,在shell中运行,在进程中管理,接受I/O管理,终止回收。通过hello的一生,很好地对计算机系统的各部分知识进行了一个较为完整的回顾和梳理。

关键词:hello;计算机系统;完整的一生;                            

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献

第1章 概述

1.1 Hello简介

P2P是指from program to process。在linux系统中,源文件hello.c记过预处理、编译、汇编、链接生成可执行文件hello。在输入命令(./hello)启动该程序后,shell为其fork,创建子进程,然后hello就从程序变为了进程。

020是指from zero to zero。hello程序开始运行成为进程后,shell为其execve,映射虚拟内存,调用程序入口时开始载入物理内存,进入main函数执行目标代码,然后分配时间片,执行逻辑控制流。hello程序运行结束后,父进程回收该子进程,内核删除进程产生的相关数据和分配的数据结构,恢复程序执行前的状态,实现020。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

软件环境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上

开发与调试工具:gcc、edb、readelf、Visual Studio等

1.3 中间结果

hello.i:预处理后的文件,加载了头文件,进行了宏替换,完成条件编译。

hello.s:编译后的汇编文件,已经变为了汇编代码

hello.o:汇编后的可重定位目标文件,将汇编语言翻译成机器语言指令,并将指令打包成可重定位目标文件。

hello: 链接产生的可执行目标文件。

hello-oobj.txt:存储了hello.0的反汇编文件。

hello-obj.txt:存储了hello的反汇编文件。

1.4 本章小结

本章对实验内容进行了一个总体的概括,介绍了hello从产生到执行的大致经过,介绍了开发环境以及工具,简述了写论文过程中生成的中间文件。

(第1章0.5分)第2章 预处理

2.1 预处理的概念与作用

以下格式自行编排,编辑时删除

预处理的概念:预处理是在程序源代码被编译之前,由预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。这个过程并不对程序的源代码语法进行解析,但它会把源代码分割或处理成为特定的符号为下一步的编译做准备工作。

预处理的作用:

展开所有宏定义,处理#define x y格式的语句,用y替代x;

加载头文件,将#include格式的头文件插入到预编译指令的位置;

完成条件编译,处理#if,#ifdef格式的语句;

删除注释。

2.2在Ubuntu下预处理的命令

应截图,展示预处理过程!

gcc -E hello.c -o hello.i

图2.2.1预处理过程

2.3 Hello的预处理结果解析

图2.3.1预处理结果

打开hello.i文件后发现,文件的内容明显增加,有3000多行,但是仍为可以阅读的C语言文本文件。预处理器对源文件中的宏进行了宏展开,将系统头文件中的内容直接插入到了程序文本中,同时也对#define相应的符号进行了替换。

2.4 本章小结

简单介绍了文件在编译之前的预处理过程,即.c文件生成.i文本文件的过程,对预处理的含义、具体执行过程和预处理结果进行了解析。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

编译的概念:编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。

编译的作用:编译过程是指对预处理生存的.i文件进行一系列词法分析、语法分析、语义分析,最后优化后生成相应的汇编代码文件。

3.2 在Ubuntu下编译的命令

gcc -S hello.c -o hello.s

图3.2.1编译过程

3.3 Hello的编译结果解析

以下格式自行编排,编辑时删除

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析

3.3.1数据

  1. .字符串:printf函数中的字符串被存储在只读数据段中

图3.3.1.1printf中的字符串

  1. .局部变量:局部变量存储在寄存器或栈中。程序中有局部变量 int i。

i被存储在栈中,地址是%rbp-4.

图3.3.1.2局部变量i

  1. .main函数的参数argc:用户传递给main函数的参数argc,被放到了堆栈,它被放在了-20(%rbp)的位置。

图3.3.1.3参数argc

4).main函数的参数argv数组:数组中的每一个元素都是一个指向字符类型的指针,数组的起始地址存放于栈中-32(%rbp)的位置。分别获取了argv[2],argv[1],argv[3]的 地址,并将其地址处的内容存储到相应的寄存器中,用于printf函数和atoi函数的参数传递。

图3.3.1.4参数argv

3.3.2操作

1).算术操作:每次循环结束后执行i++的操作

图3.3.2.1循环加一操作

2).关系操作:

[1].判断argc是否等于4:

图3.3.2.2判断argc

  1. 判断for循环中的条件:

图3.3.2.3判断for条件

3).控制转移操作:本程序主要是指if条件分支引起的跳转以及for循环分支引起的跳转。它们主要通过关系操作cmpl进行比较设置条件码,之后根据条件码进行对应的跳转。

图3.3.2.4跳转

4).函数调用操作:在函数调用前,设置用于参数传递的寄存器的值,之后通过call指令进行函数的调用,本程序主要有printf函数的调用,sleep函数的调用,atoi函数的调用,exit函数的调用,以及getchar函数的调用。

图3.3.2.5函数调用

3.4 本章小结

介绍了编译的概念和作用。通过hello程序展示了c语言转换成汇编代码的过程。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。编译程序所做的工作。并对编译的过程进行进一步的分析,加深了对c语言的数据与操作,对c语言翻译成汇编语言的逻辑有进一步的掌握。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

汇编的概念: 汇编是通过汇编器,把汇编语言翻译成机器语言的过程。

汇编的作用:将汇编语言程序翻译成机器指令,并将这些指令打包成一种叫做可重定位目标程序的格式,并将这个结果保留.o目标文件中。

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

图4.2.1汇编过程

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

  1. 查看ELF头:

ELF Headers:以 16B 的序列 Magic 开始,Magic 描述了生成该文件的系统的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、字节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量等信息。

图4.3.1ELF头信息

  1. 查看节头部表:可知道此elf文件中共有14个节,包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,各节的读写权限等。

图4.3.2节头部表信息

3.查看符号表:symtab:存放程序中定义和引用的函数和全局变量的信息。

Ndx:ABS表示不该被重定位、UND表示未定义(在这个地方被引用,但是在其他地方进行定义)、COM表示未初始化数据(未初始化的全局变量)

Bind:绑定属性:全局符号、局部符号

Type:符号类型:函数、数据、源文件、节、未知

Value:.o.文件中是偏移量

图4.3.3符号表信息

4.查看重定位条目:描述了需要进行重定位的各种信息,包括需要进行重定位符号的位置、重定位的方式、名字。

图4.3.4重定位条目

4.4 Hello.o的结果解析

将反汇编后的指令存储在hello-oobj中。

图4.4.1汇编指令

对比:

1.代码左边多了机器码;

2.hello.s中的操作数为十进制,hello.o反汇编代码中的操作数为十六进制;

3.call跳转指令,在hello.s文件中,直接加上跳转函数名,在反汇编文件中,加上了跳转的相对偏移地址,函数在链接之后才能确定执行的地址,因此在.rela.text节中为其添加了重定位条目。说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.5 本章小结

     通过将hello.s汇编指令转换成hello.o机器指令,通过readelf查看hello.o的ELF、反汇编的方式查看hello.o反汇编的内容,比较其与hello.s之间的差别,以及学习汇编指令和机器指令之间的映射关系,更深刻地理解了汇编语言到机器语言实现地转变,和这过程中为链接做出的准备。

(第4章1分)

5 链接

5.1 链接的概念与作用

链接的概念:链接是将各种不同文件的代码和数据部分收集(符号解析和重定位)起来并组合成一个单一文件的过程。

链接的作用:令源程序节省空间而未编入的常用函数文件进行合并,生成可以正常工作的可执行文件。这令分离编译成为可能,节省了大量的工作空间。

5.2 在Ubuntu下链接的命令

命令:

ld-ohello-dynamic-linker/lib64/ld-linux-x86-64.so.2/usr/lib/x86_64-linux-gnu/crt1.o/usr/lib/x86_64-linux-gnu/crti.ohello.o/usr/lib/x86_64-linux-gnu/libc.so/usr/lib/x86_64-linux-gnu/crtn.o

图5.2.1链接过程

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

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

1.查看hello的ELF头:

图5.3.1ELF头信息

由此可知hello文件类型为EXEC,即是一个可执行目标文件,文件中共有27个节。

2.查看节头部表:

图5.3.2节头部表信息

由此可知文件中各个段的基本信息,从Size获取各个段的大小,从Address可以获得各个段的起始地址即为程序被载入虚拟地址后各段的起始地址,从offset可以获得各个段在程序中的偏移量。

3.查看符号表:

图5.3.3符号表信息

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

根据程序头表查看各个段的信息:

图5.4.1起始信息

代码段从init 401000开始:

图5.4.2代码段

数据段:

图5.4.3数据段

stack:

 图5.4.4stack部分信息

5.5 链接的重定位过程分析

将hello的反汇编文件存储在hello-obj.txt中:

图5.5.1反汇编信息(以上这几张图是一起的)

对比hello.o:

1.hello.o中的相对偏移地址到了hello中变成了虚拟内存地址

2. hello中相对hello.o增加了许多的外部链接来的函数,比如exit、printf等。

3. hello相对hello.o多了很多的节类似于.init,.plt等

4. hello.o中跳转以及函数调用的地址在hello中都被更换成了虚拟内存地址

查看hello.o的重定位条目并据此分析hello的重定位过程:

图5.5.2重定位条目

1.重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。

2,重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。

3.重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。

5.6 hello的执行流程

图5.6.1edb中信息展示

0x401000 <_init>

0x401090 <puts@plt>

0x4010a0 <printf@plt>

0x4010b0 <getchar@plt>

0x4010c0 <atoi@plt>

0x4010d0 <exit@plt>

0x4010e0 <sleep@plt>

0x4010f0 <_start>

0x401120 <_dl_relocate_static_pie>

0x401125 <main>

0x4011c0 <__libc_csu_init>

0x401230 <__libc_csu_fini>

0x401238 <_fini>

5.7 Hello的动态链接分析

图5.7.1got和plt的信息

在edb中找到0x403ff0和0x404000的位置

图5.7.2调用init函数前的内容

一开始地址的字节都为0,调用_init函数之后GOT内容产生变化,指向正确的内存地址,下一次调用跳转时可以跳转到正确位置。

图5.7.3调用init函数后内容的变化

5.8 本章小结

本章介绍了链接的概念及作用,在Ubuntu下链接的命令行,并对hello的elf格式进行了详细的分析对比,并通过反汇编hello文件,将其与hello.o反汇编文件对比,详细了解了重定位过程,遍历了整个hello的执行过程,在最后对hello进行了动态链接分析,使得对hello的链接过程有了一个深刻的理解和体会。

(第5章1分)

6 hello进程管理

6.1 进程的概念与作用

进程的概念:一个执行中程序的实例。

进程的作用:进程主要为用户提供了下列两个假象

(1)一个独立的逻辑流,提供程序独占使用处理器的假象。

(2)一个私有的虚拟地址空间,提供程序独占使用整个系统内存的假象。

每次用户通过向shell 输入一个可执行目标文件的名字,运行程序时, shell 就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。

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

以下格式自行编排,编辑时删除

Shell-bash的作用:shell是一个命令解释器,它解释用户输入的命令并把它们送到内核,用于用户和系统的交互。

处理流程:

1.Shell首先从命令行中找出特殊字符(元字符),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。Shell中的元字符如下:SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |

2.程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。

3.当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块tokens。

4.Shell对~符号进行替换。

5.Shell对所有前面带有$符号的变量进行替换。

6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用$(command)标记法。

7.Shell计算采用$(expression)标记的算术表达式。

8.Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。

9.Shell执行通配符* ? [ ]的替换。

10.shell把所有从处理的结果中用到的注释删除,並且按照下面的顺序实行命令的检查:A. 内建的命令B. shell函数(由用户自己定义的)C. 可执行的脚本文件(需要寻找文件和PATH路径)

11.在执行前的最后一步是初始化所有的输入输出重定向。

12.最后,执行命令。

6.3 Hello的fork进程创建过程

当在终端中输入./hello 学号 姓名 时间(比如120L0221 yqb 5)时。shell会通过上述流程处理,首先判断出它不是内置命令,所以会认为它是一个当前目录下的可执行文件hello。在加载此进程时shell通过fork创建一个新的子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库和用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。fork调用一次,返回两次,子进程与父进程有不同的pid。父进程和子进程是并发运行的独立进程,内核可以任意方式交替执行他们的逻辑控制流中的指令,所以这会导致我们不能简单的凭直觉判断指令执行的顺序,应按照拓扑排序序列执行。

6.4 Hello的execve过程

子进程调用execve函数(传入命令行参数)在当前进程的上下文中加载并运行一个新程序即hello程序。hello子进程通过execve系统调用启动加载器,加载器删除子进程所有的虚拟地址段,并创建一组新的代码、数据、堆段。新的栈和堆段被初始化为0。通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk),新的代码和数据段被初始化为可执行文件中的内容。最后加载器跳到_start地址,它最终调用hello的main 函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,此时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存。

6.5 Hello的进程执行

上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由 通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内 核数据结构等对象的值构成。

进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被强占的进程。这种决策就叫调度(是由内核中的调度器的代码处理的)。当内核调度一个新的进程的运行的时,内核就会抢占当前进程,通过使用一种上下文切换的较为高层的形式异常控制流将控制转移到新的进程。具体如下:内核首先保存当前进程的上下文,之后恢复之前被抢占的进程保存的上下文,将控制传递给这个恢复的进程。

用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

具体分析hello的进程执行:初始时,运行hello进程,处于用户模式。调用系统函数sleep后,进入内核模式,执行另一个进程,此时间片停止。倒计时结束后,发送中断信号,转回用户模式,继续执行hello进程,重复此过程直到结束。

6.6 hello的异常与信号处理

以下格式自行编排,编辑时删除

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

异常种类:中断、陷阱、故障、终止。

  1. 中断:中断是异步发生的,来自处理器外部IO设备的信号(区别于同步异常:执行一条指令的结果),它不是由任何一条专门的指令造成的,而是来自I/O设备的信号,中断处理程序对其进行处理,返回后继续执行调用前待执行的下一条代码。
  2. 陷阱:陷阱是同步异常,是执行一条指令的结果。陷阱最重要的用途是在用户程序和内核之间提供系统调用接口。陷阱总返回到当前指令的下一条指令。
  3. 故障:故障由错误引起,它可能被故常处理程序修正,如果修正成功,将返回到当前正在执行的指令,重新执行。否则处理程序返回到内核的abort历程,将终止故障程序。故障的一个典型是缺页异常。
  4. 终止:由不可恢复的知名错误造成的结果,处理程序将返回到内核中的abort例程,终止应用程序。

可能会产生很多信号,根据信号种类执行此信号的默认行为,如果使用了signal函数则另行讨论。

回车:不会产生什么变化,只是会空行而已。

图6.6.1程序执行时按下回车

Ctrl-z:会停止。

图6.6.2程序执行时按下ctrl-z

Ctrl-c:终止进程。

图6.6.3程序执行时按下ctrl-c

Ctrl-z后运行ps: 输入ctrl-z,内核会发送SIGSTP。SIGSTP默认挂起前台hello作业,但 hello进程并没有回收,而是运行在后台下,通过ps指令可以对其进行查看。

图6.6.4ctrl-z后运行ps

Ctrl-z后运行jobs:

图6.6.5ctrl-z后运行jobs

Ctrl-z后运行pstree:

图6.6.5ctrl-z后运行pstree

Ctrl-z后运行fg:继续刚才停止的进程。

图6.6.5ctrl-z后运行fg

Ctrl-z后运行kill:

图6.6.5ctrl-z后运行kill

6.7本章小结

本章了解了进程的概念及作用和shell的作用和处理流程,分析了hello进程的执行过程以及fork和execve的作用,了解信号处理和hello进程如何在内核模式和用户模式中反复跳跃运行的。

(第6章1分)

7 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:是指由程序hello产生的与段相关的偏移地址部分(hello.o)。

线性地址:是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生逻辑地址,或者说是(即hello程序)段中的偏移地址,它加上相应段的基地址就生成了一个线性地址。

虚拟地址:也就是线性地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的,是hello中的虚拟地址。

物理地址:是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么hello的线性地址会使用页目录和页表中的项变换成hello的物理地址;如果没有启用分页机制,那么hello的线性地址就直接成为物理地址了。

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

一个逻辑地址由两部份组成,段标识符: 段内偏移量。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,Base字段表示包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。

先检查段选择符中的TI字段,以决定段描述符保存在哪一个描述符表中;

由于一个段描述符是8字节长,因此她在GDT或LDT内的相对地址是由段选择符的最高13位的值乘以8得到;

Base + offset,得到要转换的线性地址。

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

页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

页表就是一个页表条目的数组。虚拟地址空间中的每个页在页表中一个固定的偏移量处都有一个PTE。PTE包含了一个有效位和一个n位字段,有效位表明了该虚拟页当前是否被缓存在DRAM中。因为DRAM是全相联的,所以任意物理页都可包含任意虚拟页。

n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO)和一个n-p位的虚拟页号(VPN)。MMU利用VPN来选择适当的PTE.将页表条目中的物理页号和VPO串联起来就是相应的物理地址。因为物理和虚拟页面都是P字节的,所以物理页面偏移和虚拟页面偏移是相同的。

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

TLB是MMU中的一个关于PTE的小的缓存,有了TLB后,VPN又分为了TLB标记(TLBT)和TLB索引(TLBI),TLB的机制与全相联的cache的机制相同,如果TLB有T = 2s个组,那么TLB索引(TLBI)是由VPN的s个最低位组成的,TLB标记(TLBT)是由VPN中剩余的位组成。

引入多级页表后,VPN被划分成了多个区域,例如使用k级页表,VPN被划分成了k个VPN,每个VPNi都是一个到第i级页表的索引,第k个VPN中存储着VPN对应的PPN。

CPU产生一个VA,MMU在根据VPN在TLB中搜索PTE,若命中,MMU取出相应的PTE,根据PTE将VA翻译成PA;若没命中,则通过多级页表查询PTE是否在页中,若在页中,找到对应的PIE,MMU将VA翻译成PA,若没有在页中,则进行缺页处理

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

CPU发送一条虚拟地址,随后MMU按照上述操作获得了物理地址PA。根据cache大小组数的要求,将PA分为CT(标记位)CS(组号),CO(偏移量)。根据CS寻找到正确的组,比较每一个cacheline是否标记位有效以及CT是否相等。如果命中就直接返回想要的数据,如果不命中,就依次去L2,L3,主存判断是否命中,当命中时,将数据传给CPU同时更新各级cache的cacheline(如果cache已满则要采用换入换出策略)。

7.6 hello进程fork时的内存映射

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

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

7.7 hello进程execve时的内存映射

以下格式自行编排,编辑时删除

1.删除已存在的用户区域:删除当前进程虚拟地址的用户部分中已存在的区域结构。

2.映射私有区域:为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的,写时复制的。代码和数据区域被映射为hello文件中的.test和.data区。bss区域是请求二进制0的,映射到匿名文件,其大小包含在a.out当中。栈和堆区域也是请求二进制零的,初始长度为零。

3.映射共享区域hello 程序与共享对象 libc.so 链接,libc.s是动态链 接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。

4.设置程序计数器PC:execve做的最后一件事情就是设置当前进程上下文中的程序计数器,指向代码区域的入口点。

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

缺页故障的概念:缺页故障:当CPU想要读取虚拟内存中的某个数据,而这一片数据恰好存放在主存当中时,就称为页命中。相对的,如果DRAM缓存不命中,则称之为缺页。如果CPU尝试读取一片内存而这片内存并没有缓存在主存当中时,就会触发一个缺页异常。

缺页中断处理:发生缺页故障时,处出发缺页异常处理程序,缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。缺页处理程序调入新的页面,并更新内存中的PTE,缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经缓存在物理内存中,所以就会命中。

7.9动态存储分配管理

动态储存分配管理使用动态内存分配器来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。动态内存分配主要有两种基本方法与策略:

带边界标签的隐式空闲链表分配器管理:

带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的

额外填充以及一个字的尾部组成的。

隐式空闲链表:在隐式空闲链表中,因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。其中,一个设置了已分配的位而大小为零的终止头部将作为特殊标记的结束块。

当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配合最佳适配。分配完后可以分割空闲块减少内部碎片。同时分配器在面对释放一个已分配块时,可以合并空闲块,其中便利用隐式空闲链表的边界标记来进行合并。

显式空间链表管理:

显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。

显式空闲链表:在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。

7.10本章小结

本章介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,在指定环境下介绍了 VA 到 PA 的变换、物理内存访问,还介绍 hello 进程 fork 时的内存映射、 execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

(第7章 2分)

8 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

一个Linux文件就是一个m个字节的序列:B0,B1,B2….Bk,….,Bm-1

所有的I/O设备都被模型化为文件而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

以下格式自行编排,编辑时删除

Unix IO接口:

打开文件。一个应用程序通过要求内核打开相应的文件,来宣告他想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,他在后续对此文件的所有操作中标识这个文件。

Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准出错(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。

函数:

1.open()函数

功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。

函数原型:int open(const char *pathname,int flags,int perms)

参数:pathname:被打开的文件名(可包括路径名如"dev/ttyS0")flags:文件打开方式,

返回值:成功:返回文件描述符;失败:返回-1

2.close()函数

功能描述:用于关闭一个被打开的的文件

所需头文件: #include <unistd.h>

函数原型:int close(int fd)

参数:fd文件描述符

函数返回值:0成功,-1出错

3.read()函数

功能描述: 从文件读取数据。

所需头文件: #include <unistd.h>

函数原型:ssize_t read(int fd, void *buf, size_t count);

参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read操作,应该读多少数量的字符。

返回值:返回所读取的字节数;0(读到EOF);-1(出错)。

4.write()函数

功能描述: 向文件写入数据。

所需头文件: #include <unistd.h>

函数原型:ssize_t write(int fd, void *buf, size_t count);

返回值:写入文件的字节数(成功);-1(出错)

5.lseek()函数

功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。

所需头文件:#include <unistd.h>,#include <sys/types.h>

函数原型:off_t lseek(int fd, off_t offset,int whence);

参数:fd;文件描述符。offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)

返回值:成功:返回当前位移;失败:返回-1

8.3 printf的实现分析

[转]printf 函数实现的深入剖析 - Pianistx - 博客园

首先查看printf函数:

图8.3.1printf函数源码

发现它调用了两个函数,分别是vsprintf和write。

再看vsprintf函数:

图8.3.2vsprintf函数源码

vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

再看write函数:

图8.3.3write函数

write,顾名思义:写操作,把buf中的i个元素的值写到终端。

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

首先查看getchar函数:

图8.4.1getchar函数源码

通过分析可知,getchar调用了一个read函数,将整个缓冲区都读到了buf里面,然后返回缓冲区的长度。如果buf长度为0,getchar才会调用read函数,否则是直接将保存的buf中的最前面的元素返回。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章主要介绍了Linux的IO设备管理方法、Unix IO接口及其函数,分析了printf函数和getchar函数。

(第8章1分)

结论

用计算机系统的语言,逐条总结hello所经历的过程。

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。

hello所经历的过程:

1.预处理:将hello.c调用的所有外部的库拓展到hello.i文件中,宏命令被处理,但总体其实没什么改变;

2.编译:将hello.i编译得到汇编代码文件hello.s,被编译为汇编代码;

3.汇编:将hello.s汇编成为二进制可重定位目标文件hello.o,被编译为机器代码;

4.链接:将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello;

5.运行:shell中运行hello(我使用的是./hello 1111 yqb 1);

6.创建子进程:shell进程调用fork函数创建子进程;

7.运行程序:shell调用execve函数,execve调用启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入 main函数;

8.执行指令:CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流;

9.访问内存:MMU将程序中使用的虚拟内存地址通过页表映射成物理地址;

10.动态申请内存:printf会调用malloc向动态内存分配器申请堆中的内存;

11.信号:如果运行途中键入Ctrl-C、Ctrl-Z则调用shell的信号处理函数分别停止、挂起;

12.结束:shell父进程回收子进程,内核删除为这个进程创建的所有数据结构。

我对计算机系统的感悟:

以前使用电脑时从来没有深入探究过一个程序到底是怎么运行的,只是动动鼠标或者输入一段命令就可以看到结果,但通过了本学期对计算机系统的学习后,我了解了每天都在使用的计算机却有如此复杂的体系结构,从最底层的硬件设计,再到指令的运作,再到进程和存储的逻辑,一个简单的hello程序却也需要如此复杂的一套运作流程,着实令我感到惊叹。

但同时,我认为计算机系统确实有很多知识是晦涩难懂并且非常的抽象的,并不是看看书本就可以理解的,只能是通过查阅大量的资料和询问老师才有可能弄懂,计算机系统的奥秘还是需要我们细细体会。

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

附件

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

hello.i:预处理后的文件,加载了头文件,进行了宏替换,完成条件编译。

hello.s:编译后的汇编文件,已经变为了汇编代码

hello.o:汇编后的可重定位目标文件,将汇编语言翻译成机器语言指令,并将指令打包成可重定位目标文件。

hello: 链接产生的可执行目标文件。

hello-oobj.txt:存储了hello.0的反汇编文件。

hello-obj.txt:存储了hello的反汇编文件。

(附件0分,缺失 -1分)

参考文献

  1. printf函数实现的深入剖析(https://www.cnblogs.com/pianist/p/3315801.html)
  2. 计算机系统PPT
  3. Randal E. Bryant, David R. O'Hallaon. 深入理解计算机系统. 第三版. 北京市:机械工业出版社[M]. 2018: 1-737
  4. 程序的链接(一):链接的概述(https://www.jianshu.com/p/b7e44f749211)
  5. 计算机的异常控制:中断、陷阱、故障、终止、进程上下文切换、信号
  6. 预处理与编译(https://www.cnblogs.com/noticeable/p/9310798.html)
  7. Linux下 可视化 反汇编工具 EDB 基本操作知识

(往CSDN上传图片是真费劲!)

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

计算机系统大作业-程序人生相关推荐

  1. HIT 深入理解计算机系统 大作业 程序人生-Hello’s P2P

    HIT 深入理解计算机系统 大作业 程序人生-Hello's P2P 本论文旨在研究 hello 在 linux 系统下的整个生命周期.结合 CSAPP 课本, 通过 gcc 等工具进行实验,从而将课 ...

  2. 【2022】哈工大计算机系统大作业——程序人生Hello’s P2P

    2022哈工大计算机系统大作业--程序人生Hello's P2P 摘要 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概 ...

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

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机 学    号 120L021716 班    级 2003005 学       生 蔡泽栋 指 导 ...

  4. 哈工大2022计算机系统大作业---程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机类 学    号 120L021923 班    级 2003006 学       生 甄自镜 指 导 ...

  5. 哈工大计算机系统大作业——程序人生-Hello’s P2P

    计算机系统 大作业 题          目程序人生-Hello's P2P 专          业 计算机科学与技术 学       号120L022401 班          级 200300 ...

  6. 哈工大2022年春季学期计算机系统大作业——程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 人工智能(未来技术) 学   号 7203610716 班   级 20WJ102 学       生 孙铭蔚 ...

  7. 哈尔滨工业大学计算机系统大作业--程序人生

    计算机系统   大作业 题     目  程序人生-Hello's P2P      专       业   计算机科学与技术        学    号        2021110xxx      ...

  8. 2021春深入理解计算机系统大作业——程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 1190200608 班    级 1903004 学       生 琚晓龙 指 导 ...

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

    计算机系统大作业 ** 由于采用静态部署,需要看图片详细分析的小伙伴请移步个人博客网站:** 个人博客 题目:程序人生-Hello's P2P 学号: 姓名:熊峰 摘要: hello程序作为最简单的. ...

  10. 哈工大 2021春 计算机系统 大作业程序人生

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算机 学 号 1190200828 班 级 1936601 学 生 赵英帅 指 导 教 师 刘宏伟 计算机科学与技术学院 202 ...

最新文章

  1. 压力测试工具Ab简介
  2. 手机充值接口 php,首信易支付,话费充值接口
  3. python最简单单例模式_Python单例模式的4种实现方法 | 学步园
  4. UTF-8编码的字符串拆分成单字、获取UTF-8字符串的字符个数的代码及原理(c++实现)...
  5. 最全目标检测相关资料整理 (目标检测+数据增强+卷价神经网络+类别不均衡...)
  6. oracle执行计划没有执行索引,oracle理解执行计划之索引相关
  7. 查找命令find和其他常用查找命令
  8. Oracle P6培训系列:04创建EPS结构
  9. B 站视频下载器(BV号、dash、音视频分离)
  10. 本地iis部署之后网页无法打开
  11. 算法面试中:时间复杂度和空间复杂度是什么?
  12. Postman之脚本介绍( pre-request-script )
  13. 最新云智推任务提交版拉新任务分销系统源码+功能强大
  14. 将数据库转换为word文档
  15. 【CodeForces727E/CF727E】Games on a CD (字符串哈希)
  16. 以太坊五岁了,它现在还好吗?
  17. 有效沟通bic法则_猎头支招:工作中有效沟通的法则
  18. mysql实现vpd_VPD(Virtual Private Database) 简单演示
  19. 企业飞信初探:无需APP也能聊天
  20. 使用SSH连接本机WSL系统

热门文章

  1. 扫雷游戏网页版_世界排名前30,六成都是中国人:2020年,沉迷「扫雷」的玩家是怎样一群人?| 探寻游戏意义...
  2. 小米随身WIFI去掉云U盘的方法
  3. FileUtils(文件读写操作工具类)
  4. 修改WiFi/BT模组—R329智能语音开发板入门
  5. 摸鱼必备游戏(上班族专用斗地主)
  6. 原生JS和NodeJS之间的区别
  7. idea中 Java xml注释缩进问题 解决方案
  8. 实变函数与计算机有关系吗,《复变函数与实变函数》
  9. java中json转对象_Java开发中json使用,各对象与json相互转换
  10. 计算机网络——数据包抓取与分析