计算机系统
大作业
计算机科学与技术学院
2021年5月
本文主要阐述对hello.c程序在Linux系统的生命历程的分析,根据本学期所学知识,对hello程序从hello.c源文件经过预处理、编译、汇编、链接等步骤生成hello可执行文件,以及可执行文件的执行的全过程逐步分析。通过对hello一生周期的探索,展示自己对计算机系统的理解。
关键词:计算机系统;预处理;编译;汇编;链接;进程管理;虚拟内存;I/O ;
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 6 -
3.2 在Ubuntu下编译的命令............................................................................. - 8 -
4.2 在Ubuntu下汇编的命令........................................................................... - 13 -
5.2 在Ubuntu下链接的命令........................................................................... - 17 -
5.3 可执行目标文件hello的格式.................................................................. - 17 -
5.5 链接的重定位过程分析............................................................................... - 21 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 24 -
6.3 Hello的fork进程创建过程..................................................................... - 24 -
6.6 hello的异常与信号处理............................................................................ - 25 -
第7章 hello的存储管理............................................................................... - 29 -
7.1 hello的存储器地址空间............................................................................ - 29 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 29 -
7.3 Hello的线性地址到物理地址的变换-页式管理...................................... - 30 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 30 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 31 -
7.6 hello进程fork时的内存映射.................................................................. - 32 -
7.7 hello进程execve时的内存映射.............................................................. - 32 -
7.8 缺页故障与缺页中断处理........................................................................... - 32 -
8.1 Linux的IO设备管理方法.......................................................................... - 36 -
8.2 简述Unix IO接口及其函数....................................................................... - 36 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P是指From Program to Process。对于Hello来说,源文件hello.c由c语言编写,依次经过预处理、编译、汇编和链接,生成hello.i、hello.s、hello.o文件,最后成为可执行目标程序hello。通过shell执行该程序,shell会fork子进程并加载,至此hello成为进程,p2p结束。
020是指From Zero-0 to Zero-0。shell调用execve加载hello后映射虚拟内存,为hello创建新的区域结构,进入程序入口后载入物理内存,再进入main函数执行。程序运行结束后,父进程负责回收hello进程,内核删除相关数据结构,至此hello的一生结束。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows10 64位;Vmware 16;Ubuntu 18.04
开发与调试工具:gcc;vim;edb;HexEdit;codeblocks;
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i:hello.c预处理后的文本文件
hello.s:hello.i编译后的汇编文件
hello.o:hello.s汇编后的可重定位目标文件
hello:hello.o链接后的可执行文件
hello0.elf:hello.o的ELF格式
hello.elf:hello的ELF格式
hello0.txt:hello.o反汇编代码
hello.txt:hello的反汇编代码
1.4 本章小结
本章是全篇概述,简单介绍了 hello 的 p2p和020 过程,列出了本次实验软硬件环境及工具信息,和实验过程中生成的中间文件。
第2章 预处理
2.1 预处理的概念与作用
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如 hello.c中第1行的#include 命令告诉预处理器读取系统头文件 stdio.h的内容,并把它直接插人程序文本中。结果就得到了另一个C程序,通常是以.i作为文件扩展名。
预处理程序的作用是根据源代码中的预处理指令修改你的源代码。预处理指令是一种命令语句(如#define),它指示预处理程序如何修改源代码。在对程序进行通常的编译处理之前,编译程序会自动运行预处理程序,对程序进行编译预处理,这部分工作对程序员来说是不可见的。
预处理程序读入所有包含的文件以及待编译的源代码,然后生成源代码的预处理版本。在预处理版本中,宏和常量标识符已全部被相应的代码和值替换掉了。如果源代码中包含条件预处理指令(如#if),那么预处理程序将先判断条件,再相应地修改源代码。
2.2在Ubuntu下预处理的命令
cpp -E hello.c -o hello.i
如图,文件目录下增加了hello.i
2.3 Hello的预处理结果解析
可以看到,预处理生成的hello.i是c语言文本文件,和hello.c相比增加了头文件的内容,对宏进行了展开和替换。
2.4 本章小结
本章简单介绍了预处理的相关概念和行为,对预处理生成的.i文件分析
第3章 编译
3.1 编译的概念与作用
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
经过词法分析、语法分析、语义分析以及优化后编译成若干个目标模块。可以理解为将高级语言翻译成计算机可以理解的二进制码,即机器语言。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1 数据
1. 字符串常量
如图,字符串以UTF-8编码的格式存在只读数据段。
2. 局部变量i
局部变量i被存放在堆栈中,如图,-4(%rbp)处即i。
3. 参数argc
第一个参数存在寄存器edi中,如图,在-20(%rbp)保存edi后与4比较.
4. 数组argv[]
第二个参数保存在rsi中
3.3.2 赋值操作
以i=0为例,通过mov指令实现。根据数据类型的不同指令后缀不同,bwlq分别对应1248个字节,下同。
3.3.3 算数操作
i++的操作如下
程序包含:
PHDR 保存程序头表;
INTERP 指定在程序已经从可执行文件映射到内存之后,必须调用的解释器;
LOAD 表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据、程序的目标代码等;
DYNAMIC 保存了由动态链接器使用的信息;
NOTE 保存辅助信息;
GNU_STACK,权限标志,用于标志栈是否是可执行;
GNU_RELRO,指定在重定位结束之后哪些内存区域是需要设置只读。
5.5 链接的重定位过程分析
objdump -d -r hello > hello.txt
与hello.o的重定位项目相比,hello.txt中多了许多节。hello0.txt中只有一个.text节,而且只有一个main函数,函数地址也是默认的0x000000。hello.txt中有.init,.plt,.text三个节,而且每个节中有许多的函数。库函数的代码都已经链接到了程序中,程序各个节变的更加完整,跳转的地址也具有参考性。
重定位分为两步:
重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输人模块定义的每个节,以及赋给输人模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目(relocation entry)的数据结构。
调用init前后.got.plt的条目变化
5.8 本章小结
本章简单介绍了链接的概念和作用,通过对hello可执行程序的分析,回顾了文件的重定位过程,动态链接过程,虚拟地址空间,可重定位目标文件ELF格式的各个节等与链接有关的内容。
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个针对执行中的应用程序的程序的实例。系统中的每个应用程序都可以运行在某个应用进程的可执行上下文中。每次程序用户可以通过向系统中的shell应用程序输入一个可执行程序的英文名字,运行这个应用程序时,shell就可能会自动创建一个新的应用进程,然后在这个新应用进程的可执行上下文中自动运行这个可执行文件,应用程序也同样可以自动创建新的可执行进程,并且在这个新进程的可执行上下文中用户可以运行他们自己的可执行代码或者其他的应用程序。
由于程序是静态的,我们看到的程序是存储在存储介质上的,它无法反映出程序执行过程中的动态特性,而且程序在执行过程中是不断申请资源,程序作为共享资源的基本单位是不合适的,所以需要引入一个概念,它能描述程序的执行过程而且可以作为共享资源的基本单位,这个概念就是进程。进程解决了系统资源调度等一系列问题。
6.2 简述壳Shell-bash的作用与处理流程
shell俗称壳,是一种指"为使用者提供操作界面"的嵌入式软件(也被称为命令解析器)。软件提供了一种允许用户与其他操作系统之间进行通讯的一种方式。这种简单的通讯方式可以以交互方式(从键盘输入,并且用户可以立即地得到命令响应),或者以交互方式shellscript(非交互)的方式允许用户执行。shell(即壳)它是一个简单的命令解释器,它允许系统接收到一个用户的命令,然后自动调用相应的命令执行应用程序。
处理流程:
(1)从终端读入输入的命令。
(2)将输入字符串切分获得所有的参数
(3)如果是内置命令则立即执行
(4)否则调用相应的程序为其分配子进程并运行
(5)shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
shell判断出不是内置命令后,加载可执行文件hello,通过fork创建子进程,子进程得到一份与父进程用户级虚拟地址空间相同的副本,还获得与父进程打开的文件描述符相同的副本,子进程与父进程的PID不同。fork被调用一次,但返回两次,父进程中返回子进程的PID,子进程返回0。
6.4 Hello的execve过程
Execve函数加载并运行可执行文件,将hello加载到当前的进程中,将原先程序覆盖,释放掉原先程序的用户控件,加载hello程序到用户空间中,hello程序和源程序的进程相同,具有相同的PID。Execve加载了可执行程序的名字hello后调用启动代码,启动代码设置栈,将控制传递给hello的主函数。
6.5 Hello的进程执行
execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点,hello运行在用户模式,当程序收到一个信号时,进入内核模式,运行信号处理程序,之后再返回用户模式。
在hello运行的过程中,cpu不断切换上下文,使hello程序运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。
6.6 hello的异常与信号处理
执行过程可能出现的异常一共有四种:中断、陷阱、故障、终止。
中断:来自I/O设备的信号,异步发生,总是返回到下一条指令。
陷阱:有意的异常,同步发生,总是返回到下一条指令。
故障:潜在可恢复的错误,同步发生,可能返回到当前指令或终止。
终止:不可恢复的错误,同步发生,不会返回。
ctrl+c终止:
ctrl+z挂起:
kill杀死:
回车和乱按:输入的内容会被留在缓冲区中,当hello执行结束,返回shell中,shell会从缓冲区读取并尝试解析这些内容。
jobs:查看后台运行的进程
fg: 恢复一个后台进程
pstree: 用进程树的方法把各个进程用树状图的方式连接起来
6.7本章小结
本章概括了进程的概念和作用,shell-bash的处理过程与作用。展示了shell是如何fork新建子进程、execve如何加载进程,解释了内核如何接收各种信号和执行信号处理的过程。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:格式为“段地址:偏移地址”,是CPU生成的地址,在内部和编程使用,并不唯一。
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。在前端总线上传输的内存地址都是物理内存地址。
虚拟地址:保护模式下程序访问存储器所用的逻辑地址。
线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部份组成,段标识符: 段内偏移量。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,Base字段表示包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。
先检查段选择符中的TI字段,以决定段描述符保存在哪一个描述符表中;
由于一个段描述符是8字节长,因此她在GDT或LDT内的相对地址是由段选择符的最高13位的值乘以8得到;
Base + offset,得到要转换的线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
系统将虚拟页作为进行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式,然后再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)然后去TLB缓存里找所对应的PPN(物理页号)如果发生缺页情况则直接查找对应的PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。
7.4 TLB与四级页表支持下的VA到PA的变换
Core i7采用四级页表的层次结构。CPU产生VA,VA传送给MMU,MMU使用VPN高位作为TLBT和TLBI向TLB中寻找匹配。如果命中,则得到PA。如果TLB中没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成PA,添加到PLT。
7.5 三级Cache支持下的物理内存访问
物理地址分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,根据组索引找对应的组,找到组然后根据标记位找到对应的缓存块,根据偏移量找到对应的字节。如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。
7.6 hello进程fork时的内存映射
在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。
7.7 hello进程execve时的内存映射
exceve函数加载和执行程序Hello,需要以下几个步骤:
- 删除已存在的用户区域。
- 创建新的私有区域(.malloc,.data,.bss,.text)。
- 创建新的共享区域(libc.so.data,libc.so.text)。
4. 设置程序计数器PC,指向代码的入口点。
7.8 缺页故障与缺页中断处理
1. 缺页:将DRAM的缓存不命中成为缺页
2. 缺页故障:当cpu引用的虚拟地址所在的虚拟页的PTE有效位为0,即所对应的虚拟页不再内存,会引发缺页故障异常.
3. 缺页中断处理: 缺页异常调用内核的缺页异常处理程序.程序选择内存中的一个页作为牺牲页,如果这个页被修改过(修改位被设置),则将该页写回磁盘;然后按照目标虚拟页的PTE的磁盘地址,将磁盘的页取出放内存中,同时修改PTE. 然后返回程序中断处的当前指令,继续请求访问该虚拟地址.
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器分为两种基本风格:显式分配器、隐式分配器。
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。
堆中的块主要组织为两种形式:
- 隐式空闲链表(带边界标记)
在块的首尾的四个字节分别添加header和footer,负责维护当前块的信息(大小和是否分配)。由于每个块是对齐的,所以每个块的地址低位总是0,可以用该位标注当前块是否已经分配。可以利用header和footer中存放的块大小寻找当前块两侧的邻接块,方便进行空闲块的合并操作。
- 显式空闲链表
在未分配的块中添加两个指针,分别指向前一个空闲块和后一个空闲块。采用该策略,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。
7.10本章小结
本章介绍了存储器地址空间、段式管理、页式管理,描述了逻辑地址到线性地址再到物理地址的转换过程,以及进程fork和execve内存映射的内容,还有缺页问题和动态存储分配管理的问题。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有IO设备都被模型化为文件,所有的输入和输出都能被当做相应文件的读和写来执行。
设备管理:Linux内核有一个简单、低级的接口,成为Unix I/O,是的所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
open函数将filename转换为一个文件描述符,并返回描述符数字,
close关闭一个打开了的文件
read,fscanf用来对文件输入
write,fprintf用来对文件输出
RIO包中存在一些带缓存的读取输出的函数
8.3 printf的实现分析
vsprintf函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。
write函数是将buf中的i个元素写到终端的函数。
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本章小结
本章中简单的介绍了linux的io的接口及其设备和管理模式,unixio的接口及其使用的函数,以及printf函数和pritgetchar函数的实现方法以及操作过程。
结论
一个C语言程序hello.c,首先经过预处理得到hello.i,然后经过编译得到.s文件,再汇编生成.o文件,链接形成可执行文件hello,至此hello终于可以被运行了。之后shell为其创建进程,execve加载,分配时间片,使程序可以在硬件系统上执行。MMU将程序中使用的虚拟内存地址通过页表映射成物理地址,printf调用malloc向动态内存分配器申请堆中的内存。运行过程中可能收到信号,IO管理使程序又给了hello与外部设备之间交互的机会。hello的一生短暂却又漫长,背后是程序员与计算机系统共同努力,一个个细节环环相扣,让短短几行文字变成任何你希望的东西。计算机是神奇的,它的能力令人震惊,而它的发展永远不会停下脚步。
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i:hello.c预处理后的文本文件
hello.s:hello.i编译后的汇编文件
hello.o:hello.s汇编后的可重定位目标文件
hello:hello.o链接后的可执行文件
hello0.elf:hello.o的ELF格式
hello.elf:hello的ELF格式
hello0.txt:hello.o反汇编代码
hello.txt:hello的反汇编代码
参考文献
[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.