本文主要是介绍【ICS大作业】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
零、摘要
本文对给定的hello程序的生命周期进行了系统性分析,程序经预处理生成hello.i、编译生成hello.s,汇编生成hello.o;最后链接成可执行目标文件hello。Shell收到./hello的指令,调用fork函数创建进程,execve将hello加载内存,由CPU进行控制,最后结束进程并由父进程回收。
关键词:预处理;编译;汇编;链接;进程;IO管理
一、概述
1.1 Hello简介
程序员通过编辑器创建并保存成文本文件,文件名为hello.c。此程序通过字节序列存储在文件中。
GCC编译器驱动程序读取源程序文件,并经下面4个阶段将其翻译成可执行目标文件hello。
预处理器根据#开头的命令修改原始C程序。
编译器将文本文件hello.i翻译成文本文件hello.s,即汇编语言程序。
汇编器将hello.s翻译成机器语言指令,并将这些指令打包成一种可重定位目标程序,并将结果保存在目标文件hello.o中。
链接器将printf等函数的printf.o等预编译文件合并到hello.o程序中,结果得到hello文件——可执行目标文件。
可执行目标文件被存储在磁盘上,等待程序运行。
程序加载时,机器指令被复制到主存;程序运行时,机器指令又被复制到处理器。在信息传递的过程中,使用高速缓存的机制来提高程序性能。
程序运行过程中,同时有着许多进程在运行,他们之间通过上下文切换来实现并发运行。
通过shell输入./shell,shell通过fork函数创建了一个新的进程,之后调用execve映射虚拟内存,通过mmap为hello程序开辟空间。
CPU从虚拟内存中的.text,.data节取代码和数据,调度器为进程规划时间片,有异常时触发异常处理子程序。
程序运行结束时,父进程回收hello进程和它创建的子进程,内核删除相关数据结构。
1.2 环境与工具
硬件环境:X64CPU;1.80GHz/2.30GHz;16GB RAM
软件环境:Windows 10 64位;VMware;Ubuntu 64位
开发工具:Visual Studio 2022 64位;Code Blocks 64位;gedit + gcc;edb
1.3 中间结果
hello.c 源程序
hello.i 预处理后的文件
hello.s 编译后的文件
hello.o 汇编后的可重定位目标执行文件
hello 链接后的可执行目标文件
hello.elf hello.o的elf格式文件
hello1.elf hello的elf格式文件
1.4 本章小结
本章简单介绍了Hello程序的生命周期,并介绍了所需软硬件环境和开发工具的基本信息。
二、预处理
2.1 预处理的概念与作用
预处理一般是指在程序源代码被翻译成为目标代码的过程中,生成二进制代码之前的过程。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——预处理记号用来支持语言特性。
预处理会展开以#起始的行,试图解释为预处理指令,使源代码在不同的执行环境中被方便地修改或编译。hello.c生成hello.i。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
(生成了hello.i文件)
2.3 Hello的预处理结果解析
预处理将头文件展开,程序变成了3060行。其中仍为C语言代码,说明预处理还未解析。头文件中包含了函数声明、结构体定义、变量定义、宏定义等内容。
2.4 本章小结
本章借助hello程序预处理的过程来展示了预处理的概念和作用,查看hello.i文件后发现对头文件展开了。
三、编译
3.1 编译的概念与作用
编译器将hello.i转换成hello.s文件,其中包含汇编语言程序。
这个工作过程中包括:词法分析、语法分析、语义检查和中间代码生成、代码优化、目标代码生成。其中词法语法检查出错,则编译器会给出提示信息。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
生成了hello.s文件
3.3 Hello的编译结果解析
3.3.1汇编初始部分
.file 声明为源文件
.text 代码节
.section .rodata 只读数据段
.globl 声明全局变量
.type 声明符号为函数类型或数据类型
.string 声明一个字符串
.align 声明对指令或者数据的存放地址进行对齐的方式
3.3.2 数据类型
3.3.2.1字符串
程序中的含有两个字符串,均在上图中展示,即为.string后引号中的字符串。这两个字符串是作为printf函数的参数来进行调用的。
3.3.2.2局部变量i
main函数仅声明了一个局部变量i,存放在了-4(%rbp)。
3.3.2.3参数argc
argc存放在了-20(%rbp),作为main函数第一个参数,参与了main函数中选择条件判断。
3.3.2.4数组argv[]
以指针形式作为main函数的第二个函数,数组中每个元素都是一个指向字符类型的指针。如上图,数组起始地址存放在-32(%rbp)位置,下图中为main函数的两次调用。
3.3.2.5立即数
在汇编代码中带有$标志的即为立即数。
3.3.3类型转换
调用了atoi函数,将字符串类型转换为整型。
3.3.4操作
3.3.4.1赋值操作
汇编代码中使用mov指令实现赋值操作,常见的普通mov类指令有:
movb 1字节
movw 2字节
movl 4字节
movq 8字节
3.3.4.2算数操作
汇编代码中出现了最常见的addq与subq,分别代表加法与减法。
3.3.4.3关系操作
包括cmp等条件判断指令,之后常常进行j跳转。
比较argc与4,为选择条件判断。
此处为循环判断条件,i<8。
3.3.4.4控制转移
此处je为相等则跳转。
此处jmp为直接跳转。
此处jle为小于等于则跳转。
3.3.5函数操作
3.3.5.1全局函数
在汇编初始部分将main函数声明为全局函数。
3.3.5.2参数传递
main函数参数为argc和*argv[]
printf函数参数为argv[1], argv[2]
exit函数参数为1
sleep函数参数为atoi(argv[3])
getchar函数无参数。
3.3.5.3函数调用
用call指令来实现。例如:
3.4 本章小结
本章主要分析了编译后的汇编指令,将其与C语言中的数据和操作联系了起来。
四、汇编
4.1 汇编的概念与作用
汇编器将hello.s翻译成机器语言指令,这些指令打包成可重定位目标程序,并保存在目标文件hello.o中。hello.o为二进制文件,包含了main函数中指令的机器语言格式。
4.2 在Ubuntu下汇编的命令
命令:gcc -c -o hello.o hello.s
4.3 可重定位目标elf格式
命令:readelf -a hello.o > hello.elf
分析:
1 ELF头
包括Magic:一个16个字节的序列,描述了生成该文件的系统的字的大小和字节顺序和可供链接器识别的有关信息:文件类别、数据类型、版本号、系统类型、ABI版本、文件类型、系统架构、节头部表的文件偏移、节头部表中数据的大小和数量。
2 节头
节头记录了各节的名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。其中旗标部分在下面有解释部分。
3 重定位节
.rela.text即.rel.text为一个.text节中位置的列表,当链接器把这个目标文件和其它文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。重定位信息通常省略。
4 符号表
符号表存放在程序中定义和引用的函数和全局变量的信息。每个可重定位目标文件在.symtab中都有一张符号表(除非用STRIP命令去掉它)。但和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o
与hello.s存在部分差异:
1 转移指令
.L3变为了80 <main+0x80>,由原先的段地址变为了现在的确定的相对地址。
2 函数调用
函数调用由原先的函数名变为了具体的地址。
3 立即数
由原先的$0变为了$0x0,变为了16进制。
4.5 本章小结
本章介绍了汇编的概念和作用,并分析了ELF格式的文件,与链接联系了起来;分析了hello.o反汇编后的指令,发现了此时得到的汇编语言与机器语言联系的紧密性。
五、链接
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.3 可执行目标文件hello的格式
命令:readelf -a hello > hello1.elf
分析:
1 ELF头
类型不同于hello.o的ELF格式文件,类型变为EXEC可执行文件,有27个节。
2 节头
3 重定位节
4 符号表
5.4 hello的虚拟地址空间
程序头:
LOAD地址为0x400000
在edb中定位此位置,则可以通过相对位置找到各个节的信息。5.3中节头表已经给予了相对地址的信息。
5.5 链接的重定位过程分析
命令:objdump -d -r hello
相比hello.o多了许多行,本质是在.text节的基础上多了.init、.plt、.plt_sec节,且原有的.text节中也多了<start>等,链接的函数增多了,可执行文件也更丰富了,其实对于机器来讲是更直观了,只需要按部就班执行语言就好。
hello重定位的过程:
(1)重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
(2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
(3)重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目,代码的重定位条目放在.rel.txt中。
(4)重定位地址计算算法:
5.6 hello的执行流程
加载hello——>_start——>_libc_start_main——>_main——>_printf——>_exit——>_sleep——>_getchar——>exit
程序名 | 程序地址 |
_start | 0x4010f0 |
_libc_start_main | 0x2f12271d |
_main | 0x401125 |
_printf | 0x401040 |
_exit | 0x401070 |
_sleep | 0x401080 |
_getchar | 0x401050 |
5.7 Hello的动态链接分析
延迟绑定由got和plt实现,查阅ELF文件:
Got起始表位置在0x404000处。
调用dl_init之前,0x404008~0x404017均为0;
调用dl_init之后,上述地址范围被赋值,.got.plt条目已变化。
5.8 本章小结
本章介绍了链接的概念和作用,对可执行目标文件hello的ELF格式文件进行了分析,并与hello.o进行了对比,梳理了hello从加载到退出调用的函数,重点进行了重定位和动态链接的分析。
六、hello进程管理
6.1 进程的概念与作用
概念:一个执行中程序的实例。让我们的程序好像是独占地使用处理器(一个独立的逻辑控制流)和内存(一个私有的地址空间)。
创建:父进程通过调用fork函数创建一个新的运行的子进程。子进程和父进程虚拟地址空间独立、PID不同,其它完全相同。
回收:当一个进程由于某种原因终止时,被保持在一种已终止的状态中,直到被它的父进程回收。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已经终止的进程,从此开始,该进程就不存在了。一个僵死了但还未被会回收的进程被称为僵死进程。如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养分,init进程将会回收这些孤儿进程。使用waitpid函数来等待子进程终止或者停止。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是一个交互型应用级程序,为使用者提供操作界面,接收用户命令,然后调用相应的应用程序。
处理流程:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数。
3)如果是内置命令则立即执行。
4)否则调用相应的程序执行。
5)shell 应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
输入./hello 120L020211 yxm 1,shell调用eval函数,再调用builtin_command函数检查发现非内置命令,则通过fork创建一个子进程。
6.4 Hello的execve过程
fork创建子进程后,子进程调用execve函数加载并运行hello。
1、删除已存在的用户区域,删除之前进程在用户部分中已存在的结构。
2、创建新的代码、数据、堆和栈段。所有这些区域结构都是私有的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
3、映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
4、设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
6.5 Hello的进程执行
逻辑控制流:如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称为逻辑流。
并发流:一个逻辑流的执行在时间上与另一个流重叠,称为并发流。
私有地址空间:进程为每个程序提供假象,好像它独占地使用系统地址空间。进程为每个程序提供它自己的私有地址空间。
用户模式和内核模式:处理器控制某个控制寄存器中的一个模式位来限制一个应用可以执行的指令以及它可以访问的地址空间范围。当设置了模式位时,进程就运行在内核模式中;未设置模式位时,进程就运行在用户模式中。异常发生时用户模式转为内核模式。/proc文件系统允许用户模式进程访问内核数据结构的内容。
上下文切换:一种较高层形式的异常控制流,以实现多任务。
hello进程的执行:进程调用execve函数,最初在用户模式下输出Hello 120L020211 yxm,后来进入内核模式运行信号处理程序,再回到用户模式。过程中不断切换上下文,与其它进程交替占用CPU。
6.6 hello的异常与信号处理
异常种类
类别 | 原因 | 异步/同步 | 返回行为 |
中断 | 来自IO设备 | 异步 | 返回到下一条指令 |
陷阱 | 有意的异常 | 同步 | 返回到下一条指令 |
故障 | 潜在错误可恢复 | 同步 | 返回当前指令或终止 |
终止 | 错误不可恢复 | 同步 | 不会返回 |
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
正常运行在6.5中已经给出了结果,下面先展示按键盘的情况:
不停乱按
回车
Ctrl-C
Ctrl-Z
不停乱按和回车都被存入了stdin的缓存中,因此按回车的程序执行完毕后又执行了多次回车。
下面展示Ctrl-Z后运行指令:
说明Ctrl-Z后只是挂起了前台的作业,输入fg后又能继续运行。
6.7本章小结
本章先介绍了进程的概念和作用、shell的概念和处理流程,分析了建立子进程并执行所需的fork与execve,最终借助hello的执行来深化了这一章的知识。
七、hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:指的是CPU分配给每个进程的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为段标识符:段内偏移量。
线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式,分页机制中线性地址作为输入。
虚拟地址:指的是线性地址。
物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由两部分组成,段标识符:段内偏移量。
一个进程拥有4G的虚拟空间,其中0-3G属于用户空间,这3G的空间划分为多个段,如下图所示:
从下往上,5个部分依次是:
代码区:存放可执行的指令,只能读,不能写
数据区:存放初始化的全局变量和静态变量
BSS区:存放未初始化的全局变量和静态变量
heap:存放malloc/new申请的变量
stack:存放临时变量,函数参数等
linux是通过分段机制,将逻辑地址转化为线性地址。上面的变量b就属于数据段。通过数据段寄存器ds,可以找到此进程数据段所在的段描述符,再通过段描述符找到相应的线性地址。
寄存器ds中保存了16位的段选择符,段选择符格式如下:
TI:指明段描述符是在全局描述符表(GDT, TI=0)中或局部描述符表(LDT, TI=1)中
每个段都有不同的属性,如首字节的线性地址,段的访问级别(DPL)等,段描述符就保存了段的这些属性。段描述符长度为8字节,格式如下:
逻辑地址到线性地址的转换:
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址即虚拟地址(VA)到物理地址(PA)之间的转换通过分页机制完成,而分页机制是对虚拟地址内存空间进行分页。
系统将虚拟页作为进行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择适当的PTE,根据PTE,我们知道虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。VPO和PPO是相同的。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。
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支持下的物理内存访问
获得物理地址之后,先取出组索引对应位,在L1中寻找对应组。如果存在,则比较标志位,相等后检查有效位是否为1.如果都满足则命中取出值传给CPU,否则按顺序对L2cache、L3cache、内存进行相同操作,直到出现命中。然后再一级一级向上传,如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,将目标块放到被驱逐块的位置。
7.6 hello进程fork时的内存映射
在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。
7.7 hello进程execve时的内存映射
第6章中已进行相应分析:
fork创建子进程后,子进程调用execve函数加载并运行hello。
1、删除已存在的用户区域,删除之前进程在用户部分中已存在的结构。
2、创建新的代码、数据、堆和栈段。所有这些区域结构都是私有的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
3、映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
4、设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆,系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高地址)。对于每个进程,内核维护着一个变量brk指针,它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显示地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显示地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的。要么是内存分配器自身隐式执行的。分配器有两种基本风格。两种风格都要求应用显示地分配块。他们的不同之处在于由哪个实体来负责释放已分配的块。
1.显示分配器:要求应用显示的释放任何已分配的块。例如C标准库malloc和free。
2.隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,那么就释放这个块。例如Java、ML、Lisp等高级语言中的垃圾收集。
7.10本章小结
本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以intel Core7在指定环境下介绍了虚拟地址VA到物理地址PA的转换、物理内存访问,分析了hello进程fork时的内存映射,hello进程execve时的内存映射、缺页故障与缺页中断处理和动态存储分配管理。
八、hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:均模型化为文件,所有输入输出均当作读写处理。
设备管理:Unix IO接口使得输入输出都能以一种统一且一致的方式进行。
8.2 简述Unix IO接口及其函数
接口就是连接CPU与外设之间的部件,它完成CPU与外界的信息传送。还包括辅助CPU工作的外围电路,如中断控制器、DMA控制器、定时器、高速CACHE等。
函数:
打开和关闭文件:open()和close()
读写文件:read()和write()
在文件的指定位置赋值: ssize_tread()。
8.3 printf的实现分析
printf代码:
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;
}
printf生成格式化后的字符串,并返回字符串的长度。
vsprintf的函数体:
vsprintf函数将所有的参数内容格式化之后存入buf,返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序接受按键扫描码转成ASCII码,保存到系统的键盘缓冲区。
getchar调用了read系统函数,通过系统调用读取按键ASCII码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,分析了printf函数和getchar函数的实现。
结论
输入:从键盘输入hello.c文本文件。
预处理:预处理hello.c,生成hello.i。
编译:hello.i生成汇编语言文件hello.s。
汇编:hello.s翻译成可重定位目标文件hello.o。
链接:将hello.o与动态链接库链接成为可执行目标程序hello。
运行:在shell中输入./hello 120L020211 yxm 1。
创建子进程:非内置命令,shell调用fork生成子进程。
加载程序:shell调用execve函数,启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。
执行指令:CPU为每一个进程分配时间片,在一个时间片中hello“独享”CPU资源,顺序执行逻辑控制流。
访问内存:虚拟内存地址通过页表映射成物理地址。
动态申请内存:printf调用malloc动态申请内存。
信号管理:当程序在运行的时候我们输入Ctrl+C,内核会发送SIGINT信号给进程并终止前台作业。当输入Ctrl+Z时,内核会发送SIGTSTP信号给进程,并将前台作业停止挂起。
终止:子进程执行完毕,内核安排父进程收回子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有数据结构。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法:
计算机系统的设计浓缩了无数前人的结晶,一个简单的hello程序也有着丰富的内涵。每一步都耗费了不知多少前辈日日夜夜的努力。我们应当不停学习计算机系统知识,努力追赶前人,在积累了一定的知识之后,可以尝试进行改进甚至创新新的方法。但现在本人学术不精,还未能实现创新。
附件
hello.c:源文件
hello.i:预处理后的文件
hello.o:编译后的文件
hello.s:汇编后的可重定位目标文件
hello:链接后的可执行文件
hello.elf:hello.o的ELF格式
hello1.elf:hello的ELF格式
参考文献
[1] 《深入理解计算机系统》[J].科学中国人,2018,(09):69.
[2] 预处理_百度百科 (baidu.com)
[3] https://blog.csdn.net/qq_44491553/article/details/110317066
[4] http://www.xcjxzjg.com/article-22-35623-0.html
[5] https://www.bbsmax.com/A/MyJxPZZAJn/
[6] 百度安全验证
[7] 详解:链接中的重定位 - 知乎
[8] https://blog.csdn.net/sy06106/article/details/118274118
[9] https://blog.csdn.net/morixinguan/article/details/121623620
[10] https://www.cnblogs.com/pianist/p/3315801.html
这篇关于【ICS大作业】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!