哈工大计算机系统大作业

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业      计算学部            

计算机科学与技术学院

2021年5月

摘  要

本篇论文是一个默默无闻的hello小程序的自传,文章中说明了我这个不起眼的我(hello)如何从一个hello.c文件在经历了预处理,编译,汇编等一系列考验后成为了一个成熟的hello可执行文件,但正当我以为我的使命在成功运行后就要结束时,可又在运行时碰上了喜欢乱按键盘的家伙,新的考验又一次来到,我苦苦的在内存中寻找属于我自己的地址,之后又去了解帮助了自己的printf和getchar函数先生,明白了缺页后该如何处理,了解了内存如何管理我,唉,我的一生就是这样朴实无华却难死写大作业的。

关键词:hello;内存;Linux;预处理;编译;汇编;链接;进程;存储;I/O                             

(摘要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简介

1.P2P:

首先使用编译器codeblocks等编译器编写hello的C语言程序,经过预处理器cpp后得到hello.i文件,再通过编译器生成hello.s,通过汇编器生成可重定位目标程序hello.o,最后通过链接器得到可执行文件hello。

在shell中使用./hello指令运行目标程序。

  1. O2O

在hello执行后,shell通过execve函数和fork函数创建子进程并将hello载入,此时hello拥有了进程,CPU为其分配内存等资源,CPU在流水线上依次执行每一条指令,等待进程终止后,会被回收释放并被清理相应痕迹。

1.2 环境与工具

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

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

开发工具:CodeBlocks 64位;gdbgccedb等

1.3 中间结果

hello.i:预处理后得到的文件(文本文件)

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

hello.o:.s汇编后得到的可重定位目标文件(文本文件)

hello:.o经过链接生成的可执行目标文件(二进制文件)

h.elf:hello的elf格式文件

h1.txt:.o经过链接反汇编的汇编语言文件

h.txt:.o经过反汇编生成的汇编语言文件

elf.txt:.o的txt文本文件

h1.elf:hello的elf文件

1.4 本章小结

本章以hello为例子,介绍了本次实验中要用到的硬件和软件环境,并在之后对于hello如何从.c文本文件到可执行的二进制文件的过程做了详细描述,并对hello程序的大致运行过程P2P和O2O进行了诠释。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

概念:在编译之前进行的处理预处理命令以符号“#”开头。。C语言的预处主要有三个方面的内容: 

1.宏定义; 2.文件包含; 3.条件编译

作用:

1.将所有的#define删除,并展开所有的宏定义

2.处理所有的预编译指令

3.处理#include预编译指令,将被包含的文件插入到预编译指令的位置

4.添加行号信息文件名信息,便于调试

5.删除所有的注释

6.保留所有的#pragma编译指令

7.生成.i文件

2.2在Ubuntu下预处理的命令

Linux中预处理命令:gcc -E -o hello.i hello.c

预处理结果:生成hello.i文本文件 

2.3 Hello的预处理结果解析

结果分析:

在进行预处理后,生成了hello.i文件,其中内容相较于hello.c文件大大扩展,这是由于hello.i文本文件中对于hello.c中的头文件进行了展开,并且hello.i文件中依旧可以阅读hello.c中的原本代码(删除了注释)。

2.4 本章小结

本章中涉及了.c文件的预处理,介绍了预处理的概念及作用,给出了Ubuntu下的预处理指令,并通过在Ubuntu中的具体程序实践操作展示了预处理的结果。让我们对于预处理有了具体的认识

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

概念:编译器(编译程序)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。编译的过程实质上是把预处理文件进行词法分析、语法分析、语义分析、优化,从C语言等高级语言转换为成机器更好理解的汇编语言程序,转换后的文件仍为ASCII文本文件。

作用:

预处理文件进行词法分析、语法分析、语义分析、优化得到一个比.i文件让机器更好理解的文件。

        

3.2 在Ubuntu下编译的命令

Linux中编译命令:gcc -S hello.c -o  hello.s

编译结果:生成hello.s文本文件

应截图,展示编译过程!

3.3 Hello的编译结果解析

3.3.1 汇编整体结构:

.file            源文件

.text             代码节

.section/.rodata     只读变量

.global            全局变量

.data                 初始化的全局变量和静态变量

.type             函数类型或者数据类型

.size            大小

.string           字符串

.align             对齐的方式

3.3.2 数据

(1)字符串:

程序中存在两个字符串,都保存在.text,只读数据段中,hello.c中的数组,每个元素都是一个指向字符类型的指针。数组的起点存放在栈中-32(%rbp)的位置,被两次调用找参数传给printf函数

  1. 局部变量i:

hello.c中main函数申请的局部变量i被编译器放入了栈中,具体位置为栈帧%rsp-4,这代表着作为一个整型数据,i占据了4个字节的地址空间。

  1. 参数argc和数组char *argv[]:

argc:作为main函数的传入参数,在编译后,有寄存器传入,最终被存放在堆栈中。

argv[]:作为main函数的第二个传入参数,保存在栈中,起始位置在%rap-32中,程序中,通过寄存器寻址两次传给printf函数。

  1. 立即数4:

直接通过$4的形式体现在汇编代码中。

3.3.3算数操作:

通过汇编指令中的计算指令,如:add,add,sub等指令实现,本程序中的算术操作i的循环便是通过addl实现。

3.3.4 赋值操作:

在hello.s中,赋值操作通过汇编指令中的mov指令来实现。

3.3.5函数操作:

函数中声明的main函数作为一个全局函数(global main),在进行函数调用时,起始地址为该函数的起始地址,而结束后返回时,PC(程序计数器)所存的下一条指令,为之前操作中,调用这个函数的下一行的指令的地址。而对函数的参数传递则是先将参数传入所需要的寄存器,在通过call进入函数后,使用存有相应数据寄存器。

3.3.6关系操作:

  1. 循环关系:

在循环过程中进行自己需要的操作,每完成一次循环利用addl给-4(%rsp)加一,之后利用汇编指令中的cmpl与立即数7比较,符合条件进行接下来的操作。

  1. if条件句关系:

将立即数4与-20(%rsp)中的数据利用汇编指令cmpl进行比较,根据不同的结果做出符合结果的操作。

3.3.7控制转移:

根据汇编指令中的跳转指令进行跳转。

如第一种图中,如果-4(%rsp)小于等于立即数7时响应jle指令,跳转到L4。

第二张图中,如果-20(%rsp)等于立即数4,响应je指令,跳转到L2。

3.4 本章小结

本章让我们了解了编译的概念和作用,给出了Ubuntu下的具体编译指令,并通过一个具体的hello程序让我们具体体会了编译这一过程,看到了编译出的hello.s的结果文本,并通过对于hello编译结果的具体解析,让我们对于编译后的文本文件及各种操作有了具象的认识。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编的概念:汇编是指经过编译的.s文本文件通过汇编器(as)将.s文件翻译 为机器语言指令,并把这些指令打包成为可重定位目标程序,可重定位目   标程序存放在生成二进制的.o文件中。

汇编的作用:将汇编语言翻译为机器语言,并将相关指令以可重定位目标程序 格式保存在.o文件中。

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

4.2 在Ubuntu下汇编的命令

Linux中会汇编命令:as hello.s -o hello.o

汇编结果:生成hello.o文件

4.3 可重定位目标elf格式

linux下生成elf的txt命令:readelf -a hello.o > ./elf.txt

1.ELF头:首先是elf文件开头的魔数(魔数:文件开头的几个字节,通常用来确定文件的类型或格式),首先45,4c,46分别为ELF三个字母的asscii码,剩下的部分中涵盖了数据应补码表示且采用小端序,ELF头的大小,目标文件的类型,机器类型等信息。

2.节头表:记录了每一节的节名,类型,地址,偏移量,大小,旗标,链接,信息,对齐方式

  1. 重定位节:

重定位节保存的是当.text节中调用外部函数时或者是引用全局变量时需要被修正的信息,调用外部函数指令和引用全局变量需要进行重定位,程序中需要被重定位的有.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar这些符号同样需要与相应的地址进行重定位

  1. 符号表:

.symtab,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息,很多函数名称都在这一部分存储,其所含信息如下所示:

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

4.4 Hello.o的结果解析

1.Linux下的指令:objdump -d -r hello.o>h.txt

2.与hello.s对照结果:

  1. 数据格式:

在hello.o的文本文件(txt)中,立即数的表示为十六进制,而在hello.s中,立即数的表示为正常的十进制。

  1. 分支转移:

在hello.o的文本文件(txt)中,跳转的目标为直接的地址表示。而在hello.s中跳转的目标为L1,L2这样的标记符号。

  1. 函数调用:

在hello.o的文本文件(txt)中,调用函数的call后面是一条地址信息,而hello.s中的call之后则跟的是具体的函数名称。

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

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

4.5 本章小结

本章为我们介绍了汇编的概念及其作用,向我们展示了Ubuntu下的汇编指令,并用一个具体的hello程序,让我们汇编这一过程有了更加深刻的认识。同时,向我们展示了可重定位目标的elf格式。最后,本章让我们感受了反汇编后查看相应代码的过程,并将其与hello.s进行了对比,让我们明白了两者之间的区别,进一步体会了汇编语言翻译成机器语言这一过程。

(第4章1分)


5链接

5.1 链接的概念与作用

链接的概念:链接是指通过链接器,将各种代码和数据片段收集并合成为一   个单一文件的过程生成一个可执行目标文件的过程。

链接的作用:(1)模块化:程序可以编写为一个较小的源文件的集合,而不 是一个整体巨大的一团。

§      (2)可以构建公共函数库:例如, 数学运算库, 标准C库。

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

5.2 在Ubuntu下链接的命令

Linux下的链接命令: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的格式

相关命令:readelf -a hello > h.elf

  1. ELF头:

该项中与之前的elf.txt中所包含的基本相同:例如魔数,类别,数据等,但程序入口地址获得了更新程序的节数(14->27)和程序头大小增加。

  1. 节头:

节头中出现了各个节的信息,包括节的大小和位置,节的偏移量,节的类型。

  1. 程序头:

可执行文件或共享目标文件的程序头表是一个结构数组。每种结构都描述了系统准备程序执行所需的段或其他信息。

  1. 重定位节:

  1. 符号表:

符号表用来保存定位于重定位中的符号定义和引用信息

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

5.4 hello的虚拟地址空间

使用edb加载hello,在Data Dump中可以看到hello的虚拟空间地址是从0x401000开始。

根据之前h.elf的信息,在edb中进行相应查看:

  1. .txt:从地址0x4010f0开始:

  1. .rodata:从地址0x402000开始
  2. .data从地址0x404048开始

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

5.5 链接的重定位过程分析

Linux下的命令:objdump -d -r hello > h1.txt

  1. hello与hello.o的区别:

  1. 节数不同:

hello.o中只有.text节,其中只有main函数、。而可执行文件hello中,main函数之前填充有链接过程中重定位而加入进来各种函数、数据,增加了.init,.plt,.plt.sec等节。

  1. 地址不同:

hello.o中的地址为0,1,3等相对偏移地址,但在可执行文件hello中,其地址都是十六进制的从0x401000开始的。

  1. 函数调用不同

2.重定位的过程:

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

 2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。

 3)重定位条目当编译器遇到对最终位置未知的目标引用时,它会生成一个重定位条目。代码的重定位条目放在.rel.txt中。

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

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

5.6 hello的执行流程

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

5.7 Hello的动态链接分析

由于编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器延迟绑定策略处理。

共享库:共享库是一个目标模块,在加载或运行时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程被称为动态链接,是由一个叫做动态链接器的程序来执行的。共享库也称为共享目标。

动态链接:把程序按照模块拆分成各个相对独立的部分,在程序运行时将这些相对独立的部分链接在一起形成一个完整的程序。

首先找到存放got的地址为403ff0:

在dl_init前,如图所示都为0

在dl_init赋值

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

5.8 本章小结

本章中介绍了链接的概念和作用,并通过相关命令生成了可执行文件,再生成了可执行文件的ELF格式,通过使用edb对其进行查看后,分析了重定位过程、执行流程和整个动态链接过程。通过具体程序的过程加深了我们对于链接这一步骤的认识。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

进程的概念:一个执行中程序的实例。系统中的每个程序都运行在某个进程的上   下文中。

进程的作用:进程提供给应用程序两个关键抽象:

(1)逻辑控制流 (Logical control flow):每个进程似乎独占地使   用CPU,通过OS内核的上下文切换机制提供

§ (2)私有地址空间 (Private address space):每个进程似乎独占地   使用内存系统,OS内核的虚拟内存

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

1.Shell-bash的作用:Linux中的shell是指一个面向用户的命令接口,表现形   式就是一个可以由用户录入的界面这个界面也可以反馈运   行信息

2.处理流程:

(1)检查命令是否为内部命令,若是则立即执行。

(2)不是的话判断是否为应用程序,shell在搜索路径里寻找这些应用程序,  调用相应程序。

3如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文  件,将会显示一条错误信息。

6.3 Hello的fork进程创建过程

在终端输入命令后,Shell判断出不是内置命令,而因为不是内置命令,shell会通过fork()创建出一个子进程,子进程(虚拟地址独立)在除了PID外几乎与父进程完全一致(获取了父进程的上下文,环境变量等可以打开父进程可以打开的任何文件),这样完成了进程的创建。

6.4 Hello的execve过程

在fork函数成功创建子进程后,子进程会调用execve函数。execve函数在调用成功的情况下不会返回任何值,如果调用出现错误,会返回到调用程序。execve函数会先删除已存在的用户区域,再创建新的数据,堆,栈和代码。

6.5 Hello的进程执行

上下文上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对 象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数 器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地 址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打 开文件的信息的文件表。

上下文切换:进程执行的某些时刻,内核决定抢占该进程,并重新开始一个之 前被抢占的进程。

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

用户态和核心态区别:处于用户态执行时,进程所能范文的内存空间和对象受限,其所处于占有的处理机是可被抢占的;而处于核心态执行的进程则能访问所有内存空间和对象,且所占有的处理机是不允许被抢占的。

用户态和核心态的转换:(1)系统调用 (2)中断 (3)异常

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

6.6 hello的异常与信号处理

./hello 120L021903 张一帆 1

1.程序正常进行时乱按,如果乱按时存在回车键,程序结束后,缓冲区中回车之前的字符会被当做指令执行:

2.程序正常运行时输入回车,会打印空行,之后正常结束:

3.程序正常运行时输入Ctrl-Z,发送一个SIGTSTP信号给前台进程组的每一个进程,hello进程挂起并输出提示信息

4.程序正常运行时输入Ctrl-C,内核发送一个SIGINT信号给到前台进程组中的每个进程,终止前台进程

5.Ctrl-Z后运行ps,显示当前的进程状态:

  1. Ctrl-Z后运行jobs,查看被执行的任务:

  1. Ctrl-Z后运行pstree,将所有进程按照树状图显示:

  1. Ctrl-Z后运行fg,首先打印命令行后,hello再次被调到前台运行:

  1. Ctrl-Z后运行kill,杀死指定进程:

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

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

6.7本章小结

本章首先展示了进程的概念和作用,并在概念上向我们介绍了fork和execve函数,接着简要分析了进程的执行,最后了解到了异常与信号的处理。整个过程都有hello作为具体范例,让我们更了解到了hello在运行过程中会碰到的各种细节,并对于不同的异常信号,hello会如何处理,加深了对Linux系统下程序运行的具体细节的认识。

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

1.逻辑地址:

由选择符和偏移量构成,是由程序产生的偏移地址部分。在hello中具体体现为其相对偏移地址。

  1. 线性地址:

与虚拟地址相同。

3.虚拟地址:

虚拟地址是逻辑地址计算后的结果,同样不能直接用来访存,需要通过MMU翻译得到物理地址来访存。在hello反汇编代码计算后就能得到虚拟地址。

4.物理地址:

真实物理内存对应的地址。每个字节都有其对应的唯一物理地址,虚拟地址在经过翻译过后可得到物理地址,hello中可通过物理地址来找到我们需要的信息。

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

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

段式管理:把一个程序分成若干的段进行储存,每个段都是一个逻辑实体。

1、逻辑地址=段选择符+偏移量。

2、每个段选择符大小为16位,段描述符为8字节。

3、GDT为全局描述符表,LDT为局部描述符表。

4、段描述符存放在描述符表中,也就是GDT或LDT中。

5、段首地址存放在段描述符中。

每个段的首地址都存放在自己的段描述符中,而所有的段描述符都存放在一个描述符表中(描述符表分为全局描述符表GDT和局部描述符表LDT)。而要想找到某个段的描述符必须通过段选择符才能找到。

段选择符由三个部分组成,RPL、TI、index(索引)。RPL在此不做介绍。先来看TI,当TI=0时,表示段描述符在GDT中,当TI=1时表示段描述符在LDT中。

我们可以将描述符表看成是一个数组,每个元素都存放一个段描述符,那index就表示某个段描述符在数组中的索引。

现在我们假设有一个段的段选择符,它的TI=0,index=8。我们可以知道这个段的描述符是在GDT数组中,并且他的在数组中的索引是8。

假设GDT的起始位置是0x00020000,而一个段描述符的大小是8个字节,由此我们可以计算出段描述符所在的地址:0x00020000+8*index,从而我们就可以找到我们想要的段描述符,从而获取某个段的首地址,然后再将从段描述符中获取到的首地址与逻辑地址的偏移量相加就得到了线性地址。

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

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

优点1.由于它不要求作业或进程的程序段和数据在内存中连续存放,从而有效 地解决了碎片问题。2.动态页式管理提供了内存和外存统一管理的虚存 现方式,使用户可以利用的存储空间大大增加。这既提高了主存的利用率, 又有利于组织多道程序执行。

缺点1.要求有相应的硬件支持。例如地址变换机构,缺页中断的产生和选择淘 汰页面等都要求有相应的硬件支持。这增加了机器成本。2.增加了系统开 销,例如缺页中断处理机。3.请求调页的算法如选择不当,有可能产生抖 动现象。4.虽然消除了碎片,但每个作业或进程的最后一页内总有一部分 空间得不到利用果页面较大,则这一部分的损失仍然较大。

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

TLB:为了消除每次CPU产生一个虚拟地址MMU就查阅一个PTE带来的时间开销,许多系统都在MMU中包括了一个关于PTE的小的缓存,称为翻译后被缓冲器(TLB),TLB的速度快于L1 cache。

Inter Core i7实现支持48位虚拟地址空间和52位物理地址空间,使用4KB的页。X64 CPU上的PTE为64位,所以每个页表一共有512个条目。512个PTE条目需要9位VPN定位。再四级页表的条件下,一共需要36位VPN,因为虚拟地址空间是48位,故低12位是VPO。TLB四路组联,共有16组,需要4位TLBI,故VPN的低4位是TLBI,高32位是TLBT。

CPU产生虚拟地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT+TLBI向TLB中匹配,如果命中,则得到40位PPN+12位VPO组合成52位物理地址PA。如果没有命中,MMU向页表中查询,CR3确定第一级页表的起始地址,9位VPN1确定在第一级页表中的偏移量,查询出第一部分PTE,以此类推最终在四级页表都访问完后获得PPN,与VPO结合获得PA,并向TLB中更新。

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

Cache:高速缓存。其原始意义是指存取速度比一般随机存取记忆体(RAM)来得快的一种RAM,一般而言它不像系统主记忆体那样使用DRAM技术,而使用昂贵但较快速的SRAM技术,也有快取记忆体的名称。

高速缓冲存储器是存在于主存与CPU之间的一级存储器, 由静态存储芯片(SRAM)组成,容量比较小但速度比主存高得多, 接近于CPU的速度。在计算机存储系统的层次结构中,是介于中央处理器和主存储器之间的高速小容量存储器。它和主存储器一起构成一级的存储器。高速缓冲存储器和主存储器之间信息的调度和传送是由硬件自动进行的。

高速缓冲存储器最重要的技术指标是它的命中率

在三级Cache的存在下,CPU需要访问物理内存时首先会去L1 Cache中查找,如果没有找到自己需要的内容,称为不命中,之后接着去L2和L3 Cache中查找,如果找到则直接使用,未找到则去内存中寻找,找到后会将找到的信息写入Cache再继续进行读写操作。

7.6 hello进程fork时的内存映射

fork底层是调用了内核的函数来实现fork的功能的,即先create()先创建进程,此时进程内容为空,然后clone()复制父进程的内容到子进程中,此时子进程就诞生了,接着父进程就return返回了。

fork()的时候,父进程的虚拟地址映射着物理内存的实际的物理地址,clone()的时候,并不是在物理地址中直接再复制一份和父进程一样的物理内存块,而是子进程的虚拟地址也直接映射到同一物理内存块中

7.7 hello进程execve时的内存映射

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

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

2映射私有区域。为程序的代码,数据,bss 和 栈区域创建新的区域结构。所有这些新的区域都是私有的,写时复制的。代码和数据区域被映射为文件中的 .text 和 .data区.bss 区域是请求二进制零的,映射到匿名文件。

3映射共享区域:如果程序与共享对象(或目标)链接,比如标准 C 库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。

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

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

MMU 在试图翻译某个虚拟地址 A 时,触发一个缺页,这个异常导致控制转移到内核的缺页处理程序,内核知道这个缺页是合法的虚拟地址进行合法的操作造成的。随后,它选择一个牺牲页,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页程序返回时,CPU 重新启动引起缺页的指令。

7.9动态存储分配管理

Printf函数会调用malloc:

分配器将堆视为一组不同大小的块(block)的集合来维护,每个块就是一个连续的虚拟内存片(chunk),要么是已经分配的,要么是空闲的。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的,要么是内存分配器自身隐式执行的。

分配器有两种风格,它们的区别在于哪个实体来负责释放已经分配的块:

1显式分配器(explicit allocator),要求应用显式地释放任何已分配的块。例如,C 标准库提供一种叫做 malloc 程序包的显式分配器。C 程序通过调用 malloc 函数来分配一个块,并通过调用 free 函数来释放一个块。C++ 中的 new 和 delete 操作符与 C 中的 malloc 和 free 相当。

(2)隐式分配器(implicit allocator),另一方面,要求分配器检测到一个已分配块何时不被程序使用,就释放这个块。隐式分配器也叫垃圾收集(garbage collection)。

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

7.10本章小结

本章为我们介绍了hello储存管理相关知识,其中包括了逻辑地址到线性地址的转换,线性地址到物理地址的变换,之后又介绍了进程fork()函数时和execve()函数时的内存映射,让给我们对于hello在存储器中的各种操作和转换特点有了更深刻的认识,同时之后的对于缺页故障的处理告诉了我们在触发缺页时相应的替换操作,动态存储的分配管理也让我们了解到了动态内存管理的基本方法与策略,了解到了分配器显式与隐式的两种风格。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件,即所有IO设备都被模型化为文件,所有的IO操作(输入输出)都被视为文件的读写操作。

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

Unix I/O接口:

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.关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。

Unix I/O函数:

1.int open(char* filename,int flags,mode_t mode),进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位;

2.int close(fd),fd是需要关闭的文件的描述符,close返回操作结果;

3.ssize_t read(int fd,void* buf,size_t n),read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量;

4.ssize_t wirte(int fd,const void *buf,size_tn),write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。

8.3 printf的实现分析

1.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;

}

利用vsprintf函数返回的字符串数值,再使用write函数将buf中的i个元素写到终端。

2.vsprintf函数:

vsprintf函数返回的是需要打印出来的字符串的长度。

int vsprintf(char *buf,const char *fmt,va_list args) {

    char *p;

    char tmp[256];

    va_listp_next_arg = args;

    for (p = buf; *fmt; fmt++) {

        if (*fmt != '%') {

            *p++ = *fmt;

            continue;

        }

        fmt++;

        switch(*fmt) {

        case 'x':

            itoa(tmp, *((int *)p_next_arg));

            strcpy(p, tmp);

            p_next_arg += 4;

            p += strlen(tmp);

            break;

        case 's':

            break;

        default:

            break;

        }

        return (p - buf);

    }

}

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

从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;

}

代码中的buf[BUFSIZ]是缓冲区,而*bb则是指向缓冲区第一个位置的指针,getchar函数通过调用read(三个参数分别表示标准输入,缓冲区,一次读一个字符)来判断读入是否成功,实现读取一个字符的功能。

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

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

8.5本章小结

本章介绍了Linux的IO设备管理方法、Unix IO接口及其函数,分析了printf函数和getchar函数,让我们对于之前一直使用的函数printf和getchar有了更深刻的认识。

(第8章1分)

结论

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

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

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


附件

hello.i:预处理后得到的文件

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

hello.o:.s汇编后得到的可重定位目标文件

hello:.o经过链接生成的可执行目标文件

h.elf:hello的elf格式文件

h1.txt:.o经过链接反汇编的汇编语言文件

h.txt:.o经过反汇编生成的汇编语言文件

elf.txt:.o的txt文本文件

h1.elf:hello的elf文件

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


参考文献

为完成本次大作业你翻阅的书籍与网站等

  1. 逻辑地址到线性地址的转换_xuwq2015的博客-CSDN博客https://blog.csdn.net/xuwq2015/article/details/48572421
  2. Linux 虚拟内存系统, 内存映射, fork, execve, malloc, free 动态内存分配与释放 | 编程之禅 (wangjunstf.github.io)

https://wangjunstf.github.io/2021/11/09/linux-xu-ni-nei-cun-xi-tong-nei-cun-ying-she-fork-execve-malloc-free-dong-tai-nei-cun-fen-pei-yu-shi-fang/

  1. https://www.cnblogs.com/pianist/p/3315801.html
  2. https://www.csdn.net
  3. 深入理解计算机系统

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值