2023 HIT CSAPP 大作业程序人生-Hello’s P2P

摘  要

本文从计算机系统的角度来分析hello程序的诞生到结束的全过程。其中包括了预处理,编译,汇编,链接,进程管理和存储管理。概括了一个c程序时如何一步一步地变为一个可执行文件,还有可执行文件执行过程中地进程和存储分配地过程,体现了一个c程序地一生。

关键词:程序运行;汇编;编译;存储管理;进程管理                           

目  录

第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 -

结论......................................................................................................................... - 14 -

附件......................................................................................................................... - 15 -

参考文献................................................................................................................. - 16 -

第1章 概述

1.1 Hello简介

P2P:在文本编辑器或IDE中编写C语言代码,得到最初的hello.c程序,即最初的Program,hello.cLinux系统中运行shell程序输入命令gcc hello.c -o hello后经过cpp的预处理、ccl的编译、as的汇编以及ld的链接最终成为了可执目标程序文件helloterminal输入.\hello后,shell为其创建子进程(fork),产生子进程,接着hello的内容加载到子进程的地址空间,然后hello便从程序变为了进程。

020:进入程序入口后程序才开始载入物理内存。进入main()函数执行其目标代码, CPU则为运行的hello分配时间片从而执行逻辑控制流。当hello程序运行结束后,shell的父进程便负责回收hello进程,最后内核删除相关数据结构。

1.2 环境与工具

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

硬件环境:intel core i5 CPU2GHz2G RAM256GHD Disk 以上

软件环境:Windows10 64位;Vmware 16Ubuntu 16.04 LTS 64

调试工具:gccgdbedbreadelfHexEdi

1.3 中间结果

文件的名字

文件的作用

hello.i

经过预处理后的hello源程序文本文件

hello.s

经过编译之后产生的汇编文件文本文件

hello.o

经过汇编之后产生的可重定位目标文件二进制文件

hello

通过链接产生的可执行目标文件二进制文件

elf.txt

hello.oELF格式文本文件

Disas_hello.s

hello.o的反汇编代码文本文件

hello1.elf

helloELF格式文本文件

hello1_objdump.s

hello的反汇编代码文本文件

1.4 本章小结

    本章介绍了hello的P2P和020的过程和这篇文章中所用的硬件和软件工具。最后描述了从hello.c到hello的中间文件的名字和作用。对hello程序的进行进行了一个简单明了的概括。

第2章 预处理

2.1 预处理的概念与作用

预处理概念:预处理器cpp根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的所有库展开合并成为一个完整的文本文件。预处理中首先会展开以#起始的行,试图解释为预处理指令,其中包括有#if#ifdef#ifndef#else#elif#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令)。预处理完之后生成.i文件。

预处理的作用:将源文件中用#include 形式声明的文件复制到新的程序中。例如hello.c文件中第6-8行的#include<stdio.h>等命令使得预处理器读取系统头文件stdio.h、unistd.h和stdlib.h的内容,并将其直接插入到程序文本中;处理条件编译指令,条件编译指令如#ifdef#ifndef#else#elif#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉;预编译程序可以识别一些特殊符号,预编译程序对于在源程序中出现的这些特殊符号串会用合适值进行替换;合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

 

上图是hello.i的部分截图。经过预处理之后,hello.c转化为hello.i文件,打开该文件可以发现,文件的内容增加,且仍为可以阅读的C语言程序文本文件。对原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。例如声明函数、定义结构体、定义变量、定义宏等内容。另外,如果代码中有#define命令还会对相应的符号进行替换。

2.4 本章小结

本章主要是对预处理(包括头文件展开、宏替换、去掉注释、条件编译等等)的概念和作用进行介绍,除此之外还有Ubuntu下预处理的两个指令,同时也具体到了我的hello.c文件预处理结果hello.i文本文件的详细解析,深刻理解了预处理的重要内涵。我知晓了预处理可以最大程度上减轻程序员的负担,提供一定的可移植性:通过宏定义(#define)可以更简明地定义一些实用“函数”,再通过各种编译命令,将所写代码根据不同运行的平台进行调整,从而避免不必要麻烦。

第3章 编译

3.1 编译的概念与作用

编译的概念:编译器将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。其以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。 这个过程称为编译,同时也是编译的作用。

编译的作用:编译程序的基本功能是把源程序(高级语言)翻译成目标程序。除了基本功能之外,编译程序还具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人际联系等重要功能。

3.2 在Ubuntu下编译的命令

 

3.3 Hello的编译结果解析

 

结果部分截图。

3.3.1数据

  (1)常量

 

如上图有两个字符串常量存放在.rodata节。分别是printf的两个参数“用法:hello 学号 姓名 秒数”和“Hello %s %s\n”。

  (2)全局变量

 

如上图,全局变量有main函数,用globl标记main为全局变量。

  (3)局部变量

 

 

局部变量存放如堆栈中,如图i放入%rbp-4的位置。还有argc和argv也是局部变量放入堆栈当中。

  (4)关于数据的类型:在编译过程中,编译器会根据源程序中数据的类型来选取不同的寄存器以及不同的指令,比如浮点数会选择XMM寄存器,整数或指针会选择通用目的寄存器,同时也会根据数据的字节大小选择寄存器的不同部分以及指令的后缀。但在编译完成后,所有的类型信息都不复存在了,无法根据产生的汇编代码推断某个数据的类型。

3.3.2赋值操作

 

如上图,通过mov指令给变量i赋值0。

3.3.3算数操作

 

如上图,通过add指令完成i++的运算操作。还有其他的算数操作例如:lea,sub。

3.3.3关系操作

 

如上图,是对argc与4的比较,对应的c语言是(argc!=4)。

 

上图中是i与7进行比较,是for循环的条件。

3.3.4数组操作

 

如上图所示,argv[]的地址为%rbp-32,由于一个元素大小为8字节,add 24则是argv[3].

3.3.5控制转移

je是等于时转移。

 

jmp是无条件转移。

 

jle是小于等于转移。

 

3.3.6函数操作

 

在上图中,将%rax的值传给了%rdi,将%rdi作为函数参数传给了atoi@PLT函数,并且调运了函数atoi@PLT。

同样的还有main函数,printf函数,exit函数,sleep函数,getchar函数。

 

函数的返回,用ret,上图是main函数的返回。

3.3.7类型转换

其中涉及类型转换的是atoi(argv[3])。

3.4 本章小结

本章主要是对编译概念和作用进行详细介绍,同时也通过示例函数表明c语言是如何转换成为汇编代码的。除此之外我还介绍了汇编代码是如何实现变量、常量、传递参数、分支以及循环。编译程序所做的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则后,将其翻译成等价的中间代码或汇编代码来表示。包括之前对编译结果进行详尽仔细地解析,都使得我更加深刻理解C语言数据与操作,对C语言编译成汇编语言有更为恰当的掌握。因为汇编语言具有通用性,我掌握了它也相当于掌握了语言间的一些共性。汇编是C语言源程序经过预处理器、编译器处理后的结果,也是最贴近机器底层的语言。即使在高级语言盛行的今天,仍然需要学习汇编并掌握它。最后,通过学习汇编可以理解编译器的优化性能,并且分析解析出代码中所隐含的低效率从而更好地优化程序。

第4章 汇编

4.1 汇编的概念与作用

     汇编的概念: 汇编的过程将编译生成的ASCII汇编语言文件hello.s翻译成一个可重定位目标文件hello.o。可重定位目标文件包含指令对应的二进制机器语言,这种二进制代码能够被计算机理解并执行。

     汇编的作用:使汇编文件转换成计算机理解的克重定位的二进制文件,在编译时与其他可重定位目标文件能够合并起来,创建成一个可执行目标文件,从而被加载到内存中执行。

4.2 在Ubuntu下汇编的命令

 

4.3 可重定位目标elf格式

利用readelf查看hello.o的elf格式,结果如下:

ELF头:以 16B 的序列 Magic 开始,Magic 描述了生成该文件的系统 的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解 ## 标题释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、 字节头部表(section header table)的文件偏移,以及节头部表中条目的大 小和数量等信息。

 

节头: 节头部表描述不同节的位置和大小,目标文件中的每个节都有一个固定大小的节头部表条目。

到重定位节:对最终位置未知的目标引用,会产生一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位信息就放在重定位节.rel.text中,已初始化数据的重定位条目放在.rel.data中。

 

 

符号表: 存放程序中定义和引用的函数和全局变量的信息。name是符号名称,对于可冲定位目标模块,value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type要么是数据要么是函数。Bind字段表明符号是本地的还是全局的。

 

4.4 Hello.o的结果解析

下图是反汇编的部分结果

 

下图是hello.s

 

经过对比可以得到两者的区别:

  1. 立即数的变化:hello.s中的立即数都是用10进制数表示的。
  2. 分支转移的不一致:hello.s中的分支转移(即跳转指令)直接通过像.LC0.LC1这样的助记符进行跳转,会直接跳转到相应符号声明的位置。
  3. 函数调用的不一致:hello.s中的函数调用直接在call指令后面加上要调用的函数名。但是在机器语言中,call指令后是被调函数的PC相对地址。在这里,由于调用的函数都是库函数,需要在动态链接后才能确定被调函数的确切位置,因此call指令后的二进制码为全0,同时需要在重定位节中添加重定位条目,在链接时确定最终的相对地址。

4.5 本章小结

本章对hello.s进行了汇编,生成了hello.o可重定位目标文件,并且分析了可重定位文件的ELF头、节头部表、符号表和可重定位节,比较了hello.shello.o反汇编代码的不同之处,分析了从汇编语言到机器语言的一一映射关系。

5章 链接

5.1 链接的概念与作用

连接的概念:链接是将各种代码和数据的片段收集并组合成一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。在现代系统中,链接由链接器程序自动执行。

连接的作用:链接能够节省源程序空间,同时对于未编入的常用函数文件能够进行合并从而生成能够正常工作的可执行目标文件。除此之外链接也可以表述为使得一个复杂程序被分解成诸多简明模块来进行编写,并且最终被合并为一个可执行程序。这些作用使得分离编译成为了一种可能。

5.2 在Ubuntu下链接的命令

    链接如下图所示

 

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

 

使用readelf将其ELF格式文本写在txt中,方便查看。

ELF头:节头部表描述不同节的位置和大小,目标文件中的每个节都有一个固定大小的节头部表条目。由该节可知入口点位置为0x4010f0,大小为64字节。

 

节头: 程序头部表描述了可执行文件的连续的片映射到连续的内存段的映射关系。包括目标文件的偏移、段的读写/执行权限、内存的开始地址、对齐要求、段的大小、内存中的段大小等。节头大小应该为64字节

 

程序头: 程序头部表描述了可执行文件的连续的片映射到连续的内存段的映射关系。包括目标文件的偏移、段的读写/执行权限、内存的开始地址、对齐要求、段的大小、内存中的段大小等。该节大小应该为56字节。

 

符号表:能够发现可执行文件中的符号表多出很多符号,并且额外又多出一个动态符号表(.dynsym),其中printf()、puts()、atoi()、exit()、getchar()等C标准库函数在动态符号表和符号表中都有表项。.dynsym大小为24字节,开始地址为000003a8.symtab大小为24字节,开始地址为00003060.

 

重定位节:重定位条目包括offset:需要被修改的引用的节偏移;symbol:标识被修改引用应该指向的符号;type:重定位类型,告知链接器如何修改新的引用;attend:一些重定位要使用它对被修改引用的值做偏移调整。ELF定义了32种不同的重定位类型,两种最基本的重定位类型包括R_X86_64_PC32(重定位使用32位PC相对地址的引用)和R_X86_64_32(重定位使用32位绝对地址的引用)。

 

5.4 hello的虚拟地址空间

通过查看edb,看出hello的虚拟地址空间开始于0x400000,结束与0x400ff0

  

 

 

可以通过edb找到各个节的信息,比如.txt节,虚拟地址开始于0x400550,大小为0x132

 

5.5 链接的重定位过程分析

     经过反编译后部分如下图:

    

 

 

通过分析hellohello.o的不同,说明链接的过程。可以发现以下不同的地方:

  1. hello反汇编的代码有确定的虚拟地址,也就是说已经完成了重定位,hello.o反汇编代码中代码的虚拟地址均为0,未完成可重定位的过程。
  2. hello反汇编的代码中多了很多的节以及很多函数的汇编代码,如上图中的printf等等。
  3. 函数调用:hello中没有hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存空间地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,所以在.rela.text节中为其添加了重定位条目。
  4. 链接增加了节:hello中增加了.init节和.plt节和一些节中定义的函数。

5.6 hello的执行流程

使用edb打开hello

 

得到各程序地址

 

5.7 Hello的动态链接分析

5.8 本章小结

本章我主要是重新认真回顾了以往实验中在Linux系统中进行文件链接的过程。同时通过查看hello的虚拟地址空间,并且对比hello与hello.o的反汇编代码,我更充分地掌握了链接与重定位过程,了解到hello会在运行时要求动态链接器加载和链接到某个共享库,从而无需在编译时将那些库重新再链接到应用中,这为程序编写以及利用动态链接进行版本管理提供了一定的便利。

6章 hello进程管理

6.1 进程的概念与作用

进程的概念:进程是执行中程序的抽象,进程不只是程序代码,还包括当前活动,例如PC、寄存器的内容、堆栈、数据段,还可能包含堆等等。

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

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

shell的作用:shell最重要的功能是命令解释,可以说shell是一个命令解释器。Linux系统上的所有可执行文件都可以作为shell命令来执行,同时它也提供一些内置命令。此外,shell还包括通配符、命令补全、命令历史、重定向、管道、命令替换等很多功能。

shell的处理流程:从终端读入输入的命令行->解析输入的命令行,获得命令行指定的参数->检查命令是否是内置命令,如果是内置命令则立即执行,否则在搜索路径里寻找相应的程序,找到该程序就执行它。

6.3 Hello的fork进程创建过程

输入命令执行hello之后,父进程如果判断出不是内部指令,就会通过fork()函数来创建子进程。子进程与父进程十分的近似,并得到一份与父进程用户级虚拟空间相同且独立的副本(包括数据段、代码、共享库、堆以及用户栈)。父进程打开的文件,子进程也可读写。二者间最明显的区别是pid的不同:fork()函数只会被调用一次,但会返回两次。在父进程中,fork返回子进程的pid;在子进程中,fork返回0。

6.4 Hello的execve过程

当创建了一个子进程之后,子进程调用exceve函数在当前子进程的上下文加载并运行一个新的程序即hello程序,加载并运行需要以下几个步骤:

  1. 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。
  2. 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
  3. 映射共享区域。如果hello程序与共享对象链接,比如标准Clibc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
  4. 设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

6.5 Hello的进程执行

当子进程调用exceve()函数在上下文中加载并运行hello程序后,hello程序不会立即运行,需要内核调度它。进程调度是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行时,就说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,使用一种称为上下文切换的机制来将控制转移到新的进程。上下文切换包括:保存当前进程的上下文;恢复某个先前被抢占的进程被保存的上下文;将控制传递给这个新恢复的进程。其中上下文指的是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构(页表、进程表、文件表等)。

处理器提供了一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。通常用某个控制寄存器的一个模式位来提供这种机制,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程运行在内核模式中,进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置;没有设置模式位时,进程运行在用户模式中,进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据,否则会导致保护故障。运行应用程序代码的进程初始时在用户模式中,进程需要通过中断、故障或者陷入系统调用这样的异常才能从用户模式变为内核模式。

由于负责进程调度的是内核,因此内核调度需要运行在内核模式下。当内核代表用户执行系统调用时,可能会发生上下文切换,中断也可能引发上下文切换。同时,系统通过某种产生周期性定时器中断的机制判断当前进程已经运行了足够长的时间,并切换到一个新的进程。

以hello的进程执行为例。当子进程调用exceve()函数在上下文中加载并运行hello程序后,hello进程等待内核调度它。当内核决定调度hello进程时,它就抢占当前进程,进行上下文切换,将控制转移到hello进程,并从内核模式变为用户模式,这时hello进程开始运行应用程序代码。当hello进程调用sleep时,由于sleep是系统调用,进程陷入内核模式。这时hello进程被挂起,内核会选择调度其他进程,通过上下文切换保存hello进程的上下文,将控制传递给新调度的进程。定时器的时间到了后会发送中断信号,进入内核模式,将挂起的hello进程变成运行状态,这时hello进程就可以等待内核调度它。当内核再次调度hello进程时,恢复保存的hello进程的上下文,就可以从刚才停止的地方继续执行了。当hello调用getchar的时候同样会陷入内核模式,由于getchar需要来自键盘的DMA传输,时间很长,因此内核不会等待DMA完成,而是去调度其他进程。当DMA完成后,会向处理器发送中断信号,进入内核模式,内核知道DMA完成了,就可以再次调度hello进程了。

6.6 hello的异常与信号处理

hello程序出现的异常可能有:

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

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

故障:在执行hello程序的时候,可能会发生缺页故障。

终止:终止时不可恢复的错误,在hello执行过程可能会出现DRAM或者SRAM位损坏的奇偶错误。

  1. 陷阱

在运行sleep时会挂起特定时间之后再运行。

 

   2.中断

由于键盘输入引起中断,例如输入Ctrl+z。

 

通过ps查看可以看到hello处于被挂起的状态,并没有被回收,使用f让其继续执行玩剩下的进程。

 

6.7本章小结

     本章介绍了进程的概念和作用,简述shell的工作过程,并分析了使用fork+execve加载运行hello,执行hello进程以及hello进程运行时的异常/信号处理过程。

7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

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

逻辑地址由段标识符和段内偏移量两部分组成。段标识符由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号,是对段描述符表的索引,每个段描述符由8个字节组成,具体描述了一个段。后3位包含一些硬件细节,表示具体是代码段寄存器还是栈段寄存器还是数据段寄存器等。通过段标识符的前13位,可以直接在段描述符表中索引到具体的段描述符。每个段描述符中包含一个Base字段,它描述了一个段的开始位置的线性地址。将Base字段和逻辑地址中的段内偏移量连接起来就得到转换后的线性地址。

对于全局的段描述符,放在全局段描述符表中,局部的(每个进程自己的)段描述符,放在局部段描述符表中。全局段描述符表的地址和大小存放在gdtr控制寄存器中,而局部段描述符表存放在ldtr寄存器中。

给定逻辑地址,看段选择符的最后一位是0还是1,用于判断选择全局段描述符表还是局部段描述符表。再根据相应寄存器,得到其地址和大小。通过段标识符的前13位,可以在相应段描述符表中索引到具体的段描述符,得到Base字段,和段内偏移量连接起来最终得到转换后的线性地址。

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

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

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

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

MMU利用页表实现从虚拟地址到物理地址的变换。CPU中的一个控制寄存器,页表基址寄存器指向当前页表。n位的虚拟地址包含一个p位的虚拟页面偏移VPO和一个n-p位的虚拟页号VPN。MMU利用VPN选择适当的PTE,如果这个PTE设置了有效位,则页命中,将页表条目中的物理页号和虚拟地址中的VPO连接起来就得到相应的物理地址。否则会触发缺页异常,控制传递给内核中的缺页异常处理程序。缺页处理程序确定物理内存中的牺牲页,调入新的页面,并更新内存中相应PTE。处理程序返回到原来的进程,再次执行导致缺页的指令,MMU重新进行地址翻译,此时和页命中的情况一样。同时,也可以利用TLB缓存PTE加速地址的翻译。

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

TLB的支持:在MMU中包括一个关于PTE的缓存,称为翻译后备缓冲器(TLB)。TLB是一个小的、虚拟寻址的缓存,每一行保存着一个由单个PTE组成的块。由于VA到PA的转换过程中,需要使用VPN确定相应的页表条目,因此TLB需要通过VPN来寻找PTE。和其他缓存一样,需要进行组索引和行匹配。如果TLB有2t个组,那么TLB的索引TLBI由VPN的t个最低位组成,TLB标记TLBT由VPN中剩余的位组成。

当MMU进行地址翻译时,会先将VPN传给TLB,看TLB中是否已经缓存了需要的PTE,如果TLB命中,可以直接从TLB中获取PTE,将PTE中的物理页号和虚拟地址中的VPO连接起来就得到相应的物理地址。这时所有的地址翻译步骤都是在芯片上的MMU中执行的,因此非常快。如果TLB不命中,那和7.3中描述的过程类似,需要从cache或者内存中取出相应的PTE。

四级页表的支持:多级页表可以用来压缩页表,对于k级页表层次结构,虚拟地址的VPN被分为k个,每个VPNi是一个到第i级页表的索引。当1sjsk-1时,第j级页表中的每个PTE指向某个第j+1级页表的基址。第k级页表中的每个PTE和未使用多级页表时一样,包含某个物理页面的PPN或者一个磁盘块的地址。对于Intel Core i7,使用了4级页表,每个VPNi有9位。当TLB未命中时,36位的VPN被分为VPN1、VPN2、VPN3、VPN4,每个VPNi被用作到一个页表的偏移量。CR3寄存器包含L1 页表的物理地址,VPN1提供到一个L1 PTE的偏移量,这个PTE包含某个L2页表的基址。VPN2提供到这个L2页表中某个PTE的偏移量,以此类推。最后得到的L4 PTE包含了需要的物理页号,和虚拟地址中的VPO连接起来就得到相应的物理地址。

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

CPU发送一条虚拟地址,随后MMU按照7.4的上述操作获得了物理地址PA。根据Cache组数的大小要求将PA分为CT(标记位)、CS(组号)以及CO(偏移量)。根据CS寻找到正确的组,比较每一个Cacheline的标记位是否有效以及CT是否相等,如果命中就直接返回想要的数据,如果未命中,就依次去L2、L3或者主存来判断是否命中:当命中时,将数据传给CPU同时更新各级Cache的Cacheline(如果Cache已满就采用换入换出策略)

7.6 hello进程fork时的内存映射

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

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时写时复制机制就会创建新页面,为每个进程保持了私有地址空间的抽象概念。同时延迟私有对象中的副本直到最后可能的时刻,充分利用了稀有的物理内存。

7.7 hello进程execve时的内存映射

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

  1. 删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存 在的区域结构。
  2. 映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结 构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域是请求二进制零的,映射到匿名文件,其大小包含在 hello 中,栈和堆地址也是请求二进制零的,初始长度为零。
  3. 映射共享区域, hello 程序与共享对象 libc.so 链接,libc.so 是动态链 接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
  4. 设置程序计数器(PC),execve 做的最后一件事情就是设置当前进程 上下文的程序计数器,使之指向代码区域的入口点。

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

缺页故障的产生:CPU产生一个虚拟地址给MMU,MMU经过一系列步骤获得了相应的PTE,当PTE的有效位未设置时,说明虚拟地址对应的内容还没有缓存在内存中,这时MMU会触发缺页故障。

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

7.9动态存储分配管理

由于上课没有深入讲解,可以参考https://blog.csdn.net/weixin_45406155/article/details/103775420

7.10本章小结

本章主要介绍了hello的存储器的地址空间,介绍了四种地址空间的差别和地址的相互转换。同时介绍了hello的四级页表的虚拟地址空间到物理地址的转换。阐述了三级cashe的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

结论

hello所经历的过程:

源程序:在文本编辑器或IDE中编写C语言代码,得到最初的hello.c源程序。

预处理:预处理器解析宏定义、文件包含、条件编译等,生成ASCII码的中间文件hello.i。

编译:编译器将C语言代码翻译成汇编指令,生成一个ASCI1汇编语言文件hello.s。

汇编:汇编器将汇编指令翻译成机器语言,并生成重定位信息,生成可重定位目标文件hello.o。

链接:链接器进行符号解析、重定位、动态链接等创建一个可执行目标文件hello。此时,hello才真正地可以被执行。

fork创建进程:在shell中运行hello程序时,shell会调用fork函数创建子进程,供之后hello程序的运行。

execve加载程序:子进程中调用execve函数,加载hello程序,进入hello的程序入口点,hello终于要开始运行了。

运行阶段:内核负责调度进程,并对可能产生的异常及信号进行处理。MMU、TLB、多级页表、cache、DRAM内存、动态内存分配器相互协作,共同完成内存的管理。Unix 1/O使得程序与文件进行交互。

终止:hello进程运行结束,shell负责回收终止的hello进程,内核删除为hello进程创建的所有数据结构。hello的一生到此结束,没有留下一丝痕迹。

感悟:计算机系统时一个软硬件相互结合的一个精密的系统,其中包含很多巧妙的设计思想和优化思想,计算机系统的设计思想和实现都是基于抽象实现的。从最底层的信息的表示用二进制表示抽象开始,到实现操作系统管理硬件的抽象:进程是对处理器、主存和I/O设备的抽象。虚拟内存是对主存和磁盘设备的抽象。文件是对I/O设备的抽象。

附件

文件的名字

文件的作用

hello.i

经过预处理后的hello源程序文本文件

hello.s

经过编译之后产生的汇编文件文本文件

hello.o

经过汇编之后产生的可重定位目标文件二进制文件

hello

通过链接产生的可执行目标文件二进制文件

elf.txt

hello.oELF格式文本文件

Disas_hello.s

hello.o的反汇编代码文本文件

hello1.elf

helloELF格式文本文件

hello1_objdump.s

hello的反汇编代码文本文件

参考文献

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

[2]   https://blog.csdn.net/lxcshax/article/details/117875520

[3]   https://blog.csdn.net/weixin_45406155/article/details/103775420

[4]   https://blog.csdn.net/weixin_52049697/article/details/124873644

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值