计算机系统大作业

文章目录

计算机系统

大作业
题 目程序人生-Hello’s P2P
专业人工智能
学号7203611314
班级666
学生五十一
指 导 教 师刘宏伟
计算机科学与技术学院
2021年5月

摘 要

关键词: 预处理;编译;汇编;链接; 进程管理;存储管理; I/O管理
论文从第一人称的视角讲述了hello.c从源码被预处理、编译、汇编、链接,以及进程管理,存储管理和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: From Program to Process 从代码到运行
Program: 在编辑器中输入代码,保存为hello.c的程序
Process: C程序经过cpp的预处理,ccl的编译、as的汇编、ld的链接最终成为可执行目标文件hello。在shell中输入启动命令,shell将其fork成进程来运行。

020: From Zero-0 to Zero-0 我轻轻的来,正如我轻轻的走,不带走一片云彩。
Shell为进程进行execve,映射虚拟内存,进入程序入口后程序开始载入物理内存。进入main执行目标代码,cpu为hello分配时间片执行逻辑控制流。
程序运行结束时,shell父进程回收hello进程,内核删除为其产生的数据结构。

1.2 环境与工具

硬件环境:Intel® Core™ i7-10750H CPU; 2.60GHz; x86-64架构;512Disk
软件环境:ubuntu20.04;windows10; vmware
开发与调试工具:gcc; gdb; edb; vscode; gedit

1.3 中间结果

文件作用
hello.c源代码
hello.ihello.c 预处理生成hello.i
hello.shello.i 编译生成hello.o文件
hello.ohello.s 汇编生成hello.o文件
hello.ldhello.o链接生成hello.ld
obj.txthello.ld 反汇编生成
hello.objdump可执行txt文件

1.4 本章小结

本章介绍了P2P、020和软硬件环境,以及开发与测试工具,总体性的描述了高级语言编写的代码hello.c经过预处理、编译、汇编、链接等流程的中间产物,各个环节互相配合,相互依赖,高效完成,体现了计算机系统的整体性。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对原程序中的预处理部分做处理,处理完毕自动进入对源程序的编译。C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义{#define}、头文件包含{#include}、条件编译(#ifdef)等。
作用: 大多数预处理器指令属于下面三种类型:
1) 宏定义: #define指令定义一个宏,#undef指令删除一个宏定义。
2)文件包含: #include指令导致一个指定文件的内容被包含到程序中。
3) 条件编译:#if, #ifdef, #ifndef, #elif, #else 和 #dendif指令可以根据编译器测试的条件来将一段文本包含到程序中或者排除在程序之外。
4) 删除注释:不编译注释内容。

2.2在Ubuntu下预处理的命令

在这里插入图片描述

图 1 预处理命令

在这里插入图片描述

图2 hello.i

2.3 Hello的预处理结果解析

图3 hello.i
hello.i文件变成了3060行,内容增加,不过仍然是可读的C代码。预处理过程将源文件中的头文件和宏展开,将注释去除。

2.4 本章小结

hello.c经过预处理,头文件插入,宏定义展开变为hello.i后,长度极大地增加,为下一阶段的编译做准备。

第3章 编译

3.1 编译的概念与作用

概念:
编译(compilation , compile)
1) 利用编译程序从源语言编写的源程序产生目标程序的过程。
2) 用编译程序产生目标程序的动作。 编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。 3) 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
作用:
将高级语言程序代码翻译为汇编语言代码,把代码转化为汇编指令。利用编译程序从源语言编写的源程序产生目标程序,把高级语言变成计算机可以识别的2进制语言。

3.2 在Ubuntu下编译的命令

在这里插入图片描述

图4 编译命令 ### 3.3 Hello的编译结果解析

在这里插入图片描述

图5 编译结果

在这里插入图片描述

图6 编译结果
3.3.1 数据

在这里插入图片描述

图 7 伪指令

伪指令用于指导汇编器和链接器的工作。

.file	"hello.c"	// 申明源文件
.text			// 指明代码段
.section	.rodata	// 指示为rodata节
3.3.2 rodata节数据

在这里插入图片描述

图 8 编译代码数据段

如图,在.rodata段,我们有两个数据:
1) 一个是LC0它存储的是我们第一条printf语句打印的字符串用法:“Hello 学号 姓名 秒数!\n”,均为编码表示。
2) 另一个是.LC1,由两个字符串,printf输出格式话化参数,打印学号和姓名。符。

3.3.3 局部变量

在这里插入图片描述

图 9 main函数

1) 两个参数 int argc 和 char *argv[] 存在栈中地址-20和-32处的寄存器%edi和%rsi
hello.c中main函数的局部变量是i 。在汇编中将变量i放在了栈上。
在这里插入图片描述

图 10 局部变量i

观察验证:-4(%rbp)被赋予了0的初值,然后与7进行比较,参与了循环,即可证明局部变量i存储在了-4(%rbp)的位置。

在这里插入图片描述

图 11 循环中的局部变量i
3.3.4 赋值操作

使用数据传送命令,可以进行赋值操作。最简单形式的数据传输类型是MOV类,MOV有movb, movw, movl, movq等操作。分别操作1、2、4、8字节的数据。mov操作的操作源可以是立即数、寄存器、内存。目的操作数可以是:寄存器、内存。即立即数只能作为mov的操作源。x86-64规定两个操作数不能都指向内存。

观察赋值语句 i = 0对应的汇编。
在这里插入图片描述

也就是说,通过movl把立即数0,放入i中,i是四字节int型。

3.3.5 算数运算

x86-64中的运算指令如下图12所示:
在这里插入图片描述

图 12 x86-64中的运算指令

hello.c对i有自加运算,观察汇编代码有:
在这里插入图片描述

3.3.6 关系运算

CPU维护着一组单个位的条件码(condition code),它们描述了最近算数运算或逻辑运算的属性。常见的条件码有:
CF: 进位标志
ZF: 零标志
SF: 符号标志
OF: 溢出标志
算数运算会设置条件码。此外,CMP指令和TEST指令能够设置条件码并不改变其他寄存器。CMP指令与SUB指令行为一致;TEST指令与AND指令行为一致。
在我们的hello程序中,出现了i < 8 的关系运算,我们观察它的汇编代码实现:
在这里插入图片描述

图 13 比较关系运算

汇编代码利用cmpl $7, -4(%rbp) 和 jle小于等于跳转,实现 i<8,即利用i<=7实现i<8,程序正常会跳转至.L4。

3.3.7 数组运算

对于数据类型T和整型常数N。T[N]若其位置为Xa,那么T[i]就被存放在Xa+sizeof(T)*i的地址。在hello.c中访问了argv[1]与argv[2]。
对应到汇编代码:
在这里插入图片描述

图 14 访问数组元素汇编代码

argv[]首地址为-4(%rbp)。-4(%rbp)+16是argv[2]的地址,它指向的值被放入了%rdx;-4(%rbp)+8的地址是argv[1],将其指向的值放入%rsi。argv数组中存储的数据类型是char*,占据8个字节。而-4(%rbp)+16与-4(%rbp)+8恰好差8个字节。

3.3.8 控制转移

条件分支的实现由两种主要的方式:
一、通过结合有条件跳转和无条件跳转,这是使用控制的条件跳转(if);
二、计算一个条件操作的两种结果,根据条件是否满足从中选取一个(switch)。
hello程序中有一个if(argc != 4)的条件判断,汇编如下
在这里插入图片描述

图 15 条件跳转

如果-20(%rbp)等于4,则跳转到.L2
C语言有多种循环结构。
do-while循环

loop:
	body-stat;
	t = test-expr;
	if (t)
		goto loop;
	goto test;

while [jump to the middle]

	goto test;
loop:
	body-stat;
test:
	t = test-expr;
	if (t)
		goto loop;

while [guarded-do]

t = test-expr;
if (!t) 
	goto done;
loop:
	body-expr;
	t = test-expr;
	if (t)
		goto loop;
done: 

对于for循环,gcc会产生while的一种。
hello中有关for的部分对应汇编如下

在这里插入图片描述

图 16 for循环[jump to the middle]

这是典型的jump to the middle 。首先在.L2中进行变量初始化,然后跳转到.L3中进行条件测试,满足条件则进入.L4中执行循环体。

3.3.9 函数操作

在函数调用时,包括了许多机制:(P调用Q)
传递控制:程序计数器设置为Q的地址,返回时,计数器设置为Q之后指令的地址。
传递数据:P向Q提供了一个或多个参数,Q向P提供了一个返回值。
分配、释放内存。
hello.c中调用了printf, sleep等函数。printf的汇编代码如下
在这里插入图片描述

图 17 printf函数汇编调用

将.LC0中的字符串作为参数放入了%rdi,通过call调用了函数puts。x86-64中,我们最多可以通过站传递6个整形参数。参数有特定顺序:
在这里插入图片描述

图 18 参数传递顺序

也就是说,位于.LC0的字符串是puts函数的第一个参数。

3.4 本章小结

本章简述了编译的概念与作用,从伪指令、rodata节数据、局部变量、赋值操作、算数运算、关系运算、数组运算、控制转移、函数操作等方面对hello.s中的汇编代码进行了简单解析。

第4章 汇编

4.1 汇编的概念与作用

概念:
汇编器(as)将hello.s翻译为机器语言,产生可重定位目标程序,生成hello.o文件。hello.o文件是二进制文件。需要注意的是,此时的hello.o还未进行链接,所以不可直接运行。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
作用:
将hello.s 编译得到hello.o,再汇编得到机器语言二进制程序。

4.2 在Ubuntu下汇编的命令

在这里插入图片描述

图 19 汇编命令

4.3 可重定位目标elf格式

分心hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
在这里插入图片描述

图 20 可重定位目标文件

该文件可重定位目标文件,有13个节。查看头节表:
在这里插入图片描述

图 21 节头表

观察到有.text 节.data 节.bss节以及它们的偏移量等信息。

查看符号表:
在这里插入图片描述

图 22 符号表

每个节都从0开始,用于重定位,这段代码只可读不可写。
符号表中记录了程序中的符号以及它们的类型、位置等信息。

4.4 Hello.o的结果解析

objdump -d -r hello.o 

在这里插入图片描述

图 23 hello.o反汇编

在这里插入图片描述

图 24 hello.s

将hello.o的反汇编与hello.s进行对照分析。
1) 反汇编与.s主要函数大致相同,但是在栈的调用上有差别,反汇编采用不对齐的方式,对栈的利用率较高。
2) 函数调用时.s文件中需要给出函数具体的名称,反汇编中调用了函数的地址。
在这里插入图片描述

图 25 分支转移对比(左为汇编,右为反汇编)

3).s文件中指令跳转时使用段的名称,反汇编中调用跳转的地址,需要进行重定位。
在这里插入图片描述

图 26 重定位信息处理(左为汇编,右为反汇编)

可以观察到,汇编代码直接访问了.rodata节的数据,直接按函数名调用了函数,而反汇编代码中二者均未填入真实地址,需要进行重定位。

4) .s中操作数为10进制,反汇编中操作数为16进制。

4.5 本章小结

本章简述了汇编的概念与作用,对hello.s进行汇编生成了相应的可重定向目标文件hello.o 。使用readelf工具读取了elf文件的相关信息,观察了elf文件中的文件头、程序节、符号表等信息。比较了hello.s与hello.o反汇编形成的汇编代码的差异。

第5章 链接

5.1 链接的概念与作用

概念:链接是指在电子计算机程序的各模块之间传递参数和控制命令,并把它们组成一个可执行的整体的过程。即将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件的过程。
作用:让程序分离编译,将目标文件、启动代码、库文件等链接成可执行文件。

5.2 在Ubuntu下链接的命令

ld -o hello.ld -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 

输入上面指令
在这里插入图片描述

图 27 链接命令

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

 readelf -h hello.ld

在这里插入图片描述

图 28 可执行目标文件

该文件为可执行目标文件,有27个节

readelf -S hello.ld

在这里插入图片描述

图 29 节表头 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3212805603bc457ebbed546c61211d0b.png)
图 30 符号表

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息。
命令行输入edb,打开后,将hello.ld可执行文件拖入edb界面,查看左下角Data Dump一栏
在这里插入图片描述

图 31 edb查看虚拟地址空间

程序在0x00401000 ~ 0x00402000段中,虚拟地址从0x00401000开始,到0x00401ff0结束。

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

5.5 链接的重定位过程分析

首先,我们观察hello.o的反汇编代码。可以观察到,有许多地方并没有填入正确的地址,正等待进行链接。其中R_X86_64_32表示的类型是32位直接寻址,相应地方会填入.rodata的真实地址。而R_X86_64_PLT32表示puts函数和exit函数需要通过共享库进行动态链接。
输入 objdump -d -r hello.o
在这里插入图片描述

图 32 objdump查看hello.o反汇编

在hello.ld文件的反汇编代码中,我们发现之前的重定位地址已经被填入了正常的地址。
在这里插入图片描述

图 33 objdump查看hello.ld反汇编

观察elf文件信息,402008恰好属于.rodata节,而401030和401070恰好属于.plt节,而401090和4010d0恰好属于.plt节。、

  1. 链接增加了新的函数如exit、printf、sleep、getchar等
  2. hello中增加了.init和.plt节
  3. hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。

5.6 hello的执行流程

利用edb单步调试,每次跳转的函数依次记录

0000000000401000 <_init>
00000000004010f0 <_start>
00000000004011c0 <__libc_csu_init>
0000000000401125 <main>
0000000000401238 <_fini>

5.7 Hello的动态链接分析

在这里插入图片描述

在这里插入图片描述

图 34 objdump查看hello.ld反汇编

动态链接是把程序拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序。

5.8 本章小结

本章简述了链接的概念与作用,分析了我们经过链接生成的hello文件的结构以及与之前经过链接的hello.o文件的异同,我们分析了hello文件的运行流程,使用edb探索了动态链接的过程。

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是执行中程序的一个实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。
用:进程能够提供给应用程序一些关键抽象:
1) 一个独立的逻辑控制流。进程使得我们感觉好像在独占处理器。
2) 一个私有地址空间。进程使得我们感觉好像独占地使用内存系统。

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

shell是命令行界面的解析器,能够为用户提供操作界面,提供内核服务。shell能执行一系列的读、求操作,然后终止。读操作读取来自用户的一个命令行。求值操作解析命令代表用户运行程序。
shell的处理流程为:
1) 读取用户输入
2) 解析用户输入
3) 若要执行内部命令,直接执行。
4) 若要执行非内部命令,shell会fork子进程,在子进程中execve执行相关命令。
5) 根据&的有无,确定程序的前后台运行。

6.3 Hello的fork进程创建过程

在命令行输入 ./hello.ld 执行 hello 程序时,由于hello不是内部命令,所以shell会fork一个子进程并进行后续操作。
新建的子进程几乎和父进程相同。子进程拥有与父进程用户级虚拟地址空间相同且独立的一份副本,与父进程任何打开的文件描述符相同的副本。
使用fork()函数来创建一个子进程,fork函数的原型为:pid_t fork(void)
fork()函数有以下特点:
1)调用一次,返回两次。一次返回至父进程,返回的是子进程的pid;一次返回至子进程返回值为0。
2) 并发执行。父子进程是并发运行的独立进程。
3) 相同但独立的地址空间。子进程创建时,两个进程具有相同的用户栈、本地变量、堆、全局变量、代码。但是二者对这行的改变都是相互独立的。
4) 共享文件。

6.4 Hello的execve过程

使用fork创建进程后,子进程便会使用execve加载并运行hello程序,且带参数列表argv以及环境变量envp。execve调用一次,从不返回。

在这里插入图片描述

图 35 参数列表与环境变量列表

观察可知,argv指向一个指针数组,这个指针数组中的每一个指针指向一个参数字符串。其中argv[0]使我们所运行的程序的名字。envp指向一个指针数组,这个数组里面的每一个指针指向一个环境变量的字符串。环境变量字符串的格式为”name = value”。使用getenv函数获取环境变量,setenv、unsetenv来设置、删除环境变量。

execve会调用启动加载器。加载器会删除子进程现有的虚拟内存段,创建一组新的代码、数据、堆、栈。新的栈和堆被初始化为0。通过虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码数据初始化。最后,跳转到_start地址,最终调用main函数。

6.5 Hello的进程执行

系统中每个程序都运行在某个进程的上下文中。上下文是程序正确运行所需要的状态,由系统内核维持。
一个运行多个进程的系统,进程逻辑流的执行可能是交错的。每个进程执行它的流的一部分, 然后被抢占,轮到其他进程执行。一个逻辑流在时间上与另一个重叠,成为并发流。一个进程执行它的控制流的一部分时间叫做时间片。
控制寄存器利用模式位描述了当前进程享有的特权:当设置了模式位时,进程运行在内核模式中,可以执行任何命令,访问任何内存;当没有设置模式位时,进程为用户模式,不允许执行特权指令,不允许直接引用内核区的代码,数据。
在进程执行时,内核可以抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策称为调度。当进程调度一个新的进程运行后,会使用上下文切换来将控制转移到新的进程。
上下文切换时:
1) 保存当前进程的上下文;
2) 恢复某个先前被抢占进程的被保存的上下文;
3) 将控制传递给新进程。系统调用、中断可能引起上下文切换。
下图反映了进程上下文切换的过程:(源于csapp)
在这里插入图片描述

图 36 进程上下文切换的剖析

hello程序执行了sleep系统调用,应发了陷阱异常。此时会从用户模式进入内核模式,使程序休眠一段时间,将控制转给其他进程。当sleep结束后,发送信号给内核,进入内核状态异常处理,此时hello程序得以重新回到用户模式。当执行getchar函数时,会使用read系统调用,产生上下文切换。

6.6 hello的异常与信号处理

异常可分为四类:中断、陷阱、故障、终止。
在这里插入图片描述

图 37 四类异常

hello运行过程中,可能出现以上几种异常的原因:

异常导致原因
中断hello运行过程中遇到来自IO设备的中断,例如键盘
陷阱hello运行过程中调用了sleep系统调用
故障hello运行时,发生缺页异常
终止hello运行时发生硬件错误,如DRAM、SRAM的奇偶错误

hello运行过程中的异常与信号处理
正常运行
在这里插入图片描述

图 38 hello正常运行

使用Ctrl + C发出SIGINT信号,终止程序
在这里插入图片描述

图 39 Ctrl+C中断程序

使用Ctrl + Z翻出SIGTSTP信号,程序停止指导下一个SIGCONT

在这里插入图片描述

图 40 Ctrl+Z停止程序

使用ps查看进程信息
在这里插入图片描述

图 41 ps查看进程信息

使用jobs查看作业信息,可以发现hello程序处于停止状态
在这里插入图片描述

图 42 jobs查看作业状态

使用pstree查看进程树
在这里插入图片描述

图 43 pstree查看进程树

使用fg将后台程序置于前台
在这里插入图片描述

图 44 将后台进程置于前台运行

使用kill发送信号
在这里插入图片描述

图 45 kill发送信号

6.7本章小结

本章简述了进程、shell的概念,分析了hello程序使用fork创建子进程的过程以及使用execve加载并运行用户程序的过程,运用上下文切换、用户模式、内核调度等知识,分析了hello进程的执行过程,最后分析了hello对于异常及信号的处理并进行了实际操作。

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:编译器进行编译时,产生汇编代码,每句代码以及每条数据都会有自己的逻辑地址。
线性地址:CPU加载程序后,会为程序分配内存,分配的内存分为代码段数据和数据段数据。代码段基地址在CS中,数据段基地址在DS中。线性地址 = 段基地址 + 逻辑地址
虚拟地址: 虚拟内存中的地址
物理地址: 物理内存中的地址

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

段是对程序逻辑意义上的一种划分,一组完整逻辑意义的程序被划分为一段。段的长度不确定。段描述符用于描述一个段的详细信息。段选择符用于找到对应的段描述符。
流程如下:
通过段选择符的T1字段,确定是GDT中的段还是LDT中的段。
查找段描述符,获得基地址
基地址 + 偏移,得到线性地址

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

虚拟地址包含两个部分:VPN(虚拟页号)、VPO(虚拟页面偏移)页表基址寄存器指向当前的页表。MMU通过VPN在页表中找到对应的页表项PTE。将PTE中的PPN与VPO串联,得到最终的物理地址。
流程可见:
在这里插入图片描述

图 46 使用页表的地址翻译

进一步,可以将告诉缓存与虚拟内存相结合,告诉缓存可以存储我们的PTE以及数据,如下图:
在这里插入图片描述

图 47 缓存与虚拟内存

进一步,可以使用TLB加速虚拟内存。TLB是翻译后备缓冲器,是一个小的、虚拟寻址的缓存,每一行保存单个PTE组成的块。
在这里插入图片描述

图 48 TLB命中和不命中操作图

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

具体分析运行Linux的Intel Core i7, 它使用了TLB以及四级页表。Core i7支持48位的虚拟地址空间以及52位的物理地址空间。
首先,我们分析各级页表中条目的格式。
第一、二、三级页表条目格式如下:
在这里插入图片描述

图 49 第一、二、三级页表条目格式

存储的物理页号为40位。
第四级页表条目格式:
在这里插入图片描述

图 50 第四级页表条目格式

它存储的物理页号也为40位。
PTE中有三个权限位:
1) R/W 确定读写还是只读
2) U/S 确定用户模式时候可以访问
3) XD禁止执行
其他位:
A 引用位,用于实现替换算法
D 修改位,告知是否写回

进行翻译时,虚拟地址中的VPN被划分为VPN1, VPN2, VPN3, VPN4。CR3寄存器中有L1页表的基地址,依次类推,最终我们得到物理地址。并进行之后的访问。具体流程如下:
在这里插入图片描述

图 51 虚拟地址访问物理地址的过程

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

得到物理地址PA后,我们将物理地址进行分割。物理地址(52位)被分割位40位的标记位CT,6位的索引位CI,6位的块偏移位CO。通过CT查找告诉缓存中的对应块,通过CI在块中寻找行,若命中,则返回对应块偏移的数据。否则,L1不命中,需要从L2,L3甚至是主存中得到对应的数据。

7.6 hello进程fork时的内存映射

执行fork函数时,内核会为新进程创建各种数据结构,分配pid,。创建新进程的虚拟内存,新进程会拥有当前进程mm_struct,区域结构,页表的副本。但是两进程页面只读,区域写时复制。

7.7 hello进程execve时的内存映射

删除已存在的用户区域,映射私有区域:
所有这些新的区域都是私有的,写时复制的。代码和数据被映射至.text .data .bss区域,堆是请求二进制零的区域。
映射共享区域:hello程序与共享对象链接,这些对象动态链接到hello程序,然后映射到用户虚拟地址空间。
设置程序计数器:设置当前进程上下文程序计数器,使之指向代码区域的入口点。

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

DRAM缓存不命中称为缺页,当MMU翻译虚拟地址时发现在页表中,该页表项的有效位为0,触发缺页中断。然后缺页异常处理程序选择一个牺牲页将目标页加载到物理内存中,让导致缺页的指令重新启动,页面命中。

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的。分配器的类型有显示分配器和隐式分配器。
动态内存管理的策略包括首次适配、下一次适配和最佳适配。首次适配:从头搜索,遇到第一个合适的块就停下来;下次适配:从头搜索,遇到第一个合适的块不停,遇到下一个停止;最佳适配:全部搜索,选择最佳的块停止。
C语言中可以使用malloc和free函数来动态申请、释放内存。

7.9.1 隐式空闲链表

空闲块可以通过头部中的大小字段隐含地连接着。我们可以通过遍历堆中所有的块来间接遍历整个空间块的集合。
隐式空闲链表块格式:

在这里插入图片描述

图 52 使用边界标记的堆块的格式

隐式空闲链表的整体形式:(注意序言块和结尾块的存在)
在这里插入图片描述

图 53 隐式空闲链表的恒定形式
7.9.2 带边界标记的合并

通过双界标记,我们可以在常数时间内完成空闲块的合并,具体结构如下:
在这里插入图片描述

图 54 使用边界标记的堆块的格式
7.9.3 显式空闲链表

将空闲链表组织成一种显式的数据结构。在空闲块主体中放入指针。这样,我们可以快速定位到前、后的闲置链表,使首次适配的时间减少为线性时间(空闲块的个数),具体格式如下:
在这里插入图片描述

图 55 显式空闲链表
7.9.4 分离的空闲链表

维护多个空闲链表,将所有可能的块的大小划分为大小类。基本方法有:
1) 简化单分离存储
2) 分离适配
3) 伙伴系统

7.10本章小结

本章简述了系统对于hello的存储管理,介绍了Intel段式、页式管理,分析了程序的虚拟地址逐步翻译为物理地址的过程,分析程序运行过程中fork,execve函数进行的内存映射,说明了系统对于缺页异常的处理和动态存储的分配。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备模型:每一个设备都是一个文件,通过总线连接。所有的输入和输出都被当做对相应文件的读和写来执行。
设备管理:Linux内核引用出一个简单、低级的应用接口,称为Unix I/O,这使得所有输入和输出都能以一种传统且一致的方式执行。

8.2 简述Unix IO接口及其函数

1)read和write 函数

ssize_t read (int fd, void *buf, size_t n);
ssize_t write (int fd, const void *buf, size_t n);

fd是描述符
2) close()函数用于关闭一个文件,所需头文件为#include <unistd.h>,返回值为0时成功,-1时失败。
3) open()函数用于打开文件,或者创建文件,将filename转换成文件描述符。成功时返回文件描述符,失败时返回-1。

8.3 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个元素写入,从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

int getchar(void)
{
	static char buf[BUF];//缓冲区
	static char* b=buf;//缓冲区首位置的指针
	static int i=0;//静态变量个数
	if(i==0)
	{
		i=read(0,buf,BUF);//读出缓冲区中所有数
		b=buf;
	}
	return(--n>=0)?(unsigned char)*b++:EOF;//读完输出EOF,否则返回读取字符的ASCII码
}

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

介绍了系统的I/O的设备模型化和管理,以及文件的读写函数,深入学习了printf函数和getchar函数的用法。

结论

  1. 初始时,hello.c是存储在磁盘上的一个文本文件。
  2. hello.c经过gcc进行了预处理,形成了另一个文本文件hello.i
  3. hello.i经过编译器gcc翻译成文本文件hello.s,hello.s中包含着汇编语言代码。
  4. hello.s经过汇编器as翻译成机器语言指令,并打包形成可重定位目标文件。
  5. 链接器ld将hello.o需要的各种目标文件与hello.o进行链接,形成可执行目标文件hello.ld
  6. 使用shell执行hello
  7. shell使用fork创建子进程,使用execve加载并运行hello程序。这一过程中,涉及到了虚拟内存,内存映射等知识。
  8. hello运行过程中,可能要遇到各种异常,收到各种信号,hello可能需要陷入到内核,调用异常处理程序。
  9. hello调用printf,使用UNIX IO来进行输出
  10. hello运行结束,被父进程回收

收获:
抽象是计算机科学中的重要概念。计算机系统也体现了这一理念,例如文件是对IO设备的抽象,虚拟内存是对程序存储器的抽象等。

我们还需注意计算机系统在整体上的统一与系统。一个功能可能涉及和影响到许多其他功能。系统是硬件和软件相互交织的集合体,它们必须共同协作才能达到程序的最终目的。

学习计算机系统,需要不断地实践,不断地实验,从实实在在的真实系统中,收获经验以及各种设计思想。

附件

文件作用
hello.chello程序的源代码
hello.ihello程序经过预处理后产生的代码
hello.shello程序编译产生的汇编代码
hello.ohello程序汇编产生的可重定位目标文件
Hello.ld经过链接后的可执行文件
dump_asmhello.o的反汇编文件
dump_linkhello.ld的反汇编文件
MakeFile包含了实验过程中各个中间指令

参考文献

为完成本次大作业你翻阅的书籍与网站等
[1] https://www.cnblogs.com/La-pu-ta/p/14932887.html
[2] https://zhuanlan.zhihu.com/p/101335237
[3] https://www.cnblogs.com/pianist/p/3315801.html

[4] Linux C编程一站式学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值