哈工大计算机系统大作业2022

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业          计算学部        

学     号         120L020401       

班   级          2003011         

学       生          王翔宇          

指 导 教 师          郑贵滨           

计算机科学与技术学院

2021年5月

摘  要

本文通过展开hello程序执行的全过程,复习了计算机系统的重点内容。从预处理、编译、汇编、链接的细节,到基于x86-64的进程、存储、IO管理的相关机制,将所学知识串联成线,凝结成网,从而对程序在Linux系统上执行形成由粗到细、由显入微的系统认识。

关键词:预处理;编译;汇编;链接;进程;内存管理;I/O管理;计算机系统;x86-64  (摘要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简介

1.1.1 Hello的P2P(from program to process):

hello的源代码,先经过预处理、编译、汇编、链接得到可执行文件,再由shell创建进程并加载成为一个运行的进程。

1.1.2 Hello的020(from zero to zero):

shell为hello进程提供了虚拟地址空间等进程上下文。hello开始运行。最终,hello或受到异常信号、或正常终止,由父进程回收并释放资源,hello不复存在。

1.2 环境与工具

硬件环境: Intel Core i7 10510U,16GB内存。

系统环境: Ubuntu 20.04.4

工具: gcc、gedit、edb、readelf、objdump

1.3 中间结果

①hello.c是源代码。

②hello.i是预处理后的代码。

③hello.s是编译后的代码。

④hello.o是汇编后的代码。

⑤o_elf.txt是hello.o的可重定位目标文件。

⑥hello是链接后的代码。

⑦o_dump.txt是hello.o的反汇编结果。

⑧out_elf.txt是hello的可执行目标文件。

1.4 本章小结

主要介绍了hello程序P2P,020的概念及过程。列出了硬件环境、软件环境和开发工具,最后给出实验过程中的中间结果。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

2.1.1预处理的概念

程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——(用C/C++的术语来说是)预处理记号(preprocessing token)用来支持语言特性(如C/C++的宏调用)。

预处理中会展开以#起始的行,试图解释为预处理指令(preprocessing directive) ,其中 ISO C/C++要求支持的包括#if、 #ifdef、 #ifndef、 #else、 #elif、 #endif(条件编译)、 #define(宏定义)、 #include(源文件包含)、 #line(行控制)、 #error(错误指令)、 #pragma(和实现相关的杂注)以及单独的#(空指令)。预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。

2.1.2预处理的作用

改进程序设计环境,提高编程效率,使源代码在不同的执行环境中被方便的修改或者编译。本实验中预处理的结果得到了hello.i。

2.2在Ubuntu下预处理的命令

在终端内输入命令gcc -E hello.c,在屏幕上得到hello.c的预处理结果(如图)。为方便起见我们重定向gcc的输出,将结果保存到hello.i文件内。

终端输入:gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i

用文本编辑器查看hello.i内容:

2.3 Hello的预处理结果解析

hello.i相较于hello.c行数有明显增加,信息更丰富:有头文件的信息、typedef定义的诸多类型别名、枚举类型、标准C的函数原型、标准输入输出和错误,最后是hello.c的代码。

2.4 本章小结

主要是预处理的相关内容,完成对源代码进行一些的展开操作,插入include内容、宏展开等,为编译做准备。预处理后的结果仍然是文本文件,可以被文本编辑器打开、被人读懂。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

3.1.1编译的概念:

编译就是把高级语言变成计算机可以识别的2进制语言, 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分 析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。本实验 中将文本文件 hello.i 翻译成文本文件 hello.s。

3.1.2编译的作用:

编译器将原始的C语言语法格式的代码转换为面向CPU的机器指令,转换后的结果以汇编语言的文本形式保存,编译结果直接决定程序的机器级表示。

3.2 在Ubuntu下编译的命令

截图,展示编译过程!

终端输入:gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s

用文本编辑器查看hello.s内容:

3.3 Hello的编译结果解析

3.3.1 数据:常量与变量

编译后的hello代码中常数为立即数,字符串存放在.rodata节

hello的局部变量包括循环变量i、传入main的参数argc、argv,都存放在栈中

3.3.2 分支、循环

条件判断if(argc!=4),编译后通过cmp判断argc,如果是4,条件转移指令je会直接跳转到.L2位置,跳过中间的语句块;否则je不会跳转,继续执行中间的语句块。

for循环,编译后先将i赋0,然后跳转判断是否i<=7,初始显然满足则跳转到中间的循环体,执行完循环体后i++。继续判断是否i<=7,依此循环进行,直到不满足跳出循环。

3.3.3 数组操作

对于传入main的数组argv,argv[1]和argv[2]分别表示两个字符串,通过编译后的代码可以看到:每访问一个数组元素需要mov,add,mov语句的配合,通过M[%rbp-32+16]访问argv[2],通过M[%rbp-32+8]访问argv[1]。

3.3.4 函数操作参数传递、函数调用、函数返回

x86-64中的传参规律是,当参数在6个以内时,由rdi,rsi,rdx,rcx,r8,r9寄存器传递,多余参数按照参数列表顺序从右向左由栈传递。

hello中的main函数被系统启动函数调用,条件判断满足时后调用printf、exit,每次调用前保存返回地址。

函数返回前恢复堆栈状态,将返回值保存在rax中并通过ret返回。

3.4 本章小结

主要介绍了编译的概念以及过程。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。编译后的代码相比预处理后的代码有明显减少,为汇编成为真正的机器代码做准备。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

4.1.1汇编的概念

汇编器将汇编语言直接翻译成CPU能够直接执行的机器码形式,即为将汇编语言(hello.s)翻译成机器语言(hello.o)的过程。

4.1.2汇编的作用

汇编后hello程序将由文本形式转入二进制形式,具体对应机器指令,是可重定位目标文件。

4.2 在Ubuntu下汇编的命令

在终端里输入:gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o

文本编辑器已经无法正常打开此文件

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

在终端里输入:readelf -a hello.o > o_elf.txt

ELF头:

包含系统信息,编码方式,ELF头大小,节的大小和数量等信息。

头目表:

描述了各个节的类型、位置、所占空间大小等信息。

重定位节:

表述了各个段引用的外部符号等,链接通过重定位节对这些位置的地址进行 修改。

符号表:

存放在程序中定义和引用的函数和全局变量的信息。

4.4 Hello.o的结果解析

在终端中输入:objdump -d -r hello.o > o_dump.txt

文本编辑器查看如下:

4.4.1机器语言的构成

汇编语言指令是机器指令的一种符号表示,不同类型的CPU 有不同的机器指令系统,也就有不同的汇编语言。

4.4.2与汇编语言的映射关系

汇编语言指令是机器指令的一种符号表示,不同类型的CPU 有不同的机器指令系统,也就有不同的汇编语言。

4.4.3比较机器语言中与汇编语言

1)数的表示:hello.s中的操作数是十进制,hello.o反汇编代码中的操作数是十六进制。

2)全局变量的引用:hello.s中被置零,hello.o反汇编代码是重定位地址

3)分支转移:跳转语句之后,hello.s中是.L2和.LC1等段名称,而hello.o反汇编代码中跳转指令之后是相对偏移的地址,也即间接地址。

4)函数调用:hello.s中,call指令使用的是函数名称,而hello.o反汇编代码中call指令使用的是main函数的相对偏移地址。

4.5 本章小结

主要介绍汇编的相关内容。汇编语言转化为可重定位的机器码,为链接做准备。

对比hello.s和hello.o反汇编代码的区别,对可重定位目标elf格式进行了详细的分析。

(第4章1分)


5链接

5.1 链接的概念与作用

5.1.1链接概念:

链接是将各种不同文件的代码和数据部分收集(符号解析和重定位)起来并组合成一个单一文件的过程。

5.1.2链接作用:

合并未编入的函数,生成可执行文件。链接可以执行于编译时,也可以执行于加载时,或者执行于运行时。可以分离编译同一个程序分割为若干独立模块,为其编写不同的源代码,分别独自编译为目标文件或库,最终将其链接起来。

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

将hello.o与其它的必要静态库链接,得到可执行文件hello。

5.3 可执行目标文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

继续用gedit查看ELF格式各段的基本信息。

ELF文件头:

节头:

描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。

程序头:

5.4 hello的虚拟地址空间

使用edb加载hello,process properties——Memory查看本进程的虚拟地址空间各段信息:

前4个段来自hello可执行文件本身,是hello主要的代码与数据,对应readelf查看的hello的程序头的4个LOAD段。

接下来的的4个段来自hello加载的动态链接库,剩下的若干个段是栈段和内核相关段。

5.5 链接的重定位过程分析

终端输入:objdump -d -r hello  >  o_dump.txt,到可执行文件hello的反汇编结果。

重定位分析:链接器先将所有模块的节组织起来,再根据虚拟地址空间将hello.o中.rel.text和.rel.data节的重定位条目指向位置的操作数填充正确的地址。

从hello.o到hello的反汇编主要有如下四方面变化:

1.增加新函数:

在hello中链接加入了在hello.c中用到的库函数,如exit、printf、sleep、getchar等函数。

2.增加节:

hello中增加了.init和.plt节,和一些节中定义的函数。

3.函数调用:

hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。

4.地址访问:

hello.o中的相对偏移地址变成了hello中的虚拟内存地址。而hello.o文件中对于某些地址的定位是不明确的,其地址也是在运行时确定的,因此访问也需要重定位,在汇编成机器语言时,将操作数全部置为0,并且添加重定位条目。

5.6 hello的执行流程

run之前,用edb的analyze功能,可以看到动态链接库模块内部依次出现的函数:

run之后,用edb的analyze功能,可以看到hello模块内部依次出现的函数:

5.7 Hello的动态链接分析

在hello的可执行目标文件中找到关于PLT和GOT的两个节:

在程序一开始,先执行_dl_start和_dl_init,_dl_init能够修改PLT和GOT我们使用edb来验证这一过程,监测0x404000地址处PLT的数据变化。

调用_dl_init之前:

这是调用_dl_init后的,可以发现PLT表变为:

说明:plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。plt就能跳转到正确的区域。Hello程序在加载可执行文件时,自动加载了动态链接库ld-2.31.so。引用动态链接库里的符号通过PLT表和GOT表实现的,每一个条目都对应动态链接库中的符号引用。

5.8 本章小结

主要介绍链接的概念与作用、链接命令,查看了hello的可执行目标文件格式、虚拟地址空间,同时分析链接的重定位过程。以此为基础更好地理解hello的执行流程与动态链接。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

6.1.1 进程的概念

进程是程序运行的实例。一个程序可以有多个进程。

6.1.2 进程的作用

Shell通过fork多个子进程可以同时运行多个可执行程序。

6.2 简述壳Shell-bash的作用与处理流程

6.2.1 Shell-bash的作用

是实现操作系统与用户的交互的界面,shell本身作为命令解释器将用户输入的指令解析并执行。Shell还负责信号的发送管理,例如转发键盘输入的信号,当子进程终止时向父进程发送sigchld信号等。

6.2.2 Shell-bash的处理流程

1.解析用户指令

2.如果是内置命令(例如fg,bg,quit等),立即执行

3.如果是可执行程序,在子进程中执行

6.3 Hello的fork进程创建过程

用户输入./hello命令,shell就要运行这个作业,调用fork创建子进程,这个子进程具有与父进程shell不同的pid,除此之外几乎完全相同。调用setpgpid(0,0)为该作业新建进程组并将其作为组长,从而与shell进程组本身分离开来。

6.4 Hello的execve过程

删除原来的用户区域。然后加载hello的用户区域,通过虚拟内存机制,映射私有区域,将各个段映射到对应的代码段、数据段等地址空间;映射共享区域,加载共享库。最后,修改程序计数器PC指向代码入口点。

6.5 Hello的进程执行

上下文是由程序正确运行的状态组成的,这些状态包括存放在内存里的程序的代码、数据、栈、寄存器、所占用的资源等。

上下文切换时,先保存当前进程的上下文,再恢复某个先前被抢占的进程被保存的上下文,最后将控制传递给这个新恢复的进程。

时间片是多进程并发执行时,一个进程能够执行它的控制流的连续时间段。

进程调度是指运行在CPU的进程每隔一段时间就需要切换至其它进程。

用户态与核心态转换是指若运行当前进程时发生异常,要从用户态转入到内核态。内核调用异常处理机制,然后回到用户态。

例如:hello调用sleep函数,这时sleep通过syscall引发陷阱异常,由用户态转入内核态。内核保存hello的上下文,然后挂起hello进程,切换到其它进程,由内核态转入用户态。等到计时结束,时钟中断使得由用户态转入内核态,恢复hello的上下文,hello进程继续执行,由内核态转入用户态。

6.6 hello的异常与信号处理

hello执行过程中异常主要有:中断、终止,相关的信号有:sigint、sigtstp、sigchld等。讨论以下几类情景:

6.6.1 不停乱按字符(含回车)

一般不影响程序运行,屏幕上显示键入的字符。

6.6.2 Ctrl-C

用户按下Ctrl-C时,发生键盘中断异常,由用户模式转为内核模式。内核给shell发送sigint信号,shell通过自己的sigint_handler响应sigint信号,不终止自身而是将sigint信号转发给前台hello作业(进程(组)),hello作业对sigint信号默认处理方式是终止自身。

    

6.6.3 Ctrl-Z

用户按下Ctrl-Z时,发生键盘中断异常,由用户模式转为内核模式。内核给shell发送sigtstp信号,shell通过自己的sigtstp_handler响应sigint信号,不挂起自身而是将sigtstp信号转发给前台hello作业(进程(组)),hello作业对sigtstp信号默认处理方式是挂起自身,shell输出命令提示符可以继续接受用户输入。

6.6.4 Ctrl-z后运行ps  jobs  pstree  fg  kill 等命令

运行ps命令,shell将列出所有进程组

运行jobs命令,shell将列出所有作业,可以看到hello作业处于挂起状态

运行pstree命令,shell将列出所有进程组

运行fg命令,shell将后台hello作业搬到前台运行,具体实现shell是向hello作业发送sigcont信号,hello默认处理方式是修改作业状态为fg。

运行kill命令,shell将杀死后台的hello作业,具体实现shell是向hello作业发送sigkill信号,hello作业默认处理方式是杀死自身。

6.7本章小结

主要介绍了hello的进程管理有关的内容,先介绍进程和shell的概念和作用,然后说明hello如何被fork与execve执行,最后,结合hello的运行展示异常与信号的处理结果。

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

1.逻辑地址由程序产生的段内偏移地址,以“段:偏移地址”形式给出

2.线性地址是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,它加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址。

3.虚拟地址:是由程序产生的由段选择符和段内偏移地址组成的地址。不能直接访问物理内存,而是要通过分段地址的变化处理后才会对应到相应的物理内存地址。

4.物理地址是真正的内存地址,决定了数据在内存中的存储位置,是地址变换的最终结果地址。CPU可以直接将物理地址传送到与内存相连的地址信号线上,对实际存在内存中的数据进行访问。如果启用了分页机制,那么hello的线性地址会使用页目录和页表中的项变换成hello的物理地址;如果没有启用分页机制,那么hello的线性地址就直接成为物理地址了。

简单讲,在Linux中,可以认为 逻辑地址=虚拟地址=线性地址。逻辑(虚拟)地址经过分段(查询段表)转化为线性地址。线性地址经过分页(查询页表)转为物理地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

逻辑地址以“段:偏移地址”形式给出的,CPU将会通过其中的16位段选择子定位到GDT/LDT中的段描述符,得到段的基址(base),与段内偏移(offset)地址相加得到的64位整数就是线性地址。这就是CPU的段式管理机制,其中,段的划分,也就是GDT和LDT都是由操作系统内核控制的。

细节1:段选择符,前13位是一个索引号,可在段描述符表中找到段描述符,后3位包含一些硬件细节。

细节2:段选择的T1:若为0,要转换的是GDT中的段;若为1,要转换的是LDT中的段。全局的段描述符,放在“全局段描述符表(GDT)”中,GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中。一些局部的段描述符,放在“局部段描述符表(LDT)”中,LDT则在ldtr寄存器中。

7.3 Hello的线性地址到物理地址的变换-页式管理

线性地址被分为2部分,内存管理单元(MMU)取得线性地址前部分(虚拟页号)作为索引去内存中的页表里查询对应表项,得到物理页号(物理页起始地址)。再将物理页号与线性地址后部分(页中偏移)拼接到得到物理地址,CPU就可以通过这个物理地址访问到内存。

7.4 TLB与四级页表支持下的VA到PA的变换

为了节省页表的空间,CPU一般采用多级页表的机制。上一级页表中的条目指向下一级页表。(将虚拟地址的VPN划分为相等大小的不同的部分。每个部分用于寻找由上一级确定的页表基址对应的页表条目)

具体在x86-64模式下,CPU采用四级页表,线性地址被按位划分为5部分,前4个部分分别作为该级页表的索引,最低的12位作为页的偏移地址。CPU会逐级地查找到对应物理页的PTE,从而得到物理地址。

细节实现:每次CPU产生一个虚拟地址,由MMU(内存管理单元)查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。为了优化对于页表的查找效率,CPU在MMU中有一个专门用于页表的小缓存,称为翻译后备缓存器(TLB)。这样CPU可以将页表的PTE缓存到TLB中,从而减少对内存的访问。

7.5 三级Cache支持下的物理内存访问

CPU发送一条虚拟地址,通过后MMU获得物理地址。首先去看L1 Cache里是否有对应内存块,若有则直接访问L1 Cache,否则查看下一级Cache。若三级Cache都不命中,就需要访问物理内存加载到Cache中。如果cache已满就要使用某种策略替换掉Cache中的某个内存块。

7.6 hello进程fork时的内存映射

当调用fork创建子进程时,内核为新的子进程创建各种数据结构,并分配给子进程一个唯一的PID,为了给它创建虚拟内存空间,内核创建了当前进程的mm_struct、区域结构和页表的原样副本,将两个进程的页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。这当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。

7.7 hello进程execve时的内存映射

execve加载并运行可执行文件hello:

  1. 删除已存在的用户区域。

将当前进程虚拟内存空间中的用户部分已存在的区域结构删除。

  1. 映射私有区域

为新程序hello的代码、数据、bss段和栈段区域创建新的区域结构,这些新的区域都是私有且写时复制的。代码和数据区域被映射为hello可执行文件中的.text和.data节,bss区域请求二进制0,映射到匿名文件,栈和堆被初始化为空。

  1. 映射共享区域

将hello链接的动态链接库(共享对象)映射到虚拟地址空间的共享区域内。

4)设置程序计数器(PC)

最后设置当前进程的上下文中的程序计数器指向代码区域的入口点。而下一次调度这个进程时,他将从这个入口点开始执行。

7.8 缺页故障与缺页中断处理

7.8.1缺页故障

当CPU执行某条指令的内存访问时,如果页表中的PTE表明这个地址对应的页不在物理内存中,那么就会引发缺页故障,

7.8.2缺页中断处理

处理缺页需要硬件和操作系统内核协作完成。执行缺页中断处理程序,将存在磁盘上的页使用一定的替换策略加载到物理内存,并更新页表。缺页故障处理完毕后,CPU重新执行该条指令。

缺页中断处理流程:

1)处理器生成一个虚拟地址,并将它传送给MMU

2)MMU生成PTE地址,并从高速缓存/主存请求得到它

3)高速缓存/主存向MMU返回PTE

4)PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。

5)缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。

6)缺页处理程序页面调入新的页面,并更新内存中的PTE

7)缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。

7.9动态存储分配管理

动态储存分配管理使用动态内存分配器来进行。动态内存分配器维护进程虚拟地址空间中的的区域,它将堆视作一组不同大小的块的集合来维护,每个块是一段连续的虚拟内存碎片,要么是已分配的,要么是空闲的。空闲块保持空闲直至被应用程序分配,以已分配块保持已分配状态直至被释放。这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

7.10本章小结

主要介绍了 hello 的存储器地址空间的四个概念、段式管理与页式管理,四级页表 VA 到 PA 的变换、三级cache物理内存访问, hello 进程 fork与execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。展现hello内存访问背后的基于页式管理的虚拟内存机制、故障处理机制。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

Linux中所有的IO设备(网络、磁盘、终端等)、甚至内核都被模型化为文件。这使得所有的输入和输出都能以一种统一且一致的方式来执行,称为Unix I/O接口。

8.2 简述Unix IO接口及其函数

8.2.1打开文件open:

调用open函数来打开一个已存在的文件或创建一个新文件。内核返回这个文件的描述符(非负整数)以标识这个文件。Shell创建的每个进程开始时都有3个打开的文件:标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。

8.2.2.改变当前的文件位置lseek:

通过执行seek操作,设置文件的当前位置为k。

8.2.3.读写文件read/write:

调用read和write函数来执行输入输出。读操作就是从当前位置k开始,从文件复制n个字节到内存,然后将k增加到k+n,当k超出文件长度时应用程序能够通过EOF检测到。而写操作则是从内存复制n个字节到一个文件,从当前文件位置k开始,然后更新k

8.2.4.关闭文件close:

调用close函数关闭一个打开的文件,参数为open返回的文件描述符。内核释放文件打开时创建的数据结构和内存资源,将描述符恢复到描述符池中。

8.3 printf的实现分析

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

总结来看,printf的实现过程是:开辟一块缓冲区,用vsprintf在缓冲区生成要输出的字符串。然后通过write将这个字符串输出到屏幕上。

8.4 getchar的实现分析

总结来看,getchar的实现过程是:开辟一块静态缓冲区,调用read向缓冲区中读入字符串。通过syscall陷阱跳到内核,等待用户键入后,执行键盘中断处理程序,将从键盘读入的扫描码转换成字符、不断放入缓冲区,直到遇到回车为止,回车字符也会放在缓冲区中。之后,每次读取缓冲区的一个字符。若缓冲区已空还需要读取字符,就等待用户输入。

8.5本章小结

介绍了 Linux 的 I/O 设备的基本概念和管理方法,以及Unix I/O 接口及其函数,具体分析了printf 函数和 getchar 函数的实现过程,从而理解hello在屏幕上打印出信息、接受键盘输入的过程。

(第8章1分)

结论

Hello的第一个跨越是从源代码到可执行程序。

先是经历了预编译,得到hello.i文本文件,再是经过编译,得到汇编代码hello.s汇编文件,然后经过汇编,得到二进制可重定位目标文件hello.o,最后经过链接,生成了可执行文件hello,这一系列跨越要感谢gcc和链接器的慷慨帮助。

Hello的第二个跨越是从可执行程序到成功运行。

借助shell,先是调用fork函数,生成子进程;并由execve函数加载运行当前进程的上下文中加载并运行新程序hello,这一过程完成了复杂的内存映射,并得到最终的物理地址。同时,借助Unix IO接口,hello成功完成输入输出。当然,还会有许多异常的发生。在异常处理机制的帮助下,hello进程最终完成使命,被父进程回收,宣告了hello一生的结束。

就这样,小小的hello完成了P2P,020的过程,写就了自己精彩的一生。

(结论0分,缺失 -1分,根据内容酌情加分)


附件

①hello.c是源代码。

②hello.i是预处理后的代码。

③hello.s是编译后的代码。

④hello.o是汇编后的代码。

⑤o_elf.txt是hello.o的可重定位目标文件。

⑥hello是链接后的代码。

⑦o_dump.txt是hello.o的反汇编结果。

⑧out_elf.txt是hello的可执行目标文件。

(附件0分,缺失 -1分)


参考文献

[1]  《深入理解计算机系统》 Randal E.Bryant David R.O’Hallaron 机械工业出版社

[2]  预处理_百度百科 (baidu.com)

[3]  编译_百度百科 (baidu.com)

[4]  深入理解计算机系统(十三):汇编语言和机器语言 - 知乎 (zhihu.com)

[5]  (87条消息) 虚拟地址、逻辑地址、线性地址、物理地址的区别_种向日葵的小仙女的博客-CSDN博客_逻辑地址和虚拟地址

[6] https://www.cnblogs.com/pianist/p/3315801.html

(参考文献0分,缺失 -1分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值