目 录
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简介
1.编写hello的程序建立.c文件,得到hello.c的源程序。
2.运行C预处理器(cpp)将其进行预处理生成hello.i文件。
3.运行C编译器(ccl)将其进行翻译生成汇编语言文件hello.s。
4.运行汇编器(as)将其翻译成一个可重定位目标文件hello.o。
5.运行链接器(ld)将hello.o和系统目标文件组合起来,创建了一个可执行目标文件hello。
6.通过shell输入./shell,shell通过fork函数创建了一个新的进程,之后调用execve映射虚拟内存,通过mmap为hello程序开创了一片空间。
7.CPU从虚拟内存中的.text,.data节取代码和数据,调度器为进程规划时间片,有异常时触发异常处理子程序。
8.程序运行结束时,父进程回收hello进程和它创建的子进程,内核删除相关数据结构。
1.2 环境与工具
硬件:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件:Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc
实验环境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c 源文件
hello.i 经过预处理的源文件
hello.s 汇编文件
hello.o 可重定位目标文件
hello 可执行文件
hello.elf hello.o的ELF格式
hello1.txt hello.o的反汇编
hello2.txt hello的反汇编代码
hello0.elf hello的ELF格式
1.4 本章小结
HelloWorld是我们接触到的第一个程序,弄懂它在计算机中运行时的过程对理解整个计算机系统有着很大的作用
第2章 预处理
2.1 预处理的概念与作用
预处理器cpp根据字符#开头的命令修改源程序,最后生成.i文件
2.2在Ubuntu下预处理的命令
gcc –E –o hello.i hello.c
2.3 Hello的预处理结果解析
可以观察到,hello.i文件相较于hello.c文件多了很多的内容。因为hello.c文件中存在头文件,在预处理时系统将其展开,替换为宏文件中内容和存储位置。
2.4 本章小结
预处理是对宏定义和头文件的处理过程,方便系统对源文件进行接下来的处理。
第3章 编译
3.1 编译的概念与作用
编译器将预处理过的文件转换为汇编文件,是计算机能够读懂的语言,即机器码。
3.2 在Ubuntu下编译的命令
gcc –S hello.i –o hello.s
3.3 Hello的编译结果解析
3.3.1 argc存储在-20(%rbp)的内存位置中,与4比较,若不等则输出相应内容。
3.3.2 i存储在-4(%rbp)内存位置中,将i初始化为0
3.3.3 在每次.L3的循环中让i与7比较,小于7时进入.L4过程。将要输出的两个字符串存储到寄存器%rdx和%rax中,执行printf语句将其按照“Hello %s %s\n”的格式打出。再执行atoi和sleep语句,让系统停止运行一定时间,这取决于argv[3]中存储的字符串转化的数字。然后让i=i+1。当i=7时,执行getcahr()
3.4 本章小结
编译使得计算机将文件中的高级语言转变为机器能识别的低级语言,通过赋值、跳转、比较等达到函数的预期目的。
第4章 汇编
4.1 汇编的概念与作用
汇编器接受hello.s生成可重定位目标文件,将汇编代码转换为机器指令,使其在链接后能被机器识别并执行。可重定位目标文件包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来。
4.2 在Ubuntu下汇编的命令
gcc –c –o hello.o hello.s
4.3 可重定位目标elf格式
4.3.1 ELF头:ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序(小端法)。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。
4.3.2节头:记录各个节的名称、类型、地址、偏移量。
4.3.3 重定位节:保存需要被修正的信息,调用外部函数或引用全局变量的指令都需要被修正。
4.3.4 符号表:存放程序中调用的外部函数和全局变量,此时位置未确定。
4.4 Hello.o的结果解析
与汇编语言的相异之处:
1.跳转不再使用段名称,而是具体的地址;调用函数call同理;
2.立即数变为十六进制表示,方便计算机使用
4.5 本章小结
对hello.s汇编产生可重定位目标文件hello.o,包括ELF头、节头、重定位节和符号表。hello.s和hello.o的反汇编相比也存在差异。
第5章 链接
5.1 链接的概念与作用
链接过程是将多个可重定位目标文件整合到一起,生成一个可执行文件的过程。链接过程使得在完成一个应用时人为地将其分成一个个小部分开发或修改。
5.2 在Ubuntu下链接的命令
链接:
ld-ohello-dynamic-linker/lib64/ld-linux-x86-64.so.2/usr/lib/x86_64-linux-gnu/crt1.o/usr/lib/x86_64-linux-gnu/crti.ohello.o/usr/lib/x86_64-linux-gnu/libc.so/usr/lib/x86_64-linux-gnu/crtn.o
反汇编:
objdump –D hello > hello2.txt
5.3 可执行目标文件hello的格式
5.3.1 ELF头:与hello.o相比,从可重定位目标文件变成可执行文件。
5.3.2 节头:因为将多个文件整合到一起,所以节的数目增多
5.3.3重定位节
5.3.4符号表:多了标准库函数的表项,并且确定了地址
5.4 hello的虚拟地址空间
argc存储在[rbp-0x14]的内存空间中,与4作比较。不等执行printf后退出,相等则跳转到之后的循环处。
i初始为0后存放在[rbp-4]中,每一次循环都加1再与7作比较。循环中先组合需要输出的“Hello %s %s\n”,然后调用printf函数输出。再将argv[3]的内容存储在寄存器rdi中,调用atoi函数将转化的整型数存储在寄存器rdi中。再调用sleep函数让程序停止。循环直到i=7.
离开循环后调用getchar(),结束程序。
5.5 链接的重定位过程分析
hello.o中只存在main一个函数,而hello中由于已经将可重定位目标文件打包成可执行文件,存在大量的库函数。main函数的地址也从默认的0处初始化。引用库函数跳转的地址也被添加进去
链接器将所有相同类型的section合并为一个新的。汇编器在遇到最终目的不确定的引用时产生可重定位条目,告诉链接器合成可执行文件时如何修改引用。依赖可重定位条目,修改符号引用使其指向正确的地址。
5.6 hello的执行流程
_start 004010f0
main 00401125
printf 00401040
exit 00401070
atoi 00401060
sleep 00401080
getchar 00401050
5.7 Hello的动态链接分析
动态链接依靠GOT实现,根据ELF找到GOT的位置。
dl_init前
dl_init后
链接是将各种代码和数据收集并组合成一个文件的过程。将可重定位目标文件和必要的系统文件组合起来生成一个可执行目标文件。它使得程序可以分成多个部分,在编程和修改时可以按照部分进行处理
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据、栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
6.2 简述壳Shell-bash的作用与处理流程
Shell是一个交互型应用级程序,为使用者提供操作界面,接收用户命令,然后调用相应的应用程序。
shell首先对用户输入的命令进行解析,判断命令是否为内置命令。如果为内置命令,调用内置命令处理函数;如果不是内置命令,就创建一个子进程,将程序在该子进程的上下文中运行。判断为前台程序还是后台程序,如果是前台程序则直接执行并等待执行结束,如果是后台程序则将其放入后台并返回。
6.3 Hello的fork进程创建过程
fork()函数会为父进程创建一个子进程,二者独立并发执行,有相同但独立的地址空间,子进程可以继承父进程打开的文件。fork函数返回两次,一次返回父进程,一次返回新创建的子进程。
6.4 Hello的execve过程
调用加载器,在可执行程序的main函数之前,删除子进程现有的虚拟内存段,并创建新的代码、数据、堆和栈段,将控制传给新程序。该函数包括三个参数:可执行程序的文件名filename,执行程序需要输入的参数列表argv[],环境变量列表envp[]。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
对于一个进程,它有自己的独立的逻辑控制流和私有的地址空间。逻辑控制流是程序单步执行程序计数器的一系列数值,与可执行程序指令一一对应。
如果系统调用因为等待某个事件而阻塞,内核切换到另一个进程。上下文为内核重新启动一个被抢占的进程所需的状态。切换时先保存当前进程的上下文,再恢复某个之前被抢占的进程的上下文,然后将控制还给恢复的进程。
一个进程执行它的控制流的一部分的时间段叫做时间片。
Shell最开始处于用户态,可以转化到和心态进行对内核的修改或读取,结束后再回到用户态。
6.6 hello的异常与信号处理
6.6.1 正常情况
6.6.2 乱按(包括回车):作为命令输入
6.6.3 Ctrl+Z:挂起
6.6.4 Ctrl+C:终止
6.6.5 ps
6.6.6 jobs
6.6.7 pstree
6.6.8 fg:回复前台运行
6.6.9 kill
6.7本章小结
本章介绍了有关进程管理的多个概念。介绍了Shell的作用和处理流程,以及利用fork创建子进程、利用execve加载进程的方法。展示hello程序执行的具体过程,以及异常信号的处理机制。操作系统中同时有多个程序执行,但我们看到的也像是操作系统仅在运行前台程序一样,这是通过上下文切换实现的。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:为段地址+偏移地址,是CPU生成的地址,在内部和编程使用,并不唯一,指定一个操作数或者是一条指令
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。在前端总线上传输的内存地址都是物理内存地址。
虚拟地址:保护模式下程序访问存储器所用的逻辑地址。即线性地址。
线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是一个16位长的字段组成,其中前13位是一个索引号。
索引号就是段描述符的索引。段描述符具体描述了一个段地址,这样,很多段描述符就组成段描述符表。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符。
Base字段,表示的是包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。
一些全局的段描述符,就放在“全局段描述符表”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表”中。全局段描述符表在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而局部段描述符表则在ldtr寄存器中。
首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
看段选择符的T1为0还是1,知道当前要转换是全局段描述符表中的段,还是局部段描述符表中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它的即基地址就知道了。把Base + offset,就是要转换的线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址即虚拟地址,用VA来表示。VA被分为虚拟页号与虚拟页偏移量,CPU取出虚拟页号,通过页表基址寄存器来定位页表条目,在有效位为1时,从页表条目中取出信息物理页号,通过将物理页号与虚拟页偏移量结合,得到由物理地址和物理页偏移量组合的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,就必须查询页表条目。TLB是一个对页表条目的缓存,每当查询页表条目时,内存管理单元先询问TLB中是否存有该条目,若有,它可以很快地得到结果;否则,内存管理单元需要按照正常流程到高速缓存/内存中查询页表条目,把结果保存到TLB中,最后在TLB中取得结果。
将虚拟地址的虚拟页号划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。第一级VPN结合基址寄存器得到一个页表条目,其中存放下一级页表的基址,再结合VPN2,得到第三级页表基址,继续寻找,以此类推,直到最后确定对应的物理页号。
多级页表的使用减少了内存要求。第一,如果一级页表中的一个页表条目是空的,那么相应的二级页表就根本不会存在。第二,只有一级页表才需要总是在主存中。虚拟内存系统可以在需要时创建、页面调入或调出二级页表。
7.5 三级Cache支持下的物理内存访问
在从TLB或者页表中得到物理地址后,根据物理地址从cache中寻找。在L1中寻找物理地址要检测是否命中,不命中则紧接着寻找下一级L2、L3。如果L3也不命中,则需要从内存中将对应的块取出放入cache中。
7.6 hello进程fork时的内存映射
在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。它有着自己独立的虚拟内存空间,并且还拥有自己独立的逻辑控制流,它同样可以拥有当前已经可以打开的各类文件信息和页表的原始数据和样本,为了有效保护进程的私有数据和信息,同时为了节省对内存的消耗,进程的每个数据区域都被内核标记起来作为写时复制。
7.7 hello进程execve时的内存映射
(1)删除已经存在的用户区域(即除了内核以外的部分,包括代码、数据、bss、堆、栈、共享库内存映射区域);
(2)映射私有区域:为新程序的用户区域创建新的区域结构,且它们均为私有写时复制的。其中代码和数据区域被映射为hello中的.text和.data段,bss区域是请求二进制零的,其长度包含在hello文件中;栈和堆也是请求二进制零的,其初始长度均为0.
(3)映射共享区域:如果一个程序与共享对象链接,那么这些对象需要被映射到虚拟地址中的共享区域内。
(4)设置程序计数器PC。设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
当指令引用一个相应的虚拟地址,而与该地址相应的物理页面不在内存中,就会触发缺页故障,内核调用缺页处理程序。首先检查虚拟地址是否合法,若不合法则触发一个段错误,程序终止。然后检查进程是否有读、写或执行该区域页面的权限,若无则触发保护异常,程序终止。在两步检查都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个个连续的虚拟内存片,要么是已分配的,要么是空闲的。分配器有两种基本风格。两种风格都要求应用显示地分配块。它们的不同之处在于由哪个实体来负责释放已分配的块。
1.显示分配器:要求应用显示的释放任何已分配的块。例如C标准库提供一个叫做malloc程序包的显示分配器。
2.隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,那么就释
这个块。隐式分配器也叫垃圾收集器。
7.10本章小结
本章详细阐述了hello程序是如何存储,如何经过地址翻译得到最终的物理地址。介绍了hello的四级页表的虚拟地址空间到物理地址的转换。阐述了三级cashe的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
8.2.1 打开文件:int open(char *filename, int flags, mode_t mode);
filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
8.2.2 关闭文件:int close(int fd);
fd是需要关闭的文件的描述符,返回操作结果。
8.2.3 读文件: ssize_t read(int fd, void *buf, size_t n);
从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
8.2.4 写文件: ssize_t write(int fd, const void *buf, size_t n);
从内存位置buf处复制至多n个字节到描述符为fd的当前文件位置。
8.3 printf的实现分析
printf()按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
vsprintf函数作用是接受确定输出格式的格式字符串fmt(输入)。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
write函数将buf中的i个元素写到终端。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,分析了printf函数和getchar函数的实现。Linux的文件系统将许多概念都抽象成文件:网络、磁盘或是终端。这种抽象提供了一种一致的对不同设备的处理方式。
结论
程序从诞生到运行再到终止流程如下
1、编写代码:用c语言写hello.c文件;
2、预处理:从hello.c生成hello.i文件;
3、编译:由hello.i生成hello.s汇编文件;
4、汇编:将hello.s文件翻译为机器语言指令,并打包成可重定位目标程序hello.o;
5、链接:将hello.o可重定位目标文件和动态链接库链接成可执行目标程序hello;
6、运行:在shell中输入命令;
7、创建子进程:shell调用fork为程序创建子进程;
8、加载:shell调用execve函数,将hello程序加载到该子进程,映射虚拟内存;
9、执行指令:CPU为进程分配时间片,加载器将计数器预置在程序入口点,则hello可以顺序执行自己的逻辑控制流;
10、访问内存:MMU将虚拟内存地址映射成物理内存地址,CPU通过其来访问;
11、动态内存分配:根据需要申请动态内存;
12、信号:shell的信号处理函数可以接受程序的异常和用户的请求;
13、终止:执行完成后父进程回收子进程,内核删除为该进程创建的数据结构。
计算机设计是很精妙的。由于机器不可能像人一样识别高级语言,所以要将其转化为机器可以识别的底层语言;为了方便编译要将其分为多部分分别编写再打包;为了加快运行速度采取了进程的思路和多级cache。
附件
hello.c 源程序
hello.i 预处理后文件
hello.s 编译后的汇编文件
hello.o 汇编后的可重定位目标执行文件
hello 链接后的可执行文件
hello.elf hello.o的ELF格式
hello1.txt hello.o的反汇编
hello2.txt hello的反汇编代码
hello0.elf hello的ELF格式
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E.Bryant.深入理解计算机系统.北京:机械工业出版社,2019:6-1.
[2] https://www.cnblogs.com/pianist/p/3315801.html
[3] https://www.cnblogs.com/diaohaiwei/p/5094959.html