HIT CSAPP 程序人生-Hello’s P2P

题      目  程序人生-Hello’s P2P 

专      业  未来技术模块          

学   号  202211XXXX            

班   级  22WL0XX                

学      生    Saltyburst          

指 导 教 师  郑贵滨                

摘  要

本篇论文以hello.c程序为例,从理论分析和实机演示两个方面入手,详细分析了计算机在生成可执行文件时的整个生命周期,包含了计算机信息的表示、程序的机器级表示、处理器体系架构、存储器层次结构、链接、异常、虚拟内存等知识,以此阐述了计算机系统整体的各级工作原理和体系结构,可以帮助读者更深入的理解掌握C语言程序的编译和执行过程

关键词:计算机系统;编译;执行;虚拟内存;链接                           

目录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2 在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1汇编初始部分

3.3.2 数据部分

3.3.3全局函数

3.3.4赋值操作

3.3.5算术操作

3.3.6关系操作

3.3.7控制转移操作

3.3.8数组操作

3.3.9函数操作

3.3.10类型转换

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.3.1 ELF头

4.3.2 节头部表

4.3.3 重定位节

4.3.4 符号表

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.3.1 ELF头

5.3.2 节头表

5.3.3 程序头表

5.3.4 段节

5.3.5 动态节

5.3.6 重定位节

5.3.7 符号表

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

P2P:即From Program to Process。即From hello.cProgramto运行时进程(Process)。在hello.c经过预处理,编译,汇编,链接四个变化过程后,便会生成可执行文件,然后就可以在shell中执行它,通过fork创建子进程并通过execve运行。

020:即From Zero-0 to Zero-0。指最初内存并无hello文件的相关内容,shell用fork和execve函数启动hello程序,把虚拟内存映射到物理内存,并从程序入口开始加载和运行。程序结束后进程终止,shell父进程回收hello子进程,不再占用内存空间。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

硬件环境:

处理器:Intel(R) Core(TM)i7-10870H   2.20 GHz

内存:16GB

软件环境:Windows11 64位,VMware17 PlayerUbuntu 22.04

开发与调试工具:Visual Studio 2022 64位;vim objump edb gcc readelf等工具

1.3 中间结果

hello.c         源代码

hello.i         预处理后得到的文本文件

hello.s         编译后得到的汇编语言文件

hello.o        汇编后得到的可重定位目标文件

hello          链接后得到的可执行文件

hello.asm      反汇编hello.o得到的反汇编文件

hello1.asm     反汇编hello可执行文件得到的反汇编文件

1.4 本章小结

本章介绍了hello的P2P和020概念,从具体流程和实现角度叙述了这两个概念。随后列举了实验所应用的硬件软件环境以及开发调试工具,以及实验中生成的各个中间文件的名称和功能。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理是在正式的编译阶段之前进行的,根据已放置在文件中的预处理指令来对源文件的内容进行简单加工的过程。

作用:

(1)宏定义指令

如 #define a b,编译时将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。

(2)条件编译指令

如#ifdef,#ifndef,#else,#elif,#endif等使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

(3) 头文件包含指令

预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。

(4)特殊符号

预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

预处理所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。

2.2 在Ubuntu下预处理的命令

预处理的命令:gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

在linux下打开预处理程序hello.i并对比观察。

因为在hello.c头部引入了头文件stdio.h、unistd.h和stdlib.h,故预处理器将他们替换为系统文件中的内容,将数十行的hello.c扩展到数千行。如生成的hello.i文件中有大量对结构的定义,对外部变量的引用,以及对引用目录的标注。

2.4 本章小结

本章讲述了预处理的概念和其作用,在linux环境下对hello.c进行了预处理,并演示了从hello.c到hello.i的过程,并简单分析了hello.i的内容。

第3章 编译

3.1 编译的概念与作用

概念:通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将预处理后的文件翻译成等价的中间代码表示或汇编代码。

作用:使高级语言源程序变为汇编语言,提高编程效率和可移植性。计算机程序编译的基本流程包括词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成等阶段。       

3.2 在Ubuntu下编译的命令

编译的命令:gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

3.3.1汇编初始部分

在main函数前有字段展示节名称:

.file               声明出源文件

.text               表示代码节

.section   .rodata    表示只读数据段

.align              声明对指令或者数据的存放地址进行对齐的方式

.string              字符串声明

.globl              声明全局变量

.type               声明符号的类型

3.3.2 数据部分

(1)字符常量

如上图,程序中存在两个字符串存在于只读数据段中,即上图LC0和LC1后对应的两个字符串,如图:

其对应内容分别为:

(2)函数参数

1.参数argc是main函数的第一个参数,被存放在寄存器%edi中,由语句

可知该地址上的数值与立即数5判断大小,argc被存放于%edi寄存器中作为参数传入。

2.hello.c中main函数中的第二个参数(char*argv[])为函数中唯一的数组,数组的每个元素都是一个指向字符类型的指针。

      由图可知,在调用printf函数前,argv[1],argv[2].argv[3]均通过以%rbp中存储的地址值为基址的相对寻址来存入到寄存器中,而查询此前部分可以发现:

,即argv的首地址通过寄存器传入,是第二个参数。

(3)局部变量

程序中的局部变量只有i,根据

可知局部变量i是被存放在寄存器%ebp上,而%rbp早已入栈

,i存放在栈上。

3.3.3全局函数

hello.c中只声明了一个全局函数int main(int arge,.char*argv[]),通过汇编代码可知

3.3.4赋值操作

hel1o.c中的赋值操作有for循环开头的i=0,该赋值操作体现在汇编代码上,则是用mov指令实现。由于int型变量i是一个32位变量,使用movl传递双字实现。

3.3.5算术操作

hello.c中的算术操作为for循环的每次循环结束后i++,该操作体现在汇编代码则使用指令add实现,同样,由变量i为32位,使用指令addl。

3.3.6关系操作

hello.c中存在两个关系操作,分别为:

  1. 条件判断语句if(argc!=5):

使用cmp指令比较立即数5和参数argc大小。如果不相等则跳转到.L6,否则则执行后面的语句。

  1. 在for循环每次循环结束要判断一次i<10:

使用cmp指令比较立即数9和局部变量i大小。如果i小于等于9则跳转回.L3,否则则执行后面的语句

3.3.7控制转移操作

设置过条件码后,通过条件码来进行控制转移,在本程序中存在两个控制转移,具体内容同上3.3.6小节。

3.3.8数组操作

        程序中需要读取argv[1],argv[2],argv[3],argv[4]的值,通过基址寄存器%rbx进行相对寻址读取其内的值。

       argv元素为地址,地址占8个字节,故步长为8。

3.3.9函数操作

Hello.c中main共计调用了printf,exit,sleep,puts,strtol,getchar这些函数。而调用函数时,参数保存在寄存器中(6个以内),超出的则保存在栈中。如hello.c中参数argcargv就分别保存在%edi%rsi中。

而在调用函数时,通过使用call内部指令调用语句进行函数调用.具体为将要调用的函数地址数据写入栈中,然后自动跳转到这个调用函数头部。

局部变量i保存在%ebp中,而%ebp已经入栈。

3.3.10类型转换

通过strtol函数,argc[4]中字符被转换为sleep函数需要的整型参数。

在调用完函数后将%eax中返回值送入%edi中作为第一个参数传递给call。

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析

3.4 本章小结

编译环节将预处理后的c语言文件编译为汇编语言。本章中通过对Ubuntu下编译后的文件进行逐条分析,展现了各种操作以及数据处理在汇编语言中的实现方案以及具体指令。


 

第4章 汇编

4.1 汇编的概念与作用

概念:汇编实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。

作用:汇编将汇编语言进行处理进一步得到CPU可以执行的机器指令(可重定位目标文件)。

4.2 在Ubuntu下汇编的命令

gcc -m64 -Og -no-pie -fno-PIC -c hello.s -o hello.o

4.3 可重定位目标elf格式

通过readelf -a hello.o,我们可以对hello.o的各节信息进行分析。

4.3.1 ELF头

      

4.3.2 节头部表

       节头部表包含了节的名称,类型,地址,偏移量等信息。

4.3.3 重定位节

重定位节中包含了.text节中需要进行重定位的信息,提供给链接器,方便链接

器的链接工作。其中,偏移量栏中标记了需要重定位的位置在.txt中的偏移量;

信息一栏中,最左侧的4位是省略前四位的高四字节,用以保存所调用的函数的符号表索引,右侧的8位为信息的低四字节,定义了重定位的方式type。

可以看到不同类型采用不同的重定位方式。在最右侧的函数符号名称后还有加数,以方便重定位过程中对被修改引用的值作偏移调整。

4.3.4 符号表

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

      

4.4 Hello.o的结果解析

       我们无法直接打开hello.o进行分析,因此通过objdump反汇编工具对反汇编文件hello.asm进行分析。

     

       相较于hello.s的83行,反汇编后的hello.asm仅有43行。

1.对比于hello.s,反汇编文件中立即数的形式为16进制,如hello.s中的32到了反汇编文件中就变成了0x20。

2.在hello.s中,分支转移时不同的跳转分成了不同的段,在跳转时通过段进行定位,而在反汇编文件中,跳转时的目标为main的偏移地址。

3.在hello.s中,对函数的调用举例为call sleep@PLT,而在反汇编文件中目标变为了下一条地址。这是因为需要调用的函数最终需要动态链接器才能确定函数的运行时的执行地址。在机器语言中,对于这些不确定地址的函数的调用时,其调用指令后的相对地址全部设置为0,等待静态链接的进一步确定。

4.5 本章小结

本章叙述了从hello.s到hello.o的过程,并通过readelf和反汇编工具objdump分别分析了hello.o的ELF格式以及操作中的地址变化。

5章 链接

5.1 链接的概念与作用

概念:将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体的过程。

作用:将多个可重定位目标文件(.o)合并以生成可执行目标文件

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工具列出hello各段的基本信息。

5.3.1 ELF头

ELF头以一个16字节的描述了生成该文件系统的字的大小和字节顺序的序列开始。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。

5.3.2 节头表

节头表详细标识了每个节的名称、类型、地址、偏移量、大小、读取权限、对齐方式等。

5.3.3 程序头表

       程序头表描述了磁盘上可执行文件的内存布局以及如何映射到内存中。

   

5.3.4 段节

段节的作用是说明hello中各个节在段中的分布

5.3.5 动态节

包含所需要的动态库,是否立即加载以及符号表等信息。

5.3.6 重定位节

       重定位节包含有偏移地址、基址信息、链接器识别修改类型、重定位目标的名称等。

5.3.7 符号表

符号表包含了hello的模块定义和引用符号的信息

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。  

在运行进程后,其起始虚拟地址为00000000:00401000,通过与5.3.2中节头表进行比较可以发现与init节虚拟地址相同。

通过symbolview工具分析并与节头表中节的地址进行比较可以发现二者虚拟地址相同。

5.5 链接的重定位过程分析

以下格式自行编排,编辑时删除

objdump -d -r hello 生成反汇编文件hello1.asm。

通过比对可以发现首先链接器将所需要用到的库函数都包含在了文件里。

其次,相对于hello.o文件,hello文件的call指令和jmp指令后的地址变为了虚拟地址。

在重定位的第一步后,链接器将hello.o中全部的相同类型的节合并为同一类型的聚合节,使得程序中的每条指令和全部变量拥有唯一运行时地址,故hello反汇编时地址不再为相对寻址。

在重定位的第二步后链接器借助汇编器生成的重定位条目修改代码节和数据节对每个符号的引用,使其直接指向调用函数的虚拟地址。

5.6 hello的执行流程

(I)开始执行:_start、_libc_start_main

(2)执行main:_main、printf、_exit、_sleep、getchar

(3)退出:exit

程序名               程序地址

_start                0x4010f0

_libc_start_main       0x2f12271d

main                0x401125

_printf               0x4010a0

_sleep               0x4010e0

_getchar             0x4010b0

_exit                0x4010d0

5.7 Hello的动态链接分析

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。延迟绑定是通过GOT和PLT实现的,因此通过对GOT和PLT进行分析以对动态链接过程进行分析。

首先由hello的elf头可知:

GOT起始位置为0x404000,在edb中寻找相应位置。

调用dl_init前后图中字段发生变化,经过动态链接指向了正确的内存地址。

5.8 本章小结

        本章从链接的基本概念和作用入手,展现了Ubuntu下链接的指令以及通过readelf观察hello文件ELF格式下的内容,并在随后展示了通过edb分析hello文件虚拟地址空间、重定位过程、执行过程和动态链接过程的方法和结果。

6章 hello进程管理

6.1 进程的概念与作用

概念:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

作用:进程为程序提供了一种程序独占的使用处理器和内存,处理器好像是无间断地一条接一条地执行我们程序中的指令的假象。

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

作用:Shell是一个交互型应用级程序,也被称为命令解析器,它为用户提供一个操作界面,接受用户输入的命令,并调度相应的应用程序。

处理流程:从终端读入输入的命令,对输入的命令进行解析,如果该命令为内置命令,则立即执行命令,否则调用fork创建一个新的子进程,在该子进程的上下文中执行指定的程序。判断该程序为前台程序还是后台程序,如果为前台程序则等待程序执行结束,若为后台程序则将其放回后台并返回。在过程中shell可以接受从键盘输入的信号(如Ctrl z,Ctrl c)并对其进行处理。

6.3 Hello的fork进程创建过程

1.用户在shell界面输入指令./hello 姓名 学号 手机号 手机号%5

2.shell判断该指令不是内置命令

3.父进程调用fork函数创建新的子进程。

4.父进程中fork返回子进程pid。

5.子进程中fork返回0。

在fork过程中,子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程与父进程最大的区别就是具有不同的PID。

6.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行一个程序。

int execve(const char *filename, const char *argv[], const char *envp[]);

execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并不返回。main函数运行时,用户栈的结构如图所示:

6.5 Hello的进程执行

进程上下文信息:指当一个进程从CPU上被移除,另一个进程开始执行时,操作系统需要保存和恢复的所有信息。这些信息包括:

程序计数器:指向当前进程正在执行的指令的地址。

寄存器:包括CPU中存储的各种信息,如通用寄存器、浮点寄存器、状态寄存器等。

内存管理信息:如进程的页表、内存映射等。

文件描述符和I/O状态:进程打开的文件及其状态信息。

审计和安全性信息:用于安全检查和审计的信息。

进程时间片:指操作系统分配给每个进程执行的时间段。在这个时间段内,进程可以运行其代码并访问系统资源。当时间片用完时,操作系统会停止该进程的执行,并将其状态保存为上下文信息,然后调度下一个进程执行。

用户模式和内核模式:处理器通常使用某个控制寄存器中的一个模式位来提供这种功能。当设置了模式位时,进程就运行在内核模式里。一个运行在内核模式的进程可以执行指令集中的所有指令且可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,也不能直接引用地址空间中内核区内的代码和数据。

当执行hello进程时,系统进行调度,为其分配时间片,当hello下的进程从磁盘内存中读取数据的时候,就会出发陷阱异常,切换到内核模式。此时系统就会重新开始一个先前被抢占了的进程,进行上下文切换。当切换回的进程运行了一段时间后,系统就会将hello由内核态切换为用户态,进行抢占执行,继续执行下一条指令。

6.6 hello的异常与信号处理

异常分为以下四类:

其处理方式分别如下图所示。

下面将结合程序运行过程中的不同操作,信号的产生和处理方式来说明hello执行过程中异常和信号的处理。

  1. 不停乱按

可以发现,乱按键盘时程序并没有受到信号,仍按照原流程工作。

  1. 回车

这里为了方便观察将睡眠时间调为1.

在结束hello进程后,shell会把之前敲击的回车当做命令行,读入终端。

  1. Ctrl-Z

进程会受到SIGSTOPPED信号,使进程停止。Hello进程被挂起。

给hello进程发送SIGKILL信号,终止hello进程。

输入fg指令后被挂起的hello进程收到SIGCONT信号,重新切回到前台继续运行。

  1. Ctrl-C

输入Ctrl-C后,进程会受到SIGINT信号,进程终止。

6.7本章小结

本章探讨了计算机系统中的进程和shell,以及异常处理的内容。以hello程序为索引,先后介绍了进程的概念和作用、shell的作用和处理流程,分析了hello程序的进程创建、启动和执行过程。最后对hello程序可能出现的异常情况逐条解析和演示。

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:由程序产生的和段相关的偏移地址部分。hello.c经过汇编生成的偏移地址为逻辑地址。比如hello中的函数,其对应的逻辑地址就由段地址和偏移地址两部分组成。

线性地址:线性地址是逻辑地址到物理地址变换之间的一步,程序hello的代码会产生逻辑地址,在分段部件中逻辑地址是段中的偏移地址,加上基地址就是线性地址。

虚拟地址:由操作系统分配给应用程序使用的地址,使得应用程序不需要知道物理地址的实际位置就可以调用。对于hello来说,main函数中的段内偏移量就是虚拟地址。

物理地址:在存储器里以字节为单位存储信息,每一个字节单元给一个唯一的存储器地址,这个地址称为物理地址,是hello的实际地址或绝对地址。

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

概念:将程序按内容或过程函数关系分成段,例如程序段、数据段…以段为单位分配内存,通过地址映射机制,将段式虚拟地址转换成实际内存物理地址。

从逻辑地址得到段号、段内地址,再根据段号和段表求出基址,再由基址+段内地址即可得物理地址。

优点:可以分别编写和编译,可以针对不同类型的段采取不同的保护,可以按段为单位进行共享,包括通过动态链接进行代码共享,每次交换的是一组相对完整的逻辑信息。

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

内存以段为单位划分,每个段又划分成若干个页。需要有一张段表管理内存分配与释放、缺段处理。同时每个段还需要一张页表把段中的虚页转换成内存中的实际页面。页表也需要有实现缺页中断处理和页面保护等功能的表项。MMU利用页表来实现从虚拟地址到物理地址的翻译。其具体过程为MMU利用虚拟页号(VPN)找到对应的物理页号(PPN),然后将找到的PPN与由虚拟页偏移量(VPO)得到物理页偏移量(PPO)组合以构成物理地址。

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

VA包含VPN与VPO,VPO与页大小有关,VPN包含TLBT和TLBI。在CPU输出VA后,虚拟地址VA传送给MMU,MMU使用TLBT和TLBI,向TLB中寻找匹配。首先根据TBLI确定哪一组,再根据TLBT确定标志位的值,从而得到PPN,若PPO等于VPO,那么将PPN与PPO结合,即可得到PA;若TLB中没有命中,则需要去页表中找,对于四级页表,VPN对应着VPN1,2,3,4,根据VPN1,2,3,4,就可以在页表4中取到PPN,与PPO结合即可得到PA。

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

Cache是一种在CPU寄存器和物理内存之间的高速存储设备,在三级Cache中分为L1,L2,L3,它们的容量依次增大。

在现在流行的CPU中,Cache的物理地址一般由三部分组成,分别是缓存标记(CT),缓存组索引(CI)以及缓存偏移(CO)。当CPU进行访问时,会依次访问L1,L2,L3,直到命中。如果三级均没命中,则会访问主存。由于在现有的计算机中,3级Cache的结构基本可以涵盖5%的内存,因而能够极大的提升CPU的运行效率。

Cache组织结构如图:

7.6 hello进程fork时的内存映射

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

7.7 hello进程execve时的内存映射

execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:

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

(2)映射私有区域。

(3)映射共享区域。

(4)设置程序计数器。

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

当CPU想要读取虚拟内存中的某个数据,而这一片数据恰好存放在主存当中时,就称为页命中。相对的,如果DRAM缓存不命中,则称之为缺页。如果CPU尝试读取一片内存而这片内存并没有缓存在主存当中时,就会触发一个缺页异常,这个异常的类型是故障。此时控制流转到内核中。

缺页异常会调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,然后用磁盘中将要读取的页来替代牺牲页。处理程序解决了这个故障,将控制流转移会原先触发缺页故障的指令并重新执行该指令。

7.9本章小结

       本章介绍了hello的存储器地址空间,段式管理和页式管理,并逐步介绍了虚拟地址VA到物理地址PA的转换过程,随后对hello进程执行时fork的内存映射,execve的内存映射,缺页故障与缺页中断处理进行了讲解。

结论

用计算机系统的语言,逐条总结hello所经历的过程。

hello所经历的过程:

由hello代码从键盘输入开始,依次要经过以下步骤:

1、预处理(cpp)。将hello.c进行预处理,将文件调用的所有外部库文件合并展开,生成一个经过修改的hello.i文件。

2、编译(ccl)。将hello.i文件翻译成为一个包含汇编语言的文件hello.s。

3、汇编(as)。将hello.s翻译成为一个可重定位目标文件hello.o。

4、链接(ld)。将hello.o文件和可重定位目标文件和动态链接库链接起来,生成一个可执行目标文件hello。

5、运行。在shell中输入./hello 姓名 学号 手机号码 睡眠时间(手机号码%5)。

6、创建进程。终端判断输入的指令不是shell内置指令,调用fork函数创建新的子进程。

7、加载程序。shell调用execve函数,启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。

8、执行指令:CPU为进程分配时间片,在一个时间片中,hello顺序执行自己的控制逻辑流。

9、访问内存:MMU将虚拟内存地址通过页表映射成物理地址。

10、信号管理:当程序在运行时进程处理相应信号。

11、终止:当子进程执行完成时,内核安排父进程回收子进程程。内核删除这个进程创建的所有数据结构。

计算机系统的体系如同精巧的艺术品,在学习的时候我不禁感慨这无数的体系结构设计之精妙复杂,每个小小的操作都要调动各种操作来完成,各个部件之间和谐统一,而这个精妙的整体正是几十年来人类智慧的结晶。我不禁为先辈们的精巧构思折服。

附件

文件名

描述

           hello.c

源代码

hello.i

预处理后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

汇编后得到的可重定位目标文件

hello

连接后得到的可执行文件

hello.asm

反汇编hello.o得到的反汇编文件

hello1.asm

反汇编hello可执行文件得到的反汇编文件

详细附件文件见大作业同目录下文件夹。

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1]    Randal E.Bryant David R.O'Hallaron.深入理解计算机系统(第三版).机械工业出版社,2016.

[2]      ELF格式解读 Dynamic节

https://blog.csdn.net/qfanmingyiq/article/details/124527430

[3]    操作系统的内存管理——页式、段式管理、段页式管理

           https://blog.csdn.net/weixin_46199479/article/details/123438544

[4]    预处理、编译、汇编和链接

            https://blog.csdn.net/Zhubingge/article/details/105202116

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值