计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 未来技术学院
学 号 2022112017
班 级 22WL023
学 生 杨绍鹏
指 导 教 师 刘宏伟
计算机科学与技术学院
2024年5月
本文探讨了hello程序的整个生命周期。从hello.c开始,它先经过预处理变为hello.i,然后通过编译生成hello.s,再通过汇编生成hello.o,最后通过链接生成hello可执行文件。然后hello文件开始运行,经历进程调用、虚拟内存提供、进程回收和信号处理等过程完成hello程序的一生。
关键词:预处理,编译,汇编,链接,进程,存储
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述................................................... - 4 -
1.1 Hello简介............................................ - 4 -
1.2 环境与工具........................................... - 4 -
1.3 中间结果............................................... - 4 -
1.4 本章小结............................................... - 4 -
第2章 预处理............................................... - 5 -
2.1 预处理的概念与作用........................... - 5 -
2.2在Ubuntu下预处理的命令................ - 5 -
2.3 Hello的预处理结果解析.................... - 5 -
2.4 本章小结............................................... - 5 -
第3章 编译................................................... - 6 -
3.1 编译的概念与作用............................... - 6 -
3.2 在Ubuntu下编译的命令.................... - 6 -
3.3 Hello的编译结果解析........................ - 6 -
3.4 本章小结............................................... - 6 -
第4章 汇编................................................... - 7 -
4.1 汇编的概念与作用............................... - 7 -
4.2 在Ubuntu下汇编的命令.................... - 7 -
4.3 可重定位目标elf格式........................ - 7 -
4.4 Hello.o的结果解析............................. - 7 -
4.5 本章小结............................................... - 7 -
第5章 链接................................................... - 8 -
5.1 链接的概念与作用............................... - 8 -
5.2 在Ubuntu下链接的命令.................... - 8 -
5.3 可执行目标文件hello的格式........... - 8 -
5.4 hello的虚拟地址空间......................... - 8 -
5.5 链接的重定位过程分析....................... - 8 -
5.6 hello的执行流程................................. - 8 -
5.7 Hello的动态链接分析........................ - 8 -
5.8 本章小结............................................... - 9 -
第6章 hello进程管理.......................... - 10 -
6.1 进程的概念与作用............................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程.. - 10 -
6.3 Hello的fork进程创建过程............ - 10 -
6.4 Hello的execve过程........................ - 10 -
6.5 Hello的进程执行.............................. - 10 -
6.6 hello的异常与信号处理................... - 10 -
6.7本章小结.............................................. - 10 -
第7章 hello的存储管理...................... - 11 -
7.1 hello的存储器地址空间................... - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................................ - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理........................................................ - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换................................................................ - 11 -
7.6 hello进程fork时的内存映射......... - 11 -
7.7 hello进程execve时的内存映射..... - 11 -
7.8 缺页故障与缺页中断处理................. - 11 -
7.9动态存储分配管理.............................. - 11 -
7.10本章小结............................................ - 12 -
第8章 hello的IO管理....................... - 13 -
8.1 Linux的IO设备管理方法................. - 13 -
8.2 简述Unix IO接口及其函数.............. - 13 -
8.3 printf的实现分析.............................. - 13 -
8.4 getchar的实现分析.......................... - 13 -
8.5本章小结.............................................. - 13 -
结论............................................................... - 14 -
附件............................................................... - 15 -
参考文献....................................................... - 16 -
第1章 概述
1.1 Hello简介
P2P: 全称为From Program to Progress。首先在文本编辑器上写下代码,存成hello.c文本文件,然后,调用编译软件将写好的hello.c经过预处理生成hello.i,再通过编译器编译形成hello.s,最后通过汇编形成hello.o。之后链接器将hello.o与系统里面的库进行链接,最后生成可以运行的可执行文件。
020:全称为From 0 to 0。操作系统进行存储管理、地址翻译、内存访问,按需页面调度来开始这段生命。父进程或祖先进程的回收也标志着它生命的结束。
1.2 环境与工具
硬件环境:Intel x64 CPU 2.50GHz
软件环境:Windows 11 64位; VirtualBox; Ubuntu 22.04 LTS 64位
调试工具:编辑器VS Code、反汇编工具EDB、编译环境GCC
1.3 中间结果
文件名 | 作用 |
Hello.c | 源代码 |
Hello.i | 经过预处理后得到的文本文件 |
Hello.s | 经过编译后得到的汇编文本文件 |
Hello.o | 经过汇编后得到的可重定位目标文件 |
Helloo.s | Hello.o的反汇编文件 |
Helloo.elf | Hello.o的elf文件 |
Hello | 可执行文件(二进制格式) |
Hello.elf | 可执行文件的elf文件 |
Hello1.s | 可执行文件的反汇编文件 |
表1 中间结果文件
1.4 本章小结
本章主要介绍了hello.c P2P和020的过程。列出了本次实验所需的环境和工具以及过程中所生成的中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
1.概念:预处理指编译器将.c文本文件改写成更加规范的文本文件,其主要操作包括头文件的引入、宏定义的替换、删除注释等。
2.作用:使文本文件更加完善,有利于后续的操作。
2.2在Ubuntu下预处理的命令
图1 预处理命令
2.3 Hello的预处理结果解析
图2 hello.i的部分内容
可见.i文件相比于.c源文件多了很多的内容,将头文件中的内容包含到这个文件中,对源文件中定义的宏进行了展开,同时注释也被删除。
2.4 本章小结
本章介绍了预处理的概念和作用,并对代码进行了预处理,分析了代码预处理的结果。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
1.概念:编译器将.i文件翻译成一个后缀为.s的汇编语言文件,即从高级程序设计语言到机器能理解的汇编语言的转换过程。
2.作用:将高级编程语言转换为机器能够识别的低级语言,使机器和人都能识别。
3.2 在Ubuntu下编译的命令
图3 编译命令
3.3 Hello的编译结果解析
图4 hello.s文件
3.3.1数据
(1)常量
cmpl $5, -20(%rbp)
movl $0, -4(%rbp)
if或for语句中的立即数的值直接保存在.text中。
.section .rodata
printf中字符串类型的常量的值保存在.rodata中。
(2)变量
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
变量argc与*argv[]保存在栈中。
3.3.2算术操作
addl $1, -4(%rbp)
每次循环结束后,i加1。
3.3.3关系操作
cmpl $5, -20(%rbp)
使用cmp比较两个值的大小关系,将结果放到条件码寄存器中。
3.3.4控制转移
cmpl $5, -20(%rbp)
je .L2
je用于判断cmpl比较的结果,若两个操作数的值不相等则跳转到指定地址。
3.3.5数组/指针/结构操作
movq -32(%rbp), %rax
addq $24, %rax
movq (%rax), %rcx
movq -32(%rbp), %rax
addq $16, %rax
由上文知*argv[]存储在%rbp-32。
3.3.6函数操作
call puts@PLT
call exit@PLT
call printf@PLT
call atoi@PLT
call sleep@PLT
call getchar@PLT
call指令用来进行函数的调用。它会先将函数的返回地址压入运行时栈中,之后跳转到相应的函数代码段进行执行。执行结束通过ret指令返回。
3.4 本章小结
本章介绍了编译的概念和作用,并对预处理后的代码进行了编译,分析了编译的结果。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
1.概念:编译器将低级的汇编语言进一步改写,转化为机器可直接识别的机器语言,即二进制代码。
2.作用:完成从汇编语言文件到可重定位目标文件的转化。
4.2 在Ubuntu下汇编的命令
图5 汇编命令
4.3 可重定位目标elf格式
4.3.1ELF头
图6 ELF头
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。剩下的部分包含帮助链接器语法分析和解释目标文件的信息。包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。
4.3.2节头
图7 节头
节头表中记录了每个节的各种信息,如大小,地址,偏移量等。
名称 | 存储的信息 |
.text | 已编译程序的机器代码 |
.data | 已初始化全局或静态C变量 |
.bss | 未初始化的全局和静态C变量 |
.rodata | 只读数据:如printf中的字符串,switch中的跳转表 |
.symtab | 符号表,存放程序中定义和引用的函数和全局变量的信息 |
.strtab | 一个字符串表 |
表2 节头表不同节的存储信息(部分)
4.3.3符号表
图8 符号表
4.3.4可重定位段信息
图9 可重定位段信息
4.4 Hello.o的结果解析
图10 hello.o的反汇编结果
该反汇编文件中的汇编代码与hello.s汇编文件的汇编代码是一样的,只是多了机器语言。每一条机器语言都对应着一条汇编代码。
机器代码与汇编代码不同的地方在于:
- 分支跳转:汇编语言中的分支跳转语句使用的是标识符来决定跳转到哪里,而机器语言直接使用对应的地址来实现跳转。
- 函数调用:汇编语言中函数调用直接写函数名,而反汇编文件中写的是相对main函数的偏移地址,同时在反汇编代码中调用函数的操作数都为0,这是因为在链接后才会生成确定的地址,故先用0代替。
4.5 本章小结
本章介绍了汇编的概念和作用,生成了可重定位文件hello.o,并进行了hello.o反汇编代码与hello.s的比较。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
1.概念:链接是将各种不同文件的代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。
2.作用:把若干目标文件合并成为一个可执行目标文件。
5.2 在Ubuntu下链接的命令
图11 生成hello文件
5.3 可执行目标文件hello的格式
5.3.1ELF头
图12 ELF头
文件的类型从REL变成了EXEC,节头部数量变为27个。
5.3.2节头
图13 节头
节头部表对hello中信息进行了声明,包括了大小、偏移量、起始地址等信息。
5.3.3符号表
图14 符号表
可以看到链接之后符号表的符号数量增加了很多,说明经过连接之后引入了许多其他库函数的符号,都加入到了符号表中。
5.3.4可重定位段信息
图15 可重定位段信息
5.4 hello的虚拟地址空间
图16 hello起止虚拟地址
hello虚拟地址空间的起始地址为0x401000,结束地址为0x401ff0。
图17 0x401000在elf中对应.init
5.5 链接的重定位过程分析
图18 hello可执行文件的反汇编结果
- 与hello.o相比,hello的反汇编代码每行指令都有唯一的虚拟地址,因为其已经完成了重定位,每条指令的地址已确定。
- 增加了很多函数代码,因为链接时将共享库中hello.c用到的函数加入到了可执行文件中。
5.6 hello的执行流程
图19 运行hello
程序名称 | 程序地址 |
_start | 0000000000001100 |
deregister_tm_clones | 0000000000001130 |
register_tm_clones | 0000000000001160 |
__do_global_dtors_aux | 00000000000011a0 |
frame_dummy | 00000000000011e0 |
main | 00000000000011e9 |
_fini | 000000000000128c |
_init | 0000000000001000 |
.plt | 0000000000001020 |
__cxa_finalize@plt | 0000000000001090 |
puts@plt | 00000000000010a0 |
printf@plt | 00000000000010b0 |
getchar@plt | 00000000000010c0 |
atoi@plt | 00000000000010d0 |
exit@plt | 00000000000010e0 |
sleep@plt | 00000000000010f0 |
表3 hello的执行顺序
5.7 Hello的动态链接分析
图20 动态链接之前
图21 动态链接之后
可以看到动态链接前后0x404000不同,动态链接之前存的是INIT的虚拟地址,而动态链接后变成了实际地址。
5.8 本章小结
本章主要介绍了连接的过程,解释了程序进行重定位的操作。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
1.概念:进程是指计算机中已运行的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
2.作用:在运行一个进程时,我们的这个程序好像是系统当中唯一一个运行的程序,这使得计算机的硬件资源能够得到更好的使用。
6.2 简述壳Shell-bash的作用与处理流程
1.作用:shell是一个命令解释器,能够读取用户从键盘发送的指令,并完成识别,将对应的系统信号发送给进程。
2.处理流程:读入命令,若是内置命令则立即执行,否则调用相应的程序执行。
6.3 Hello的fork进程创建过程
程序运行时,Shell就会调用fork()创建一个新的进程,子进程中fork返回0,在父进程中fork返回子进程的Pid。子进程与父进程虚拟地址空间相同但是独立,并拥有与父进程不同的Pid。
6.4 Hello的execve过程
在当前进程中加载并执行一个新调用execve时,操作系统会用指定的可执行文件替换当前进程的地址空间,包括代码、数据和堆栈等。打开指定的可执行文件,加载其代码和数据到内存,设置新程序的堆栈和参数,最终将控制权转交给新程序。
6.5 Hello的进程执行
6.5.1进程上下文信息
当操作系统切换到另一个进程之前,它需要保存当前进程的上下文信息,以便稍后能够恢复到该进程的执行状态。
6.5.2进程时间片
进程时间片是指每个进程在系统中分配到的执行时间。操作系统切换进程,即时间片轮转,可以确保每一个进程都有机会执行。
6.5.3进程调度
进程调度可以让某些进程挂起,某些进程执行,保证多个进程同时进行。
6.5.4用户态与核心态
从Shell执行hello程序时,会先处于用户模式运行。在运行过程中,由内核不断进行上下文切换。如果在运行过程中收到了信号,那么就会陷入到内核中进入内核模式运行信号处理程序,之后再进行返回。
6.6 hello的异常与信号处理
6.6.1异常
图22 异常
图23 中断处理
图24 陷阱处理
图25 故障处理
图26 终止处理
6.6.2信号
图27 Ctrl-z
当hello在运行时我们在键盘上按下Ctrl-z,这个操作就会向进程发送SIGSTP信号,进入信号处理程序。可以从后台工作信息看到hello被暂时挂起,进程号为1。
图28 ps
图29 jobs
图30 pstree
图31 fg
程序回到前台继续运行。
图32 kill
发送SIGTERM,终止信号。
6.7本章小结
本章介绍了异常与信号处理,列举了常见的异常以及信号。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
1.逻辑地址:访内指令给出的地址或操作数,即相对地址,经过寻址方式的变换才能得到内存储器中的实际有效地址,即物理地址。
2.线性地址:是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
3.虚拟地址:CPU启动保护模式后,程序运行在虚拟地址空间中。保护模式下,hello 运行在虚拟地址空间中,它访问存储器所用的逻辑地址。
4.物理地址:它是在地址总线上,以电子形式存在的,使得数据总线可以访问 主存的某个特定存储单元的内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
内存地址由两个部分组成:段选择符和偏移量。逻辑地址通过这两个部分经过一系列的变换得到线性地址。变换过程如下:
(1)使用段选择符中的偏移值在GDT或LDT表中定位相应的段描述符。
(2)利用段选择符检验段的访问权限和范围,以确保该段可访问。
(3)把段描述符中取到的段基地址加到偏移量上,最后形成一个线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
图33 页式管理
虚拟地址可被分为两个部分:VPN(虚拟页号)和VPO(虚拟页偏移量)。线性地址的高位部分经过页表的查找得到相应的页表项,当页表项有效位为 1 时,操作系统或硬件使用该页表项进行虚拟地址到物理地址的映射。页表项中包含物理页框号以及一些控制信息。将页表项中的物理页框号与线性地址的低位部分(偏移量)相加,得到最终的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
图34 PTE寻页
TLB:翻译后备缓冲器。页表中的线性地址可以分为虚拟页号(VPN)和虚拟页偏移(VPO)两部分。然后VPN在查找过程中又可以被拆分为两部分,一部分是TLB标记(TLBT),另一部分是TLB索引(TLBI)。这两部分可以类似于cache查找的方式在TLB缓存中找到对应的物理页号,最后的物理页号(PPN)加上VPO得到的新地址就是最后生成的物理地址。
7.5 三级Cache支持下的物理内存访问
图35 TLB cache地址翻译
Cache中物理地址可以被分为三部分,一部分是CT(标记),标记对应cache中的组数;CI(索引),索引用来查找某组中对应的行号;最后是CO(偏移量),在对应行号中根据偏移量就能找到对应数据在cache中存放的位置。当有多级cache时,首先从L1开始查找,如果不命中将会访问L2,进行一个块的替换,如果还不命中就L3,直到访问内存。
7.6 hello进程fork时的内存映射
当用fork函数创建一个子进程时,子进程会得到一个与父进程一样的页表副本,数据栈,以及虚拟地址。
7.7 hello进程execve时的内存映射
execve调用hello时,需要为hello在对应地址开辟新的空间。我们知道在计算机中就是一个数据覆盖的过程。首先需要覆盖对应的用户区域,刷新数据栈,使用mmap函数为hello映射一块新的空间。
7.8 缺页故障与缺页中断处理
图36 缺页故障
缺页故障与缺页中断都将调用缺页异常处理子程序进行处理。
通常情况下,缺页异常都是由系统在内存找不到对应地址造成的。MMU会访问所需地址的页表,检测其有效位,同时在内存中选择一个牺牲页,进行一个牺牲替换。从而将所需地址内容传入内存,然而存在一些特殊情况:
段错误:
虚拟地址中对应的所有区域结构都不对应,说明发生了段错误。
非法访问:
所需地址的数据不能被访问。
7.9动态存储分配管理
动态内存分配器维护着一个称为堆的进程的虚拟内存区域。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放可以由应用程序显式执行或内存分配器自身隐式执行。
分配器有两种基本风格,显式分配器和隐式分配器。显示分配器要求应用显式地释放任何已分配的块;隐式分配器要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。
7.10本章小结
本章主要介绍了hello 的存储器地址空间、段式管理、页式管理, VA 到PA 的变换、物理内存访问,fork、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理的相关内容。
(第7章 2分)
结论
总结hello所经历的过程:
- 预处理:对hello.c进行头文件包含、宏替换和删除注释等操作。预处理器将生成一个经过处理的文本文件hello.i。
- 编译:编译器将预处理后的文件转换为汇编代码hello.s。
- 汇编:将汇编代码翻译成机器语言,并生成可重定位目标文件hello.o。
- 链接:链接器将hello的程序编码与动态链接库等收集整理成为一个单一文件,生成完全链接的可执行的目标文件hello。
- 运行:fork()新建进程,并通过execve()把代码和数据加载入虚拟内存空间,程序开始执行。
- 信号处理:允许程序在运行时对外部事件作出响应。
- 终止和回收:当进程执行结束后,由父进程对子进程进行回收。至此,Hello的一生结束。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
Hello.c | 源代码 |
Hello.i | 经过预处理后得到的文本文件 |
Hello.s | 经过编译后得到的汇编文本文件 |
Hello.o | 经过汇编后得到的可重定位目标文件 |
hello | 可执行文件(二进制格式) |
Helloo.s | Hello.o的反汇编文件 |
Hello1.s | 可执行文件的反汇编文件 |
(附件0分,缺失 -1分)