计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业
学 号
班 级
学 生
指 导 教 师
计算机科学与技术学院
2022年5月
本文,从C语言程序hello的“一生”入手,从预编译,编译,汇编,链接,运行,结束。来加深对“计算机系统”课程的理解,在linux系统下演绎了程序从编写到运行的每一步计算机的操作,并在此过程中再次了解计算机各构件的相互连接和相互支持。
关键词:计算机系统,CSAPP,C语言,程序的生命周期,进程
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
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 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,O2O的整个过程。
P2P即program to process,从程序到进程。在Linux系统下,hello.c源程序经过cpp的预处理、ccl的编译、as的汇编以及ld的链接四个过程,最后成为可执行目标程序hello,在shell中输入启动命令,shell为其创建一个子进程,然后hello便从程序变成了进程
O2O:shell为此子进程execve,将其映射到虚拟内存,进入程序入口后程序开始载入物理内存,开始执行hello的程序,将其output的东西显示到屏幕,然后hello进程结束,shell回收内存空间。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64CPU; 8GHz; 8GRAM; 1TB HD
软件环境:Windows10 64位;VMware14.12; Ubuntu 16.04 LTS 64位
使用工具:codeblocks, objdump, gdb, edb, hexedit,gedit
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
名称 | 作用 |
Hello.i | Hello.c预处理得到的文件 |
Hello.s | Hello.i编译得到的汇编文件 |
Hello.o | Hello.s汇编后得到的可重定位文件 |
Hello | 链接后的可执行文件 |
Hello_objdump.txt | Hello.o的反汇编 |
Hello.elf | Hello.o的ELF格式 |
Hello_1.txt | Hello的反汇编 |
Hello.txt | Hello的ELF格式 |
1.4 本章小结
本章对hello的一生进行了简要的介绍和描述,介绍了P2P的整个过程,介绍了本计算机的硬件环境、软件环境、开发工具,介绍了为编写本论文的中间文件的名称和其作用。
第2章 预处理
2.1 预处理的概念与作用
预处理概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序
预处理作用:
- 处理条件编译指令,将#ifdef、#else等指令进行处理,把某部分代码包含进来或排除在外,将不必要的代码过滤掉。
- 处理#define #include等指令,将对应的头文件复制到源程序中,
- 处理一些特殊符号
2.2在Ubuntu下预处理的命令
通过使用gcc -E -o hello.i hello.c命令生成修改了的源程序hello.i
2.3 Hello的预处理结果解析
查看这个hello.i文件,发现由原来的18行变为3060行,但文件仍然为可以阅读的C语言程序文本文件,其中原来的C语言代码放在了最后,且在此基础之上还增加了许多其他文本内容
这最开始的部分,是hello.c拼接的各种库文件
中间有很多内部函数的声明
2.4 本章小结
本章介绍了Linux环境下对C语言程序进行预处理的命令,同时简要介绍了预处理的概念和作用,然后用简单的hello程序实际演示了从hello.c到hello.i的过程并结合具体代码对预处理结果进行了简单的分析。
第3章 编译
3.1 编译的概念与作用
编译的概念:编辑器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
编译的作用:把源程序翻译成目标程序,进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信息。
3.2 在Ubuntu下编译的命令
使用命令:gcc -S -o hello.s hello.i
3.3 Hello的编译结果解析
3.3.1数据
.file:声明源文件
.text:代码节
.section:
.rodata:只读代码段
.align:数据或者指令的地址对其方式
.string:声明一个字符串(.LC0,.LC1)
.global:声明全局变量,比如main
.type:声明一个符号是数据类型还是函数类型,比如main就是函数类型
- 常量
声明在.LC0和.LC1中
.LC0 声明的是“用法: Hello 学号 姓名 秒数!\n”
.LC1 声明的是“Hello %s %s\n”
可以看到这两个字符串都作为printf函数的第一个参数
- 局部变量
Main函数声明了一个int型的局部变量i,编译器在编译时会将局部变量i放在堆栈中,在此程序中被放入了-4(%rbp)中
- Main函数
Main函数的参数argc也被放入堆栈中
- 数组char *argv[]
char *argv[]是作为main函数的第二个参数,数组的每个元素都是一个指向字符型类型的指针,数组的起始地址放在-32(%rbp)中,两次取出这个地址,然后分别加上不同的偏移量就是argv[1]和argv[2],分别作为后边printf的第二个和第三个参数
- 立即数
立即数直接体现在汇编代码中,比如上图的$0
3.3.2赋值
赋值操作有将i的初始值赋值为0
3.3.3算术操作
hello.c中出现的算术操作是i++,对应汇编语言为
3.3.4关系操作
- argc!=4
判断argc(在-20(%rbp)中)是否不等于4i<8
判断i(在-4(%rbp)中)是否小于等于8,设置条件码,然后判断是否需要跳转
3.3.5类型转换
hello.c中涉及的类型转换是atoi(argv[3]),他将字符串转换成整数类型
3.3.6控制转移
判断argc(在-20(%rbp)中)是否不等于4,设置条件码,然后判断是否需要跳转
判断i(在-4(%rbp)中)是否小于等于8,设置条件码,然后判断是否需要跳转
3.3.7函数操作
- main函数
参数传递:argc和argv[]
函数调用:程序开始时调用
局部变量:int i
函数返回:return 0
- printf函数
参数传递:argv[1]和argv[2]的首地址
函数调用:if和for循环中
局部变量:无
函数返回:无
- sleep函数
参数传递:atoi(argv[3])
函数调用:无
局部变量:无
函数返回:无
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.4 本章小结
本章简要介绍了编译的概念和作用,介绍了编译器处理C语言程序的基本过程,函数从hello.i变为hello.s,编译器分别从C语言的数据,赋值,类型转换,算术操作,逻辑/操作,关系操作,数组/指针/结构操作,控制转移,函数操作这几个点进行分析,生成最终的hello.s文件。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编器(as)将hello.s汇编程序翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,将结果保存在目标文件hello.o中,这个过程称为汇编。
汇编的作用:将汇编代码转换为机器指令,使其在链接后能被机器识别并执行,因为机器语言是计算机能直接识别和执行的一种语言。
4.2 在Ubuntu下汇编的命令
使用命令:gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
使用命令:readelf -a hello.o > hello.elf
- ELF头:
ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统字的大小和字节顺序,剩下的部分包含包含帮助链接器语法分析和解释目标文件的信息,包括ELF头大小,目标文件类型,机器类型,版本,入口点地址,程序头起点,节头部表的文件偏移,以及节头部表中条目的大小和数量。
- 节头
记录各个节的名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等信息。
- 重定位节
.real.text: .text节中位置的列表,包含需要进行重定位的信息,当连接器把这个目标文件和其他文件组合时,需要对这些位置进行修改
.rela.eh_frame节是.eh_frame节重定位的信息
- 符号表
.symtab一个符号表,它存放在程序中定义和引用的函数和全局变量的信息
4.4 Hello.o的结果解析
使用命令:objdump -d -r hello.o > hello_objdump.txt
hello.s和hello_objdump.txt的差异:
- 分支转移:
hello.s:
hello_objdump.txt:
hello.s中使用的是段名称比如.L3,但是在hello_objdump.txt中用的是确定的地址。
2.立即数使用16进制的格式
hello.s:
hello_objdump.txt:
- 函数调用
hello.s:
hello_objdump.txt:
hello.s中的函数调用就是call后面跟着具体的函数名称,而hello_objdump.txt中的函数调用不再是函数的具体名称,而是一条重定位条目指引的信息
4.5 本章小结
本章对hello.s进行了汇编得到了hello.o可重定位目标文件,分析了可重定位文件的结构和各个组成部分,以及它们的内容和功能,还查看了hello.o的elf 格式,并使用objdump 得到反汇编代码与hello.s 进行比较,了解从汇编语言映射到机器语言汇编器需要实现的转换。
第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
/usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o hello.o
5.3 可执行目标文件hello的格式
- ELF头
- 节头
- 重定位节
- 符号表
- 程序头
通过查看edb的data dump区,发现hello虚拟地址起始于0x400000,对应前边5.3程序头load可加载的程序段的第一个地址0x400000,同时结束于0x400ff0。
根据前面的节头部表可以用edb查看各个节的信息。比如.init节,虚拟地址起始于0x401000,大小为0x1b
经过对hello和hello.o的比较分析,可以看出hello反汇编的代码有明确的虚拟地址,即完成了重定位,而hello.o中虚拟地址为0,未完成重定位。同时hello多了许多节比如.init,.plt,. start等等许多节,而且这些节中也有很多函数,库函数的代码都已经链接到了程序中,程序各个节变的更加完整
hello重定位的过程:
1.重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
2.重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
3.重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目,代码的重定位条目放在.rel.txt中
5.6 hello的执行流程
载入:_dl_start、dl_init
开始执行:_start、_libc_start_main
执行main:_main、_printf、_exit、_sleep、_getchar、_dl_runtime_resolve_xsave、_dl_fixup、_dl_lookup_symbol_x
退出:exit
5.7 Hello的动态链接分析
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,共享链接库代码是动态的目标模块,在程序开始运行或者调用程序加载时,可以自动加载该代码到任意的一个内存地址,并和一个在目标模块内存中的应用程序链接起来,这个过程就是对动态链接的重定
位过程。动态链接器采用延迟绑定的连接策略,利用PLT和GOT实现链接。
GOT表起始位置为0x404000
未调用dl_init之前0x404008后的16个字节全为0,之后产生数据
5.8 本章小结
本章主要介绍了链接的概念与作用,分析了可执行文件的ELF格式,hello的虚拟地址空间,重定位过程,执行过程,动态连接过程。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程的经典定义就是一个执行中程序的实例,更准确的定义是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
进程的作用:为用户提供一种假象: 我们的程序好像是系统中当前运行的唯一程序,我们的程序独占使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序的代码和数据好像是系统中内存唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash的作用:Shell是一个交互型的应用级程序,它代表用户运行其他程序,shell应用程序提供了一个界面,用户通过访问这个界面访问操作系统内核的服务。
处理流程:
从终端读入输入的命令
将输入字符串切分获得所有的参数
如果是内置的命令则立即执行
否则调用相应的程序执行
Shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
父进程通过调用fork函数创建一个新的运行的子进程,新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程完全相同但是相互独立的副本,包括代码段、段、数据段、共享库以及用户栈,子进程还获得与父进程任何打开文件描述符相同的副本,所以子进程可以读写父进程中打开的任何文件,父进程和子进程最大的区别是PID不同
在本篇中,我们在shell中输入./hello 2021112662 Zhu Helin 1时,shell会对我们的命令进行解析,判断是否为内置命令,我们输入的不是内置命令,所以shell会调用fork创建一个子进程,并让hello在子进程的上下文中运行。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个新程序,execve函数的原型如下图所示
execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp,只有出现错误才会返回到调用程序,argv和envp变量都指向一个以null结尾的指针数组,argv每个指针都指向一个参数字符串,envp每个指针都指向一个环境变量字符串。
execve具体执行过程是:
- 删除当前虚拟地址中已存在的用户区域
- 创建新的代码、数据、堆和栈段。
- 映射共享区域。
- 设置程序计数器(PC)。
6.5 Hello的进程执行
逻辑控制流:程序一系列的PC值叫做逻辑控制流
上下文切换:内核为每个进程维持一个上下文,它由一些对象的值组成,包括通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内 核数据结构等对象,当内核选择一个新的进程运行时,我们就说内核调度了这个进程,然后通过上下文切换的机制来将控制转移到新的进程
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式:用户模式中的进程不允许执行特权指令,内核模式中的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置
当我们在shell中输入./hello 2021112662 Zhuhelin 1时,程序开始运行,在调用进程发送sleep之前,hello在用户模式下运行,在运行过程中,内核会不断进行山下文切换,也就是hello程序和sleep程序不断抢占内核,如果在运行过程中收到了信号,那么就会陷入到内核中进入内核模式运行信号处理程序,之后再进行返回。
6.6 hello的异常与信号处理
异常的种类:
处理方法:
中断:
陷阱:
故障:
终止:
信号种类:
执行过程中输入:
回车:
在运行时输入回车会多输出几个空行,并正常结束
Ctrl-C:
按下Ctrl-C时,SIGINT信号会发送到前台进程组的每个进程,默认情况是终止前台作业,所以shell会结束hello进程并返回
Ctrl-Z:
输入Ctrl-Z程序会停止运行
- 输入ps和jobs命令:
ps命令可以查看hello进程并没有被回收,jobs命令可以查看hello的job代号为1
- pstree命令
将所有进程以树状图显示
- fg命令:
通过jobs命令查看hello的job编号,然后使用fg %1命令使hello进程从挂起处继续运行,打印出剩下的语句,程序可以正常结束
- kill命令
使用kill -9 %1命令杀死了后台挂起的hello进程
- 乱输入
程序运行时乱按键盘会输出到屏幕中,最后程序也能正常结束
6.7本章小结
本章介绍了进程的概念和作用,介绍了shell-bash的运行原理,分析了fork、execve等函数的执行过程,以及hello进程执行过程,并且分析了hello进程在运行时遇到的异常与信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
(以下格式自行编排,编辑时删除)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
(以下格式自行编排,编辑时删除)
7.3 Hello的线性地址到物理地址的变换-页式管理
(以下格式自行编排,编辑时删除)
7.4 TLB与四级页表支持下的VA到PA的变换
(以下格式自行编排,编辑时删除)
7.5 三级Cache支持下的物理内存访问
(以下格式自行编排,编辑时删除)
7.6 hello进程fork时的内存映射
(以下格式自行编排,编辑时删除)
7.7 hello进程execve时的内存映射
(以下格式自行编排,编辑时删除)
7.8 缺页故障与缺页中断处理
(以下格式自行编排,编辑时删除)
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
(以下格式自行编排,编辑时删除)
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
(第8章1分)
结论
- 预处理:将hello.c中include的所有外部头文件内容插入程序文本中生成hello.i
- 编译:将hello.i文件进行翻译生成汇编语言文件hello.s
- 汇编:将hello.s翻译成一个可重定位目标文件hello.o
- 链接:利用连接器,将hello的程序编码与动态链接库等收集整理成为一个单一文件,生成完全链接的可执行目标文件hello
- 加载运行:在shell中输入./hello 2021112662 朱贺林 1,终端为其fork新建进程,并通过execve把代码和数据加载入虚拟内存空间,程序开始执行。
- 执行指令:在该进程被调度时,CPU为hello其分配时间片,在一个时间片中,hello享有CPU全部资源,PC寄存器一步一步地更新,CPU不断地取指,顺序执行自己的控制逻辑流。
- 访存:内存管理单元MMU将逻辑地址,一步步映射成物理地址,进而通过三级高速缓存系统访问物理内存/磁盘中的数据。
- 动态申请内存:printf 会向动态内存分配器申请堆中的内存。
- 信号处理:若程序运行时遇到ctrl-c或者ctrl-z要执行相应的信号处理程序
- 终止并回收:Shell父进程等待并回收hello子进程,内核删除为hello进程创建的所有数据结构。
深切感悟:
学习了计算机系统这门课,让我收获最大的就是深入理解了计算机系统,理解了计算机内部的构成,理解了数据在计算机内部的存储方式,理解了汇编语言、流水线、虚拟存储器、程序有花的方法、链接、信号与异常等知识,对计算机有了个更具体的认识,终于明白计算机是如何构建起来的。这门课程让我认识到,我要想成为优秀的程序员、工程师,我们不应该只盯着顶层的实现,而忽视底层的逻辑。对于计算机底层架构的了解同样十分重要!
附件
名称 | 作用 |
Hello.i | Hello.c预处理得到的文件 |
Hello.s | Hello.i编译得到的汇编文件 |
Hello.o | Hello.s汇编后得到的可重定位文件 |
Hello | 链接后的可执行文件 |
Hello_objdump.txt | Hello.o的反汇编 |
Hello.elf | Hello.o的ELF格式 |
Hello_1.txt | Hello的反汇编 |
Hello.txt | Hello的ELF格式 |
参考文献
[1]《深入理解计算机系统》
[2] CSDN 《C语言之链接知识》DingUXiu
[3] CSDN 《什么是重定位?为什么需要重定位?》旋涡小林
[4] CSDN 《浅谈GOT表与PLT表》xdesk