程序人生-Hello’s P2P
https://img-blog.csdnimg.cn/direct/bf9d9998a296436797e0c3dc38b54a4a.jpeg
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 网络空间安全
学 号 2022113594
班 级 2203901
学 生 郑逸韬
指 导 教 师 史先俊
计算机科学与技能学院
2024年5月
摘 要
本论文通过讨论一个简单的hello程序在计算机系统中从生存,到成为一个进程,终极进程终止的生命流程来贯穿本学期学到的计算机系统知识。通过这次贯穿的实践,我对庞大而精细的计算机系统又有一个更深入的了解。hello的一生如下:编写并生存源码,预处置处罚,编译,汇编,链接,shell创建新进程,调用fork+execve后加载器加载hello.out进程并建立虚拟内存映射,可执行目标文件从外部存储颠末一系列I/O流程后被载入内存,hello.out在运行过程中吸收并处置处罚信号,hello.out进行内存操纵时通过MMU访存,hello.out调用Unix I/O函数进行对尺度输出流stdout的输出,程序返回,进程被采取。过程中我们通过edb, objdump, readelf等一系列工具检察hello的一生中的编译、汇编、链接等过程,并在这些过程的中间结果之间进行一些比力和验证,以此对这些过程的工作内容得到进一步的理解。终极我们到达了目的,对上述整个过程有了较为精细的理解,对辅助我们的计算机系统课程学习起了很大作用。
关键词:hello;预处置处罚;编译;汇编;链接;进程;存储;Linux;I/O
目 录
第1章 概述... - 4 -
1.1 Hello简介... - 4 -
1.2 环境与工具... - 5 -
1.3 中间结果... - 5 -
1.4 本章小结... - 5 -
第2章 预处置处罚... - 6 -
2.1 预处置处罚的概念与作用... - 6 -
2.2在Ubuntu下预处置处罚的下令... - 6 -
2.3 Hello的预处置处罚结果解析... - 6 -
2.4 本章小结... - 7 -
第3章 编译... - 8 -
3.1 编译的概念与作用... - 8 -
3.2 在Ubuntu下编译的下令... - 8 -
3.3 Hello的编译结果解析... - 8 -
3.4 本章小结... - 10 -
第4章 汇编... - 11 -
4.1 汇编的概念与作用... - 11 -
4.2 在Ubuntu下汇编的下令... - 11 -
4.3 可重定位目标elf格式... - 11 -
4.4 Hello.o的结果解析... - 12 -
4.5 本章小结... - 13 -
第5章 链接... - 15 -
5.1 链接的概念与作用... - 15 -
5.2 在Ubuntu下链接的下令... - 15 -
5.3 可执行目标文件hello的格式... - 15 -
5.4 hello的虚拟地点空间... - 18 -
5.5 链接的重定位过程分析... - 19 -
5.6 hello的执行流程... - 21 -
5.7 Hello的动态链接分析... - 22 -
5.8 本章小结... - 23 -
第6章 hello进程管理... - 24 -
6.1 进程的概念与作用... - 24 -
6.2 简述壳Shell-bash的作用与处置处罚流程... - 24 -
6.3 Hello的fork进程创建过程... - 24 -
6.4 Hello的execve过程... - 25 -
6.5 Hello的进程执行... - 25 -
6.6 hello的异常与信号处置处罚... - 25 -
6.7本章小结... - 27 -
第7章 hello的存储管理... - 28 -
7.1 hello的存储器地点空间... - 28 -
7.2 Intel逻辑地点到线性地点的变换-段式管理... - 28 -
7.3 Hello的线性地点到物理地点的变换-页式管理... - 29 -
7.4 TLB与四级页表支持下的VA到PA的变换... - 29 -
7.5 三级Cache支持下的物理内存访问... - 29 -
7.6 hello进程fork时的内存映射... - 30 -
7.7 hello进程execve时的内存映射... - 30 -
7.8 缺页故障与缺页制止处置处罚... - 30 -
7.9动态存储分配管理... - 31 -
7.10本章小结... - 31 -
第8章 hello的IO管理... - 32 -
8.1 Linux的IO设备管理方法... - 32 -
8.2 简述Unix IO接口及其函数... - 32 -
8.3 printf的实现分析... - 33 -
8.4 getchar的实现分析... - 33 -
8.5本章小结... - 34 -
结论... - 34 -
附件... - 36 -
参考文献... - 37 -
第1章 概述
1.1 Hello简介
P2P过程:
1. 编写并生存源程序.c文件。
2. 预处置处罚器cpp根据include语句将头文件内容复制到这个文件,根据define语句进行宏更换等等,终极形成.i文件。
3. 编译器cc1将.i文件编译成.s汇编程序文件。
4. 汇编器as将.s文件中的汇编程序翻译成机器语言,天生一个.o可重定位目标文件。
5. 链接器ld对我们天生的.o文件中引用的printf, sleep等库函数和两个pritnf格式串做符号解析,对非共享函数和两个格式串做重定位,天生一个可执行目标文件hello.out。
6. 在shell中输入./hello.out 2 xxx 3 4,指示shell运行hello程序并给出参数。shell通过Unix I/O得到该下令并进行处置处罚。
7. shell调用fork函数创建一个子进程,调用execve函数将hello.out文件载入该进程的上下文,调用mmap函数建立虚拟内存映射,终极设置当前进程上下文中的程序计数器PC,使之指向hello.out的ELF头中的程序入口处。CPU通过进程调度让hello.out进程在不同的时间片上运行,进而在硬件上流水线化执行。此时,hello.c真正从一个文本的C语言程序变为一个执行hello.out的进程。
020过程:
0. 在完成P2P过程后,hello.c从一个C语言程序变为被加载至内存正在执行的hello.out进程,完成了从0到1的转变。
1. 程序运行中进行访存时,首先由CPU中的段——页式管理机制将机器指令中的逻辑地点翻译成虚拟地点,然后由CPU上的内存管理单元MMU将虚拟内存地点翻译成物理内存地点:先后在CPU内部的TLB里中查询该虚拟地点对应的PTE,失败后在DRAM中的四级页表中查询PTE,该虚拟页未分配或未缓存则触发缺页异常更新页表,回到CPU重新执行该机器指令,终极在TLB或页表中获取对应的PTE,根据PTE将虚拟内存地点翻译成物理内存地点。获取物理地点后,硬件通过三级cache逐级查询该物理地点对应的实际数据,cache miss后根据各种读、写更新策略对cache进行相应的更新。
2. 程序运行中调用了共享库函数时必要完成动态链接,特别是第一次调用该函数时进行了延迟链接,在运行时通过GOT表的初始化魔数、PLT和GOT两个数据结构和动态链接器的配合,得到函数的运行时地点并更新GOT表。进行后续的共享库函数调用时,就可以通过延迟链接写入内存的GOT条目得到该共享库函数的运行时地点。
3. 程序执行途中可能有程序调用kill函数大概键盘按下Ctrl+C或Ctrl+Z,使内核发送信号,hello.out所在进程可能会对这些信号做出反应,比如终止或制止。假如制止可以使用shell的fg下令规复运行。
4. 输出和getchar过程:printf调用malloc进行动态内存分配获取一个缓冲区,通过vsprintf进行格式字符串和可变参数的解析,最后调用系统write函数,通过显卡上的显存向液晶显示器传输每一个点,使输出字符串终极显示在屏幕上。程序的最后调用getchar函数调用了系统read函数,等待第一个回车符后返回。
5. 程序终止后,内核向父进程发送SIGCHLD信号,hello.out所在进程被shell采取。采取完成后,内存中不再留下与hello.out相关的信息,从1变成了0,完成了020的过程。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位
开辟工具:vscode, cpp, cc1, as, readelf, objdump, ld, edb
1.3 中间结果
hello.i: 颠末cpp预处置处罚之后的源程序。
hello.s: 颠末cc1编译之后得到的汇编语言程序。
hello.o: 颠末as汇编hello.o天生的可重定位目标文件。
hello.out: 颠末ld链接后天生的可执行目标文件。
1.4 本章小结
本章简述了hello的P2P,O2O的整个过程,并列出了编写本论文使用的环境与工具和天生的中间结果文件。
第2章 预处置处罚
2.1 预处置处罚的概念与作用
预处置处罚是预处置处罚器(cpp)根据原始C程序文本中以#开头的下令修改原始程序的过程,该阶段竣事终极得到了另一个C程序,通常是一个以.i为文件拓展名的文本文件。
2.2在Ubuntu下预处置处罚的下令
调用预处置处罚下令为cpp ./hello.c -o hello.i,截图如图2-1所示,输出文件部分节选如图2-2所示
图2-1
https://img-blog.csdnimg.cn/direct/7b666ee25ad34f83a95f364f9148efd7.png
图2-2
https://img-blog.csdnimg.cn/direct/be2589089d01430f9dc302f0d7d37528.png
2.3 Hello的预处置处罚结果解析
预处置处罚程序大致可以分为两部分:开头插入的头文件和结尾生存不变的原始代码:如图2-3、2-4分别为来自头文件stdio.h内部包含的bits/types.h(如图2-3-2所示)的一部分范例定义和stdio.h本身的函数原型声明,图2-5为hello.c的原始代码。
https://img-blog.csdnimg.cn/direct/d4252bc4de1941ccb56e3dfef149deb8.png
图2-3-2 (下) 图2-3(右)
https://img-blog.csdnimg.cn/direct/fb77c0247fc9494fb6ddf426113c6daa.png
图2-4
https://img-blog.csdnimg.cn/direct/fe624b38da594f82971da4548cffd025.png
https://img-blog.csdnimg.cn/direct/e93fc0342cdf477ebd0712a99ee5039e.png
图2-5
2.4 本章小结
本章围绕C语言程序的预处置处罚过程和结果进行了展开分析,举例阐明了预处置处罚程序修改源程序具体的实现效果。
第3章 编译
3.1 编译的概念与作用
编译是编译器(cc1)将预处置处罚器修改过的.i源程序翻译成一个汇编语言程序的过程,该程序一般是文件拓展名为.s的文本文件。
3.2 在Ubuntu下编译的下令
编译下令为cc1 -c ./hello.i -o hello.s,截图和输出效果如图3-1所示。
https://img-blog.csdnimg.cn/direct/a022612e16a0429d8e8b1697d8196cf9.png
图3-1
3.3 Hello的编译结果解析
hello.s的内容如图3-2所示
https://img-blog.csdnimg.cn/direct/522c94c0f60a44dd9bf7156c1f4490dc.png
图3-2(右,上半)
https://img-blog.csdnimg.cn/direct/5ea2cb14af10430f9d8bc8309a508418.png
图3-2(右,下半)
3.3.1 数据处置处罚
由图3-2上半可以看到,printf的两个格式字符串是分别存储在两个由标签.LC0, .LC1(第5、7行)定位的地点,调用时访问标签的相对地点来提取格式字符串,这是汇编中存储字符串常量的方法;这个汇编程序中存储作用于main函数内作为循环变量的局部变量i的地点是-4(%rbp)(第55行),阐明局部变量在汇编中被存在栈上。操纵该变量时,主要的下令为cmpl, addl(第55、53行),阐明该变量的巨细为四字节,即64bit下的int。对该变量赋值为0的初始化位于第30行,汇编中根本都使用mov指令进行变量的初始化赋值。
3.3.2 算数操纵
hello.c中主要的算术操纵为循环变量的更新i++,在汇编中被转化为addl $1, -4(%rbp)。
3.3.3 数组操纵
hello.c中对数组进行取值的操纵体现在printf中对char*argv[]求argv argv argv argv。由argv的定义可知这是一个存储若干个char*指针的数组,即每个元素都是一个长8字节(64bit环境)的地点,故在第34、37、40、47行分别取argv, argv, argv, argv的值时将地点偏移了24、16、8、32字节。同时可以发现,argv的初始地点位于-32(%rbp),也就是说记该地点为d,则argv位于d,argv位于d+8,以此类推得到的结果与前文的推理符合,印证了推理的精确性。
3.3.4 控制转移
hello.c中涉及控制转移的部分有一个if和一个for循环。
对于if判断,由于hello.c中的判断条件为argc!=5,故在汇编程序第23、24行中条件跳转的条件为argc==5,跳转失败则执行hello.c中if内的代码段,跳转乐成则执行hello.c中if外部的代码块。
对于for循环,编译器天生的循环翻译方法为跳转至中间策略,即循环的初始化init在第30行,在第31行跳转至第55行循环的条件判断test,若循环条件满足则在第56行跳转至第33行循环体body-statement,在第53行执行循环变量更新update-expr后按顺序执行到循环条件判断test,重复该过程直到循环条件不满足,此处即循环变量i在init被初始化为0,在test时进行的循环条件判断为i<=9,每次更新update-expr的效果为i自增1,终极test循环条件不满足时继承执行第57行后在原程序中位于for循环之后部分的程序。
3.3.5 函数操纵
hello.c中主要的函数调用有printf、atoi、exit、sleep。在第26行,会发现对纯文本的格式字符串使用printf在汇编中会调用puts而非printf,这可能是出于性能优化的考虑,省去了printf解析格式的消耗。对于exit、sleep、atoi、puts四个只有一个参数的函数调用,这些参数被直接放到寄存器%edi或%rdi中(第25、27、49、51行),其中exit、sleep由于参数为int和unsigned int,二者的参数寄存器为%edi;atoi、puts的参数为const char*,故寄存器为%rdi。从第51行,可以看到atoi的返回值为一个四字节数,位于寄存器%eax中。对于第45行的printf,这个printf一共传入了四个参数:一个格式字符串、argv,argv,argv。根据第35、38、42、43行可以发现这四个参数分别位于%rdi, %rsi, %rdx, %rcx这四个寄存器中,这可能跟printf中后三个参数的格式指定符为%s有关:printf为了担当三个字符串,必要在参数中传递三个字符串的初始地点,即三个八字节数。
3.4 本章小结
本章围绕C语言的编译过程,对编译前的原程序和编译后的汇编语言之间操纵和结构的对应进行了详细的展开分析。
第4章 汇编
4.1 汇编的概念与作用
汇编是汇编器(as)将汇编程序翻译为机器语言指令的过程,终极产生一个拓展名为.o的二进制文件,不能在文本编辑器中直接打开。
4.2 在Ubuntu下汇编的下令
汇编下令为as -c hello.s -o hello.o,结果如图4-1所示
https://img-blog.csdnimg.cn/direct/18fe75eaefc14f74ac4b027fb79c7e41.png
图4-1
4.3 可重定位目标elf格式
使用readelf -a hello列出根本信息,如图4-2所示。
https://img-blog.csdnimg.cn/direct/6a78c5ee6d37473dadaa9323b2d04060.png
图4-2-1
https://img-blog.csdnimg.cn/direct/bc55482acfe84512a632894f2cd056b6.png
https://img-blog.csdnimg.cn/direct/ccc3874337a5485aa5e29afa258c8716.pnghttps://img-blog.csdnimg.cn/direct/4b810f0c143e47cca3db3849b4faef5c.png
2-2
图4-2-4
分析hello的ELF格式:图4-2-1为hello.o的ELF头,包含与天生hello.o系统相关的环境变量和帮助链接器进行语法分析息争释目标文件的信息(如ELF头巨细,图中为Size of this header、目标文件范例,图中值为REL可重定位文件、及其范例、节头部表的文件偏移、条目巨细和数目,在图中为Start of section headers, Size of section headers和Number of section headers)
下一部分(图4-2-2)是hello.o的节头,包含对应节的名称和地点偏移。
图4-2-3是具体的重定位表,其中范例为PC32的符号和PLT32的符号都使用相对寻址。这个重定位表阐明了编译hello.s得到的可重定位目标文件中有两个符号为该文件的本地符号(.rodata-4, .rodata+2c),通过起始点+偏移坐标给出其起始坐标;剩下的均为外部符号(puts, exit, printf, atoi, sleep, getchar),必要与其它可重定位目标文件进行链接。
4.4 Hello.o的结果解析
hello.o的反汇编如下图4-2-5所示。
在图中,机器语言是每行左侧的二进制字节序列,通过objdump将其根据已知的规则与汇编语言进行转化。机器语言中的跳转指令参数代表相对地点,其意义为将现在执行下令的字节地点向下或向上跳转若干个字节,显示为汇编语言的jmp后参数则显示的是绝对地点(如13:74 16被翻译为je main+0x2b,0x13+0x16+0x02=0x2b)。函数调用也遵从类似的规则,如1c:e8 00 00 00 00 00被直接翻译为callq main+0x21, 0x1c+0x05+0x00=0x21
与hello.s相比,hello.o的反汇编中的call、lea指令中有时会在参数部分留下4字节的0-padding,在下一行中给出此处4字节0-padding必要被指定的重定位符号的终极地点进行更换(如15:48 8d 3d 00 00 00 00,下方的18:PC32 .rodata-0x4阐明了此处lea的参数00 00 00 00在链接时必要被更换为.rodata-0x4的实际地点)。反汇编中的跳转指令与hello.s相比,其跳转目标从给定的label变成了写死的常量,接纳相对地点的方式进行跳转,而不像call和lea一样使用重定位符号地点参数更换。
https://img-blog.csdnimg.cn/direct/d87ecf20cb50478fb36ebdd014cf4976.png
图4-2-5
4.5 本章小结
本章从汇编编译得到的hello.s得到的可重定位目标文件hello.o开始,分析了hello.o的ELF头及其重定位表的意义,比力了objdump hello.o得到的汇编语言和hello.s的差异,展示了机器语言与汇编语言之间的关系以及两个汇编语言文件之间的不同点。
第5章 链接
5.1 链接的概念与作用
链接是将hello.o与各种在hello内部使用到的代码和数据片段进行收集并组合成为一个单一的可执行目标文件的过程。
5.2 在Ubuntu下链接的下令
链接下令为:ld -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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello.out,链接结果如图5-1所示。
https://img-blog.csdnimg.cn/direct/d5467fee381041be847138365c9c6bcc.png
图5-1
5.3 可执行目标文件hello的格式
hello的ELF头如图5-2系列图所示。
https://img-blog.csdnimg.cn/direct/ce665ed7f6bc461582f4ef2b7dd23f39.png
图5-2-1
https://img-blog.csdnimg.cn/direct/8cbd23030227434bb46504bf23314ca8.png
图
https://img-blog.csdnimg.cn/direct/e2dea945a434493280ac6036d58d23b1.png
https://img-blog.csdnimg.cn/direct/39b6a319387946d39a425477382cd13f.png
5-2-3(上)
图5-2-2(上) 图5-2-4(上)
https://img-blog.csdnimg.cn/direct/b077405da346441a9c4a800aed8e155e.png
图5-2-5
https://img-blog.csdnimg.cn/direct/a6e89b39942b4dcbbbe0927af16ce52a.png
图5-2-6
https://img-blog.csdnimg.cn/direct/140fa100931844e39077606cfe91b495.png
图5-2-7
根据图5-2-3,该程序中主要的三段来自位于虚拟地点0x401000-0x401294的可读可执行的代码段、位于虚拟地点0x4020000-0x402130的只读内存段和位于虚拟地点0x403e00-0x404060的可读可写内存段,这一部分的内存从目标文件中偏移0x2e00处的.init_array往下0x260个字节读取并初始化。
5.4 hello的虚拟地点空间
使用edb加载hello.out,开始执行后状态如图5-3所示。
https://img-blog.csdnimg.cn/direct/072e1e2ace7d467e886c0dda75dab2e6.png
图5-3
可以发现,栈中生存了一个位于0x401090的返回地点,对应5.3中的目标文件的入口点地点;rip在开始执行后指向0x401176,对应main函数的起始地点。
观察执行窗口(图5-3-1),可以发现在地点0x401295之后的所有地点内均为0,阐明该地区已经超出了5.3中的可执行代码段0x401000-0x401294。
https://img-blog.csdnimg.cn/direct/6ae2544122ec4bcfb255bad21808240e.png
图5-3-1
观察内存窗口(图5-3-2),发现在地点0x402000-0x402130中可以找到printf的两个输出字符串。
https://img-blog.csdnimg.cn/direct/16e1aed5cddd4908a86285f0344db971.png
如5-3-2
5.5 链接的重定位过程分析
hello.out的objdump结果如图5-4系列图所示。
https://img-blog.csdnimg.cn/direct/6e2bdfb5b9bf4ce0a1e62c7bc6a206fb.png
图5-4-1
https://img-blog.csdnimg.cn/direct/0621a5add9b143e8a263e3cb198a4354.png
图5-4-2
https://img-blog.csdnimg.cn/direct/596ad5c5e343439cb0a23145d9bca2be.png
图5-4-3
https://img-blog.csdnimg.cn/direct/c92ae3d338b14d5fb8088b64c82bf1c9.png
图5-4-4
与hello.o相比,hello.out的objdump结果中出现了不同内容的ELF头(如.init,.fini等),还出现了.plt中的过程连接表,用于链接hello中对库函数的调用。
在完成链接的hello.out中,出现了系统目标文件ctrl.o中定义的程序入口点_start,libc.so中的系统启动函数__libc_start_main,颠末两层调用后才进入hello.o中的main函数。
main函数中,加载有效地点指令lea的参数被分别更换为0xe76和0xe63,这是由于在图5-2-2中.rodata节的地点起始于0x402000,printf的两个字符串分别起始与0x402008和0x402038,而加载.text节时可以固定main函数在二进制文件中的地点,通过两个地点差在链接时计算并更换lea的参数。类似的尚有函数调用指令callq的参数计算:hello.o中的call puts-0x4颠末计算变成了401192:e8 99 fe ff ff即callq 0x401070(0x401192 + 0xfffffe99 = 0x401030,高位溢出),对应的是.plt中链接puts的调用外部函数puts的过程:链接器提前固定过程连接表.plt的地点(在图5-2-2中位于0x401020),将main放入.text时获得在hello.o中为e8 00 00 00 00的带更换参数的jmp指令的地点,计算该符号与.plt中对应符号的地点差更换参数完成该符号的重定位。
5.6 hello的执行流程
call main时堆栈如下(图5-5-1),可以发现在main之前尚有__libc_start_main和_start(程序入口点)。追踪__libc_start_main发现在call main之前还执行了__cxa_atexit、__libc_csu_init、_setjmp,然后是main
https://img-blog.csdnimg.cn/direct/2549703e8d8348c3aa1a26a14a874f7c.png
图5-5-1
main函数正常退出后,使用步进分析从堆栈中返回的过程。回到__libc_start_main后执行了exit,exit执行完毕后程序退出。
5.7 Hello的动态链接分析
hello.out中对于共享库的代码使用GOT-PLT的协作运行方式进行延迟绑定的动态链接。首先,在图5-2-2中可以看到.got.plt的起始地点为0x404000,这阐明在后续进行共享库函数的第一次调用时,该地点内相应条目的偏移量会被修改为该函数的运行时地点。
具体描述,当程序刚刚被加载时,其.got.plt节在内存中如图5-6-1所示。此时的GOT中只有一个条目:.dynamic节的地点。程序运行至call main时,GOT中新增了两个条目,其中0x404000和0x404008为动态链接器在解析函数地点时必要的信息,0x404010为动态链接器在ld-linux.so中的入口点。后续的条目都对应于一个必要在运行时解析其地点的被调用的函数,现在,这些条目的GOT地点指向其PLT条目的地点(如图5-6-3所示)。
https://img-blog.csdnimg.cn/direct/76936458f4f34545a1f6be48ab265e79.png
图5-6-1
https://img-blog.csdnimg.cn/direct/d2b3e66c6f274bb28fa0a4d8def42b89.png
图5-6-2
https://img-blog.csdnimg.cn/direct/d251d30aacb145268da169f268ba33b7.png
图5-6-3
当main函数第一次运行至printf时,程序首先跳转至printf所在的plt表的地点(在此处为0x401040),此时程序的运行流如图5-6-4所示。下一步中程序跳转至GOT条目中的地点(运算结果如图5-6-5所示),其效果等同于跳转至下一条指令:pushq $1。这一步的效果是将printf函数在GOT中对应的id压入栈(此处为1),然后跳转至plt表的最初始项:与动态链接器交互的项,下文中用plt指代。
https://img-blog.csdnimg.cn/direct/120745ba41644e71b3cc9054a2a3d767.png
图5-6-4
https://img-blog.csdnimg.cn/direct/60273e35ea7949138ad65904f9bf32c2.png
图5-6-5
当程序运行至plt时,程序接下来的运行流如图5-6-6所示。根据图5-6-7和上文的讨论,该指令的效果是将地点为0x404008的量,即GOT表中的第二个条目压入栈中作为动态链接器的一个运行参数。接下来一条指令的运行效果,根据图5-6-8所示,为跳转至位于0x404010处的地点,即GOT表中的第三个条目,动态链接器的入口点。这条指令的作用相当于将函数id和GOT条目作为参数传递给动态链接器。
https://img-blog.csdnimg.cn/direct/31825da2052147c6ad2283e31fd4d0c7.png
图5-6-6
https://img-blog.csdnimg.cn/direct/a7123e325bd44eaeb52715459bf6d302.png
图5-6-7
https://img-blog.csdnimg.cn/direct/5b92687e348e4751ac496ccf3146b4e8.png
图5-6-8
动态链接器执行完毕后,内存中的.got.plt表被修改为如下所示,表中的第二个调用函数项(地点0x404020)的值从0x401046被修改为了00007f6e:b23adc90。在CPU窗口中跟踪该地点,确认该地点对应的过程为printf函数(图5-6-10),printf函数第一次完成调用后,动态链接器对内存的.got.plt表的printf项进行了修改。这样,在程序之后调用printf函数时,printf函数位于plt表中地点为0x401040的jmp指令将把程序流直接跳转至printf函数所在的地点,达成延迟链接的效果。
https://img-blog.csdnimg.cn/direct/34861748329642c99b28b98404777b2c.png
图5-6-9
https://img-blog.csdnimg.cn/direct/b62faf46acab4e07af01dc216687b057.png
图5-6-10
5.8 本章小结
本章对上一章中汇编得到的hello.o进行ld链接得到的hello.out可执行目标文件进行了分析。通太过析其ELF头、比对objdump的汇编结果、使用edb进行运行时单步调试,本章将链接过程中链接器的作用以及共享库函数的动态延迟链接过程进行了梳理。
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中程序的实例。在系统中,每个程序都运行在某个进程以及其对应的上下文当中。上下文包含使程序精确运行所需的状态信息。每次用户通过shell键入下令执行一个可执行目标文件时,shell将会创建一个新进程,并在这个进程的上下文中运行这个可执行目标文件。类似的,应用程序也可以通过类似的路径创建新进程。
进程的作用在于进程为应用程序提供时空上的抽象:应用程序在进程的抽象中好像正在独占处置处罚器与内存系统,分别对应于进程提供的独立逻辑控制流和私有空间地点。
6.2 简述壳Shell-bash的作用与处置处罚流程
shell是一个交互型的应用程序。通过shell,用户可以交互地访问和操纵系统内核,shell代表用户运行各种程序。
shell的处置处罚流程如下:shell读取用户输入的指令;shell分割用户的指令字符串获取下令和各种参数,假如最后一个参数为一个&字符则代表shell应该在后台执行该下令,否则在前台执行;shell检查该指令的第一个参数是否为shell内置的下令,假如是内置下令则直接执行,否则假如是一个可执行文件的文件名则shell为其分配一个子进程执行该文件,否则根据情况报错。假如shell必要进行一个后台执行,则shell在执行下令的同时返回第一步等待用户输入,否则shell通过waitpid系统接口函数等待作业的竣事,然后返回第一步;在任何时刻,shell都可以通过异步的方式担当来自I/O设备或应用程序的信号并处置处罚。
6.3 Hello的fork进程创建过程
在终端输入./hello.out 1 郑逸韬 1 1后,shell发现./hello.out不是内置下令,然后在执行目录下找到hello.out可执行文件后,通过fork系统接口函数创建一个当前进程的子进程。该子进程得到一份与父进程用户级虚拟地点空间相同的副本,包括其中的代码、数据、堆、共享库和用户栈。子进程还获得所有与父进程的任何正在打开的文件描述符相同的副本,拥有读写父进程正在打开的任何文件的能力。子进程与父进程的根本不同在于二者的PID进程号不同。
6.4 Hello的execve过程
通过fork创建子进程后,子进程通过execve系统接口函数调用启动加载器,删除子进程现有的虚拟内存段并创建一组新的代码、数据、堆和栈段,堆栈被初始化为0。在这之后虚拟地点空间中的页被映射到可执行文件的页的巨细的片,将虚拟内存空间中的代码段和数据段初始化为可执行文件中的对应内容。该过程还将该程序链接的共享对象映射到用户虚拟地点空间中的共享地区内。完成准备工作后,加载器跳转至可执行文件的入口处,即可执行文件的_start地点,终极进入程序的main函数。在整个execve的加载过程中,除了一部分头部信息以外没有发生从磁盘到内存的数据复制。在后续的运行过程中,若CPU引用了一个被映射的虚拟页,此时将会发生上文的数据复制,操纵系统通过其页面调度机制自动地将对应页面从磁盘复制至内存。
6.5 Hello的进程执行
上下文信息:指使得程序精确运行所需的各种状态信息,包括内存中的代码、数据、栈、通用寄存器的内容、程序计数器、环境变量以及打开文件描述符。
时间片:操纵系统中有多个进程轮流运行,而对于一个进程,执行其控制流的一部分的对应时间段就是时间片。
用户态与核心态:处置处罚器通过某个控制寄存器中的一个位提供这两种转换功能。该寄存器描述了该进程现在享有的特权。寄存器的该位被设置时,代表进程运行在核心态,否则为用户态。核心态的进程可以执行指令会合的任何指令,还可以访问系统中的任何内存位置;用户态的进程不答应执行特权指令(如改变模式位、发起I/O操纵、制止处置处罚器等),不能直接引用内核区地点的代码和数据(必须通过系统接口间接访问),任何违背用户态限制的尝试都会触发一个致命的保护故障。
进程调度过程:通过shell执行hello.out时,hello.out开始时在用户态运行,在hello.out调用sleep函数后转入核心态。此时内核休眠,并将hello.out对应的进程从运行状态设置为等待状态,让计时器颠末肯定时间(此处为1s)后发送一个制止信号,令核心态执行制止处置处罚,将hello.out的进程由等待状态变为运行状态,hello.out继承执行。在循环中hello.out反复重复上述过程,直到hello.out中的循环竣事,不再调用sleep函数。
6.6 hello的异常与信号处置处罚
可能出现的异常:陷阱异常、制止异常、故障异常。
信号:SIGINT、SIGTSTP、SIGCHLD。
陷阱异常:hello.out每次调用sleep函数时都必要系统使用陷阱异常使得内核休眠。陷阱处置处罚程序返回后控制流回到调用sleep函数的下一条指令。
制止异常:sleep函数在调用后将设置一个定时器,定时器竣事后会发送信号导致制止异常;在程序运行时按下Ctrl+C、Ctrl+Z等组合键也会发送该信号。发生制止异常后会调用制止处置处罚程序,该程序返回后将控制回到下一条指令。
故障异常:缺页异常是一种常见的故障异常。在CPU访问一个被映射的虚拟页时,触发缺页异常,此时缺页处置处罚程序从磁盘中加载对应页面至内存,然后将控制返回引起该故障异常的指令。由于返回后,CPU试图访问的物理页面已经被复制到内存中,指令就不会再次触发故障异常。
SIGINT:键盘按下Ctrl+C时会产生该信号,运行中的程序在收到该信号后会终止。
SIGTSTP:键盘按下Ctrl+Z时会产生该信号,运行中的程序在收到该信号后会制止。
SIGCHLD:当父进程创建的子进程终止时,内核会向父进程发送SIGCHLD信号,通知父进程采取子进程。
图6-1-1:在hello.out执行过程中乱按:键盘输入会被存放至输入缓冲区,hello.out结尾的getchar会读取缓冲区第一个回车前的所有内容然后终止,在这之后的输入会被shell读取并尝试执行。
https://img-blog.csdnimg.cn/direct/6f2b231789f24d8386e4c37fbd51d18a.png
图6-1-1
图6-1-2:Ctrl+C:按下Ctrl+C后向前台运行的hello.out发送SIGINT信号,导致hello.out终止,进程被采取。
https://img-blog.csdnimg.cn/direct/c16ff4613662408fbb813ab774be2600.png
图6-1-2
图6-1-3、图6-1-4:Ctrl+Z:按下Ctrl+Z后向前台运行的hello.out发送SIGTSTP信号,导致hello.out制止。输入ps可以看到该进程暂时还未终止;输入jobs看到该作业被制止;输入pstree得到hello.out和bash的树状进程父子结构;输入fg 1让该作业继承在前台运行;输入kill -9给该进程发送SIGINT信号,在这之后通过ps发现该进程已终止。
https://img-blog.csdnimg.cn/direct/81097b5368e9418f950a1a09e2a148d1.png
图6-1-3
https://img-blog.csdnimg.cn/direct/495293e7372542e198810e0ccb527605.png
图6-1-4
6.7本章小结
本章先容了进程的概念和作用、shell的作用和处置处罚流程、通过fork创建子进程、通过execve执行hello.out的过程;分析了hello.out在执行过程中的上下文切换和进程调度过程;最后枚举了几种在hello.out执行过程中可能出现的异常和信号处置处罚情况。
第7章 hello的存储管理
7.1 hello的存储器地点空间
逻辑地点:程序的代码在编译后出现在汇编语言程序中的地点,是一种段地点,是机器语言指令使用的地点,用于指定操纵数或指令的地点,同时也是hello.out的机器代码使用的地点。
线性地点:一个非负整数地点组成的有序聚集是一个地点空间,若其中的整数是一连的则该地点空间又称作线性地点空间。
虚拟地点:CPU在一个带虚拟内存的系统中天生一个具有2n个地点的虚拟地点空间,n通常为32或64.
物理地点:计算机系统的主存中每个字节都拥有的唯一的物理地点,是执行hello.out时访存的地点终极转化成的地点。
7.2 Intel逻辑地点到线性地点的变换-段式管理
Intel中将逻辑地点转化为线性地点的过程主要与以下几个概念有关:段寄存器、段选择符、段描述符表。
段寄存器:一个16位的寄存器,用于存储该段的段选择符。
段选择符:一个长16位的二进制格式串,前13位用于定位该段的段描述符位于段描述符表内的下标;第14为标志该段描述符位于全局描述符表GDT中照旧局部描述符表LDT中;第15、16两位代表CPU的特权级,等于00代表最高级的内核态,等于11代表最低级的用户态。
段描述符:段描述符为一个长8字节的二进制格式串,具体格式如图7-1所示。其中DPL代表访问该段所需的最低特权等级要求,若CPL>DPL则访问越级,禁止访问;B31-B0共32位代表这个段的段基地点;L19-L0共20位代表该段中的最大页号;G=1时该段内的页以4KB为单位,G=0时以字节为单位;D=1时该段的段内偏移量为32位宽,D=0代表为16位宽。
https://img-blog.csdnimg.cn/direct/ee951243f0cd452e9c2d222530dbcccf.png
图7-1
转化流程:一个逻辑地点通常有48位,其中前16位为段选择符,后32位为段内偏移量。获取段选择符后系统在对应的GDT或LDT中探求对应的段描述符。获得段描述符后系统获取该段的段基地点,将32位段基地点与32位段内偏移量相加获得32位线性地点。
7.3 Hello的线性地点到物理地点的变换-页式管理
Intel中将线性地点作为虚拟地点。硬件维护一个表明该虚拟页是否被分配或被缓存在DRAM、该缓存在DRAM中的起始地点的PTE列表,这个列表被称作页表。每次hello.out访问一个虚拟地点,硬件通过该地点访问其在页表中对应的PTE条目,通过获取的条目选择访问DRAM读取数据、从磁盘中读取并更换DRAM内的缓存或在磁盘上创建一个新的虚拟页。这个过程被称作虚拟内存的页式管理。
7.4 TLB与四级页表支持下的VA到PA的变换
由于页表本身必要直接存在DRAM中,当物理地点空间变得很大时对应的页表也会变大,所以引入了多级页表的方式来减小DRAM存储页表的空间开销。
在多级页表的工作方式下,虚拟地点VA被拆成(n-p)位的虚拟页号VPN和p位的虚拟页偏移量VPO。相应地,物理地点拆成(m-p)位的物理页号PPN和p位的物理页偏移量PPO。
在多级页表中,每个非最后一级页表的PTE存储的是该PTE条目对应的下一级页表的基地点,只有最后一级页表中的PTE存储该PTE对应的PPN。将PPN与VPO组合后得到实际访问的物理地点。访问多级页表时,VPN被分割为若干个对应每一级页表的VPN,分别用于对应级页表的PTE条目查找。通过多级页表的构造,DRAM中只必要存储一级页表就能获得整个物理地点空间的访问路径。
由于多级页表中每次转换VA与PA都必要将每级页表访问一次会造成不小开销,在这个过程中使用TLB加速访问。TLB相当于一个位于CPU内的小型页表,一共有2t组,将VPN的后t位作为TLB索引TLBI,剩余的(n-p-t)位作为TLB标记TLBT。CPU每次访问一个虚拟地点时,先在TLB中探求该虚拟地点的VPN对应的PTE,若找到该PTE则直接获取对应的物理地点,否则从DRAM的页表中取出相应的PTE并存放或覆盖在TLB中。
7.5 三级Cache支持下的物理内存访问
三级缓存的具体工作方式为每级缓存存储下一级缓存的内容,L3缓存存储DRAM中的内容。三级缓存的每一级都被分成若干包含E个块的组,每块的巨细均为64字节,E被称为缓存的相联度。对于一个物理地点,其末端6位被缓存解读为块偏移CO,接下来的6、9、13位被解读为高速缓存组索引CI,分别对应64组L1缓存、512组L2缓存和8192组L3缓存。剩余的位被解读为高速缓存标记CT。
每次CPU试图访问一个物理地点时,CPU都会逐级去尝试访问缓存,若该级缓存未命中则尝试访问下一级缓存。每次在某一级读取到内容后,该内容会被写入或覆盖上一级缓存,具体的覆盖模式由缓存的更换策略控制。
每次CPU试图写入一个物理地点时,根据该地点的缓存与否有两种情况。若写入一个被缓存的地点,存在直写(立即将更改写入下一级缓存)和写回(当被修改的块将被丢弃时将更改写入下一级缓存)两种策略。若写入一个未被缓存的地点,也存在写分配(将该地点对应的块读入该级缓存后进行更改)和非写分配(直接在下一级缓存中进行更改)两种策略。
7.6 hello进程fork时的内存映射
当进程调用fork函数时,内核为hello.out进程创建各种数据结构并分配一个唯一的进程号PID,同时还必要为其创建虚拟内存。在创建虚拟内存的过程中,内核创建描述当前进程虚拟内存状态的mm_struct、地区结构和页表的副本。两个进程中的所有页面都被标记为只读,两个进程中的地区结构都被标记为私有的写时复制。这样,当新进程的fork返回时,新进程所在的虚拟内存就会与调用fork时的虚拟内存相同。当这两个进程在之后进行写操纵时,写时复制机制就会创建新页面,进而保持两个进程的私有地点空间抽象。
7.7 hello进程execve时的内存映射
调用execve后,为了在当前进程中加载并运行hello.out中的程序替代当出息序,内核必要完成以下步骤:删除当前进程虚拟地点中用户部分的地区结构;为新进程的代码、数据、bss区和栈地区创建新的私有、写时复制的地区结构。其中,代码和数据地区被映射为hello.out中的.text和.data区,bss区的巨细在hello.out的ELF头中给出,初始化为0,栈和堆地区初始化长度为0;将hello.out链接的共享对象进行动态链接,然后映射到用户虚拟地点空间的共享地区;设置当前进程的程序计数器PC为hello.out的代码入口点。完成所有操纵后,下次调度该进程时,进程会在hello.out的上下文中从hello.out的入口点开始执行,完成剩余工作。
7.8 缺页故障与缺页制止处置处罚
缺页故障的触发缘故原由除了DRAM缓存miss,尚有访问未分配的虚拟地点等情况。对于每个缺页故障,缺页异常处置处罚程序会根据试图访问的虚拟地点VA进行以下操纵:根据地区结构表查询该VA是否是一个位于某个地区内的正当地点,对非法地点触发段错误并终止进程;根据地区页面的权限管理决定对VA的内存访问是否正当,若程序试图对一个不具有对应权限的地区进行不正当的内存操纵,缺页异常处置处罚程序会触发一个保护异常并终止该进程;否则,该缺页异常由一个对正当的VA进行正当的内存操纵造成,内核将该地点所在的页面与DRAM内的某个页面进行交换并更新页表,然后缺页处置处罚程序返回。返回后,CPU再次执行引起缺页故障的指令时对虚拟地点VA的访问可以通过页表进行正常翻译,进而不会产生相同的缺页故障。
7.9动态存储分配管理
为了提供动态内存分配功能,内核维护虚拟内存的一部分地区,将其分配为堆。对于每个进程,内核还维护一个指向堆顶部的变量brk。
进行分配时,分配器将堆空间视为一组由不同巨细的块组成的聚集。每个块对应一个一连的、已分配或空闲的虚拟内存片。已分配的块显示地为程序使用生存,而空闲块直到被应用分配前都保持空闲。一个已分配的块直到被开释后变为空闲状态。
malloc是一种显式分配器,对于所有颠末malloc分配的块必须使用free进行开释。为了分身内存动态分配和开释请求的吞吐率和内存的使用率,malloc使用一种双向的链表结构维护整个堆空间。对于其中的内存块,在内存块的载荷头部维护这一块的巨细和空闲标记,在脚部维护对齐所需的0-padding、块巨细和空闲标记。当一个内存块被开释为空闲块后,将其载荷修改为包含该块的前驱块和后继块信息。这样,每次开释一个内存块时都可以及时将其与周围的空闲块进行合并,通过常数时间的开销进步了内存使用率。
7.10本章小结
本章先容了执行hello.out时会发生的内存管理,包含四种地点、Intel的段式内存管理、页表的页式管理、TLB与四级页表实现的虚拟地点VA与物理地点PA之间的转换过程、三级cache实现的物理地点PA的内存访问。对于进程运行,先容了调用fork、调用execve时发生的内存映射、缺页故障与缺页制止处置处罚程序和内核的动态内存存储分配管理过程。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
在Linux中,所有的I/O设备都被模型化为文件,而所有的输入和输入都被党走对相应文件的读、写执行。通过这种映射方式,Linux内核能够引出一个简单、低级的应用接口Unix I/O来将所有输入、输出方式统一为一种方式来执行。
8.2 简述Unix IO接口及其函数
接口:打开文件、改变当前的文件位置、读写文件、关闭文件。
打开文件:应用程序要求内核打开相应文件,宣告其试图访问一个I/O设备。内核返回一个小非负整数作为描述符标识该文件。应用程序记录该描述符,而内核记录有关该文件的信息。Linux shell创建的进程在开始时都有三个打开的文件:尺度输入(0)、尺度输出(1)和尺度错误(2),在<unistd.h>中由STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO定义。
改变当前文件位置:内查对每个打开的文件保持一个文件位置k,即从文件开头起始的字节偏移量。
读写文件:读操纵是从当前文件位置k开始复制n个字节到内存,然后更新k为k+n,若这次操纵导致k大于文件的巨细m则触发一个EOF条件;写操纵是从内存复制n个字节到文件位置k,然后更新k。
关闭文件:应用完成对文件的访问后通知内核关闭这个文件,内核开释该文件打开时创建的数据结构并将对应的描述符规复到可用的描述符池中。当一个进程岂论任何缘故原由终止时,内核都会关闭打开的所有文件并开释内存资源。
函数:open, close, read, write, lseek
open(char* filename, int flags, mode_t mode)用于将filename转换为文件描述符,flags标明进程访问该文件的方式,mode限定该文件的访问权限。
close(int fd)关闭一个打开的文件。
read(int fd, void*buf, size_t n)用于从描述符为fd的文件中复制最多n个字节到内存位置buf,返回实际复制的字节数,-1表示错误,0表示EOF。
write(int fd, const void* buf, size_t n)用于从内存位置buf向描述符为fd的文件中复制至多n字节到当前文件位置,返回实际复制的字节数,-1代表错误。
lseek函数用于显示地修改当前文件的位置。
8.3 printf的实现分析
读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
调用printf后,printf内创建一个巨细256字节的缓冲区,获取可变形参在栈上的第一个地点,将缓冲区、格式字符串和可变形参的第一个地点放入vsprintf中获取其返回值,通过write函数调用Unix I/O完成输出后返回输出的字节个数。
在vsprintf中,函数通过解析格式字符串对printf创建的输出缓冲区进行添补,最后返回输出缓冲区内添补的字节个数,即write函数所需的写入文件的字符个数。
回到write函数后,经反汇编发现函数通过int指令调用制止门来实现特定的系统服务。在这之后,write调用sys_call函数,sys_call函数生存制止前的进程上下文,然后将字符串中的字节从寄存器中通过总线复制将字符的ASCII码复制到显存。字符显示驱动子程序通过ASCII码在字模库中找到点阵信息并存储到vram中,显示芯片按照肯定频率逐行读取vram后通过信号线向液晶显示器传输每一个点,使得字符串能够在屏幕上显示。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘制止的处置处罚:键盘制止处置处罚子程序。担当按键扫描码转成ascii码,生存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到担当到回车键才返回。
getchar()函数静态地定义了一个本地的缓冲区和缓冲区字符计数器。getchar()执行时通过read函数,调用Unix I/O不断从输入缓冲区中读入一个定义为BUFSIZ的字节数,更新缓冲区计数器和缓冲区本身,每次将计数器减少1并返回在缓冲区中对应的字符,直到计数器归0后再次调用read函数更新缓冲区。若更新缓冲区后计数器仍为0则代表read触发了一次EOF,此时getchar()返回EOF。
当用户在键盘按下按键时,键盘接口会得到一个代表该按键的扫描码,同时产生一个制止请求。此时,键盘制止服务程序先从键盘接口取得按键扫描码,然后根据该扫描码判断对应按键并进行后续处置处罚(如正常的输入字符、Ctrl+C发送SIGINT信号等),最后通知制止控制器竣事本次制止并返回。每次getchar()开始进行读入缓冲区就有可能是按下回车键触发了某个后续的特殊信号导致的。
8.5本章小结
本章先容了Linux的I/O设备管理方法、Unix I/O的接口和函数,分析了printf和getchar的实现,更深入地体现了Linux对万物皆文件的系统级抽象。
结论
hello程序履历的过程:
1. 编写并生存源程序.c文件。
2. 预处置处罚器cpp根据include语句将头文件内容复制到这个文件,根据define语句进行宏更换等等,终极形成.i文件。
3. 编译器cc1将.i文件编译成.s汇编程序文件。
4. 汇编器as将.s文件中的汇编程序翻译成机器语言,天生一个.o可重定位目标文件。
5. 链接器ld对我们天生的.o文件中引用的printf, sleep等库函数和两个pritnf格式串做符号解析,对非共享函数和两个格式串做重定位,天生一个可执行目标文件hello.out。
6. 在shell中输入./hello.out 2 xxx 3 4,指示shell运行hello程序并给出参数。shell通过Unix I/O得到该下令并进行处置处罚。
7. shell调用fork函数创建一个子进程,调用execve函数将hello.out文件载入该进程的上下文,调用mmap函数建立虚拟内存映射,终极设置当前进程上下文中的程序计数器PC,使之指向hello.out的ELF头中的程序入口处。CPU通过进程调度让hello.out进程在不同的时间片上运行,进而在硬件上流水线化执行。
8. 程序运行中进行访存时,首先由CPU中的段——页式管理机制将机器指令中的逻辑地点翻译成虚拟地点,然后由CPU上的内存管理单元MMU将虚拟内存地点翻译成物理内存地点:先后在CPU内部的TLB里中查询该虚拟地点对应的PTE,失败后在DRAM中的四级页表中查询PTE,该虚拟页未分配或未缓存则触发缺页异常更新页表,回到CPU重新执行该机器指令,终极在TLB或页表中获取对应的PTE,根据PTE将虚拟内存地点翻译成物理内存地点。获取物理地点后,硬件通过三级cache逐级查询该物理地点对应的实际数据,cache miss后根据各种读、写更新策略对cache进行相应的更新。
9. 程序运行中调用了共享库函数时必要完成动态链接,特别是第一次调用该函数时进行了延迟链接,在运行时通过GOT表的初始化魔数、PLT和GOT两个数据结构和动态链接器的配合,得到函数的运行时地点并更新GOT表。进行后续的共享库函数调用时,就可以通过延迟链接写入内存的GOT条目得到该共享库函数的运行时地点。
10. 程序执行途中可能有程序调用kill函数大概键盘按下Ctrl+C或Ctrl+Z,使内核发送信号,hello.out所在进程可能会对这些信号做出反应,比如终止或制止。假如制止可以使用shell的fg下令规复运行。
11. 输出和getchar过程:printf调用malloc进行动态内存分配获取一个缓冲区,通过vsprintf进行格式字符串和可变参数的解析,最后调用系统write函数,通过显卡上的显存向液晶显示器传输每一个点,使输出字符串终极显示在屏幕上。程序的最后调用getchar函数调用了系统read函数,等待第一个回车符后返回。
12. 程序终止后,内核向父进程发送SIGCHLD信号,hello.out所在进程被shell采取。
感悟:计算机系统是一个复杂而精细的系统,几乎它的每个部分都体现着设计者的聪明:比如对进程为应用程序提供的时空上的抽象:应用程序在进程的抽象中好像正在独占处置处罚器与内存系统,分别对应于进程提供的独立逻辑控制流和私有空间地点。这两个抽象极大地简化了编写应用程序时对控制流和对地点空间的思索;延迟链接与PLT、GOT、动态链接器的实现,真正做到了花费最小的开销完成最多的工作,极大地减小了链接时的工作复杂度;Linux对I/O设备和文件的抽象,将鼠标、键盘这种实际的物理设备和输入流stdin、输出流stdout、错误流stderr这种虚拟的I/O流抽象成具有同种属性同种接口的文件,最小化了各种管理I/O设备的难度。所有的这些优化汇聚到一起才有了今天的快速而安全的计算机系统。
附件
hello.i: 颠末cpp预处置处罚之后的源程序。
hello.s: 颠末cc1编译之后得到的汇编语言程序。
hello.o: 颠末as汇编hello.o天生的可重定位目标文件。
hello.out: 颠末ld链接后天生的可执行目标文件。
参考文献
https://www.cnblogs.com/pianist/p/3315801.html
程序人生-Hello’s P2P_edb symcall-CSDN博客
深入了解计算机系统(第三版)2016 Bryant,R.E. 机器工业出书社
C语言 getchar()原理及易错点解析_getchar(c)错在哪儿-CSDN博客
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]