计算机科学与技术学院
2021年6月
本文通过现场加载并执行一个hello.c程序,串联概括了一个程序在整个执行的过程中所经历的处理与链接等操作,是对《深入理解计算机系统》的实践和高度概括,可以帮助我们理解linux执行程序的机制和汇编语言的核心知识
关键词:计算机系统,linux
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式........................................................................ - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程......................................................................... - 10 -
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.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
hello.c首先经过cpp进行预处理,然后通过ccl进行编译,as对其进行汇编,再经过ld的链接后生成可执行目标程序hello,使用shell运行fork后生成子程序,在在其中使用execve进行加载程序,hello从程序变为进程, 即从program变为process,即为p2p。之后映射虚拟内存,载入物理内存,执行代码,为程序分配时间片,程序运行结束后,父进程回收子进程,即为020。
1.2 环境与工具
CPU AMD4900HS 16GRAM 256GSSD
Unbuntu 16
Dev, vi, readelf, edb, hexedit
1.3 中间结果
hello.i:预处理文件;hello.s编译后文件;hello.o:可重定位目标文件;hello可执行目标文件;helloobj.objdmp:反汇编代码 helloelf.elf: hello.o的elf格式文件
1.4 本章小结
介绍了程序运行过程和实验环境文件等信息
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
(以下格式自行编排,编辑时删除)
概念:cpp为预处理器,根据命令修改c程序,将引用的所有库和源代码合并成一个完整的文本文件。
功能:
- 将#include语句中提到的文件添加到源代码中
- 用常量替换#define定义的值
- 根据#if后面的条件决定需要编译的代码
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析打开hello.i后我们可以发现其中增加了许多代码,都是#include引用的库中的代码,在这里直接添加到了我们的程序中,保证我们引用的库中的函数可以正常运行
2.4 本章小结
预处理过程是将我们编写代码时为了简化使用的库还原成真正可以直接运行的程序,方便在后面的过程中进行编译和运行。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译是将生成的与处理文件转换成汇编语言的程序的过程
作用:编译转换为汇编程序后,会更接近计算机的底层执行程序的逻辑,方便后面进一步的转换为机器代码,并生成可执行目标程序。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1 数据
在这次我们的hello程序中有三种数据类型,整数,字符数组和数组
首先对于字符数组即字符串,我们使用文本编辑器打开生成的hello.s汇编文件,可以轻易找到我们再.c程序中定义的字符串,前面使用string表示,在汇编程序中,被转化为utf8编码,放在特定的位置中,hello也是同理
其次对于整型,其中有全局变量,局部变量,和立即数对于全局变量,与上面的字符串类似,放在了特定的位置中直接进行了定义
对于局部变量,int i,编译器使用了将其存储在栈中的策略,在汇编代码中使用%rbp即可对栈顶进行访问;而立即数则直接转化为了数字出现在汇编代码中
对于数组,我们以argv[]为例,其中存储着只想参数的指针,在编译的过程中,编译器将数组放在指针连续的空间中,并以头部作为起始地址,访问数组中对应的元素时,只需要指定一个索引寄存器,并使用对齐修改值的操作修改,直接访问数组即可,例如途中,rax即为索引,对其加8,即可访问下一元素
3.3.2赋值
我们以对i的赋值为例,在汇编语言中往往也会使用对应的赋值语句操作,例如movl,对i对应的寄存器赋值为0
3.3.3类型转换
程序中有一个运行类型转换操作,当使用int sleepsecs = 2.5 语句时,对sleepsecs的定义为一个int整型,而对它的赋值确实一个浮点数,这里会对2.5进行类型转换为int,编译器一般会使用向0舍入的原则,直接将其变为2,并将其放在汇编代码中赋值给sleepsecs。
3.3.4计算
上面对数组的操作中已经提到了一些计算的操作,例如addq等,它们会以对应的寄存器为初始目标,并在其上在进行对应操作,c程序中我们一般的计算操作都可以转换为对应的汇编操作,例如i++就是在其对应的寄存器上再加上8访问数组中下一元素的
3.3.5比较与跳转
为了实现分支语句和循环语句,汇编语言中比较和跳转的操作,在跳转时,我们可以直接跳转,也可以使用比较后,根据结果进行跳转的选择,例如在我们的程序中,
这里我们将i与9进行比较,如果i<=9则使用jle语句进入L4。
3.3.6函数调用
在一个函数调用另一个函数时,首先将本函数的返回地址先存在栈中,在向被调用函数传递参数,前六个函数使用规定好的顺序的寄存器,按照顺序分别为rdi,rsi,rdx,rcx,r8,r9,对于剩下需要传递的参数,我们将其保存在栈中传递给被调用函数,当被调用函数返回时,一定会正好清空该函数构造的栈帧,最终栈顶会指向我们最初存下的函数返回地址,使用ret指令即可将PC更新为该返回地址后回到调用函数。
3.4 本章小结
本章叙述了汇编语言对应与我们的c程序的一些对应关系和对应转换,可以看出汇编代码的逻辑更加接近机器的底层逻辑。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编会将我们刚刚生成的汇编语言代码生成机器语言,并将其打包为可重定位目标文件,保存在hello.o中
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
分使用命令查看hello.o的elf格式:
打开生成的elf文件
我们可以在其中直观的看到程序的各个信息,Magic描述了生成该文件的系统的字的大小和字节顺序,剩下的部分包括ELF头的大小、目标文件的类别、机器类型、以及节头部表中条目的大小和数量等信息。
接下来是节头,即节头部表
其中为文件中各个接的类型,位置大小等信息
重定位节
其中包含了各个变量和函数的重定位信息,主要包含两种类型,相对寻址即R_X86_64_32,相对寻址即R_X86_64_32_PC,在后面链接为程序时,程序即可根据重定位的信息重新找到变量或函数对应的地址,并进行修改和添加,使得每个变量或函数的信息都是唯一且确定的。
4.4 Hello.o的结果解析
使用命令获得反汇编代码
与前面生成的hello.s进行对比,查看差别
1.在直接生成汇编语言时,我们的跳转使用的是形如L1, L2等的跳转标记,但是在反汇编生成的代码中,我们可以发现对应的位置变成了直接的地址。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
2.之前生成的汇编语言中,函数调用是直接使用函数名称进行的,但是在这里面,需要进行重定位的符号或者函数,目前的跳转地址都是0,需要根据相对或绝对寻址方式在后面连接的过程中生成新的地址。
4.5 本章小结
本章介绍了汇编语言转化为.o文件的过程,并介绍了可重定位文件中的对应信息等,并将反汇编文件和正向生成的汇编语言文件进行了比较。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
在前面生成可重定位文件的基础上,链接会将对应的多个可重定位文件链接加载到一块,链接可以执行于编译时,链接可以执行于编译时或者在运行时。
5.2 在Ubuntu下链接的命令
运行命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
执行命令生成hello.elf文件,查看对应信息
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
使用edb打开后,可以研究发现,首先在从0x400000的部分到0x401000的地址中加载程序,一直到0x400fff中的每个节都对应了上图中的地址部分,而0x400fff之后存放了程序的.dynamic-.shstrtab节,我们可以查看程序头
其中提供了各个段在虚拟地址和物理地址中的大小位置标志等信息,例如VirAddr注明了对应的虚拟地址,PhysAddr注明了对应的物理地址。
5.5 链接的重定位过程分析
运行命令
通过与helloobj.objdump进行对比,我们可以看到其中多出来了一些节内容,列举如下
.interp
.note.ABI-tag
.hash
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rala.plt
.init
.plt
.fini
.en_frame
.dynamic
.got
.got.plt
.data
.comment
我们可以归纳出以下结论
- 在进行链接时,我们同时引用了动态链接器加载入了/lib64/ld-linux-x86-64.so.2,crto、crti.o, crtn.o, 添加了许多模块,例如程序入口,初始化函数等,而另一个动态链接共享库中真正定义了我们一般正常使用的函数入printf, sleep等。
- 与上面所说的相对PC寻址和绝对寻址,一些重定位条目中需要进行重定位的内容, 根据它的类型是R_X86_64_32还是_X86_64_32_PC,连接器将它们重新分配正确的调用地址。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
子程序列举如下:
- ld-2.27.so!_dl_start
- ld-27.so!_dl_init
- hello!_start
- libc-2.27.so!__libc_start_main
- -libc-2.27.so!__cxa_atexit
- -libc-2.27.so!__libc_csu_init
- hello!_init
- libc-2.27.so!_setjmp
- -libc-2.27.so!_sigsetjmp
- --libc-2.27.so!__sigjmp_save
- hello!main
- hello!puts@plt
- hello!exit@plt
- *hello!printf@plt
- *hello!sleep@plt
- *hello!getchar@plt
- ld-2.27.so!_dl_runtime_resolve_xsave
- -ld-2.27.so!_dl_fixup
- --ld-2.27.so!_dl_lookup_symbol_x
- libc-2.27.so!exit
5.7 Hello的动态链接分析
对于动态共享链接库中PIC函数,需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表实现函数的动态链接。
5.8 本章小结
本章介绍了链接的过程以及相比于之前没有连接的程序,程序增加的部分,以及重定位寻址等知识的具体操作。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中的程序的实例,包括文本区域、数据区域、和堆栈。
CPU的处理过程为每个进程提供了一种抽象的假象, 我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
Shell是系统运行用户程序的方式, Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
首先, shell通过命令行分析函数分析用户输入的命令行,并声称argv和envp,接下来,shell将判断程序是否为shell的内置程序, 如果是则立刻执行,如果不是则使用fork创建子进程,并使用execve加载我们输入的制定运行程序,接下来即判断用户是否要求后台运行,如果不是则让父进程等待回收子进程。
6.3 Hello的fork进程创建过程
与上面的运行过程相同,shell会首先受到我们运行hello的命令和我们输入的其他参数,例如学号等,接下来,shell会判断我们运行的程序不是shell中内置程序,所以使用fork,fork函数调用一次,返回两次,给子进程返回0,给父进程返回子进程的PID,父进程和子进程拥有不同的PID,并并行运行,具体的内部运行顺序由CPU随机决定。
6.4 Hello的execve过程
excve函数会在保存当前进程的上下文, 并在其中加载并运行新的程序,在这里即为我们的hello程序,execve被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。
在新程序的内存映像中,与源程序是私有的写时复制区域
6.5 Hello的进程执行
逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
内核态和用户态的转换:hello进程在正常运行时处于用户态阶段,当hello程序需要进行系统调用时,会转换进入内核态,在这个过程中会进行上下文切换,在内核态时可以访问内核所有信息,在完成系统调用切换会用户态时,系统会检查信号的状况并选择一个执行对应操作。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
按下ctrl-z之后,shell父进程收到SIGSTP信号, 将hello进程挂起,通过ps命令我们可以看出hello进程没有被回收
按下ctrl-c之后,shell父进程收到SIGINT信号,信号处理函数的逻辑是结束hello,并回收hello进程
乱按只是将屏幕的输入缓存到stdin,当下次运行到有关输入的函数例如gets等后,其他字串会当做shell命令行输入
6.7本章小结
本章叙述了hello在被shell调用过程中整个系统的进程调用和信号过程。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。
逻辑地址:程序代码经过编译后出现在 汇编程序中地址。
线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式。
虚拟地址类似线性地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值。逻辑地址的格式为段选择符:段内偏移地址
我们首先观察段选择描述符中的T1字段是0or1,得知当前要转换的是GDT中的段,还是LDT中的段,再根据指定的相应的寄存器,得到其地址和大小,得到一个数组,接下来拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,即基地址,基地址加上Offset即为要转换的下一个阶段的地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
分页把内存划分成大小固定的若干单元,每个单元称为一页,每页包含4k字节的地址空间。每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表
为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。32位的线性地址被分成3个部分:最高10位页目录表偏移量,中间10位页表偏移量,最低12位是物理页内的字节偏移量。页目录表的大小为4k,包含1024项,每个项4字节,项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。
7.4 TLB与四级页表支持下的VA到PA的变换
MMU根据VPN访问TLB和页表,获取其中的PPN,并与VPO进行合并,即可得到PA
7.5 三级Cache支持下的物理内存访问
首先直接访问一级缓存,不命中时访问二级,再不命中访问三级,接下来主存,访问到后即逐步将新的内容加载到上级缓存中,必要时会进行驱逐。
7.6 hello进程fork时的内存映射
当fork函数被shell进程调用时,内核为新进程分配一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本,将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
首先删除已存在的用户区域创建新的区域结构: 代码和初始化数据映射到.text和.data区(目标文件提供), .bss和栈映射到匿名文件设置PC,指向代码区域的入口点
7.8 缺页故障与缺页中断处理
7.9动态存储分配管理
维护一个虚拟内存区域“堆”,将堆视为=不同大小的块的集合来维护,分为已分配块和空闲块
隐式空闲链表为所有的块构成一个链表,为了方便合并空闲块,每个空闲块存在头部和脚部,可以分为三种情况进行合并,分别为这个空闲块的前面为空闲块,后面不是,或者后面是,前面不是,或者后面前面都是。在这三种情况下分别进行头部脚部的更新操作,即可极大的减少外部碎片发生的几率
对于显式空闲链表,是所有的空闲块构成一个链表,这样在我们寻找空闲块的过程中,就不需要搜索所有块了,只需要寻找所有的空闲块。
7.10本章小结
本章主要介绍了计算机的虚拟内存和物理内存的对应关系,以及在程序运行时,分配内存等操作。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
1. 打开和关闭文件:open()and close()
2. 读写文件:read() and write()
3. 改变当前的文件位置 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码,直到接受到回车键才返回。
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return(–n>=0)?(unsigned char)*bb++:EOF;
}
8.5本章小结
本章主要阐述了Linux I/O和UnixI/O接口的关系方式和信息,并分析了一些经典函数例如printf和getchar
(第8章1分)
结论
Hello.c程序到最后经过了以下的过程 预处理---编译---汇编---链接---运行---创建子进程---运行程序---执行指令
预处理将我们引用的库转换为代码放到代码中,编译将完全体的代码转换为汇编代码,汇编将汇编代码生成可重定位目标文件,链接将我们运行时需要的库加载进来,并进行重定位操作。运行调用shell,创建进程加载我们的程序,执行指令即为CPU将我们的程序转换为底层的指令信息。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.i:预处理文件;hello.s编译后文件;hello.o:可重定位目标文件;hello可执行目标文件;helloobj.objdmp:反汇编代码 helloelf.elf: hello.o的elf格式文件
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[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.
(参考文献0分,缺失 -1分)