摘 要
本文在linux操作系统下对C语言程序hello.c的运行全过程进行了分析。 分析了从c文件转化为可执行文件过程中的预处理、编译、汇编和链接阶段,和可执行文件执行过程中的进程管理、存储空间管理和I/O管理的原理。
关键词:预处理,编译,汇编,链接,进程,加载运行,异常处理,虚拟内存,I/O函数;
(摘要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 Hello简介
P2P:程序诞生的第一步自然是要书写代码,也就是程序文本,我们称之为hello.c。书写完代码之后,由于程序文本中存在
O2O:shell作为命令行解释器,在接收到运行hello的指令后,会调用操作系统内核代码中的加载器来运行它。加载器会通过execve函数在fork函数产生的子进程上下文中加载hello。具体步骤为:先删除当前虚拟地址的用户部分已存在的数据结构,为hello的代码、数据、bss和栈区域创建新的区域结构,然后映射共享区域,设置程序计数器,使之指向代码区域的入口点,执行程序的第一条指令,CPU为hello分配时间片执行逻辑控制流。hello通过Unix I/O管理来控制输出。hello执行完成后shell会回收hello进程,并且内核会从系统中删除hello所有痕迹。至此,hello结束了它的一生。
1.2 环境与工具
硬件环境:Intel Core i7-8750H CPU; 2.20GHz;16RAM
软件环境:Windows 10;Ubuntu18.04;VMware Workstation 14 Pro
实验工具:CodeBlocks 64位;readelf;vim+gcc;Hexedit;edb
1.3 中间结果
文件名 作用
hello.c 大作业源程序
hello.i 预处理后的源程序
hello.s hello.i经编译后的汇编程序文件
hello.o hello.s经过汇编器生成的可重定位目标文件
hello Hello.o与其他函数链接后的可执行目标文件
Asm_hello_o hello.o反汇编生成的汇编文件
ELF_hello_o hello.o的ELF格式
Asm_hello hello反汇编生成的汇编文件
ELF_hello hello的ELF格式
1.4 本章小结
本章介绍了hello程序诞生的P2P与O2O过程,以及实验的环境,所需工具和程序的中间文件。
(第1章0.5分)
2.1 预处理的概念与作用
2.2在Ubuntu下预处理的命令
图2.2.1 在Ubuntu下预处理的命令
我们可以看到,-E指令是用来查看hello.c经预处理之后得到的hello.i的文本内容,目录中也确实出现了hello.i文件。
2.3 Hello的预处理结果解析
图2.3.1 hello.i文件的末尾部分
hello.i文件过于庞大,我们可以看到它将近有4000行。原本的编译预处理指令消失了,取而代之的是具体翔实的声明。
以下是对hello.i文件内容的部分展示。它们分别描述了运行库在计算机上的位置以及函数所需函数的声明。
图2.3.2 hello.i文件中的部分内容
图2.3.3 hello.i文件中的部分内容
2.4 本章小结
本章主要讲解了预处理的概念与作用,并展示了预处理在Ubuntu上的指令,对hello.i文件的内容分析以及hello.i文件的部分内容。
(第2章0.5分)
3.1 编译的概念与作用
3.3 Hello的编译结果解析
hello.s文件内容如下:
i是一个局部变量。局部变量一般存在寄存器或堆栈中。由汇编代码可以得到i存于%ebx中。
图3.3.2 局部变量i的存储位置
3.字符串:程序中出现了两个字符串,分别为"用法: Hello 学号 姓名 秒数!\n"以及"Hello %s %s\n"。它们都位于只读数据段中。
图3.3.3 程序中的字符串
2.赋值
i = 0在程序执行循环操作时被赋值。对应指令是:movl $0, %ebx。
mov指令其他类型如下:
图3.3.4 mov指令(图片来自CSAPP的PDF版截图)
3.算术操作
常用的算术逻辑指令为:
图3.3.5 算术逻辑指令(图片来自CSAPP的PDF版截图)
在汇编代码中主要体现为每次对循环变量i加1.
4.关系操作
关系操作主要是指cmp指令与test指令,分别用于比较两个数的大小以及判断两个数是否相等。这两个指令不会改变寄存器中的值,只会改变条件码。
在程序中具体指:
1)判断循环变量i是个否满足循环条件
2)判断argc是否等于4
5.数组/指针/结构操作
循环体中取数组元素值的操作为:
程序中对argv[1],argv[2]的寻址被编译为基址+偏移的寻址方式。
(%rbp)+8存放的是argv[1],(%rbp)+16存放的是argv[2]。
6.控制转移
控制转移主要为jmp,以及jxx指令。
在程序中,它对应着:
1)在argc不等于4时的条件跳转
2)在循环变量小于特定值时的跳转
7.函数操作
程序中的主要函数有:
1.Main函数:被系统函数_libc_start_main调用。内核执行c程序时调用特殊的启动例程,并将启动例程作为程序的起始地址,从内核中获取命令行参数和环境变量地址,执行main函数。
2.Getchar函数:main函数通过call指令调用getchar()函数,且getchar()函数无参数。getchar()函数返回值类型为int,如果成功返回用户输入的ASCII码,出错返回-1。
图3.3.6 getchar函数的调用
3.Printf函数:printf参数根据字符串中的输出占位符数量来决定的。
图3.3.7 printf函数的调用
4.Exit函数:使程序根据用户设定好的某种情况代号退出。Exit(1)说明程序以情况1退出。
图3.3.8 exit函数的调用
5.Sleep函数:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
图3.3.9 sleep函数的调用
3.4 本章小结
本章主要介绍了编译的概念与作用,并对汇编文件进行了详细的分析。从数据的赋值,到算术逻辑指令,再到函数的调用与控制的传递。汇编代码展示了计算机执行该程序的思想与步骤,帮助我们在机器层面上分析程序的执行过程,值得仔细钻研。
(第3章2分)
4.1 汇编的概念与作用
4.3 可重定位目标elf格式
通过命令生成ELF格式文件
图4.3.1 生成hello.o的ELF格式文件
其内容如下所示:
图4.3.2 ELF文件内容
1.ELF头:
ELF头首先以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。剩下部分列出了包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小,目标文件的类型(如可重定位,可执行或共享),机器类型(如X86-64),节头部表的文件偏移,以及节头部表中条目的大小和数量。
图4.3.3 ELF头
2.节:
夹在ELF头和节头部表的部分都是节。以下列出部分节的内容
1).text:已编译程序的机器代码,文件中信息如下:
图4.3.4 .text节
2).rela.text:一个.text节中位置的列表,用于重定位中确定代码对应的绝对物理地址。信息如下:
图4.3.5 .rela.text节
3).data:存放已初始化的全局和静态C变量。局部C变量在运行时被保存到栈中,既不出现在.data节,也不出现在.bss节。信息如下:
图4.3.6 .data节
4).bss节:存放未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它只是一个占位符,其目的是提高空间效率。程序运行时,在内存中分配这些变量,初始值为0。信息如下:
图4.3.7 .bss节
5).rodata节:只读数据,信息如下:
图4.3.8 .rodata节
6).symtab节:存放在程序中定义和引用的函数和全局变量信息的符号表,不包含局部变量的信息。如下所示:
图4.3.9 .symtab节
7).strtab节: 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。字符串表就是已null结尾的字符串序列。
图4.3.10 .strtab节
综合信息如下:
图4.3.11 节头
3.重定位条目
汇编器在遇到对最终位置未知的目标引用时,就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中,已初始化数据的重定位条目放在.rel.data中。条目格式如下:
图4.3.12 重定位条目结构体(图片来自CSAPP的PDF版截图)
Offset为节偏移;type为重定位类型(相对引用还是绝对引用);symbol为符号名字;addend为一个常数。
重定位算法如下所示:
图4.3.13 重定位算法(图片来自CSAPP的PDF版截图)
4.符号表
每个可重定位目标模块都有一个符号表,它包含符号的定义和引用的符号的信息。符号表是由汇编器构造,使用编译器输出到汇编语言.s文件中的符号。每个符号表是一个条目的数组,信息如下所示:
图4.3.15 符号表
4.4 Hello.o的结果解析
通过objdump -d -r hello.o命令得到hello.o的反汇编文件
图4.4.1 hello.o的反汇编文件
我们可以看出反汇编文件asm_hello_o与汇编文件hello.s的区别:
1.反汇编文件中出现了16进制数的机器码,这些16进制数即为每一条汇编代码的对应机器代码。
图4.4.3 指令对应机器码
2.反汇编代码中出现了相对偏移地址。作为可重定位目标文件的反汇编,我们可以推导出:可重定位目标文件在重定位之前,其物理地址是不确定的,每一个目标模块的地址都是从0开始,只有在这些模块重定位后,才会确定每个模块的真实物理地址。
图4.4.4 地址偏移
3.在反汇编文件中,对每一个符号都表明了引用类型。
图4.4.5 引用类型
4.汇编文件中使用段的标号作为分支后跳转的地址,反汇编代码中用相对main函数起始地址的偏移量来表示跳转的地址。
图4.4.6 跳转地址
5.汇编文件中函数调用后直接跟着函数的名字,反汇编代码中函数调用的目标地址是当前的下一条指令,也是用相对main起始地址的偏移量来表示。
图4.4.7 调用函数
4.5 本章小结
本章主要介绍了汇编的概念与作用,介绍了可执行目标文件的相关信息,主要包括ELF头,节,重定位条目,符号表,还比较分析了反汇编文件与汇编文件的不同。可以看出可重定位目标文件就是在为模块整合做准备。
(第4章1分)
5.1 链接的概念与作用
1.ELF头:用于描述文件的总体格式,与可重定位文件不同的是,它还包含了程序的入口点,即程序将要执行的第一条指令的地址。
图5.3.2 可执行目标文件的ELF头
2.节
可执行目标文件的节与可重定位目标文件的节有许多相同的,这里列出部分不同的节并说明其作用:
1)interp节:指定动态链接器在操作系统中的位置
2)dynamic节:该节中保存了动态链接器所需要的基本信息,是一个结构数组
3).init节:该节定义了一个小函数_init,程序的初始化代码会调用它。
4).plt 过程链接表,包含动态链接器调用从共享库导入的函数所必须的相关代码,存在于text段中。
5).got节保存全局偏移表。它和.plt节一起提供了对导入的共享库函数访问的入口,由动态链接器在运行时进行修改。
6).dynsym节:保存共享库导入的动态符号信息,在.text段中存储
7).dynstr节:保存动态符号字符串表,存放一系列字符串,代表了符号的名称,以null作为终止符。
具体信息如下:
3.程序头:将程序分为数段
具体信息如下:
图5.3.4 程序头表
5.4 hello的虚拟地址空间
2.虚拟地址各段空间信息:
1)PHDR段:指定程序头表在文件及程序内存映像中的位置和大小
可以看出:起始地址为0x400040,大小为0x1c0,Edb相应内容为:
图5.4.2 PHDR的edb内容
2)INTERP段: 指定要作为解释程序调用的以空字符结尾的路径名的位置和大小
可以看出:起始地址为0x400200,大小为0x1c,edb相应内容为:
图5.4.3 INTERP的edb内容
3)LOAD段:指定可装入段,通过p_filesz和p_memsz进行描述。文件中的字节会映射到内存段的起始位置
可以看出,第一个LOAD段起始地址为0x400000,大小为0x8f4
第二个LOAD段起始地址为0x600e00,大小为0x270
Edb相应内容为:
图5.4.4 LOAD的edb内容
4)DYNAMIC段: 指定动态链接信息
可以看出,起始地址为0x600e10,大小为0x1e0
图5.4.5 DYNAMIC的edb内容
5)NOTE段: 指定辅助信息的位置和大小
可以看出,起始地址为0x40021c,大小为0x20
图5.4.6 NOTE的edb内容
7)GNU_RELRO段: 指定在重定位结束之后哪些内存区域需要设置只读
可以看出,起始地址为0x600e00,大小0x200
图5.4.7 GNU_RELRO的edb内容
5.5 链接的重定位过程分析
通过objdump命令生成hello的反汇编文件
图5.5.1 生成反汇编文件
hello.o的反汇编文件和hello的反汇编文件有许多不同,具体如下:
1)Hello的反汇编文件中多出来许多函数的信息,这是将其他目标模块重定位的结果
图5.5.2 hello反汇编文件中的其他函数
2)Hello中的地址为具体地址,而hello.o中的地址为地址偏移
图5.5.3 地址表示不同
3)Hello.o中存在许多用于重定位的机器码占位符,而hello中无此项,因为已经完成重定位了
图5.5.4 占位符消失
5.6 hello的执行流程
Hello执行流程如下:
程序加载 ld-2.27.so!_dl_start 0x7f5d6118fea0
ld-2.27.so!_dl_init 0x7f5d6119e630
程序运行 hello!_start 0x4005c0
hello!puts@plt 0x400560
程序退出 hello!exit@plt 0x4005a0
5.7 Hello的动态链接分析
在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定,将过程地址的绑定推迟到第一次调用该过程时。
延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:
1.PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
2.GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
图5.7.1 .got.plt节
在节头表中找到GOT的起始位置为0x601000
调用_dl_start之前可以看出有16个为0的字节:
在调用之后字节值发生了变化
GOT[[2]]是动态链接器在ld-linux.so模块中的入口点,共享库模块信息如下:
图5.7.2 共享库模块
5.8 本章小结
本章主要介绍了链接的概念作用,分析可执行文件hello的ELF格式及其虚拟地址空间,同时通过实例分析了重定位过程、加载以及运行时函数调用顺序以及动态链接过程,深入理解了链接和重定位的过程。
(第5章1分)
6.1 进程的概念与作用
此外,进程也为每个程序提供一个他们独占内存的假象,称为私有地址空间。
图6.5.2 私有地址空间(图片来自CSAPP的PDF版截图)
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程的决定叫做调度,这一过程称为上下文切换。
图6.5.3 上下文切换(图片来自CSAPP的PDF版截图)
6.6 hello的异常与信号处理
异常:控制流的突变,用来响应某种变化。
异常分为4类:
1)中断:异步异常,来自处理器外部的I/O设备。异常处理后会执行下一条指令
2)陷阱:同步异常,是执行系统调用函数的结果。函数调用结束后会执行下一条指令
3)故障:同步异常,由错误情况引起,如缺页,浮点异常等等。异常处理成功则重新执行该指令,否则程序终止
4)终止:同步异常,由致命错误造成。该异常将终止程序
信号处理:在发生异常时会产生信号,用来通知系统。
常用信号为:
图6.6.1 信号类型(图片来自CSAPP的PDF版截图)
3.在输入./hello 学号 姓名 循环间隔后,对进程的几种处理方式:
1)程序正常退出:字符串输出8次后正常退出,进程被回收
2)Ctrl+c:使内核发送一个SIGINT信号,使程序终止,信号处理程序会回收子进程
3)随便乱按:输入字符后键入回车键会将字符串内容存入缓冲区,作为终端的下一条命令
4)Ctrl+z:程序将被暂时挂起,并没有被回收,它会等待其他信号令其继续运行
6.7本章小结
本章主要介绍了进程的概念与作用,shell的作用和处理流程,执行hello时的fork和execve过程。并分析了hello的进程执行和异常与信号处理过程。了解了几种命令对程序可能造成的影响。
(第6章1分)
7.1 hello的存储器地址空间
处理流程如下:
图7.5.2 访问数据的过程(图片来自CSAPP的PDF版截图)
CPU传递虚拟地址至MMU,MMU首先将VPN传递至TLB,查看是否存在缓存命中,如果命中,则直接生成PPN,如果不命中,则将VPN传递至页表,通过4级页表查找PPN,并将结果写入TLB中。PPN与PPO(即VPO)拼接得到物理地址。物理地址载利用cache访问数据,若命中,则将结果传递至CPU,如果不命中,则在主存中查找数据,并将数据存入cache中。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的pid。为了给这个新进程创建虚拟内存。它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
execve函数在hello进程中加载并运行hello,主要步骤如下:
1.删除已存在的用户区域
2.映射私有区域
3.映射共享区域
4.设置程序计数器
映射空间区域如下:
图7.7.1 程序映射的空间区域(图片来自CSAPP的PDF版截图)
7.8 缺页故障与缺页中断处理
虚拟内存中,页表不命中即为缺页。缺页处理如下:
1.控制传递给操作系统内核中的缺页异常程序
2.缺页异常处理程序确定出物理内存中牺牲页,如果这个页面已经被修改了,则把它换出到磁盘
3.缺页异常处理程序调用新的页面,更新PTE
4.控制转移至原来的进程,重新执行导致缺页的指令,这次会得到一个页命中
5.缺页处理程序不是直接就替换,它会经过一系列的步骤:
1) 虚拟地址是合法的吗?如果不合法,它就会触发一个段错误
2) 试图进行的内存访问是否合法?意思就是进程是否有读,写或者执行这个区域的权限
3) 经过上述判断,这时才能确定这是个合法的虚拟地址,然后才会执行上述的替换。
图7.8.1 缺页情况(图片来自CSAPP的PDF版截图)
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
1.隐式空闲链表:
图7.9.1 隐式空间链表(图片来自CSAPP的PDF版截图)
空闲块通过头部中的大小字段隐含地连接着。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。
1)放置策略:首次适配、下一次适配、最佳适配。
首次适配从头开始搜索空闲链表,选择第一个合适的空闲块。下一次适配从上一次查询结束的地方开始。最佳适配检查每个空闲块,选择适合所需请求大小的最小空闲块。
2)合并策略:立即合并、推迟合并。
立即合并就是在每次一个块被释放时,就合并所有的相邻块;推迟合并就是等到某个稍晚的时候再合并空闲块。
带边界标记的合并:
图7.9.2 带边界标记链表(图片来自CSAPP的PDF版截图)
在每个块的结尾添加一个脚部,分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态,从而使得对前面块的合并能够在常数时间之内进行。
2.显式空闲链表
图7.9.3 显示空间链表(图片来自CSAPP的PDF版截图)
每个空闲块中,都包含一个pred(前驱)和succ(后继)指针。使用双向链表使首次适配的时间减少到空闲块数量的线性时间。
空闲链表中块的排序策略:一种是用后进先出的顺序维护链表,将新释放的块放置在链表的开始处,另一种方法是按照地址顺序来维护链表,链表中每个块的地址都小于它后继的地址。
分离存储:维护多个空闲链表,每个链表中的块有大致相等的大小。将所有可能的块大小分成一些等价类,也叫做大小类。
分离存储的方法:简单分离存储和分离适配。
7.10本章小结
本章主要介绍了程序的存储结构,通过段式管理在逻辑地址到虚拟地址,页式管理从虚拟地址到物理地址。程序访问过程中的cache结构和页表结构,进程如何加载自己的虚拟内存空间,内存映射和动态内存分配。
(第7章 2分)
8.1 Linux的IO设备管理方法
设备的模型化:所有的IO设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
(第8章1分)
Hello程序的一生:
1.被程序员编写成程序源代码,该文件称为hello.c
2.Hello.c文件中的编译预处理指令所指内容经过预处理器被穿插在程序源代码中,成为修改过的源程序,称为hello.i
3.Hello.i经过编译器生成代表程序执行顺序的指令语言——汇编程序,其文件称为hello.s
4.hello.s经过汇编器翻译成可重定位目标文件,用于之后的模块整合
5.Hello.o经过与其他目标模块整合,形成可执行目标文件hello
6.创建进程:在shell利用./hello运行hello程序,父进程通过fork函数为hello创建进程
7.加载程序:通过加载器,调用execve函数,删除原来的进程内容,加载我们现在进程的代码,数据等到进程自己的虚拟内存空间。
8.执行指令:CPU取指令,顺序执行进程的逻辑控制流。这里CPU会给出一个虚拟地址,通过MMU从页表里得到物理地址, 在通过这个物理地址去cache或者内存里得到我们想要的信息
9.异常(信号):程序执行过程中,如果从键盘输入Ctrl-C等命令,会给进程发送一个信号,然后通过信号处理函数对信号进行处理。
10.结束:程序执行结束后,父进程回收子进程,内核删除为这个进程创建的所有数据结构
计算机程序的执行是一个复杂的过程,我们学习的只能说是看到了计算机复杂机制的冰山一角。我们只是从比较宽泛,普遍的角度来遵循一个程序的开始到结束。计算机本身的运行机制也是十分复杂,从基础的门电路,到一些基本的逻辑部件,再到由许多部件组成的集成电路,通过这些硬件大厦来构建操作系统与应用软件。未知的知识还有许多。总而言之,计算机世界的大门才刚刚打开,我们需要学习的事物还有很多。
(结论0分,缺失 -1分,根据内容酌情加分)
1.hello.c 程序的原始代码
2.hello.i 将编译预处理指令内容插入源代码后的文件
3.hello.s 记录汇编代码的汇编文件
4.hello.o 可重定位目标文件
5.hello 可执行目标文件
6.asm_hello_o hello.o的反汇编文件
7.asm_hello hello的反汇编文件
8.ELF_hello_o hello.o的ELF格式文件
9.ELF_hello hello的ELF格式文件
(附件0分,缺失 -1分)
[1] 兰德尔E.布莱恩特 大卫R.奥哈拉伦. 深入理解计算机系统(第3版)
[2] ELF文件-段和程序头:https://blog.csdn.net/u011210147/article/details/54092405
[3] 编译 百度百科:https://baike.baidu.com/item/编译/1258343?fr=aladdin
[4] 键盘的中断处理
https://blog.csdn.net/xumingjie1658/article/details/6965176
(参考文献0分,缺失 -1分)