程序人生
Posted 御用马铃薯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序人生相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 190110904
班 级 12
学 生 王俊杰
指 导 教 师 史先俊
计算机科学与技术学院
2021年5月
摘 要
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
摘要:本篇论文用自然语言描述了一个最简单的hello.c文件在linux系统下是如何经历预处理、编译、汇编、链接等过程,以及在这些过程中shell对该进程进行的进程管理、存储管理和IO管理。
关键词:预处理、编译、汇编、链接、进程管理、存储管理、IO管理。
(摘要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.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:from program to process
P2P具体过程:
- 驱动程序首先运行C预处理器cpp,将源程序hello.c翻译成一个ASCII码格式的中间文件hello.i(命令linux> cpp [args] hello.c -o hello.i)
- 驱动程序运行C编译器ccl,将上一步生成的.i文件翻译成一个ASCII汇编语言文件hello.s(命令linux> gcc -S hello.i -o hello.s,教材上给的cc1在实际执行时无此命令)
- 驱动程序运行汇编器as,将hello.s翻译成一个可重定位目标文件hello.o(命令linux> as [args] hello.s -o hello.o)
- 驱动程序运行链接器ld,将hello.o与一些必要的系统目标文件和其他需要链接的.o文件(本实验中没有)组合起来,生成一个可执行目标文件hello(命令linux> ld -o hello [sysfiles & args] hello.o)
- 执行hello时,shell调用操作系统中的加载器,将可执行文件中的代码和数据复制到内存,然后将控制转移到这个程序的开头(命令linux> ./hello)
020:from 0 to 0
具体过程:
- shell运行一个程序时,父shell进程生成一个子进程,它是父进程的一个复制。
- 子进程通过execve系统调用启动加载器,加载器会删除子进程现有的虚拟内存段,并创建一组全新的代码、数据和堆栈。
- 虚拟地址空间中的页映射到可执行文件的页大小的片,新代码和数据段被初始化为可执行文件的内容。
- 加载器跳到_start地址,最终调用应用程序的main函数,执行代码。
- 代码执行完毕后父进程(或init进程)对子进程进行回收,内核删除相关数据。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:Intel(R) Core(TM) M-5Y10c CPU @ 0.80GHz (x64) 4G cache
软件环境:Ubuntu 18.04 Gnome Terminal gdb-debugger objdump
1.3 中间结果
hello.c: C源程序
hello.i: 将.c文件预处理后得到的中间文件
hello.s: 将.i文件编译后得到的汇编代码文件
hello.o: 将.s文件汇编后得到的可重定位目标文件
hello: 将.o文件链接后得到的可执行文件
1.4 本章小结
第一章宏观的简述了hello.c程序的生命历程。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:
预处理是指在程序源代码被翻译为目标代码的过程中生成二进制代码之前的过程,具体来说是在对源程序正式编译前由预处理程序完成的过程。
作用:
宏指令预处理:(例如#define x y)替换在文件中后续出现的所有宏x将会在程序编译之前被替换为y。
条件编译:某些指令可以用来有选择的对部分程序源代码进行编译。例如#ifdef - #endif。
头文件包含指令:#include <file> / #include “file”
#和##预处理运算符:(例如#define x #x)将x转换为用引号引起来的的字符串”x”(字符串化)。
C++中还提供了例如__LINE__、__FILE__的预定义宏。
2.2在Ubuntu下预处理的命令
命令linux> cpp [args] hello.c -o hello.i
2.3 Hello的预处理结果解析
通过对hello.i文件查看,里面代码量达到三千多行,主要内容是hello.c文件中包含的头文件的被加载在.i文件中(进行了宏替换),在.i文件的底部能看到熟悉的main函数的中间文件的语言格式。
2.4 本章小结
预处理的过程:驱动程序运行C预处理器cpp,将源程序hello.c翻译成一个ASCII码格式的中间文件hello.i(命令linux> cpp [args] hello.c -o hello.i),其中.i文件里面代码量很大,主要是包含的头文件的宏替换。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:
指将经过预处理之后的程序,进行语法和句法的分析、确认所有的指令都符合语法规定之后,将高级语言转换成功能相同的汇编代码的过程。
作用:
将预处理后的程序转换为汇编代码,为后面的汇编做准备。
3.2 在Ubuntu下编译的命令
linux> cc hello.i -Og -o hello.s
或linux> gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
使用objdump -D hello.s命令进行反汇编查看,截图中展示了代码段的内容
由于源程序中只涉及到几个局部变量,而没有任何全局变量或静态C变量,所以查看.bss和.data段时二者为空(如下图),而对汇编代码分析,局部变量i存储在寄存器%ebx中,参数argc存放在寄存器%edi中。
3.3.1数据
按照汇编代码约定,局部变量依次存放在寄存器%rdi %rsi %rdx %rcx %r8 %r9中,若还有更多,则存放在堆栈中(x64系统、Intel格式汇编)。
通过观察汇编代码可知,涉及的变量全部存在寄存器或堆栈中:
变量 |
存放位置 |
i |
%ebx |
argc |
%edi |
argv[] |
堆栈中,调用时存放在%rdi、%rsi中 |
3.3.2赋值操作
赋值操作主要用mov指令进行完成,mov指令的具体后缀因操作数的不同而不同,具体规定总结如下:
suffixes |
全称 |
含义 |
b |
byte |
8 bits |
s |
single |
32-bit floating point |
w |
word |
16 bits |
l |
long |
32-bit integer/64-bit floating point |
q |
quad |
64 bits |
t |
ten-bytes |
80-bit floating point |
o |
octuple |
128 bits(octuple words) |
而本实验中只使用了mov指令,省略了后缀,分析可知实际上用到的只有movq和movl。此外,还可以使用lea指令达到赋值目的。
3.3.3类型转换
本例中涉及到了atoi类型转换,汇编代码中对应了
callq 660<atoi@plt>
这句指令。
3.3.4算术操作
本例中涉及的算数操作只有i++这句,对应的汇编代码为
add $0x1,%ebx
常用的算数操作还有sub、imul、idiv,它们的后缀q、l等与上面列表中写出的相同。
3.3.5关系操作
本例中涉及的关系操作只有\'argc!=4\'这句指令,对应的汇编指令是
cmp $0x4,%edi
cmp(compare)指令的实质是做减法操作,暂存差值,通常与跳转指令联用,可以直接与0进行比较。
3.3.6数组/指针
本例中参数*argv[]是一个指针数组,对应的汇编代码实现包括以下几句:
mov 0x8(%rbp),%rsi
mov 0x10(%rbp),%rdx
mov 0x18(%rbp),%rdi
可见,指针数组被存放在堆栈中,用%rbp记录数组的起始位置,通过每次增加8字节的偏移量来表示数组中的每个元素。
3.3.7控制转移
本例中涉及到的控制转移语句有for循环语句和if条件分支语句。
if语句:
if(argc != 4)对应
cmp $0x4,%edi
jne 7bf
if语句是使用jmp类型的语句完成跳转的,jmp类型的语句分类如下表:
jmp |
jump |
je |
jump if equal |
jne |
jump if not equal |
js |
jump if signed |
jns |
jump if not signed |
jg |
jump if greater |
jge |
jump if greater or equal |
jl |
jump if less |
jle |
jump if less or equal |
ja |
jump if above |
jae |
jump if above or equal |
jb |
jump if below |
jbe |
jump if below or equal |
表中jg、jl与ja、jb的区别是前两个进行有符号数的判断,后两个用于无符号数。
for语句:
for(i = 0; i<8 ;i++)对应的汇编代码为:
mov $0x1,%ebx
cmp $0x7,%ebx
jle 7d5
add $0x1,%ebx
for循环同样是使用上面表中列出的jmp系列的指令来实现的。
此外,控制转移指令还可以通过call指令实现。
3.3.8函数操作
本例中涉及的函数调用操作有printf("..."); exit(1) sleep getchar() return atoi
对应的汇编代码为:
lea 0xe2(%rip),%rdi
callq 630<puts@plt>
mov $0x1,%edi
callq 670<exit@plt>
mov $0x0,%eax
callq 640<printf@plt>
mov 0x18(%rbp),%rdi
callq 660<atoi@plt>
mov %eax,%edi
callq 680<sleep@plt>
callq 650<getchar@plt>
retq
基本都是依靠call指令实现的。
3.4 本章小结
本章说明了编译过程的原理,编译过程的命令是linux> cc -S hello.i -o hello.s或linux> gcc -S hello.i -o hello.s,并以hello.c与hello.s对照为例,说明了c语言中各种命令操作在汇编语言中的体现。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:
汇编过程指的是把汇编语言代码翻译成目标机器指令的过程。
作用:
将汇编代码翻译为目标机器指令,生成二进制的目标文件。使其链接后能被机器识别并执行。
4.2 在Ubuntu下汇编的命令
命令为linux> gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
命令为linux> readelf -a hello.o
4.4 Hello.o的结果解析
可重定位目标文件的结构如上图所示,分析内容:
1.ELF头:以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。具体内容如截图:
2..text段:存储已编译程序的机器代码。
3..rodata段:存储只读数据。
4..data段:存储已初始化的全局和静态变量。
5..bss段:存储未初始化的全局和静态变量以及所有被初始化为0的全局或静态变量。
6..symtab段:一个符号表,存放在程序中定义和引用的函数和全局变量的信息。
7..rel.text段:一个.text节中位置的列表。当链接器把这个目标文件和其他文件组合时需要修改这些位置。
8..rel.data段:存放被模块引用和定义的所有全局变量的重定位信息。
9..debug段:符号调试表。
10..line段:原始C源程序中的行号和.text节中机器指令之间的映射。
11..strtab段:存放字符串表。
如图可知
1.对比发现,汇编操作前hello.s文件的分支跳转语句是利用.L1这样的助记符来辅助描述段中的位置;而反汇编查看汇编后的hello.o文件,分支跳转语句变成了利用段内函数的偏移地址来描述跳转命令。汇编以后显然每条指令都有了确定的虚拟地址,而汇编前是没有的。
2.函数调用指令也有不同。图中可以清晰地看到,汇编前调用atoi、exit等系统函数是使用call指令+函数名进行的;而汇编后则变成了call+偏移地址,很显然汇编的过程使得程序内引用的系统函数都加入到整个代码段中了。
4.5 本章小结
本章主要描述了汇编的过程。通过分析可重定位目标文件的结构内容和将汇编前后的汇编指令的对比,描述了汇编的具体工作内容和原理。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程。
作用:
链接过程用到的链接器在软件开发中扮演着重要的角色。链接器使得分离编译成为可能。不需要将一个大型的应用陈谷组织为一个内容很多的源文件,而是可以把它分解为更小的更便于管理的子模块,修改器中的某个模块时只需要重新编译它自己并重新将其与其他模块链接,而不需要编译整个工程的所有代码。
5.2 在Ubuntu下链接的命令
使用gcc -v hello.o命令查看hello.o在生成可执行文件时依赖的系统库和相关目标文件
执行命令ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/7 -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. hello.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o -o hello
执行结果
5.3 可执行目标文件hello的格式
各节的起始地址与大小的等信息的对比如图,节头增加至28个。
5.4 hello的虚拟地址空间
程序头如图
PHDR:保存程序头表
INTERP:动态链接器的路径
LOAD:可加载的程序段
DYNAMIN:保存了由动态链接器使用的信息
NOTE保存辅助信息
GNU_STACK:标志栈是否可执行
GNU_RELRO:指定重定位后需被设置成只读的内存区域
使用gdb工具调试hello程序,设置断点并run至该断点处,此时使用ps -u命令查看此进程的PID,如图
使用cat /proc/<PID>/maps命令查看该进程虚拟地址空间的内存布局。如图
5.5 链接的重定位过程分析
对比可知变化:
1.链接后新增加了很多重定位的节,包括.init .plt .plt.got .fini
2.text节中增加了很多函数段,例如<_start> <frame_dummy>等
5.6 hello的执行流程
(1)载入:_dl_start、_dl_init
(2)开始执行:_start、_libc_start_main
(3)执行main:_main、_printf、_exit、_sleep、_getchar、_dl_runtime_resolve_xsave、_dl_fixup、_dl_lookup_symbol_x
(4)退出:exit
5.7 Hello的动态链接分析
在dl_init之前,每一条动态共享链接库中的PIC函数的调用,由于编译器无法确定函数运行时的地址,所以需要进行重定位。此时对每一条IC的调用过,目标地址都是PLT的代码逻辑,GOT存放PLT中函数调用的下一条地址。
5.8 本章小结
通过调试程序以及利用readelf、objdump等工具对链接前后的程序结构内容的查看,说明了链接过程的作用和效果。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:
一个执行中程序的实例
作用:
进程概念的引入使得我们运行某个程序时,改程序好像占有着全部的软硬件资源。
6.2 简述壳Shell-bash的作用与处理流程
shell-bash作用:
是一种交互式的应用程序,提供了一个用户可以访问系统内核的界面。
处理流程:
1.获取命令
2.分割命令,获取指令以及指令参数
3.含有的内置命令会被立即执行
4.命令是一个可执行文件,为其创建子程序并执行
5.时刻接收来自外部的信号
6.3 Hello的fork进程创建过程
在终端输入./hello命令执行hello文件时,由于输入的不是内置命令而是可执行文件,shell会立即在一个心得子进程的上下文中加载并运行hello文件。新创建的子进程几乎但不完全与父进程相同,子进程可以得到与父进程的代码、数据段、堆栈和共享库的相同但独立的一个副本。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个新程序,加载并运行可执行文件hello,并带参数列表argv和环境变量列表envp(出现错误时返回到调用应用程序)。在本例中,生成的子进程通过execve系统调用启动加载器,加载器删除子进程先用的的虚拟内存段,并创建一组新的代码和数据结构。
6.5 Hello的进程执行
输入./hello,shell为hello创建一个新的子进程,进行上下文切换(用户模式进入内核模式,切换完成后回到用户模式),子进程调用execve加载运行,期间遇到任何来自键盘的外部中断都会导致进入内核模式。子进程运行时,shell使用waitpid函数等待其作业终止,终止后shell会回收该子进程并进入下一轮迭代。
6.6 hello的异常与信号处理
运行时会遇到可能的四种异常:
1.中断:来自I/O设备的信号(硬件异常)
2.陷阱:有意的异常
3.故障:潜在可恢复的错误
4.终止:不可恢复的错误
正常运行时会将字符串“190110904 wangjunjie”连续打印8次,期间输入ctrl+z可以发送SIGSTSTP信号,导致进程暂停,这时输入ps命令可以看出hello进程并未被回收,输入fg可以恢复运行hello
输入ctrl+c会直接终止命令(发送SIGINT信号),如果运行期间乱点键盘,输入将被getchar函数存入缓冲区,以回车结尾可以退出getchar函数并继续执行return 0,退出程序。
6.7本章小结
本章节概括了进程这个重要的概念,并由进程的角度分析了hello这个可执行文件在执行过程的从头至尾调用的函数、涉及的shell变化、遇到中断时的内部运作机制。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:段地址:偏移地址。由cpu生成,在debug时经常用到。
物理地址:真正的硬件上的地址。cpu芯片通过mmu将虚拟地址翻译为唯一的物理地址。
虚拟地址:cpu通过生成虚拟地址实现访存。
线性地址:线性地址是逻辑地址翻译为物理地址的中间过程。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式内存管理:
程序由四个逻辑分段组成:代码段、数据段、堆段和栈段,其中不同的段有不同的属性,由不同的段寄存器对应。
地址变换过程:
分段机制下的虚拟地址由两部分组成:段选择因子和段内偏移量。CPU将虚拟地址解析为段选择因子和段内偏移量,其中段选择因子包含了段号以及一些标志位(如特权标志位),由其中的段号在段表中搜索特定的段内描述符,找到对应的段内描述符后将其解析为段基地址、段界限、DPL三部分,拿出其中的段基地址作为起始地址,与虚拟地址中的段偏移量共同组合成为物理地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式内存管理:
把整个虚拟和物理内存空间都切分成一块块固定大小的页,将虚拟页和某些物理页对应起来,这样找到虚拟页就能找到对应的物理页,来达到访问物理页中数据的目的。
地址变换机制:
将虚拟地址解析为两部分:虚拟页号和虚拟页偏移量。拿出虚拟页号,到页表(存放在MMU中,由页表基址寄存器PTBR指向)中搜索对应的页表条目,寻址过程中我们关注页表条目中的两部分:“有效位”和“物理页号或磁盘地址”,有效位为1则表明该虚拟页已经与某个内存中的物理页产生了对应关系,可以直接在内存中取出;有效位为0则表明该虚拟页对应的物理页不在内存中,此时有效位后存放的就是磁盘地址,需要到磁盘中取出该物理页,将其放入内存并进行数据访问。这个过程中如果出现缺页现象,则还需要进行缺页中断处理,用本次操作需要的物理页替换掉内存中某个物理页,并修改页表中二者的状态。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:
translation lookaside buffer,翻译后备缓冲器,是一个小的、虚拟寻址的缓存,每一行都保存着一个由单个PTE组成的块。
多级页表:
32位系统下一级页表寻址即可满足要求,但64位下情况会更复杂。多级页表机制下CPU会生成一个由k个虚拟页号和一个虚拟页偏移量组成的虚拟地址,其中每个虚拟页号分别对应每一级列表的页内偏移地址。每一级页表的每一个页表条目都会指向某个下一级页表的起始位置。以这种机制实现大量页表数据的更快速查找。
地址翻译机制:
CPU会生成一个由4个9位虚拟页号和1个12位虚拟页偏移量组成的48位虚拟地址,首先将4个9位虚拟页号组成的36位虚拟页号解析为32位的TLB标记(TLBT)和4位的TLB索引(TLBI),并放入翻译后备缓冲器TLB中搜索条目,若命中,则直接取出物理页号,与之前的虚拟页偏移量(等于物理页偏移量)组合生成物理地址;若不命中,则将虚拟页号按照多级页表的查询方式查找到物理页号,同样与虚拟页偏移量组成物理地址。
7.5 三级Cache支持下的物理内存访问
将上一题中得到的物理地址,取索引对应位,在一级缓存中寻找对应组,若寻找命中,则比较标志位,并检查有效位是否为1,满足条件则为命中。若不满足条件,则继续在二级、三级缓存中进行相同的查找步骤,若命中,则将找到的物理页放入一级缓存中(可能会发生页牺牲)并将地址返回给CPU。
7.6 hello进程fork时的内存映射
输入命令./hello后shell会调用fork创建子进程,分配一个与父进程不同的PID,但获取与父进程相同的堆栈、本地变量值、相同的全局变量和代码(指向相同的物理内存);但若某个进程尝试对某个变量x进行“写”操作,则会导致shell为此变量x创建一个全新的副本(不同的物理地址)作为此次“写”操作的操作数,此时父进程与子进程中该变量x的值是不同的,体现出每个进程保持了私有的地址空间。
7.7 hello进程execve时的内存映射
步骤:
1.删除已存在的用户区域。
2.映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构(所有这些新的区域都是私有的、写时复制的)。代码和数据被映射为hello的.text区域和.data区域。
3.映射共享区域。hello程序与共享对象libc.so链接,然后映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器。
7.8 缺页故障与缺页中断处理
在访问一级缓存时若发生页面不命中,则触发缺页中断机制。首先会检查地址是否合法,不合法则触发段错误并终止;合法则检查进程对该区域页面的读写权限,若不满足则触发保护异常并终止。若两步都没有触发异常,则选择一个一级缓存内的牺牲页,将其用需要的物理页号换入内存并更新页表。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆,他紧接在未初始化的数据区域后开始并向上生长。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留、供应用程序使用;空闲的块用来分配。
其中涉及到了两种模型:隐式空闲链表和显式空闲链表。
隐式空闲链表的分析:
数据结构:用来区别已分配块和空闲块的信息、以及块大小的信息,被存储在块内部的头部和脚部,在块中的头尾各占四字节;剩余部分被称为有效载荷,为了保持8字节对齐,有效载荷的空间大小也必须是8字节的整数倍。除此之外,在隐式链表的开头和结尾还有序言和结尾部分,其中序言部分占8字节,结尾占字节,序言部分之前还有4字节的对齐为目标的空闲区域。
使用:查找时根据头部和脚部存储的信息,可以判断当前块的大小和状态,由于是八字节对齐,表示大小的二进制数据的最后三位一定是000,而将其中的最低位设置成0或1来表示是否为空闲块,这样可以合理的利用空间。涉及优化目标,可以将000中的更多位设置成表示前后块的状态信息。
显式空闲链表的分析:
数据结构:在隐式链表的基础上,将其中的空闲块(其中必含有至少为8字节空闲有效载荷)的空闲数据区的前四字节存储链表中上一个空闲区的位置信息、第五到第八字节存储后一个空闲区的位置信息。
使用:查找时找到某一个空闲块,若它的大小不合适,可以直接根据里面的内容跳到下一个空闲块区域。
7.10本章小结
本章节区分了段式内存管理和页式内存管理,并以页式内存管理为核心,说明了多级页表配合翻译后备缓冲器进行虚拟地址到物理地址的查找过程。最终以动态内存分配的思想,重新认识了fork函数、execve函数以及异常中断处理程序。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:所有IO设备都被模型化为文件,所有的输入和输出都被当作对应文件的读和写来执行。
设备管理:设备映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix IO,使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
统一操作:
1.打开文件。一个应用程序通过要求内核打开相应的文件来宣告它想要访问一个IO设备。
2.Linux shell创建的每个进程开始时都有三个打开的文件:标准输入、标准输出、标准错误。
3.改变当前文件的位置:对于每个打开的文件,内核保持着一个文件位置k。文件位置是从文件开头起始的字节偏移量。
4.读写文件:一个读操作就是从文件复制n>0个字节到内存;写操作就是从内存复制n>0个字节到一个文件。
5.关闭文件:当应用完成了对文件的访问之后,他就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。
函数:
1.int close(int fd)
2.ssize_t read(int fd , void *buf , size_t n)
3.ssize_t write(int fd , const coid *buf , size_t n)
4.int stat(const cahr *filename , struct stat *buf)
5.int open(cahr *filename , int flags , mode_t mode)
8.3 printf的实现分析
以下为glibc-2.11.1中printf函数的源代码
#include <libioP.h>
#include <stdarg.h>
#include <stdio.h>
#undef printf
/* Write formatted output to stdout from the format string FORMAT. */
/* VARARGS1 */
int
__printf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}
#undef _IO_printf
ldbl_strong_alias (__printf, printf);
/* This is for libg++. */
ldbl_strong_alias (__printf, _IO_printf);
可知printf函数的过程:
1.调用va_start,初始化变量arg
2.调用vfprintf,使用参数列表发送格式化输出流到stdout中
3.调用va_end,允许使用了va_start宏的带有可变参数的函数返回
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户输入内容时会产生一个键盘中断请求,保存原进程的上下文并进入内核模式,内核调用键盘中断处理子程序,接受按键扫描码并转换为ASCII码,保存在系统的键盘缓冲区。getchar调用read系统函数读取键盘缓冲区中的ASCII码。接着会退出内核模式,直到下一次按键操作,重复上述过程。当用户按下回车键时,键盘中断处理子程序会在调用read函数时返回,得到整个字符串。
8.5本章小结
本章简要叙述了Linux IO设备管理的方法,介绍了常用的几种Unix IO函数以及接口,最终结合上面的内容分析了printf函数和getchar函数。
(第8章1分)
结论
1.预处理:运行C预处理器cpp,将源程序hello.c翻译成一个ASCII码格式的中间文件hello.i。
2.编译:运行C编译器ccl,将hello.i文件翻译成一个ASCII汇编语言文件hello.s。
3.汇编:运行汇编器as,将hello.s翻译成一个可重定位目标文件hello.o。
4.链接:运行链接器ld,将hello.o与一些必要的系统目标文件和其他需要链接的.o文件组合起来,生成一个可执行目标文件hello。
5.运行:shell中输入可执行文件hello的相对或绝对地址可以运行,要添加参数。
6.创建子进程:运行hello后shell会自动调用fork函数创建一个与原进程一样但相互独立的子进程。
7.运行:调用execve函数,调用启动加载器,分配虚拟内存,进入main函数。
8.执行:子进程得益于进程概念的引入,看似“独享”所有的软硬件资源。
9.访存:通过页式管理下将虚拟地址翻译为物理地址的机制进行数据访问。
10.信号:键盘键入的中断信号会导致程序进入内核模式。
11.结束:子进程运行结束时会由父进程或init进程将其回收。
快两年前我写下第一个hello world时从未想过,为了简单地执行编程语言、实现在屏幕上显示出这十个字母,计算机内部需要做出多大的努力。这个过程艰辛而繁琐。得益于深入理解计算机系统这门课程,现在我终于能对“hello world”的生命历程有一个粗浅的认识和理解,其中还有很多细节是需要不断的巩固和实验才能拥有更加透彻的理解的。简单的几行代码却对应着如此复杂的执行过程,这会提醒我在今后的编程道路上不忘“初心”,知其然且知其所以然。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c: C源程序
hello.i: 将.c文件预处理后得到的中间文件
hello.s: 将.i文件编译后得到的汇编代码文件
hello.o: 将.s文件汇编后得到的可重定位目标文件
hello: 将.o文件链接后得到的可执行文件
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] https://www.cnblogs.com/kelamoyujuzhen/p/9415010.html
[2] 《深入理解计算机系统》by Randal E. Bryant & David R. O\'Hallaron
[3] https://blog.csdn.net/qq_41683305/article/details/104142500
[4] cc1命令https://qastack.cn/unix/77779/relationship-between-cc1-and-gcc
[5] 虚拟地址空间https://blog.csdn.net/ASJBFJSB/article/details/81429576
[6] 虚拟地址空间https://blog.csdn.net/unix21/article/details/8450042
[7] 动态链接分析https://www.jianshu.com/p/5092d6d5caa3
[8] 段式内存管理https://blog.csdn.net/jinking01/article/details/107098437
[9] fork函数与虚拟地址
https://blog.csdn.net/suliangkuanjiayou/article/details/115406301
[10] printf函数的实现分析https://www.cnblogs.com/pianist/p/3315801.html
(参考文献0分,缺失 -1分)
以上是关于程序人生的主要内容,如果未能解决你的问题,请参考以下文章