HITcsapp大作业 程序人生

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业     人工智能          

学     号      2021112700        

班     级      21WL024         

学       生       毛彬丞       

指 导 教 师        郑贵滨         

计算机科学与技术学院

2023年5月

摘  要

本文以一个简单的hello.c程序为例,在Linux操作系统下逐步拆解其生命周期,解释hello从程序到进程(P2P)的整个过程,包括预处理、编译、汇编、链接、进程、虚拟内存、逻辑内存等,结合CSAPP知识进行详细阐述,利用Linux命令行实现全过程可视化展示,加深对计算机系统工作原理的理解。

关键词:P2P;预处理;编译;汇编;链接;进程;内存。                           

目  录

第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章 概述

(0.5分)

1.1 Hello简介

P2P,From Program to Process,指的是以某一用C语言编写的.c文件为原程序,采用一系列处理办法得到可执行文件的过程,包括预处理(.c->.i),编译(.i->.s),汇编(.s->.o),链接(.o->可执行)等步骤,最终得到的是一个hello二进制可执行文件。执行该文件时,OS(Operation System)在shell中利用fork新建一个子进程来执行该文件。

020,From Zero to Zero,指的是shell中程序被映射到虚拟内存,执行,开始进程后被载入物理内存,执行完毕后,内核通过信号管理相关子进程,将虚拟内存恢复到程序执行前的状态。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

软件环境:Windows10 64位;VMware Workstation Pro15.5.1;Ubuntu 20.04.4

开发和调试工具:gdb;edb;readelf;objdump;Code::Blocks

1.3 中间结果

文件名称

作用

Hello.c

源代码,文本文件

Hello.i

预处理得到,有一定的代码优化

Hello.s

编译得到,汇编语言的文本文件

Hello,o

汇编得到,可重定位

Hello.elf

ELF格式,用于分析hello.o

Hello_objdump,txt

Hello.o的反汇编文件,用于分析

Hello

链接得到,可执行文件

1.4 本章小结

本章根据hello的自白,概括介绍了helloP2PO2O的过程。此外,还介绍了本实验用到的硬软件环境和开发调试工具。

第2章 预处理

(0.5分)

2.1 预处理的概念与作用

概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——预处理记号用来支持语言特性。

对C语言而言,指预处理器cpp将以字符#开头的命令,如宏定义、条件编译、头文件等展开,将展开内容插入到原程序中,形成一个.i文本文件。

作用

(1)将所有宏定义展开,进行字符替换

(2)将注释删除

(3)将所有引用头文件插入程序

(4)添加行号和文件标识

(5)处理条件编译指令

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

生成hello.i文件

编译器分别对#include<stdlib.h>#include<stdio.h>#include<unistd.h>进行了相应处理,对于原文件的宏进行了宏展开,引入头文件内容,同时注释也被删除了。代码扩充为3055行,最后一部分与hello.c的main函数中内容完全相同。具体如下图所示。

Hello.i部分截图

2.4 本章小结

本章首先介绍了预处理的概念与作用,接着以hello.c为例,演示了在Ubuntu下如何预处理程序,并对结果进行分析。

第3章 编译

2分)

3.1 编译的概念与作用

概念:编译,指利用编译程序从源语言编写的源程序产生目标程序的过程。

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

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1 对文件信息的记录

注意到hello.s文件中有许多代码以“.”开头,称这些代码为伪指令。其中.file表明了源文件,.text代码段,.section .radata只读代码段,.align对齐方式为8字节对齐,.string字符串,.global全局变量,.type声明main是函数类型。

3.3.2对局部变量的操作

局部变量存储在栈中,当进入函数main的时候,会根据局部变量的需求,在栈上申请一段空间供局部变量使用。当局部变量的生命周期结束后,会在栈上释放。

i是局部变量,在hello.s中可以看到,首先跳转到了.L3的位置,然后将栈指针减少4,即存储局部变量i,然后跳转到.L4进行接下来的操作

3.3.3对字符串常量的操作

main函数前,在.rodata处的.LC0.LC1已经存储了字符串常量,标记该位置是代码是只读的。在main函数中使用字符串时,得到字符串的首地址。

3.3.4对立即数的操作

在数字前加$表示立即数

以上三个均为加法操作。

以上为减法操作

以上为赋值操作

3.3.5参数传递

main函数的开始部分,因为后面还会使用到%rbp数组,所以先将%rbp压栈保存起来。

此处将栈指针减少32位,然后分别将%rdi%rsi的值存入栈中。
由此我们知道,%rbp-20%rbp-32的位置分别存了argv数组和argc的值。

3.3.6数组的操作

对数组的操作,都是先找到数组的首地址,然后加上偏移量即可。例如在main中,调用了argv[1]和argv[2],在汇编代码中,每次将%rbp-32的的值即数组首地址传%rax,然后将%rax分别加上偏移量16和8,得到了argv[1]和argv[2],在分别存入对应的寄存器%rsi和%rdx作为第二个参数和第三个参数,之后调用printf函数时使用。

调用完printf后,同样在偏移量为24时,取得argv[3]并存入%rdi作为第一个参数在调用函数atoi使用。

3.3.7函数的调用和返回

函数的前六个参数有寄存器传参,返回值存在%rax寄存器中。在函数调用时,先将相应的值存入相应的寄存器,然后使用call指令调用函数和ret指令返回函数。由于函数是公用一套寄存器的,在调用一个函数之前,要先将当前函数的一些值保存起来,调用完再恢复。

对printf函数的调用,再3.3.6中已经介绍过,取得argv数组的第二个和第三个元素放入寄存器%rsi和%rdx,然后取得了字符串的地址,并存入了2%rdi中作为第一个参数,这样三个参数都准备好后,用call指令调用了printf函数。

atoi函数和sleep函数的调用,先取得argv存入%rdi作为第一个参数,然后第48call指令调用了atoi函数,接着atoi的返回值存入了%rax中,再将其存入%rdi中作为sleep的第一个参数,然后用call调用sleep函数。

3.3.8 for 循环

对于for循环,将循环变量存入一个寄存器中,然后当执行完一个循环体之后,更新循环变量(一般是用add指令进行自增),然后用cmp指令将其与条件进行比较,满足则继续,否则退出循环。

3.4 本章小结

本章以一个简单的程序hello.s为例,解释了编译器处理C语言各种操作的方式, 宏观上表现为编译器将.i程序编译为.s的汇编程序。编译将原本的C语言程序处理为更低级的汇编语言,为后面处理为可执行程序做了一步准备。

第4章 汇编

2分)

4.1 汇编的概念与作用

概念:把汇编语言翻译成机器语言的过程称为汇编。

作用:使程序向机器级程序更进一步,变为机器可以理解的二进制程序,使计算机可以执行该程序。

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

将hello.o转换为hello.elf文件

4.3.1 ELF头

ELF头以16字节的序列开始,描述了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部分包含帮助两届其语法分析和解释目标文件的信息,包括ELF头的大小、目标文件的类型(如可执行、可重定位或者共享的)、机器类型、节头部表的文件偏移以及节头部表中条目的大小和数量。

4.3.2节头

节头部表非常简明地给出了各节的相关信息,包括名称、类型、地址、偏移量、大小、全体大小、旗标、连接、信息、对齐。

4.3.3重定位节

重定位节包含了.text 节中需要进行重定位的信息,为链接器操作文件时修改提供方便。

偏移量,指需要被修改的引用节的偏移。

信息,包括symbol和type两个部分,前四个字节为symbol,后四个字节为type。

Symbol,标识被修改引用应该指向的符号。

Type,重定位的类型。

类型,给链接器提供修改文件的标准。

符号值,一个有符号常数,这里均为0。

符号名称:重定向到的目标的名称。

4.3.4符号表

符号表中存放了程序中定义和引用的函数和全局变量的信息,其中声明了重定位需要的所有符号。

4.4 Hello.o的结果解析

生成反汇编文件

对比hello_objdump.txt和hello.s,可以看出除格式不同外,二者没有十分显著的差别。反汇编代码除汇编代码外,同时提供了汇编代码对应的机器代码即二进制代码。二者不同之处主要体现在以下几点:

一、在反汇编的代码中,操作数均以十六进制表示,而hello.s中以十进制表示。

二、在反汇编的代码中,一些指令不带有表示操作数大小的后缀,而hello.s中均有具体写明。

三、在反汇编的代码中,调用函数采用直接计算首地址的表达方式,而hello.s中采用直接调用函数名的方式。

四、在反汇编的代码中,没有伪指令,而hello.s中含有大量伪指令,以指导编译器进行后续操作。

五、在反汇编的代码中,跳转通过直接给出指令地址的方式表达,而在hello.s中,跳转采取的是Label表达。

4.5 本章小结

本章首先介绍了汇编的概念和作用,接着通过实操,对hello.s文件进行汇编,生成ELF可重定位目标文件hello.o,接着使用readelf工具,通过设置不同参数,查看了hello.o的ELF头、节头表、可重定位信息和符号表等,通过分析理解可重定位目标文件的内容。最后将其与hello.s比较,分析不同,并说明机器语言与汇编语言的一一对应关系

5章 链接

1分)

5.1 链接的概念与作用

概念:链接是将各种代码和数据片段收集并组合称为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。

作用:链接使代码更加简洁,具有鲜明的模块化特征。链接允许我们将代码封装为各个模块。在编程时,在已有模块的前提下,我们直接利用这些模块的函数原型调用它们,这对主函数的编写提供了巨大的便利。

作为编译的最后一步,链接操作根据原汇编文件的信息,向相应的可执行文件中插入对应模块下的具体代码。

5.2 在Ubuntu下链接的命令

ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello

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

生成hello程序的ELF文件

5.3.1 ELF头

hello.elf一样,ELF头以一个16字节的序列Magic开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。此外,ELF头还给出了帮助链接器语法分析和解释目标文件的信息。

5.3.2节头

同hello.elf相同,节头部表非常简明地给出了各节的相关信息,包括名称、类型、地址、偏移量、大小、全体大小、旗标、连接、信息、对齐。这里重点比较二者的不同之处:在hello_1.elf文件中,节的个数有明显增加,地址不再全为0,偏移量有所改变,这是因为链接器会根据代码之间的关系对代码进行修改,通过修改首地址和偏移量的方式将程序相互链接,各个符号的地址和偏移量因此改变。

5.3.3程序头

5.3.4段节

5.4 hello的虚拟地址空间

根据计算机系统的特性,程序被载入至地址0x400000~0x401000中。在该地址范围内,每个节的地址都与前一节中节对应的 Address 相同。通过ELF可知,程序从0x00400000到0x00400fff,在0x400fff之后存放的是.dynamic到.shstrtab节的内容。

5.5 链接的重定位过程分析

生成反汇编文件

注意到hello的反编译文件具有明显的模块化特征,整体结构清晰明了,且比hello.o多出了一些段,具体说明如下:

一、函数:在使用ld命令链接的时候,指定了动态链接器为64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,其中定义了hello.c中用到的printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini。链接器将上述函数加入。

二、函数调用:链接器解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入到了PLT中,.text与.plt节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为PLT中相应函数与下条指令的相对地址,指向对应函数。对于此类重定位链接器为其构造.plt。

三、.rodata引用:链接器解析重定条目时发现两个类型为R_X86_64_PC32的对.rodata的重定位(printf中的两个字符串),.rodata与.text节之间的相对距离确定,因此链接器直接修改call之后的值为目标地址与下一条指令的地址之差,指向相应的字符串。

5.6 hello的执行流程

程序名称

程序地址

<_start>

4010f0

<__libc_csu_init>

4011c0

<_init>

401000

<main>

401125

<.plt>

401020

<puts@plt>

401090

<printf@plt>

4010a0

<atoi@plt>

4010c0

<exit@plt>

4010d0

<sleep@plt>

4010e0

<getchar@plt>

4010b0

<__libc_csu_fini>

401230

<_fini>

401238

5.7 Hello的动态链接分析

  延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:

PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。

GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。

调用 dl_init 之前的全局偏移表

调用 dl_init 之后的全局偏移表

由上两图可知,调用dl_init之后,发生了动态链接,GOT条目改变。

5.8 本章小结

本章以一个简单的hello程序为载体,阐述了链接的概念和作用,通过分析可执行文件的ELF格式及其虚拟地址空间,重定位过程、加载以及运行时函数调用顺序以及动态链接过程,直观地解释了链接和重定位的详细过程。

6章 hello进程管理

1分)

6.1 进程的概念与作用

进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动过程调用的指令和本地变量。

进程为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。

其基本功能是解释并运行用户的指令,重复如下处理过程:

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量

(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令

(4)如果不是内部命令,调用fork( )创建新进程/子进程

(5)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

(6)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait…等待作业终止后返回。

(7)如果用户要求后台运行(如果命令末尾有&号),则shell返回;

6.3 Hello的fork进程创建过程

进程通过调用fork函数创建一个新的子进程。新创建的子进程几乎但不完全与子进程相同。在创建子进程的过程中,内核会将父进程的代码、数据段、堆、共享库以及用户栈这些信息全部复制给子进程,同时子进程还可以读父进程打开的副本。唯一的不同就是他们的PID,这说明,虽然父进程与子进程所用到的信息几乎是完全相同的,但是这两个程序却是相互独立的,各自有自己独有的用户栈等信息。

fork函数虽然只会被调用一次,但是在返回的时候却有两次。在父进程中,fork函数返回子进程的PID;在子进程中,fork函数返回0。这就提供了一种用fork函数的返回值来区分父进程和子进程的方法。

6.4 Hello的execve过程

fork之后,子进程调用execve函数(传入命令行参数)在当前进程的上下文中加载并运行hello程序,execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存。

6.5 Hello的进程执行

进程调度指在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个过程称为调度。内存收到中断信号之后,将当前进程加入等待序列,进行上下文切换将当前的进程控制权交给其他进程,当再次收到中断信号时将hello从等待队列加入运行队列

6.6 hello的异常与信号处理

异常可以分为四类:中断(interrupt),陷阱(trap),故障(fault)和终止(abort)。

中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。在当前指令完成执行后,处理器注意到中断引脚的电压变高,就从系统总线读取异常号,然后调用适当的中断处理程序。当处理程序返回后,它就将控制返回给下一条指令。

陷阱是有意的异常,是执行一条指令的结果。应用程序执行一次系统调用,然后把控制传递给处理程序,陷阱处理程序运行后,返回到syscall之后的指令。

故障由错误情况引起,故障发生时处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则,处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。

终止是不可恢复的致命错误造成的结果。终止处理程序从不将控制返回给应用程序,处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。

6.6.1正常运行

每隔3秒打印异常,不停止。

6.6.2 不同异常

Ctrl+c停止运行程序

Ctrl+z暂时挂起程序

输入ps查看进程,发现hello的PID值为2202。

输入jobs可进一步查看其详细信息。

执行tree

执行fg 1调到前台运行

执行kill

随意输入,不会对程序产生影响

6.7本章小结

本章从进程的角度分别描述了hello子进程forkexecve过程,并针对execve过程中虚拟内存映像以及栈组织结构等作出说明。同时了解了逻辑控制流中内核的调度及上下文切换等机制。阐述了ShellBash运行的处理流程以及hello执行过程中可能引发的异常和信号处理。

7章 hello的存储管理

2分)

7.1 hello的存储器地址空间

逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。

线性地址:也叫虚拟地址,和逻辑地址类似,也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件也是内存的转换前地址。

虚拟地址:就是线性地址。

物理地址:用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽象,内存的寻址方式并不是这样。所以,说它是“与地址总线相对应”更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,更便于人们理解。

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

逻辑地址的实际上是一对<选择符,偏移>,从左开始,第13位是索引,通过这个索引,可以定位到段描述符,而段描述符记载了有关一个段的位置和大小信息,以及访问控制的状态信息。段描述符一般由8个字节组成。由于8B较大,而Intel为了保持向后兼容,将段寄存器仍然规定为16-bit,我们无法通过16-bit长度的段寄存器来直接引用64-bit的段描述符。因此在逻辑地址中,只用13bit记录其索引。而真正的段描述符,被放于数组之中。

这个内存中的数组就叫做GDT,Intel的设计者提供了一个寄存器GDTR用来存放GDT的入口地址。程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,此后,CPU根据此寄存器中的内容作为GDT的入口来访问GDT了。除了GDT之外,还有LDT,但与GDT不同的是,LDT在系统中可以存在多个,每个进程可以拥有自己的LDT。LDT的内存地址在LDTR寄存器中。

TI位就是用来表示此索引所指向的段描述符是存于全局描述表中,还是本地描述表中。为0,表示用GDT,为1表示用LDT。

RPL位,占2bit,是保护信息位

n.net/x1299135087/article/details/124881230(以下格式自行编排,编辑时删除

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

线性地址(也就是虚拟地址 VA)到物理地址(PA)之间的转换通过分页机制完成。而分页机制是对虚拟地址内存空间进行分页。

使用虚拟寻址,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址被送到内存之前首先转换为适当的物理地址。将一个虚拟地址转换为物理地址的过程叫做地址翻译,需要CPU硬件和操作系统之间的紧密合作。CPU芯片上的内存管理单元利用主存中的查询表来动态翻译虚拟地址。

虚拟地址作为到磁盘上存放字节的数组的索引,磁盘上的数组内容被缓存在主存中。同时,磁盘上的数据被分割成块,这些块作为磁盘和主存之间的传送单元。虚拟内存分割被成为虚拟页。物理内存被分割为物理页,物理页和虚拟页的大小相同。

任意时刻虚拟页都被分为三个不相交的子集:

未分配的:VM系统还未分配的页

缓存的:当前已经缓存在物理内存的已分配页

未缓存的:当前未缓存在物理内存的已分配页

每次将虚拟地址转换为物理地址,都会查询页表来判断一个虚拟页是否缓存在DRAM的某个地方,如果不在DRAM的某个地方,通过查询页表条目可以知道虚拟页在磁盘的位置。页表将虚拟页映射到物理页。有效位表明虚拟页是否缓存在DRAM中,n位地址字段是物理页的起始地址或者虚拟页的起始地址。

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

TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。用于组选择和行匹配的索引和标记字段是从虚拟地址的虚拟页号中提取出来的。TLB中所有地址翻译步骤都是在MMU上执行的。

具体步骤为:

CPU产生一个虚拟地址

MMU从TLB中取出相应的PTE

MMU将这个虚拟地址翻译成物理地址,发送给高速缓存

高速缓存返回数据子给CPU

使用四级页表的地址翻译步骤:

如果TLB未命中,MMU会向页表中查询,CR3确定第一级页表的起始地址,VPN1确定第一级页表的偏移量,查询出PTE,如果在物理内存中,且权限符合,则确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN与VPO组合成PA。

TLB

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

针对物理内存访问,主要对各类高速缓存存储器的读写策略做出说明:

当CPU执行一条读内存字w的指令,它向L1高速缓存请求这个字。如果L1高速缓存由w的一个缓存的副本,那么就得到L1的高速缓存命中,高速缓存会很快抽取出w并返回给CPU。否则就是缓存不命中,当L1高速缓存向主存请求包含w的块的一个副本时,CPU必须等待。当被请求的块最终从内存到达时,L1高速缓存将这个快存放在它的一个高速缓存行里,从被缓存的块中抽取字w,然后返回给CPU。总体来看,高速缓存确定一个请求是否命中,然后抽取出被请求的字的过程,分为组选择、行匹配、字抽取三步。

7.6 hello进程fork时的内存映射

当fork函数被shell进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的、写时复制的。

article/details/124881230(以下格式自行编排,编辑时删除

7.7 hello进程execve时的内存映射

execve函数在shell中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效的替代了当前程序。加载并运行hello需要以下几个步骤:

删除已存在的用户区域。删除shell虚拟地址的用户部分中的已存在的区域结构。

映射私有区域。为hello的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。

映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

设置程序计数器(PC)。 execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

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

缺页故障:当指令引用一个相应的虚拟地址,而与改地址相应的物理页面不在内存中,会触发缺页故障。通过查询页表PTE可以知道虚拟页在磁盘的位置。缺页处理程序:缺页异常导致控制转移到内核的缺页处理程序。处理程序随后执行以下步骤:(1)判断虚拟地址是否合法。缺页处理程序搜索区域结构的链表,把虚拟地址和每个区域结构中的vm_start和vm_end做比较。如果指令不合法,缺页处理程序会触发一个段错误,从而终止这个进程。(2)判断内存访问是否合法。比如缺页是否由一条试图对只读页面进行写操作的指令造成的。如果访问不合法,缺页处理程序会触发一个保护异常,从而终止这个进程。(3)这时,内核知道缺页是由合法的操作造成的。内核会选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。处理程序返回时,CPU重新执行引起缺页的指令,这条指令将再次发送给MMU。这次,MMU能正常地进行地址翻译,不会再产生缺页中断了。

缺页故障处理

7.9本章小结

本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以及VAPA的变换、物理内存访问和hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

结论

0分,必要项,如缺失扣1分,根据内容酌情加分)

hello程序从.c文件开始,经过操作系统的一系列加工,最后总算走完了它的一生。它这一生,是艰苦的,因为饱受折磨(被软件处理的);又是幸福的,因为有人相助(也是软件处理的)。它这一生意义非凡,作为一个简单甚至可以说是简陋的程序,它的实现却是几十上百年无数计算机工程师努力的结果。

下面让我们回顾它光辉灿烂的一生:

编写,通过随便什么编辑器将代码键入hello.c

预处理,将hello.c调用的所有外部的库展开合并到一个hello.i文件中

编译,将hello.i编译成为汇编文件hello.s

汇编,将hello.s汇编成为可重定位目标文件hello.o

链接,将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello

运行,在shell中输入./hello 2021112700 毛彬丞

创建子进程,shell进程调用fork为其创建子进程

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

执行指令,CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流。

访问内存,MMU将程序中使用的虚拟内存地址通过页表映射成物理地址。

动态申请内存,printf会调用malloc向动态内存分配器申请堆中的内存。

信号,如果运行途中键入ctrl-c或ctrl-z则调用shell的信号处理函数分别停止、挂起。

结束,shell父进程回收子进程,内核删除为这个进程创建的所有数据结构。

以上,向hello的一生致敬。向所有计算机工程师致敬。

附件

文件名称

作用

hello.c

源代码,文本文件

hello.i

预处理得到,有一定的代码优化

hello.s

编译得到,为汇编语言文本文件

hello.o

汇编得到,可重定位

hello.elf

hello.oELF格式,用于分析hello.o

hello_objdump.txt

hello.o的反汇编文件,用于分析hello.o

hello

链接得到,可执行文件

hello_1.elf

helloELF格式,用于分析hello

hello_objdump_1.txt

hello的反汇编文件,用于分析hello

列出所有的中间产物的文件名,并予以说明起作用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值