计算机系统
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机与科学技术
学 号 2021110669
班 级 2103101
学 生 陈欣然
指 导 教 师 刘宏伟
计算机科学与技术学院
2022年5月
本文将漫游hello程序的一生,即hello是如何生成,运行及最终被回收的。我们将详细地介绍hello.c的预处理,编译,汇编,链接过程,以及lunix下的进程管理,进而深入了解计算机系统的奥秘。
关键词:hello;预处理;编译;汇编;链接;进程管理;lunix
目 录
第1章 概述
1.1 Hello简介
P2P:From Program to Process
hello.c最初是通过编译器输入的一段程序代码,即Program。此时hello.c是一个文本文件。然后,hello.c通过预处理(cpp)、编译(ccl)、汇编(as)、链接(ld)最终成为可执目标程序hello。当我们在shell中输入./hello后,内核调用fork()函数产生子进程,并且在子进程中调用execeve,执行hello,并且分给它时间片,它就成为了一个进程(Process)。
020:From Zero-0 to Zero-0
- from 0
最开始,内存中没有hello文件,这便是“From 0”。但是程序员编写了hello.c文件,创造了hello.c。
- to 0
hello.c经过预处理、编译、的汇编、链接的流程,生成了可执目标程序hello。通过在Shell下输入命令,内核调用execve函数,系统会将hello文件载入内存,执行代码,当程序运行结束后, hello进程被回收,并由内核删除hello相关数据,这即为“to 0”。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk
软件环境:Windows10;Vmware 11;Ubuntu 16.04 LTS 64位
调试工具:gcc,vim,edb,gdbhello.c:源程序(文本文件)
1.3 中间结果
hello.c:源程序(文本文件)
hello.i:预处理后的文本文件(文本文件)
hello.s:编译后汇编程序文本文件(文本文件)
hello.o:汇编后的可重定位目标程序(二进制文件)
hello:链接后的可执行目标文件(二进制文件)
1.4 本章小结
本章主要介绍了hello的p2p和020的过程和程序执行时的中间结果。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:
预处理(预编译),是预处理器(cpp)以字符#开头的命令,修改原始的C程序,最终生成.i文件的过程。
预处理的作用:
预处理的主要作用是将头文件中的内容插入(“include”格式包含的文件)到源文件中和根据宏定义在源程序中进行替换(用“#define”定义的字符串)和删除(注释和空白字符),根据“#if”后面的条件决定需要编译的部分。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
用vscode查看hello.i的信息,发现在hello.i文件中把include格式包含的文件复制到了main函数之前,并且注释内容已经被删除。
此为hello.c文件:
此为hello.i文件:
2.4 本章小结
本章介绍了预处理阶段的概念和作用,在Ubuntu下对hello.c程序进行了预处理,并且解释了预处理的结果,探究了预处理阶段的机制。
第3章 编译
3.1 编译的概念与作用
编译的概念:编译器(ccl)将预处理后的文本文件.i文件(hello.i)翻译成文本文件.s文件(hello.s)。
编译的作用:把高级语言程序翻译成便于机器理解的低级机器指令。同时,在编译时,一些操作可以被简化。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
生成的hello.s文件如下:
结果解析:
- 数据:
全局变量:
这两个全局变量分别是字符串"用法: Hello 学号 姓名 秒数!\n"和"Hello %s %s\n"
argc:存储在-20(%rbp)中
argv[]:地址在-32(%rbp)。
i:放在-4(%rbp)中(堆栈里)在每次循环中更新。
- 赋值:
hello.c中的i=0,对应于hello.s中的部分是:
利用movl,把0移动到i处。
- 算数操作:
hello.c中的i++,对应于hello.s的部分是:
利用addl,实现i的自增。
- 关系操作:
hello.c中的argc!=4的判断对应于hello.s的部分是:
如果相等,就跳转到L2
hello.c中的i<9,对应于hello.s的部分是:
如果小于等于8,就跳转到L,即:如果大于9,就退出循环。
- 数组/指针/结构操作:
argc[]通过首地址+偏移量访问
此处addq $16,%rax是argc[2]
addq $8,%rax是argc[1]
addq $24,%rax是argc[3]
- 控制转移:
if(argc!=4)对应hello.s中的
如果argc不等于4,就跳转到L2
for(i=0;i<9;i++)对应hello.s中的
使用cmpl进行判断,如果i小于等于8(i<9),用jle跳转,否则,退出循环。
- 函数操作:
7.1main函数
参数传递:
参数argc存储在%rdi;argv[]存储在%rsi。
函数返回:
return 0对应着.s文件中
将存储返回值的寄存器%eax赋值为0,然后再返回。
7.2 printf("用法: Hello 学号 姓名 秒数!\n");(printf("Hello %s %s\n",argv[1],argv[2]);类似)
参数传递:把.LCO的地址传递到%rdi
函数调用:使用call指令。
7.3exit(1)
参数传递:把1通过movl指令传递给%edi
函数调用:使用call指令。
7.4sleep(atoi(argv[3]));
参数传递:将atoi的返回值传入%rdi中
函数调用:使用call指令。
7.5atoi(argv[3])
参数传递:将%rax中的值传入%rdi中
函数调用:使用call指令。
函数返回:返回值被传入sleep函数中
7.6 getchar();
函数调用:使用call指令。
3.4 本章小结
本章介绍了编译的概念与作用。我们将hello.i编译成hello.s,并且对其进行数据、赋值、类型转换(隐式或显式) 、sizeof、算术操作、逻辑/位操作、关系操作、控制转移、函数操作等方面的分析,详细地了解了编译的过程。
第4章 汇编
4.1 汇编的概念与作用
(1)概念:汇编是汇编器(as)将.s文件(文本文件)翻译成.o文件(二进制文件)。
(2)作用:汇编将汇编代码翻译成机器可以理解并且执行的语言。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
gcc hello.s -c -o hello.o
4.3 可重定位目标elf格式
指令:readelf -a hello.o >helloref.txt
得到的helloref.txt如下:
分析:
ELF头:
节头:
程序头:无
重定位节:
4.4 Hello.o的结果解析
指令:objdump -d -r hello.o
区别1:
数据:十进制数->16进制数。
区别2:
函数调用:hello.s仅仅是使用call指令,call +函数名。hello.s的符号用于重定位的部分信息被放到了表里面。
区别3:
全局变量:hello.s是将全局变量值直接赋给寄存器(使用$.LC0、$.LC1给%edi赋值)但是在反汇编代码中,将可重定位文件重定位之后计算出全局变量的地址,赋值给寄存器。
区别4:
分支转移:hello.s中的跳转指令跳转到代码中的某一个部分。而反汇编中的跳转指令是直接跳转到具体的地址地址。
4.5 本章小结
本章中,我们对hello.s进行了汇编得到hello.o文件,hello.o的ELF可重定位目标文件hello_o_elf.txt,和反汇编hello.o得到hello_o.objdump文件。通过对hello_o_elf.txt和对hello_o.objdump的分析,并且将hello_o.objdump与hello.s对比分析,深入地探究了汇编的过程。
第5章 链接
5.1 链接的概念与作用
链接是指将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件,也就是从.o文件到可执行文件的过程。目标文件是包括机器码和链接器可用信息的程序模块。
(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
5.3 可执行目标文件hello的格式
命令:readelf -a hello > hello1.elf
ELF头:
节头:
程序头:
Dynamic section:在偏移量为0x2e50的位置,包含21个条目。它是可执行文件新增的内容。
重定位节:可执行文件依然存在重定位节,原因是动态链接时还会需要重定位信息。
符号表:符号表有51个条目。
5.4 hello的虚拟地址空间
程序在0x400000~0x401000段中载入。并且从0x400000开始,顺序与hello的elf格式文件顺序相同。
5.5 链接的重定位过程分析
指令:objdump -d -r hello
hello与hello.o对比来看基本相同。区别是hello是CPU直接访问的虚拟地址,而hello.o是相对偏移。因为重定位的一个过程是链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。
5.6 hello的执行流程
使用edb执行hello,调用与跳转的各个子程序名字如下:
ld-2.29.so!_dl_init
Hello!_start
libc-2.29.so!__libc_start_main
Hello!main
Hello!printf@plt
hello!atoi@plt
Hello!sleep@plt
hello!getchar@plt
libc-2.29.so!exit
5.7 Hello的动态链接分析
GOT[1]保存的是指向已经加载的共享库的链表地址。GOT[2]是动态链接器在ld-linux.so模块中的入口。在程序执行时,使用过程链接表PLT和全局偏移量表GOT进行动态链接。GOT采用延迟绑定的策略。GOT中保存着下一条指令的地址。dl_init执行后,通过重定位确定函数地址。
查看hello_elf.txt文件可以得到GOT的起始位置为0x403ff0。再用edb查看.got的内容。
调用dl_init前:
调用dl_init后:
比较可以得知,0x403ff0之后的内容发生了变化,即全局偏移量表GOT[1]和GOT[2]的内容发生了变化。
5.8 本章小结
在这一章中,我们介绍了链接的过程。以hello.o链接静态库形成hello可执行文件为例,对hello可执行文件的格式进行分析、hello的虚拟地址空间,hello的可重定位的过程、hello的动态链接分析。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程的经典定义是一个执行中程序的实例。系统的每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程的作用:进程提供程序独占地使用处理器并且可以独占内存的假象。每次用户通过想shell输入一个可执行目标文件的名字,运行程序是,shell会创建一个新的进程,然后再这个新进程的上下文中运行这个可执行目标文件。应用程序课能够创建新进程,并且在这个新进程的上下文中云心它们自己的代码或者其他应用程序。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash是一个lunix下的交互型的应用级程序,用户可以通过这个界面访问操作系统的内核,为用户与内核之间提供了一个交互的界面。
Shell的处理流程:
- 从命令行中读取用户输入
- 解析命令行,检查第一个参数是否是一个内置的shel命令。
- 如果是内置命令,立即解释
- 如果不是,创建子进程,执行该程序。
3、如多参数的最后为&,shell不会等待程序结束;否则,shell会调用waitpid()函数来等待前台作业的结束。
6.3 Hello的fork进程创建过程
在命令行输入./hello 2021110669 chenxinran 1 后,shell检查该命令是否为内置命令。因为这个命令不是一个内置命令,shell调用fork函数创建一个新的子进程,子进程得到与父进程用户级虚拟地址空间相同的一份副本,但是PID不同。
6.4 Hello的execve过程
调用函数fork创建新的子进程之后,子进程会调用execve函数,加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。只有出现错误时,例如找不到hello时,execve才会返回到调用程序。Execve调用一次且从不返回。首先,它加载可执行文件并进行可执行性检查,在当前的进程中删除当前进程中现有的虚拟内存段,并将堆栈初始化为0,将新的代码和数据段初始化为可执行文件的内容,跳转到_start的地址,最后进行控制传递。在CPU开始引用被映射的虚拟页的时候,内核才会将需要用到的数据从磁盘中放入内存中。
6.5 Hello的进程执行
首先明确一些概念:
内核模式:进程可以访问任何内存,调用任何指令。
进程从用户模式变为内核模式的方法:通过中断、故障等异常的调用。
上下文:内核为每一个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态。
内核调度进程的机制:上下文切换。
在hello程序执行时,会有其他进程并发地运行。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
接下来具体分析一下hello的进程执行流程:
(1)hello起初初始用户模式,继续运行调用printf函数,进程从用户模式变成内核模式(由于系统调用)。
(2)在printf函数执行完之后又返回到用户模式。继续运行调用sleep函数,内核进行上下文切换,调用其他进程运行,同时计数器记录休眠的时间。因为参数为1,所以内核中的计时器计到1s时,系统发生中断转换到hello进程原先运行的位置,将之前挂起的进程放到运行队列中继续执行。
(3)循环中,hello进程会多次进行用户模式和内核模式的转变。
(4)之后调用getchar函数,进入内核模式,再进行上下文切换,运行其它进程,当数据传输结束,发生中断,回到hello进程,最后返回,hello进程运行终止。
6.6 hello的异常与信号处理
(1)中断(异步)
中断是来自处理器外部的I/O设备的信号的结果,中断处理程序运行之后,返回到下一条指令。
(2)陷阱(同步)
陷阱是有意的异常,是执行一条指令的结果,它总是返回到下一条指令。
(3)故障(同步)
故障是由错误情况引起的,当故障发生时,是利器将控制转移给故障处理程序,如果错误情况可以修正,则将控制返回到引起故障指令,重新执行,否则处理程序返回到内核abort,终止故障的应用程序。
(4)终止(同步)
终止是不可恢复的致命错误造成的结果,会终止应用程序,不会返回。
空格:进程不会被打断。
回车:进程被回收。
Ctrl+c:进程接收到SIGNIT信号,终止并回收hello进程。
Ctrl+z:进程收到SIGSTP信号在屏幕上显示提示信息并挂起hello进程。
使用ps可以查看进程的基本信息,如:PID、TTY、TIME、CMD。
使用jobs:查看进程状态,此时为suspended。
使用pstree:可以查看进程树。
使用fg:重新运行
Kill:向进程发送一个信号。
6.7本章小结
在这一章中,我们介绍了hello的进程管理。首先我们介绍了进程的概念和应用,然后我们介绍了shell-bash的作用与处理流程,shell的fork进程创建过程和execve过程,hello的执行,hello的异常与信号处理。特别地,对于hello的异常和信号处理,我们通过在命令行中演示,具体呈现hello的异常和信号处理机制。
第7章 hello的存储管理
7.1 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动态存储分配管理
7.10本章小结
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
8.3 printf的实现分析
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
结论
Hello的一生的过程如下:
- 源文件:程序员编写的hello.c
- 预处理:预处理(预编译),是预处理器(cpp)以字符#开头的命令,修改原始的C程序,最终生成hello.i文件.
- 编译:编译器(ccl)将预处理后的文本文件hell0.i文件翻译成文本文件hello.s。
- 汇编:汇编器(as)将hello.s文件(文本文件)翻译成hello.o文件(二进制文件)。
- 运行:得到hello文件,并且在shell中输入命令行,传入参数。
- 进程管理:shell调用fork函数为hello创建子进程;exceve()函数加载运行hello程序;hello与很多别的进程是并行的,当发生中断或者异常是,发生上下文切换,进入内核模式。
- 存储管理:MMU将虚拟地址翻译为物理地址。
- IO管理:hello输入输出与外界交互通过linux I/O实现。
- Hello执行完,最后被shell回收,实现从0到0的一生。
通过对hello一生的研究,我们更好地理解了计算机系统的原理。我认为这就是深入了解计算机系统的“深入”所在。
附件
hello.c 源文件
hello.i:经过预处理器(cpp)以字符#开头的命令,修改原始的C程序,hell0.c最终生成hello.i文件。
hello.s:hello编译后的文件
hello.o:hello汇编后的文件
hello:hello链接之后的文件
hello1.elf : hello文件可重定位目标elf文件
Helloref.txt: hello.o文件可重定位目标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.