计算机系统大作业——程序人生

Posted 叮当猫252

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机系统大作业——程序人生相关的知识,希望对你有一定的参考价值。

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 未来技术(人工智能)
学   号 7203610402
班   级 2036017
学 生 陈鹏宇    
指 导 教 师 史先俊

计算机科学与技术学院
2022年5月
摘 要
hello基本上是每一个学习编程的人第一个接触的程序。本文以hello程序从一个文本文件经过预处理、编译、汇编、链接的一系列步骤变成可执行文件的过程为主要内容,同时分析了hello运行过程中涉及到的进程管理、内存管理、io管理、回收等问题。整个过程是在ubuntu的环境下运行的,并且使用了gcc、edb等工具协助。
关键词:预处理、编译、汇编、链接、进程管理、内存管理、io管理
查看图片请访问
https://www.cnblogs.com/ddm252/p/16292156.html

目 录

第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过程,即将程序文件翻译成一个可执行文件hello。这个过程可以分成四个阶段完成:
预处理:根据以字符#开头的命令,修改原始的C程序。
编译阶段:翻译成汇编语言。
汇编阶段:翻译成机器语言指令,并打包保存在hello.o中。
链接阶段:合并库文件。
图1 P2P示例
对于020过程:shell调用fork函数为程序创建新的进程,之后操作系统调用exceve在上下文中加载并运行hello,映射虚拟内存,并加载对应的物理内存。随后在时间片到达时开始执行主函数。主函数执行结束后,父进程将回收这一进程,进程终止,回收资源,恢复shell上下文,将控制权交给shell。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1.2.1 硬件环境
CPU; AMD Ryzen 4800H
RAM;16GB
1.2.2 软件环境
Windows 10 ;Vmware 15;Ubuntu 16.04 。
1.2.3 开发工具
Visual Studio 2022;CodeBlocks 64位;vi/vim/gedit+gcc。

1.3 中间结果
hello.c 源程序
hello.i 预处理后文本文件
hello.s 汇编程序
hello.o 可重定位目标程序
hello 可执行目标程序(二进制)
hello1.txt hello.o的反汇编文件
hello2.txt hello的反汇编文件
elf.txt hello.o的ELF
elf2.txt hello的ELF
1.4本章小结
本章介绍了hello程序的P2P过程(From Program to Process)和020过程(From Zero-0 to Zero-0),对实验所涉及的文件做了基本介绍。

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
2.1.1预处理的概念
预处理是C语言的一个重要的功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理命令部分做处理,处理完毕自动进入对源程序的编译。任何C语言程序都有一个预处理程序。预处理器根据以字符#开始的预编译指令,修改原始的C语言程序。主要包括1. 宏定义;2. 文件包含;3. 条件编译
2.1.2 预处理的作用
得到另一个C程序,通常以.i作文件扩展名。根据预处理指令分别处理:将源文件中以“include”格式包含的文件复制到编译的源文件中;用实际值替换“#define”定义的字符串;3.根据“#if”后面的条件决定需要编译的代码。为下一步编译提供文本文件。
2.2在Ubuntu下预处理的命令
预处理命令为gcc -E hello.c -o hello.i
图2 预处理示例

2.3 Hello的预处理结果解析
打开预处理得到的文本程序,可以发现hello.i仍未高级语言程序,实际上这就是插入头文件所得到的高级语言程序源码。

图3 hello.i
2.4 本章小结
本章介绍了预处理的概念及作用。并且以hello.c为例,在ubuntu下利用gcc对hello.c进行了预处理,并且分析了预处理所得到的hello.i文件。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念
编译器(ccl)将文本文件.i翻译成文本文件.s,它包含一个汇编语言程序。
3.1.2编译的作用
编译将预处理之后得到的高级语言程序.i翻译成汇编语言程序.s同时进行词法分析和语法分析,若发现错误,可以给出提示。
3.2 在Ubuntu下编译的命令
编译的命令:gcc -S hello.i -o hello.s
图4 在ubuntu
3.3 Hello的编译结果解析
3.3.1汇编指令

图5 汇编指令
.file:声明源文件
.text: 声明代码节
.section: 指示把代码划分成若干个段(Section)
.rodata: 只读代码
.align: 声明对指令或数据的存放地址对齐方式
.string: 声明一个字符串(.LC0,.LC1)
.global: 声明全局变量(main)
.type: 指定类型
3.1.2数据
在hello.c程序中使用到的数据结构有整数和字符串。
3.1.2.1整数
图6 i的定义
i为程序开始就声明的局部变量,被保存在栈%rbx中。i为int类型共占四个字节。
图7 变量argc
argc为形参,保存在寄存器%edb中。
图8 立即数举例
除了上述的两个int型整型参数,程序中还有立即数表示为$形式。
3.1.2.2字符串
在hello.c的只读数据段
图9 字符串

3.1.2.3数组
图10 数组
指针数组char argv[]是main函数的形参,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别指向两个字符串数组的首地址。char占8个字节,所以argv[1]和argv[2]两个字符串的首地址分别为-16(%rbp)和-24(%rbp)。
3.2赋值操作

图11 赋值
Hello.c程序中的赋值只有在for循环中的一条,即为将i的初始值赋值为0,在汇编中使用mov直接利用立即数赋值。
3.3类型转换
hello.c中的类型转换是使用atoi函数进行的,程序利用函数将字符串转换成了数字。
图12 类型转换
3.4算数操作
在循环中的i++为算数操作,直接使用立即数$1来操作。同时移栈操作也是使用算数操作进行的例如在为数组argv开辟空间时使用到了subq $32, %rsp,取出argv数组中的对应内容时的addq $24, %rax等。
图13 i++算数运算
3.5关系操作
在程序中一共使用了两个关系判断,分别为if和for的判断语句。即
if(argc!=4),for(i=0;i<8;i++)。
3.5.1 if的关系操作

图14 if的关系操作语句
使用cmpl将argc直接与立即数$4进行比较,如果相等跳转到L2,如果不相等继续按照顺序运行。
3.5.2 for的关系操作
图15 for的关系操作
使用cmpl将i直接与立即数$7进行比较,如果小于或等于7,跳转到L4,如果不相等继续按照顺序运行。
3.6数组/指针/结构操作
本程序中只有argc字符指针数组涉及到了数组/指针/结构操作,主要是利用栈操作实现的命令。
图16 指针操作
3.7控制转移
控制转移操作与3.5中所介绍的关系操作基本一致。
在if的控制转移中,使用cmpl将argc直接与立即数$4进行比较,如果相等跳转到L2,如果不相等继续按照顺序运行。(图14)
在for的控制转移中使用cmpl将i直接与立即数$7进行比较,如果小于或等于7,跳转到L4,如果不相等继续按照顺序运行。(图15)
3.8函数操作
hello.c程序一共使用了五次函数调用,在汇编程序中使用call表示函数调用。
3.8.1exit()
exit函数的作用是直接退出程序运行,%edi寄存器中的值为1,将1传递进去之后直接退出程序执行。
图17 exit函数的调用
3.8.2atoi()
atoi函数作用是将字符串转化为整型。在hello.c中,将参数存储在%rdi中,然后调用atoi函数。
图18 atoi函数调用
3.8.3printf()
printf函数作用是打印。在hello.c中,使用两次printf,但是在汇编程序中只有一次printf函数的调用。原因是第一次的printf被优化成为了puts(可能是因为没有参数输入)
图19 printf被优化成了puts
图20 printf函数调用
3.8.4sleep()
sleep函数在atoi函数调用完后紧接着调用。atoi函数的返回值即为sleep函数的参数。
图21 sleep函数调用
3.8.5getchar()
getchar()函数直接调用。
图22 getchar函数调用
3.4 本章小结
本章介绍了编译的概念和作用。并且在ubuntu下,以hello程序为例对编译所得到的汇编程序进行了初步分析。详细介绍了编译的数据、赋值、类型转换、算术操作、关系操作、数组指针结构操作、函数调用、控制转移等方面的处理方法。

(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
4.1.1汇编的概念
汇编器(as)将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序(relocatable object program)的格式,并将结果保存在.o 目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。
4.1.2 汇编的作用
将汇编语言翻译成机器语言。
4.2 在Ubuntu下汇编的命令
Ubuntu下汇编的命令为:gcc -c -o hello.o hello.s
图23 Ubuntu下的汇编指令
4.3 可重定位目标elf格式
使用readelf -a hello.o > ./elf.txt,可以提取hello.o文件的ELF。
图24 生成elf.txt
Elf可以分为四个部分,ELF头、节头部表、符号表、重定位节。
4.3.1 ELF头
打开elf.txt,寻找到ELF头。ELF头以16进制的Magic开始,Magic描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,ELF头其余部分包含了帮助链接器语法分析和解释目标文件的信息,包括ELF头的大小、目标文件的类型(如可重定位、可执行、共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。
图25 ELF头
4.3.2节头部表
打开ELF.txt,找到节头部表。记录这里各节的名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等。
图26 节头部表
4.3.3 符号表
打开ELF.txt,找到符号表。符号表存放程序中定义和引用的函数和全局变量的信息。其中name是符号名称, value是符号相对于目标节的起始位置偏移,size是目标的大小,type数据或者函数。Bind字段表明符号是本地的还是全局的。
图27 符号表
4.3.4 重定位节
重定位节保存的是.text节中需要被修正的信息,任何调用外部函数或者引用全局变量的指令都需要被修正,调用外部函数的指令和引用全局变量的指令需要重定位,调而用局部函数的指令不需要重定位。链接器会通过重定位节的重定位条目计算出正确的地址。
在hello程序中,puts,exit,printf,atoi,sleep,getchar都需要进行重定位。
图28 重定位节
4.4 Hello.o的结果解析
使用objdump -d -r hello.o > hello1.s得到hello.o的反汇编,保存在hello1.s中。与hello.s做对比。
图29 hello1.s
机器语言的构成:机器语言是二进制(十六进制表示)序列,机器直接根据这个序列进行工作。是最低级的语言,也是执行效率最高的语言。
机器语言与汇编语言的映射关系:汇编语言程序可以由汇编程序转为机器语言,接着机器读取机器语言运行程序,同时机器语言可以通过反汇编生成汇编语言文件。
hello.s和hello1.s的区别:
1)分支转移:在汇编语言中使用.Lx等助记符表示;在反汇编代码中,分支转移通过主函数+偏移量来表示。
2)函数调用:在汇编语言中直接使用函数名称来表示函数调用;在反汇编代码中,直接用地址跳转来表达函数调用。
3)立即数表示:在汇编语言中使用十进制数;在反汇编语言中使用16进制数。
4.5 本章小结
本章介绍了汇编的概念和作用,并以hello程序为例进行了汇编的演示。利用readelf对文件做了分析。最后进行了反汇编,比较了汇编语言和反汇编语言的区别。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
5.1.1链接的概念
链接(linking)是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(run time),也就是在由应用程序来执行。
5.1.2链接的作用
链接器在软件开发过程中扮演着一个关键的角色,因为它们使得分离编译(separate compilation)成为可能。

5.2 在Ubuntu下链接的命令
在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
图30 链接命令
5.3 可执行目标文件hello的格式
使用得到readelf -a hello > elf2.txt

图 31 生成elf2.txt
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。这里的ELF格式同上文所提到的类似,都分为ELF头、节头部表、程序头、重定位节和字符表。
图32 ELF头
ELF头主要存储程序信息,与hello.o的ELF相比可以发现记录的类型发生了变化,变成了exec(可执行文件)。
节头部表记录各节的名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等。链接时会把节头部表进行合成和更改。(图33)
程序头记录了页面大小、虚拟内存、段大小等信息。
符号表多出了一些符号,来源应为链接时产生的库或者启动函数。
重定位节失去了.rela.text,并且多一些新的重定位,应该与链接的库中的函数有关
图33 节头部表 图34 程序头
5.4 hello的虚拟地址空间
图35 虚拟地址空间范围
在edb中打开可执行程序hello,可以看到进程的虚拟地址信息。段的虚拟空间从0x400000开始到0x401000结束。
以5.3中以节头部表为例,.interp的偏移量为2e0,查找0x4002e0,证明成立。
图36 .interp的虚拟空间
其他部分同理。

5.5 链接的重定位过程分析
使用objdump -d -r hello对hello进行反汇编,比较其与hello.o的反汇编的差异。
图37 对hello反汇编
在链接之后,hello中增加了使用到的库函数,即puts、exit、printf、sleep、atoi、getchar等。
图38 增加的库函数
在连接之后,所有地址使用虚拟地址来表示。实现了调用函数和控制转移的重定位。(图39)
最后hello中还增加了.init和.plt。(图40)
5.6 hello的执行流程

图39虚拟地址 图40.init和.plt

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程如下。执行流程和地址为
_libc_start_main 401008
_main 401125
_printf 4010a0
_exit 4010d0
_sleep 4010e0
_getchar 4010b0
Exit 4010d0
5.7 Hello的动态链接分析
查看.elf文件,寻找got位置,在edb中寻找该位置。
图41 寻找got
如图got的位置为403ff0。使用edb查找该位置内存如下图所示(图42)。在调用dl__init之后改变为(图43)。

图42 got初始
图43 got改变之后的状态
5.8 本章小结
本章介绍了链接的概念和作用。利用工具edb查看了虚拟地址,并且对重定位和动态链接进行了深入分析。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中程序的实例。一个程序运行起来我们就称为一个进程,是系统资源和调度的独立单位,它会占用对应的内存区域,由CPU进行执行与计算。进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响。

6.2 简述壳Shell-bash的作用与处理流程
Shell:Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。处理流程:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数
3)如果是内置命令则立即执行
4)否则调用相应的程序执行
5)shell 应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
在终端输入命令,shell对命令进行判断,如果不是内置指令则调用一个fork函数创建新的子进程。例如输入./hello 7203610402 陈鹏宇时,shell分析不是内置命令,则调用fork创建一个子进程,子进程与父进程数据空间、栈、堆等资源完全相同。
6.4 Hello的execve过程
创建子进程之后,子进程调用exceve()函数在当前子进程的上下文加载并运行一个新的程序。当调用exceve函数加载并运行可执行目标文件时,如果出错,exceve会返回到调用程序,如果顺利进行,exceve不返回。exceve调用加载器加载可执行目标文件时首先删除已存在的用户区域,随后创建新的代码、数据、堆和栈段并且映射共享区域;最后设置程序计数器(PC)。
6.5 Hello的进程执行
上下文:系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量立即打开文件描述符的集合。
进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。多任务也叫做时间分片。
进程调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这种决策叫做调度。
用户态和内核态:运行应用程序代码的进程初始是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。当异常发生时,控制传递函数到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。
6.6 hello的异常与信号处理
hello执行时可能出现如下四种异常:

图44 四种异常

下面利用hello程序进行实践:
1)程序正常进行时为:
图45 正常运行
2)回车:(图46)
3)Ctrl+C:程序终止(图47)

图46 回车 图47Ctrl+C
4)Ctrl+Z
图48 Ctrl+Z
5)ps
图 49 ps
6)Jobs
图50 jobs
7)pstree
图51 pstree
8)fg

图52 fg

9)kill
图53 kill
10)乱按
图54 乱按
6.7本章小结
本章介绍了进程的概念和作用,以及shell的作用和处理流程。并且以hello程序为例,介绍了fork,execve函数,异常的种类。最后介绍了hello的异常与信号处理。

(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:指在计算机体系结构中是指应用程序角度看到的内存单元(memory cell)、存储单元(storage element)、网络主机(network host)的地址。 逻辑地址往往不同于物理地址(physical address),通过地址翻译器(address translator)或映射函数可以把逻辑地址转化为物理地址。 在有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。
线性地址:如果地址空间中的整数是连续的,那么我们说它是一个线性地址空间。实际上就是偏移量。
虚拟地址:与逻辑地址类似与物理内存无关。
物理地址:在存储器中,每一个字节都有唯一的物理地址来表示该位置。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段选择符的格式如图55所示其中 当TI=0时,表示全局描述符表(GDT),TI=1时,表示局部描述符表(LDT)若RPL=00,为第0级,位于最高级的内核态,RPL=11,为第3级,位于最低级的用户态,第0级高于第3级,最后高13位-8K个索引用来确定当前使用的段描述符在描述符表中的位置。
图55 段选择符
寻址时被选中的段描述符先被送至描述符cache,每次从描述符cache中取32位段基址,与32位段内偏移量(有效地址)相加得到线性地址。其中GDT首址或LDT首址都在用户不可见寄存器中。

图56 Intel处理器的存储器寻址
图57 逻辑地址向线性地址转换
7.3 Hello的线性地址到物理地址的变换-页式管理
图58 基于页表的地址翻译
虚拟页地址到物理页地址的映射通过页表进行, CPU中的一个控制寄存器,页表基址寄存器指向当前页表。n位虚拟地址包含两部分,一个p位的虚拟页面偏移(VPO)和一个(n-p)位的虚拟页号(VPN),MMU利用VPN来选择适当的PTE,例如,VPN 0选择PTE 0,VPN 1选择PTE 1,以此类推。将页表中的物理页号PPN与虚拟地址中的VPO串联起来,就得到相应的物理地址,
7.4 TLB与四级页表支持下的VA到PA的变换
7.4.1利用TLB加速地址翻译
图59 TLB命中 图60 TLB不命中
TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块,通常有高度的相联度。
当TLB命中时步骤为:
1)CPU产生一个虚拟地址;
2)MMU从TLB中取出相应的PTE。
3)MMU将这个虚拟地址翻译成一个物理地址,并将它发送到高速缓存/主存。
4)高速缓存/主存将所请求的数据字返回给CPU。
当TLB不命中时,MMU必须从L1缓存中取出相应的PTE,新取的PTE存放在TLB中,可能会覆盖一个已经存在的条目。
7.4.2 四级页表
虚拟地址被划分为4个VPN和1个VPO。每个VPNi都是一个到第i级页表的索引,其中1<=i<=4。第j级页表中的每个PTE,1<=j<=k-1,都指向第j+1级的某个页表的基址,第4级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问四个PTE。

图61 多级页表的地址翻译
7.5 三级Cache支持下的物理内存访问

图62 三级Cache支持下的物理内存访问
在获得PA之后,根据得到的PA进行索引。依次比较L1中的数据,如果命中直接返回,如果不命中依次上下一级(L2,L3)中需按照,若命中返回数据,更新cache。更新cache时,如果有空闲块优先将数据放到空闲块中,如果没有则将某个块驱逐,目标块放到驱逐块的位置。
7.6 hello进程fork时的内存映射
在fork函数被当前进程调用时,内核为新进程上下文,并分配一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
使用execve函数加载并运行hello.out,使用hello.out程序替代当前程序。该过程分为以下几个步骤:
1)删除当前进程虚拟地址中已存在的用户区域
2)映射私有区域,为新程序的代码、数据、bss和栈创建新的区域结构。
3)映射共享区域。
4)设置当前进程上下文程序计数器(PC)。
下一次调度这个进程时,它将从这个入口开始执行。
7.8 缺页故障与缺页中断处理

图63 缺页故障与缺页中断处理
假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页。这个异常导致控制转移到内核的缺页处理程序,处理程序随后就执行下面的步骤:
1)虚拟地址A是否合法?缺页处理程序搜索区域结构的链表,把A和每个区域结构中的vm_start和vm_end做比较。如果这个指令是不合法的,那么缺页处理程序就触发一个段错误,从而终止这个进程。在图63中标记为1
2)试图进行的内存访问是否合法?进程是否有读、写或者执行这个区域内页面的权限?如果试图进行的访问是不合法的,那么缺页处理程序会触发一个保护异常,从而终止这个进程。在图63中标记为2
3)内核知道了这个缺页是由于对合法的虚拟地址进行合法的操作造成的。接下来选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令将再次发送A到MMU。
7.9动态存储分配管理
当运行时需要额外虚拟内存时,用动态内存分配器更方便,也有更好的可移植性。
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。
分配器将堆视为一组不同大小的块( block)的集合来维护。每个块就是一个连续的虚拟内存片(chunk),要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格。两种风格都要求应用显式地分配块。它们的不同之处在于由哪个实体来负责释放已分配的块。
显式分配器(,要求应用显式地释放任何已分配的块。
隐式分配器(implicit allocator),另一方面,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器,而自动释放未使用的已分配的块的过程叫做垃圾收集。
7.10本章小结
本章介绍了hello的地址存储空间、段式管理、页式管理,VA到PA的转换,内存映射、缺页故障与缺页中断处理、和动态存储分配管理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
在Linux系统中所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输人和输出都被当作对相应文件的读和写来执行。这种方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
函数为:int open(char* filename,int flags,mode_t mode)
Linux shell 创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件< unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO和 STDERR_FILENO,它们可用来代替显式的描述符值。
改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek 操作,显式地设置文件的当前位置为k。
读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
函数:ssize_t read(int fd,void *buf,size_t n)
ssize_t wirte(int fd,const void *buf,size_t n)
关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
函数:int close(int fd)

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;


查看vsprintf函数
int vsprintf(char *buf, const char fmt, va_list args)

char
p;
char tmp[256];
va_list p_next_arg = args;

for (p=buf;*fmt;fmt++)  
if (*fmt != '%')  
*p++ = *fmt; 
continue; 
 

fmt++; 

switch (*fmt)  
case 'x': 
itoa(tmp, *((int*)p_next_arg)); 
strcpy(p, tmp); 
p_next_arg += 4; 
p += strlen(tmp); 
break; 
case 's': 
break; 
default: 
break; 
 
 

return (p - buf); 


vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。代码中的vsprintf只实现了对16进制的格式化。随后时函数write和sys_call.
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
int INT_VECTOR_SYS_CALL表示要通过系统来调用sys_call这个函数。
sys_call:
call save

 push dword [p_proc_ready]

 sti

 push ecx
 push ebx
 call [sys_call_table + eax * 4]
 add esp, 4 * 3

 mov [esi + EAXREG - P_STACKBASE], eax

 cli

 ret

sys_cal可以显示格式化了的字符串。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。从而实现printf的功能。
8.4 getchar的实现分析
getchar()函数运行时,用户通过键盘输入控制权交给os,输入的内容便会显示在屏幕上。按下回车键表示输入完成,这时控制权将被交还给程序。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了在Linux系统下的IO管理方法,IO的接口和函数,最后分析了printf和getchar函数。
结论
Hello的经历:
1)源文件:高级程序语言文本文件,hello.c
2)预处理:对源程序中的预处理命令部分做处理,主要包括1. 宏定义;2. 文件包含;3. 条件编译,得到hello.i。
3)编译:编译器(ccl)将文本文件.i翻译成文本文件.s,即翻译成汇编语言。
4)汇编:将汇编程序翻译成机器语言指令(.i)
5)链接:链接器进行符号解析、重定位、动态链接等创建一个可执行目标文件hello。可以执行。
6)fork创建进程:调用fork函数创建子进程,供hello程序的运行。
7)运行:在内核的调度下配合存储管理(MMU、TLB、多级页表、cache、DRAM内存、动态内存分配器)运行。
8)终止:结束进程,020。
学习这门课对计算机有了更深刻的认识,在简单的hello程序的运行背后是硬件、软件、操作系统配合协调工作的结果。在深刻了解了计算机系统之后,我对程序的认知更加深刻,帮助我写出面对机器更加友好的程序。我认识到了计算机是复杂而美丽的,它精巧的设计凝聚了人类的智慧的精华。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
hello.c 源程序
hello.i 预处理后文本文件
hello.s 汇编程序
hello.o 可重定位目标程序
hello 可执行目标程序(二进制)
hello1.txt hello.o的反汇编文件
hello2.txt hello的反汇编文件
elf.txt hello.o的ELF
elf2.txt hello的ELF

参考文献
为完成本次大作业你翻阅的书籍与网站等
[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.
[7] [转]printf 函数实现的深入剖析 - Pianistx - 博客园 (cnblogs.com)
[8] randal E.Bryant 深入理解计算机系统[M]. 机械工业出版社, 2016

(参考文献0分,缺失 -1分)

哈工大2022计算机系统大作业---程序人生

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业        计算机类         

学     号      120L021923          

班     级        2003006          

学       生        甄自镜         

指 导 教 师         吴锐             

计算机科学与技术学院

2021年5月

摘  要

    本文对hello.c在linux下的运行的整个生命周期进行分析,从编写完成开始到程序回收结束,介绍了hello从预处理、编译、汇编、链接直到被回收的整个过程及原理,同时介绍了与运行相关的进程管理、存储管理、I/O管理的内容,描绘了hello波澜壮阔的一生

关键词:预处理;编译;汇编;链接;进程管理;存储管理;linux;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.建立hello.c文件

2. 预处理器将其进行预处理生成hello.i文件。

3. 编译器编译得到hello.s文件

4. 汇编器汇编得到hello.o

5. 链接器与库函数链接生成一个可执行程序

6.通过shell输入./shellshell通过fork函数创建了一个新的进程

7. 调用execve映射虚拟内存,通过mmap为hello程序开创了一片空间。

8. CPU从虚拟内存中的.text,.data节取代码和数据,调度器为进程规划时间片,有异常时触发异常处理子程序。

9. .程序运行结束,父进程回收hello进程和它创建的子进程,内核删除相关数据结构。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

1.2.1 硬件环境

X64 CPU;2GHz;2G RAM;256GHD Disk 以上

1.2.2 软件环境

Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;

1.2.3 开发工具

Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

文件名

功能

hello.c

源程序 

hello.i

预处理后的文件

hello.s 

汇编文件

hello.o

可重定位目标执行文件

hello

可执行文件

hello.elf

hello.o的ELF格式

hello.txt

hello.o的反汇编语言

hello1.txt

hello的反汇编语言

hello1.elf

hello的ELF格式

1.4 本章小结

本章对hello的一生进行了简要的介绍和描述,介绍了P2P,O2O的整个过程,介绍了计算机硬件环境、软件环境、开发与调试工具,介绍了中间文件的名称即其作用。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。

作用:预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。

2.2在Ubuntu下预处理的命令

图2.2.1

      在linux 系统中用gcc -E -o hello.i hello.c进行预处理的命令

2.3 Hello的预处理结果解析

图2.3.1 Hello.i文件截图

分析:hello.c预处理后变为hello.i文件,文件内容如上图所示,文件扩展为三千多行,将原程序中的宏进行了宏展开,对函数的声明,变量定义等信息放在中间,程序的源代码被放到了最后。

2.4 本章小结

本章介绍了预处理的概念与作用,学习了用gcc对hello.c文件进行预处理,将其重定向到hello.i中,并且分析了hello.i文件中的内容

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

作用:

1.语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位。

2.中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。

3.代码优化:指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。

4.目标代码:生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。此处指汇编语言代码,须经过汇编程序汇编后,成为可执行的机器语言代码。

3.2 在Ubuntu下编译的命令

图3.2.1

在linux 系统中用gcc -S hello.i -o hello.s进行编译的命令

3.3 Hello的编译结果解析

3.3.1汇编第一部分:

      图3.3.1

file:声明源文件

text:代码节

section:指示把代码划分成若干个段(Section)

rodata:只读代码段,后面是printf()与scanf()中存储的字符串。

align:数据或者指令的地址对其方式

string:声明一个字符串LC0,LC1

global:声明全局变量(main)

type:声明一个符号是数据类型还是函数类型

3.3.2数据

       1.字符串:即只读代码段中的两个字符串LC0,LC1。二者作为printf(),scanf()的参数,如图:

 

                                                                         图3.3.2

       2.整数:

              在程序中一共有两个int变量,分别为i与argc

i是一个局部变量,编译器进行编译的时候将局部变量i会放在堆栈中。如图所示,局部变量i放在栈上-4(%rbp)的位置。

Argc是传给main的参数,进入了堆栈

3.立即数,形式为$+常数

3.3.3赋值操作:

       利用mov实现,类如:

             

PS:movb:一个字节

movw:“字”

movl:“双字”

movq:“四字”

3.3.4关系操作和控制转移

             

Jle:判断cmpl产生的条件码,小于等于7跳转L4

             

Je:判断cmpl产生的条件码,不等于4则跳转.L2、

3.3.5算术操作:

             

Add 即i++,图片对应解释为:对i自加,栈上存储变量i的值加1

3.3.6数组/指针/结构操作

       主函数main的参数中有指针型数组char *argv[],对此数组的操作通常由mov指令实现。取到argv数组的首地址,然后对首地址加相应字节得到对应的地址,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。char* 数据类型占8个字节

3.3.7函数操作:

Main函数;传入参数argc和argv[],分别用寄存器%rdi和%rsi存储。被系统启动函数调用。设置%eax为0并且返回,对应return 0 。

Printf:第一处调用由于只是输入一串字符串,所以被优化成puts函数,之后通过call来调用puts,而第二处调用printf,有三个参数,因此我们需要取出参数

 

Exit:传入参数1后调用函数退出。

 

Atoi:将字符串类型转换为整型。

Sleep:atoi被调用完后,会将其返回值作为sleep函数的参数,调用sleep。

Getchar:直接调用

3.4 本章小结

介绍了编译的概念以及过程。通过hello函数分析了c语言如何转换成为汇编代码。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中。

作用:将汇编代码转换为机器指令,使其在链接后能被机器识别并执行。

4.2 在Ubuntu下汇编的命令

图4.2.1

在linux 系统中用gcc -c -o hello.o hello.s进行汇编的命令

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

典型ELF可重定位目标文件:

图4.3.1

图4.3.2

在linux 系统中用readelf -a hello.o > hello.elf进行生成elf格式文件的命令

ELF文件格式分析:

1.ELF头:ELF头(ELF header)以一个16B的序列Magic开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。

Hello.elf文件中ELF头:

图4.3.3

2.节头目表:(节头):记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。 因为是可重定位目标文件,所以每个节都从0开始,用于重定位。

图4.3.4

3.重定位节:各个段引用的外部符号等在链接时需要通过重定位对这些位置的地址进行修改。链接器会通过重定位节的重定位条目计算出正确的地址。

需要重定位有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等符号。

图4.3.5

4.符号表:存放在程序中定义和引用的函数和全局变量的信息。编译器中的符号表不同,.symtab符号表不包含局部变量的条目。

图4.3.6

4.4 Hello.o的结果解析

图4.4.1

利用objdump -d -r hello.o >hello.txt 进行反汇编。

机器语言的构成:机器语言是机器能直接识别的程序语言或指令代码,无需经过翻译,每一操作码在计算机内部都有相应的电路来完成它,或指不经翻译即可为机器直接理解和接受的程序语言或指令代码。机器语言使用绝对地址和绝对操作码。不同的计算机都有各自的机器语言,即指令系统。从使用的角度看,机器语言是最低级的语言。

与汇编语言的映射关系:机器语言是用于控制计算机中处理器的实际位,通常被视为十六进制数字序列(通常为字节).处理器从程序存储器中读取这些位,这些位表示下一步操作的"指令".因此,机器语言提供了一种将指令输入计算机的方式

与hello.s的不同:

1.控制转移:

Hello.s

Hello.txt

Hello.s中使用L2这样的段名称进行跳转,而在反汇编中,跳转到函数+偏移量这样的形式。因为段名称在汇编语言中为便于编写的助记符,所以在汇编成机器语言之后就不存在了,而是确定的地址。

2.函数调用

汇编代码中函数调用时直接使用函数名称,而在反汇编的文件中call之后定位到call的下一条指令,即用具体的地址表示。例如:

3.数的表示

hello.s中的操作数表现为十进制,而hello.o反汇编代码中的操作数为十六进制。

4.5 本章小结

本章介绍了汇编。经过汇编器,将汇编语言转化为机器语言,hello.s文件转化为hello.o可重定位目标文件。分析了ELF的文件格式,了解了ELF头等相关概念,分析了汇编语言与机器语言的不同。

(第41分)

第5章 链接

5.1 链接的概念与作用

以下格式自行编排,编辑时删除

注意:这儿的链接是指从 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

图5.2.1

5.3 可执行目标文件hello的格式

利用readelf -a hello > hello1.elfELF Header的具体信息

图5.3.1

1.ELF头

图5.3.2

2.节头

图5.3.3

3.重定位节

图5.3.4

4.符号表

图5.3.5

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

  从data dump中发现虚拟地址从0x401000开始到0x401ff0结束,根据5.3节头部表,可以找到各个节的信息

图5.4.1

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

通过objdump -d -r hello >hello1.txt得到hello的反汇编文件hello1.txt

二者的不同:

1.hello1.txt中多了许多节

比如下图中的.int .plt等

图5.5.1

2.文件内容有所不同

Hello已经完成了重定位,因此在调用函数时使用的地址已经是函数的虚拟地址,同时在跳转时也是使用虚拟地址。

图5.5.2

5.6 hello的执行流程

以下格式自行编排,编辑时删除

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

过程:

0x7ffff7de3f30 <_init>

0x4010f0<_start>

0x4011f1 <in __libc_csu_init>

0x401000<_init>

0x401125 <main>

0x4010a0 printf@pl

0x4010c0 atoi@plt

0x4010e0 sleep@plt

0x4010b0 getchar@plt

0x7ffff7e06a70 <exit>

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

延迟绑定是通过GOT和PLT实现的

由ELF文件可知,got起始表位置为0x403ff0

图5.7.1

调用dl_init前0x403ff0为空

图5.7.2

调用后:

图5.7.3

我们可以发现,0x403ff0出现了变化,存入地址。

5.8 本章小结

本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。

(第51分)

第6章 hello进程管理

6.1 进程的概念与作用

概念:一个执行程序中的实例,系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

作用:进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统。它提供了一个假象,好像我们的程序独占地使用内存系统,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。多进程可以完成多任务,每个进程就好比一家独立的公司,每个公司都各自在运营,每个进程也各自在运行,执行各自的任务。

6.2 简述壳Shell-bash的作用与处理流程

作用:shell是一个交互型应用程序,把用户输入的命令翻译给操作系统。提供了一个界面,用户可以通过这界面访问操作系统内核。

处理流程:shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序(这里的应用程序可以是Linux本身的实用程序,如ls和rm,也可以是购买的商业程序,如xv,或者是自由软件,如emacs)。然后shell在搜索路径里寻找这些应用程序(搜索路径就是一个能找到可执行程序的目录列表)。如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。

6.3 Hello的fork进程创建过程

在我们输入./hello 120L021923 甄自镜 1后,shell将会对我们的命令进行解析,由于我们输入的不是一个内置命令,shell会调用fork(),创建一个子进程,子进程得到与父进程用户级虚拟地址空间相同的但是独立的一份副本,然后开始执行程序。

6.4 Hello的execve过程

子进程创建完毕后,会去调用execve()来加载可执行文件到当前进程。然后删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。创建新的区域结构:通过虚拟内存机制将可执行文件hello中的各个段映射到对应的代码段、数据段等地址空间。然后通过跳转到hello程序的第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

进程上下文信息:当进程调度一个新的进程运行后,会使用上下文切换来将控制转移到新的进程。上下文切换会:1.保存当前进程的上下文。2.恢复某个先前被抢占进程的被保存的上下文。3.将控制传递给新进程。系统调用、中断可能引起上下文切换。

进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

进程调度的过程:在对进程进行调度的过程,操作系统主要做两件事:加载保存的寄存器,切换虚拟地址空间。

用户态与核心态转换:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中, 用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

具体过程如下:输入命令./hello 120L021923 甄自镜 ,然后调用sleep()函数进入内核模式,进行信号处理,然后返回用户模式,运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。

图6.5.1

6.6 hello的异常与信号处理

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

1.异常

异常和信号异常可以分为四类:中断、陷阱、故障、终止

中断:处理器外部I/O设备引起,异步异常,例如:时钟中断,键盘上敲击Ctrl-C.

陷阱:有意的异常,执行指令产生的结果,发生时间可预知,同步异常,例如:系统调用。

故障:不是有意的,但可能被修复,同步异常,例如:缺页故障,保护故障。

终止:非故意,不可恢复非致命错误造成,例如:非法指令,奇偶校验错误。

2.各种键盘操作导致的异常:

正常:

图6.6.1

回车

图6.6.2

Ctrl Z 默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下

图6.6.3

Ps

图6.6.4

Jobs

图6.6.5

Pstree

图6.6.6

Fg

图6.6.7

Kill 挂起的进程被终止,在ps中无法查到到其PID。

图6.6.8

Ctrl C

图6.6.9

PS:运行了两个hello,因此显示有两个

6.7本章小结

本章介绍了进程的概念和作用,以及壳Shell-bash的作用与处理流程,调用 fork 创建新进程,调用 execve函数执行<

以上是关于计算机系统大作业——程序人生的主要内容,如果未能解决你的问题,请参考以下文章

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

哈工大计算机系统大作业——程序人生

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

哈工大2022计算机系统大作业---程序人生

哈工大计算机系统2022年秋程序人生大作业

计算机系统大作业——程序人生