目  录

第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简介

在linux下,hello程序的一生可以分成P2P,020两个方面来描述

P2P: 程序从程序到进程的过程,一个c程序经过预处理,编译,汇编,链接4个阶段生成一个可执行目标文件,在shell中执行这个可执行文件,这个程序就被加载到了内存,变成了一个进程,从程序到进程,一个P2P的过程就结束了。

020: shell在执行程序前,先用fork创建一个空间,在根据用户输入的相应参数,提供execve函数加载到这个进程,在程序结束后,shell会回收这个空间,这就是hello的020。

1.2 环境与工具

1.2.1 硬件环境

X64CPU;2GHz;16G RAM; 256G固态硬盘;

1.2.2 软件环境

Windows10 64位;VMware 15; Ubuntu 18.04 64位;

1.2.3 开发工具

gcc,vscode,edb,readelf,objdump

1.3 中间结果

hello                           最终的可执行二进制文件

helloelfld.txt       链接之后的可执行文件的readelf信息

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

hello.o                      可重定位二进制文件

hello.s                         汇编之后的汇编代码

hello.c                       原始的C文件

helloelf.txt                  链接之前的二进制文件的readelf信息

hellold.s                   链接之后文件的反汇编

helloo.s                       链接之前文件的反汇编

1.4 本章小结

本章简单介绍了我们的研究内容,即从P2P,020两个角度开始对计算机系统的研究。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理器(cpp)根据以字符#开头的命令,修改原始的c程序。比如hello.c中第1行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入到程序文本中。得到一个以.i为文件扩展名的c程序。

这样可以方便编译器的转换,程序员只需要引入头文件,就可以引入很多东西,方便编程。

2.2在Ubuntu下预处理的命令

图2.2-1 预处理命令

处理后的.i文件:

图2.2-2  处理后的.i文件

2.3 Hello的预处理结果解析

生成的预处理文件看起来很复杂,比原始的c文件大了很多,我们直接跳到文件结尾,文件一共有3000多行,在文件的末尾,我们发现了我们熟悉的代码。

可以分析出,之前多出来的内容,是头文件展开后的结果。

图2.3-1  扩展后的c文件

2.4 本章小结

本章通过实际操作,直观的了解了预处理过程,预处理是hello程序P2P的第一步。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

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

文件中用文本格式描述了低级机器语言指令,方便了机器将其转换成机器编码。

3.2 在Ubuntu下编译的命令

图3.2-1  编译命令

生成的文件:

图3.2-2 编译的文件

3.3 Hello的编译结果解析

先看原程序

图3.3-1  源程序

对于原程序,我们可以看到全局变量sleepsecs, i等信息,接下来根据汇编进行详细的解释。

3.3.1 全局变量

程序中的全局变量只有一个,sleepsecs,我们注意到,变量是int类型,却赋了一个浮点类型的初值,根据c语言的要求,2.5会被强制转换成整型。这里是隐式转换。

在汇编代码中我们看到:

Sleepsecs在.data中,sleepsecs的大小为4

图3.3.1-1 sleepsecs反汇编代码

Sleepsecs的实际值是2,在调试中进行检验可以看到确实如此。

图3.3.1-2 sleepsecs测试

3.3.2 常量

程序中的常量有两处,分别在两个printf的地方

图3.3.2-1 常量

3.3.3 局部变量

代码中的局部变量为i,经过学习,我们知道函数中的局部变量存在栈中或者存在寄存器中,分析代码:

根据原码,这个循环变量只在循环中使用过,所以直接找循环即可

图3.3.3-1 局部变量循环

循环10次,恰好与9相比较,所以,我们的变量i在程序中被放进了栈,在使用时才会出现。

3.3.4 控制转移(if else)

源代码中有一处if else语句,我们查看汇编代码

图3.3.4-1 if else

这里采用的是直接比较的方法,如果不等于3的话就执行括号内语句,如果等于就跳过这部分。

3.3.5 控制转移 (for循环)

图3.3.5 for循环

整体采用的是跳到中间的翻译方法

3.3.6 数组

源代码中只有一个关于数组的操作,就是关于argv的操作,在循环部分,对于数组的操作,显然是以寻址的方式进行。

我们查看源代码,发现其中的rsi,rdx就是argv[1], argv[2],而其中的rdi就是相应的格式字符串,在gdb中验证如下

图3.3.6-1 测试结果

分析代码知rdx得到过程为

图3.3.6-2 rdx

rsi的得到过程

图3.3.6-3 rsi

rsi与rdx是同一地址的不同偏移量所得到,符号我们对数组的认知,大致如下图

图3.3.6-4 指针数组

3.3.7 函数调用

函数过程中有对printf,exit,getchar等的调用,直接用call命令即可,对于传入的参数,可以放到栈里,寄存器里,对于返回值,一般在rax中。

3.3.8 赋值

赋值直接采用mov命令即可

图3.3.8-1 赋值

3.3.9 转换

当在double或float向int进行类型转换的时候, 值会向零舍入。

3.3.10 算术操作

以i为例,这里通过add指令更新i

图3.3.10-1 算术操作

3.3.11 关系操作

程序中通过cmp语句进行关系操作,类似还有test,set等等都可以判断条件进行操作

程序中对i的判断

图3.3.11-1 对i的判断

程序中对argc的判断

图3.3.11-2 对argc的判断

3.4 本章小结

本章通过实际操作,了解到了预处理后的文件如何转换为汇编代码,并通过对汇编代码的研究,对程序由从语言的抽象,到机器代码的具体,有了一些了解。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

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

将汇编代码变成真正的机器代码,便于机器处理。

4.2 在Ubuntu下汇编的命令

图4.2-1 汇编命令

通过这一步生成的文件仍然不能执行,不是可执行二进制文件

4.3 可重定位目标elf格式

图4.3-1 readelf命令

ELF头,描述了elf文件的总体信息:

图4.3-2 ELF头

节头表:描述了各个节的总体信息

图4.3-3 节头

重定位条目:包含重定位信息,当链接器把这个目标文件和其他文件组合时,要修改这些位置

图4.3-4 重定位信息

重定位算法:

图4.3-5 重定位算法

4.4 Hello.o的结果解析

反汇编结果:

图4.4-1 反汇编

与第3章的hello.s相对比,我们发现主要的差别在于,左边多了地址号,并且在函数调用的代码处有了实际的代码,分支跳转的部分也不再只是L2或L3等了,全局变量调用,在.s文件中,访问rodata(printf中的字符串),使用段名称+%rip,在反汇编代码中0+%rip,因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。

机器所采用的指令都是二进制的,与右边的汇编语言是一一对应的。

4.5 本章小结

本章介绍了hello.s到hello.o的过程,通过对hello.o的反汇编,与原来的相比,我们间接了解到从汇编语言到机器语言的过程,这一步意义重大,我们初步的得到了二进制的机器指令。

(第41分)

第5章 链接

5.1 链接的概念与作用

链接就是将多个文件拼接在一起的过程,链接可以发生在编译时,加载时运行时。

链接最大的好处就是使分离编译成为可能, 我们可以把程序分成更小,更好管理的模块进行处理。

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

5.2 在Ubuntu下链接的命令

图5.2-1 链接命令

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

图5.3-1 节头

相比于之前,这里多了一些其他的段,查阅相应资料,得到一些补充信息:

interp:该段保存了ld.so的路径

dynsym:动态符号表

dynstr:动态符号表中的符号名称

rela.dyn:动态重定位表

init:程序初始化

plt:动态链接表

fini:程序终止时需要的执行的指令
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间

Edb打开hello:

图5.4-1 edb

通过查询程序头(由反汇编得到):

程序头表在执行的时候被使用,它告诉链接器运行时加载的内容并提供动态链接的信息。每一个表项提供了各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对齐方面的信息。

注意第三列就是程序的虚拟地址信息

图5.4-2 程序头

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

5.5 链接的重定位过程分析

hello.o的反汇编:

图5.5-1 反汇编

hello的反汇编:

图5.5-2 反汇编

不同之处:

  1. 多了我们需要的函数,如printf,exit等
  2. 相比链接前,多了一些节,如,init,plt
  3. 重定位条目,hello中没有了重定位条目,并且函数调用的位置都换成了相应地址
  4. 地址访问,对于rodata,sleepsecs等,在hello.o中,其值被赋为0,其地址在运行时才能获得,故hello.o中赋为0,在hello中则有了具体的值。

链接的过程:包括符号解析与重定位

重定位算法:

图5.5-3 重定位算法

5.6 hello的执行流程

从hello加载到_start的过程

_dl_start

_dl_start_user

_init

__init_misc

_dl_init

_dl_start_user

从_start到main

_start

_init

__libc_csu_init

__libc_start_main

从main到程序终止

main

__libc_start_main

5.7 Hello的动态链接分析

调用前

图5.7-1 调用前

调用后

图5.7-2 调用后

调用前后第一行发生了变化,调用前全是0,调用后有了数值。

对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数

在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,GOT存放的是PLT中函数调用指令的下一条指令地址。

在dl_init调用之后,有了值用来确定调用的函数地址。

5.8 本章小结

本章主要介绍了链接相关的一些内容,链接是程序的最后一步,通过这一步,程序真正变得可以执行,可以加载到内存正确运行了。

(第51分)

第6章 hello进程管理

6.1 进程的概念与作用

进程的经典定义就是一个执行中程序的实例。

进程是计算机程序需要进行对数据集合进行操作所运行的一次活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

进程为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量

(3)检查第一个命令行参数是否是一个内置的shell命令

(3)如果不是内部命令,调用fork( )创建新进程/子进程

(4)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

(5)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait...)等待作业终止后返回。

(6)如果用户要求后台运行(如果命令末尾有&号),则shell返回;

6.3 Hello的fork进程创建过程

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

6.4 Hello的execve过程

execve函数在shell为hello开辟的空间中加载子进程,步骤如下:

  1. 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构
  2. 映射私有区域。为新程序的代码,数据,bss和栈区域创建新的数据结构。
  3. 映射共享区域。如果hello程序与共享对象链接,这些对象动态链接到这个程序,再映射到用户虚拟地址空间中的共享区域内。
  4. 设置程序计数器。execve做的最后一件事就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点

图6.4-1 内存映射

6.5 Hello的进程执行

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

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

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

调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度( scheduling),是由内核中称为调度器( scheduler)的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换1)保存当前进程的上下文,2)恢复某个先前被抢占的进程被保存的上下文,3)将控制传递给这个新恢复的进程。

当调用sleep之前,如果hello程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换,上下文切换是由内核中调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并进行1)保存以前进程的上下文2)恢复新恢复进程被保存的上下文,3)将控制传递给这个新恢复的进程 ,来完成上下文切换。

程序中调用了sleep函数,该函数显式的请求让调用进程休眠,hello进程初始运行在用户模式中,调用sleep函数时进入内核,内核执行,让进程休眠2秒,在切换会用户态,继续执行下一个循环。

程序中其他的需要调用内核的程序也是如此。

图6.5-2 切换

6.6 hello的异常与信号处理

正常运行时的结果:

图6.6-1 正常运行

Ctrl-C:SIGINT信号 停止执行,结束并回收hello进程

图6.6-2 停止

Ctrl-Z:SIGSTP信号 程序挂起

图6.6-3 挂起

乱按加回车:被缓存进stdin,第一个回车完成getchar,第二个回车时输入shell,报错

图6.6-4 乱按加回车

Ctrl-Z加ps:

图6.6-5 ps

Ctrl-Z加jobs:

图6.6-6 jobs

Ctrl-Z加pstree:

图6.6-7 pstree

Ctrl-Z加fg:

图6.6-8 fg

Ctrl-Z加kill:

图6.6-9 kill

6.7本章小结

本章阐明了进程的定义与作用,本章通过shell加载进程,并进行相应信号的处理,我们更加明确了程序在计算机中的执行。

(第61分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

逻辑地址:相对地址,机器语言中出现的地址都是逻辑地址,需要转换成线性地址,再经过MMU转换成物理地址才能够被访问到。

线性地址:如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间。

虚拟地址:在虚拟地址空间中生成地址,就是线性地址。

物理地址:真实的地址,放在寻址总线上的地址,用于内存芯片级内存单元寻址。

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

根据程序的模块化性质,可按程序的逻辑结构划分成多个相对独立的部分,例如,过程、数据表、数据阵列等。这些相对独立的部分被称为段,它们作为独立的逻辑单位可以被其他程序段调用,形成段间连接,从而产生规模较大的程序。段通常有段名、段起点、段长等。段名可用用户名、数据结构名或段号标识,以便于程序的编写、编译器的优化和操作系统的调度管理等。

可以把段作为基本信息单位在主存和辅存之间传送和定位。分段方式下,将主存空间按实际程序中的段来划分,每个段在主存中的位置记录在段表中,段的长度可变,所以段表中需有长度指示。每个进程有一个段表,每个段在段表中有一个段表项,用来指明对应段在主存中的位置、段长、访问权限、使用和装入情况等。段表本身也是一个可再定位段,可以存在外存中,需要时调入主存,但一般驻留在主存中。

在分段式虚拟存储器系统中,虚拟地址由段号和段内地址组成。通过段表把虚拟地址变换成主存物理地址,其变换过程如图所示。

每个用户进程有一个段表基址寄存器,存放其段表在内存的首地址。根据虚拟地址中的段号,找到对应段表项,检查是否存在以下三种异常情况

(1)缺段(段不存在):装入位=0

(2)地址越界:偏移量超出最大段长

(3)保护违例:操作方式与指定访问权限不符。
若发生以上三种情况,则调用相应的异常处理程序,否则,将段表项中的段首址与虚拟地址中的段内地址相加,生成主存物理地址

图7.2-1 分段内存相关

分段对程序员是不透明的,分页对程序员是透明的。

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

在分页式虚拟存储系统中,主存储器和虚拟地址空间都被划分成大小相等的页面,磁盘和主存之间按页面为单位交换信息。通常把虚拟地址空间中的页面称为虚拟页、逻辑页或虚页;主存空间中的页面被称为页框(页帧)、物理页或实页。有时虚拟页简称为VP( virtual page),物理页简称为PF( page frame)或PP( physical page)。对于这些概念的名称,不同教材的说法可能不同,但含义是一样的。

对采用虚存机制的存储区进行的访问,指令中给出的地址是虚拟地址,所以,CPU执行指令时,首先要将虚拟地址转换为主存物理地址,才能到主存取指令和数据。地址转换工作由CPU中的存储器管理部件( Memory Management Unit,MMU)来完成假设虚拟存储器中每个进程有m页,主存中有m个页框,通常情况下m>n。由于页面大小是2的幂次,所以,每一页的起点都落在低位字段为零的地址上。因此,虚拟地址分为两个字段:高位字段为虚拟页号,低位字段为页内偏移地址。主存物理地址也分为两个字段:高位字段为物理页号,低位字段为页内偏移地址。由于两者的页面大小一样,所以页内地址是相等的分页式地址变换过程如图所示。每个进程都有一个页表基址寄存器,存放该进程的页表首地址。首先根据页表基址寄存器的内容,找到对应的页表,然后由虚拟地址高位字段的虚拟页号为索引,找到对应的页表项,若装人位为1,则取出物理页号,和虚拟地址中的页内地址拼接,形成实际主存物理地址;若装入位为0,则说明缺页,需要操作系统进行缺页处理。

图7.3-1 分页内存相关

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

TLB:从上述地址转换过程可看出,访存时首先要到主存查页表,然后才能根据主存物理地址再访问主存以存取指令或数据。如果缺页,则还要进行页面替换、页表修改等,访问主存的次数就更多。因此,采用虚拟存储器机制,使得访存次数增加了。为了减少访存次数,往往把页表中最活跃的几个页表项复制到高速缓存中,这种在高速缓存中的页表项组成的页表称为后备转换缓冲器,通常简称为TLB或快表,相应地称主存中的页表为慢表。

图7.4-1 tlb

四级页表:图给出了 Core i7MMU如何使用四级的页表来将虚拟地址翻译成物理地址。36位VPN被划分成四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1PET的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2PTE的偏移量,以此类推。

图7.4-2 四级页表

在下图中,CPU给出的是一个32位的虚拟地址,首先由CPU中的MMU进行虚拟地址到物理地址的转换;然后由处理 cache的硬件根据物理地址进行存储访问。MMU对TLB查表时,20位的虚拟页号被分成标记(Tag)和组索引两部分,首先由组索引确定在TLB的哪一组进行查找。查找时将虚拟页号的标记部分与该组TLB中的每个TLB标记字段同时进行比较,若有某个相等且对应有效位为1,则TIB命中,此时,可直接通过TLB 进行地址转换;若都不相等,则TLB缺失,此时,需要访问主存去查页表,图中所示的是两级页表方式,虚拟页号被分成目录索引和页表索引两部分,由这两部分得到对应的页表项,从而进行地址转换,并将虚拟页号的高位部分作为TLB标记和对应页表项的内容一起送入TLB.若TLB已满,还要进行TL.B替换,为降低替换算法开销,TLB常采用随机替换策略。在MMU完成地址转换后, cache硬件根据映射方式将转换得到的主存物理地址划分成多个字段,然后,根据 cache索引,找到对应的 cache行或 cache组,将对应各 cache行中的标记与物理地址中的高位地址进行比较,若相等且有效位为1,则 cache命中,此时,根据块内地址取出对应的字需要的话,再根据字节偏移量从字中取出相应字节送CPU

图7.4-3  cache与tlb下的情况

64位情况如图:

图7.4-4 过程

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

图示为三级cache下的地址翻译情况:

图7.5-1 地址翻译

L1 Cache是8路64组相联。块大小为64B。CO和CI都是6位,CT是40位。根据物理地址(PA),首先使用CI组索引,每组8路,分别匹配标记CT。如果匹配成功且块的有效位是1,则命中,根据块偏移CO返回数据。

如果没有匹配成功或者匹配成功但是标志位是1,则不命中,向下一级缓存中取出被请求的块,然后将新的块存储在组索引指示的组中的一个高速缓存行中。一般而言,如果映射到的组内有空闲块,则直接放置,否则必须驱逐出一个现存的块,一般采用最近最少被使用策略LRU进行替换。

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的 mm struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
    当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面, 因此,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

以实际创建进程为例,具体步骤 如下:

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

图7.7-1 内存情况

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

当页面不命中时就会缺页,此时具体步骤如下:

第1步:处理器生成一个虚拟地址,并把它传送给MMU。

第2步:MMU生成PTE地址,并从高速缓存/主存请求得到它。

第3步:高速缓存/主存向MMU返回PTE。

第4步:PTE中的有效位是零,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序

第5步:缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了, 则把它换出到磁盘。
第6步:缺页处理程序页面调入新的页面,并更新内存中的PTE。

第7步:缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面现在缓存在物理内存中,所以就会命中,在MMU执行了图9-13b中的步骤之后,主存就会将所请求字返回给处理器。

图7.8-1 缺页时的情况

图7.8-2 故障异常

7.9动态存储分配管理

简介:

动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap)(见图9-33)。系统之间细节不同, 但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk(读做“ break"”),它指向堆的顶部

分配器将堆视为一组不同大小的块( block)的集合来维护。每个块就是一个连续的虚拟内存片( chunk), 要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
    分配器有两种基本风格。两种风格都要求应用显式地分配块。它们的不同之处在于由哪个实体来负责释放已分配的块。

两种风格:显式分配器,隐式分配器

图7.9-1 内存结构

实现策略:

对于分配链表:

  1. 隐式链表

堆块格式:

图7.9-2 格式

隐式空闲链表的结构:

图7.9-3 隐式链表

在这种结构下为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。该结构优点是简单,缺点就是开销比较大。

  1. 显式空闲链表

格式:

图7.9-4 格式

据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。

使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。不过,释放一个块的时间可以是线性的,也可能是个常数, 这取决于我们所选择的空闲链表中块的排序策略。

后进先出(LIFO):放一个块可以在常数时间内完成。如果使用了边界标记,那么合并也可以在常数时间内完成。

按地址顺序:在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。按地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,接近最佳适配的利用率。

  1. 分离的空闲链表

维护多个空闲链表,其中每个链表中的块有大致相等的大小

对于合并空闲块:

  1. 立即合并
  2. 带边界标记的合并

格式:

图7.9-5 格式

在每个块的结尾处添加一个脚部( footer,边界标记),其中脚部就是头部的一个副本。如果每个块包括这样一个脚部,那么分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态这个脚部总是在距当前块开始位置一个字的距离

从而,有4种情况:

1)前面的块和后面的块都是已分配的。
2)前面的块是已分配的,后面的块是空闲的。
3)前面的块是空闲的,而后面的块是已分配的

4)前面的和后面的块都是空闲的。

如图:

图7.9-6 四种情况

对于适配方式:则有首次适配,下一次适配和最佳适配。

7.10本章小结

本章主要对存储管理进行了简介,介绍了段式管理与页式管理,还介绍了程序创建时的内存映射,以及缺页的情况和动态内存分配管理。有了这些程序才能在计算机内如鱼得水。

(第7 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

所有的I/O设备(例如网络,磁盘和终端)都被模型化为文件,所有的输入和输出都被当作相应文件的读和写来执行。

设备管理:unix io接口

上面这种将设备优雅地映射为文件的方式,允许linux内核引出一个简单,低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

打开和关闭文件:

open()函数:这个函数会打开一个已经存在的文件或者创建一个新的文件

图8.2-1 函数

close()函数:关闭一个已打开的函数

图8.2-2 函数

读和写文件:

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

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

图8.2-3 函数

lseek函数:应用程序能够显示地修改当前文件的位置。

8.3 printf的实现分析

printf源码:

图8.3-1 printf

可以看到函数调用了vsprintf和write两个函数,其中write是系统函数,

vsprintf函数如下图:

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

图8.3-2 vsprintf

write是通过syscall运行得。

字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。

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

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

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

最终,我们想要输出得东西就显示在了屏幕上。

8.4 getchar的实现分析

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

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

通过这两个步骤,我们想要输入得东西就通过getchar被读取了。

8.5本章小结

本章主要介绍了系统I/Ode管理,系统将I/O设备统一抽象成文件,我们通过分析printf与getchar函数,明白了系统I/O得实现,也对计算机系统有了更深刻得理解。

(第81分)

结论

hello程序看似很简单,在计算机系统中却经历了相当复杂得过程:

  1. 源代码的诞生,程序员的努力
  2. 预处理,将hello头文件等展开,加入到hello.i中
  3. 编译,hello.i编译为汇编代码hello.s
  4. 汇编,hello.s经过处理转为可重定位目标文件hello.o,此时的文件仍然不能执行
  5. 链接,将hello.o与需要的动态链接库等链接成为可执行的文件,此时生成的hello就可以运行了
  6. 运行,在shell中输入相应指令,shell为其创建进程,并把hello加载到进程
  7. 执行,CPU为其分配时间片,以执行其自己的逻辑流
  8. 访存,虚拟地址转化为实际的物理地址访问,虚拟内存大大简化了程序,每个程序都有独立的虚拟空间
  9. 动态内存申请:程序中的printf用到了动态内存
  10. 信号,程序在运行中会对相应信号做出反应
  11. 结束,程序停止,shell对其进行了回收,020的过程结束了

看似简简单单的hello程序,背后确是复杂的过程的产物,计算机领域的任何一部分都需要我们的详细钻研,学习了计算机系统更是如此,学完之后只能感叹计算机系统的博大精深,我们只是了解了一个大概,更深的部分仍需要我们钻研。

万丈高楼平地起,小小的hello蕴含着无数大师的心血,我们必须脚踏实地,一点一点学。

大作业的过程使我回顾了一边本学期学过的东西,也弄明白了一些以前有些模糊的东西,在做作业的过程中也对linux的操作更熟练了。

一些看法:我觉得个人计算机会逐渐向云方向发展,计算机的性能越来越好,也许以后会重新出现那种一台主机多人使用的情况(通过云的方式)

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

附件

hello                           最终的可执行二进制文件

helloelfld.txt       链接之后的可执行文件的readelf信息

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

hello.o                      可重定位二进制文件

hello.s                         汇编之后的汇编代码

hello.c                       原始的C文件

helloelf.txt                  链接之前的二进制文件的readelf信息

hellold.s                   链接之后文件的反汇编

helloo.s                       链接之前文件的反汇编

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

参考文献

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

[1]  深入理解计算机系统(原书第3版)(美)兰德尔・E.布菜恩特( Randal E. Bryant)等著; 龚奕利,贺莲译,一北京:机械工业出版社,2016.7 (计算机科学丛书)

[2]  计算机组成与系统结构/袁春风编著.一北京:清华大学出版社,2010.4 (21世纪大学本科计算机专业系列教材)

[3]  https://www.cnblogs.com/pianist/p/3315801.html

[4]  https://www.cnblogs.com/pianist/p/3315801.html?tdsourcetag=s_pctim_aiomsg

[5]  https://www.cnblogs.com/diaohaiwei/p/5094959.html

[6]  百度百科

(参考文献0分,确实 -1分)

CSAPP 大作业 程序人生相关推荐

  1. 哈尔滨工业大学CSAPP大作业程序人生

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 1190202126 班 级 1936602 学 生 李映泽 指 导 教 师 刘宏伟 计算机科学与技术学院 20 ...

  2. CSAPP大作业程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学   号 班   级 学       生 指 导 教 师 吴锐 计算机科学与技术学院 2022年5 ...

  3. hit csapp大作业 程序人生-Hello’s P2P

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机科学与技术 学   号 2021112810 班   级 2103103 学       生 肖芩芩 指 ...

  4. 哈工大csapp大作业程序人生

    大作业 题     目 程序人生-Hello's P2P 专       业 计算机科学与技术 学    号 2021111719 班    级 2103101 学       生 杨济荣 指 导 教 ...

  5. 哈工大2022春CSAPP大作业-程序人生(Hello‘s P2P)

    摘  要 本论文研究了hello.c这一简单c语言文件在Linux系统下的整个生命周期,以其原始程序开始,依次深入研究了编译.链接.加载.运行.终止.回收的过程,从而了解hello.c文件的" ...

  6. CSAPP大作业——程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机科学与技术 计算机科学与技术学院 摘  要 本文基于Linux系统下,对hello程序的生命周期展开研究 ...

  7. csapp大作业程序人生-Hello’s P2P

    目  录 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概念与作用 2.2在Ubuntu下预处理的命令 2.3 Hell ...

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

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

  9. HITICSAPP2019大作业——程序人生-Hello’s P2P

    俺是Hello,额是每一个 程序猿 ¤的初恋(羞羞--)却在短短几分钟后惨遭每个菜鸟的无情抛弃(呜呜--),他们很快喜欢上sum.sort.matrix.PR.AI.IOT.BD.MIS--,从不回头 ...

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

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

最新文章

  1. 练手扎实基本功必备:非结构文本特征提取方法
  2. 机器学习拓展知识(数学/统计/算法)
  3. python 的日志logging模块学习
  4. Fedora重新调整发布周期,Fedora 30将保留很长时间
  5. 如何去掉Delphi自动生成的~.pas等临时文件
  6. 创建可调试,热加载的TypeScript+Koa工程
  7. mysql 中eq_表达式中的运算符EQ NE GT GE LT LE…..
  8. 实现在Android 多点手势识别
  9. oracle用分号拼接函数,ORACLE以逗号分隔连接列的值   函数名:wmsys.wm_concat
  10. 我为什么鼓励大家运营个人公众号
  11. CVX使用基础教程-结合官网教程翻译
  12. 实验用USB转RS-232下载线制作
  13. 按键精灵文字识别插件_按键精灵课程学习目录
  14. 为了入职蚂蚁金服,鬼知道我经历了什么.. 蚂蚁金服架构师面经
  15. 查看docker镜像中的文件
  16. 狂飙突进的幻想乡 - 最短路 - 辛普森积分
  17. AcceptEx函数特点及需要提防的地方,看完了解更深入了,所以转过来
  18. ESP8266/ESP32 网络温控器监控 Web服务器-基于温度控制输出
  19. java_枚举类型(支付方式枚举)
  20. 京东码农:淡定认为裁员轮不到我!没想正讨论工作就接到被裁通知

热门文章

  1. 洛谷试炼场------题目
  2. 使用vue实现四级联动
  3. 教你快速设计正交试验及试验数据分析
  4. 模糊c均值聚类算法原理详细讲解
  5. php 怎么判断是微信浏览器,php 判断是否为手机浏览器和微信浏览器
  6. Axure RP 使用基础教程
  7. IDEA中下载mybatis插件mybatis plugins 离线版安装
  8. 2.(echarts篇)echarts颜色地图边缘高亮
  9. 实现背景轮播登录页面
  10. python+jpype+linux出现内存溢出问题解决方案