计算机系统大作业

摘 要
从hello入手,我们将一步步漫游整个计算机世界。究竟是如何从平凡的hello.c变成高贵的可执行文件hello的呢?从一个简简单单的c文件进行预处理、编译、汇编、链接,这中间发生的过程、每个操作的特点都会在本文被一一解析。同时我们也将讲解为了让小小的hello运行,在运行期间整个计算机所做出的巨大努力。
关键词:计算机系统、程序结构和执行、在系统上运行程序、程序间的交互和通信

(摘要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简介
P2P(from program to process):hello.c经过预处理得到hello.i、经过编译得到hello.s、经过汇编得到hello.o、经过链接之后得到可执行程序hello。
020:在输入启动程序命令后shell调用fork函数惊醒船舰子进程,子进程再调用execve函数并传入argc、argv[]、envp[]作为参数。映射虚拟内存,载入物理内存,进入主函数执行代码。控制上下文切换、分配时间片、执行逻辑控制流。在程序运行结束后,父进程接受子进程的信号,由父进程或者是init进行对子进程的回收,并且内核删除子进程的数据。
1.2 环境与工具
硬件环境:intel core i7-8750H CPU ,2.20GHz ,16G RAM
软件环境:Windows 10 64位 ,Vmware 14 ,Ubuntu 16.04
开发工具:gcc + gedit , Codeblocks , gdb edb
1.3 中间结果
hello.c .C文件
hello.i 预处理后得到的文件
hello.s 汇编语言文件
hello.o 可重定位目标文件
hello 可执行文件
hello.txt反汇编代码
1.4 本章小结
介绍了p2p与020,概述了程序在计算机中被执行的总体过程。

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理:预处理器(ccp)根据以字符#开头的命令,修改原始的C程序。
作用:对预处理命令进行处理,主要有三方面内容:1、宏定义 2、文件包含 3、条件编译 例如:hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h内容,并把它们直接插入程序文本中。
2.2在Ubuntu下预处理的命令
在这里插入图片描述
图2.2.1预处理的命令
在这里插入图片描述
图2.2.2 Hello.i文件

图2.2.3 hello.i代码
2.3 Hello的预处理结果解析
在这里插入图片描述
图2.3.1 hello.i代码
在这里插入图片描述
图2.3.2 Hello.c代码

Hello.i总共有3042行,但hello.c只有短短的23行。两者比较发现main函数没有变化,但原来的注释都没了,并且头文件没有了。Hello.i中新增了大量的代码来替换头文件,这就是头文件的源代码,预处理的作用之一就是把头文件转换成源代码,然后进行宏定义与拓展和条件编译。
2.4 本章小结
讲述了hello.i的概念及其作用,又用具体的例子帮助大家理解了整个预处理过程计算机完成的任务,与程序发生的变化。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译:编译器将文本文件hello.i翻译成文本文件hello.s ,它包含一个汇编语言程序。也就是将处理后得到的修改了的源文件到生成汇编语言程序。
作用:将c语言文件转化成汇编语言文件。在这个过程中,会进行一系列的语法检查,代码优化,目标代码生成等任务。

3.2 在Ubuntu下编译的命令

图3.2.1 Ubuntu下的编译命令

在这里插入图片描述
图3.2.2生成的hello.s文件

3.3 Hello的编译结果解析
3.3.1数据
局部变量:i :作为再函数中船舰的局部变量i被储存在栈中,在这个程序中i被储存在%rbp-4

           argc:传入的参数,存在栈中,位于%rbp-20
           argv[]:传入的数组,存在栈上,argv的地址存在%rbp-32,利用argv的地址加8、加16,就能得到argv[1]和argv[2]
立即数:一开始储存在代码段中,之后在运行的时候被取出存在寄存器中,如果没有空闲寄存器就放入栈中   
表达式:存在代码段的.rodata中

在这里插入图片描述
图3.3.1 表达式在汇编代码中的形式

3.3.2赋值
唯一的赋值是i=0 ,i在栈上,用mov赋值。
3.3.3 size of
Argc、i=4
Argv=8
3.3.4 算术操作
加法操作:i++,在本程序中用addl实现。
在这里插入图片描述

3.3.5 关系操作
关系操作在汇编语言中通过cmpl和标志位的状态来实现,如在cmpl a b时我们通过计算(b-a)当结果为0 ZF就等于1,这样我们就能判断两个数字是否相等了。如果有进位或者是溢出了,那CF 与OF分别为1,如果结果是负数,那么SF为1 通过这些标志位的状态我们就能知道比较的结果。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.3.6数组/指针/结构
Argv数组是传入的参数,储存在栈上,如图3.3.6.1所示。我们在找到argv的地址后就可以通过偏移argv+8和argv+16找到argv[1]和argv[2]的地址了。
在这里插入图片描述
图3.3.6.1 函数的堆栈,argc和argv在战中的位置

在这里插入图片描述

3.3.7 控制转移
在这里插入图片描述
在这里插入图片描述
在汇编语言中控制转移通过cmp再加上jXX来实现,cmp指令来改变标志位,然后再用JXX根据标志位来决定是否跳转。
在这里插入图片描述
在这里插入图片描述
3.3.8函数操作
1、main函数
在这里插入图片描述
main在代码段中,再声明为全局变量和函数类型。
在这里插入图片描述
Argc和argv[]都是传入main函数的参数

2、printf函数
在这里插入图片描述
在调用printf之前,先把要输出的字符串的首地址传给rdi
3、exit函数
在这里插入图片描述
实现退出操作
4、sleep函数
在这里插入图片描述
先把参数放入rdi,然后调用sleep函数。

3.4 本章小结
本章讲述了编译阶段系统是如何把c源代码转换成汇编代码的,同时我们也分析了我们所熟知的c源代码中的各个操作还有数据是如何在汇编代码中储存并实现的。更重要的是汇编语言也是我们每个程序员以后经常接触的代码语言形式,掌握汇编语言对我们来说至关重要。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编:汇编器将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序,并将结果保存在hello.o上
作用:将汇编语言代码转换成机器语言
4.2 在Ubuntu下汇编的命令
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020010100342915在这里插入图片描述
图4.2.1 生成的hello.o文件
4.3 可重定位目标elf格式

ELF头
.text
.rodata
.data
.bss
.symtab
.rel.text
.tel.data
.debug
.line
.strtab
节头表
图4.3.1 可重定位elf

在这里插入图片描述
图 4.3.2
1、ELF
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小,目标文件的类型,及其类型,节头部表的文件偏移,节头部表中条目的大小和数量。如图4.3.3,ELF包括类别,数据,类型,入口点地址,程序头起点地址,本头大小,节头部大小。
在这里插入图片描述
图4.3.3 ELF表头
2、节头部表
不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
3、重定位节
如图4.3.4所示,每一行一五个数据,分别是偏移量、信息、类型、符号值、符号名称+加数。
rela.text:储存可重定位信息,当进行链接操作的时候,因为各种地址操作的地址都已经改变了,所以许多数据都需要进行重定位。
在这里插入图片描述
图4.3.4 重定位节

偏移量是重定向部分在各自接种需要偏移的位置,信息储存了它在symbol中的偏移量以及类型,类型和符号值和符号名称就分别是重定位的目标的类型和数值和名称。加数是为了计算位置所储存的信息。
4.4 Hello.o的结果解析

图4.4.1 反汇编代码

1、在跳转时,hello.s是直接利用段的名字进行跳转,而在反汇编中则使用相对寻址来进行跳转
2、Hello.s用十进制,而反汇编用十六进制。
3、Hello.o用函数的名字来调用函数,但反汇编则是用相对寻址来调用函数,并且因为还没有进行重定位所以操作数全是0
4.5 本章小结
本章讲述了可重定位目标程序的ELF格式,以及ELF表中所包含的各项信息,以及他们的作用,之后我们又比较了机器语言与汇编语言的区别,这些区别同样体现了汇编过程的特点与作用
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接:是将各种代码和数据片段组合成为一个单一文件的过程。
作用:链接使分离编译成为可能,我们可以独立的修改或编译与文件中的一个个很小的块,在改变这些块之后只需要简单的重新编译他即可,这样做还大大提高了效率。
5.2 在Ubuntu下链接的命令

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小,目标文件的类型,及其类型,节头部表的文件偏移,节头部表中条目的大小和数量。如图,ELF包括类别,数据,类型,入口点地址,程序头起点地址,本头大小,节头部大小。
在这里插入图片描述

图5.3.1 ELF节头表

如图5.3.1每一行的信息分别从左到右是:第一行:名称、类型、地址、偏移量。
第二行:大小、全体大小、旗帜、链接、信息、对齐

在这里插入图片描述
在这里插入图片描述
图5.3.2

只读内存段:起始:0x400000 大小:0x2000
读写内存段:起始:0x404048 大小:0x27
不能加载到内存的符号表和调试信息:起始:0x000000 大小:0x6b5
5.4 hello的虚拟地址空间

在这里插入图片描述
图5.4.1 栈中的ELF表的位置

可以看到edb中0x400000所储存的信息与我们表头所储存的信息相吻合
在这里插入图片描述
Init段存在于0x401000
在这里插入图片描述
rodata段
在这里插入图片描述

数据段
5.5 链接的重定位过程分析
在完成符号解析之后,链接器将代码中的每个符号引用和符号定义关联,于是链接器就得到了代码节和数据节的大小。然后先进行重新定位,所有同类型的数据都会被重定位到一个节上,然后再对每个节赋予不同的地址,以及节中的每个符号,这个操作保证了程序中的每个节和每个符号都有相应的地址。之后对代码节和数据节中有地址操作的命令或者是数据进行修改,使他们能调用正确的地址。链接器完成符号解析后,将代码中每个符号引用和一个符号定义(即它的一个输入目标模块中的一个符号表条目)关联起来,此时,链接器就知道了它的输入目标模块中的代码节和数据节的确切大小。
每个节的位置确定: 用已知的每个节的大小加上每个节与头部之间的相对距离来确定具体的位置。
节中每个命令的位置确定:因为已经把接的位置固定了,那么每条命令的位置只需要用每个命令的相对偏移加上节的位置就能得到每条命令的位置。
在重定位之后的变化:原来的可重定位文件中的地址都是相对偏移地址,有一些有关函数调用的地址甚至直接用0来省略了。但hello中的地址都是真正的地址,从这些地址上我们可以取到相应数据。在重定位之后,函数以及各种跳转都是通过虚拟地址或者是偏移量来直接完成的。同时,重定位之后,程序中也新增加了一些函数,这是本程序引用的,且不由本程序自己创建的函数。

在这里插入图片描述
在这里插入图片描述
图5.5.1 反汇编后的汇编语言
5.6 hello的执行流程
hello!_start 0x401090
__libc_start_main 0x403ff0
main 0x4010c1
hello!puts@plt 0x401030
hello!exit@plt 0x401070
hello!printf@plt 0x401040
hello!atoi@plt 0x401060
hello!sleep@plt 0x401080
hello!getchar@plt 0x401050

5.7 Hello的动态链接分析
执行前:
在这里插入图片描述
执行后:
在这里插入图片描述
图5.7.1 执行前后global表的对比

执行之前global表是全0的,执行之后被赋予了一个新的地址,我们推断出因为其中一个外部函数属于动态链接符号,所以重定位的过程会从链接阶段延后到dl_init阶段。
5.8 本章小结
本章讲述了链接,分析了链接前后elf表的变化还有重定位之后的ELF表在栈中的储存。在讲述了冲的为的操作流程之后我们分析了重定位前后代码的变化,主要对重定位之后的地址做了解析。最后我们对动态链接进行了分析,通过edb来清晰的观测了动态链接的重定位过程。

(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
作用:通过进程,我们能得到假象:我们的程序是系统中当前运行的唯一的程序一样,我们的程序好像是在独占使用处理器和内存,处理器好像是一条无间断地一条接着一条的执行我们程序中的命令,我们程序中的代码好像是系统中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:一个交互型的应用级程序。可以接受用户输入的命令,然后再系统中运行相应的程序,是用户与系统之间的桥梁。
处理流程:读取用户输入的命令,将命令分开,判断是否为内置命令,如果是那就立刻执行,如果不是那就调用fork和execve函数来运行程序完成命令,同时在运行的时侯Shell依然可以读取用户的输入来对当前正在执行的命令进行其他操作,如果找不到能执行这条命令的应用,就显示错误。
6.3 Hello的fork进程创建过程
Shell解析命令,根据命令的输入来构造参数argv argc envp。调用fork函数创造hello进程,hello进程会把父进程(私有写时复制)的信息复制下来,虽然虚拟地址还是相同,但已经不会再相互影响了,是两个独立的副本。Fork函数调用一次返回两次,然后用hello进程调用execve函数。
对于hello函数,shell解析完命令之后argc=3, argv中存了函数名字的地址,学号地址,姓名地址
6.4 Hello的execve过程
Execve函数在被调用后系统会进行以下步骤
删除已存在的用户区域:删除当前进程虚拟地址的用户部分中的已存在的区域结构

映射私有区域:为hello的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的写时复制的。代码和数据被映射为a.out文件中的.text和.data区。Bss区域是请求二进制0的,映射到匿名文件,其大小包含在a.out中。站和对区域也是请求二进制0的,初始化长度为0.

映射共享区域:这些对象都是动态连接到hello,然后再映射到用户虚拟地址空间中的共享区域内

设置程序计数器(PC):execve做的最后一件是就是设置hello上下文中的程序计数器,使之指向代码区域的入口点。

下面就是一个新的用户栈的示例。

以NULL结尾的环境变量字符串
以null结尾的命令行字符串

Envp[n]=NULL
Envp[n-1]
……………
Evnp[0]
Argv[argc]=NULL
……………
Argv[0]

Lib_start_main的栈帧
Main的未来的栈帧
将 hello 中的节中的内容加载到当前进程的虚拟地址空间中。当加载结束后,系统开始运行 hello 。

6.5 Hello的进程执行
上下文:内核为每一个进程维持一个上下文,上下文就是内核重新启动是个被强占的进程所需的状态。
调度:再进程执行的默写时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这种策略叫调度。
时间片:连续执行同一个进程的时间
在内核调度了一个新的进程运行时,我们说内核调度了这个进程。在讷河调度一个新的进程运行后,他就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。
下图最好的阐释了进程执行的问题

图6.5.1进程上下文切换的剖析
进程A初始运行在用户模式种,直到它通过执行系统调用read陷入到内核。内核中的陷阱处理程序请求来自磁盘控制器的DMA传输,并且安排在磁盘控制器完成从磁盘到内存的数据传输后,磁盘中断处理器。
在切换的第一部分中,内核代表进程A在内核模式下执行指令。然后在某一时刻,他开始代表进程B执行命令。在切换之后,内核待代表进程B在用户模式下执行指令。这就是内核与用户模式之间的切换。

现在我们来分析hello的进程执行过程:刚开始的时候hello运行在用户模式中,当hello执行到sleep命令的时候,这个命令请求hello进程被挂起,于是内核就进行上下文切换,此时是在内核模式。在经过了预定的时间之后,再次进入内核模式进行上下文切换,将时间片分配给hello,返回用户模式,重新执行hello进程内核切换回用户模式继续运行hello
6.6 hello的异常与信号处理
乱按键盘:对输出结果不会有影响

图6.6.1 乱按键盘

回车:同样对输出结果不会有影响,但是输入的回车命令会在结束之后被解析,这也就是为什么我的截图下面会有很多空的命令。
图6.6.2 按回车

Ctrl-Z:下面是ctrl-z加上各种操作。父进程会接收到SIGSTP信号,将子进程挂起,放到后台。

Fg:将进程重新放到前台运行
Jobs:输出shell正在处理的进程
Kill:杀死一个进程
Pstree:输出进程树(树状图表示的进程之间的关系)
Ps:输出当前系统中的所有进程

图6.6.3 输入ps fg

图6.6.4 输入kill

图 6.6.5 输入jobs
图6.6.6 输入pstree 得到的进程树

图6.6.7

Ctrl-C:shell父进程收到SIGINT信号,信号处理函数结束hello并回收子进程。

6.7本章小结
本章讲述了进程,概述了shell的作用,以及用fork,execve开始一个进程,之后我们还用具体操作向大家展示了hello进程对于各种信号的反应与shell对于信号的处理。这让我们更加清晰的了解了进程信号之间的关系。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:在机器语言中用来确定一个指令或者是操作数的地址。逻辑地址包含段和偏移量,而偏移量是相对偏移,而段则确定了偏移开始的地方,这样就能通过段和偏移来确定地址。也就是hello.o中的相对偏移地址
线性地址:逻辑地址与物理地址之间的桥梁。用偏移加上段的地址就能得到线性地址,也就是虚拟内存地址。也就是hello中的虚拟内存地址
虚拟地址:虚拟地址和线性地址一样。也就是hello中的虚拟内存地址
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。通过地址翻译器,hello的虚拟地址可转换成物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理

图7.2.1 逻辑地址得出线性地址

当TI=0则选择GDT首地址,当TI=1则选择LDT首地址。然后我们选取段选择符的前13位乘8再加上根据TI所选的首地址,然后根据得到的结果我们就能再描述符表中找到段描述符。再将其中的32位基地址和32位段内偏移量相加,就能得到32位线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
每一个线性地址被分解为,10位的面目录索引、10位的页表、12位的索引偏移:
然后根据线性地址前十位,找到索引项,根据页索引项来确定页表的地址。根据线性地址的中间十位,在页表中找到页的起始地址。将页的起始地址与线性地址中最后12位相加,得到最终的物理地址。

总结:页式管理是把物理空间划分成许多块。相应地,把逻辑地址空间划分成许多页页号和页内地址构成线性地址,我们通过页号找到页的起始地址,再加上页内地址就可以得到物理地址。

线性地址被分成两部分,一部分是VPO一部分是VPN
VPN作为索引,用于在页表中查询物理地址的一部分。
VPO与页表中查到的物理地址组合成为完整的物理地址。
在用VPN做索引来查找物理地址时会发生两种情况。
页命中:1、处理器生成一个虚拟地址,并把它传送给MMU
2、MMU生成PTE地址,并从高速缓存或主存请求得到它
3、高速缓存或主存向MMU返回PTE
4、MMU构造物理地址,并把它出送给告诉缓存或主存
5、高速缓存或主存返回所请求的数据给处理器

页不命中:1、处理器生成一个虚拟地址,并把它传送给MMU
2、MMU生成PTE地址,并从高速缓存或主存请求得到它
3、高速缓存或主存向MMU返回PTE
4、PTE中的有效位为0,MMU解析了一次异常,传递CPU的控制到操作系统内核中的缺页异常处理程序。
5、缺页处理程序确定出物理内存中的牺牲也如果这个也面已被修改则把它换出到磁盘。
6、缺页处理程序调入新的页面,更新内存中的PTE
7、却也处理程序返回到原本的进程再次执行导致缺页的指令。CPU将引起缺页的指令地址重新发给MMU,这次命中,主存将所请求的字返回给处理器

7.4 TLB与四级页表支持下的VA到PA的变换
TLB是一个小的虚拟寻址的缓存,其中每一行都保存着一个有单个PTE组成的块。TLB通常有高度的相联度。用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号提取的。
如图

                   图7.4.1  TLB

TLB命中时的步骤:1、CPU产生一个虚拟地址
2、3、MMU从TLB中取出相应的PTE
4、MMU将这个虚拟地址翻译成一个物理地址,并且将它送到高速缓存或主存
5、高速缓存或主存将所请求的字返回给CPU
因为一系列操作都是在芯片上的MMU执行的,因此非常快。

多级页表:一级页表中的每个PTE负责映射虚拟地址空间中的一个4MB的片,这里每一个是由1024个连续的页面组成的。比如PTE0映射第一篇,PTE1映射接下来的一篇,以此类推。假设地址空间是4GB,1034个PTE已经足够覆盖整个空间了。如果片i中的每个页面都未被分配,那么以及PTE就为空,以此类推。

这种方法从俩个方面减少了内存需求,第一,如果第一个PTE为空那么二级页表就不会存在。第二,只有一级页表才需要总是在主存中,系统可以在需要是才创建、调入、调出二级页表。
多级页表的索引方式如下图

                图7.4.2  k级页表 

7.5 三级Cache支持下的物理内存访问
我们将物理地址分为标记位、组索引、块内地址,通过组索引去确定数据在cache的哪组,然后通过标记为来确定是否是我们想要的数据,如果命中那就通过块内地址来选取想要的数据,如果不命中那就向下级cache寻找相应的数据,在找到数据后如果cache当前行中有空闲那就将数据放入空闲行,如果没有就替换一个最晚用到的行。(LRU算法)
7.6 hello进程fork时的内存映射
调用fork函数时,内核为hello创建各种数据结构,并分配给他唯一的PID。为了给hello创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原本副本。而且hello的储存空间也是私有写时复制的。与父进程唯一不同的就是PID的值。
7.7 hello进程execve时的内存映射
Execve函数在被调用后系统会进行以下步骤
删除已存在的用户区域:删除当前进程虚拟地址的用户部分中的已存在的区域结构

映射私有区域:为hello的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的写时复制的。代码和数据被映射为a.out文件中的.text和.data区。Bss区域是请求二进制0的,映射到匿名文件,其大小包含在a.out中。站和对区域也是请求二进制0的,初始化长度为0.

映射共享区域:这些对象都是动态连接到hello,然后再映射到用户虚拟地址空间中的共享区域内

设置程序计数器(PC):execve做的最后一件是就是设置hello上下文中的程序计数器,使之指向代码区域的入口点。

用户栈 私有的,请求二进制0的

共享库的内存映射区域 共享的,文件提供的

运行时堆(通过malloc分配的) 私有的,请求二进制0的
未初始化的数据(.bss) 私有的,请求二进制0的
已出始的数据(.data) 私有的,文件提取的
代码(.text) 私有的,文件提取的

7.8 缺页故障与缺页中断处理
页不命中:1、处理器生成一个虚拟地址,并把它传送给MMU
2、MMU生成PTE地址,并从高速缓存或主存请求得到它
3、高速缓存或主存向MMU返回PTE
4、PTE中的有效位为0,MMU解析了一次异常,传递CPU的控制到操作系统内核中的缺页异常处理程序。
5、缺页处理程序确定出物理内存中的牺牲也如果这个也面已被修改则把它换出到磁盘。
6、缺页处理程序调入新的页面,更新内存中的PTE
7、却也处理程序返回到原本的进程再次执行导致缺页的指令。CPU将引起缺页的指令地址重新发给MMU,这次命中,主存将所请求的字返回给处理器

缺页故障操作:发现缺页异常之后,把CPU的控制传递给操作系统内核中的缺页异常处理程序,选择一个牺牲页,如果已被修改那么就把他患处到磁盘,然后缺页处理程序会调入新的页面,更新内存中的PTE,然后返回原本的进程再次执行导致缺页的指令。

7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将对是为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显示的保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到他显示的被应用所分配。一个已分配的快保持已分配状态,知道他被释放,这种释放要么是应用程序显示执行的,要么是北村分配器自身隐式执行的。
动态分配器有两种情况:1、显示分配器:要求应用显示的释放任何已分配的块
2、隐式分配器:会自动检测不再使用的块将他自动回收

隐式空闲链表:
一个块是由字的头部、有效荷载、以及可能的一些额外的填充数成的。头部编码了这个块的大小,以及这个块是已分配的还是空闲的。如果我们强加一个双字的对其约束条件,那么块的大小就是8的倍数,且块大小的最低3为总是0。因此我们需要内存的大小的29个高位,释放其余的3位来编码其他信息。在这种情况下,我们用其中的最低位来表示这个块是已分配的还是空闲的。
头部后面就是应用调用malloc是请求的有效荷载。有效荷载后面是一片不适用的填充块,其他小可以是任意的。空闲块是通过头部中的大小字段隐含的连接着的,分配器可以通过遍历对中所有的块从而间接地遍历整个空闲块的集合。

放置策略:
1、首次适配:遇到合适的块就停止搜索
2、下次适配:遇到下一个合适的块就停止搜索
3、最佳适配:遇到最合适的块才停止搜索
7.10本章小结
本章主要讲述了有关虚拟内存的知识,虚拟内存地址与物理内存地址的转化、TLB和四级页表的结构和查找原理、以及如何用物理地址通过三级cache来访存,同时还学习了fork和execve的内存映射,以及动态储存分配管理的原理、实现方式。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
一个Linux文件就是一个m个字节的序列:B0,B1,B2….Bk,….,Bm-1
所有的I/O设备都被模型化为文件而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
Unix IO接口:
打开文件。一个应用程序通过要求内核打开相应的文件,来宣告他想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,他在后续对此文件的所有操作中标识这个文件。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准出错(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。
改变当前的文件位置,文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。
读写文件,从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k
关闭文件。当应用完成对文件的访问后,通知内核关闭这个文件。

Unix IO函数:

  1. open()函数
    功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。

函数原型:int open(const char *pathname,int flags,int perms)

参数:pathname:被打开的文件名(可包括路径名如"dev/ttyS0")flags:文件打开方式,

返回值:成功:返回文件描述符;失败:返回-1

  1. close()函数

功能描述:用于关闭一个被打开的的文件

所需头文件: #include <unistd.h>

函数原型:int close(int fd)

参数:fd文件描述符

函数返回值:0成功,-1出错

  1. read()函数

功能描述: 从文件读取数据。

所需头文件: #include <unistd.h>

函数原型:ssize_t read(int fd, void *buf, size_t count);

参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read操作,应该读多少数量的字符。

返回值:返回所读取的字节数;0(读到EOF);-1(出错)。

  1. write()函数

功能描述: 向文件写入数据。

所需头文件: #include <unistd.h>

函数原型:ssize_t write(int fd, void *buf, size_t count);

返回值:写入文件的字节数(成功);-1(出错)

8.3 printf的实现分析

fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素而vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
Getchar中的read函数将缓冲区都读入buf数组中,返回缓冲区的长度。当buf数组为空,调用read函数,如果不空就返回buf中的第一个元素。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章讲述了IO设备的管理方法,IO接口及其函数,最后分析了printf和getchar函数的实现方法。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
Hello.c先经过预处理,对原本的hello.c进行宏定义、文件包含、条件编译操作。再进行编译,将c语言转化为汇编语言,并且完成语法检查,代码优化,目标代码生成等任务。然后进行汇编,汇编阶段将汇编语言转化为机器语言,并且建立了ELF表,为了之后的重定位做了准备。最后进行链接,将各种代码和数据片段组合成为一个单一文件,在这个阶段我们进行了重定位,把各种同类数据都放在一个段中,并且更新了所有数据的虚拟地址。在结束了整个过程之后我们已经把hello变成了可以执行的文件了,现在我们只需要再命令行里输入执行hello的命令,shell会主动接受我们的命令并且进行识别。在识别出我们的命令是要运行hello文件的时候,系统会fork一个子进程,并且分配给他相应的写时复制的储存空间,然后创建argc argv envp参数来传递用户的输入。子进程调用execve函数,在建立该函数的栈之后我们就可以去开始运行hello函数,在运行函数期间,shell依然可以接受各种命令来改变hello的进程的状态,hello本身也会通过发出信号来实现代码。在运行的过程中hello需要去访问内存中的各种数据,这时就涉及到了地址转换,我们通过段式管理把逻辑地址转化成线性地址,然后再通过TLB或者页表结构将线性地址转化为物理地址,最后我们通过物理地址和三级cache结构得到数据。最后我们用得到的数据和操作命令来将完成hello
天不生我计系统,程序万古如长夜。唯有如此精密而巧妙地系统结构,才能将如此复杂的过程转化为简单而易懂的各个阶段的操作。

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

附件
Hello.c 源程序
Hello.i 预处理后得到的文本文件
Hello.s 编译后得到的汇编文件
Hello.o 汇编后得到的可重定位文件
Hello 生成的可执行程序
Hello.elf hello的ELF格式
Hello.objdump hello的反汇编代码
Helloo.objdump hello.o的反汇编文件
Helloo.elf hello.o的ELF格式
(附件0分,缺失 -1分)

参考文献

[1] Pianistx. printf 函数实现的深入剖析,2013-09-11 22:15https://www.cnblogs.com/pianist/p/3315801.html
[2] 深入理解计算机系统 Randal E.Bryant David R.O’Hallaron 机械工业出版社
[3] 维基百科 virtua memory https://en.wikipedia.org/wiki/Virtual_memory
[4] 内存管理笔记(分页,分段,逻辑地址,物理地址与地址转换方式)
https://www.cnblogs.com/felixfang/p/3420462.html
(参考文献0分,缺失 -1分)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值