程序人生-Hello’s P2P

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业   计算机科学与技术       

学     号   2021113301             

班   级   21W0312               

学       生   刘颜硕           

指 导 教 师   史先俊              

计算机科学与技术学院

2022年5月

摘  要

本文简要概述了一个hello.c程序,在Linux系统下如何预处理、编译、汇编、链接生成可执行文件的全过程,借助gcc、edb等工具全方面对hello的“一生”进行梳理,同时阐述进程管理、存储管理以及IO管理,站在程序员的视角深入理解计算机系统。

关键词:计算机系统;程序;进程;汇编;存储                       

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第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.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

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动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

P2P:(From Program to Process)

首先编译器编写hello.c源程序Program,然后运行C预处理器(cpp)将hello.c翻译成一个ASCII码的中间文件hello.i,而后编译器(ccl)将hello.i翻译成一个ASCII汇编语言文件hello.s,接下来汇编器(as)将hello.s翻译成一个可重定位目标文件hello.o,在通过链接器与库函数链接,经过符号解析和重定位生成一个hello 的可执行文件。

020:(From Zero to Zero)

shell调用fork和execve后,生成子进程和加载运行程序。CPU通过取值、译码、执行、访存、写回、更新pc等操作,一条一条执行指令。运行结束后,父进程回收子进程,避免子进程称为僵死进程,删除与该进程相关的内容。

1.2 环境与工具

硬件环境

X64CPU、8G RAM

软件环境

Windows 11

开发工具

Ubuntu 20.04 LTS 64位、gcc、edb

1.3 中间结果

hello.c 源程序

hello.o 汇编后的可重定位目标执行文件

hello.i 预处理后的文件

hello.elf hello的可执行程序的ELF文件

hello 动态链接后的可执行文件

hello.s 编译后的汇编文件

hello1.elf hello.o的ELF文件

hello1.txt hello的反汇编代码

1.4 本章小结

本章简述了Hello的P2P、020的整个过程并介绍了实验的基本信息:环境、工具以及实验的中间结果。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

预处理的概念:

预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译,预处理中会展开以#起始的行,修改原始的C程序。将所引用的所有库展开,处理所有的条件编译,并执行所有的宏定义,得到另一个通常是以.i作为文件扩展名的C程序。

预处理的作用:

1.宏的替换。将宏名(#define定义的字符串)替换为实际值(可以是字符串、代码等)。

2.文件包含。将c程序中所有#include声明的头文件复制到新的程序中。将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件。

3.条件编译。根据#if以及#endif和#ifdef以及#ifndef来判断是否处理之后的代码。

2.2在Ubuntu下预处理的命令

图2.2 预处理命令 

2.3 Hello的预处理结果解析

图2.3 预处理结果 

结果解析:经过预处理后,hello.c被处理成为hello.i文件。打开文件后发现hello.i文件中文件内容大大增加,且仍为可阅读的c语言程序文本文件。hello.i文件对hello.c程序中的宏进行了宏展开,该文件包含了头文件中的内容。如果代码中有#define命令还会对相应符号进行替换。

2.4 本章小结

本章介绍了预处理的概念及作用,在Ubuntu中将hello.c预处理为目标文件hello.i,并检查了hello.i文本。使我对程序预处理有了更深的理解。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译的概念:

编译就是将源语言经过词法分析、语法分析、语义分析以及经过一系列优化后生成汇编代码的过程。这儿的编译是指从.i到.s即预处理后的文件到生成汇编语言程序。

编译的作用:

编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。

1.词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。

2.编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,如表达式、赋值、循环等,最后看是否构成一个符合要求的程序,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构,程序是最终的一个语法单位。编译程序的语法规则可用上下文无关文法来刻画。

3.中间代码是源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码,即为中间语言程序,中间语言的复杂性介于源程序语言和机器语言之间。

4.代码优化是指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。所谓等价,是指不改变程序的运行结果。所谓有效,主要指目标代码运行时间较短,以及占用的存储空间较小。这种变换称为优化。

5.目标代码生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。    

3.2 在Ubuntu下编译的命令

图3.2 编译命令

3.3 Hello的编译结果解析

图3.3 编译结果 

3.3.1汇编初始部分

.file    声名源文件

.text    代码节

.section .rodata  只读数据节

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

.string   声名一个字符串

.globle   声名全局变量

.type    声名一个符号是函数类型还是数据类型

.size     声名大小

3.3.2 数据

1.字符串

图3.3.2.1 字符串 

程序中有两个字符串 只读数据节

                                                         图3.3.2.2 编译得到字符串

在argv[]中,数组的每个元素都是一个字符串,数组的起点存放在栈中(%rbp)的位置,被两次调用参数传给printf函数,因此,这两个字符串作为printf的参数。

  1. 参数argv[]

参数argv时作为用户传给main函数的参数,被放置在堆栈中,数组中每个元素都是一个字符串,在内存中两次被printf函数调用。

  1. 局部变量

main函数在for循环中声明了一个局部变量i,编译器进行编译的时候将局部变量放在堆栈中,i被放在-4(%rbp)的位置。

 图3.3.2.3 编译得到局部变量

  1. 立即数

通常把在立即寻址指令中给出的数称为立即数,立即数直接体现在汇编代码中。

3.3.3全局函数

图3.3.3 编译得到全局函数 

hello.c声名了一个函数int main(int argc,char *argv[]),由上图可知,该函数为全局函数。

3.3.4赋值操作

hello.c中的赋值操作为i=0,在汇编语言中赋值操作用mov指令来实现。

 图3.3.4 编译得到赋值操作

3.3.5算术操作

hello.c中的算术操作为i++,i为int类型,在汇编代码中用addl实现

 图3.3.5 编译得到算术操作

3.3.6关系操作

hello.c中if(argc!=4),这是一个条件判断语句,在进行编译时,被编译为:

图3.3.6.1 编译得到if操作 

在判断后设置判断,根据条件判断是否跳转。

hello.c中for循环的循环条件i<8,在进行编译时,被编译为:

图3.3.6.2 编译得到for条件判断操作 

同样在判断后设置判断,根据条件判断是否跳转。

3.3.7函数操作

hello.c中涉及的函数主要有:main、printf、exit、sleep、getchar函数。所有函数的返回值都会存储在%eax寄存器中,函数的调用在汇编语言中,被编译为call,如:

 图3.3.7 编译得到函数操作

3.3.8类型转换

hello.c中的atoi函数将字符串转换为整形。

3.4 本章小结

本章主要介绍了编译的概念及作用,在Ubuntu中编译的指令及结果,详细分析hello.s中各数据类型、操作和函数如何实现。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编的概念:

汇编器as,将.s文件翻译成机器指令,也即.o文件,这一过程称为汇编,同时这个.o文件也是可重定位目标文件。

汇编的作用:

将编译器产生的汇编语言进一步翻译为计算机可以理解的机器语言,生成.o文件。

4.2 在Ubuntu下汇编的命令

 图4.2 汇编命令

4.3 可重定位目标elf格式

图4.3.1 得到可重定位目标ELF文件命令 

 图4.3.2 ELF文件内容 

分析hello.elf中的内容

4.3.1ELF头

ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。

4.3.2重定位节

重定位节:重定位节保存的是.text节中需要被修正的信息(任何调用外部函数或者引用全局变量的指令都需要被修正),调用外部函数的指令和引用全局变量的指令需要重定位,调用局部函数的指令不需要重定位。Hello程序中需要被重定位的有printf、puts、exit、sleep、sleepseces、getchar和.rodata中的.L0和.L1。

.rela.eh_frame节是.eh_frame节的重定位信息。

4.3.3符号表

.symtab,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息,一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。

4.4 Hello.o的结果解析

图4.4.1 反汇编指令 

 图4.4.2 反汇编文件

结果分析:

4.4.1分支转移

hello.s

图4.4.3 hello.s文件条件转移

hello1.txt

图4.4.4 反汇编文件条件转移

反汇编代码跳转时使用的不是段名称,在汇编成机器语言中不存在段名称,而是确定的地址。

4.4.2对函数的调用与重定位条目对应

hello.s

图4.4.5 hello.s文件对函数的调用与重定位

hello.txt

图4.4.6 反汇编文件对函数的调用与重定位

在可重定位文件中call后面是一条重定位条目指引的信息,不再是一个函数名。在反汇编文件中call后面直接加的是偏移量。

4.4.3立即数转变

hello.s

图4.4.7  hello.s文件立即数转变

hello1.txt

图4.4.8  反汇编文件立即数转变

在反汇编文件中,立即数以16进制的格式出现。

4.5 本章小结

本章介绍了汇编的概念及作用,在Ubuntu中得到汇编文件。此外,对反汇编文件和hello.s文件进行对比,加深理解。

(第4章1分)


5链接

5.1 链接的概念与作用

1.链接的概念:

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

2.链接的作用:

链接器使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,更便于我们维护管理,可以独立的修改和编译我们需要修改的小的模块。

5.2 在Ubuntu下链接的命令

图5.2 链接的指令

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

    

图5.3.1 得到可执行目标文件hello的指令

ELF头节表:储存节头表在文件的偏移、每条节目大小、条目的数目等

图5.3.2 ELF头内容

节头:存储节名在字符串节中下表、根据节的内容和语义对节进行分类、标记是否可读可写可执行,以及是否在内存中分配内存等信息。

图5.3.3 节头内容

重定位节:符号在链接时,确定每个符号定义的运行时内存地址,并修改对这些符号的引用,使之指向符号定义的运行时内存地址。

图5.3.4 重定位节内容

符号表:存储若干固定结构的符号信息。

图5.3.5 符号表内容

5.4 hello的虚拟地址空间

加载指令:

  图5.4.1 edb加载hello指令

   

 图5.4.2  edb加载hello内容

.text节对应的虚拟地址为:004004c0~00400630、.rodata节对应的虚拟地址为004006a0~004006c0、.data节对应的虚拟地址为00400370~004003c0。ELF格式文件,告诉来凝结其运行时加载的内容,并提供动态链接的信息,每个表项提供了各段在虚拟地址和物理地址空间的各方面信息。

5.5 链接的重定位过程分析

指令:

图5.5 反汇编指令

在hello的反汇编文件中新增了.init和.plt节,和一些节中定义的函数。在hello.c中调用的一些库函数被链接器链接到了hello文件中,通过反汇编代码就可以查看到这些新增函数,如puts、printf、getchar等函数。hello的跳转和函数地址都变成了虚拟内存地址,而在hello.o的反汇编代码中没有相应的虚拟内存地址。

图5.5.1 反汇编内容

重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。

在.rela.text节中添加了重定位条目。Hello.o中的代码段的起始地址全部为0,需要将其映射到对应的可执行文件的虚拟地址中,因此需要重定位,并且添加重定位条目。

图5.5.2 反汇编内容

5.6 hello的执行流程

加载指令:

图5.6.1 edb加载hello指令

图5.6.2 edb加载hello内容

开始执行:

_start   0x2d9d6000

_libc_start_main  0x2d9d6100

执行main:

_main    0x2d9d61e9

_printf   0x2d9d6040

_sleep    0x2d9d6080

_getchar 0x2d9d6050

退出:

_exit   0x2d9d6070

5.7 Hello的动态链接分析

程序链接时需要添加重定位记录,等待动态链接器处理。对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址。动态链接器使用过程链接表plt+全局偏移量表got实现函数的动态链接,got中存放函数目标地址,plt使用got中地址跳转到目标函数。

ELF中.got.plt节的内容如下图

图5.7.1 ELF中.got.plt节

edb执行init之前的地址

图5.7.2 edb执行init之前的地址

Edb执行init之后的地址

图5.7.2 edb执行init之后的地址

一开始地址的字节都为0,调用init函数之后GOT内容产生变化,指向正确的内存地址,下一次调用跳转时可以跳转到正确位置。

5.8 本章小结

本章介绍了链接的概念及作用,将目标文件hello.o链接为可执行文件,此外我们利用edb以及objdump指令对文件的链接、重定位以及动态链接有了更深的理解。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

进程的概念:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

进程的作用:

它提供一个假象,好像我们的程序独占地使用内存系统,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

Shell-bash的作用:是一种交互级的应用程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(command line interface,简写为 CLI)。Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。其次,Shell 是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用 Shell 命令写出各种小程序,又称为脚本(script)。这些脚本都通过 Shell 的解释执行,而不通过编译。最后,Shell 是一个工具箱,提供了各种小工具,供用户方便地使用操作系统的功能。

处理流程:

  1. 读取用户键盘输入的命令
  2. 分析命令行。若为内置命令,则立即执行
  3. 否则,调用fork()函数创建子进程,在调用execve()执行指定程序

6.3 Hello的fork进程创建过程

在终端输入./hello 2021113301 刘颜硕 1后,shell判断出不是内置命令,调用fork()函数创建子进程,在调用execve()执行指定程序。通过fork()创建的子进程与父进程PID不同,有着相同的用户及虚拟地址空间副本。

6.4 Hello的execve过程

 调用fork()之后,创建新的子进程,子进程会调用execve()函数,来运行hello程序,若成功调用则不用返回,否则返回-1。

  1. 删除已存在的用户区域,创建一组新的代码、数据、堆端,并初始化为0。
  2. 映射私有区域和共享区域,初始化新的代码和数据段。
  3. 设置程序计时器,exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

6.5 Hello的进程执行

操作系统内核使用一种叫做上下文切换的较高层形式的一场控制流来实现多任务。在进程进行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度,是由内核中称为调度器的代码处理的。

上下文切换的流程为:1.保存当前进程的上下文;2.恢复某个先前被抢占的进程被保存的上下文3.将控制流传递给这个新回复的进程。

当内核代表用户执行系统调用时,可能会发生上下文切换。如果系统调用因为等待某个时间发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程。如read系统调用需要访问磁盘,内核可以选择执行上下文切换,运行另一个进程;sleep系统调用显式地请求让调用进程休眠。

中断也可能引发上下文切换。所有系统都有某种产生周期性定时器中断的机制,每次发生定时器中断时,内核就能判定当前进程已经运行了足够长的时间,并切换到一个新的进程。

Shell使得用户可以有机会修改内核,所以需要设置一些防护措施来保护内核,如限制指令的类型和可以作用的范围。

hello程序的执行:在进程调用execve函数后,进程为hello程序分配了新的虚拟地址空间,最初hello运行在用户模式下,键盘输入hello  2021113301 刘颜硕 1 ,然后调用sleep函数进程进入内核进行信号处理,与其他进程交替占用CPU,实现进程的调度。

6.6 hello的异常与信号处理

异常种类:

中断     中断是异步发生的,是来自 外部处理器外部I/O 设备的信号的结果

陷阱      陷阱是有意的异常,是执行一条指令的结果,调用后也会返回到下一条指令

故障       故障是由错误情况引起的,它可能能够被故障处理程序修正

终止       终止是不可恢复的致命错误造成的结果,通常是一些硬件的错误

异常处理:

图6.6.1 异常处理

图6.6.2 故障处理

图6.6.3 陷阱处理

图6.6.4 中断处理

正常运行:

图6.6.5 hello正常运行

Ctrl-C终止,shell收到sigint信号,回收hello进程

图6.6.5 hello运行中键入Ctrl-C

回车 打印出空行,会存在缓冲区,在程序执行结束之前输出

图6.6.6 hello运行中键入回车

不停乱按  乱按只是将屏幕的输入缓存到 stdin,当 getchar 的时候读出一个’\n’结尾的字串(作为一次输入),其他字串会当做 shell 命令行输入

图6.6.7 hello运行中不停乱按

Ctrl-Z 程序终止并退出,是挂起前台的作业,hello进程并没有回收,而是运行在后台下

图6.6.8 hello运行中键入Ctrl-Z

Ctrl-Z    ps查看后台进程

图6.6.9 hello运行中键入Ctrl-Z后 输入ps

Ctrl-Z    pstree查看进程树

图6.6.10 hello运行中键入Ctrl-Z后 输入pstree

Ctrl-Z   fg 回到前台继续执行

图6.6.11 hello运行中键入Ctrl-Z后 输入fg

Ctrl-Z jobs  获取任务列表和任务的状态

图6.6.12 hello运行中键入Ctrl-Z后 输入jobs

Ctrl-Z kill 杀死进程

图6.6.13 hello运行中键入Ctrl-Z后 输入kill

6.7本章小结

本章介绍了进程和shell,通过对hello的分析了解hello的进程如何执行的,通过调用fork和execve,最后对异常和信号处理机制进行实操,加深理解。

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:

也叫做相对地址,是由程序产生的与段有关偏移地址,用来指定一个操作数或是一条指令的地址,其格式为:段地址:段偏移量。

线性地址:

地址空间是一个非负整数地址的有序集合,而如果地址空间中的整数时连续的,那么我们可以说它时一个线性地址空间。

虚拟地址:

程序运行在保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址,虚拟内存被组织为一个存放在磁盘上的N个连续的字节大小的单元组成的数组,其每个字节对应的地址称为虚拟地址。

物理地址:

计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组,其每个字节都被赋予一个唯一的地址,这个地址称为物理地址,物理地址同样也是计算机的硬件中的电路进行操作的地址。

对hello进行反汇编得到的每一节所在的地址均是逻辑地址的段偏移量部分,而段偏移量加上段基址即为虚拟地址,在这里虚拟地址与线性地址相同,而Linux的所有段基址均是0,所以此时逻辑地址与虚拟地址是相同的。

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

一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,表示具体的是代码段寄存器还是栈段寄存器或是数据段寄存器。

段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本组成方式是段号+段内偏移地址。

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

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

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

虚拟地址由虚拟页号和虚拟页偏移量组成,物理地址由物理页号和物理页偏移量组成。处理器生成一个虚拟地址,并将其传送给MMU ,MMU使用内存中的页表根据相应的虚拟页号生成PTE地址,根据PTE,我们知道虚拟页的信息,若虚拟页已缓存,且有效位是有效的。那么页表条目的物理页号和虚拟地址的虚拟页偏移量传来你得到相应的物理地址,若虚拟页是未缓存的,会触发缺页故障,调用缺页处理子程序,将磁盘中的虚拟页重新加载到内存中,再执行缺页的指令。

图7.3 页式管理示意图

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

CPU 产生虚拟地址 VA,VA 传送给 MMU,MMU 使用前 36 位 VPN 作为 TLBT(前 32 位)+TLBI(后 4 位)向 TLB 中匹配,如果命中,则得到 PPN (40bit)与 VPO(12bit)组合成 PA(52bit)。 如果 TLB 中没有命中,MMU 向页表中查询,CR3 确定第一级页表的起始地 址,VPN1(9bit)确定在第一级页表中的偏移量,查询出 PTE,如果在物理内存 中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查 询到 PPN,与 VPO 组合成 PA,并且向 TLB 中添加条目。如果查询 PTE 的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误。

图7.4 多级页表

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

CPU发送一条虚拟地址,在TLB和四级页表支持下变换成物理地址,将物理地址拆分成标记位、组索引、块偏移三部分。先在L1cache中通过组索引找到所在的组。把所在组中的所有行的标记位和物理地址中的标记位匹配,若匹配成功且行的有效位是1,则匹配成功。在相应行中通过物理地址的块偏移,即取出相应字节,将其返回CPU。若匹配不成功,向L2cache、L3cache、内存中继续匹配直到找到为止。匹配成功后,向上一级返回直至L1cache,若上一级中有空余位置就放到空余位置中,若没有,则驱逐一块内容,将目标块放到被驱逐的块位置上。

7.6 hello进程fork时的内存映射

当fork函数被shell进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。通过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 缺页故障与缺页中断处理

缺页故障:处理器将虚拟地址发送给MMU,MMU使用内存中的页表生成PTE地址,有效位为零, 因此 MMU 触发缺页异常。

缺页中断处理:选择一个牺牲页,换入新的页面并更新页表。返回原先的进程,继续执行缺页异常的指令,CPU重新访问虚拟内存的操作,页命中。

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆堆是一个不同大小块的集合,每个块由连续虚拟内存组成,分为已分配和未分配两种。未分配的块会保持状态直到被分配,已分配的块会供程序使用直到被释放。动态内存的分配一般分为显式空闲链表管理和隐式空闲链表管理两种。

显示空闲链表管理:

                                                   图7.9 堆数据结构

显示空闲链表是将空闲块组织为某种形式的显示数据结构。如图所示。堆被组织为一个双向空闲链表,在每个空闲块中,都包含一个前驱和后继的指针。此外,还需要一个边界标记,便于块合并。此时可以通过地址顺序或进出顺序管理链表,从而管理空闲空间。

隐式空闲链表管理:

所谓隐式空闲链表,对比于显式空闲链表,代表并不直接对空闲块进行链接,而是将对内存空间中的所有块组织成一个大链表,其中Header和Footer中的block大小间接起到了前驱、后继指针的作用。

7.10本章小结

本章介绍了存储器的地址空间,讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念。详细介绍了地址管理的段式管理和页式管理,还有进程fork和execve内存映射的内容,了解了缺页故障和缺页中断处理,最后介绍了动态内存分配管理的方式和策略。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:所有IO设备都被模型化为文件,所有的输入和输出都能被当做相应文件的读和写来执行。
设备管理:Unix I/O,允许Linux内核引出一个简单、低级的应用接口。

8.2 简述Unix IO接口及其函数

Unix I/O接口:

(1)打开文件。一个应用程序通过要求内核打开相应的文件,会访问一个I/O设备,内核返回一个描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。

(2)Linux Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。

(3)改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。

(4)读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

(5)关闭文件。内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放他们的内存资源。

Unix I/O函数:

打开函数:int open(char* filename,int flags,mode_t mode) ,进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。

关闭函数:int close(fd),关闭一个打开的文件,fd是需要关闭的文件的描述符,close返回操作结果。

读函数:ssize_t read(int fd,void *buf,size_t n),read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。

写函数:ssize_t wirte(int fd,const void *buf,size_t n),write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。

8.3 printf的实现分析

查看printf函数的函数体:

图8.3.1 printf函数

fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。arg获取第二个参数,即输出时格式化串对应的值。

查看vsprintf函数的代码:

图8.3.2 vsprintf函数

vsprintf函数将所有的参数内容格式化后存入buf,返回字符串的长度。

再printf中调用函数write(buf,i)将长度为i的buf输出,write函数如下:

图8.3.3 write函数

将栈中参数放入寄存器,ebx是第一个字符的地址,ecx是串的长度。

printf函数的实现:从vsprintf生成显示信息,显示信息传送到write系统函数,write函数再陷阱-系统调用 int 0x80或syscall。

[转]printf 函数实现的深入剖析 - Pianistx - 博客园

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

getchar()从缓存区中读入一行,并返回第一个字符,若输入不止一个字符,则保存在缓冲区,等待后续getchar调用。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章介绍了Linux的设备管理方法,讲述了Unix I/O接口及其函数,从底层函数分析了printf函数和getchar()的实现。

(第8章1分)

结论

初学C语言时只是了解函数的功能,不知道源文件如何编译执行,在系统中如何储存、运行。在学习深入理解计算机系统后,从hello的一生,使我对计算机有了更深的理解和更浓厚的兴趣。

我们从键盘敲入代码,写下hello.c文件,通过预处理器(cpp)变成hello.i,再经过编译器(cc1)变成hello.s,在经过汇编器(as)生成hello.o,最后通过链接器(ld)生成可执行程序。

此外我们通过shell,让hello成为一个运行中的进程,shell接受到命令后,通过fork创建新的子进程,内核为其分配一段虚拟内存,再通过execve来运行hello,当hello开始运行时,虚拟内存缺页异常,进行缺页异常处理,为其分配物理地址,时hello正常运行。

在hello结束后,父进程回收子进程,从主存删除与其相关的所有数据。

Hello的一生短暂而丰富,使我感受到了计算机系统的精巧与复杂,我将付出更多努力深入理解。

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


附件

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

hello.c 源程序

hello.o 汇编后的可重定位目标执行文件

hello.i 预处理后的文件

hello.elf hello的可执行程序的ELF文件

hello 动态链接后的可执行文件

hello.s 编译后的汇编文件

hello1.elf hello.o的ELF文件

hello1.txt hello的反汇编代码

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


参考文献

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

[1]  林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.

[2]  辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.

[3]  赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).

[4]  谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.

[5]  KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.

[6]  CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值