hello的一生

计算机系统

大作业

题 目 程序人生-Hello’s
P2P

专 业 数学类(人工智能)

学   号 1181200111

班   级 1812001

学 生 薄轶博

指 导 教 师 史先俊

计算机科学与技术学院

2020年3月

摘 要

摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

摘要:本文通过在linux系统下对hello.c程序的P2P,020过程中的预处理、编译、汇编、链接等具体阶段以及进程管理、存储管理和IO管理进行分析讨论,深入理解计算机的linux系统以及上述操作的实现方法。

关键词:程序的一生;P2P;020;

(摘要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的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

P2P: 程序hello在计算机中经过预处理cpp、编译ccl、汇编as、链接ld等操作后生成一个可执行的文件hello,当我们再次运行时,shell将会通过fork、execve运行hello,通过虚拟内存映射、分配空间、内核为其划分时间片,子进程,这个时候我们的hello就从一个程序program变成了一个进程process,这就是P2P过程。

020: 在上述过程结束后,shell会通过父进程bash来回收已经终止的hello进程,清理内存中的数据以及hello的相关结构。这个时候,我们的hello从无到有,再到被计算机清空,就是020过程。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

硬件环境:Inter® Core™ i7-9750H
CPU @ 2.60GHz 2.59 GHz; 16G RAM

软件环境:Ubuntu 18.04; Windows10 64位;

开发与调试工具:GDB; EDB; OBJDUMP; READELF; CodeBlocks;
vim/gedit+gcc; Visual Studio 2019; 记事本; Hexeditor;

1.3 中间结果

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

hello.c 原hello C程序文件

hello.i hello.c预处理后生成的文本文件

hello.s hello.i编译后生成的文本文件

hello.o hello.s汇编后生成的目标文件

hello hello.o链接后生成的可执行目标文件

hello.elf hello.o的ELF格式文件

hello_.elf hello的ELF格式文件

hello_obj hello.o的反汇编文件

hello_objdump hello的反汇编文件

1.4 本章小结

   本章主要介绍了hello在计算机中的P2P,020过程,并介绍了完成本论文使用的软硬件环境及开发调试工具,展示了过程中生成的中间结果文件及其各自的作用。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序

作用:对程序中可能存在的宏定义(#define)进行数值替换;

如果程序中包含(#include)有其他文件,则进行包含文件的操作;

帮助解释执行某些可能存在的需要在特定的条件满足时才可以进行编译的语句

2.2在Ubuntu下预处理的命令

命令:gcc -E hello.c -o hello.o

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

2.3 Hello的预处理结果解析

打开hello.i文件,由于在预处理阶段,#include的文件会被扩展,在hello.c中扩展的即为stdio.h文件,并将扩展的内容加入到程序文件中,最终得到hello.i文件。此时的hello.i文件已多达3042行,其中从第3028行开始才是我们的初始代码。

2.4 本章小结

本章主要介绍了预处理的概念及其作用,并通过指令实际对hello.c程序进行预处理操作,查看了生成的hello.i文件,了解到预处理阶段对程序文件的扩展等操作,从而对预处理阶段有了更深的了解。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

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

作用:将使用高级语言的文本文件(hello.i)翻译成为同等的使用汇编语言的文本文件(hello.s)

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s

应截图,展示编译过程!

3.3 Hello的编译结果解析

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

3.3.1 数据

  1. main函数内局部变量

int i

  i是main函数中的局部变量,存储在栈区,其地址为-4(%rbp),运行时才对其进行赋值。

  2. main函数传入的参数

int argc

  argc是传入的参数,存储在%edi中,在栈中进行使用。

  3. 主函数

int main

  main是定义的主函数,存储在.text节中

  4. 字符串常量

字符串常量存储在.rodata中,其中中文字符被以\三位数的方式存储。

  1. main函数传入的参数数组 char* argv[]

argv是传入的字符串数组,存储在%rsi中,在程序运行时,将其放入栈中通过字符串首地址完成使用

3.3.2 赋值

  1. for循环中的

i=0

  在运行时才对其赋予初值。

3.3.3 类型转换

  1. for循环中的atoi函数

  

  将argv的地址赋给%rax,随后通过addq指令进行四字加法,通过movq指令进行四字传送,最终赋给%rdi,并通过call atoi@PLT指令使用。完成从字符串到整型数的转换。

3.3.4 算术操作

  1. for循环中的

i++

  在栈中通过addl指令实现i的自加。

3.3.5 关系操作

  1. if中的

argc!=4

  通过cmpl指令将argc的值与4作比较,来判断下一步执行哪条语句。

  2. for循环中的

i<8

  通过cmpl指令将i的值与7作比较,来判断循环是否已经结束。

3.3.6 控制转移

  1. if(argc!=4)

  

  通过cmpl指令将argc的值与4作比较,同时通过je指令判断是否跳转,如果argc与4相等,则设置标志位ZF=0,则je指令判断后不执行if后的语句,进入L2。

  2. for(i=0;i<8;i++)

  

  通过addl指令来实现i的自加操作,随后使用cmpl指令将i的值与7比较,如果判断不退出循环,则通过jle指令跳回L4。

3.3.7 函数操作

  1. main函数

  

  首先main函数传入argc, argv两个参数,并分别存储与%edi与%rsi中,而后在运行程序时进行调用。

  

  最终将%eax赋值为0,而后返回该值。

  2. printf函数

(1) if 分支中的printf

将字符串的地址传给%rdi,而后通过call puts@PLT指令直接打印

(2) for循环中的printf

将字符串的地址传给%rdi,而后通过call printf@PLT指令传参。

  3. exit函数

  

  将%edi赋值为1,而后通过call exit@PLT指令使用。

  4. sleep函数

  

  将%eax中的值赋给%edi,而后通过call sleep@PLT指令使用。

  5. atoi函数

  

  将argv的地址赋给%rax,随后通过addq指令进行四字加法,通过movq指令进行四字传送,最终赋给%rdi,并通过call atoi@PLT指令使用。

  6.getchar函数

  

  通过call getchar@PLT指令使用

3.4 本章小结

本章主要介绍了编译的概念及其作用,并通过指令实际对hello.c程序进行编译操作生成hello.s文件,使代码从高级语言转化为汇编语言。并且对编译过程中,对C语言中的数据与操作的编译进行了各个解析,从而对编译阶段有了更深的了解。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

概念:汇编器(as)将文本文件(hello.s)翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件(hello.o)中。

作用:将使用汇编语言的文本文件(hello.s)翻译成为同等的cpu可执行的机器语言指令结果,并将此结果打包为可重定位目标程序,保存在目标文件(hello.o)中。

4.2 在Ubuntu下汇编的命令

命令:gcc -c hello.s -o hello.o

应截图,展示汇编过程!

4.3 可重定位目标elf格式

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

使用readelf -a hello.o > hello.elf指令生成hello.elf文件

查看hello.elf文件

ELF头

ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,以及ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移、节头部表中条目的大小和数量等帮助链接器语法分析和解释目标文件的信息。

节头部表

节头部表中包含了目标文件中各个节的类型、地址、大小、偏移等信息,以及可以进行的操作

.rela.text重定位表

.rela.text重定位表中包含.text节中的位置的列表,含有该.text中所需要进行重定位操作的信息,当目标文件与其他文件由链接器(ld)进行结合时,这些位置将会被修改。

其中,Offset是需要重定位的信息偏移位置;Type显示重定位的类型;Symbol是被重定位时指向的符号;Append部分重定位过程中需使用它作偏移调整。

.rela.eh_frame重定位表

.rela.eh_frame重定位表中包含着.eh_frame节的重定位信息

.symtab符号表

  符号表中包含程序中所使用的全局变量以及函数的信息

4.4 Hello.o的结果解析

objdump -d
-r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

使用objdump -d -r hello.o > hello_obj指令生成文件

查看hello_obj文件及hello.s文件

通过对比查看可以发现hello.s中的汇编代码与hello_obj中的反汇编代码大致一样,但是指令有所不同。

汇编语言文件(hello.s)中列出了每个段的段名,而在反汇编语言文件(hello_obj)中却是明确的地址。

汇编语言文件(hello.s)中使用的是10进制,而在反汇编语言文件(hello_obj)中使用的是16进制。

汇编语言文件(hello.s)中调用函数时,是直接引用函数名称,而在反汇编语言文件(hello_obj)中,通过重定位信息,在链接之后获取函数地址再调用。

4.5 本章小结

本章主要介绍了汇编的概念及其作用,并通过指令实际对hello.s程序进行汇编操作生成hello.o文件,并对hello.o文件的elf格式进行了查看和内容解析,同时对比发现了hello.s与hello.o文件中的细微差异。从而对汇编阶段有了更深的了解。

(第4章1分)

第5章 链接

5.1 链接的概念与作用

注意:这儿的链接是指从 hello.o 到hello生成过程。

概念:
将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。链接可以在编译时执行,即源代码被翻译成机器代码时;可以在加载时执行,即程序被加载器加载到内存并执行时;可以在运行时执行,即由应用程序来执行。

作用:将一个目标文件(hello.o)或多个目标文件外加库链接合并为一个可执行目标文件(hello),可执行目标文件可以被加载到内存中,由系统执行。

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

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

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

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

通过readelf -a hello > hello_.elf指令生成hello的ELF格式文件(hello_.elf)

  1. ELF Header

  2. Section Headers

  3. Program
    Headers

  4. Segment
    Section

Dynamic Section

Relocation Section

Symbol Table

Version Symbols Section

Version needs Section

5.4 hello的虚拟地址空间

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

.PDHR 起始位置为0x400040 大小为0x230

.INTERP 起始位置为0x400270 大小为0x1c

.LOAD 起始位置为0x400000 大小为0x530

.LOAD 起始位置为0x401000 大小为0x1bd

.LOAD 起始位置为0x402000 大小为0x13c

.LOAD 起始位置为0x403e50 大小为0x1fc

.DYNAMIC 起始位置为0x403e50 大小为0x1a0

.GNU_STACK 起始位置为0x400000 大小为0x0

.GNU_RELRO 起始位置为0x403e50 大小为0x1b0

  1. .NOTE 起始位置为0x40028c 大小为0x20

5.5 链接的重定位过程分析

objdump -d -r
hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

通过objdump -d -r hello > hello_objdump 指令生成文件

查看hello_obj与hello_objdump

通过对比查看发现,hello比hello.o多了许多函数,同时main函数的地址也发生了改变,此外,链接器将hello.o中的程序在虚拟内存中的地址加上偏移量,重定位后就可以得到hello中的程序的具体地址。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

dl_init前:

dl_init后:

通过动态链接器完成hello的动态链接,同时此过程还需要使用GOT表和PLT表。但编译器无法在运行时预测函数地址,所以需要重定位,等待动态链接器处理,通过GOT和PLT的交互来实现。调用定义在共享库中的函数就会有GOT和PLT。其中GOT是数据段的一部分,PLT是代码段的一部分。随后调用动态链接器进行动态链接。动态链接的最后,将会修改GOT表项(上图)来保证函数能够正常调用。

5.8 本章小结

本章主要介绍了链接地概念及其作用,同时对hello的ELF格式文件进行了了解,分析了hello中的重定位过程,对hello的执行流程及动态链接也有了一定的认识。

(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

作用:使CPU被科学有效地划分成多个部分以并行地运行多个进程;同时使计算机看起来是在单独且专门地使用所有cpu等资源运行我们的程序。

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

作用:Shell为用户提供一个命令行界面,使得用户可以在这个界面中输入shell命令来完成与计算机的交互,从而操作计算机。

处理流程:在用户输入指令后,从终端读取该命令并进行判断,判断是否为内置命令,如果为内置命令则立即执行该命令;如果不是内置命令则shell创建一个子进程在后台运行。如此逐行处理命令。

6.3 Hello的fork进程创建过程

在终端中输入
./hello 1181200111 byb 1

Shell对hello进行判断,由于hello不是内置命令,故运行当前目录下的可执行文件hello,随后shell通过fork创建一个子进程,该子进程获得与父进程虚拟地址空间代码、栈等相同但独立的一个副本,他们的PID也不同,故此时子进程可以对shell中打开的文件进行读写操作。之后hello就在该子进程中执行。

6.4 Hello的execve过程

在shell给hello fork之后,调用execve函数,在进程的上下文中加载运行hello,调用_start创建新的且被初始化为0的栈等,随后将控制给主函数main,传入参数列表。

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态,它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,

上下文切换的流程是:1.保存当前进程的上下文。2.恢复某个先前被抢占的进程被保存的上下文。3.将控制传递给这个新恢复的进程。

当开始运行hello时,内核分配时间片给hello,在用户态下执行并保存上下文。如果在此期间发生了异常或系统中断,则内核休眠该进程,并在核心态中进行上下文切换且控制将交付给其他进程。hello 执行到 sleep时,hello 休眠,再次上下文切换,控制交付给其他进程,一段时间后再次上下文切换,恢复
hello 在休眠前的上下文信息,控制权回到 hello 继续执行。在循环后,程序调用 getchar , hello 从用户态进入核心态,并再次上下文切换,控制交付给其他进程。最终,内核从其他进程回到 hello 进程,在return后进程结束。

6.6 hello的异常与信号处理

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

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

随机乱按

运行过程中随机乱按会干扰显示,但程序正常运行。

Crtl+z 随后通过ps pstree jobs fg kill 查看

运行过程中crtl+z会停止子进程hello的运行,但不会终止,仅是挂起。此时父进程可以同时接受其他指令。

通过ps指令可以看到hello仍然在进程列表中

通过fg指令可以使hello继续运行

通过kill指令则会终止hello的运行。

Crtl+c

运行过程中crtl+c, 立即终止子进程hello的运行,同时父进程会通过waitpid回收子进程

回车

运行过程中回车会干扰显示,但程序正常运行。

异常种类:

信号种类:

6.7本章小结

本章主要介绍了进程的概念及作用,同时分析了shell-bash的处理流程及hello的fork, execve过程,同时通过实际操作展示了hello的异常与信号处理。

(第6章1分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

逻辑地址:由程序产生的与段有关的偏移地址。分为两个部分,一个部分为段基址,另一个部分为段偏移量。在hello.o中即为相对偏移地址。

线性地址:地址空间是一个非负整数地址的有序集合,如果此时地址空间中的整数是连续的,则我们称这个地址空间为线性地址空间。hello.o中的偏移地址与基地址相加后即为线性地址。

虚拟地址:CPU启动保护模式后,程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟内存被组织为一个存放在磁盘上的 N 个连续的字节大小的单元组成的数组,其每一个字节都被给予一个唯一的地址,即虚拟地址。在hello_.elf中为VirtAddr.

物理地址:计算机系统的主存被组织成一个由 M 个连续的字节大小的单元组成的数组,其每一个字节都被给予一个唯一的地址,即物理地址。hello内的虚拟地址经过地址翻译后得到的即为物理地址。

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

一个逻辑地址由2部分组成,段标识符:段内偏移量.段标识符由一个16位长的字段组成,成为段选择符.其中前13位是一个索引号.后面3位包含一些硬件细节。

地址空间被分成一个个段,而段描述符具体地址描述了一个段,多个段描述符则组成一个数组,即段描述符表,由此我们通过段标识符的前13位,可以找到一个具体的段描述符,这个描述符就描述了一个段,每一个段描述符由8个字节组成。Base字段描述一个段的开始位置的线性地址。 其中全局段描述符,存储在全局段描述符表(GDT)中,局部段描述符,存储在局部段描述符表(LDT)中。段选择符中的T1 =0时使用全局段描述符表(GDT);T1=1时使用局部段描述符表(LDT)。 GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。

当我们拥有一个完整的逻辑地址[段选择符:段内偏移地址]时首先依据其中段选择符T1的值为0或1,我们可以指导Intel逻辑地址到线性地址的变换是GDT的段或LDT的段,随后再根据GDT与LDT所对应的寄存器,得到地址和大小。这样我们就获得了段描述符表。之后查看段选择符的前13位,这样可以可以在段描述符表中查找到对应的段描述符,由此我们可以获取到Base字段,即开始位置的线性地址。最后我们将此地址与段内偏移量(offset)相加,就可以得到转换后的地址了。

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

将线性地址划分成以一定长度为单位的组,即为页;所有被划分出的页构成一个数组,即为页目录,页目录中的每个项是页的地址

32位的线性地址一般被划分成三个部分,前10位是面目录索引;中间的10位为页表索引;最后的12位为偏移量

线性地址到物理地址的变换通过以下步骤进行:首先从cr3中得到hello进程的页目录地址,将这个地址存储在对应的寄存器;同时根据线性地址前10位,即面目录索引,在数组中找到与之对应的索引项,即一个页表的地址,其中存储着页的地址,然后根据线性地址的中间10位,即页表索引,在页表中找到页的起始地址,最后将页的起始地址与线性地址中最后12位,即偏移量,相加,就可以得到物理地址。

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

TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。TLB通常有高度的相联度。用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中提取出来的。如果TLB有T=2^t个组,那么TLB索引(TLBI)是由VPN的t个最低位组成的,而TLB标记(TLBT)是由VPN中剩余的位组成的。

步骤:

  1. CPU产生一个虚拟地址
    
  2. MMU从TLB中取出相应的PTE
    
  3. MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。
    
  4. 高速缓存/主存将所请求的数据字返回给CPU
    

当TLB不命中时,MMU必须从L1缓存中取出相应的PTE,新取出的PTE存放在TLB中,可能会覆盖一个已经存在的条目。

四级页表中,虚拟地址被划分成4个VPN和1个VPO,每个VPNi都是一个到第i级页表的索引,其中1≤i≤4。第j级页表中的每个PTE,1≤j≤3,都指向j+1级的某个页表的基址。第4级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问4个PTE。对于只有一级的页表结构,PPO和VPO是相同的。访问4个PTE,看上去昂贵而不切实际。但这里TLB起作用,将不同层次上页表的PTE缓存起来。

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

在得到物理地址VA后,通过CI组索引,每组8路,对8路的块分别进行CT匹配,如果在此过程中匹配成功,并且块的有效位为1。则判断命中,根据CO取出数据后返回;如果匹配不成功但有效位为1,则判断不命中,继续向下一级缓存中查询数据,按照L1-L2-L3-主存顺序,查到数据后,如果映射到的组内存在空闲块,则直接将其放置进去,否则产生冲突,通过使用LRU进行替换。

7.6 hello进程fork时的内存映射

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

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

7.7 hello进程execve时的内存映射

execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效的替代了当前程序。加载并运行hello需要以下几个步骤:

  1. 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
    
  2. 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为0。
    
  3. 映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后在映射到用户虚拟地址空间中的共享区域内。
    
  4. 设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
    

下一次调度这个进程时,他将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

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

   DRAM缓存不命中的情况称为缺页

CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3,从有效位推断出VP3未被缓存,并且触发一个缺页异常,缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,即存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。

接下来,内存从磁盘复制VP3到内存中的PP3,更新PTE3,随后返回。当异常处理程序返回时,他会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。此时,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。

7.9动态存储分配管理

Printf会调用malloc,请简述动态内存管理的基本方法与策略。

动态内存分配器维护者一个进程的虚拟内存区域,称为堆。分配器将内存视为一组不同大小的块的集合来维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是未分配的。

已分配的块显式地保留为供应用程序使用,空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序执行的,要么是内存分配器自身隐式执行的。

分配器有两种基本风格。

显示分配器,要求应用显式地释放任何已分配的块。

隐式分配器,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。

程序通过调用malloc函数来从堆中分配块。

Malloc函数返回一个指针,指向大小为至少size字节的内存块,这个块会为可能包含在这个块里的任何数据对象类型做对齐。实际中,对齐依赖于编译代码在32位模式(gcc -m32)还是64位模式(默认的)中运行。在32位模式中,malloc返回的块的地址总是8的倍数。在64位模式中,该地址总是16的倍数。

如果malloc遇到问题(例如,程序要求的内存块比可用的虚拟内存还要大),那么它就返回NULL,并设置errno。Malloc不初始化它返回的内存。

7.10本章小结

本章主要介绍了hello存储器的地址空间以及逻辑地址到线性地址的变换-段式管理和线性地址到物理地址的变换-页式管理,以及TLB与多级页表、三级CACHE,fork与execve的内存映射有一定的了解,对于缺页异常以及动态存储分配也有一定认识。

(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

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

8.2 简述Unix
IO接口及其函数

IO接口的操作:

1.     打开文件。一个应用程序通过要求内核打开相应的文件,来宣告他想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,他在后续对此文件的所有操作中表示这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。

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

3.     改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显式地设置文件的当前位置k。

4.     读写文件,一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发EOF条件,应用程序能检测到这个条件,在文件结尾处并没有明确的EOF符号。

类似的,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

5.     关闭文件。当应用完成了对文件的访问,他就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件,并释放它们的内存资源。

相关函数:

1.     打开文件 open函数

Open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。Flags参数指明了进程打算如何访问这个文件。Mode参数指定了新文件的访问权限位。

2.     关闭文件 close函数

进程通过调用close函数关闭一个打开的文件。

3.     读写文件 read函数 write函数

Read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。

Write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。

8.3 printf的实现分析

va_list是一个字符指针,(char*)(&fmt) + 4) 表示的是其中的第一个参数。

在C语言中,参数压栈的方向是从右往左。即当调用printf函数时,最右边的参数先入栈。fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。 同时fmt也是个变量,它的位置是在栈上分配的,它也有地址。

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

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

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

8.4 getchar的实现分析

getchar的返回值是一个整型数据。当程序调用getchar时,用户输入的字符被存放在键盘缓冲区中,直到用户回车为止。当用户回车后,getchar从stdin流中依次读取字符。getchar返回值是用户输入字符的ASCII码,若文件结尾EOF则返回-1。若用户回车之前输入了多个字符,其他字符会被保留在键盘缓存区中,等待getchar读取。getchar调用直到缓冲区中的字符全部读取结束后,才等待用户再次输入。

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

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

8.5本章小结

本章主要介绍了hello的IO设备管理,IO接口及相关函数,同时解析了printf与getchar函数的具体实现方法。

(第8章1分)

结论

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

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

1.    
用户通过编程创建hello源程序文件hello.c

2.    
计算机对hello.c进行预处理,获取文本文件hello.i

3.    
计算机对hello.i进行编译,将文本文件hello.i翻译成文本文件hello.s

4.    
计算机对hello.s进行汇编,将文本文件hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件hello.o中

5.    
计算机对hello.o进行链接,将目标文件hello.o外加库链接合并为一个可执行目标文件hello

6.    
用户在shell中通过./hello 学号 姓名 秒数,执行hello

7.    
Shell通过fork为hello创建子进程

8.    
Shell通过execve加载并运行hello,由内存映射分配虚拟内存空间

9.    
为hello划分时间片,将控制交给hello

10.  通过MMU、TLB、多级页表等访问物理地址

11.  通过malloc申请动态内存

12.  在执行过程中,处理可能的异常信号,crtl+z会使hello进程被挂起,crtl+c会终止hello进程

13.  通过Unix I/O使程序与文件进行交互操作

14.  Hello运行结束,父进程将回收hello进程,同时内核中删除相关的数据结构

通过学习了解,我感到计算机系统的设计与实现具有极强的连续性,各个部分相辅相成,一环扣一环,共同工作从而实现各种程序的执行等。我也由此更加深入更加本质性地了解和认识了计算机系统。

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

附件

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

hello.c 原hello C程序文件

hello.i  hello.c预处理后生成的文本文件

hello.s  hello.i编译后生成的文本文件

hello.o  hello.s汇编后生成的目标文件

hello  hello.o链接后生成的可执行目标文件

hello.elf  hello.o的ELF格式文件

hello_.elf  hello的ELF格式文件

hello_obj  hello.o的反汇编文件

hello_objdump  hello的反汇编文件

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值