计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机类
学 号 1190200709
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
本论文以hello程序为研究对象,结合课堂学习的知识对该程序的生命周期进行探寻。在Ubuntu下进行操作,并借助gcc、objdump和edb等工具完成整个开发过程,在程序的生命过程中体会计算机系统各个组成部分发挥的作用,对于深刻认识理解计算机系统工作原理具有重要意义。
关键词:计算机系统;Ubunbu;预处理;编译;汇编;链接;进程管理;存储管理;IO管理;P2P;020
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
P2P(Program to Process):
由高级语言编写的hello.c文件(Program),在Linux系统下由编译器预处理得到预处理文件hello.i,然后编译,得到hello.s(汇编语言文件)。汇编器把.s文件翻译成机器语言,然后打包成可重定位的目标文件hello.o,最后由链接器将其与库函数链接,得到可执行文件hello。执行时操作系统产生子进程,然后execve加载进程。
020(Zero to Zero):
操作系统调用execve后映射虚拟内存,先删除当前虚拟地址的数据结构并未hello创建新的区域结构,进入程序入口后载入物理内存,再进入main函数执行代码。执行完成后,父进程回收hello进程,内核删除相关数据结构。
1.2 环境与工具
Intel(R)Core™i5-7300HQ CPU 2.50GHz 2.50GHz 8G RAM
Win10 64位
虚拟机VMware Workstation Pro12.0
Ubuntu16.4
gcc ld readelf gedit objdump edb hexedit
1.3 中间结果
hello.i:预处理生成的文本文件
hello.s:编译后得到的汇编语言文件
hello.o:汇编后得到的可重定位目标文件
hello:经过链接生成的可执行目标文件
1.4 本章小结
对hello程序P2P、020过程的整体概括,以及追踪这些具体步骤中借助的软硬件环境和得到的过程文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:预处理器会展开以#开头的命令(宏定义、条件编译、读取头文件),删除注释等来修改c程序生成.i文件。一般情况下这种处理是为了方便在不同的执行环境下修改或编译程序。
预处理的作用:
- 用实际值替换宏定义的字符串
- 读取系统头文件,将头文件中的代码插入到新程序中
- 根据if后面的条件决定需要编译的代码
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
原文件长度得到扩展,如图:
原代码只在扩展后的文件中占很小一部分。剩下的代码都是对宏定义的展开,以及引入的头文件的代码。
2.4 本章小结
本章主要介绍了预处理的概念和应用功能,Ubuntu下预处理指令,以及预处理文件.i的具体内容,对预处理过程和结果有比较直观的了解。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译的概念:编译程序就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。 同时将文本文件 hello.i 翻译成文本文件 hello.s。
编译的作用:进行词法分析、语法分析、目标代码的生成,检查无误后生成汇编语言。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1 数据
3.3.1.1 常量
语句和中的数字常量,都存放在.text节,观察.s文件.text节的指令可以得出结论。而字符串常量则存放在.rodata节,。
3.3.1.2 变量
全局变量存储在.data节,在汇编语句中没有体现。局部变量存储在栈/寄存器中。例如main函数内部定义的整型变量i。
在汇编指令中i存储的位置是-4(%rbp):
对应的代码这一行:
这是对i赋值的过程,可以看到对应的汇编语句采用了movl。
3.3.2 算数操作
hello.c中使用到算术运算符的地方只有一处:,对i执行++操作。可以查看一下.s文件中的汇编指令是这样的:,用了addl操作向i的地址中加1。
3.3.3 关系操作&控制转移
hello.c中这条语句用到了关系操作符。这一处转化成汇编代码如下:
cmpl完成关系操作,je则根据cmpl产生的条件码完成控制转移。类似的,循环控制语句中的处理方式也类似。
3.3.4 数组/指针/结构
main函数的两个参数中,*argv[]是一个指针数组。其中argv[0]指向存有程序名字符串的地址,argv[1]和argv[2]指向main函数的两个参数。
上面这一部分汇编代码中,%edi存放参数argc,%rsi存放参数argv[]。
3.3.5 函数操作
main():
参数传递:argc和argv[],上文已经提到过,分别传入%edi和%rsi两个寄存器。
函数调用:系统启动函数调用。
函数返回:返回值存储在寄存器%eax中,在返回前将其设置为0。
printf():
参数传递:
- 源程序的这一行,只传入了要打印的字符串的首地址,对应的指令为。
- 在后面的for循环的循环体中又一次调用了printf,但这一次传入了两个参数。对应的汇编指令如下:
sleep():
参数传递:传入参数atoi(argv[3])
函数调用:在for循环的循环体中被调用,对应的汇编指令:
getchar():
函数调用:被main调用
3.4 本章小结
本章主要介绍了编译的概念以及过程,以及c语言如何转换成为汇编代码。结合实例介绍了汇编代码中对c语言的数据与操作是如何实现的。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:驱动程序运行汇编器as,将汇编语言翻译成机器语言的过程称为汇编。
汇编的作用:把汇编指令转换成能够直接被机器执行的机器指令,并将这些指令打包成可重定位目标程序的形式。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
先得到hello.o的elf格式:
然后查看输出文件。
Elf头:
节头:这个部分主要是对上一节提到的汇编指令的各个节进行集中记录,包括每个节的名称、类型、地址和大小等详细信息。如下图:
重定位节:在这一节会展示各个段所要引入的外部符号,链接时需要依赖重定位节对这些地址进行修改。对于不同的重定位条目类型采用不同的寻址方式。重定位节的格式如下:
符号表:主要存储程序定义的全局变量和引用的函数信息。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
- 分支语句:
hello.s中跳转语句所用的地址是用段的名称代替的。而反汇编文件中,跳转语句所用的地址是将要跳转到的指令的间接地址。
- 函数调用:
hello.s中的call指令在调用其他函数时使用的是函数名,而反汇编文件里调用函数的语句用的是相对main函数的间接地址调用。
- 数字表示:
hello.s中立即数都是用十进制写法表示的,这与反汇编文件中采用的十六进制完全不同。
4.5 本章小结
本章介绍了将汇编语言转化为机器语言的过程。对汇编过后的文件hello.o分别分析它的elf格式文件和反汇编文件,对hello.o的内部结构有了比较直观的认识,也更深刻了解到其与hello.s阶段的不同。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接的概念:链接是将多个不同文件的代码和数据部分收集起来并组合成一个单一文件的过程。
链接的作用:令源程序节省空间而未编入的常用函数文件进行合并,生成可以正常工作的可执行文件。这令分离编译成为可能,节省了大量的工作空间,且有助于提升大型文件的编写效率。
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.3 可执行目标文件hello的格式
Elf头:
节头:展示各个节的大小、偏移量等。链接时链接器将这些文件的相同段合并,并且根据合并后的段的大小以及偏移量重新设置地址。
5.4 hello的虚拟地址空间
使用edb加载后可以看到,hello的虚拟地址从0x400000开始,到0x400ff0结束。
放大:
要找到各个节信息,可以对照5.3的节头部表依次查看,edb中的各节地址和表是对应的,下面截了几张:
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
hello与hello.o的不同:
- 链接完成后的文件加入了原代码里的库函数。
- hello中的.init和.plt节在hello.o中没有出现。
- hello.o中指令的地址采用相对偏移地址表示,但在链接后的hello中使用的是虚拟内存地址。
hello如何进行重定位:
因为hello.o中的某些命令地址并不固定,具体情况是由程序每一次运行时临时决定的,因此在这时的文件中才存在重定位条目。而hello用虚拟地址直接替换了相对偏移。
5.6 hello的执行流程
ld -2.27.so!_dl_start 7efb ff4d8ea0
ld-2.27.so!_dl_init 7efb ff4e7630
hello!_libc_start_main 004004c0
hello!printf@plt 004004c0
hello!puts@plt 004004a0
hello!sleep@plt 00400500
hello!getchar@plt 004004d0
hello!exit@plt 004004f0
libc-2.27.so!exit 7efb ff122120
edb调试截图:
5.7 Hello的动态链接分析
计算变量地址时牢记代码段和数据段相对位置不变。至于库函数,需要plt和got合作。plt跳转到got指向的位置,然后链接器会对got进行修改,等待下一次调用plt时,可以保证plt跳转到正确的位置。
edb执行init之前的地址:
edb在执行init之后的地址:
5.8 本章小结
本章主要介绍链接的过程,以及链接后生成的文件内容特点。对比了hello与hello.o的反汇编代码,从代码角度分析了链接时链接器是怎样完成重定位过程的。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程是一个针对执行中的应用程序的程序的实例。
进程的作用:进程给正在运行的程序一个独立的逻辑控制流,给人一种错觉:这个程序正独占着处理器。进程还提供给程序一个私有的地址空间,仿佛这个程序正独占内存系统。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell俗称壳,是一种指“为使用者提供操作界面”的嵌入式软件(也被称为命令解析器)。它是一个简单的命令解释器,允许系统接收到一个用户的命令,然后自动调用相应的命令执行应用程序。总而言之,shell程序起到了连接用户与操作系统和内核的桥梁作用。
处理流程:shell读取用户从终端使用外部设备输入(通常是键盘输入)的指令。然后解析所读取的指令,如果这个指令是一个内部指令则立即执行;如果不是,shell会加载调用一个应用程序为申请的程序创建新的子进程,在子进程的上下文中运行。同时shell还允许接收从键盘读入的外部信号,并根据不同信号的功能进行对应的处理。
6.3 Hello的fork进程创建过程
运行hello,按照提示输入指令,shell进行读取后判断输入指令是否为内部指令。当输入指令不是内部指令时, 调用hello应用程序并创建子进程。子进程会得到和父进程完全相同的数据结构副本,其与父进程最大的不同在于PID不同。
6.4 Hello的execve过程
execve函数加载并运行可执行目标文件Hello,且包含相对应的一个带参数的列表argv和环境变量列表envp。该函数的作用就是在当前进程的上下文中加载并运行一个新的程序。只有当出现错误时,例如找不到Hello时,execve才会返回到调用程序,否则不会返回。
execve会调用启动加载器代码,在当前进程(子进程)的上下文中加载新程序hello。这时hello会覆盖正在执行的进程的所有地址空间,继续使用原来进程的PID运行。严格意义上来说这并不是一次创建新进程的操作,但此时原来进程的堆栈都被重新初始化,代码和数据段也被重新加载,内容是新的可执行文件中的内容。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
进程的上下文信息:在内核重新启动一个先前被抢占的进程时,需要恢复该进程原来的状态,此时需要重新加载的信息就是进程的上下文信息。上下文由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。
进程时间片:指一个进程在执行控制流时候所处在的每一个时间段。
用户态与核心态的转换:在某个特定的控制寄存器中存在一个模式位,寄存器以此来限制程序可以执行的指令和可以访问的地址空间。初始情况下没有设置模式位前进程是以用户模式运行的。程序在用户模式下程序无权执行特权指令,也无权访问和使用内核区的代码和数据。设置模式位后进程进入内核模式运行,上述无权执行的指令以及无法访问的内存空间全部开放。完成上述模式切换必须使用诸如中断,故障或陷入系统调用这样的异常。当异常发生时调用异常处理程序,处理器从用户模式转到内核模式。
以hello程序为例,刚开始时进程运行在用户模式下。当调用sleep函数后,进程转入内核状态运行,执行倒计时。此时sleep抢占了内核。倒计时结束时发送一个中断信号,进程重新转回用户模式下运行。
6.6 hello的异常与信号处理
正常运行:
第一种异常:Ctrl-Z
Ctrl-Z会使进程挂起。进程挂起后可以通过ps指令查看hello的PID,如下图:
第二种异常:Ctrl-C
Ctrl-C会结束hello的运行并回收该进程。依然使用ps查看hello的PID,由于此时hello已被回收,所以并没有出现在列表中。
第三种:乱按键盘
对于单个的字符输入,进程并没有给出任何反馈。对于连续的乱码字符输入,后续会被当作命令识别,不过因为不存在这种命令所以会提示未找到命令。如下图:
Ctrl-Z后键入命令:
ps:查看进程PID
jobs:查看hello后台的job号
pstree:(部分截图)
fg:重新执行被挂起的进程
kill:杀死进程
6.7本章小结
本章主要描述了hello进程的执行过程,分析shell的工作过程和应对不同种信号的处理方式,以及一些具体的操作是怎样完成的,比如fork如何创建子进程,execve如何加载,sleep函数被调用时如何完成用户模式与内核模式的切换,等等。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:由选择符和偏移量组成,是汇编程序(hello.o)中的地址。
线性地址:由逻辑地址加上相应段的基地址,得到的就是线性地址。线性地址是逻辑地址到物理地址之间变换的桥梁。
虚拟地址:即逻辑地址。
物理地址:存在于CPU外部地址总线上的寻址物理内存的地址信号,对应于真实的物理内存。当没有采用分页机制时hello的线性地址即物理地址本身。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式内存管理方式是直接将逻辑地址转换成物理地址(即线性地址),当CPU不支持分页机制时采用这种管理方式。段式管理下地址的基本组成方式是段号+段内偏移地址。
逻辑地址到线性地址的转换过程:
1.看段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT(全局段描述符)中的段,还是LDT(局部段描述符表)中的段,再根据指定的相应的寄存器,得到其地址和大小,我们就有了一个数组了。
2.拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,这样就有了Base,即基地址就知道了。
3.把基地址Base+Offset,就是要转换的下一个阶段的物理地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
CPU的页式内存管理单元负责把一个线性地址转换为物理地址。从管理和效率的角度出发,线性地址被划分成固定长度单位的数组,称为页。例如,一个32位的机器,线性地址可以达到4G,用4KB为一个页来划分,这样,整个线性地址就被划分为一个2^20次方的的大数组,共有2的20次方个页,也就是1M个页,我们称之为页表,改页表中每一项存储的都是物理页的基地址。
系统将每个段分割为被称为虚拟页(VP)的大小固定的块来作为进行数据传输的单元,在linux下每个虚拟页大小为4KB,类似地,物理内存也被分割为物理页(PP/页帧),虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。而这在不同情况下可能花费不同的时间开销。为了尽可能消除不必要的时间开销,在MMU中存在一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。
多级页表:将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。
VA到PA变换:在四级页表支持下,VPN被分为四段,CR3寄存器对应着L1P1的物理地址。寻址时按照层级递推,将条目对应的区域逐级缩小,经过四级寻址后可以找到对应的PPN和VPO组合获得PA。
7.5 三级Cache支持下的物理内存访问
CPU发送一条虚拟地址,由MMU翻译获得物理地址PA。PA可以拆分为CT(标记位)CS(组号),CO(偏移量)。根据CS寻找到正确的组,然后开始匹配标记位CT。如果匹配成功且有效,则命中,直接返回想要的数据。如果不命中,就依次向下一级缓存查询数据。主存判断是否命中,当命中时,将数据传给CPU同时更新各级cache的cacheline(如果cache已满则要采用换入换出策略)。
7.6 hello进程fork时的内存映射
进程调用fork函数,此时会创建一个新进程,新进程拥有全新的PID和独立的虚拟内存空间和逻辑控制流,也拥有所有当前已打开的各类文件信息和页表的原始数据样本。两个进程中的每个页面都被标记为只读,并且每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
execve的执行过程:
- 删除已存在的用户区域;
- 映射私有区域;
- 映射共享区域;
- 设置程序计数器PC使其指向代码的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:当指令请求一个虚拟地址时,MMU查找页表,如果需要的物理地址不在主存中,即认为发生缺页故障。缺页发生后,系统会调用内核中的缺页处理程序,选择一个牺牲页。
大致处理流程如下图:
- CPU生成一个虚拟地址,并将它传送给MMU
- MMU生成PTE地址,并向高速缓存/内存发送请求
- 高速缓存/内存向MMU返回PTE
- PTE中的有效位是0,由MMU触发异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
- 缺页处理程序选出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
- 缺页处理程序页面调入新的页面,并更新内存中的PTE
- 返回到原来的进程,重新执行之前引起缺页的指令。但因为这次已经将缺失的虚拟页面加载到了物理内存中,所以这次不会发生缺页异常。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格:
1.显式分配器:要求应用显式的释放任何已分配的块。
2.隐式分配器:也叫做垃圾收集器,例如,诸如Lisp、ML、以及Java之类的高级语言就依赖垃圾收集来释放已分配的块。
空闲块合并:
合并的情况一共分为四种:前空后不空,前不空后空,前后都空,前后都不空。对于四种情况分别进行空闲块合并,我们只需要通过改变头部和脚部中的值就可以完成这一操作。
分离的空闲链表:
维护多个空闲链表,其中每个链表中的块有大致相等的大小。分配器维护着一个空闲链表数组,每个大小类一个空闲链表,按照大小的升序排列。当分配器需要一个大小为n的块时,它就搜索相应的空闲链表。如果不能找到合适的块与其匹配,它就搜索下一个链表,以此类推。
7.10本章小结
本章主要介绍了 hello 的存储器地址空间、段式管理和页式管理,以及在指定环境下完成 VA 到 PA 的变换过程、物理内存访问,fork和execve的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件,所有的I/O设备都被模型化为文件,甚至内核也被映射为文件。
设备管理:unix io接口,操作包括:打开和关闭文件、读取和写入文件以及改变当前文件的位置。
8.2 简述Unix IO接口及其函数
Unix接口:
打开文件:内核返回一个非负整数的文件描述符,在后续所有对此文件进行的操作中标识这个文件。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准出错(描述符为2)。头文件定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。
改变当前的文件位置,每一个打开的文件都有一个初始位置k(初始化为0),应用程序通过seek操作,可显式的设置文件的当前位置。
读写文件,读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k。
关闭文件:当应用完成对文件的访问后,通知内核关闭这个文件。内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中。
Unix函数:
(1)int open(const char *pathname,int flags,int perms),打开一个已经存在的文件或者创建一个新文件。
(2)int close(int fd),关闭一个打开的文件。
(3)ssize_t read(int fd, void *buf, size_t count),从当前文件位置复制字节到内存。
(4)ssize_t write(int fd, void *buf, size_t count),从内存复制字节到当前文件位置。
(5)off_t lseek(int fd, off_t offset,int whence),移动文件位置。
8.3 printf的实现分析
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;
}
注意va_list的定义:typedef char *va_list,这说明它是一个字符指针。发现printf内部调用了vsprintf,这个函数的功能是返回要打印的字符串的长度。后面执行了写操作,把buf中的i个元素的值写到终端。综合一下可以得知vsprintf的作用是格式化,它接受确定输出格式的格式字符串fmt,然后用格式字符串对个数变化的参数进行格式化,产生格式化输出。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数有一个int类型的返回值。当getchar调用read时,read函数把整个缓冲区都读到buf里面,然后返回缓冲区长度。注意只有当buf长度为0时,getchar才会调用read函数,否则直接将保存的buf中的首元素,然后返回用户输入的第一个字符的ascii码(如果出错,则返回-1)。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了 Linux 的 I/O 设备的管理方法,以及Unix I/O 接口及其函数。还分析了printf 函数和 getchar 函数的内部实现是怎样完成的。
(第8章1分)
结论
hello的整个生命周期中:
- hello.c经过预处理,拓展得到hello.i文本
- hello.i经编译得到汇编文件hello.s,由汇编代码描述
- hello.s汇编后得到二进制可重定位目标文件hello.o
- 最后hello.o经过链接器链接得到可执行文件hello。
在shell中输入指令开始运行hello。此时shell会调用fork和execve,创建新进程并在新进程的上下文中加载运行hello。hello的执行过程中产生各种类型的地址(逻辑地址/线性地址/物理地址等),调用各种类型的函数(printf/getchar等),在计算机系统的帮助下完成命令执行。最后shell回收hello,至此hello的生命周期就结束了。
计算机系统这门课使我对计算机系统的整体工作原理及各个部分的特点有了比较直观和整体的了解。老实说对于计算机系统,要了解和实践的内容远远超出了这门课时间内所能给予的内容,即使在课程中体会到如此庞大而复杂的知识,对于实际了解的计算机系统也仅仅是冰山一角。本人才疏学浅,在一个学期的时间里要融会贯通这些知识多少有些困难,因此在以后的学习中还会继续回顾已知和探索新知。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.i 预处理器进行拓展的源程序
hello.s 编译器生成的汇编语言程序
hello.o 汇编生成的可重定位目标文件
hello 可执行目标文件
hello.elf hello.o的elf格式
objhello.txt hello.o的反汇编文件
helloelf1.txt 可执行文件hello的elf格式
Helloobj1.txt 可执行文件hello的反汇编文件
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 《深入理解计算机系统》 Randal E.Bryant David R.O’Hallaron 机械工业出版社
[2] [转]printf 函数实现的深入剖析 博客园
[3] 内存管理——逻辑地址转换为线性地址https://www.cnblogs.com/diaohaiwei/p/5094959.html
[4] 《步步惊芯——软核处理器内部设计分析》 TLB的作用及工作过程
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CSDN博客 ELF可重定位目标文件格式
(参考文献0分,缺失 -1分)