摘 要
本文主要讲述了hello.c程序在编写完成后运行在linux中的生命历程,借助相关工具分析预处理、编译、汇编、链接等各个过程在linux下实现的原理,分析了这些过程中产生的文件的相应信息和作用。并介绍了shell的内存管理、IO管理、进程管理等相关知识,了解了虚拟内存、异常信号处理等相关内容。

关键词:预处理;编译;汇编;链接;shell;

(摘要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.5 三级Cache支持下的物理内存访问- 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 Process
用高级语言编写得到.c文件,再经过编译器预处理得到.i文件,进而对其进行编译得到.s汇编语言文件。此后通过汇编器将.s文件翻译成机器语言,将指令打包成可重定位的.o目标文件,再通过链接器与库函数链接得到可执行文件hello,执行此文件,操作系统会为其fork产生子进程,再调用execve函数加载进程。至此,P2P结束。
020:From Zero-0 to Zero-0
操作系统调用execve后映射虚拟内存,先删除当前虚拟地址的数据结构并未hello创建新的区域结构,进入程序入口后载入物理内存,再进入main函数执行代码。执行完成后,父进程回收hello进程,内核删除相关数据结构。
1.2 环境与工具
Intel(R)Core™i5-7300HQ CPU 2.50GHz 2.50GHz 8G RAM
Win10教育版 64位
虚拟机VMware Workstation Pro12.0
Ubuntu18.4
gcc ld readelf gedit objdump edb hexedit
1.3 中间结果
hello.i:预处理生成的文本文件
hello.s:.i编译后得到的汇编语言文件
hello.o:.s汇编后得到的可重定位目标文件
Hello:.o经过链接生成的可执行目标文件
1.4 本章小结
本章主要介绍了P2P、020的过程并列出实验基本信息
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:预处理器执行以#开头的命令(宏定义、条件编译、读取头文件)、删除注释等来修改c程序生成.i文件
作用:1、用实际值替换宏定义的字符串2、文件包含:将头文件中的代码插入到新程序中3、条件编译:根据if后面的条件决定需要编译的代码
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i

图2.2.1预处理命令
2.3 Hello的预处理结果解析
预处理得到.i文件打开后发现得到了扩展,到3118行。原文件中的宏进行了宏展开,头文件的内容得到引入。
2.4 本章小结
了解了预处理的概念和作用及ubuntu下预处理命令,并分析了.i文件所包含的信息。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
概念:编译是利用编译程序从预处理文本文件(.i)产生汇编程序(.s)的过程。
作用:进行词法分析、语法分析、目标代码的生成,检查无误后生成汇编语言。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

图3.2.1编译命令
3.3 Hello的编译结果解析
3.3.1汇编文件指令
指令内容
.file声明源文件
.text声明代码段
.data声明数据段
.section.rodata只读数据,rodata节
.globl全局变量
.size声明大小
.type指定类型
.align声明对指令或数据的存放地址进行对齐的方式
3.3.2整型
1)int sleepsecs:这是个已经初始化的全局变量,存放于.data节中。
2)Main函数的参数argc:函数第一个参数,占四个字节,保存于栈空间中
3.3.3字符串
输出字符串作为全局变量保存,存储与.rodata节中。.s文件中两个字符串均为printf的参数。
3.3.4数组
有两个参数int argc,char *argv[]。argv作为存放char指针的数组为第二个参数。

图3.3.1参数的地址
3.3.5赋值
将赋值操作编译为汇编指令MOV,根据数据类型由movb、movw、movl、movq、movabsq。如图3.3.1也为赋值操作。
3.3.6算术和逻辑运算

图3.3.2算术和逻辑操作
.s中开辟栈空间、获得参数argv[1]和argv[2]及循环中i++均用到上述操作中的指令。
3.3.7控制转移

图3.3.3跳转指令
.c中比较argc与3时用到跳转,在.s中则为:

图3.3.4比较
不相等时:

图3.3.5不等时跳转
3.4 本章小结
本章介绍了编译器如何处理c程序,将预处理文本文件.i翻译成.s。介绍了汇编语言的相关知识。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
概念:将.s汇编程序翻译成机器语言指令并将这些指令打包成可重定位目标程序的格式存放在hello.o中。
作用:将汇编代码转为机器指令,使其在链接后能被机器识别并执行。
4.2 在Ubuntu下汇编的命令
gcc –c –o hello.o hello.s
图4.2.1生成hello.o
4.3 可重定位目标elf格式
使用readelf -a hello.o > hello.elf 指令获得hello.o文件的ELF格式。
图4.3.1Elf头
1)Elf 头:以一个16字节序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,剩下的信息帮助链接器语法分析和解释目标文件的信息。包括ELF头大小,目标文件类型,机器类型,节头部表的文件偏移以及节头部表中条目的大小和数量。
图4.3.2节头部表
2)节头部表:记录了各节名称、类型、地址、偏移量、大小、全体大小、旗标、连接、信息、对齐信息。
图4.3.3
3)重定位节:.rela.text ,一个.text节中位置的列表,包含.text节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。调用本地函数的指令则不需要修改。链接器会依据重定向节的信息对可重定向的目标文件进行链接得到可执行文件。

图4.3.3符号表
4)符号表:.symtab存放着程序中定义和引用函数和全局变量的信息。且不包含局部变量的条目。重定位中的符号类型全在该表中有声明。
4.4 Hello.o的结果解析
图4.4.1反汇编命令
使用命令objdump -d -r hello.o > hellores.txt获得反汇编代码,与hello.s比较发现以下差别:
1)分支转移:在汇编代码中,分支跳转是直接以.L0等助记符表示,但是在反汇编代码中,分支转移表示为主函数+段内偏移量。反汇编代码跳转指令的操作数使用的不是段名称如.L3,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。

图4.4.2分支转移的比较
2)函数调用:汇编代码中函数调用时直接跟函数名称,而在反汇编的文件中call之后加main+偏移量(定位到call的下一条指令)。在.rela.text节中为其添加重定位条目等待链接。
3)访问全局变量:汇编代码中使用.LC0(%rip),反汇编代码中为0x0(%rip)。因为访问时需要重定位,所以初始化为0并添加重定位条目。
4.5 本章小结
本章介绍了从.s到.o的过程。通过汇编文件和elf格式与.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.2.1使用链接命令生成可执行程序hello
5.3 可执行目标文件hello的格式
使用readelf -a hello >hello1.elf生成hello的ELF格式文件

图5.3.1elf头

图5.3.2节头表
5.4 hello的虚拟地址空间

图5.4.1elf文件中的程序头
PHDR保存程序头表
INTERP动态链接器的路径
LOAD可加载的程序段
DYNAMIN保存了由动态连接器使用的信息
NOTE保存辅助信息
GNU STACK标志栈是否可执行
GNU RELRO指定重定位后需被设置成只读的内存区域

通过edb加载hello从Data Dump中查看虚拟地址空间

图5.4.2虚拟地址空间
在0x400000~0x401000段中,程序被载入,自虚拟地址0x400000开始,到0x400fff结束,这之间每个节的地址同图5.3.2中的地址声明。
5.5 链接的重定位过程分析
使用指令objdump -d -r hello > helloout.txt 生成反汇编文件。
与hello.o生成的反汇编文件对比发现,helloout.txt中多了许多节。Hellores.txt中只有一个.text节,而且只有一个main函数,函数地址也是默认的0x000000。Helloouttxt中有.init,.plt,.text三个节,而且每个节中有许多函数.库函数的代码都已经链接到了程序中,程序各个节变得更加完整,跳转的地址也具有参考性。

图5.5.1hello生成的反汇编文件

图5.5.2hello.o生成的反汇编文件
5.6 hello的执行流程
使用edb执行hello,单步调试运行。
程序名称地址
ld-2.27.so!_dl_start0x7fce 8cc38ea0
ld-2.27.so!_dl_init0x7fce 8cc47630
hello!_start0x400500
libc-2.27.so!__libc_start_main0x7fce 8c867ab0
hello!main0x400532
hello!puts@plt0x4004b0
hello!exit@plt0x4004e0

5.7 Hello的动态链接分析
在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定,将过程地址的绑定推迟到第一次调用该过程时。
5.8 本章小结
本章主要介绍从可重定位文件hello.o生成可执行文件hello的过程,讨论了链接过程中对程序的处理。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
作用:进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
6.2 简述壳Shell-bash的作用与处理流程
作用:是一种交互型的应用级程序,时Linux的外壳,提供了一个界面,用户可以通过这界面访问操作系统内核。
流程:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数
3)如果是内置命令则立即执行
4)否则调用相应的程序为其分配子进程并运行
5)shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
在终端中输入命令后,shell会处理该命令,判断出不是内置命令,则会调用fork函数创建一个新的子进程,子进程几乎但不完全与父进程相同。通过fork函数,子进程得到与父进程用户级虚拟地址空间相同的但是独立的一份副本。
6.4 Hello的execve过程
fork之后子进程调用execve函数在当前进程的上下文中加载并运行一个新程序即hello程序。execve加载并运行可执行目标文件,且带参数列表argv和环境变量列表envp,并将控制传递给main函数。
6.5 Hello的进程执行
逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。
 时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
 用户模式和内核模式:shell使得用户可以有机会修改内核,所以需要设置一些防护措施来保护内核,如限制指令的类型和可以作用的范围。
上下文切换:上下文就是内核重新启动一个被抢占的进程所需要的状态,是一种比较高层次的异常控制流。
开始Hello运行在用户模式,收到信号后进入内核模式,运行信号处理程序,之后再返回用户模式。运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。
6.6 hello的异常与信号处理
运行过程中可能出现的异常种类由四种:中断、陷阱、故障、终止。
中断:来自I/O设备的信号,异步发生。硬件中断的异常处理程序被称为中断处理程序。
陷阱:是执行一条指令的结果。调用后返回到下一条指令。
故障:由错误情况引起,可能能被修正。修正成功则返回到引起故障的指令,否则终止程序。
终止:不可恢复,通常是硬件错误,这个程序会被终止。

图6.6.1正常运行

图6.6.2ctrl+c终止

图6.6.3ctrl+z暂停

图6.6.4运行途中
6.7本章小结
本章介绍了进程的相关概念,描述了hello子进程fork和execve的过程,介绍了shell的一般处理流程和异常与信号处理。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:格式为“段地址:偏移地址”,是CPU生成的地址,在内部和编程使用,并不唯一。
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。在前端总线上传输的内存地址都是物理内存地址。
虚拟地址:保护模式下程序访问存储器所用的逻辑地址。
线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
分段功能在实模式和保护模式下有所不同。
实模式:逻辑地址=线性地址=实际的物理地址。段寄存器存放真实段基址,同时给出32位地址偏移量,则可以访问真实物理内存。
保护模式:线性地址还需要经过分页机制才能够得到物理地址,线性地址也需要逻辑地址通过段机制来得到。
段寄存器用于存放段选择符,通过段选择符可以得到对应段的首地址。处理器在通过段式管理寻址时,首先通过段描述符得到段基址,然后与偏移量结合得到线性地址,从而得到了虚拟地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。
7.4 TLB与四级页表支持下的VA到PA的变换
为减少时间开销,MMU中存在一个关于PTE的缓存,成为翻译后备缓冲器TLB。其中每一行都保存着一个由单个PTE组成的块。TLB通常有高度的相联度。

图7.4.1TLB命中与不命中操作图
7.5 三级Cache支持下的物理内存访问
1、CPU给出VA
2、MMU用VPN到TLB中找寻PTE,若命中,得到PA;若不命中,利用VPN(多级页表机制)到内存中找到对应的物理页面,得到PA。
3、PA分成PPN和PPO两部分。利用其中的PPO,将其分成CI和CO,CI作为cache组索引,CO作为块偏移,PPN作为tag。
先访问一级缓存,不命中时访问二级缓存,再不命中访问三级缓存,再不命中访问主存,如果主存缺页则访问硬盘
7.6 hello进程fork时的内存映射
Fork函数被调用是内核为hello新进程创建虚拟内存和各种数据结构并为其分配唯一的PID。为进行以上操作,还创建了当前进程的mm_struct、区域结构和样表的原样副本。Shell将两个进程中每个页面都标为只读,并将每个进程中的每个区域结构标记为写时复制。
7.7 hello进程execve时的内存映射
删除已存在的用户区域。
映射私有区域:为新程序的代码、数据、.bss和栈区域创建新的区域结构。
映射共享区:hello与系统执行文件链接映射到共享区域。
设置程序计数器PC:设置当前进程上下文中的PC,指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障是一种常见的故障,要访问的主页不在主存,需要操作系统调入才能访问。缺页中断处理函数为do_page_fault函数。

图7.8.1缺页故障处理流程
7.9动态存储分配管理

图7.9.1动态内存分配
分配器将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的。分配器的类型有两种:
显式分配器:要求应用显式地释放任何已分配的块.例如,C语言中的 malloc 和 free
隐式分配器:应用检测到已分配块不再被程序所使用,就释放这个块

图7.9.2记录空闲块的方法

图7.9.3分配策略
7.10本章小结
本章了解了存储器地址空间,段式管理和页式管理,介绍了动态内存分配和进程的创建及fork和execve时的内存映射,还介绍了缺页故障和缺页中断处理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有的IO设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单低级的应用接口,称为 Unix I/O。
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Linux以文件的方式对I/O设备进行读写,将设备均映射为文件。对文件的操作,内核提供了一种简单、低级的应用接口,即Unix I/O接口。
打开文件:int open(char *filename, int flags, mode_t mode);
关闭文件:int close(int fd);
读文件:ssize_t read(int fd, void *buf, size_t n);
写文件:ssize_t write(int fd, const void *buf, size_t n);
8.3 printf的实现分析
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
Syscall将字符串中的字节以ASCII字符形式从寄存器复制到显卡的显存中。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章主要介绍了Linux的IO设备管理方法、Unix IO接口及函数,分析了printf和getchar函数。
(第8章1分)
结论
hello程序的过程可总结如下:
1、编写代码:用高级语言写.c文件
2、预处理:从.c生成.i文件,将.c中调用的外部库展开合并到.i中
3、编译:由.i生成.s汇编文件
4、汇编:将.s文件翻译为机器语言指令,并打包成可重定位目标程序hello.o
5、链接:将.o可重定位目标文件和动态链接库链接成可执行目标程序hello
6、运行:在shell中输入命令
7、创建子进程:shell嗲用fork为程序创建子进程
8、加载:shell调用execve函数,将hello程序加载到该子进程,映射虚拟内存
9、执行指令:CPU为进程分配时间片,加载器将计数器预置在程序入口点,则hello可以顺序执行自己的逻辑控制流
10、访问内存:MMU将虚拟内存地址映射成物理内存地址,CPU通过其来访问
11、动态内存分配:根据需要申请动态内存
12、信号:shell的信号处理函数可以接受程序的异常和用户的请求
13、终止:执行完成后父进程回收子进程,内核删除为该进程创建的数据结构
至此,hello运行结束
(结论0分,缺少 -1分,根据内容酌情加分)

附件
hello.i预处理得到的文本文件
hello.s编译得到的汇编程序
Hello.o汇编后生成的可重定位目标文件
hello连接后生成的可执行目标文件
Hello.elfHello.o的elf文件
Hello1.elfhello的elf文件
Helloout.txtHello的反汇编文件
Hellores.txtHello.o的反汇编文件

(附件0分,缺失 -1分)

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 兰德尔 E.布莱恩特,大卫 R.奥哈拉伦. 深入理解计算机系统. 机械工业出版社
[2]https://www.cnblogs.com/pianist/p/3315801.html printf函数实现的深入剖析
[3]https://blog.csdn.net/rabbit_in_android/article/details/49976101 关于逻辑地址、虚拟地址、线性地址、物理地址
[4]https://blog.csdn.net/drshenlei/article/details/4261909 地址转换与分段
(参考文献0分,确实 -1分)

02-12 07:57