【无标题】

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算学部
学   号 120L020120
班   级 2003010
学 生 刁浩宇    
指 导 教 师 郑贵滨

计算机科学与技术学院
2022年5月
摘 要
关键词:计算机系统 汇编 链接 进程 存储 I/O

目 录

第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简介
P2P指的是由program到process,也就是hello文件整个的编译及运行过程,其中包含了程序的指令和数据等信息。程序运行时,操作系统将该文件加载到内存中,并且将运行中的程序实例抽象为一个进程(process),程序员用高级语言编写得到.c文件,再经过编译器进行预处理得到.i文件,然后对其编译得到.s汇编语言文件,最后通过汇编器将.s文件翻译成机器语言,将指令打包成为可重定位的.o目标文件,再通过链接器与库函数链接得到可执行文件hello,执行此文件hello,操作系统会为其fork产生子进程,再调用execve函数加载进程。
O2O指的是由0到0,从二进制文件运行开始,操作系统会为其fork产生子进程,再调用execve函数加载进程,操作系统运行结束后清理掉程序运行时所占用的内存,关闭程序打开的文件描述符,释放程序运行占用的物理资源,清理程序在内核中的数据结构等,至此。程序运行的痕迹被操作系统完全擦除,回归至“0”。
1.2 环境与工具
硬件Intel Core i7 x64CPU
软件Ubuntu18.04.1 LTS
开发工具visual studio 、edb、gcc、gdb、readelf、HexEdit、ld
1.3 中间结果
hello.i 预处理器修改后的源程序,用来分析预处理器的行为
hello.o 可重定位目标程序,用来分析汇编器的行为
hello.s 编译器生成的编译程序,用来分析编译器行为
hello.elf hello.o的elf格式 hello1.txt hello.o的反汇编代码
hello.elf hello的elf格式
hello 可执行目标程序,用来分析链接器行为
hello1.s hello反汇编之后的可重定位文件,用来分析重定位过程
1.4 本章小结
主要介绍了P2P、O2O的概念,硬软件环境及中间产物等。

第2章 预处理
2.1 预处理的概念与作用
预处理的概念:系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。C语言提供多种预处理功能,主要处理以#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。
预处理的作用:使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
以#includ为例,预处理器(cpp)把程序中声明的文件复制到这个程序中,具体到hello.c的就是#include <unistd.h> 、#include <stdlib.h>、#include<stdio.h>。cpp把这些头文件的内容插入到程序文本中,方便编译器进行下一步的编译。结果就是得到了另一个c程序,通常得到的程序以.i作为文件扩展名。
2.2在Ubuntu下预处理的命令
gcc hello.c -m64 -no-pie -fno-PIC -E -o hello.i
图一  Ubuntu下预处理命令
2.3 Hello的预处理结果解析
可以发现,原本的源代码文件只有28行,预处理后的文件为3060行,原本的
源代码部分在 3046 行之后,在这之前是 hello 引用的所有的头文件的stdio.h,unistd.h , stdlib.h 内容的展开。而很显然我们发现插入的部分不止有这三个头文件的内容,还出现了其他的头文件,这是以为这三个头文件中同样使用#include 命令引入了其他的头文件,这些头文件同样出现在了 hello.i 文件中。插入的库文件的具体信息
如下图所示:
图二  hello.i文件节选
可以观察如下两张图,我们发现在源代码头部出现的注释在预处理之后的源代码部分已经不可见,因此这一点就印证了我们上面说的在预处理过程中预处理器将删除源代码中的注释部分。由于源代码中不存在宏定义与#ifdef 等部分,因此这一部分无法展示。
图三  hello.c文件
图四  hello.i文件中主程序
2.4 本章小结
这一部分介绍了在预处理过程中预处理器的工作(头文件展开,宏替换,删除注释,条件替换等),同时使用 ubuntu 系统展示了对于 hello.c 文件的预处理过程与预处理结果。

第3章 编译
3.1 编译的概念与作用
概念:编辑器将文本文件hello.i翻译成文本文件hello.s,即一个汇编语言程序。
作用:把源程序翻译成目标程序,进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信息。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
在这里插入图片描述
3.3 Hello的编译结果解析
3.3.1 数据
①常量
数字常量:通过观察我们可以发现在源代码中使用的数字常量都是储存在.text段的,包括在比较的时候使用的数字变量 3,在循环的时候使用的循环比较变量等数字常量都是储存在.text 节的,具体情况可以见如下截图:
在这里插入图片描述
字符串常量:可以发现在 printf 等函数中使用的字符串常量是储存在.rotate段的,具体储存情况可以见如下截图:
在这里插入图片描述
②变量
全局变量:本代码中并没有全局变量
局部变量:可以发现局部变量是储存在栈中的某一个位置的或是直接储存在寄存器中的,对于源代码中的每一个局部变量可以进行逐一分析。局部变量共有三个,一个是循环变量 i,以及 argc 和 argv,对于 i,我们发现它储存在栈中地址为-4(%rbp)的位置,对于 i 的操作可见如下截图:
在这里插入图片描述
对于局部变量 argc,标志的是在程序运行的时候输入的变量的个数,可以发现它储存在栈中地址为-20(%rbp)的位置,对于它的操作主要是与 3 比较之后确定有一部分代码是否执行,具体汇编代码如下截图:
在这里插入图片描述
对于局部变量 argv,是一个保存着输入变量的数组,观察发现它储存在栈中, 具体汇编代码段如下:
在这里插入图片描述
3.3.2 赋值
可以发现对于变量的赋值在代码中是对于循环变量 i 的在循环中的赋值,可以分别进行分析。对于局部变量 i,每次循环结束的时候都对齐进行+1 操作,具体的操作汇编代码如下:
在这里插入图片描述
3.3.3 算术操作
对于局部变量 i,由于其是循环变量,因此在每一轮的循环中都要修改这个值, 对于这个局部变量的算术操作的汇编代码如下:
在这里插入图片描述
3.3.4 关系操作
源代码中一共出现了两处关系操作,具体情况可以分别分析。第一处是对于 argc 的判断,当等于 3 的时候将进行条件跳转,其中源代码片段如下: 在这里插入图片描述
而对应的汇编代码如下: 在这里插入图片描述
另一处是在 for 循环中对于循环变量 i 的判断,这一段的汇编代码如下图所示,
在这里插入图片描述
3.3.5 数组/指针/结构操作
这一段代码中出现的数组操作只有一个,也就是对于 argv 数组的操作,观察 汇编代码可以发现 argv 储存的两个值都存放在栈中,argv[1]的储存地址是-32 (%rbp),而 argv[1]的储存地址是-24(%rbp),对于数组操作的汇编代码如下截图:
在这里插入图片描述
3.3.7 函数调用
在这一段代码中出现了几个函数调用的情况,首先明确在 X86 系统中函数参数储存的规则,第 1~6 个参数依次储存在%rdi、%rsi、%rdx、%rcx、%r8、%r9 这六个寄存器中,其余的参数保存在栈中的某些位置。
Main 函数:
参数:传入参数 argc 和 argv,其中 argv 储存在栈中,argc 储存在%rdi 中
返回:在源代码中最后的返回语句是 return 0,因此在汇编代码中最后是将%eax设置为 0 并返回这一寄存器。汇编代码如下:
在这里插入图片描述
Printf 函数:
参数:第一次调用的时候只传入了字符串参数首地址;for 循环中调用的时候传入了 argv[1]和 argc[2]的地址。
调用:第一次是满足 if 条件的时候调用,第二次是在 for 循环条件满足的时候调用。
具体汇编代码如下:
在这里插入图片描述
Exit 函数:
参数:传入的参数为 1,执行退出命令。
调用:当 if 条件满足的时候调用这一函数。
具体汇编代码如下:
在这里插入图片描述
3.4 本章小结
本章主要介绍了在将修改了的源程序文件转换为汇编程序的时候主要发生的变化以及汇编代码文件中主要存在的部分以及源代码中的一些主要的操作对应的汇编代码中的汇编代码的展现形式。总的来说,编译器做的就是在进行词义分析和语义分析之后判断源代码符合语法要求之后将其转换为汇编代码。

第4章 汇编
4.1 汇编的概念与作用
概念:汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程,汇编器(as)将.s汇编程序翻译成机器语言并将这些指令打包成可重定目标程序的格式存放在.o目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。
作用:将汇编语言翻译成机器语言,使其在链接后能够被机器识别并执行。
在这里插入图片描述
4.3 可重定位目标elf格式
在这里插入图片描述
在linux下生成hello.o文件的elf格式命令:readelf -a hello.o > hello.elf
在这里插入图片描述
ELF头 :ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
在这里插入图片描述
节头:记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。
在这里插入图片描述
在这里插入图片描述
重定位节:重定位节保存的是.text节中需要被修正的信息(任何调用外部函数或者引用全局变量的指令都需要被修正),调用外部函数的指令和引用全局变量的指令需要重定位,调用局部函数的指令不需要重定位。Hello程序中需要被重定位的有printf、puts、exit、sleep、sleepseces、getchar和.rodata中的.L0和.L1。
.rela.eh_frame节是.eh_frame节的重定位信息。
在这里插入图片描述
符号表:.symtab,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息,一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
在这里插入图片描述
4.4 Hello.o的结果解析
objdump -d -r hello.o>hello1.txt
在这里插入图片描述在这里插入图片描述
结果解析:与hello.s的差异
分支转移
hello.s
在这里插入图片描述
hello1.txt
在这里插入图片描述
反汇编代码跳转指令的操作数使用的不是段名称,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。
对函数的调用与重定位条目对应
hello.s
在这里插入图片描述
hello1.txt
在这里插入图片描述
在可重定位文件中call后面不再是函数的具体名称,而是一条重定位条目指引的信息。而在反汇编文件中可以看到,call后面直接加的是偏移量。
立即数变为十六进制格式
hello.s
在这里插入图片描述
hello1.txt
在这里插入图片描述
在编译文件中,立即数全部是以16进制表示的,因为16进制与2进制之间的转换比十进制更加方便,所以都转换成了16进制。

4.5 本章小结
本章对应的主要是hello.s汇编到hello.o的过程。在本章中,我们查看了hello.o的可重定位目标文件的格式,使用反汇编查看hello.o经过反汇编过程生成的代码并且把它与hello.s进行比较,分析和阐述了从汇编语言进一步翻译成为机器语言的汇编过程。

第5章 链接
5.1 链接的概念与作用
概念:链接是将各种不同文件的代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。
作用:把预编译好了的若干目标文件合并成为一个可执行目标文件。使得分离编译称为可能,不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为可独立修改和编译的模块。当改变这些模块中的一个时,只需简单重新编译它并重新链接即可,不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
在这里插入图片描述
5.3 可执行目标文件hello的格式
readelf -a hello > hello1.elf
ELF头和节头
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
5.4 hello的虚拟地址空间
在这里插入图片描述
5.5 链接的重定位过程分析
objdump -d -r hello > hello1.s
在这里插入图片描述
在链接过程中,hello 中加入了代码中调用的一些库函数,
在 hello 中,对全局变量进行了定位,因此全局变量的的值使用一个确切的值加上%rip 表示全局变量的位置。
hello 中无 hello.o 中的重定位条目,并且跳转和函数调用的地址在 hello 中都变成了虚拟内存地址。这是由于 hello.o 中对于函数还未进行定位,只在.rel.text 中添加了重定位条目,而 hello 进行定位之后自然不需要重定位条目。
地址访问:在链接完成之后,hello 中的所有对于地址的访问或是引用都调用的是虚拟地址地址。

链接的过程:
链接主要分为两个过程:符号解析和重定位。
符号解析:目标文件定义和引用符号,符号解析将每个符号引用和一个符号定义关联起来。
重定位:编译器和汇编器生成从 0 开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
5.6 hello的执行流程
根据反汇编代码可以看出执行函数及虚拟内存地址如下:
401000 <_init>
401020 <.plt>
401090 puts@plt
4010a0 printf@plt
4010b0 getchar@plt
4010c0 atoi@plt
4010d0 exit@plt
4010e0 sleep@plt
4010f0 <_start>
401120 <_dl_relocate_static_pie>
4011d6
401270 <__libc_csu_init>
4012e0 <__libc_csu_fini>
4012e8 <_fini>
5.7 Hello的动态链接分析
当程序调用一个由共享库定义的函数时,由于编译器无法预测这时候函数的地址是什么,因此这时,编译系统提供了延迟绑定的方法,将过程地址的绑定推迟到第一次调用该过程时。通过 GOT 和过程链接表 PLT 的协作来解析函数的地址。在加载时,动态链接器会重定位 GOT 中的每个条目,使它包含正确的绝对地址,而PLT 中的每个函数负责调用不同函数。那么,通过观察 edb,便可发现 dl_init后.got.plt 节发生的变化。
5.8 本章小结
在链接过程中,各种代码和数据片段收集并组合为一个单一文件。利用链接器,分离编译称为可能,我们不用将应用程序组织为巨大的源文件,只是把它们分解为更小的管理模块,并在应用时将它们链接就可以完成一个完整的任务。
经过链接,已经得到了一个可执行文件,接下来只需要在 shell 中调用命令就可以为这一文件创建进程并执行该文件。

第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程的经典定义就是一个执行中程序的实例,系统中的每个程序都运行在某个进程的上下文中。
进程的作用:每次用户通过向shell应用程序输入一个可执行程序的名字,运行这个可执行程序时,shell就会自动创建一个新的应用进程,并在这个新的应用进程的可执行上下文中自动运行这个可执行文件,应用程序也同样自动创建新的可执行进程,并在这个新进程的可执行上下文中用户可以运行自己的可执行代码或者其他应用程序。进程为用户提供了两个假象:一是程序好像是当前系统中运行的唯一的程序,好像独占的使用处理器和内存。二是处理器好像是无间断的执行着程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell 执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解析命令行,并根据解析结果运行程序。
处理流程:
从终端读入输入的命令。
将输入字符串切分获得所有的参数。
如果是内置命令则立即执行。
否则调用相应的程序为其分配子进程并运行。
shell应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
父进程通过调用 fork 函数创建一个新的运行的子进程。调用 fork 函数后,新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这意味着当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。fork 被调用一次,却返回两次,子进程返回 0,父进程返回子进程的 PID。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。
6.4 Hello的execve过程
当创建了一个子进程之后,子进程调用execve函数在当前子进程的上下文中加载并运行一个新的程序——hello程序。
Execve:execve调用驻留在内存中被称为启动加载器的操作系统代码来执行Hello程序,加载器删除子进程现有的用户虚拟内存段,并创建一组新的代码、数据、堆和栈,并将新的栈和堆初始化为0。然后通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC 指向_start 地址,_start 最终调用hello中的main 函数。然后加载器将PC指向hello程序的起始位置(_start),即从下条指令开始执行hello程序。
6.5 Hello的进程执行
用户模式和内核模式:处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围,从而使操作系统内核提供一个无懈可击的进程抽象。处理器通常是用某个控制寄存器的一个模式位来提供能这种功能的,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据。
上下文:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。
上下文切换:当内核选择一个新的进程运行时,则内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程:(1)保存之前进程的上下文(2)恢复某个先前被抢先的进程被保存的上下文(3)将控制传递给这个新恢复的进程。
在这里插入图片描述
6.6 hello的异常与信号处理
Hello程序可能会出现的异常有:
(1)中断:在程序执行过程中可能出现外部I/O设备引起的异常,或时钟中断等。
在这里插入图片描述
(2)陷阱:陷阱是执行一条指令的结果,hello执行sleep函数时会出现这个异常。
在这里插入图片描述
(3)故障:可能会发生缺页故障等。
在这里插入图片描述
(4)终止:终止是不可恢复的错误,在hello执行过程可能会出现DRAM或SRAM位损坏的奇偶错误等。
在这里插入图片描述
不停乱按,乱码会被认为是命令,不影响进程。
Ctrl-Z,产生中断异常,它的父进程收到SIGSTP信号,程序本身这时被挂起
Ctrl-Z 后运行 ps,打印出了各进程的 pid,可以看到之前挂起的进程 hello。Ctrl-Z 后运行 jobs,打印出了被挂起进程组的 jid,可以看到之前被挂起的 hello,已被挂起的标识 Stopped。
在这里插入图片描述
Ctrl-Z 后运行 Kill:重新执行进程,可以发现 hello 的进程号为 4030,那么便可通过 kill -9 4030 发送信号 SIGKILL 给进程 4030,它会导致该进程被杀死。然后再运行 ps,可发现已被杀死的进程 hello。
在这里插入图片描述
按下 Ctrl-C:进程收到 SIGINT 信号,结束 hello。在 ps 中查询不到其 PID,在job 中也没有显示,可以看出 hello 已经被彻底结束。
在这里插入图片描述
6.7本章小结
本章主要介绍了 hello 可执行文件的执行过程,包括进程创建、加载和终止,以及通过键盘输入等过程。从创建进程到进程并回收进程,这一整个过程中需要各种各样的异常和中断等信息。程序的高效运行离不开异常、信号、进程等概念,正是这些机制支持 hello 能够顺利地在计算机上运行。

第7章 hello的存储管理
7.1 hello的存储器地址空间
1.逻辑地址:逻辑地址是程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或一条指令的地址。它由选择符和偏移量组成。
2.线性地址(虚拟地址):一个逻辑地址在经过段地址机制的转化后变成一个线性分页地址,它与逻辑地址类似也是一个不真实的地址。其格式可以表示为虚拟地址描述符:偏移量。
3.物理地址:计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。物理地址用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。最后两位涉及权限检查。
7.2 Intel逻辑地址到线性地址的变换-段式管理
机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到。
一个逻辑地址由两部份组成,段号: 段内地址。段号是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,表示具体的是代码段寄存器还是栈段寄存器抑或是数据段寄存器。
在这里插入图片描述
段式管理特点:
1.段式管理以段为单位分配内存,每段分配一个连续的内存区。
2.由于各段长度不等,所以这些存储区的大小不一。
3.同一进程包含的各段之间不要求连续。
4.段式管理的内存分配与释放在作业或进程的执行过程中动态进行。
7.3 Hello的线性地址到物理地址的变换-页式管理
将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片,然后把页式虚拟地址与内存地址建立一一对应页表。
在这里插入图片描述
7.4 TLB与四级页表支持下的VA到PA的变换
Core i7采用四级页表的层次结构。CPU产生虚拟地址VA,虚拟地址VA传送给MMU,MMU使用VPN高位作为TLBT和TLBI,向TLB中寻找匹配。如果命中,则得到物理地址PA。如果TLB中没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。
在这里插入图片描述
7.5 三级Cache支持下的物理内存访问
获得物理地址之后,先取出组索引对应位,在L1中寻找对应组。如果存在,则比较标志位,相等后检查有效位是否为1.如果都满足则命中取出值传给CPU,否则按顺序对L2cache、L3cache、内存进行相同操作,直到出现命中。然后再一级一级向上传,如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,将目标块放到被驱逐块的位置。
7.6 hello进程fork时的内存映射
在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。
7.7 hello进程execve时的内存映射
①删除已存在的用户区域
②映射私有区域
③映射共享区域
7.8 缺页故障与缺页中断处理
如果程序执行过程中发生了缺页故障,则内核调用缺页处理程序。
处理程序执行如下步骤:
1.检查虚拟地址是否合法,如果不合法则触发一个段错误,程序终止。
2.检查进程是否有读、写或执行该区域页面的权限,如果不具有则触发保护异常,程序终止。
3.两步检查都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。
7.9动态存储分配管理
Malloc函数的机制说明
从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。关于堆的知识呢可以查询数据结构方面的知识。在使用malloc()分配内存空间后,一定要记得释放内存空间,否则就会出现内存泄漏。
7.10本章小结
本章主要介绍了 hello 进程在执行的过程中的虚拟内存与物理内存之间的转换关系,以及一些支持这些转换的硬件或软件机制。同时介绍了在发生缺页异常的时候系统将会如何处理这一异常。最后介绍了动态内存分配的作用以及部分方法与策略。

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件。所有的 I/O 设备都被模型化 为文件,而所有的输入输出都被当做对相应文件的读和写来执行。
设备管理:unix io接口,即将设备映射为文件的方式。
8.2 简述Unix IO接口及其函数
Unix IO 函数:
(1).打开文件:int open(char *filename, int flags, mode_t mode);
Open 函数将 filename 转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程当中没有打开的最小描述符。Flags 参数指明了进程打算如何访问这个文件,同时也可以是一个或者更多为掩码的或,为写提供给一些额外的指示。
Mode 参数指定了新文件的访问权限位。
(2).关闭文件:int close(int fd);
调用 close 函数,通知内核结束访问一个文件,关闭打开的一个文件。成功返回 0,出错返回-1。
(3).读文件:ssize_t read(int fd, void *buf, size_t n);
调用 read 函数从描述符为 fd 的当前文件位置复制最多 n 个字节到内存位置buf。返回值-1 表示错误,返回值 0 表示 EOF,否则返回值表示的是实际传送的字节数量。
(4).写文件:ssize_t write(int fd, const void *buf, size_t n);
调用从内存位置 buf 复制至多 n 个字节到描述符 fd 的当前文件位置。返回值-1 表示出错,否则,返回值表示内存向文件 fd 输出的字节的数量。
8.3 printf的实现分析
printf函数:
printf(const char *fmt,…)
{
int i;
va_list arg = (va_list)((char *)($fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
引用的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);
}
}
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar 是读入函数的一种。它从标准输入里读取下一个字符,相当于
getc(stdin)。返回类型为 int 型,为用户输入的 ASCII 码或 EOF。getchar 可用宏实
现:#define getchar() getc(stdin)。getchar 有一个 int 型的返回值。当程序调用 getchar
时.程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回
车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar 才开始从 stdin
流中每次读入一个字符。getchar 函数的返回值是用户输入的字符的 ASCII 码,若
文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕[3]。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章主要介绍了 linux 系统中的 I/O 设备基本概念和管理方法,同时简单介绍了 printf 和 getchar 函数的实现。
结论
预处理:hello.c预处理到hello.i文本文件;
编译:hello.i编译到hello.s汇编文件;
汇编:hello.s汇编到二进制可重定位目标文件hello.o;
链接:hello.o链接生成可执行文件hello;
创建子进程:bash进程调用fork函数,生成子进程;
加载程序:execve函数加载运行当前进程的上下文中加载并运行新程序hello;
访问内存:hello的运行需要地址的概念,虚拟地址是计算机系统最伟大的抽象;
交互:hello的输入输出与外界交互,与linux I/O息息相关;
终止:hello最终被shell父进程回收,内核会收回为其创建的所有信息。

附件
hello.i 预处理器修改后的源程序,用来分析预处理器的行为
hello.o 可重定位目标程序,用来分析汇编器的行为
hello.s 编译器生成的编译程序,用来分析编译器行为
hello.elf hello.o的elf格式
hello1.txt hello.o的反汇编代码
hello.elf hello的elf格式
hello 可执行目标程序,用来分析链接器行为
hello1.s hello反汇编之后的可重定位文件,用来分析重定位过程

参考文献
[1] 深入理解计算机系统原书第3版-文字版.pdf
[2] https://www.cnblogs.com/pianist/p/3315801.html
[3] https://baike.baidu.com/item/getchar/919709?fr=aladdin

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值