计算机系统
 
大作业

题     目  程序人生-Hello’s P2P     
专       业   计算机科学与技术       
学     号        2021110xxx       
班     级      2103xxx   
学       生     ghk    
指 导 教 师        lhw   

计算机科学与技术学院
2022年5月
摘  要
本文章用拟人形象化的语言,同时兼具科学严谨的术语与逻辑,讲述了一个Hello程序的一生。以一个简单的C语言程序为切入点,用生动形象的语言说明了一个程序从预处理,编译,汇编,链接,到进程出现,存储管理,输入输出,进程回收等方面。全面细致的讲述了一个C程序的生命周期,目的是加强对计算机系统的理解,从而以后能面向系统编写更好的程序。

关键词:计算机科学与技术,Linux系统,计算机系统,操作系统,编译原理,计算机体系结构,汇编语言。

(摘要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的整个过程。
P2P:From Program to Process,指源文件到可执行文件,可执行文件到进程的一系列过程。首先是用高级语言编写的源文件,GCC编译器读取hello.c文件,预处理器cpp转化为hello.i修改后的源程序,然后编译器ccl再编程hello.s汇编语言,之后汇编器as将其变为hello.o可重定位目标程序,是二进制编码。最后链接器ld将可重定位目标程序与标准C库链接,最后生成了可执行程序。
然后再shell中./hello,shell就会对读入的字符串进行解析,发现是可执行文件后会调用fork函数去分配一个子进程给hello。之后会调用execve函数去分配内存,初始化等等,跳转地址等等。然后从.rodata只读段读取到hello world并且输出到屏幕上。Hello的生命便被创建了。
020:From Zero-0 to Zero-0,指hello的生命从0开始,最后归于0,hello进程的一切都是shell fork出来的,shell会先为hello清理出一片虚拟内存,保留好原来的寄存器状态之后给hello全新的寄存器。,。设置程序计数器按序执行程序。CPU为hello给出了寄存器,划分了逻辑控制流,开辟了一条条新的流水线,I/O输出了hello语句,RAM为hello备好了数据。待到hello执行返回后,shell父进程会回收掉hello,回收掉其占用的内存寄存器等等,内核会从操作系统中删掉hello的痕迹,hello就这样来无影去无踪,是020的。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X86-64 CPU Intel i5 11400H,16G RAM 
软件环境:Windows 10 64位 ,Ubuntu 16.04 LTS 64 位,Vmware, 
开发工具:gcc ,DEV C++, gdb edb
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i 预处理之后文本文件
hello.s 编译之后的汇编文件
hello.o 汇编之后的可重定位目标程序
hello 链接之后的可执行目标文件
hello.objdmp  Hello.o 的反汇编代码
helloo.objdmp  Hello 的反汇编代码
hello.elf  Hello.o 的 ELF 格式
helloolf  Hello 的 ELF 格式

1.4 本章小结
通过阅读hello的自白,形象的感受了hello经过编写、预处理、编译、汇编、链接和执行等阶段历程。说明了计算机系统在让一个程序运行不同的组成部分,一个进程P2P,020,体现了其中间状态,以及计算机分层次处理程序的思想
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
1.    预处理的概念
预处理是指程序源代码在被翻译成目标代码的过程中,生成二进制文件之前的准备过程,预处理器会读取#开头的内容,修改源程序,将头文件的内容插入到程序文本中,最后得到hello.i文件。
2.    预处理的作用
根据宏定义#define将程序中对应的宏进行替换
针对#ifdef等进行条件编译
将#include的头文件进行复制,添加到源代码中,提高编程模块性
整体的作用就是便于接下来编译器的编译

2.2在Ubuntu下预处理的命令
命令:gcc hello.c -E -o hello.i


 经过预处理之后,可以看hello.c转为hello.i文件
2.3 Hello的预处理结果解析
打开hello.i,发现main函数之前田间了大量代码,大量都是来自于头文件的。


这也间接体现了预编译为源文件到可执行文件的转化节省了许多人力与代码量。hello.i整体上还是原来的C语言语法,是可读的,我们可以尝试理解一下。
从标准库中引用的外部函数


还有体现运行时位置的信息
 
    这样虽然增加了hello.i的大小,但是便于进行汇编。

2.4 本章小结
本章节分析了从hello.c到hello.i预处理的过程,介绍了预处理的概念和作用。并且自己利用命令对文件进行了预处理操作,并且尝试读取解析预处理文件的内容,分析预处理到底进行了什么变化。
(第2章0.5分)
 
第3章 编译
3.1 编译的概念与作用
1.    编译的概念:将与处理好的高级语言文本转换成执行同样操作的汇编语言的过程
2.    编译的作用:计算机无法理解高级语言,所以要把高级语言转化为更简单的汇编语言,机器才有可能把其转化为机器语言。编译器ccl将hello.i翻译成hello.s。同时如果有语法错误就会报错输出提示信息。

3.2 在Ubuntu下编译的命令
对hello.i的命令:gcc -S -o hello.s hello.i
 
打开后发现hello.s是汇编程序
 
3.3    Hello的编译结果解析
3.3.1    数据
可以看到%rbp-20是argc,数据argc保存的位置得到
 
下面是i的赋值,发现循环变量i存放在%rbp-4,局部变量都是存在内存里
 
        
从上面也能看到立即数用4开头表示
        字符串数据存在.LC0段中,保存在.rodata中
     
打印的时候也体现了
 
3.3.2    赋值
循环变量i赋初值为0
 
3.3.3    算术操作
循环变量i++操作
 
对返回值的修改操作
 
用leaq进行计算赋值变址寻址,将存储在.rodate节中的printf语句的格式串传递给寄存器%rdi作为调用printf函数的参数
 
3.3.4    关系操作
这个是argc!=4的语句,不等于4就跳到后面
 
循环控制中的i<9语句
 
3.3.5    控制转移
If的条件判断
 
满足if条件则顺序执行,要是不满足,则相等,则跳转到L2,L3,不执行if内的操作
For循环
 
For的主题在L4内,如果符合循环条件就跳到L4中执行循环体
3.3.6    函数操作
Exit函数:
参数:传递参数寄存器%edi值为1
函数调用:通过指令call 调用exit函数
函数返回:无返回

Printf函数:
参数:argv[1]和argc[2]的地址或字符串参数首地址
函数调用:在if条件判断执行体与for循环执行体之中被调用。
函数返回:无返回
 
 
Sleep函数:
参数:传递参数寄存器%edi值为atoi后的返回值rax
函数调用:通过指令call 调用sleep函数
函数返回:无返回
 
Main函数:
参数:argc和argv,对应的寄存器分别为%rdi和%rsi。
函数调用:系统启用函数__libc_start_main通过使用call语句来调用main函数,call指令将下一条指令的地址dest压入栈中,然后跳转至main函数执行。
函数返回:return,0为main函数的正常返回指令。即把0设置到rax然后结束
 
Getchar函数
参数:无参数
函数调用:通过指令call 调用getchar函数
函数返回:无返回
 
3.3.7    类型转换操作
这里用atoi转换
 
把array转换成int,然后传到sleep中当参数
(此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。)

3.4 本章小结
本章中我们主要了解了编译的概念和作用,学习了编译的相关指令。我们还细致的对汇编文件hello.s进行了分析。分析了其中的数据存储,赋值,运算,函数调用,控制转移,关系操作等等。我们对汇编语言有更深的理解,可以读懂汇编语言中的信息,了解了C到汇编语言之间的关系。
(第3章2分)
 
第4章 汇编
4.1 汇编的概念与作用
1.    概念
汇编器将hello.s语言翻译成机器二进制指令语言,结果保存在hello.o中。hello.o为二进制文件。我们打开是乱码的
2.    作用
将人类能读懂的汇编语言进一步抽象,转换为机器能读懂的二进制代码,而且是可重定位的二进制目标文件
4.2 在Ubuntu下汇编的命令
 
 
汇编生成了hello.o文件,并且我还生成了一个hello.elf文件,用于查看elf头
应截图,展示汇编过程!
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.3.1.    ELF头
下图是elf可重定位目标文件的格式。ELF头以一个16字节的序列开始,这个序列描述生成了该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含辅助链接器分析语法和解释目标文件信息。其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表
的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定的大小的条目。
 
4.3.2.    节
不同节的内容存储在ELF头和节头表之间的位置
 
我们随便看看几个节的内容
4.3.3.    节头表
一般节头表都包含以下几个部分
.text:已编译程序的机器代码

.rodata:只读数据,比如printf语句中的格式串和开关语句中的跳转表

.data:已初始化的全局和静态C变量。局部局部C变量在运行的时候保存在栈中,既不出现在.data节中,也不出现在.bss节中。

.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态C变量。在目标文件中这个节不占用实际的空间,它仅仅是一个占位符。目标文件格式区分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0。

.symtab:一个符号表,它存放在程序中定义和引用的的函数和全局变量的信息。一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上,每个可重定位目标文件在.symtab中都有一张符号表。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。

.rel.text:;一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。注意,可执行目标文件并不需要重定位信息,因此通常忽略,除非用户显示地指示链接器包含这些信息。

.rel.data:被模块引用或者定义的所有全局变量的重定位信息。一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都要被修改。

.debug:一个调试符号表,其条目时程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译器驱动程序时,才会得到这张表。

.line:原始C程序中的行号和.text节中机器指令之间的映射。只有以-g选项调用编译器驱动程序时,才会得到这张表。

.strtab:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串的序列。

下图是本程序的节头表
 
其中还包含重定位节.rela.text,其中包含了需要重定位的代码,链接器再处理时会修改这些位置。

重定位节包含以下信息
Offset:需要被修改的节的偏移量Info:包括symbol和type,symbol是前四个字节,type是后四个字节。
symbol:标识被修改后的内容应该指向的符号。
type:重定位的类型。其中包含两种基本类型,分别是R_X86_64_PC32 :重定位一个使用32位PC相对地址的引用。和R_X86_64_32 :重定位一个使用32位PC绝对地址的引用。
value:标识链接器如何修改新的应用。
Attend:有符号整型数,重定位的偏移调整。
Name:重定位到的目标的名称。
而.rela.eh_frame用来储存eh_frame 节的重定位信息。
4.4 Hello.o的结果解析
反汇编命令:objdump -d -r hello.o对hello.o进行了反汇编。结果如下
 
 
Hello.o反汇编与hello.s对比分析,得到以下内容:
1.    机器语言是有操作码,操作数,还有内存,寄存器编号等等数字构成的
2.    机器语言是由汇编语言一一对应映射而来的,汇编语言实际上是机器语言用人类能懂的方式书写,机器语言还添加了重定位条目等更细节的信息,便于后来程序的生成。
但是也有不同:
1.    汇编语言的跳转是用编号表示的,而机器指令中使用相对位置,即目标地址和下一条指令头部的偏移量来表示的。
下图为机器指令的跳转
 
下图为汇编指令的标号跳转
 
2.    汇编使用函数名称进行函数调用,而机器指令更明确的用地址进行函数调用
下图为机器指令的函数调用,而且可以看到后面全为0,因为这些是库中的函数,要等到动态链接后才能确定函数的地址。
 
下图为汇编的函数调用
 
3.    hello.s中的操作数时十进制,hello.o反汇编代码中的操作数是十六进制。机器编码中还存在大小端的差别。
4.    机器指令中有重定位条目,但是汇编hello.s中没有
下图为机器指令中的重定位条目
 
而hello.s中不存在重定位条目。
5.    机器反汇编每条语句都有对应的编号,按照字节排序生成的,而汇编则没有
下图0,4,5,8便是编号
 
而hello.s不存在,语句前没有编号
 
6.    分支转移,汇编语言只有转移到标号,没有具体的地址,而机器语言有详细的地址偏移量描述
下图为机器语言中的分支转移
 
下图为汇编语言中的分支转移
 
objdump -d -r hello.o  分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章我们把汇编语言源程序利用汇编器汇编成了可重定位的目标文件。分析了可重定位目标文件的elf格式,elf头、节头表、符号表等内容,理解了汇编后的二进制可重定位代码。了解了重定位条目信息,当重定位形成可执行文件之后,就会在对应的位置填入相应的数据。
除此以外,我们还利用objdump工具把hello.o文件反汇编为了汇编语言,然后分析了机器语言反汇编形成的汇编语言与hello.s的不同之处。发现了机器语言和汇编语言的不同与映射关系,以及机器语言会留下动态链接的标记,为进一步处理做准备。
(第4章1分)
 
第5章 链接
5.1 链接的概念与作用
1.    链接的概念
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存中并执行。链接可以执行于编译时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。
2.    链接的作用
链接器在软件开发中扮演着一个关键的角色,因为他们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需要简单地重新编译它,并重新链接应用,而不是重新编译其他文件。
总而言之,链接将多个模块化,可复用的小代码段或者库,动态的合成到我们编写的程序中,这样可以综合的使用功能的同时确保代码量不会很大,不会占用很多的空间。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
链接命令;
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
 
可以看到链接后生成了可执行目标文件hello
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
下图是ELF头与节头表
 
 
下面是ELF文件中包含的一些额外内容
 
 
 
以上是全部的ELF内容,分析如下:
由ELF头可以看出,整个程序的种类变成了EXEC可执行文件
再看节头表,可以发现其声明了hello中的所有section信息,包括程序中的size和offset,所以可以根据Section Headers中的信息使用HexEdit定位每个被占用的section在区间(起始位置,大小),其中Address是程序在虚拟地址处加载的起始地址。
比如我们看到只读内存段如下图
 
Rodata段的地址是0x402000,大小是0x3c
Data段的地址是0x404048,大小是0x4
 
等等信息,我们都可以从ELF头分析得到
除此以外,由于可执行文件是被完全链接的,因此也没有了rel节。
    分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.4 hello的虚拟地址空间
使用edb加载hello程序

我们可以进一步查看符号
 
可以发现与之前的下图是一致的
 
interp都在0x4002e0。其他的节都在这附近,比如init段存在于0x401000
rodata段在0x40404等等 ,可以看到用edb说明了我们的程序加载到了正确的地址空间。
    使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。   
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
链接的过程如下:
1.    符号解析,目标文件.o定义和引用符号,每个符号都对应这一个全局变量或者静态变量或者一个函数。
其中函数和已初始化的全局变量或者静态变量是强符号,未初始化的全局变量或者静态变量是弱符号;然后根据链接的符号规则,为每个符号开辟相应的存储空间与赋值。从而每个符号都能得到一个精确的映射定义
2.    重定位,编译器和汇编器生成的代码段和数据段的开始地址都是0。而链接器给每个符号都映射了一个内存地址,这样对符号的操作就是对相应的内存空间的操作。
链接器通过汇编器生成的重定位条目来进行重定位操作。
所以可执行文件是根据重定位条目重定位后的,没有重定位条目,都确定好了位置。Hello.o需要保留重定位条目供后续链接使用。链接的本质是把不同的节合并,如果文件交叉使用不同的符号,便需要把这些符号链接在一起。
下图是hello反汇编的结果。
 
可以看到有以下不同点
1.    地址长度不同,地址内容不同,在未链接的文件中只是简单的使用相对偏移量,而链接后的文件中则是使用虚拟地址空间中的地址。
下图是左未链接右链接后对比,可以看到右边是有虚拟地址显示的
 
2.    未链接时找不到一些库函数的定义,但是在链接后我们都可以看到调用的库函数
下图时部分调用的库函数展示
 
3.    链接后有具体的可追溯的重定位过程
比如我们看库函数atoi的重定位
 
 
 


虚拟地址计算结果就是e8后跟着的偏移量。利用call语句时的pc值计算偏移量跳转到atoi虚拟地址位置便是重定位过程
4.    所有的重定位条目都被修改消失了,因为变成了确定的运行时内存地址
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.6 hello的执行流程
在命令行用edb执行命令
 
之后逐个STEP检查,可以看到执行流程如下
[1]    ld-2.27.so!_dl_start 0x7fce 8cc38ea0
[2]    ld-2.27.so!_dl_init 0x7fce 8cc47630
[3]    hello!_start 0x400500
[4]    libc-2.27.so!libc_start_main 0x7fce 8c867ab0
[5]    -libc-2.27.so!__cxa_atexit 0x7fce 8c889430
[6]    -libc-2.27.so!__libc_csu_init 0x4005c0
[7]    hello!_init 0x400488
[8]    libc-2.27.so!_setjmp 0x7fce 8c884c10
[9]    -libc-2.27.so!_sigsetjmp 0x7fce 8c884b70
[10]    –libc-2.27.so!__sigjmp_save 0x7fce 8c884bd0
[11]    hello!main 0x400532
[12]    hello!puts@plt 0x4004b0
[13]    hello!exit@plt 0x4004e0
[14]    *hello!printf@plt –
[15]    *hello!sleep@plt –
[16]    *hello!getchar@plt –
[17]    ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce 8cc4e680
[18]    -ld-2.27.so!_dl_fixup 0x7fce 8cc46df0
[19]    –ld-2.27.so!_dl_lookup_symbol_x 0x7fce 8cc420b0
[20]    libc-2.27.so!exit 0x7fce 8c889128
下图是edb调试的一个过程实例

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
当程序调用一个有共享库定义函数时,编译器无法预测函数运行地址,所以采用一种延迟绑定的技术,让程序执行时才对共享链接库进行绑定。动态链接器用GOT和PLT实现动态链接,GOT中存放的时函数的目标地址,PLT则使用GOT的条目进行目标函数跳转。
而根据hello的ELF文件可知GOT起始表位置为0x404000,如下图。
 
然后我们用edb去查看项目变化
 
上图是动态链接前
 
上图是动态链接后
可以看到dl_init前0x404000是后面有一大串0的,在动态链接后,这些0全部都被替换成了链接后的值。

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
链接器的出现便于模块化编程,分块编程,独立的编译,链接在一起。有效的节省了程序员的工作量,便于维护代码的同时节省了内存。也由此产生了库函数,便于编写程序。本章着重分析了链接的过程,区分了链接前后程序的异同,了解了链接的原理与步骤。并且对于我们hello程序生命在链接这个部分,利用反汇编,edb等工具,做了具体剖析,对于程序的生成有了更深入的理解。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
1.    进程的概念
2.    CSAPP上解释shell为一个交互的应用型程序,它代表用户运行其他程序。Shell被称为“壳”,给用户提供了操作页面,接受命令然后调试相应的进程。
狭义上,进程是正在运行程序的实例,而广义上,进程是一个具有独立功能的程序关于某一组数据的依次运行。操作系统执行的基本单元就是一个进程。进程既是基本的分配单元,也是基本的执行单元。
每个进程都有自己的pid,也有自己的地址空间与数据,包括文本区域、数据区域和堆栈。文本区域存储处理器执行的代码。而数据存储在进程执行期间动态分配的内存区域中,堆栈区域则管理者调用的指令和本地变量。进程只有在程序被shell调用执行,CPU给予其生命时才成为一个活动的实体
3.    进程的作用
现代操作系统运行一个进程,我们得到了一个假象就是程序独占了整个计统,包括处理器和内存,但是事实上,我们只是在上下文中不停的切换进程来维持着这个假象。进程为应用程序提供了两种形象,一种是独立的逻辑控制流,一种是私有地址空间。提高CPU执行效率,减少因程序等待造成的CPU空闲和其他计算机软硬件资源的浪费。
6.2 简述壳Shell-bash的作用与处理流程
1.    Shell首先从命令行找出特殊字符,将空格,逗号,冒号等字符翻译成间隔符号,把命令行分割成一小条语句的集合
2.    解析命令行,并且把命令行参数传递给execve函数和argv参数
3.    然以这些语句集合会被处理,检查其中是否有shell的关键字
4.    如果不是关键字,则检查路径下是否有相应的可执行程序
5.    如果既不是可执行程序也不是关键字,则输出相应的错误提示信息
6.    如果是可执行程序,则利用fork函数创建相应的子进程
7.    在子进程中,继续之前的步骤获取参数,并且执行程序
8.    如果是shell的内置指令,则执行
9.    对于命令行末尾有&则代表创建的是前台工作,使用waiotpid等待工作返回
10.    如果没有&代表是后台工作,shell返回。
6.3 Hello的fork进程创建过程
命令行输入下图指令,回车后开始整个进程
 
此时bash首先会搜索这是否是一个内置命令,结果不是,那么shell就会在磁盘上开始搜索,找到hello的程序,然后为他分配相应的内存空间并且调入
接着bash会使用fork函数创建子进程。子进程和父进程有相同的虚拟地址空间副本,但是相对于父进程是独立的。每个进程都是有相同的本地变量值还有相同的用户栈。父进程和子进程的区别就是pid不同。
Fork函数会调用一次返回两次,在子进程中返回值是0.在父进程中返回的是子进程的pid号。因此我们可以区分程序的部分是在父进程中进行还是子进程中进行。
6.4 Hello的execve过程
Fork函数并没有执行hello,hello的执行是由execve进行的
Execve 函数,如果成功执行则不返回,如果失败则返回-1
可执行目标文件由execve函数加载运行,即hello程序被赋予生命了。只有当出现错误时,才会返回到调用程序通知他。
Bash首先会调用启动代码,从而设置栈并且将控制传递给主函数。当main开始执行时,用户栈被分配给程序,用户栈的大致结构如下,从栈底到栈顶:
栈底的是参数和环境字符串,往上是null结尾的指针数组,指向的是环境变量字符串,全局变量environ指向这些指针中的第一个envp[0]。环境变量指针数组后的是argv数组,指向的是参数字符串。栈的顶部是系统启动函数libc_start_main的栈帧,之后便是为main函数分配的栈帧了。
6.5 Hello的进程执行
上下文信息的概念:
上下文就是内核重新启动一个被抢占的进程所需要的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。
进程时间片的概念:
进程时间片是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:进程程开始运行直到被抢占的时间)。现代操作系统允许同时运行多个进程 ,即并行。
但是在一个CPU的情况下,前后台的进程不可能同时进行没那么就是不同的进程抢占不同的时间片,时间片在不停分配给不同的进程切换,从而看起来这些进程在同时运行。
进程调度的概念:
进程调度是由内核控制安排的,在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。当内核调度一个新的进程后,就会抢占当前进程,并且被给予一个新的时间片,这便是上下文切换
 
用户态和核心态的概念:
Hello程序一开始是运行在用户状态下,是不能够执行内核代码的。但是如果遇到了异常,便可以切换到内核模式,异常程序会调用内核系统代码,等到执行完毕后,内核模式又会切换为用户模式,如果可能的话,继续执行hello程序。进程上下文切换也是在内核模式完成的。
如下图就是在发送SIGSTP挂起进城后仍然可以查看进程。
 
6.6 hello的异常与信号处理
hello出现的异常:中断,陷阱(即系统调用)
hello产生的信号:SIGINT、SIGTSTP、SIGCONT、SIGCHLD
我们在执行hello程序时,利用键盘CtrlC发送一个终止信号SIGINT
 
当我们用CtrlZ暂停进程后,发送SIGSTP,可以发现hello进程仍然存在
 
用jobs也可以查看
 
我们还可以观察进程树
 
如上图,可以看到pstree和bash都是 bash的子进程。
用kill发送信号SIGCONT,让hello程序执行完
 
还可以用kill发信号SIGKILL,让挂起的hello被杀死

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
进程给我们一种多个程序同时运行的抽象,提高了计算机的性能释放。我们通过这章的学习,了解到了进程的基本概念,原理,和用户模式和内核模式,上下文切换,信号,四大异常,fork函数等等。我们通过具体的剖析自己linux虚拟机上的hello程序的运行
(第6章1分)
 
第7章 hello的存储管理
课程未教授该模块,内容属于自学自己理解的。
7.1 hello的存储器地址空间
逻辑地址:又称相对地址,是程序运行由CPU产生的与段相关的偏移地址部分。他是描述一个程序运行段的地址。

物理地址:程序运行时加载到内存地址寄存器中的地址,内存单元的真正地址。他是在前端总线上传输的而且是唯一的。在hello程序中,他就表示了这个程序运行时的一条确切的指令在内存地址上的具体哪一块进行执行。

线性地址:这个和虚拟地址是同一个东西,是经过段机制转化之后用于描述程序分页信息的地址。他是对程序运行区块的一个抽象映射。
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在段式存储管理中,将程序的地址空间划分为若干个段(segment),这样每个进程有一个二维的地址空间。在前面所介绍的动态分区分配方式中,系统为整个进程分配一个连续的内存空间。而在段式存储管理系统中,则为每个段分配一个连续的分区,而进程中的各个段可以不连续地存放在内存的不同分区中。

程序加载时,操作系统为所有段分配其所需内存,这些段不必连续,物理内存的管理采用动态分区的管理方法。在为某个段分配物理内存时,可以采用首先适配法、下次适配法、最佳适配法等方法。在回收某个段所占用的空间时,要注意将收回的空间与其相邻的空间合并。段式存储管理也需要硬件支持,实现逻辑地址到物理地址的映射。

程序通过分段划分为多个模块,如代码段、数据段、共享段:可以分别编写和编译;可以针对不同类型的段采取不同的保护;可以按段为单位来进行共享,包括通过动态链接进行代码共享。这样做的优点是:可以分别编写和编译源程序的一个文件,并且可以针对不同类型的段采取不同的保护,也可以按段为单位来进行共享。

总的来说,段式存储管理的优点是:没有内碎片,外碎片可以通过内存紧缩来消除;便于实现内存共享。缺点与页式存储管理的缺点相同,进程必须全部装入内存。
 
7.3 Hello的线性地址到物理地址的变换-页式管理
在这个转换中要用到翻译后备缓冲器(TLB),首先我们先将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式,然后再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)然后去TLB缓存里找所对应的PPN(物理页号)如果发生缺页情况则直接查找对应的PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。
7.4 TLB与四级页表支持下的VA到PA的变换
首先在TLB中查找PTE,若能直接找到则直接得到对应的PPN,具体的操作是将VPN看作TLBI和TLBT,前者是组号,后者是标记,根据TLBI去对应的组找,如果TLBT能够对应的话,则能够直接得到PTE,进而得到PPN。
 
其中若是在TLB中找不到对应的条目,则应去多级页表中查找,VPN被分为了四块。有一个叫做CR3的寄存器包含L1页表的物理地址,VPN1提供到了一个L1
PET的偏移量,这个PTE包含L2页表的基地址,VPN2提供到一个L2
PTE的偏移量。依次类推,最终找到页表中的PTE,得到PPN。
而VPO和PPO相等,最终的PA等于PPN+PPO。
7.5 三级Cache支持下的物理内存访问
得到物理地址之后,先将物理地址拆分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。
 
7.6 hello进程fork时的内存映射
mm_struct(内存描述符):描述了一个进程的整个虚拟内存空间
vm_area_struct(区域结构描述符):描述了进程的虚拟内存空间的一个区间
在用fork创建虚拟内存的时候,要经历以下步骤:
创建当前进程的mm_struct,vm_area_struct和页表的原样副本
两个进程的每个页面都标记为只读页面
两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制。
 
7.7 hello进程execve时的内存映射
加载hello并执行需要以下几个步骤:
1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构(即mmap指向的vm_area_structs)。
2.映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域时请求二进制0的,映射到匿名文件,其大小包括在hello中。栈和堆区域也是请求二进制0的,初始长度为0.
3.映射共享区域。如果hello程序域共享对象链接,比如C标准库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

7.8 缺页故障与缺页中断处理
情况1:段错误:首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)
情况2:非法访问:接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。
情况3:如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长。对于每个进程,内核维护着一个变量brk,指向堆的顶部。

分配器有两种风格,但是这两种风格都要求应用显示的分配块。

其中显示分配器要求显示释放任何已分配的块,如malloc、new等。

隐式分配器要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。所以也叫垃圾收集器。
显示分配器有以下几点要求:能够处理任意请求序列;立即相应请求;只是用堆;对齐开;不修改已分配的块。
在性能上有两点追求:
1.最大化吞吐率;
2.最大化内存利用率;
但是这两点通常是对立的。
具体的技术有以下:隐式空闲列表和显式空闲链表
 
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
本章是理解计算机系统存储的重中之重,从内存的分页式管理,到三级cache的高层内存管理,再到一个程序内部的堆的管理。其中的思想十分复杂,却又十分精巧。
我们主要介绍了储存器的地址空间,讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念,还有进程fork和execve时的内存映射的内容。描述了系统如何应对那些缺页异常,最后描述了malloc的内存分配管理机制,操作系统为了稳定的运行,不辞劳苦,精妙的安排了内存的空间,既要分配内存,又要回收内存。 
(第7章 2分)
 
第8章 hello的IO管理
课程未教授该模块,故内容属于自学自己理解的。
8.1 Linux的IO设备管理方法
设备的模型化:文件
文件的类型:
普通文件(regular file):包含任意数据的文件。
目录(directory):包含一组链接的文件,每个链接都将一个文件名映射到一个文件(他还有另一个名字叫做“文件夹”)。
套接字(socket):用来与另一个进程进行跨网络通信的文件
命名通道
符号链接
字符和块设备
设备管理:unix io接口
打开和关闭文件
读取和写入文件
改变当前文件的位置
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
int open(char * filename, int flags, mode_t mode);
若打开文件成功则返回文件描述符,否则返回-1。
flags有多种O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(可读可写)、O_CREAT(如果文件不存在,就创建它的一个截断的空文件)、O_TRUNC(如果文件已经存在,就截断它)、O_APPEND(在每次写操作前,设置文件位置到文件的结尾处)。
mode参数指定访问权限位。
int close(int fd);
会关闭一个打开的文件,如果关闭一个已关闭的文件描述符会出错。
ssize_t read(int fd, void *buf, size_t n);
若成功则返回读的字节数,若EOF则为0,若出错则为-1。
ssize_t write(int fd, const void *buf, size_t n);
若成功则返回为写的字符数,若出错则返回-1。
read函数从描述符为fd的当前位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。
write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。可以通过调用lseek函数,显示地修改当前文件的位置。
8.3 printf的实现分析

Printf的运行过程:
从vsprintf生成显示信息,显示信息传送到write系统函数,write函数再陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序。从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
printf需要做的事情是:接受一个fmt的格式,然后将匹配到的参数按照fmt格式输出。
 
上面是printf的代码,我们可以发现,他调用了两个外部函数,一个是vsprintf,还有一个是write
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return (–n>=0)?(unsigned char)*bb++:EOF;
}

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章简要总结I/O的有关知识,通过对外部设备的模型化实现简单、贯通的读写操作,将所有的IO设备看作文件,通过简单的几个系统函数的组合就完成了各种的读写操作。同时还提供了一种树结构的文件管理模式,并且把目录也抽象为了一种文件。
需要我们重点在编程中掌握read、write、lseek、open、close等基本函数,深入理解内部机制,做到安全的对文件进行读写。
(第8章1分)
结论
Hello的一生就此终结了,让我们用计算机系统的语言回顾hello的一生吧
1.    预处理,把C原始文件变成预处理文件
2.    编译,编译成汇编语言
3.    汇编,汇编语言转成二进制hello.o
4.    链接,把外部库链接到hello.o上,并且进行重定位等一系列操作,最后成为了可执行文件
5.    Shell fork 出子进程,用execve对可执行文件进行加载
6.    发送信号,进行信号处理
7.    最后终止程序并回收
用形象的语言说,懵懂的,刚编写程序的入门菜鸟,编写了一个hello.c程序。但是我们的语言对于计算机来说太难懂了,他尝试翻译,终于编译成了汇编语言,但是他还是读不懂具体的处理信息和地址,所以他又尝试翻译成了二进制语言。二进制语言他读懂了,但是发现里面有很多自己其他的知识,于是计算机把许多的库链接到我们的程序上。他尝试fork出自己的一部分精力处理hello,但是我们又脸滚键盘,发了一堆的信号,我们的程序和shell只能艰难的阻塞,处理各种信号。终于,我们按了CtrlC,我们的hello终于可以被终止了,然后尸体被回收,我们的计算机也终于可以休息了(或者说处理其他进程了)。
在学习后,我发现计算机系统是十分精妙的系统,充满了人类的智慧,他把一个复杂的事物划分成简单的步骤,一步步进行处理。并且进行了合理的调度安排,让进程有条不紊的进行。这也启发了我的程序设计与日常生活,我们不妨把自己的生活看成一个系统,按照步骤预处理,汇编自己的生活,面对各种信号有条不紊的处理屏蔽等等。至于计算机系统,我们可以尝试打破现在的架构,比如让指令和数据隔离,亦或者硬件级并行的架构,都是未来可能的创新点。
(结论0分,缺失 -1分,根据内容酌情加分)
 
附件
hello.i 预处理之后文本文件
hello.s 编译之后的汇编文件
hello.o 汇编之后的可重定位目标程序
hello 链接之后的可执行目标文件
hello.objdmp  Hello.o 的反汇编代码
helloo.objdmp  Hello 的反汇编代码
hello.elf  Hello.o 的 ELF 格式
helloolf  Hello 的 ELF 格式
(附件0分,缺失 -1分)
 
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1]    Bryant and O’Hallaron ,Computer System,A Programmer’s perspective
[2]    https://www.bilibili.com/video/BV1iW411d7hd/?spm_id_from=333.337.search-card.all.click&vd_source=7fa0313d89e2a2a1ed7e393702d7382d
[3]    计算机组成与设计:硬件 / 软件接口 David A.Patterson
(参考文献0分,缺失 -1分)

哈尔滨工业大学计算机系统大作业--程序人生相关推荐

  1. 哈尔滨工业大学计算机系统大作业 程序人生-Hello’s P2P

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 计算机科学与技术学院 2021年5月 摘  要 本文以hello程序从hello.c到进程各个阶段所 ...

  2. 哈尔滨工业大学计算机系统大作业——程序人生-Hello’s P2P

    目录 摘要 第1章 概述 1.1 Hello简介 1.1.1 P2P:From Program to Process 1.1.2 020:From Zero-0 to Zero-0 1.2 环境与工具 ...

  3. 哈尔滨工业大学计算机系统大作业-程序人生 Hello‘s P2P

    文章目录 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的作用及概念 2.2 在Ubuntu下预处理的命令 2.3 Hel ...

  4. 哈尔滨工业大学CSAPP大作业程序人生

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 1190202126 班 级 1936602 学 生 李映泽 指 导 教 师 刘宏伟 计算机科学与技术学院 20 ...

  5. HIT 深入理解计算机系统 大作业 程序人生-Hello’s P2P

    HIT 深入理解计算机系统 大作业 程序人生-Hello's P2P 本论文旨在研究 hello 在 linux 系统下的整个生命周期.结合 CSAPP 课本, 通过 gcc 等工具进行实验,从而将课 ...

  6. 【2022】哈工大计算机系统大作业——程序人生Hello’s P2P

    2022哈工大计算机系统大作业--程序人生Hello's P2P 摘要 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概 ...

  7. 哈尔滨工业大学 计算机系统大作业

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机类 学   号 120L022010 班   级 2003007 学       生 窦馨宇 指 导 教 ...

  8. 哈工大2022年春季学期计算机系统大作业——程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 人工智能(未来技术) 学   号 7203610716 班   级 20WJ102 学       生 孙铭蔚 ...

  9. 2021春深入理解计算机系统大作业——程序人生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 1190200608 班    级 1903004 学       生 琚晓龙 指 导 ...

最新文章

  1. public接口可以被任何一个类实现_一文带你深入Java核心技术:对象克隆+接口与回调,还有这种操作...
  2. 在Linux 环境下搭建 JDK 和 Tomcat
  3. 洛谷——1064金明的预算方案————有依赖的背包
  4. VC实现自定义控件步骤
  5. 离线java人脸识别开源算法_Java 离线人脸识别 基于ArcFace 2.0 Demo
  6. php数据访问层设计,php - Zend框架数据访问层(DAL) - 堆栈内存溢出
  7. micropython编译原理_C语言嵌入式Linux高级编程第9期:CPU和操作系统入门视频课程...
  8. 程序员每天少吃===活120岁
  9. 内置函数enumerate()使用
  10. 安全中心登录_放心使用instagram需要做到的5个信息安全设置
  11. Spring 常见注解原理和自定义@interface注解
  12. sqlserver 软件授权
  13. 三条中线分的六个三角形_三角形三条中线的交点叫什么
  14. BUUCTF-[网鼎杯 2020 青龙组]虚幻2
  15. 关于洗地机电池容量问题,你怎么看
  16. word2vec初步使用
  17. JAVA服务器端发送邮件问题:Could not connect to SMTP host: smtp.qq.com, port: 465
  18. 地震发生时,我们如何避震自救?
  19. 采集碰到的一大堆麻烦事
  20. 史上最欠揍的28条短信

热门文章

  1. 深度揭秘Java GC底层,这次让你彻底弄懂她
  2. 入门设计原则C++实现五:依赖倒置原则
  3. 三门问题详解(附C语言实现)
  4. 怎么在看视频时保持电脑屏幕不灭,干货到,WIN10如何设置电脑屏幕一直亮着
  5. MFC界面——实现分割与合并文件
  6. PPT文档打不开提示访问出错怎么办
  7. centos安装MySQL到指定盘_Centos下安装mysql 和挂载硬盘
  8. 麻雀要革命2 第8节:莽撞的麻雀小姐
  9. Python中的函数及Python字符串中提取字母、数字
  10. USA gov data from Bitly