计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机类
学 号 1190201425
班 级 1903004
学 生 胡文晴
指 导 教 师 史先俊
计算机科学与技术学院
2021年5月
本文介绍了hello程序从最初的编写到最后被回收的一生
关键词:计算机系统;CSAPP
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
6.2 简述壳Shell-bash的作用与处理流程 - 31 -
7.2 Intel逻辑地址到线性地址的变换-段式管理 - 36 -
7.3 Hello的线性地址到物理地址的变换-页式管理 - 36 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 37 -
7.7 hello进程execve时的内存映射 - 39 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:
- 程序员通过编辑器创建并保存hello.c源文件
- 预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,得到hello.i
- 编译器(ccl)将文本文件hello.i翻译成汇编程序hello.s
- 汇编器(as)将hello.s翻译成机器语言指令,打包成可重定位目标程序,保存在hello.o中
- 链接,将库中的函数合并得到可执行目标文件hello
- 调用shell执行hello
- Fork创建子进程
- execve加载并运行程序
020:
最初程序不是在内存中的,是在磁盘中,所以说最初是0,在shell中调用execve加载并运行程序时,操作系统会为进程分配虚拟空间,映射到物理内存中,执行hello程序。进程终止后,回收hello进程,删除相关数据结构,回到0。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 Intel® Core™ i7-8650U CPU;8.0GB RAM;512GB SSD
软件环境:Windows10;Vmware 14;Ubuntu 20.04 LTS 64位;
开发工具:Edb, gdb, ccp, as, ld, readelf, gcc。
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
1.4 本章小结
本章介绍了hello的P2P和020的内容,简要列出了实验的软硬件环境,实验使用的开发工具,列出了编写论文生成的中间结果文件的名字和作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,比如hello.c 中#include命令告诉预处理器读取系统头文件stdio.h的内容,并把它 直接插入到程序文本中。结果得到另一个C程序,通常是以.i作为文件的扩展 名。
作用:
方便编译器的编译工作。
C语言预处理主要有以下三个方面的内容:
1.宏定义(#define)
格式如:#define标识符 字符串,在预处理过程中,用字符串替代标识符。
2.文件包含(#include);
读取include后文件的内容,并把它 直接插入到程序文本中。
3.条件编译(#ifdef/#ifndef/#if/#else/#elseif/#endif)
有时我们希望程序中的一部分代码在满足一定条件时才进行编译,否则不 参加编译。条件编译是在编译之前进行,比函数效率更高,可以使目标程序变 小,缩小运行时间。
2.2在Ubuntu下预处理的命令
命令:cpp hello.c > hello.i
图2-1 预处理过程
2.3 Hello的预处理结果解析
(以下格式自行编排,编辑时删除)
hello.i文件相对于hello.c文件来讲,增加了很多代码,文件大小增大很多。
图2-2 对.h文件的预处理结果
上图是对hello.c文件中.h文件的预处理结果,预处理器将使用绝对路径进 行替换。预处理过程会将hello.c包含的三个头文件stdio.h、unistd.h、stdlib.h
展开插入到程序文本中。
图2-3 hello.i中对数据类型的声明
hello.i中还有对一些数据类型的声明。
图2-4 hello.i中与hello.c文件相同的地方
在hello.i的文件末尾,存在和hello.c代码相同的代码,但删除了注释的内容和包含头文件的代码。
图2-5 发现的其他.h文件
在hello.i中,除了hello.c文件开头包含的头文件还发现了很多源文件没有的头文件,分析原因为在stdio.h、unistd.h、stdlib.h文件中可能又包含进了其他.h文件,在预处理时也要插入进来,形成一定的循环嵌套,导致文件代码量大大增加。
2.4 本章小结
(以下格式自行编排,编辑时删除)
hello.c文件首先经过预处理变成hello.i文件,预处理过程中删除了.c文件中的注释部分,将#include包含的头文件内容插入进来,对宏定义的符号进行替换。是hello.c形成可执行文件的第一步。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
(以下格式自行编排,编辑时删除)
概念:编译器(ccl)将文本文件hello.i翻译成文本文件hello.s。它包含一个汇编语言程序。汇编语言是介于高级语言和机器语言之间的中间代码,为不同的编译器提供了通用的输出语言。
作用:
- 扫描:扫描源代码程序,将源代码的字符系列分割成一系列单词符号。
- 语法分析:基于词法分析得到的单词符号,生成语法树
- 语义分析:由语义分析器完成,指示判断是否合法,并不判断对错。
- 中间代码:中间代码是源程序内部的一种表示,作用是可以使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现。
- 代码优化:对程序进行多种等价变换,即不改变程序运行结果的变换,使得从变换后的程序出发,能够生成更有效的目标代码,即运行时间更短,或占用的存储空间更小的代码。
- 目标代码:目标代码生成器把语法分析后或优化后的中间代码变换成为目标代码。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
(以下格式自行编排,编辑时删除)
gcc -S hello.i -o hello.s
图3-1 编译的命令
应截图,展示编译过程!
3.3 Hello的编译结果解析
(以下格式自行编排,编辑时删除)
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1数据
局部变量:局部变量存储在栈中
int i存储在-4(%rbp)中,如图赋初值0。
int argc存储在%edi寄存器中,然后存放在栈中-20(%rbp)位置。
char** argv存储在%rsi寄存器中,然后存放在栈中-32(%rbp)位置。
字符串:printf输出两个格式串,存储在.rodata节。
图3-2 printf输出的格式串
3.3.2赋值
图3-3 对int i的赋值
3.3.3算术操作
i++算术操作,每次循环将i增加1.
图3-4 i++算术操作
3.3.4 关系操作
判断argc != 4,将栈中-20(%rbp)与3进行比较。
图3-5 关系操作1
判断i<8,将栈中-4(%rbp)与7进行比较。
图3-6 关系操作2
3.3.5数组/指针/结构操作
printf调用了argv[1]和argv[2],通过-32(%rbp)得到argv[0]的指针的地址,+8得到argv[1]的地址,+16得到argv[2]的地址。
图3-7 数组/指针操作
3.3.6控制转移
第一处为if语句,判断argc是否等于4。
图3-8 if语句部分汇编代码
第二处为for循环,判断i是否小于8.
图3-9 for语句部分汇编代码
3.3.7函数操作
调用函数的第一个参数保存在%rdi中,第二个参数保存在%rsi中,第三个参数保存在%rdx中。返回值保存在%rax中。函数调用情况如下图:
图3-10 调用printf函数
图3-11 调用atoi函数
图3-12 调用sleep函数
图3-13 调用getchar函数
图3-14 函数返回
3.4 本章小结
本章讲述了编译的概念,分析了编译的作用,以及编译器是怎么处理C语言的各个数据类型以及各类操作的。在本节描述的过程中,编译器把hello从高级语言转变为汇编语言,形成文件hello.s,使其更接近底层、机器。
(以下格式自行编排,编辑时删除)
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
(以下格式自行编排,编辑时删除)
概念:汇编器(as)将hello.s编译成机器语言指令,把这些指令打包成一种叫可重定位目标程序的格式,并将结果保存在文件hello.o中,hello.o是一个二进制文件,在文本编辑器中打开看到的将是一堆乱码。
作用:将文本格式的汇编语言翻译成二进制机器语言,使它成为机器可以直接识别的程序。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
(以下格式自行编排,编辑时删除)
应截图,展示汇编过程!
图4-1 汇编的命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
使用命令readelf -a hello.o > hello.elf将hello.o的ELF输出到文件中。
图4-2 输出ELF文件命令
4.3.1 ELF头
图4-3 ELF头信息
ELF头以一个16字节序列开始,该序列描述了生成该文件系统字的大小和字节顺序。ELF头剩下的部分包含帮助链接器进行语法分析和解释目标文件的信息,其中包括ELF头的大小,目标文件的类型(如可重定位、可执行或可共享),机器类型(x86-64),节头部表的文件偏移,以及节头部表中条目的大小与数量。
4.3.2节头部表
图4-4 节头部表
节头部表描述了不同节的位置和大小。
4.3.3重定位节
.rel.text是一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。.rela.eh_frame节包含了对en_frame节的重定位信息。
图4-5中,offset是需要被修改的引用的节偏移。type告知链接器如何修改新的引用,使用了哪种重定位类型。Sym. Name标识被修改引用应该指向的符号。addend是一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。
图4-5中,需要重定位的有两个printf中的格式串,函数puts、exit、printf、atoi、sleep、getchar。
图4-5 重定位节
4.3.4符号表
符号表存放在程序中定义和引用的函数和全局变量的信息。
图4-6 符号表
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
机器语言的构成:机器语言由二进制的机器指令序列构成,机器指令由操作码和操作数构成。如图4-7,图中每一行是一条机器指令,同时机器指令的前部分是操作码,后部分是操作数。
图4-7 hello.o的部分反汇编
机器语言与汇编语言的映射关系:每一条汇编指令都被翻译为二进制的机器指令,存在一对一的映射关系。如图4-7,前一半的十六进制数是由每行对应的后一半的汇编语言翻译得到的。
机器语言中的操作数与汇编语言不一致:
- 汇编语言用十进制表示数,机器语言用十六进制
图4-8 数的进制不同
- 分支转移
在汇编语言中,分支跳转使用的是.L2等助记符。
在机器语言中,分支跳转使用的是相对偏移量。
图4-8 汇编语言分支转移
图4-9 机器语言分支转移
- 函数调用
在汇编语言中,使用call指令后加函数名表示对函数的调用。
在机器语言中,使用call指令后加下一条指令的地址来表示对函数的调用,在hello.o中,call指令后的操作数均为0(如e8 00 00 00 00),这里用0代替,等到链接生成可执行目标文件时再进行替换。
图4-10 汇编语言的函数调用
图4-11 机器语言的函数调用
4.5 本章小结
本章讲述了汇编的概念,分析了汇编的作用。分析了hello.o文件的elf格式,用readelf命令列出了各个节的相关信息,然后分析了汇编语言与机器语言的不同之处,主要为分支转移的不同和函数跳转的不同。
(以下格式自行编排,编辑时删除)
(第4章1分)
第5章 链接
5.1 链接的概念与作用
(以下格式自行编排,编辑时删除)
概念:链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(run time),也就是由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker)的程序自动执行的。
作用:链接器使得分离编译(separate compilation)成为可能。有了链接器我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
(以下格式自行编排,编辑时删除)
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
命令:
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
图5-1 链接的命令
图5-2 生成的文件
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用命令readelf -a hello > helloo.elf
图5-3 生成elf文件
5.3.1ELF头
图5-4 ELF头信息
ELF头以一个16字节序列开始,该序列描述了生成该文件系统字的大小和字节顺序。ELF头剩下的部分包含帮助链接器进行语法分析和解释目标文件的信息,其中包括ELF头的大小,目标文件的类型(如可重定位、可执行或可共享),机器类型(x86-64),节头部表的文件偏移,以及节头部表中条目的大小与数量。
5.3.2节头部表
节头部表描述了不同节的位置和大小。节头部表的第一列按地址顺序列出了各段的名称及大小,第三列列出了各段的起始地址,最后一列列出来各段的偏移量。
图5-5 节头部表
图5-6 节头部表(续)
图5-7 程序头
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
使用edb打开hello,通过edb的Data Dump窗口查看加载到虚拟虚拟地址空间的hello程序。Data Dump是从地址0x400000开始的,此处有ELF标识,所以可执行目标文件加载时的信息是从0x400000地址处开始的。
查看edb中的symbols窗口,与节头表一一对应。
图5-8 edb symbols窗口
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
图5-9 objdump -d -r hello命令
- 在hello.o中,看不到函数的代码段,而在hello中,存在了各个函数的代码段,同时每条指令都有了对应的虚拟地址。
- 在hello.o中,main函数的起始地址简单标记为0,以后的每一条指令按顺序标记偏移量,而在hello中,每条指令都拥有一个虚拟地址,不是简单的从0开始。
- 对于函数调用,hello.o中还没有对函数重定位,在调用时用call指令后加下一条指令的偏移量来表示,hello已经重定位过,各函数已拥有了各自的虚拟地址,所以在调用时call后加其虚拟地址来表示。
- 对于分支跳转,hello.o中使用在函数的相对偏移量表示,在hello中用已经拥有的虚拟地址表示。
图5-10 hello.o
图5-11 hello
链接过程:符号解析和重定位。
- 符号解析:目标文件定义和引用符号,符号解析将每个符号引用和一个符号定义关联起来。
- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
分析重定位方法:
链接器将所有相同类型的节合并为一个聚合节,然后链接器将运行时内存地址赋值给新的节和输入模块定义的每个符号,然后链接器利用可重定位条目,修改代码节和数据节中对符号的引用。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
Hello!_start
libc-2.27.so!__libc_start_main
Hello!main
Hello!printf@plt
hello!atoi@plt
Hello!sleep@plt
hello!getchar@plt
libc-2.27.so!exit
5.7 Hello的动态链接分析
(以下格式自行编排,编辑时删除)
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
通过readelf命令发现.got.plt节在地址为0x601000的地方开始。而它后面的.data节从地址0x601040开始。那么中间部分便是.got.plt的内容。
5.8 本章小结
本章介绍了链接的概念,作用,hello和hello.o的不同,链接的过程。经过链接,hello.o与其他部分结合起来,生成可执行目标文件hello,此时的hello可以开始在shell中运行。
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是正在运行的程序的实例,是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
作用:进程使得我们在运行程序时,会得到一个假象,我们的程序好像独占处理器和内存。这些假象是通过进程的概念提供的。进程提供给应用程序以下两方面关键抽象:(1)一个独立的逻辑控制流,好像一个程序独占处理器的假象;(2)一个私有的地址空间,好像一个程序独占地使用内存系统的假象。
6.2 简述壳Shell-bash的作用与处理流程
(以下格式自行编排,编辑时删除)
作用:Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。shell提供了一个界面,用户可以通过其提供的界面访问操作系统内核。其基本功能是解释并运行用户的指令。
处理流程:
(1)终端进程读取用户由键盘输入的命令行。
(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量
(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令
(3)如果不是内部命令,调用fork( )创建新进程/子进程
(4)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。
(5)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait...)等待作业终止后返回。
(6)如果用户要求后台运行(如果命令末尾有&号),则shell返回;
6.3 Hello的fork进程创建过程
输入./hello命令之后,shell会分析该命令,发现这不是一条内置命令,所以shell会找到执行当前目录下的hello文件去执行。然后shell调用fork();函数创建新进程,新进程作为子进程会得到一份父进程的虚拟地址空间的副本(但是独立的)拥有和父进程相同的代码、数据段、堆、共享库以及用户栈。于是hello就拥有了独立的地址空间。
6.4 Hello的execve过程
(以下格式自行编排,编辑时删除)
execve 函数在当前进程中加载并运行包含在hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
- 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
- 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。
- 映射共享区域。把共享对象动态链接到这个程序,然后再映射到用户虚拟地址空间中的共享区域内。
- 设置程序计数器(PC)。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
- 进程的上下文信息:内核为每个进程维持一个上下文,它是内核重启被抢占的进程所需的状态,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构的值。
- 进程调度的过程:进程执行到某些时刻,内核可决定抢占该进程,并重新开启一个先前被抢占了的进程,这种决策称为调度。内核调度一个新的进程运行后,通过上下文切换机制来转移控制到新的进程:1)保存当前进程上下文;2)恢复某个先前被抢占的进程被保存的上下文3)将控制转移给这个新恢复的进程。当内核代表用户执行系统调用时,可能会发生上下文切换,这时就存在着用户态与核心态的转换。
- hello程序执行时,内核会为其保存一个上下文,直到hello调用函数sleep陷入内核,执行第一次上下文切换,内核处理完系统函数调用后,将执行第二次上下文切换,将控制返回给hello的下一条语句。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
- Hello执行过程中可能出现的异常:
运行过程中可能出现的异常种类由四种:中断、陷阱、故障、终止。
中断:来自I/O设备的信号,异步发生。总是返回到下一条指令。
陷阱:是执行一条指令的结果,是有意的异常。同步发生。调用后返回到下一条指令。
故障:由错误情况引起,可能能够被故障处理程序修正。修正成功则返回到引起故障的指令,否则终止程序。
终止:不可恢复,通常是硬件错误。这个程序会被终止。
- 可能产生的信号:可能产生SIGSTP,它会将程序挂起,直到有下一个SIGCONT信号;可能产生信号SIGINT,它会将进程终止。
- 正常运行界面
- 不断乱按:不会影响程序的正常运行,当前程序执行时不会受到外部输入的影响,他会阻塞这些操作产生的信号。
- 按ctrl c:触发中断异常,产生信号SIGINT,父进程会终止子进程hello并回收它。
- 按ctrl z:hello程序会被挂起。
输入命令ps,打印进程的PID,可以看到hello进程被挂起。
输入命令jobs,看到进程hello已停止。
输入命令pstree,可以看到进程hello的位置
此时输入fg重新开始hello,然后通过命令ps得知hello的进程号87137,然后通过命令kill -9 87137发送信号SIGKILL,杀死该进程。
6.7本章小结
(以下格式自行编排,编辑时删除)
本章介绍了进程的概念和作用,分析了fork、execve函数的执行过程,分析了hello进程执行的过程,分析了hello进程执行过程中遇到的异常和信号处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
(以下格式自行编排,编辑时删除)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:是指由程序产生的与段相关的偏移地址部分。
线性地址:是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。
物理地址:出现CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
虚拟地址:虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。
7.2 Intel逻辑地址到线性地址的变换-段式管理
(以下格式自行编排,编辑时删除)
逻辑地址由段选择符和偏移量组成,线性地址由段首地址与逻辑地址中的偏移量组成。段首地址存放在段描述符中,段描述符存放在描述符表中,也就是GDT(全局描述符表)或LDT(局部描述符表)中。
段选择符由索引、TI、RPL组成,其中索引指示段描述符在段描述符表中的位置,TI指示段描述符是在GDT还是LDT中。通过段选择符的指示在段描述符表中找到对应的段描述符,然后从段描述符中获得段首地址,将其与逻辑地址中的偏移量相加,就得到了线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
(以下格式自行编排,编辑时删除)
线性地址(虚拟地址)由VPN和VPO组成。MMU利用VPN选择一条PTE,将PTE对应的PPN和虚拟地址的VPO连接,得到对应的PA
7.4 TLB与四级页表支持下的VA到PA的变换
(以下格式自行编排,编辑时删除)
TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。TLB通常有高度的相联度。用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中提取出来的。虚拟地址中用以访问TLB的组成部分如图所示:
下图展示了当TLB命中时所包括的步骤。TLB的存在使得所有的地址翻译步骤都是在芯片上的 MMU中执行的,因此非常快。
- CPU产生一个虚拟地址。
- MMU从TLB中取出相应的PTE。
- MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。
- 高速缓存/主存将所请求的数据字返回给CPU。
当TLB不命中时,MMU必须从Ll缓存中取出相应的PTE,新取出的PTE存放在TLB中,可能会覆盖一个已经存在的条目。
四级页表支持下的地址翻译:下图描述了使用k级页表层次结构的地址翻译。虚拟地址被划分成为k个VPN和1个VPO。每个VPN i都是一个到第i级页表的索引,其中 1≤i≤k。第j级页表中的每个PTE,1≤j≤k-1,都指向第j+1级的某个页表的基址。第k级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问k个PTE。对于只有一级的页表结构,PPO和VPO是相同的。
7.5 三级Cache支持下的物理内存访问
(以下格式自行编排,编辑时删除)
得到物理地址之后,进行物理内存的访问。首先计算得到物理地址中的组索引位,找到相匹配的组,然后从物理地址的标志位中获得标志信息和cache中的内容进行匹配,如果匹配成功,同时检查得有效位为1,则命中,即可按照块偏移取出数据。如果不命中,就要向下一级的cache寻找数据,如果三级cache中都没有数据,则要向内存中寻找。找到之后首选cache中的空闲块更新,若没有空闲块,则选择一定的替换策略选择出一个牺牲快进行驱逐,把数据放到这个位置。
7.6 hello进程fork时的内存映射
(以下格式自行编排,编辑时删除)
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给他一个唯一的pid。为了给这个新进程创建虚拟内存,他创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有写时复制。
当fork从新进程返回,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve 函数在当前进程中加载并运行包含在hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
- 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
- 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。
- 映射共享区域。把共享对象动态链接到这个程序,然后再映射到用户虚拟地址空间中的共享区域内。
- 设置程序计数器(PC)。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
(以下格式自行编排,编辑时删除)
7.8 缺页故障与缺页中断处理
CPU引用了一个虚拟地址,传给MMU,MMU在查找页表时,发现对应的物理地址不在内存中,此时会触发一个缺页异常,缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,如果这个页面已被修改,那么内核会把它复制到磁盘,换入新的页面并更新页表,随后返回,重新启动引起缺页的命令,此时已经可以正常运行了。
(以下格式自行编排,编辑时删除)
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
基本方法:动态内存分配器维护着堆:一个进程的虚拟内存区域。分配器将堆看做一组不同大小的块的集合,其中每个块就是一个连续的已经分配的、空闲的虚拟内存片。已分配的块要保留不能用来分配,在被释放之前一直要保留已经被分配的状态,释放可以是应用程序显示的执行的,也可以是内存分配器自身隐式执行的。空闲块可用来分配,在被显示地分配前一直保持空闲的状态。
分配器有两种基本风格:显式分配器和隐式分配器。两种风格都要求应用显式地分配块。它们的不同之处在于由哪个实体来负责释放已分配的块。
显式分配器(explicit allocator),要求应用显式地释放任何已分配的块。例如,C标准库提供一种叫做malloc程序包的显式分配器。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。C++中的new和 delete操作符与C中的 malloc和free 相当。
隐式分配器(implicit allocator),另一方面,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器(garbage collec-tor),而自动释放未使用的已分配的块的过程叫做垃圾收集(garbage collection)。例如,诸如Lisp、ML以及Java 之类的高级语言就依赖垃圾收集来释放已分配的块。
基本策略:
- 对与堆中的块的组织,可以选择隐式、显示、分离空闲链表等。、
- 查找空闲块时,可以采用不同的放置策略,如首次适配(从头开始搜索链表)、下一次适配(从上一次找到的空闲块的剩余块除法)以及最佳适配。
- 分割空闲块时,又可以采用将剩余块分割为新的空闲块的策略。
- 合并时,可以采用带边界标记的合并。通过边界标记来判断当前块周围是否也同样是空闲块,以此来判断是否需要合并。
7.10本章小结
(以下格式自行编排,编辑时删除)
本章介绍了段式管理、页表管理的相关内容,介绍了TLB、四级页表支持下的地址翻译,三级cache支持下的物理内存访问,介绍了缺页故障与缺页中断处理和动态存储分配管理等内容。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
I/O设备被模型化为文件,输入和输出被当作对相应文件的读和写来执行。将设备映射为文件,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O(即unix io接口),这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
将设备映射为文件,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,它使得所有的输入和输出都能以一种统一且一致的方式来执行:
- 打开文件:应用程序通过要求内核打开相应的文件,表示程序要访问一个I/O设备。内核返回描述符,用于标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
- Linux shell创建的进程开始时有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。unistd.h定义了常量STDIN_FILENO、STOOUT_FILENO和STDERR_FILENO,用来代替显式的描述符值。
- 改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k, 初始为0。应用程序能够通过执行seek操作,显式地设置文件的当前位置为k。
- 读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k>=m时执行读操作会触发一个称为EOF的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
- 关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
Unix IO接口的函数:
- 打开文件:int open(char *filename, int flags, mode_t mode);
调用open函数,通知内核你准备好访问该文件,打开一个已存在的文件或创建一个新文件。flags参数指明进程如何访问这个文件,mode参数指定新文件的访问权限位。若成功则为文件描述符,失败则返回-1
- 关闭文件:int close(int fd);
调用close函数,通知内核你要结束访问一个文件,关闭打开的一个文件。成功返回0,出错返回-1。
- 读文件:ssize_t read(int fd, void *buf, size_t n);
调用read函数,从当前文件位置复制字节到内存位置,然后更新文件位置。从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。成功则返回读的字节数,出错则返回-1,EOF返回0
- 写文件:ssize_t write(int fd, const void *buf, size_t n);
调用write函数,从内存复制字节到当前文件位置,然后更新文件位置。从内存位置buf复制至多n个字节到描述符fd的当前文件位置。返回值-1表示出错,否则,返回值表示的是从内存向文件fd实际传送的字节数量。
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
首先,先观察一下printf函数的实现代码:
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
当程序调用getchar()时,它会等待用户按键来输入字符。用户输入的字符被存放在缓冲区中,直到用户按了回车键,这时getchar()才会从stdio流中读入一个字符。
getchar函数的返回值是用户输入的字符的ASCII码,若出错返回-1,且将用户输入的字符回显到屏幕.若用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取.也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,介绍了如何实现printf和getchar函数。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
- 编写hello.c程序
- Cpp预处理得到hello.i
- Ccl编译得到汇编文件hello.s
- As汇编得到可重定位目标文件hello.o
- Ld链接得到可执行文件hello
- 在shell输入命令 ./hello 1190201425 胡文晴 1 运行程序
- shell通过fork创建子进程
- 调用execve函数加载运行hello程序
- 在cpu的帮助下,hello一步步执行指令
- hello程序结束,被父进程回收。内核把它从系统中清除。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
感悟:计算机系统的设计十分复杂同时非常精密。每一步都有非常精巧的设计细节,非常独特有效的设计思想。而且各个环节之间配合十分紧密。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
依次为:
和执行程序
源文件
hello.o的elf
经预处理得到的hello.i
经汇编得到的hello.o
经编译得到的hello.s
Hello的elf
(附件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分)