2021-06-27

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机学院
学   号 1190200914
班   级 1903012
学 生 刘旭东
指 导 教 师 史先俊

计算机科学与技术学院
2021年5月
摘 要
关键词:预处理,编译,汇编,链接,进程管理,存储管理,I/O管理
本文通过介绍hello.c这个源代码如何经过预处理,编译,汇编和链接形成一个可执行文件的,以及这个可执行文件如何经过linux中的shell的命令行来运行它,它的运行所需要的进程的思想,虚拟内存的思想,高速缓存的思想,以及相应的数据结构来阐释计算机系统的原理以及其执行机制。Hello,一个简单而又复杂的一生,揭开了程序底层运行的机制。

目 录

第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的整个过程。
P2P:在编写完一个hello.c的一个高级语言程序后,经过预处理器(cpp)将头文件中的内容插入到文本中得到hello.i的ascii码文件。经过编译器(cc1)将文件编译成由源代码翻译的汇编代码的hello.s的ascii码文件。经过汇编器(as)将hello.s翻译成机器语言指令(可重定位目标程序)的二进制文件。经过链接器(ld)将多个可重定位目标文件合并,得到可执行目标文件(hello)。在命令行中执行此文件,shell判定这个不是内置命令,所以就为其fork一个子进程,于是hello就变成了一个process。至此,P2P结束。
020:在子进程中shell为其execve调用加载器,删除子进程现有的虚拟内存段并创建一组新的代码段,数据段,堆和栈。其中堆和栈都初始化为0。加载器跳转到_start地址,最终会调用应用程序的main函数(加载过程中没有任何从磁盘到内存的数据复制,直至cpu引用一个被映射的虚拟页才进行复制)。当进程执行完后,shell父进程负责回收hello进程,然后内核删除hello相应的数据结构。至此,020结束。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk
软件环境:Windows10 64位;Vmware 11;Ubuntu 16.04 LTS 64位
开发工具:Vscode, Visual studio,gdb,gcc,edb
1.3 中间结果
hello.i 预处理后的文件
hello.s 编译后的文件。
hello.o 汇编后的文件
hello 链接后的可执行文件
1.4 本章小结
本章介绍了通过预处理,编译,汇编,链接,fork的过程的总体概括,介绍了一个代码如何转化成一个进程。通过execve,创建虚拟地址空间,执行目标文件,回收进程介绍了如何将一个文件执行并且回收的机制。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理概念:是一种命令语句(如#define),它指示预处理程序如何修改源代码。将预处理指令(可以简单理解为#开头的正确指令)转换为实际代码中的内容(替换)。
作用:根据源代码中的预处理指令修改你的源代码。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析
删除了hello.c中的注释,并标记了hello.c中行号,hello.i前面是对include中每一个文件展开,在每个文件中会有struct定义以及函数的外部定义等。最后是hello.c main函数部分。
2.4 本章小结
预处理头文件的内容以及宏定义都插入到程序中。生成一个ascii码的文本文件。完备了程序需要的函数和解释了宏的必要步骤。

第3章 编译
3.1 编译的概念与作用

编译概念:就是把预处理后的文件转化为汇编指令的过程
作用:将属于高级语言的代码翻译成低级语言的汇编代码,方便生成机器码使CPU可以执行。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析
3.3.1
.file 显示源文件的名称
.text 声明代码段的起始点
.global 声明一个全局变量sleepsecs,是一个宏
.data 声明数据段的起始点
.align 声明所有数据都以4字节对齐
.type 指明数据的类型或函数的类型
.size 声明一个定义的宏的具体数值大小
.long 声明一个long型的变量
.section 声明只读数据代码段的起始点
.string 声明一个字符串变量
3.3.2
数据格式决定汇编代码后缀:
如果是字节(char)用b,如果是字(short)用w,如果是双字(int,double)用l,如果是四字(long,char*)用q。
数据传送指令:mov,第一个是源操作数,第二个是目的操作数。将数据从源操作数复制到目的操作数。
压入和弹出栈数据:push,pop。
算术和逻辑操作:add:加 sub:减 imul:乘
加载有效地址:lea 从内存读数据到寄存器。
跳转指令:
cmp 根据两个操作数之差设置条件码。
jmp 直接跳转
je 两个操作数相等或者为0,即条件码为0时跳转。
通过跳转指令实现条件分支:

如果相等,则跳转到.L2继续执行,否则会执行exit函数终止程序。

通过跳转指令执行循环分支:

在第31行将其初始化为0并进入.L4中,.L4中将其+1,并与7比较,如果小于等于7的话,继续进行.L4,否则退出循环。
ret 结束程序
3.3.3
栈:程序利用栈结构后进先出的内存管理原则,管理它的过程所用的存储空间,程序寄存器等。栈向低地址方向增长,%rsp指向栈顶元素。可以用push,pop将数据存入栈或从栈顶取出。给栈指针减少一个量可以为没有初始值的数据在栈上分配空间。x86-64需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为过程的栈帧。
因此,汇编代码中的-4(%rbp),-32(%rbp)等,就是这样的未初始化的数据。期中-4(%rbp)就是i,-20(%rbp)就是argc,-32(%rbp)就是argv[3]。
对于64位操作系统,函数的第一个参数是%rdi,第二个参数是%rsi。
3.4 本章小结
本章讲述了程序的机器级表示,高级语言是如何产生汇编代码以及如何理解以及书写汇编代码。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编:将汇编代码生成机器级代码的过程
作用:生成机器级代码便于机器直接执行该文件。

4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式
4.3.1 ELF头
以一个16字节序列开始,该序列描述了生成该文件的系统的字的大小和字节顺序。剩下部分包含帮助链接器语法分析和解释目标文件的信息,包括ELF头的大小,机器类型,目标文件的类型,机器类型,字节头部表的文件偏移,以及节头部表的条目大小和数量。

4.3.2段头部表:
传达了各个段距离段头部表的偏移量,还有每个节的大小和类型。因为还没有进行重定位,所以这个段头部表并不能显示完整的信息。

4.3.3:.text:已编译程序的机器代码
包含.data数据,puts函数,printf函数,atoi函数,sleep函数,getchar函数。

4.3.4:rela.eh_frame:eh_frame的重定位信息

4.3.5:.symtab符号表:
存放程序中的定义和引用的函数和全局变量的信息。

4.4 Hello.o的结果解析
objdump -d -r hello.o 生成hello.o的汇编

汇编内容:

1.调用函数:在.s文件中,函数是直接用函数的名称进行调用的,而在反汇编文件中,call的目标地址是下一条指令的地址。

查表可知,偏移21和2b的分别旧是puts函数和exit函数,与链接器中对应。
2.分支转移:.s文件中的跳转是使用的目标地址的记号,而反汇编文件中是直接用确定的地址进行跳转的。

4.5 本章小结
本章说明了从hello.s转化成二进制可重定位目标文件hello.o的过程,以及作用和为后面链接做得重定位的准备。
通过将.s和.o的代码的对比,发现两者的不同在于对不确定地址的引用。.s文件直接用名称引用,而.o文件要用精确的地址进行引用,不确定的地址暂时填为0,并添加到重定位条目中,等待静态和动态链接器的调节。

(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:将所有二进制形式的目标文件和系统组件组合成一个可执行文件。
作用:将可重定位目标文件hello.o和其他需要的库函数合并成一个可执行目标文件hello,可以被加载到内存,由系统执行
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib/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/lib.so /usr/lib/x86-64-linux-gnu/crtn.o
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间
使用edb打开hello程序后,可以通过data dump来看加载到虚拟地址的hello程序。在0x400000~0x401000内存段,程序载入到主存中。
再次通过readelf查看hello程序的段头部表,程序头表在执行时被使用,告诉链接器运行时加载的内容并提供动态链接的信息,8个段每个都给出的他的虚拟地址和物理地址。

该程序共包含8个段:
1.PHDR:保存程序头表。
2.INTERP:指定在程序加载到内存后,必须调用的解释器。
3.LOAD:表示需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据,程序的目标代码等。
4.DYNAMIC:保存了由动态链接器保存的信息。
5.NOTE:保存辅助信息。
6.GNU_STACK:权限标志,标志栈是否可执行。
7.GNU_RELRO:指定在重定位结束之后内存区域是否设置为只读。
通过data dump查看虚拟地址段0x60000-0x602000,在0fff区域与0x400000存放的程序相同。

5.5 链接的重定位过程分析
hello.o文件中只有main函数,hello文件中,有_init,.plt等函数的定义。链接。
hello.o 文件中对库文件中函数的调用没法用确定的序号,hello文件中由于链接了库文件,可以直接跳转到该序号中。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
Ld-2.27.so!_dl_start 0x7fce8cc38ea0
Ld-2.27.so!_dl_init 0x7fce8cc47630
Hello!_start 0x400500
Libc-2.27.so!__lib_start_main 0x7fce8c867ab0
-lib-2.27.so!__cxa_atexit 0x7fce8c889430
-libc-2.27.so!__libc_csu_init 0x4005c0
Hello!_init 0x400488
Hello!main 0x400532
Hello!puts@plt 0x4004b0
Hello!exit@plt 0x4004e0
*hello!printf@plt
*hello!sleep@plt
*hello!getchar@plt
Ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce8cc4e680
Ld-2.27.so!_dl_fixup 0x7fce8cc46df0
–ld-2.27.so!_dl_lookup_symbol_x 0x7fce8cc420b0
Libc-2.27.so!exit 0x7fce8c889128
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章主要介绍了链接使一个可重定位文件链接上其需要的函数之后变成了一个可执行目标文件,此外,还分析了hello的ELF内容的组成,hello的虚拟地址空间,位置无关代码的第一次引用和之后引用的区别。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是正在运行的程序的实例。系统中每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中程序的代码,数据,栈,通用寄存器的内容,程序计数器以及环境变量。
作用:进程为用户提供了一个假象,好像我们的程序是系统当前运行的唯一的程序一样,独占地使用处理器和内存。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个用编程语言编写的程序,是linux系统和用户之间沟通所需的必要工具,它提供了一个界面,用户通过这个界面访问操作系统和运行自己的目标文件。
流程:
1.Shell首先从命令行中找出特殊字符(如SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。
2. 程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
3. 当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块tokens。
4.Shell对~符号进行替换。
5.Shell对所有前面带有 符 号 的 变 量 进 行 替 换 。 6 . S h e l l 将 命 令 行 中 的 内 嵌 命 令 表 达 式 替 换 成 命 令 ; 他 们 一 般 都 采 用 符号的变量进行替换。 6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用 6Shell(command)标记法。
7.Shell计算采用$(expression)标记的算术表达式。
8.Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。
9.Shell执行通配符* ? [ ]的替换。
10.shell把所有待处理注释删除,并且按照下面的顺序实行命令的检查:
A. 内建的命令
B. shell函数(由用户自己定义的)
C. 可执行的脚本文件(需要寻找文件和PATH路径)
11.在执行前的最后一步是初始化所有的输入输出重定向。
12.最后,执行命令。

6.3 Hello的fork进程创建过程
在命令行中输入./hello。hello不是一个shell的内置命令,所以调用子进程来完成这个可执行目标文件。
fork创建一个新的运行的子进程。子进程得到父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段,堆,共享库以及用户栈。所以子进程可以读写父进程中打开的任何文件,最大的区别在于他们拥有不同的pid。
fork函数调用一次返回两次,在父进程中fork返回子进程的PID,子进程中返回0。
父进程与子进程是并发的进程,内核通过上下文切换来执行他们两个中的其中一个,当父进程完成之后,会等待子进程的完成,将子进程回收,并将其在内核中的上下文删除。
两个进程由相同但是独立的地址空间
两个进程共享文件。比如,父进程调用fork时,stdout文件是打开的指向屏幕,因此子进程继承了这个文件,输出也是指向屏幕的。
6.4 Hello的execve过程
execve在上下文中加载并运行一个新程序。
当在父进程fork一个子进程后,子进程将调用execve函数,并且把命令行参数传递给execve。然后将子进程原来的虚拟内存内容删除,为execve创建一个新的虚拟内存。Execve调用加载器的操作系统代码来执行hello程序,新的栈和堆被初始化为0.通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容,最后加载器设置PC指向函数_start地址,_start调用主函数main。
6.5 Hello的进程执行
进程上下文信息:内核为每一个进程维护一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态(通用目的寄存器,浮点寄存器,程序寄存器,用户栈,状态寄存器,内核栈和各种内核数据结构)
进程时间片:一个进程执行它的控制流的一部分的每一时间段。
进程调度的过程:在某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策叫做调度。是由内核中称为调度器的代码处理的。内核调度了一个新的进程运行后,就抢占当前进程,并使用上下文切换的机制来将控制转移到新的进程。上下文切换:(1)保存当前进程上下文(2)恢复某个先前被抢占的进程被保存的上下文(3)将控制转移给这个新恢复的进程。
用户态与核心态转换:处理器通常是使用某个控制寄存器中的一个模式位来提供两种模式的区别。当设置了模式位时,进程就运行在内核模式中,一个运行在内核模式中的进程可以执行指令集中的任何指令,并且可以访问系统中的任何位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令。也不允许用户模式中的进程之间引用地址空间中内核区的代码和数据。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结
shell为系统与用户之间搭建了沟通的桥梁,shell为了hello fork并且execve,分配时间片,linux依靠进程的上下文切换有条不紊的并发地执行各种各样的进程(即程序)。并且实验证明了在进程运行的过程中如果遇到意外的情况,系统的意外处理程序会采取各种各样的动作以维持系统的运行

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序被划分为代码段,数据段,堆栈段等,将每段的首地址存储于基地址寄存器,程序代码操作的地址为段偏移量。
线性地址:某地址空间中的地址是连续的非负整数时,该地址空间中的地址被成为线性地址。
虚拟地址:CPU在寻址的时候,系统为每个进程都安排了一个虚拟地址空间,其中每个地址空间都对应一个主存的大小,通过CPU的MMU将虚拟地址转化成主存中实际的地址来寻找想要找到的元素。
物理地址:计算机系统主存被组织成一个由M个连续字节大小的单元组成的数组,每字节都有一个唯一的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个段描述符由8个字节组成,分成了GDT和LDT两类,段描述符描述了段的特征。一般系统只定义一个GDT。IA-32中引入了GDTR和LDTR两个寄存器,用来存放当前正在使用的GDT和LDT的首地址。在linux系统中,每个CPU对应一个GDT。一个GDT中有18个段描述和14个未使用的保留项。
段地址的转换过程
1.IA-32首选确定要访问的段,然后决定使用的段寄存器。
2.根据段选择符号的TI字段决定是访问GDT和LDT,他们的首地址则通过GTDR和LDTR来获得。
3.将段选择符的Index字段的值*8,然后加上GDT或LDT的首地址,就能得到当前段描述符的地址。
4.得到段描述符地址后,可以通过段描述符中BASE获得段的首地址。
5.将逻辑地址中32位的偏移地址和段首地址相加就可以得到实际要访问的物理地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
Linux下的虚拟地址VA属于线性地址的一种。
一级页表中的每个PTE映射虚拟地址空间中的4MB的片,由1024个连续页面组成,以此类推还有二级页表,三级页表等。。。最后找到PPN后,将PPN和VPO结合,得到了物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
CPU想要得到一个VA的数据,先将VA传给内存管理单元(MMU),然后MMU在TLB中寻找相应的VPN,如果匹配上的话,就hit,将得到的PPN和VPO结合就是想要的物理地址,然后再将PA传递给主存的高速缓存中找到相应的地址的数据,传递给CPU就结束了。
如果在TLB中没有找到相应的PPN的话,就会将VA分为4个VPN和1个VPO。每一个级数的VPN都是在相应级数的页表的索引,就这样在每一级中寻找相应的PPN,再将其组合起来,就得到了PA,接下来和HIT后的操作一样。

7.5 三级Cache支持下的物理内存访问
在MMU得到了一个PA之后,将其传递给高速缓存cache中。并且按照cache的大小来将PA进行划分,最右边是CO,中间是CI,左边是CT首先在最高级的cache下寻找有没有匹配到的,如果有,直接将数据返回给CPU。
如果没有命中的话,一次在L2,L3,主存中寻址。直到把相应的数据找到并返回给CPU。
7.6 hello进程fork时的内存映射
fork被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个进程创建虚拟内存,它创建了当前进程的mm_struct,区域结构和页表的原样副本。它将两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记位私有的写时复制。
fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新的页面。因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
在bash中的进程中执行了如下的execve调用:execve(“hello”,NULL,NULL);
execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。
先删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在区域结构。
映射私有区域。为新进程的代码,数据,bss和栈区域创建新的区域结构。所有这些区域都是私有的,写时复制的。
映射共享区域。
设置远程计数器PC。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:DRAM缓存不命中称为缺页。
页面命中是完全由硬件来处理的,与之不同的是,处理缺页要求硬件和操作系统内核协作完成。
(1) 处理器生成一个虚拟地址并将它传给MMU
(2) MMU生成PTE地址,并从高速缓存/主存请求得到它
(3) 高速缓存/主存向MMU返回PTE
(4) PTE有效位为0,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
(5) 缺页处理程序确定出物理内存中的牺牲页。如果这个页面已经被修改了,则把它换出到磁盘。
(6) 缺页处理程序页面调入新的页面并更新内存的PTE.
(7) 缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
动态存储分配管理:动态内存分配器维护一个进程的虚拟内存区域,称为堆。系统之间细节不同,但是不失通用性。分配器将堆视为一组不同大小的块的计核来维护。每个块就是一个连续的虚拟内存片。要么是已分配的,要么是空闲的。已分配的块显式的保留位供应用程序使用,空闲块可以用来分配。空闲块保持空闲直到它显式的被应用所分配,一个已分配的块保持分配状态直到它被释放。这种释放要么是应用程序显示执行的,要么是内存分配器自身隐式执行的。
两种分配方法:
(1)隐式空闲链表:一个块由一个字的头部,有效载荷,以及可能的一些额外的填充组成的。头部编码了这个块的大小。以及这个块是已分配的还是空闲的。并且需要对齐。因此空闲块是通过头部中的大小字段隐含地连接着的,分配器可以通过遍历堆中的所有块间接遍历空闲块集合。
(2)显式空闲链表:显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。
三种分配策略:
(1)首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块。
(2)下一次适配:从上一次查询结束的地方开始搜索,选择第一个合适的空闲块。
(3)最佳适配:检查每个空闲块,选择适合所需请求大小的最小空闲块。
7.10本章小结
本章主要介绍了 hello 的存储器地址空间、 intel 的段式管理、 hello 的页式管理,在指定环境下介绍了 VA 到 PA 的变换、物理内存访问,还介绍 hello 进程 fork 时的内存映射、 execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备都被模型化位文件,而所有的输入和输出都被当做对相应文件的读和写来执行。
允许Linux内核引出一个简单低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一中统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
打开文件:内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操作。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准出错(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。
改变当前的文件位置,文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。
读写文件,读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k
关闭文件:当应用完成对文件的访问后,通知内核关闭这个文件。内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中
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;
}
形参列表:fmt是一个需要格式化的字符串,后面 … 是传递参数的个数不确定时,表示一个列表。
vsprintf函数:作用是格式化,并返回字符串长度。
从寄存器中通过总线复制到显卡的显存中,此时字符以ascii码形式存储。字符显示驱动子进程将ascii码在自模库找到点阵信息存储到vram中,最后显示芯片按照刷新频率逐行读取vram,并通过信号线向显示器传输每一点。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向显示器传输每一个点。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结
本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,最后分析了两个常用函数:printf getchar的实现分析。

(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1.编写程序。
2.源文件的预处理:将hello.c中头文件的内容都插入到文本中,将所有的宏都解释成具体的值。
3.编译:编译器将hello.i转化成汇编语言的ascii码文件hello.s。
4.汇编:汇编器将hello.s转化成二进制的机器代码,生成hello.o(可重定位目标文件)。
5.链接:对hello.o中引用的外部函数,全局变量等进行符号解析,重定位并且生成hello的可执行目标文件。
6.运行:在命令行中输入hello 1182910227 liufengzhe
7.创建子进程:通过fork创建。
8.执行:通过execxe调用加载器,建立虚拟内存映射,设置当前进程的上下文中的程序计数器,使之指向程序入口处。
9.访存:CPU上的MMU根据页表将CPU生成的虚拟地址翻译成物理地址,并用物理地址在主存的高速缓存中找相应的数据。
10.动态内存申请:printf调用malloc进行动态内存分配,在堆中申请所需的内存。
11.接收信号:中途接收ctrl+z挂起,ctrl+c终止。
12.程序返回后,内核向父进程发送SIGCHLD信号,终止的hello被父进程回收,内核将其上下文删除。

本次大作业概括了深入了解计算机系统这本书,不可谓不好。hello.c程序要进行预编译,编译,汇编,链接才能成为一个可执行目标文件hello。再通过shell来执行这个目标程序。其中包括创造子进程,执行,访问内存数据,通过malloc和free动态分配内存,以及信号的机制和作用,最后再将程序返回删除。这样才能运行起来hello程序。其中虚拟内存的思想使得许多进程能够共享主存这个空间。不得不说,这是一批批计算机科学家智慧的结晶,让人动容,更让人敬佩。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
Hello.i 预处理之后的ascii码程序
Hello.s 编译之后的汇编程序
Hello.o 汇编之后的可重定位目标程序(二进制程序)
Hello 可执行目标程序
Hello1.s 反汇编后输出的程序
Hello2.可执行程序的反汇编代码

参考文献
[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分)

[在此处输入文章标题]

0

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机学院
学   号 1190200914
班   级 1903012
学 生 刘旭东
指 导 教 师 史先俊

计算机科学与技术学院
2021年5月
摘 要
关键词:预处理,编译,汇编,链接,进程管理,存储管理,I/O管理
本文通过介绍hello.c这个源代码如何经过预处理,编译,汇编和链接形成一个可执行文件的,以及这个可执行文件如何经过linux中的shell的命令行来运行它,它的运行所需要的进程的思想,虚拟内存的思想,高速缓存的思想,以及相应的数据结构来阐释计算机系统的原理以及其执行机制。Hello,一个简单而又复杂的一生,揭开了程序底层运行的机制。

目 录

第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的整个过程。
P2P:在编写完一个hello.c的一个高级语言程序后,经过预处理器(cpp)将头文件中的内容插入到文本中得到hello.i的ascii码文件。经过编译器(cc1)将文件编译成由源代码翻译的汇编代码的hello.s的ascii码文件。经过汇编器(as)将hello.s翻译成机器语言指令(可重定位目标程序)的二进制文件。经过链接器(ld)将多个可重定位目标文件合并,得到可执行目标文件(hello)。在命令行中执行此文件,shell判定这个不是内置命令,所以就为其fork一个子进程,于是hello就变成了一个process。至此,P2P结束。
020:在子进程中shell为其execve调用加载器,删除子进程现有的虚拟内存段并创建一组新的代码段,数据段,堆和栈。其中堆和栈都初始化为0。加载器跳转到_start地址,最终会调用应用程序的main函数(加载过程中没有任何从磁盘到内存的数据复制,直至cpu引用一个被映射的虚拟页才进行复制)。当进程执行完后,shell父进程负责回收hello进程,然后内核删除hello相应的数据结构。至此,020结束。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk
软件环境:Windows10 64位;Vmware 11;Ubuntu 16.04 LTS 64位
开发工具:Vscode, Visual studio,gdb,gcc,edb
1.3 中间结果
hello.i 预处理后的文件
hello.s 编译后的文件。
hello.o 汇编后的文件
hello 链接后的可执行文件
1.4 本章小结
本章介绍了通过预处理,编译,汇编,链接,fork的过程的总体概括,介绍了一个代码如何转化成一个进程。通过execve,创建虚拟地址空间,执行目标文件,回收进程介绍了如何将一个文件执行并且回收的机制。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理概念:是一种命令语句(如#define),它指示预处理程序如何修改源代码。将预处理指令(可以简单理解为#开头的正确指令)转换为实际代码中的内容(替换)。
作用:根据源代码中的预处理指令修改你的源代码。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析
删除了hello.c中的注释,并标记了hello.c中行号,hello.i前面是对include中每一个文件展开,在每个文件中会有struct定义以及函数的外部定义等。最后是hello.c main函数部分。
2.4 本章小结
预处理头文件的内容以及宏定义都插入到程序中。生成一个ascii码的文本文件。完备了程序需要的函数和解释了宏的必要步骤。

第3章 编译
3.1 编译的概念与作用

编译概念:就是把预处理后的文件转化为汇编指令的过程
作用:将属于高级语言的代码翻译成低级语言的汇编代码,方便生成机器码使CPU可以执行。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析
3.3.1
.file 显示源文件的名称
.text 声明代码段的起始点
.global 声明一个全局变量sleepsecs,是一个宏
.data 声明数据段的起始点
.align 声明所有数据都以4字节对齐
.type 指明数据的类型或函数的类型
.size 声明一个定义的宏的具体数值大小
.long 声明一个long型的变量
.section 声明只读数据代码段的起始点
.string 声明一个字符串变量
3.3.2
数据格式决定汇编代码后缀:
如果是字节(char)用b,如果是字(short)用w,如果是双字(int,double)用l,如果是四字(long,char*)用q。
数据传送指令:mov,第一个是源操作数,第二个是目的操作数。将数据从源操作数复制到目的操作数。
压入和弹出栈数据:push,pop。
算术和逻辑操作:add:加 sub:减 imul:乘
加载有效地址:lea 从内存读数据到寄存器。
跳转指令:
cmp 根据两个操作数之差设置条件码。
jmp 直接跳转
je 两个操作数相等或者为0,即条件码为0时跳转。
通过跳转指令实现条件分支:

如果相等,则跳转到.L2继续执行,否则会执行exit函数终止程序。

通过跳转指令执行循环分支:

在第31行将其初始化为0并进入.L4中,.L4中将其+1,并与7比较,如果小于等于7的话,继续进行.L4,否则退出循环。
ret 结束程序
3.3.3
栈:程序利用栈结构后进先出的内存管理原则,管理它的过程所用的存储空间,程序寄存器等。栈向低地址方向增长,%rsp指向栈顶元素。可以用push,pop将数据存入栈或从栈顶取出。给栈指针减少一个量可以为没有初始值的数据在栈上分配空间。x86-64需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为过程的栈帧。
因此,汇编代码中的-4(%rbp),-32(%rbp)等,就是这样的未初始化的数据。期中-4(%rbp)就是i,-20(%rbp)就是argc,-32(%rbp)就是argv[3]。
对于64位操作系统,函数的第一个参数是%rdi,第二个参数是%rsi。
3.4 本章小结
本章讲述了程序的机器级表示,高级语言是如何产生汇编代码以及如何理解以及书写汇编代码。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编:将汇编代码生成机器级代码的过程
作用:生成机器级代码便于机器直接执行该文件。

4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式
4.3.1 ELF头
以一个16字节序列开始,该序列描述了生成该文件的系统的字的大小和字节顺序。剩下部分包含帮助链接器语法分析和解释目标文件的信息,包括ELF头的大小,机器类型,目标文件的类型,机器类型,字节头部表的文件偏移,以及节头部表的条目大小和数量。

4.3.2段头部表:
传达了各个段距离段头部表的偏移量,还有每个节的大小和类型。因为还没有进行重定位,所以这个段头部表并不能显示完整的信息。

4.3.3:.text:已编译程序的机器代码
包含.data数据,puts函数,printf函数,atoi函数,sleep函数,getchar函数。

4.3.4:rela.eh_frame:eh_frame的重定位信息

4.3.5:.symtab符号表:
存放程序中的定义和引用的函数和全局变量的信息。

4.4 Hello.o的结果解析
objdump -d -r hello.o 生成hello.o的汇编

汇编内容:

1.调用函数:在.s文件中,函数是直接用函数的名称进行调用的,而在反汇编文件中,call的目标地址是下一条指令的地址。

查表可知,偏移21和2b的分别旧是puts函数和exit函数,与链接器中对应。
2.分支转移:.s文件中的跳转是使用的目标地址的记号,而反汇编文件中是直接用确定的地址进行跳转的。

4.5 本章小结
本章说明了从hello.s转化成二进制可重定位目标文件hello.o的过程,以及作用和为后面链接做得重定位的准备。
通过将.s和.o的代码的对比,发现两者的不同在于对不确定地址的引用。.s文件直接用名称引用,而.o文件要用精确的地址进行引用,不确定的地址暂时填为0,并添加到重定位条目中,等待静态和动态链接器的调节。

(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:将所有二进制形式的目标文件和系统组件组合成一个可执行文件。
作用:将可重定位目标文件hello.o和其他需要的库函数合并成一个可执行目标文件hello,可以被加载到内存,由系统执行
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib/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/lib.so /usr/lib/x86-64-linux-gnu/crtn.o
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间
使用edb打开hello程序后,可以通过data dump来看加载到虚拟地址的hello程序。在0x400000~0x401000内存段,程序载入到主存中。
再次通过readelf查看hello程序的段头部表,程序头表在执行时被使用,告诉链接器运行时加载的内容并提供动态链接的信息,8个段每个都给出的他的虚拟地址和物理地址。

该程序共包含8个段:
1.PHDR:保存程序头表。
2.INTERP:指定在程序加载到内存后,必须调用的解释器。
3.LOAD:表示需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据,程序的目标代码等。
4.DYNAMIC:保存了由动态链接器保存的信息。
5.NOTE:保存辅助信息。
6.GNU_STACK:权限标志,标志栈是否可执行。
7.GNU_RELRO:指定在重定位结束之后内存区域是否设置为只读。
通过data dump查看虚拟地址段0x60000-0x602000,在0fff区域与0x400000存放的程序相同。

5.5 链接的重定位过程分析
hello.o文件中只有main函数,hello文件中,有_init,.plt等函数的定义。链接。
hello.o 文件中对库文件中函数的调用没法用确定的序号,hello文件中由于链接了库文件,可以直接跳转到该序号中。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
Ld-2.27.so!_dl_start 0x7fce8cc38ea0
Ld-2.27.so!_dl_init 0x7fce8cc47630
Hello!_start 0x400500
Libc-2.27.so!__lib_start_main 0x7fce8c867ab0
-lib-2.27.so!__cxa_atexit 0x7fce8c889430
-libc-2.27.so!__libc_csu_init 0x4005c0
Hello!_init 0x400488
Hello!main 0x400532
Hello!puts@plt 0x4004b0
Hello!exit@plt 0x4004e0
*hello!printf@plt
*hello!sleep@plt
*hello!getchar@plt
Ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce8cc4e680
Ld-2.27.so!_dl_fixup 0x7fce8cc46df0
–ld-2.27.so!_dl_lookup_symbol_x 0x7fce8cc420b0
Libc-2.27.so!exit 0x7fce8c889128
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章主要介绍了链接使一个可重定位文件链接上其需要的函数之后变成了一个可执行目标文件,此外,还分析了hello的ELF内容的组成,hello的虚拟地址空间,位置无关代码的第一次引用和之后引用的区别。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是正在运行的程序的实例。系统中每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中程序的代码,数据,栈,通用寄存器的内容,程序计数器以及环境变量。
作用:进程为用户提供了一个假象,好像我们的程序是系统当前运行的唯一的程序一样,独占地使用处理器和内存。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个用编程语言编写的程序,是linux系统和用户之间沟通所需的必要工具,它提供了一个界面,用户通过这个界面访问操作系统和运行自己的目标文件。
流程:
1.Shell首先从命令行中找出特殊字符(如SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。
2. 程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
3. 当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块tokens。
4.Shell对~符号进行替换。
5.Shell对所有前面带有 符 号 的 变 量 进 行 替 换 。 6 . S h e l l 将 命 令 行 中 的 内 嵌 命 令 表 达 式 替 换 成 命 令 ; 他 们 一 般 都 采 用 符号的变量进行替换。 6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用 6Shell(command)标记法。
7.Shell计算采用$(expression)标记的算术表达式。
8.Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。
9.Shell执行通配符* ? [ ]的替换。
10.shell把所有待处理注释删除,并且按照下面的顺序实行命令的检查:
A. 内建的命令
B. shell函数(由用户自己定义的)
C. 可执行的脚本文件(需要寻找文件和PATH路径)
11.在执行前的最后一步是初始化所有的输入输出重定向。
12.最后,执行命令。

6.3 Hello的fork进程创建过程
在命令行中输入./hello。hello不是一个shell的内置命令,所以调用子进程来完成这个可执行目标文件。
fork创建一个新的运行的子进程。子进程得到父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段,堆,共享库以及用户栈。所以子进程可以读写父进程中打开的任何文件,最大的区别在于他们拥有不同的pid。
fork函数调用一次返回两次,在父进程中fork返回子进程的PID,子进程中返回0。
父进程与子进程是并发的进程,内核通过上下文切换来执行他们两个中的其中一个,当父进程完成之后,会等待子进程的完成,将子进程回收,并将其在内核中的上下文删除。
两个进程由相同但是独立的地址空间
两个进程共享文件。比如,父进程调用fork时,stdout文件是打开的指向屏幕,因此子进程继承了这个文件,输出也是指向屏幕的。
6.4 Hello的execve过程
execve在上下文中加载并运行一个新程序。
当在父进程fork一个子进程后,子进程将调用execve函数,并且把命令行参数传递给execve。然后将子进程原来的虚拟内存内容删除,为execve创建一个新的虚拟内存。Execve调用加载器的操作系统代码来执行hello程序,新的栈和堆被初始化为0.通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容,最后加载器设置PC指向函数_start地址,_start调用主函数main。
6.5 Hello的进程执行
进程上下文信息:内核为每一个进程维护一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态(通用目的寄存器,浮点寄存器,程序寄存器,用户栈,状态寄存器,内核栈和各种内核数据结构)
进程时间片:一个进程执行它的控制流的一部分的每一时间段。
进程调度的过程:在某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策叫做调度。是由内核中称为调度器的代码处理的。内核调度了一个新的进程运行后,就抢占当前进程,并使用上下文切换的机制来将控制转移到新的进程。上下文切换:(1)保存当前进程上下文(2)恢复某个先前被抢占的进程被保存的上下文(3)将控制转移给这个新恢复的进程。
用户态与核心态转换:处理器通常是使用某个控制寄存器中的一个模式位来提供两种模式的区别。当设置了模式位时,进程就运行在内核模式中,一个运行在内核模式中的进程可以执行指令集中的任何指令,并且可以访问系统中的任何位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令。也不允许用户模式中的进程之间引用地址空间中内核区的代码和数据。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结
shell为系统与用户之间搭建了沟通的桥梁,shell为了hello fork并且execve,分配时间片,linux依靠进程的上下文切换有条不紊的并发地执行各种各样的进程(即程序)。并且实验证明了在进程运行的过程中如果遇到意外的情况,系统的意外处理程序会采取各种各样的动作以维持系统的运行

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序被划分为代码段,数据段,堆栈段等,将每段的首地址存储于基地址寄存器,程序代码操作的地址为段偏移量。
线性地址:某地址空间中的地址是连续的非负整数时,该地址空间中的地址被成为线性地址。
虚拟地址:CPU在寻址的时候,系统为每个进程都安排了一个虚拟地址空间,其中每个地址空间都对应一个主存的大小,通过CPU的MMU将虚拟地址转化成主存中实际的地址来寻找想要找到的元素。
物理地址:计算机系统主存被组织成一个由M个连续字节大小的单元组成的数组,每字节都有一个唯一的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个段描述符由8个字节组成,分成了GDT和LDT两类,段描述符描述了段的特征。一般系统只定义一个GDT。IA-32中引入了GDTR和LDTR两个寄存器,用来存放当前正在使用的GDT和LDT的首地址。在linux系统中,每个CPU对应一个GDT。一个GDT中有18个段描述和14个未使用的保留项。
段地址的转换过程
1.IA-32首选确定要访问的段,然后决定使用的段寄存器。
2.根据段选择符号的TI字段决定是访问GDT和LDT,他们的首地址则通过GTDR和LDTR来获得。
3.将段选择符的Index字段的值*8,然后加上GDT或LDT的首地址,就能得到当前段描述符的地址。
4.得到段描述符地址后,可以通过段描述符中BASE获得段的首地址。
5.将逻辑地址中32位的偏移地址和段首地址相加就可以得到实际要访问的物理地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
Linux下的虚拟地址VA属于线性地址的一种。
一级页表中的每个PTE映射虚拟地址空间中的4MB的片,由1024个连续页面组成,以此类推还有二级页表,三级页表等。。。最后找到PPN后,将PPN和VPO结合,得到了物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
CPU想要得到一个VA的数据,先将VA传给内存管理单元(MMU),然后MMU在TLB中寻找相应的VPN,如果匹配上的话,就hit,将得到的PPN和VPO结合就是想要的物理地址,然后再将PA传递给主存的高速缓存中找到相应的地址的数据,传递给CPU就结束了。
如果在TLB中没有找到相应的PPN的话,就会将VA分为4个VPN和1个VPO。每一个级数的VPN都是在相应级数的页表的索引,就这样在每一级中寻找相应的PPN,再将其组合起来,就得到了PA,接下来和HIT后的操作一样。

7.5 三级Cache支持下的物理内存访问
在MMU得到了一个PA之后,将其传递给高速缓存cache中。并且按照cache的大小来将PA进行划分,最右边是CO,中间是CI,左边是CT首先在最高级的cache下寻找有没有匹配到的,如果有,直接将数据返回给CPU。
如果没有命中的话,一次在L2,L3,主存中寻址。直到把相应的数据找到并返回给CPU。
7.6 hello进程fork时的内存映射
fork被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个进程创建虚拟内存,它创建了当前进程的mm_struct,区域结构和页表的原样副本。它将两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记位私有的写时复制。
fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新的页面。因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
在bash中的进程中执行了如下的execve调用:execve(“hello”,NULL,NULL);
execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。
先删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在区域结构。
映射私有区域。为新进程的代码,数据,bss和栈区域创建新的区域结构。所有这些区域都是私有的,写时复制的。
映射共享区域。
设置远程计数器PC。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:DRAM缓存不命中称为缺页。
页面命中是完全由硬件来处理的,与之不同的是,处理缺页要求硬件和操作系统内核协作完成。
(1) 处理器生成一个虚拟地址并将它传给MMU
(2) MMU生成PTE地址,并从高速缓存/主存请求得到它
(3) 高速缓存/主存向MMU返回PTE
(4) PTE有效位为0,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
(5) 缺页处理程序确定出物理内存中的牺牲页。如果这个页面已经被修改了,则把它换出到磁盘。
(6) 缺页处理程序页面调入新的页面并更新内存的PTE.
(7) 缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
动态存储分配管理:动态内存分配器维护一个进程的虚拟内存区域,称为堆。系统之间细节不同,但是不失通用性。分配器将堆视为一组不同大小的块的计核来维护。每个块就是一个连续的虚拟内存片。要么是已分配的,要么是空闲的。已分配的块显式的保留位供应用程序使用,空闲块可以用来分配。空闲块保持空闲直到它显式的被应用所分配,一个已分配的块保持分配状态直到它被释放。这种释放要么是应用程序显示执行的,要么是内存分配器自身隐式执行的。
两种分配方法:
(1)隐式空闲链表:一个块由一个字的头部,有效载荷,以及可能的一些额外的填充组成的。头部编码了这个块的大小。以及这个块是已分配的还是空闲的。并且需要对齐。因此空闲块是通过头部中的大小字段隐含地连接着的,分配器可以通过遍历堆中的所有块间接遍历空闲块集合。
(2)显式空闲链表:显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。
三种分配策略:
(1)首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块。
(2)下一次适配:从上一次查询结束的地方开始搜索,选择第一个合适的空闲块。
(3)最佳适配:检查每个空闲块,选择适合所需请求大小的最小空闲块。
7.10本章小结
本章主要介绍了 hello 的存储器地址空间、 intel 的段式管理、 hello 的页式管理,在指定环境下介绍了 VA 到 PA 的变换、物理内存访问,还介绍 hello 进程 fork 时的内存映射、 execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备都被模型化位文件,而所有的输入和输出都被当做对相应文件的读和写来执行。
允许Linux内核引出一个简单低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一中统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
打开文件:内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操作。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准出错(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。
改变当前的文件位置,文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。
读写文件,读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k
关闭文件:当应用完成对文件的访问后,通知内核关闭这个文件。内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中
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;
}
形参列表:fmt是一个需要格式化的字符串,后面 … 是传递参数的个数不确定时,表示一个列表。
vsprintf函数:作用是格式化,并返回字符串长度。
从寄存器中通过总线复制到显卡的显存中,此时字符以ascii码形式存储。字符显示驱动子进程将ascii码在自模库找到点阵信息存储到vram中,最后显示芯片按照刷新频率逐行读取vram,并通过信号线向显示器传输每一点。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向显示器传输每一个点。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结
本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,最后分析了两个常用函数:printf getchar的实现分析。

(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1.编写程序。
2.源文件的预处理:将hello.c中头文件的内容都插入到文本中,将所有的宏都解释成具体的值。
3.编译:编译器将hello.i转化成汇编语言的ascii码文件hello.s。
4.汇编:汇编器将hello.s转化成二进制的机器代码,生成hello.o(可重定位目标文件)。
5.链接:对hello.o中引用的外部函数,全局变量等进行符号解析,重定位并且生成hello的可执行目标文件。
6.运行:在命令行中输入hello 1182910227 liufengzhe
7.创建子进程:通过fork创建。
8.执行:通过execxe调用加载器,建立虚拟内存映射,设置当前进程的上下文中的程序计数器,使之指向程序入口处。
9.访存:CPU上的MMU根据页表将CPU生成的虚拟地址翻译成物理地址,并用物理地址在主存的高速缓存中找相应的数据。
10.动态内存申请:printf调用malloc进行动态内存分配,在堆中申请所需的内存。
11.接收信号:中途接收ctrl+z挂起,ctrl+c终止。
12.程序返回后,内核向父进程发送SIGCHLD信号,终止的hello被父进程回收,内核将其上下文删除。

本次大作业概括了深入了解计算机系统这本书,不可谓不好。hello.c程序要进行预编译,编译,汇编,链接才能成为一个可执行目标文件hello。再通过shell来执行这个目标程序。其中包括创造子进程,执行,访问内存数据,通过malloc和free动态分配内存,以及信号的机制和作用,最后再将程序返回删除。这样才能运行起来hello程序。其中虚拟内存的思想使得许多进程能够共享主存这个空间。不得不说,这是一批批计算机科学家智慧的结晶,让人动容,更让人敬佩。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
Hello.i 预处理之后的ascii码程序
Hello.s 编译之后的汇编程序
Hello.o 汇编之后的可重定位目标程序(二进制程序)
Hello 可执行目标程序
Hello1.s 反汇编后输出的程序
Hello2.可执行程序的反汇编代码

参考文献
[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分)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值