程序人生-Hello’s P2P

关键词:计算机系统;预处理;编译;汇编;链接;P2P;

第1章 概述

1.1 Hello简介

P2P(Program to Process):即从源程序的.c文本文件经过预处理、编译、汇编、链接等过程,依次形成.i、.s、.o文件,并最终形成可执行文件。在执行此文件时,会调用fork()函数创建进程/使用execve()函数加载进程,至此完成由程序至进程的P2P过程。
020(Zero to Zero):即在CPU加载进程后,为程序分配机器周期、映射虚拟内存,使指令的流水线开始工作。在全部指令完成后,使用shell回收进程,释放内存,删除相关缓存。最终完成了从零到有最终恢复零的过程。

1.2 环境与工具

1.2.1 硬件环境

Intel® Core™ i7-8550U CPU;1.99GHz;8G RAM;256GHD Disk

1.2.2 软件环境

Win 10 家庭版 64位;VMware 15.5;Ubuntu 19.04 LTS 64位

1.2.3 开发工具

Visual Studio 2019 64位;Code::Blocks 64位;objdump;gdb;hexedit

1.3 中间结果

hello.c:源程序
hello.i:经过预处理的源程序
hello.s:经过编译的汇编程序
hello.o:经过汇编的可重定位目标程序
hello:经过链接的可执行目标程序

1.4 本章小结

简述了hello.c从编写完成到执行到结束的全部过程,即P2P和020过程,并展示了逐步生成的文件机器作用。

第2章 预处理

2.1 预处理的概念与作用

预处理:即对.c文本文件的初步处理,主要是对import等命令进行解释,最终得到预处理后的.i文本文件。在一个程序处理的过程中,最先进行的往往就是预处理过程。
预处理过程的作用主要是提升程序编写过程的效率,方便对一定的程序进行打包,预处理过程即为拆包操作,是程序编写集成发展的产物。另外,预处理过程方便源代码在不同的执行环境中的修改或编译。

2.2在Ubuntu下预处理的命令

预处理指令:gcc -E [.c] -o [.i];
对应本次指令为 gcc -E hello.c -o hello.i
命令行下对hello.c文件的预处理图2.2-1 命令行下对hello.c文件的预处理
经过预处理后的hello.i文件展示图2.2-2 经过预处理后的hello.i文件展示

2.3 Hello的预处理结果解析

通过对预处理文件进行分析,可知其对原.c文件中的main函数基本不做处理。
在这里插入图片描述图2.3-1 hello.i文件中的main函数
对比.c和.i文件,可以发现,预处理命令最要时对原.c文件的头文件进行扩展,相比于程序文本文件,.i文件的头文件可以扩展到3000+行,其中包含大量的宏定义。
在这里插入图片描述图2.3-2 hello.i文件中的头文件展示

2.4 本章小结

由上可知,.c文件的预处理即对#开头的命令进行处理,修改原始的.c文件,形成完整的.i命令文件。本章主要对Ubuntu下的预处理命令进行分析,展示预处理过程的作用。

第3章 编译

3.1 编译的概念与作用

编译:即将一种语言的程序等价翻译为另一种语言,在这里指将预处理后的.i文件编译成汇编语言程序的.s文件。
编译的作用利用编译器将C语言相对抽象的语言模型翻译为方便机器运行执行的基本指令。翻译成的汇编语言程序是计算机底层机器语言的重命名,拥有了一定的可读性。
在编译过程中,编译器会对程序进行一定的语法检查、调试、修改、覆盖、优化等处理。方便程序员对程序进行纠错修改,并自动对程序进行一定程度的优化,提高了程序的运行效率。

3.2 在Ubuntu下编译的命令

编译命令: gcc -S [.i] -o [.s]
对应本实验命令为:gcc -S hello.i -o hello.s
.在这里插入图片描述图3.2-1 Ubuntu命令行下编译的运行

3.3 Hello的编译结果解析

3.3.1 数据类型

·字符串类型:在本文件中,共有两个string类型数据,为printf函数的两个参数。可以看出,字符串中的中文字符采用的是utf-8编码,每个中文字符都站三个字符位,全角符号占两个字符位。
在这里插入图片描述图3.3.1-1 .s文件下字符串展示
·整形:在本文件中,仅对变量i进行存放,存放位置是(%rbq)空间,对于局部变量,仅在开始使用变量时对其进行初始化。变量的转化类型为隐式转换在这里插入图片描述
图3.3.1-2 .s文件下的整形数据存储展示
·数组:argv[2]作为printf函数的第三个参数,应当存于寄存器%rdx中,因此可推断argv[2]地址为-0x16(%rbp);argv[1]作为printf第二个参数,应当存于寄存器%rsi中,因此可推断argv[1]地址为-0x2A(%rbp)中,数组首地址位于-0x32(%rbp) ,以上所占字节数为8。
在这里插入图片描述 图3.3.1-3 .s 文件下的数组存储展示

3.3.2 赋值

程序中对计数变量i进行了赋值操作,具体过程如图所示: 在这里插入图片描述
图3.3.2 赋值操作语句

3.3.3 关系操作符

在程序中使用了比较关系符!=,具体操作过程如下图所示:
在这里插入图片描述
图3.3.3 比较关系福!=实现展示

3.3.4 算数操作符

x=x+y addq y,x
x=x-y subq y,x
x=x*y imulq y,x
x=x/y movq y,x
在这里插入图片描述
图3.3.4 .s文件下的累加语句展示

3.3.5 控制转移符

在.c文件中使用了if判断语句,而在汇编语言中实现if语句需要使用控制转移操作。控制转移的具体实现方法各不相同,本次文件中使用了cmpl命令使寄存器中的argc减去4,用je命令判断寄存器零标志位是否为1,并进行相关跳转。
在这里插入图片描述
图3.3.5 .s文件中的if语句实现展示

3.3.6 函数调用与参数传递

在.c文件中,调用了printf函数,并隐性调用了exit等函数函数。而在汇编语言.s文件中,具体实现函数调用的方式为:①通过movl、movq、leaq等指令向参数寄存器%rdi、%rsi、%rdx进行赋值 ②使用call语句调用函数 ③使用ret从main函数返回,返回值被存放在%rax中。
在这里插入图片描述
图3.3.6 .s文件中函数调用的实现

3.4 本章小结

本章主要对编译完成后的.s汇编语言文件进行解析,学习了基础的机器及编程方式,能够读懂编译完成后的汇编语言文件。这有助于我们学习并掌握机器执行程序的基础过程,编写对机器友好的程序。

4章 汇编

4.1 汇编的概念与作用

汇编:即使用汇编器从汇编语言程序翻译成机器语言的过程,即从.s文件生成.o的二进制文件。
汇编不仅可以将.s文件中的汇编语言程序翻译为机器语言程序,还可以将这些指令打包形成可重定位程序格式文件。

4.2 在Ubuntu下汇编的命令

汇编指令:gcc -c [.s] -o [.o]
在本次实验中为:gcc -c hello.s -o hello.o
在这里插入图片描述图4.2 Ubuntu命令行下汇编的运行

4.3 可重定位目标elf格式

使用readelf生成hello.o文件的 ELF格式;

4.3.1 Header

以16字符位的Magic开始,Magic 描述了生成该文件的系统的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解 释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、 字节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量等信息。
在这里插入图片描述图4.3.1 ELF文件的Header展示。

4.3.2 Section Headers

节头部表,包含了文件中出现的各个节的语义,包括节 的类型、位置和大小等信息。
在这里插入图片描述图4.3.2 ELF文件的Section Headers展示。

4.3.3 .rela.text

一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。包含的信息有:offset 需要进行重定向的代码在.text 或.dat节中的偏移位置,8 个字节。Info 包括 symbol 和 type 两部分, 其中 symbol 占前 4 个字节, type 占后 4 个字节,symbol 代表重定位到的目标在.symtab 中的偏移量,type 代表重定位 的类型Addend 计算重定位位置的辅助信息, 共占 8 个字节。Type 重定位到的目标的类型。Name 重定向到的目标的名称。
在这里插入图片描述图4.3.3 ELF文件的.rela.text展示。

4.3.4 .rela.eh_frame

eh_frame 节的重定位信息
在这里插入图片描述图4.3.4 ELF文件的rela.eh_frame展示。

4.4 Hello.o的结果解析

使用objdump -d -r hello.o命令反汇编hello.o文件,分析汇编语言和机器语言的区别和联系
机器语言中每条机器指令都有对应的操作码或操作数(十六进制表示),汇编语言中只有自然语言而没有机器可执行的机器指令。与机器代码的二进制格式相比,汇编代码的主要特点是它用可读性更好的文本格式表示。
机器语言中所有的操作数都以十六进制表示,而汇编语言中以可读性更强的十进制表示。
汇编语句中代码被分为若干段,跳转目标用段标识符表示;机器语言中直接用相对于对应代码段偏移地址表示。
在汇编语言中,由于没有重定位条目,在调用外部库函数时无法给出地址,以函数名称作为引用的目标;机器语言中由于存在重定位条目,可以通过重定位对相关的符号进行解析。
在这里插入图片描述图4.4 objdump 输出结果图

4.5 本章小结

本章我们了解到了汇编器对s的处理,汇编器将hello.s翻译为二进制机器语言,将其转化为了可重定位目标文件(ELF),这是链接时使用的重要文件类型。ELF文件的格式固定,其中的文件头和节提供之后链接时需要的各种种类的信息,包括数据、代码、符号以及重定位等。

第5章 链接

5.1 链接的概念与作用

链接:即由链接器将各种代码和数据片段收集并组合成为一个单一文件
由于链接的存在,使得分离编译称为可能。它将一个大型的应用程序分解为更小、更好管理的模块并且可以独立地修改和编译这些模块。当更改这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

5.2 在Ubuntu下链接的命令

Ubuntu下执行命令而:ld -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/gcc/x86_64-linux-gnu/7/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello.out
在这里插入图片描述图5.2 Ubuntu下命令行链接指令展示

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

用readelf -a hello > hello_out.elf执行获得包含hello的ELF格式的文件。节头部表中包含了各段的基本信息,包括名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等信息。
在这里插入图片描述图5.3-1 ELF Header信息展示
在这里插入图片描述图5.3-2 节头信息展示
在这里插入图片描述图5.3-3 程序头信息展示
在这里插入图片描述图5.3-4 重定位节信息展示
在这里插入图片描述图5.3-5 符号表信息展示

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
在这里插入图片描述图5.4 hello虚拟地址空间展示

5.5 链接的重定位过程分析

使用命令:objdump -d -r hello
在这里插入图片描述图5.5 重定位分析截图(部分)
重定位的过程分析:①重定位节和符号定义,链接器将所有相同类型的节合并为同一类型的新的聚合节。②重定位节中的符号引用,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。

5.6 hello的执行流程

(从加载hello到_start,到call main,以及程序终止的所有过程,调用与跳转的各个子程序的地址由低到高依次为:
0000000000401000 <_init>:
0000000000401020 <.plt>:
0000000000401030 puts@plt:
0000000000401040 printf@plt:
0000000000401050 getchar@plt:
0000000000401060 atoi@plt:
0000000000401070 exit@plt:
0000000000401080 sleep@plt:
0000000000401090 <_start>:
00000000004010c0 <_dl_relocate_static_pie>:
00000000004010d0 <deregister_tm_clones>:
0000000000401100 <register_tm_clones>:
0000000000401140 <__do_global_dtors_aux>:
0000000000401170 <frame_dummy>:
0000000000401172 :
0000000000401200 <__libc_csu_init>:
0000000000401260 <__libc_csu_fini>:
0000000000401264 <_fini>:

5.7 Hello的动态链接分析

在这里插入图片描述图5.7-1 调用前信息展示
在这里插入图片描述图5.7-2 调用后信息展示

5.8 本章小结

本章了解了链接的概念作用,分析可执行文件 hello 的 ELF 格式及其虚拟地址空间,同时通过实例分析了重定位过程、加载以及运行时函数调用顺序以及动态链接过程,深入理解链接和重定位的过程。

第6章 hello进程管理

6.1 进程的概念与作用

进程:进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,涵盖存储代码的文本区域,存储变量和动态分配内存的数据区域,存储调用指令和本地变量的堆栈区域。
进程可以向每个可执行文件营造一个单独使用处理器和内存系统的假象,方便了不同程序同步执行而不相互干扰。

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

Shell-bash:作为C编写的程序,它是用户使用Linux的桥梁。Shell是一种应用程序,它为用户访问操作系统内核提供了一个交互界面。
处理流程为:①读入输入的命令 ②分割字符串,获取命令 ③若为内置命令则执行,否则调用相应的程序为其分配子进程执行 ④Shell可以异步接收来自I/O设备的信号,并对这些中断信号进行处理。

6.3 Hello的fork进程创建过程

父进程通过调用fork函数创建一个新的运行的子进程。父进程和新创建的子进程最大的区别在于他们有不同的id。fork后调用一次返回两次,在父进程中fork会返回子进程的PID,在子进程中fork会返回0。

6.4 Hello的execve过程

execve 函数加载并运行可执行目标文件filename, 参数列表argv 和环境变量列表envp 。加载hello后,调用_start,_start设置栈,将控制传递给新程序的主函数。

6.5 Hello的进程执行

在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这个决策就叫做调度,是由内核中称为调度器的代码处理的。在内和调度了一个新的进程运行后,它就抢占当前进程,并使用上文所述的上下文切换的机制将控制转移到新的进程。内核代表的用户执行系统调用时,可能会发生上下文切换;中断也有可能引发上下文切换。
通过上图所示的内核模式用户模式的切换描述用户态核心态转换的过程,在切换的第一部分中,内核代表进程A在内核模式下执行指令。然后在某一时刻,它开始代表进程B(仍然是内核模式下)执行指令。在切换之后,内核代表进程B在用户模式下执行指令。随后,进程B在用户模式下运行一会儿,直到磁盘发出一个中断信号,表示数据已经从磁盘传送到了内存。内核判定进程B已经运行了足够长的时间,就执行一个从进程B到进程A的上下文切换,将控制返回给进程A中紧随在系统调用read之后的那条指令。进程A继续运行,直到下一次异常发生,依此类推。
在这里插入图片描述
图6.5 进程切换处理过程展示

6.6 hello的异常与信号处理

hello执行过程中会出现:①终止:会产生SIGINT信号,程序的运行被终止 ②中断:会产生SIGSTP信号,程序的运行被挂起。
① 执行hello程序,在程序的运行过程中ctrl + z将进程挂起,如下图所示。
② 运行ps,如下图所示。
③ 运行jobs,如下图所示。
④ 运行pstree,如下图所示(仅截取了部分)。
⑤ 运行fg,进程收到SIGCONT信号,继续运行,如下图所示。
⑥ 运行kill -9 2813,发送SIGKILL信号给指定的pid杀死指定的进程,如图所示。

6.7本章小结

本章介绍了进程的概念和作用,讲述了shell的操作和各种内核信号和命令,阐明了shell的fork新建子进程、execve执行进程、hello执行时可能进行的上下文切换以及shell对信号的处理的整个过程。

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:即由程序产生的与段相关的偏移地址部分。若地址空间是连续的,那么称为一个线性地址空间。在一个带虚拟内存的系统中,CPU将会生成一系列虚拟地址,整个地址空间称为虚拟地址空间,也就是可执行文件的地址。在hello运行的过程中,物理地址指的是那些真正位于内存中的且与其虚拟地址空间存在映射关系的那些地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

段式管理通过段寄存器(如CS等)与偏移地址(16/32/64,EA)的组合来完成逻辑地址到线性地址的变换。在实模式下,逻辑地址CS: EA所转换得到的物理地址为CS × 16 + EA。在保护模式下,以段描述符作为下标,到GDT/LDT表中查表获得短地址,将段地址加上偏移地址,得到线性地址,完成转换。

7.3 Hello的线性地址到物理地址的变换-页式管理

虚拟内容被组织为一个由存放在磁盘上的连续的字节大小的单元组成的数组。每个字节都有一个唯一的虚拟地址,作为数组的索引。磁盘上数组的内容被缓存在主存中。这些数据在缓存时被分割成块,作为磁盘和主存(较高层)之间的传输单元。虚拟内存系统通过将虚拟内存分割为大小固定的“虚拟页”来处理这个问题。同时物理内存也被分割为大小等同与虚拟页的“物理页”,并与“虚拟页”之间建立映射关系。从而达到由线性地址(虚拟地址)到物理地址的变换。

7.4 TLB与四级页表支持下的VA到PA的变换

为了部分消除MMU机制从高速缓存中请求页表条目不命中时将会读取内存从而产生的巨大开销,MMU中建立了一个翻译后备缓冲器(之后将简称其为TLB)。TBL是一个专门为页表设计的一个组相联高速缓存,速度甚至接近于处理器,用来存放常用的页表条目。虚拟地址中将虚拟页号(VPN)拆分为两个部分:TLB标记和TLB索引,用以访问TLB

7.5 三级Cache支持下的物理内存访问

处理器封装包括四个核、一个大的所有核共享的L3高速缓存,以及一个DDR3内存控制器。每个核包含一个层次结构的TLB、一个层次结构的数据和指令高速缓存,以及一组快速的点到点链路(让一个核和外部I/O桥直接通信)。TLB是虚拟寻址的,是四路组相联的。L1、L2和L3高速缓存是物理寻址的,块大小为64字节。L1和L2是8路组相联的,而L3是16路组相联的。页大小可以在启动时被配置为4KB或4MB。Linux使用的是4KB的页。

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

假设在运行当前进程中的程序执行了如下的execve调用:execve(“a.out”, NULL, NULL);。
函数execve在当前进程中加载并运行包含在可知行目标文件a.out中的程序,用a.out程序有效地替代了当前程序。加载并运行需要以下几个步骤:①删除已存在的用户区域 ②映射私有区域 ③映射共享区域 ④设置程序计数器。

7.8 缺页故障与缺页中断处理

7.9动态存储分配管理

7.10本章小结

本章节中主要以总结课程中“虚拟内存”这一部分内容的重点为主。从段式管理到页式管理出发,较为详细地讨论了虚拟地址到物理地址的变换过程。同时,对进程在调用fork和exceve函数时的内存映射进行了回顾。最后,总结了发生缺页故障时的处理步骤。由于老师在上课的过程中没有对“动态内存”作相关的要求,这一部分的总结略去。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件的类型有如下几种:①普通文件,包含任意数据的文件 ②目录,包含一组链接的文件,每个链接都将一个文件名映射到一个文件 ③套接字,用来与另一个进程进行跨网络通信的文件 ④命名通道 ⑤符号链接 ⑥字符和块设备。
设备管理:unix io接口:①打开和关闭文件 ②读取和写入文件 ③改变当前文件的位置

8.2 简述Unix IO接口及其函数

Open() 打开一个已经存在的文件或者创建一个新文件
Close() 关闭一个打开的文件
Read() 从当前文件位置复制字节到内存
Write() 从内存复制字节到当前文件位置
Lseek() 移动文件位置

8.3 printf的实现分析

https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章了解Unix I/O设备管理机制及方法,Unix I/O接口及函数。通过对printf和getchar函数的底层解析加深对Unix I/O及异常中断的了解。

结论

用计算机系统的语言,逐条总结hello所经历的过程。
① 程序员创建.c文本文件,并点击按钮开始执行;
② 编译器对生成的.c文件进行预处理,并形成.i文本文件;
③ 编译器对.i文件进行编译,形成汇编语言的.s文件;
④ 汇编程序对.s进行处理,形成可重定位的.o机器语言文件;
⑤ 链接器对.o文件进行连接形成无后缀的可执行目标文件;
⑥ 机器开始执行可执行文件,创建进程,并与IO合作进行打印输出。

附件

hello.c
hello.i
hello.s
hello.o
hello
hello.elf

参考文献

为完成本次大作业你翻阅的书籍与网站等
[1] https://blog.csdn.net/Sophisticated_/article/details/82796129
[2] https://www.cnblogs.com/dpf-learn/p/6127750.html
[3] https://blog.csdn.net/Com_ma/article/details/78145333
[4] https://blog.csdn.net/happyforever91/article/details/51713741
[5] 深入理解计算机系统(原书第3版)/(美)兰德尔·E.布莱恩特(RandalE.Bryant)等著;龚奕利,贺莲译.北京:机械工业出版社,2016

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值