计统大作业

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业 人工智能模块(2+X模式)

学     号    2021113541           

班     级       21WL023         

学       生       陈思阳       

指 导 教 师        郑贵滨         

计算机科学与技术学院

2023年5月

摘  要

本文以一个简单的程序hello.c为例子,探究了程序从源代码到可执行目标文件与程序载入、进程上下文切换、进程回收、信号发送、接收、处理过程。

在第一章,通过hello的自白,从流程方面梳理了程序从源代码到可执行目标文件与程序在操作系统的管理下运行的过程。在第二章至第五章,文章详细阐述了程序预处理、编译、汇编、链接的过程;在第六章至第八章,文章梳理了一个程序是如何在操作系统的管理下运行的。

关键词:P2P、020、计算机系统、编译、运行。

目  录

第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过程:程序员写好代码hello.c(Program)后由编译器驱动程序GCC进行进行处理。在程序员于终端中输入gcc -m64 -Og -no-pie -fno-PIC hello.c -o hello之后,GCC读取hello.c,按以下四个步骤得到可执行目标文件hello。

1、预处理。GCC调用预处理器cpp。它会根据以字符#开头的命令修改hello.c,得到处理后的程序文本hello.i。

2、编译。GCC调用编译器cc1。它会将hello.i翻译成文本文件hello.s。翻译所得文件是hello.c的汇编语言程序。

3、汇编。GCC调用汇编器as。它会将hello.s翻译成机器语言指令,并把这些指令打包成一个可重定位目标程序hello.o。

4、链接。GCC调用连接器ld。它会将hello.o与printf.o合并,得到最终的可执行目标文件Hello(Process)。

020:当用户输入./hello之后,shell的前端开始等待用户的信号输入,并暂停指令的读入,同时创建一个自己的子进程。这个子进程调用execve函数将hello加载到对应的上下文中,并将hello的指令与数据装载进内存中,再调用可执行目标文件hello的main函数开始执行hello。在程序结束后,系统回收进程,释放相应的内存空间,shell的前端继续等待用户之后的指令输入,系统回到了hello运行之前的状态(Zero-0)。

1.2 环境与工具

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

硬件环境:X64 CPU;2.00GHz;8G RAM;512GHD Disk

软件环境:Windows 64位;Vmware 17;Ubuntu 22.10

开发工具:Visual Studio Code 1.76.0 64位;CodeBlocks 64位;vi/vim/gedit+gcc

1.3 中间结果

hello.i:hello.c预处理后的文件。

hello.s:hello.i编译后的文件。

hello.o:hello.s汇编后的文件。

hello:hello.o链接后的文件。

图1 由源代码生成程序的中间文件

1.4 本章小结

       本章概括了P2P和020的含义,对程序的预处理、编译、汇编、链接与进程的上下文切换、程序的运行、进程的回收、信号的发送、接收、处理过程有了一个整体的把握。

第2章 预处理

(0.5分)

2.1 预处理的概念与作用

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

预处理就是针对c语言源代码中以“#”开头的预处理命令进行处理,从而将多个有着复杂符号定义与条件编译选项的文件得到一个简单的便于编译器处理的源代码。

通过这样的预处理操作,我们可以得到与程序员所编写的hello.c在运行结果上完全等价的hello.i,便于后续编译器的处理。

2.2在Ubuntu下预处理的命令

命令:gcc -E hello.c -o hello.i

 

图2 Ubuntu下的预处理命令与所得结果

2.3 Hello的预处理结果解析

       占据了hello.i文件绝大部分内容的是一些变量类型的声明(如_int_64、pid_t等)和外部函数的声明(如printf()、rand()、sleep()等)。这一部分来自于我们在hello.c中声明的头文件。之后,就是hello.c 的程序本体。这一部分和我们平时写的c语言语法完全一致,就不做过多的叙述。

        

图3 hello.i中对变量与函数的声明

       hello.i文件中相对特殊的是一些以“#”为开头的语句。它包含了所用头文件的路径信息。其前面的数字x是hello.i中使用这个文件中第x行之后的代码(例如“# 10 hello.c”表示接下来的一段代码是来自hello.c中第10行及其之后的)。

 

图4 “#”开头指令的语法说明

2.4 本章小结

本章简述了预处理的概念与方便编译的作用,并展示了如何获得原代码经预处理后的文件。然后,本章着重地分析了预处理文件本身,对于各个文件在预处理结果中的出现位置有了较为清晰的对应关系。

第3章 编译

2分)

3.1 编译的概念与作用

编译就是将经过预处理的代码翻译成运行时与之完全等价的汇编语言程序文件的过程。由于汇编语言就是机器码的一种表现形式,所以也可以将编译理解为将经过预处理的代码翻译成机器码的过程。

编译的一大作用就是将高级语言的代码翻译为低级的机器码,从而使得机器可以执行程序员编写的程序。同时,编译器没有直接将代码翻译成机器码,而是将它翻译成了相对可读性更强、便于查错的汇编语言代码,为程序员调试程序、进一步优化程序性能提供了便利。

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s

图5 Ubuntu下的编译命令与所得结果

3.3 Hello的编译结果解析

3.3.1数据

常量:.LC0中的.string=”用法: Hello 学号 姓名 秒数!”(没有回车是因为汇编码调用了puts()函数)、.LC1中的string=” Hello %s %s\n”。

      全局符号:main(是一个函数)

3.3.2 赋值

在.L2处的第一行,程序将内存地址%rbp-4的地方写成0,这一步与原程序for循环中的i=0等价。

3.3.3 算术操作

在.L4处的最后一行,程序将内存地址%rbp-4的地方加1,这个操作与原程序for循环中i++等价。

同时,程序中还使用了很多加法指令,但与其说它们是为了进行算术运算,不如说是为了数组的内存寻址而做出的操作。所以对于它们的解析将在3.3.6中重点展开。

3.3.4 关系操作

在.LFB6中的第11行,程序将内存地址%rbp-20的地方与4比较,即减4,不记录计算结果但会改变标志位,结合第12行就实现了原程序中if里面的不等关系表达。

同时,在.L3中的第一行,程序将内存地址%rbp-4的地方与7比较,即减7,不记录计算结果但会改变标志位,结合第2行就实现了原程序中for里面的小于关系比较(由于比较值是7,再加上这个内存地址里面存的是整数,所以条件跳转是jle,其实与小于8是等价的)。

3.3.5 数组、指针、结构体操作

在.L4中的第1行,程序读取了内存地址%rbp-32的值,并将其装载进%rax中,然后将%rax+16,把所得的值作为地址,并将内存中相应地址的值装载进%rdx中,这一步对应原程序中读取argv[2]中的值。同理,接下来程序重复了与上述类似的操作,只是将%rax加了8与24,从而读取了argv[1]与argv[3]的值。

3.3.6 控制转移

汇编语言的控制转移是结合cmp指令实现的,原理是读取经cmp计算后标志位的变化来进行相应的条件判断,同时进行程序的跳转。例如在3.3.4中描述的if里面的不等关系表达,在第12行使用了等于则跳转指令。对应我们的hello.c,这个指令的含义是如果等于则跳转出if语句的代码块,从而实现了分支跳转。又如3.3.4中描述的for里面的小于关系表达,在第2行中使用了小于等于则跳转指令,对应我们的hello.c,就相当于如果小于则跳转进入for语句的代码块,实现了c语言程序的循环结构。

3.3.7 函数操作

通过阅读hello.c,我们发现:这份代码只是调用了printf()、exit()与sleep()。故我们先对调用它们时的行为进行分析。

在hello.c中的第14行处调用printf()时,由于作为参数的字符串只是一个带有回车的常字符串(.LC0中的.string),所以程序改为速度更快的puts()函数来实现这个功能。并将这个字符串的地址装载进%rdi中,作为puts()的第一个也是唯一一个参数传入函数。在完成这些操作后,程序调用了puts()函数。

由于exit()与sleep()都是只含一个参数的函数,其调用行为与puts()基本一致,所以不再赘述。

对于第18行的printf()函数,由于有多个参数,编译器没有使用puts()替代它。而是将.LC1中的.string的地址载入记录第一个参数的%rdi,argv[1]的值载入记录第二个参数的%rsi,argv[2]的值载入记录第三个参数的%rdx。之后便调用printf()函数,在命令行窗口中输出字符。

然后就是分析main()函数的调用行为。在main()被调用后,它首先创建了自己的栈帧,将传入的参数存入栈帧所对应的内存中。阅读后面的代码可以发现,这个栈帧里面还存放了hello.c里面的局部变量i。当它成功调用getchar()之后,程序将返回值0载入用于存放返回值的%rax中,再使用leave指令关闭栈帧,最后再返回。

3.4 本章小结

本章首先阐述了编译的概念和作用,然后使用相应的Ubuntu指令完成了预处理文件的编译工作。之后,对应所得的汇编语言程序文件hello.s,参考C语言的数据与操作列表,系统、详细地分析了这个汇编语言程序,对hello.c中代码所蕴含的真正含义有了深度的认知。

第4章 汇编

2分)

4.1 汇编的概念与作用

       汇编就是将编译得到的.s文件中包含的汇编代码翻译成机器码,得到它的可重定位目标文件。

       这一步的作用时将机器不懂的汇编码翻译成机器能够处理的机器码,并且可以结合链接器,利用.o文件可重定位特性将编写将它与其它文件中用其它编程语言编写的函数所得到的.o文件链接起来,从而实现调用其它编程语言编写的功能。

4.2 在Ubuntu下汇编的命令

       命令:gcc -c hello.s -o hello.o

图6 Ubuntu下汇编的指令与所得的hello.o的部分信息

4.3 可重定位目标elf格式

4.3.1 ELF

图8 hello.o的elf头信息。

由图知,ELF头以长度为16B的Magic序列开始,描述了生成该文件系统的字的大小和字节顺序。7f、45、4c、46分别对应ascii中的Del(删除)、E、L、F。这四个字节被称为ELF文件的魔数,操作系统在加载可执行文件是会确认魔术是否正确,如果不正确则拒绝加载。

ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中:

1)第五个字节标识ELF文件是32位(01)还是64位(02)的;

2)第六个字节标识该ELF文件字节序是小端(01)还是大端(02)的;

3)第七个字节指示ELF文件的版本号,一般是01;

4)后九个字节ELF标准未做定义。一般为00。

4.3.2 节头部表

完整的hello.o的节信息如图8所示。由图知,这个文件有13个节,这个表对重定位很有用。

图8 hello.o完整的节信息

4.4.3重定位节

图9 hello.o的重定位节信息

由图9知,这个文件有1种类型的重定位信息。

4.4.4 符号表

图10 hello.o的符号表信息

      由图10 知,这个文件由11个符号,包括了其中定义、引用的函数和全局变量的信息。

4.4.5 重定位条目

图11 hello.o的重定位条目信息

由图11知,这个文件有8个重定位信息,它们总共有两个种类,包括一些外部的只读数据以及若干外部函数,如printf()、sleep()等。

注意,其中类型R_X86_64_PC32已经出现在这个文件中。(通过后续的反汇编可以清晰地看到它们就是hello.c中的两个需要输出的字符串和相应的格式串)

4.4 Hello.o的结果解析

将hello.o反汇编得到的结果如图12所示。

图12 hello.o的反汇编结果

通过与hello.s对比,发现了机器码与汇编码存在着一一对应的关系。反汇编结果表明,每一条汇编语言操作码都可以用机器二进制数据表示,反之同理。

同时,还发现了机器码相较于汇编码的一些不同之处:

1)汇编语言中的操作数都是十进制数,而反汇编程序中的操作数都是十六进制数。

2)汇编语言中的分支转移是由助记符来标识,而在反汇编程序中的分支转移则是直接跳入目的地址。

3)汇编语言中的函数调用紧跟函数名称,而在反汇编程序中,call的目标地址是下一条指令的地址。

4.5 本章小结

本章首先阐述了汇编的概念、作用、以及在Ubuntu下执行汇编的指令,并利用readelf查看了可重定位目标文件的文件结构,分析了其elf格式,还通过反汇编的方法发现了汇编语言与机器码的一一对应关系和不同之处。

5章 链接

1分)

5.1 链接的概念与作用

链接就是将若干个可重定位目标文件合并起来,得到可执行的目标文件。其作用就是将各个模块正确地衔接起来,使得其可以被操作系统加载至内存中执行。

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

图13 Ubuntu下链接的指令与所得结果

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

5.3.1 ELF头

图14 hello的ELF头信息

       由图至,hello的文件类型为EXEC,是一个可执行目标文件,文件中总共有27个节,运行时从0x4010f0开始。

5.3.2 节头

       相关信息如下图所示。

 

图14 hello各段的基本信息

5.4 hello的虚拟地址空间

图14  hello的Data Dump信息

       观察图14可以看出,hello的虚拟地址从0x401000开始。结合图14可知,从004010f0开始为运行的代码段,而前面的就是一些只读数据和各种函数。

5.5 链接的重定位过程分析

为了方便阅读,由于本节主要是分析链接的重定位过程,所以仅截取main()函数部分(如图15所示),而前面的函数均已省略。

图15  hello的反汇编程序

对比图12可知,hello的反汇编程序中的每一个函数都有了确定的地址。同时,相比于图12中在调用外部函数与数据时都是跳转至函数或数据调用指令的下一行而言,hello的反汇编程序增加了外部链接的函数,并填写了相应的跳转位置。这说明已经完成了重定位。

结合链接的指令与教材,我们可以得知指令链接的过程:把编译好的目标文件和其他的一些目标文件和库链接在一起,形成最终的可执行文件。

在这个过程中,链接器完成符号的重定位。在链接器完成符号解析后,就把代码中的每个符号引用和正好一个符号定义(符号表条目)关联起来,得知每一个节的大小。然后,链接器将输入目标文件的相同节合并成一个节,合并的节将作为可执行目标文件中此类型的节。随后,根据各个合并后节的大小,链接器将确定每个合并节的运行时内存地址,并确定合并节中符号定义的运行时内存地址。最后,链接器修改所有的符号引用,使之指向符号定义的运行时内存地址。链接器要执行此步骤依赖于目标文件中的重定位信息。

5.6 hello的执行流程

序号

函数名称

1

ld-2.23.so!_dl_start

2

ld-2.23.so!_dl_init

3

hello!_start

4

lbc-2.23.so!_libc_start_main

5

libc-2.23.so!_cxa_atexit

6

libc-2.23.so!_libc_csu.init

7

hello!_init

8

libc-2.23.so _setjmp

9

libc-2.23.so exit

5.7 Hello的动态链接分析

 模块的外部函数指针有全局偏移表(GOT)保存。运行时,动态链接器会填充GOT中的各个表项,以保证每个指针均指向正确的地址。所以,我们可以观察.got对应位置的变化情况来观察动态链接过程。由图14可知,.got表项目在地址0000000000403fe8~ 0000000000404030之间。在运行过程中,确实观察到了这里面的值发生变化(如图16所示)。

图16 Hello的动态链接过程

5.8 本章小结

本章阐述了链接的概念与作用,并展示了在Ubuntu下链接的命令。之后,简单分析了hello的ELF文件格式与虚拟内存情况,重点分析了hello与hello.o反汇编程序的区别,并借助edb分析了程序运行时函数的调用过程与动态链接的过程。

6章 hello进程管理

1分)

6.1 进程的概念与作用

概念:一个执行中程序的实例,系统中的每个程序都运行在某个进程的上下文中。每个进程都有它自己的地址空间,一般情况下包括代码段、数据段、和堆栈段,包括了存放在内存中的程序的代码和数据,它的栈、通用目的寄存器、程序计数器、环境变量以及打开文件描述符的集合。

作用:进程主要为用户提供了下列两个抽象:

(1)一个独立的逻辑流,提供程序独占使用处理器的抽象。

(2)一个私有的虚拟地址空间,提供程序独占使用整个系统内存的抽象。

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

作用:

(1) 用户和Linux内核之间的接口程序。在提示符下输入的每个命令都由shell先解释然后传给Linux内核。

(2)调用其他程序,并传递数据、参数、获取程序的处理结果。

(3)在多个程序之间传递数据,把一个程序的输出作为另外一个程序的输入。

处理流程:

(1)接收stdin上输入的命令行。

(2)调用parseline函数解析命令行参数,并且构造argv向量。

(3)调用builtin_command函数,检查第一个命令行参数是否是shell命令。

是则执行

(4)如果不是,那么shell创建一个子进程,并且在子进程中用execve执行程序,如果后台运行则shell返回顶部,等待下一命令。否则使用waitpid函数等待终止,终止后,shell返回顶部,等待下一命令。

16.3 Hello的fork进程创建过程

在命令行输入./hello命令运行hello程序时,此命令不是内置命令,因此调用fork()函数创建子进程。得到与父进程shell虚拟地址空间相同(但是独立的)一份副本。

6.4 Hello的execve过程

1)在6.3中得到的shell子进程中使用execve函数运行hello。

(2)execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量envp。只有当出现错误的时候,才会返回到调用的程序。

(3)hello执行完成并正常退出后,不再返回子进程。

6.5 Hello的进程执行

1)运行一个新进程时,内核会抢占当前进程,通过使用一种上下文切换的较为高层的形式异常控制流将控制转移到新的进程。

(2)进程hello初始运行在用户模式中。当它执行系统调用函数sleep或者exit时,该进程被挂起或终止,进入到内核模式,控制权交予内核处理程序,由它完成对系统函数的调用。之后,执行上下文切换,将控制返回给进程hello系统调用之后的那条语句或是终止进程hello。在hello运行时,不断地进行着上下文的切换,使得Hello程序被切分成一个个时间片,和其他进程交替的占用着处理器。从而实现了进程调度。

6.6 hello的异常与信号处理

可能异常:中断、陷阱、故障、终止。

中断:在hello程序执行的过程中可能会出现外部I/O设备引起的异常。

陷阱:通常是有意的异常,是执行一条指令的结果。例如,hello执行sleep函数的时候会出现这个异常。

故障:导致hello无法继续执行,但是可以在修复后继续执行的一类异常。比如执行hello程序的时候,可能会发生缺页故障。经过修复后即可找到相应的页。

终止:不可恢复的错误。例如在hello执行过程可能会出现段错误。

信号类型:SIGINT、SIGKILL、SIGSEGV、SIALARM、SIGCHLD。

按下Ctrl-C: 在键盘上输入Ctrl+c会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业。

按下Ctrl-Z,并运行ps: 输入ctrl-z,内核会发送SIGSTP。SIGSTP默认挂起前台hello作业,但 hello进程并没有回收,而是运行在后台下,通过ps指令可以对其进行查看。

Ctrl-z后运行fg:进程继续运行

Ctrl-z后运行jobs:进程仍在后台

Ctrl-z后运行pstree:

Ctrl-Z后按下kill 命令:内核会发送SIGKILL信号给指定的pid(hello程序),杀死hello程序

终端随意输入:没有影响

6.7本章小结

本章介绍了进程、Shell的概念和作用、shell如何调用fork和execve函数、以及通过对hello的创建、加载和终止、进程执行、异常与信号处理。

7章 hello的存储管理

2分)

7.1 hello的存储器地址空间

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

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

逻辑地址:hello产生的与段相关的偏移地址部分,是相对于进程数据段0x400010的地址。

线性地址:逻辑地址到物理地址变换之间的中间层。将hello数据段中的偏移地址加上相应的段基址0x400010就生成了hello的线性地址。

虚拟地址:在一个带虚拟内存的系统中,CPU从一个有N=2^n个地址的地址空间中生成虚拟地址。这个地址空间称为虚拟地址空间。

物理地址:hello储存在内存中的真实位置。

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

段式存储管理中以段为单位分配内存,每段分配一个连续的内存区,但各段之间不要求连续,内存的分配和回收类似于动态分区分配,由于段式存储管理系统中作业的地址空间是二维的,因此地址结构包括两个部分:段号和段内位移。

段式管理通过偏移地址来完成逻辑地址的空间表示,在段式管理地址变换的过程中间,需要位运行的进程建立一个段表,将程序的地址空间划分为若干个段,这样每个进程有一个二维的地址空间。

线性地址的计算过程是计算有效地址EA后取出段寄存器DS对应的描述符cache中的段基址,最后得到线性地址LA=段基址+E

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

线性地址被划分为固定长度的组,成为页。为了节约页表所占用的内存空间,线性地址空间通过页目录表和页表两级查找转换成为物理地址。页目录表的物理地址存储在cr3寄存器当中。

页命中时,处理器生成一个虚拟地址,并把它传送给MMU,从而生成PTE地址,并向高速缓存/主存请求,在高速缓存/主存向MMU返回PTE后,MMU构造物理地址,并把它传送给高速缓存/主存,最后高速缓存/主存返回所请求的数据字给处理器。

MMU从cr3寄存器当中取出进程的页目录地址,根据线性地址高10位找到索引,得到了下一级页表的地址,根据线性地址的中间10位,在页表当中得到页的起始地址,通过页的起始地址和低10位的虚拟地址相加,得到最终的物理地址。

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

TLB是一个小的,虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。虚拟地址传送给MMU时,MMU在TLB中进行相应的匹配,如果命中,那么将得到PPN,其与VPO进行组合将会得到52位的物理地址PA。

如果TLB没有命中,那么MMU向页表当中进行相应的查询,CR3确定第一级页表的起始地址。VPN1确定在确定在第一级页表中的偏移量,查询出PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA,并且向TLB中添加条目。

如果查询PTE的时候找不到,那么会引发缺页异常。

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

在得到物理地址之后,先将物理地址拆分成CT、CI、CO,先对一级cache进行访问,先找组索引位,然后与标志位对比。如果未能寻找到标记位为有效的字节,即一级cache不命中那么访问二级cache,如果二级cache不命中,那么访问三级cache,依然不命中,那么访问主存,如果主存缺页中断那么就访问硬盘。

7.6 hello进程fork时的内存映射

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

fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

先删除已存在的用户区域,即删除当前进程虚拟地址的用户部分中的已存在的区域结构。

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

之后映射共享区域,如果程序与共享对象链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

最后设置PC,使之指向代码区域的入口点。

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

缺页故障:当CPU想要读取虚拟内存中的某个数据,而这一片数据恰好存放在主存当中时,就称为页命中。相对的,如果DRAM缓存不命中,则称之为缺页。

缺页中断处理:发生缺页故障时,触发缺页异常处理程序,选择出物理内存中的牺牲页,如果这个页已经被修改了,则把它复制回磁盘。程序调入新的页面,并更新内存的PTE,接着执行命令。CPU将引起缺页的虚拟地址重新发送给MMU。

7.9本章小结

本文结合hello讲述了计算机内存的四种内存空间,并描述了hello的寻址方式和执行时的fork与execve映射,最后阐述了缺页故障和缺页中断处理的方式。

结论

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

hello的历程:

  1. 程序员编写hello.c并启动gcc
  2. gcc调用cpp将hello.c预处理得到hello.i
  3. gcc调用ccl将hello.i编译得到hello.s
  4. gcc调用as将hello.s汇编得到hello.o
  5. gcc调用ld将hello.o链接得到hello
  6. 程序员在shell中输入./hello
  7. Shell将指令解析发现是需要运行一个叫hello的可执行目标文件
  8. Shell创建一个自身的子进程,由子进程使用execve运行hello并载入参数(空的)。父进程则维护作业列表并等待hello结束。
  9. 调用execve后,操作系统将hello加载到虚拟内存,并由MMU将虚拟地址翻译为物理地址,此后执行逻辑控制流。
  10. hello输出格式错误信息后调用exit返回,触发信号处理函数。此后进程结束,占用资源被操作系统回收。

感悟:计算机的每一步设计都是前人深思熟虑,历经迭代,经过严格规定处理正常情况或异常情况之后的结果,其知识的深度与广度每一寸都凝聚着前人的心血。妥善地利用这些设计所体现的特性,将有助于我编写正确、高效的程序,对我后续的学习有很大的帮助。

附件

hello.i:hello.c预处理后的文件。

hello.s:hello.i编译后的文件。

hello.o:hello.s汇编后的文件。

hello:hello.o链接后的文件。

参考文献

[1]  Computer Systems:A Programmer's Perspective

[2] 编译预处理—C语言 - 知乎

[3] c语言预处理机制详细分析_tutu-hu的博客-CSDN博客

[4] 编译_百度百科

[5] gcc 编译命令详解及最佳实践 - 知乎

[6] readelf命令使用说明_木虫下的博客-CSDN博客

[7]https://wapbaike.baidu.com/item/leave/9433787#:~:text=leave%E6%98%AF%E6%B1%87%E7%BC%96,%E5%B8%B8%E7%94%A8%E4%BA%8E%E5%87%BD%E6%95%B0%E6%9C%AB%E5%B0%BE%E3%80%82

[8]https://blog.csdn.net/wohenfanjian/article/details/105618467#:~:text=%E9%87%8D%E5%AE%9A%E4%BD%8D%E7%9A%84%E6%95%B4%E4%BD%93%E8%BF%87%E7%A8%8B%201%20%E9%87%8D%E5%AE%9A%E4%BD%8D%E8%8A%82%E5%92%8C%E7%AC%A6%E5%8F%B7%E5%AE%9A%E4%B9%89%E3%80%82%20%E9%93%BE%E6%8E%A5%E5%99%A8%E5%B0%86%E8%BE%93%E5%85%A5%E7%9B%AE%E6%A0%87%E6%96%87%E4%BB%B6%E7%9A%84%E7%9B%B8%E5%90%8C%E8%8A%82%E5%90%88%E5%B9%B6%E6%88%90%E4%B8%80%E4%B8%AA%E8%8A%82%EF%BC%8C%E5%90%88%E5%B9%B6%E7%9A%84%E8%8A%82%E5%B0%86%E4%BD%9C%E4%B8%BA%E5%8F%AF%E6%89%A7%E8%A1%8C%E7%9B%AE%E6%A0%87%E6%96%87%E4%BB%B6%E4%B8%AD%E6%AD%A4%E7%B1%BB%E5%9E%8B%E7%9A%84%E8%8A%82%E3%80%82%20%E9%9A%8F%E5%90%8E%EF%BC%8C%E9%93%BE%E6%8E%A5%E5%99%A8%E7%A1%AE%E5%AE%9A%E6%AF%8F%E4%B8%AA%E5%90%88%E5%B9%B6%E8%8A%82%E7%9A%84%E8%BF%90%E8%A1%8C%E6%97%B6%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80%EF%BC%8C%E5%B9%B6%E7%A1%AE%E5%AE%9A%E5%90%88%E5%B9%B6%E8%8A%82%E4%B8%AD%E7%AC%A6%E5%8F%B7%E5%AE%9A%E4%B9%89%E7%9A%84%E8%BF%90%E8%A1%8C%E6%97%B6%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80%E3%80%82,%E8%BF%99%E4%B8%80%E6%AD%A5%E9%AA%A4%E5%AE%8C%E6%88%90%E5%90%8E%EF%BC%8C%E5%8F%AF%E6%89%A7%E8%A1%8C%E7%9B%AE%E6%A0%87%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E6%8C%87%E4%BB%A4%E5%92%8C%E7%AC%A6%E5%8F%B7%E5%AE%9A%E4%B9%89%E7%9A%84%E8%BF%90%E8%A1%8C%E6%97%B6%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80%E5%B0%B1%E5%94%AF%E4%B8%80%E7%A1%AE%E5%AE%9A%E4%BA%86%E3%80%82%202%20%E9%87%8D%E5%AE%9A%E4%BD%8D%E8%8A%82%E4%B8%AD%E7%9A%84%E7%AC%A6%E5%8F%B7%E5%BC%95%E7%94%A8%E3%80%82%20%E9%93%BE%E6%8E%A5%E5%99%A8%E4%BF%AE%E6%94%B9%E6%89%80%E6%9C%89%E7%9A%84%E7%AC%A6%E5%8F%B7%E5%BC%95%E7%94%A8%EF%BC%8C%E4%BD%BF%E4%B9%8B%E6%8C%87%E5%90%91%E7%AC%A6%E5%8F%B7%E5%AE%9A%E4%B9%89%E7%9A%84%E8%BF%90%E8%A1%8C%E6%97%B6%E5%86%85%E5%AD%98%E5%9C%B0%E5%9D%80%E3%80%82%20%E9%93%BE%E6%8E%A5%E5%99%A8%E8%A6%81%E6%89%A7%E8%A1%8C%E6%AD%A4%E6%AD%A5%E9%AA%A4%E4%BE%9D%E8%B5%96%E4%BA%8E%E7%9B%AE%E6%A0%87%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84%E9%87%8D%E5%AE%9A%E4%BD%8D%E4%BF%A1%E6%81%AF%E3%80%82

[9] 动态链接的整个过程_动态链接过程_鬼灭之刃的博客-CSDN博客

[10] https://www.cnblogs.com/losing-1216/p/4884483.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值