计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 120L022422
班 级 2003008
学 生 曹瑞麒
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
本文围绕一个简单的c语言程序hello,通过其被编译、运行的全过程,阐释了计算机系统的相关知识。包括可执行程序生成过程中的预处理、编译、汇编、链接,可执行程序运行过程中的:bash、进程调度、Linux系统调用、Unix IO接口等,让我们对计算机系统有更深的了解。
关键词:Hello程序;预处理;编译;汇编;链接;进程;存储;虚拟内存;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简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
1.P2P:Program to Progress, 用C语言编写的高级语言程序,首先由预处理器进行预处理,进行宏替换和宏展开,得到hello.i文件;然后编译器进行编译,得到hello.s汇编文件;接着汇编器将其汇编为hello.o可重定位目标文件;最后链接器将其与库函数链接得到可执行文件hello。在bash下输入./hello,命令行将运行当前目录下指定的程序。bash调用操作系统的fork函数创建新进程,在子进程中调用execve函数加载并执行hello,期间主进程(即bash进程)在后台挂起。hello便成为了一个进程。
2.O2O:From Zreo-O to Zero-O。shell执行可执行目标文件,管理hello进程,对其进行存储管理,分配映射虚拟内存、分配物理内存,输出结果到显示器,最后结束hello进程,回收其内存空间。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件:Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz ;16GB RAM;
软件:Windows 10 64位;VMware:Ubuntu 20.04.2 LTS 64位VMware;
开发工具:Vim 8.1.2269 64位;Visual studio 2019 64位;CodeBlocks 20.03 64位
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
1.hello.c: C语言源文件。
2.hello.i:预处理文本文件。
3.hello.s:汇编代码文件。
4.hello.o:可重定位目标文件。
5.hello:可执行目标文件。
6.hello.asm:由hello.o反汇编生成的汇编代码。
7.hello_o.asm:由hello反汇编生成的汇编代码文本。
8.hello.elf:hello.o的elf信息文本。
9.hello_o.elf:hello的elf信息文本。
1.4 本章小结
简介了hello 的 p2p,020 过程,阐述了本次实验的环境与开发工具,概述了整个过程的中间文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理器根据以字符#开头的命令,修改原始的C程序,如头文件、宏定义和条件编译。生成一个.i文本文件。
作用:预处理器加入头文件、执行宏替换和条件编译。再进行正式的编译工作,便于编译的高效进行。
2.2在Ubuntu下预处理的命令
Linux系统中使用如下指令进行预处理工作
gcc hello.c -E -o hello.i
2.3 Hello的预处理结果解析
经过预处理之后,hello.c转化为hello.i文件,打开该文件可以发现,文件的内容增加(多出了几千行的代码,大小也扩大了几百倍),且仍为可以阅读的C语言程序文本文件。对原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。例如声明函数、定义结构体、定义变量、定义宏等内容。另外,如果代码中有#define命令还会对相应的符号进行替换。
2.4 本章小结
本章介绍了预处理的概念和作用,将hello.c预处理为hello.i,并对预处理结果进行了分析。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译器将预处理文本文件hello.i翻译成汇编语言文本文件hello.s,该文件用汇编语言描述了程序的内容
作用:经过词法分析,语法分析,语义分析,和一定的优化来生成可以实现对应程序功能的汇编代码文件,与高级语言相比,汇编语言更接近计算机能够理解的语言,进一步有利于二进制机器语言的生成。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1 汇编文件指令
.file 源文件声明
.global 全局变量
.data 数据段
.align 对齐方式
.type 用来指定是函数类型或是对象类型
.size 大小声明
.long 长整型
.section .rodata 说明以下是rodata节
.string 字符串
.text 说明以下是代码段
3.3.2数据类型
立即数:
立即数在汇编代码中的呈现形式最为简单易辨认。即数顾名思义,是直接显式使用的一类数据,在汇编代码中通常以$美元符号作为标识。例如下图中的例子,表示的含义是比较(cmp compare)寄存器中的值和4,根据结果设置条件码。
寄存器变量:
在汇编代码中,指令后面出现过许许多多的形如-32(%rbp)形式的代码声明,其实这些就是寄存器存储的变量,通过特定的寻址方式进行引用。
例如下图中的例子,表示的就是将寄存器%rsi中存储的值,加载到以现在栈指针%rbp指向的位置基础上,减去32所对应的地址中去。
字符串:
LC0和.LC1作为两个字符串变量被声明。而在.LC0中出现的\347\224等是因为中文字符没有对应的ASCII码值无法直接显式显示,所以这样的字符方式显示。而且这两个字符串都在.rodata下面,所以是只读数据。随后有两句leaq指令,这个指令为加载有效地址,相当于转移操作。
3.3.3运算与操作
(1)赋值:通过mov指令完成赋值操作。
(2)算术运算:通过sub指令完成减法算术运算。
(3)关系判断:通过com指令完成大小关系运算判断。
(4)数组/指针/结构操作:argv[0]指向输入程序的路径和名称,addq $16,%rax得到argv[1]。
(5)控制转移:通过上面一个条件判断与7做比较,如果相等则跳转到.L2处,否则忽略跳转指令,继续接下来的指令。
3.3.4函数操作
(1)main函数
参数传递::第一个参数是argc(int型),第二个参数是argv[](char *型),分别存放在寄存器%rdi和%rsi中;
Call调用:main函数被系统函数__libc_start_main调用,call指令将main函数的地址分配给%rip,随后调用main函数。
函数操作中的栈维护:main函数使用栈指针,同时使用栈帧%rbp来记录使用情况。如图main函数在进入时先将rsp减去32形成一个栈空间结构,然后开始进行各种操作。
(2)循环函数:
可以看到在cmpl处将4与-20(%rbp)比对,因此可以得出argc存放在-20(%rbp)处。若argc不等于4,进入下面的leaq语句
(3)exit函数:
传递数据:将%edi 设置为 1。
控制传递:call exit@PLT。
(4)sleep函数:
传递数据:将%edi 设置为 sleepsecs。
控制传递:call sleep@PLT
(5)getchar函数:
控制传递:call gethcar
3.4 本章小结
本章通过对hello.i文件进行汇编得到其汇编文本文件hello.s。编译器将.i的拓展程序编译为.s的汇编代码。经过编译之后, hello自C语言解构为更加低级的汇编语言。通过深入汇编代码细致地分析了汇编代码如何实现变量、常量、传递参数以及分支和循环。编译程序所做的工作,就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码表示
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as)将.s汇编程序翻译成机器语言,把这些机器语言指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中,这个过程就叫做汇编.
作用:同上述概念,即翻译成机器语言并打包成可重定位目标程序格式
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
(1)ELF Header:用命令:readelf -h hello.o,如图4.3.1ELF Header
ELF Header:以 16B 的序列 Magic 开始,Magic 描述了生成该文件的系统 的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解 ## 标题释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、 字节头部表(section header table)的文件偏移,以及节头部表中条目的大 小和数量等信息。
根据头文件的信息,可以知道该文件是可重定位目标文件,有13个节。
(2) Section Headers:命令:readelf -S hello.o
Section Headers:节头部表,包含了文件中出现的各个节的语义,包括节 的类型、位置和大小等信息。 由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,同时可以观察到,代码是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。
(3)查看符号表.symtab :命令readelf -s hello.o
.symtab: 存放程序中定义和引用的函数和全局变量的信息。name是符号名称,对于可冲定位目标模块,value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type要么是数据要么是函数。Bind字段表明符号是本地的还是全局的。
(4)重定位节.rela.text
重定位节:一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
重定位节.rela.text中各项符号的信息:
Offset:需要被修改的引用节的偏移Info:包括symbol和type两个部分,symbol在前面四个字节,type在后面四个字节,
symbol:标识被修改引用应该指向的符号,
type:重定位的类型
Type:告知链接器应该如何修改新的应用
Attend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整Name:重定向到的目标的名称。
4.4 Hello.o的结果解析
(1)操作数:1.hello.s中的操作数时十进制,hello.o反汇编代码中的操作数是十六进制。
(2)分支转移函数调用:跳转语句之后,hello.s中是.L2和.LC1等段名称,而反汇编代码中跳转指令之后是相对偏移的地址,也即间接地址。
(3)操作指令:机器指令中有许多mov,add,去掉了汇编指令中的该类的表示大小的后缀。
4.5 本章小结
汇编器接受汇编代码,产生可重定位目标文件。它可以和其他可重定位目标文件合并而产生一个可以直接加载被运行的可执行目标文件。正因为它并不包含最终程序的完整信息,它的符号尚未被确定运行时位置,并用0占位。在第五部分中将说明如何将多个可重定位目标文件合并,并确定最终符号的最终运行位置。
汇编的过程相当于重定位,将汇编语言和机器语言以一一映射的关系对应起来翻译。使得程序进一步转化为可识别与可执行。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。
作用:链接可以使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以将它分解成更小、更好管理的模块,可以独立地修改和编译这些模块,当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
1. ELF Header:
命令:readelf -h hello
Type类型为EXEC表明hello是一个可执行目标文件,有12个节
2. Section Headers:
命令:readelf -S hello
3.symtab:
命令:readelf -s hello
5.4 hello的虚拟地址空间
1. 通过查看edb,看出hello的虚拟地址空间开始于0x400000,结束与0x400ff0
2. 由elf文件可看出.inerp偏移量为0x2e0,在edb对应位置找到
3. 同样可以在edb中找到.text段和.rodata 段等
5.5 链接的重定位过程分析
(1)hello中增加新的函数:在hello中链接加入了在hello.c中用到的函数,如exit、printf、sleep、getchar等函数。
(2)hello中增加了.init和.plt节,和一些节中定义的函数。
函数调用:
(3)hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。
(4)地址访问:
hello.o中的相对偏移地址变成了hello中的虚拟内存地址。而hello.o文件中对于某些地址的定位是不明确的,其地址也是在运行时确定的,因此访问也需要重定位,在汇编成机器语言时,将操作数全部置为0,并且添加重定位条目。
hello中,首先重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。然后进行重定位节中的符号引用。链接就是链接器(ld)将各个目标文件(各种.o文件)组装在一起,文件中的各个函数段按照一定规则累积在一起。
5.6 hello的执行流程
用edb的analyze功能可以发现动态链接库中依次出现了如下函数:
下面是hello模块内部依次出现的函数
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定(lazybinding),将过程地址的绑定推迟到第一次调用该过程时。
延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。
5.8 本章小结
本章通过链接成功生成了可执行的目标文件,并介绍了动态链接库等内容
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量、以及打开文件描述符的集合。
作用:进程给应用程序提供的关键抽象有两种:一个独立的逻辑控制流,提供一个假象,程序独占地使用处理器;一个私有的地址空间,提供一个假象,程序在独占地使用系统内存。
6.2 简述壳Shell-bash的作用与处理流程
shell是用户与系统交互作用界面。Shell是一个命令解释程序,也是一种程序设计语言。
1.读入命令行、注册相应的信号处理程序、初始化进程组。
2. 通过paraseline函数解释命令行,如果是内置命令则直接执行,否则阻塞信号后创建相应子进程,在子进程中解除阻塞,将子进程单独设置为一个进程组,在新的进程组中执行子进程。父进程中增加作业后解除阻塞。如果是前台作业则等待其变为非前台程序,如果是后台程序则打印作业信息。
6.3 Hello的fork进程创建过程
int fork(void)函数:
子进程返回0,父进程返回子进程的PID。新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本。子进程获得与父进程任何打开文件描述符相同的副本。子进程有不同于父进程的PID,fork()调用一次,返回两次。
在shell中如果fork返回值小于0,说明fork时出现了一些问题这时需要进行处理。否则就成功创建了子进程。
用户在终端输入对应的指令(./hello 120L022422 曹瑞麒 1),这时shell就会读取输入的命令,并开始进行以下操作:
第一步:判断hello不是一个内置的shell指令,所以调用应用程序,找到当前所在目录下的可执行文件hello,准备执行。
第二步:Shell会自动的调用fork()函数为父进程创建一个新的子进程,子进程就会因此得到与父进程(即shell)虚拟地址空间相同的一段各种的数据结构的副本(包括代码和数据段,堆,共享库和用户栈)。父进程与子进程最大的不同在于他们分别拥有不同的PID,父进程与子进程分别是两个并发的进程,在子进程中程序运行的这个过程中,父进程在原位置等待着程序的运行完毕。
6.4 Hello的execve过程
execve的功能是在当前进程的上下文中加载并运行一个新程序。在执行fork得到子进程后随即使用解析后的命令行参数调用execve,execve调用启动加载器来执行hello程序。加载器执行的操作是,加删除子进程现有的虚拟内存段,并创建新的代码、数据、堆和栈段。代码和数据段被初始化为hello的代码和数据。堆和栈被置空。然后加载器将PC指向hello程序的起始位置,即从下条指令开始执行hello程序。
6.5 Hello的进程执行
Linux 系统中的每个程序都运行在一个进程上下文中,有自己的虚拟地址空间。当shell 运行一个程序时,父shell 进程生成一个子进程,它是父进程的一个复制。子进程通过execve 系统调用启动加载器。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk), 新的代码和数据段袚初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用程序的main 函数。
逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式:shell使得用户可以有机会修改内核,所以需要设置一些防护措施来保护内核,如限制指令的类型和可以作用的范围。
上下文切换:上下文就是内核重新启动一个被抢占的进程所需要的状态,是一种比较高层次的异常控制流。
过程:开始Hello运行在用户模式,收到信号后进入内核模式,运行信号处理程序,之后再返回用户模式。运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
1. 乱按键盘:
乱按的内容将会显示出来,如果包含回车,将会在程序结束之后作为命令输入。
2. Ctrl+Z:
在键盘下按下Ctrl-Z之后,会导致内核发送一个SIGTSTP信号到前台进程组的每个进程,默认情况下结果是停止(挂起)前台作业。
3. Ctrl+C:
在键盘上按下Ctrl-C之后,会导致内核发送一个SIGINT信号到前台进程组中的每个进程,默认情况下结果是终止前台作业。
4.ps
5.jobs
6.pstrees
7.fg
8.kill
6.7本章小结
本章介绍了有关进程管理的多个概念。介绍了Shell的作用和处理流程,以及利用fork创建子进程、利用execve加载进程的方法。展示hello程序执行的具体过程,以及异常信号的处理机制。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序代码经过编译后出现在 汇编程序中地址.逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址.
线性地址:也叫虚拟地址,和逻辑地址类似,也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件也是内存的转换前地址。
虚拟地址:也即线性地址。
物理地址:CPU地址总线传来的地址,由硬件电路控制(现在这些硬件是可编程的了)其具体含义.物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等).在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写;而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到存储器管理单元MMU,把虚拟地址映射为物理地址.
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部份组成,段标识符、段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。索引号,是“段描述符(segment descriptor)”,段描述符具体地址描述了一个段。这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,由8个字节组成。Base字段:它描述了一个段的开始位置的线性地址。 Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。
看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。把Base + offset,就是要转换的线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
系统将每个段分割为被称为虚拟页(VP)的大小固定的块来作为进行数据传输的单元,在linux下每个虚拟页大小为4KB,类似地,物理内存也被分割为物理页(PP/页帧),虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
将VPN分成三段,对于TLBT和TLBI来说,可以在TLB中找到对应的PPN,但是有可能出现缺页的情况,这时候就需要到页表中去找。此时,VPN被分成了更多段(这里是4段)CR3是对应的L1PT的物理地址,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过4层寻址之后找到相应的PPN让你和和VPO拼接起来。
7.5 三级Cache支持下的物理内存访问
现代计算机高速缓存Intel core i7层次结构:每个CPU芯片有四个核,每个核有自己私有的L1 i-cache、L1 d-cache和L2统一的高速缓存。所有的核共享片上的L3统一的高速缓存。
通过内存地址的组索引获得值,如果对应的值是data则像L1 d-cache对应组中查找,如果是指令,则向L1 i-cache对应组中查找。将L1对应组中的每一行的标记位进行对比,如果相同并且有效位为1则命中,获得偏移量,取出相应字节,否则不命中,向下一级cache寻找,直到向内存中寻找。
7.6 hello进程fork时的内存映射
Fork函数在前面已经讲过了,这里简单重复。当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制,当fork函数在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当两个进程中的任一个后来进行写操作时,写时复制就会创建新页面。因次,也就为每个进程保持了私有地址空间的概念
7.7 hello进程execve时的内存映射
execve的功能是在当前进程的上下文中加载并运行一个新程序,execve函数在当前代码共享进程的上下文中加载并自动运行一个新的代码共享程序,它可能会自动覆盖当前进程的所有虚拟地址和空间,删除当前进程虚拟地址的所有用户虚拟和部分空间中的已存在的代码共享区域和结构,但没有自动创建一个新的代码共享进程。新的运行程序仍然在堆栈中拥有相同的区域pid。之后为新运行程序的用户共享代码、数据、bss和所有堆栈的区域结构创建新的共享区域和结构,新代码共享区域可能是在运行时私有的、写时复制的。它首先映射到一个共享的区域,hello这个程序与当前共享的对象libc.so链接,它可能是首先动态通过链接映射到这个代码共享程序上下文中的,然后再通过映射链接到用户虚拟地址和部分空间区域中的另一个共享代码区域内。为了设置一个新的程序计数器,execve函数要做的最后一件要做的事情就是自动设置当前代码共享进程上下文的一个程序计数器,使之成为指向所有代码共享区域的一个入口。
7.8 缺页故障与缺页中断处理
我在ppt中找到了相关的讲解,如果程序执行过程中遇到了缺页故障,则内核调用缺页处理程序。处理程序会进行如下步骤:检查虚拟地址是否合法,如果不合法则触发一个段错误,程序终止。然后检查进程是否有读、写或执行该区域页面的权限,如果不具有则触发保护异常,程序终止。在两步检查都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。
7.9动态存储分配管理
动态内存分配器维护者一个进程的虚拟内存区域,成为堆。分配器将堆视为一组不同的大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。分配器有两种基本风格。两种风格都是要求显示的释放分配块。
隐式空闲链表:分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块
显式空闲链表:每次声明内存空间都保证至少分配size_t大小的内存,双字对齐,每次必须从空闲块中分配空间,在申请空间时将空闲的空间碎片合并,以尽量减少浪费。
7.10本章小结
本章主要介绍了hello的存储器地址空间、intel的段式管理、hello 的页式管理, 以intel Core7在指定环境下介绍了VA 到PA 的变换、物理内存访问,还介绍进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux中的I/O设备被表示为文件,输入输出用文件的读和写来执行。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
打开文件:int open(char *filename, int flags, mode_t mode);
关闭文件:int close(int fd);
读文件: ssize_t read(int fd, void *buf, size_t n);
写文件: ssize_t write(int fd, const void *buf, size_t n);
更改文件位置:lseek(),将文件位置更改为目标位置
8.3 printf的实现分析
博客中给出了以上的实现,printf函数中调用了sprintf函数和write函数。sprintf函数将所有的参数内容格式化为字符串并存入buf,然后返回格式化数组长度;write函数将buf中的元素写到终端。
(以下为原文档中的提示)
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar 函数落实到底层调用了系统函数 read,通过系统调用read读取存储在键盘缓冲区中的 ASCII 码直到读到回车符然后返回整个字串,getchar 进行封装,大体逻辑是读取字符串的第一个字符然后返回。
8.5本章小结
本章主要介绍了 Linux 的 IO 设备管理方法、Unix IO 接口及其函数,分析了 printf 函数和 getchar 函数的实现。
(第8章1分)
结论
本次大作业是对本学习计算机系统学习内容的一次总结和回顾,运用到了课程各章节介绍的原理和方法,通过这门课我了解到计算机的底层实现原理非常重要,我们在编程中也应该更多考虑底层原理与问题,这样可以更深入地理解计算机系统。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c:C语言源程序,文本文件
hello.i:经过预处理的hello源程序,文本文件
hello.s:编译后产生的汇编文件,文本文件
hello.o:汇编产生的可重定位目标文件,二进制文件
hello:链接产生的可执行目标文件,二进制文件
hello.out:hello反汇编之后的可重定位文件
(附件0分,缺失 -1分)
参考文献
[1] 百度百科.维基百科
[2] CSDN shell命令执行过程
[3] CSDN Ubuntu系统预处理、编译、汇编、链接指令
[4] github 汇编层面看函数调用的实现原理
[5] 《深入理解计算机系统》 Randal E.Bryant David R.O’Hallaron 机械工业出版社