[无标题]

摘 要
本文主要讲述了hello.c程序在编写完成后运行在linux中的生命历程,借助相关工具分析预处理、编译、汇编、链接等各个过程在linux下实现的原理,分析了这些过程中产生的文件的相应信息和作用。
关键词: 预处理;编译;汇编;链接;shell;虚拟内存;IO;存储;

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

目 录

第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章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
在这里插入图片描述

1.程序员通过编辑器编写hello.c文件,得到hello.c的源程序。
2.预处理阶段:预处理器(cpp)将hello.c进行预处理修改原始的C程序生成hello.i文件(另一个C程序)。
3.编译阶段:编译器(cc1)将hello.i进行翻译生成文本文件hello.s,它包含一个汇编语言程序。
4.汇编阶段:汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一个可重定位目标文件hello.o。
5.链接阶段:链接器ld将hello.o和系统目标文件组合起来,创建了一个可执行目标文件hello。
6.通过shell输入./hello,shell通过fork函数创建新的进程,然后调用了execve对虚拟内存进行映射,通过mmap为hello开辟一片空间。
7.中央处理器CPU从虚拟内存中的.text,.data截取代码和数据,调度器为进程规划时间片,在发生异常时触发异常处理子程序。

1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:Intel® Core™ i7-10875H CPU 2.30 GHz;16GRAM;1024Disk
软件环境:Windows10 64位;Vmware 16;Ubuntu 20.04 LTS 64位
工具:codeblocks;gdb;Objdump;HexEdito

1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

1.4 本章小结
本章介绍了hello程序“一生”的过程,以及进行实验时的软硬件环境及开发与调试工具等基本信息。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:预处理器(cpp)根据以字符”#”开头的命令,修改原始的C程序。
作用:根据源代码中的预处理指令修改源代码,预处理从系统头文件中将头文件的源码插入到目标文件中,结果得到另一个C程序,通常以.i作为文件扩展名。

2.2在Ubuntu下预处理的命令
Linux中预处理hello.c文件的命令是:gcc -E -o hello.i hello.c

在这里插入图片描述

2.3 Hello的预处理结果解析

在这里插入图片描述在这里插入图片描述

以上为hello.i的部分截图。经过预处理后,hello.c被处理成为hello.i文件。打开文件后发现hello.i文件中文件内容增加,且仍为可阅读的c语言程序文本文件。hello.i文件对hello.c程序中的宏进行了宏展开,该文件包含了头文件中的内容。如果代码中有#define命令还会对相应符号进行替换。
2.4 本章小结
本章介绍了预处理的相关概念和作用,进行实际操作查看了hello.i文件,是对源程序进行补充和替换。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
概念:编辑器将文本文件hello.i翻译成一个文本文件即汇编语言程序hello.s。
作用:把源程序翻译成目标程序,进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信息。

3.2 在Ubuntu下编译的命令
Linux下编译的命令:gcc -S hello.i -o hello.s
在这里插入图片描述

3.3 Hello的编译结果解析

在这里插入图片描述

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1汇编初始部分
.file:声明源文件
.text:代码节
.section.data:只读数据段
.globl:声明全局变量
.type:声明一个符号是函数类型还是数据类型
.size:声明大小
.string:声明一个字符串
.align:声明对指令或者数据的存放地址进行对齐的方式

3.3.2 数据
(1)字符串
在这里插入图片描述

程序中有两个字符串,这两个字符串都在只读数据段中,如上图所示。
hello.c中的数组是作为main函数的第二个参数,数组的每个元素都是一个指向字符类型的指针。数组的起点存放在栈中-32(%rbp)的位置,被两次调用找参数传给printf函数。

printf的参数即为这两个字符串。
(2)局部变量
在这里插入图片描述在这里插入图片描述

如上图所示,main函数声明了一个局部变量i,编译器进行编译的时候将局部变量i放入堆栈中。i被放置在栈中-4(%rbp)的位置。
(3)参数argc
参数argc用于传递给main函数的参数,被放置于堆栈之中。
(4)数组char* argv[]

argv[]是main函数的第二个参数,数组的起始地址存放在-32(%rbp)的位置,数组中存放的元素是指向字符类型的指针,在内存中被两次调用传递给printf函数。
(5)立即数
立即数直接可以出现在汇编代码当中。
3.3.3全局函数
在这里插入图片描述

声明全局函数。
3.3.4赋值
在.s文件中汇编代码主要使用mov指令来实现赋值。
mov指令根据操作数的字节大小(b,w,l,q)分为:
movb:一个字节赋值,
movw:两个字节赋值,
movl:四个字节赋值,
movq:八个字节赋值。
3.3.5算术操作
在这里插入图片描述

上图通过addl指令实现int型变量i的算术操作i++。
3.3.6关系操作
在这里插入图片描述

上图所示是if条件判断语句对应的汇编代码。
hello.c中 “if(argc!=4)”在进行编译时,
被编译为:cmpl $4, -20(%rbp)。
比较后设置条件码,根据条件码判断是否需要跳转,上图为如果相等则跳转 至.L2。
在这里插入图片描述

hello.c源程序中的for循环条件是for(i=0;i<8;i++),
该条指令被编译为cmpl $7, -4(%rbp)。
同样在判断后设置条件码,为下一步的jle利用条件码跳转做准备,上图为i 小于或等于7时跳转至.L3。
3.3.7控制转移指令
汇编语言中设置了条件码,然后根据条件码来进行控制程序的跳转。
在这里插入图片描述

上图代码含义为判断argc是否等于4。如果等于4,则不执行if语句;反之if不等于4,则执行后续的语句
在这里插入图片描述

上图汇编代码是在for循环中的判断
先对i进行赋初值,然后无条件跳转至判断条件的.L3中,然后判断i是否符合循环的条件,若符合则直接跳转到.L4中。
3.3.8函数操作
函数的调用与传递参数的过程是:
给函数传递参数需要先设定一个寄存器,将参数传给这个设定的寄存器后, 再通过call来跳转到调用的函数开头的地址。
3.3.9类型转换
int、float、double、short、char各种类型变量之间可以进行相互转化。
hello.c中的atoi(argv[3])将字符串类型转换为整型。

3.4 本章小结
本章介绍了编译器处理c语言程序的过程,编译器分别从c语言的数据,赋值语句,类型转换,算术操作,逻辑/位操作,关系操作,控制转移与函数操作各个方面进行分析,将C语言转化为汇编语言。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
概念:汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程,汇编器(as)将.s汇编程序翻译成机器语言并将这些指令打包成可重定目标程序的格式存放在.o目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。
作用:将汇编语言翻译成机器语言,使其在链接后能够被机器识别并执行。
4.2 在Ubuntu下汇编的命令
gcc -c -o hello.o hello.s
在这里插入图片描述在这里插入图片描述

4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
在这里插入图片描述

在linux下生成hello.o文件的elf格式命令:readelf -a hello.o > hello.elf
在这里插入图片描述在这里插入图片描述

分析:
在这里插入图片描述

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

2)节头:记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。
在这里插入图片描述

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

4)符号表(.symtab):它存放在程序中定义和引用的函数和全局变量的信息,一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

objdump -d -r hello.o>hello1.txt
在这里插入图片描述在这里插入图片描述

hello1.txt与hello.s的差异:
1)分支转移
hello.s:
在这里插入图片描述

hello1.txt:
在这里插入图片描述

反汇编代码跳转指令的操作数使用的不是段名称,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。
2)函数的调用与重定位
hello.s:
在这里插入图片描述

hello1.txt:
在这里插入图片描述

在编译文件中,立即数全部是以16进制表示的,因为16进制与2进制之间的转换更加方便,所以都转换成了16进制。

4.5 本章小结
本章介绍了hello.s汇编到hello.o的过程。我们查看了hello.o的可重定位目标文件的格式,使用反汇编查看hello.o经过反汇编过程生成的代码并且把它与hello.s进行比较,观察分析他们之间的差异,分析和阐述了从汇编语言进一步翻译成为机器语言的汇编过程。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
注意:这儿的链接是指从 hello.o 到hello生成过程。
概念:链接是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。
作用:链接器在软件开发过程中扮演着一个关键的角色,因为它们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其它文件。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
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的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
命令:readelf -a hello > hello1.elf

在这里插入图片描述

ELF头:hello的文件头和hello.o文件头的不同之处如下图标记所示,hello是一个可执行目标文件,有27个节。
在这里插入图片描述

节头:对 hello中所有的节信息进行了声明,包括大小和偏移量。
在这里插入图片描述

上图是重定位节.rela.text

在这里插入图片描述

上图是符号表.symtab

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

在这里插入图片描述

分析程序头LOAD可加载的程序段的地址为0x400000。

在这里插入图片描述

在0x401000~0x402000段中,程序被载入,虚拟地址0x401000开始,到0x401ff0结束,根据5.3中的节头部表,可以通过edb找到各个节的信息,比如.txt节,虚拟地址开始于0x4010f0,大小为0x145.

5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
命令: objdump -d -r hello > hello2.txt
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

hello和hello.o相比,首先多了很多经过重定位之后的函数,如_init、puts@plt等,hello.o在.text段之后只有一个main函数;hello.o的地址是从0开始的,是相对地址,而hello的地址是从0x401000(_init的地址)开始的,是已经进行重定位之后的虚拟地址;在hello的main函数中,条件跳转指令和call指令后均为绝对地址,而hello.o中是相对于main函数的相对地址。
在这里插入图片描述

链接器完成的两个主要任务:符号解析和重定位。
重定位由两步组成:1. 重定位节和符号定义。 2. 重定位节中的符号引用。
如对puts函数的重定位:
在hello.o反汇编代码中,该行二进制编码为e8 00 00 00 00
addr(text)= 0x401105
refaddr = addr(text)+offset = 0x401126,即引用运行时的地址
addr(r.symbol) = addr(puts) = 0x401080
然后更新该引用,*refptr = (unsigned) (addr(r.symbol) + r.addend - refaddr)
= (unsigned) (0x401080 +(-4) – 0x401126) = (unsigned) (-aa) = ff ff ff 56
将其以小段序填入可得 56 ff ff ff ,与反汇编代码一致。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
①开始执行:_start、_libc_start_main

②执行main:_main、_printf、_exit、_sleep、_getchar

③退出:exit

程序名 程序地址

_start 0x4010f0

_libc_start_main 0x2f12271d

main 0x401125

_printf 0x401040

_exit 0x401070

_sleep 0x401080

_getchar 0x401050

详细过程:

对hello进行gdb调试,用rbreak命令在每个函数入口处打上断点:
在这里插入图片描述

然后执行run和continue命令,查看整个程序进行的所有过程

在这里插入图片描述

5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

首先在elf文件中找到.got的地址0x403ff0
在这里插入图片描述

在edb找到相应地址处,并且在dl_init处设置断点,分析在dl_init前后该地址附近变化:
dl_init前:
在这里插入图片描述

dl_init后:
在这里插入图片描述

PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,跳转到动态链接器中。每个条目都负责调用一个具体的函数。PLT[[1]]调用系统启动函数 (__libc_start_main)。从PLT[[2]]开始的条目调用用户代码调用的函数。
GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[0]和GOT[[1]]包含动态链接器在解析函数地址时会使用的信息。GOT[[2]]是动态链接器在ld-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。

5.8 本章小结
本章主要介绍了链接的过程,利用edb、gdb、objdump等工具对链接的ELF文件、虚拟地址空间、重定位过程等进行详细分析,同时对hello程序的执行流程以及动态链接也做了简要介绍。至此,hello程序已经从程序代码转换成了一个可执行文件。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
作用:进程为程序提供了一种假象,程序好像是独占的使用处理器和内存。处理器好像是无间断地一条接一条地执行我们程序中的指令。
6.2 简述壳Shell-bash的作用与处理流程
Shell是指为使用者提供操作界面的软件(命令解析器)。它接受用户命令,然后调用相应的应用程序。Linux系统中所有的可执行文件都可以作为Shell命令来执行。
处理流程:首先对用户输入的命令进行解析,判断命令是否为内置命令,如果为内置命令,调用内置命令处理函数;如果不是内置命令,就创建一个子进程,将程序在该子进程的上下文中运行。判断为前台程序还是后台程序,如果是前台程序则直接执行并等待执行结束,如果是后台程序则将其放入后台并返回。同时Shell对键盘输入的信号和其他信号有特定的处理。
6.3 Hello的fork进程创建过程
父进程通过调用fork函数创建一个新的运行的子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。父进程和子进程之间的最大区别是他们有不同的PID。在父进程中,fork返回子进程的PID。在子进程中,fork返回0,返回值提供一个明确的方法来分辨程序是父进程还是在子进程中执行。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个程序。
int execve(const char *filename, const char *argv[], const char envp[]);
execve函数加载并运行可执行文件filename,且带参数列表argv和环境变量列表envp,execve调用一次并从不返回。
当main程序开始执行时,用户栈的组织结构如下图所示:
在这里插入图片描述

6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

进程为每个程序提供了一种假象,好像程序在独占的使用处理器。如图每个竖直的条表示一个进程的逻辑控制流的一部分。
在这里插入图片描述

一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流称为并发地运行。进程也为每个程序提供一种假象,好像它独占地使用系统地址空间。
处理器通常使用某个控制寄存器中的一个模式位来提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存的位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,也不允许直接引用地址空间中内核区的代码和数据。
运行应用程序的代码的进程开始处于用户模式中。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式改为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式改回用户模式。
内核为每个进程维持一个上下文。它由一些对象的值组成,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈等。在进程执行的某些时刻,内核可以决定抢占当前进程,并开始一个先前被抢占的进程,这种决策就叫调度。在内核调度了一个新的进程运行后,它他就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。
在这里插入图片描述

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

在hello中,程序执行sleep函数时,sleep显式请求让调用进程休眠,调度器抢占当前的进程,并且利用上下文切换转移到新进程。Sleep函数结束后,再通过上下文切换返回到hello函数中。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.6.1hello的正常运行状态
在这里插入图片描述

6.6.2按下ctrl-z
在这里插入图片描述

输入ctrl-z默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下,用ps命令可以看到,hello进程并没有被回收。此时他的后台 job 号是 1,调用 fg 1 将其调到前台,此时 shell 程序首先打印 hello 的命令行命令, hello 继续运行打印剩下的 8 条 info,之后输入字串,程序结束,同时进程被回收,如下图。
在这里插入图片描述

在这里插入图片描述

6.6.3按下ctrl-c
在这里插入图片描述

在键盘上输入Ctrl+c会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业,用ps查看前台进程组发现没有hello进程,如图所示。
在这里插入图片描述

6.6.4不停乱按
在这里插入图片描述

无关输入被缓存到stdin,并随着printf指令被输出到结果。

6.6.5 pstree
在这里插入图片描述

6.6.6 kill
在这里插入图片描述

6.7本章小结
本章复习了hello进程的执行过程。在hello运行过程中,内核对其调度,异常处理程序为其将处理各种异常。每种信号都有不同的处理机制,对不同的shell命令,hello也有不同的响应结果。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:格式为“段地址:偏移地址”,是CPU生成的地址,在内部和编程使用,并不唯一。
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。在前端总线上传输的内存地址都是物理内存地址。
虚拟地址:保护模式下程序访问存储器所用的逻辑地址。
线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址.

7.2 Intel逻辑地址到线性地址的变换-段式管理
在 Intel 平台下,逻辑地址(logical address)是 selector:offset 这种形式,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。如果用 selector 去 GDT( 全局描述符表 ) 里拿到 segment base address(段基址) 然后加上 offset(段内偏移),这就得到了 linear address。我们把这个过程称作段式内存管理。
一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段(段选择符)。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
给定一个完整的逻辑地址段选择符+段内偏移地址,看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。Base + offset = 线性地址。
8086共设计了20位宽的地址总线,通过将段寄存器左移4位加上偏移地址得到20位地址,即逻辑地址。将内存分为不同的段,每个段有段寄存器对应,段寄存器有一个栈、一个代码、两个数据寄存器。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址即虚拟地址(VA)到物理地址(PA)之间的转换通过分页机制完成,而分页机制是对虚拟地址内存空间进行分页。系统将虚拟页作为进行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择适当的PTE,根据PTE,我们知道虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。VPO和PPO是相同的。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。
7.4 TLB与四级页表支持下的VA到PA的变换
如果按照上述模式,每次CPU产生一个虚拟地址并且发送给地址管理单元,MMU就必须查找一个PTE行来用将虚拟地址翻译成物理地址。为了消除这种操作带来的大量时间开销,MMU中被设计了一个关于PTE的小的缓存,称为翻译后备缓冲器(TLB)也叫快表。例如当每次cpu发现需要重新翻译一个虚拟地址时,它就必须发送一个vpn得到虚拟地址mmu,发送一个vpo位得到一个l1高速缓存.例如当我们使用mmu向一个tlb的组请求一个页表中的条目时,l1高速缓存通过一个vpo位在页表中查找一个相应的数据标记组,并在页表中读出这个组里的个数据标记和相应的数据关键字.当mmu从一个tlb的组得到一个ppn时,代表缓存的工作在这个组的请求之前已经完全准备好,这个组的ppn与就已经可以与这些数据标记文件中的一个虚拟地址进行很好的匹配了。

corei7采用四级页表层次结构,每个四级页表进程都有他自己私有的页表层次结构,这种设计方法从两个基本方面就是减少了对内存的需求,如果一级页表的pte全部为空,那么二级页表就不会继续存在,从而为进程节省了大量的内存,而且也只有一级页表才会有需要总是在一个内存中。四级页表的层次结构操作流程如下:36位虚拟地址被寄存器划分出来组成四个9位的片,每个片被寄存器用作到一个页表的偏移量。cr3寄存器内储存了一个l1页表的一个物理起始基地址,指向第一级页表的一个起始和最终位置,这个地址是页表上下文的一部分信息。vpn1提供了到一个l1pet的偏移量,这个pte寄存器包含一个l2页表的起始基地址.vpn2提供了到一个l2pte的偏移量,一共四级,逐级以此层次类推。
在这里插入图片描述

7.5 三级Cache支持下的物理内存访问
通过内存地址的组索引获得值,如果对应的值是data则像L1 d-cache对应组中查找,如果是指令,则向L1 i-cache对应组中查找。将L1对应组中的每一行的标记位进行对比,如果相同并且有效位为1则命中,获得偏移量,取出相应字节,否则不命中,向下一级cache寻找,直到向内存中寻找。

7.6 hello进程fork时的内存映射
shell通过一个调用用fork的函数可以让进程内核自动创建一个新的进程,这个新的进程拥有各自新的数据结构,并且被内核分配了一个唯一的pid。它有着自己独立的虚拟内存空间,并且还拥有自己独立的逻辑控制流,它同样可以拥有当前已经可以打开的各类文件信息和页表的原始数据和样本,为了有效保护进程的私有数据和信息,同时为了节省对内存的消耗,进程的每个数据区域都被内核标记起来作为写时复制。
7.7 hello进程execve时的内存映射
1.删除已存在的用户区域. 删除当前继承虚拟地址的用户部分中的已存在的区域结构

2.映射私有区域. 为新程序的代码,数据,bss和站区域创建新的区域结构.所有这些新的区域都是私有的,写时复制的.代码和数据区域被映射为hello.out文件中的.text和.data取. bss区域时请求二进制零的.

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

4.设置程序计数器(PC). execve做的最后一件事就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点.
7.8 缺页故障与缺页中断处理
页面命中完全是由硬件完成的,而处理缺页是由硬件和操作系统内核协作完成的,在指令请求一个虚拟地址时,MMU中查找页表,如果这时对应得物理地址没有存在主存的内部,我们必须要从磁盘中读出数据。在虚拟内存的习惯说法中,DRAM缓存不命中成为缺页。在发生缺页后系统会调用内核中的一个缺页处理程序,选择一个页面作为牺牲页面。具体流程如下:
1)处理器生成一个虚拟地址,并将它传送给MMU
2)MMU生成PTE地址,并从高速缓存/主存请求得到它
3)高速缓存/主存向MMU返回PTE
4)PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
5)缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
6)缺页处理程序页面调入新的页面,并更新内存中的PTE。
7)缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
在这里插入图片描述

7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存管理的基本方法与策略:

动态内存分配器维护着一个进程的虚拟内存区域,称为堆,系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高地址)。对于每个进程,内核维护着一个变量brk,它指向对的顶部。

分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显示地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显示地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的。要么是内存分配器自身隐式执行的。分配器有两种基本风格。两种风格都要求应用显示地分配块。他们的不同之处在于由哪个实体来负责释放已分配的块。

1.显示分配器:要求应用显示的释放任何已分配的块。例如C标准库提供一个叫做malloc程序包的显示分配器。

2.隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,那么就释放这个块。隐式分配器也叫垃圾收集器。
①隐式空闲链表的堆块格式:
在这里插入图片描述

②隐式空闲链表的带边界标记的堆块格式:
在这里插入图片描述

使用边界标记的堆块的格式其中头部和脚部分别存放了当前内存块的大小与是否已分配的信息。通过这种结构,隐式动态内存分配器会对堆进行扫描,通过头部和脚部的结构实现查找。使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。维护链表的顺序有:后进先出(LIFO),将新释放的块放置在链表的开始处,使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块,在这种情况下,释放一个块可以在线性的时间内完成,如果使用了边界标记,那么合并也可以在常数时间内完成。按照地址顺序来维护链表,其中链表中的每个块的地址都小于它的后继的地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。平衡点在于,按照地址排序首次适配比LIFO排序的首次适配有着更高的内存利用率,接近最佳适配的利用率。
7.10本章小结
本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, hello 进程fork时和execve 时的内存映射、缺页故障与缺页中断处理、包括隐式空闲链表和显式空闲链表的动态存储分配管理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。Linux就是基于Unix I/O实现对设备的管理。
8.2 简述Unix IO接口及其函数
Unix IO接口:
1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.Linux Shell创建的每个进程都有三个打开的文件:标准输入(描述符为0),标准输出(描述符为1),标准错误(描述符为2)。
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);
关闭文件:int close(int fd);
读文件: ssize_t read(int fd, void *buf, size_t n);
写文件: ssize_t write(int fd, const void *buf, size_t n);
8.3 printf的实现分析
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

printf函数:
printf(const char *fmt,···){
int i;
va_list arg = (va_list)((char *)($fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
在printf中调用系统函数write(buf,i)将长度为i的buf输出,在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,
int INT_VECTOR_SYS_CALLA代表通过系统调用syscall。

syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。

字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。

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

于是我们的打印字符串就显示在了屏幕上。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。当程序调用getchar时,程序等待用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车(回车也在缓冲区中)。当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。异步异常-键盘中断的处理:当用户按键时触发键盘终端,操作系统将控制转移到键盘中断处理子程序,中断处理程序执行,接受按键扫描码转成ascii码,保存到系统的键盘缓冲区,显示在用户输入的终端内。当中断处理程序执行完毕后,返回到下一条指令运行。

8.5本章小结
本章介绍了Linux的IO设备管理方法,Unix IO接口及其函数,分析了printf函数和getchar函数的实现。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
预处理:hello.c预处理到hello.i文本文件;
编译:hello.i编译到hello.s汇编文件;
汇编:hello.s汇编到二进制可重定位目标文件hello.o;
链接:hello.o链接生成可执行文件hello;
创建子进程:bash进程调用fork函数,生成子进程;
加载程序:execve函数加载运行当前进程的上下文中加载并运行新程序hello;
访问内存:hello的运行需要地址的概念,虚拟地址是计算机系统最伟大的抽象;
交互:hello的输入输出与外界交互,与linux I/O息息相关;
终止:hello最终被shell父进程回收,内核会收回为其创建的所有信息。
至此,hello运行结束。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
ello.c 源程序

hello.i 预处理后文件

hello.s 编译后的汇编文件

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

hello 链接后的可执行文件

hello.elf hello.o的ELF格式

hello1.txt hello.o的反汇编

hello2.txt hello的反汇编代码

hello1.elf hello的ELF格式
(附件0分,缺失 -1分)

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E.Bryant David R.O’Hallaron. 深入理解计算机系统(第三版). 机械工业出版社,2016.
[2] CSDN:https://www.csdn.net/
(参考文献0分,缺失 -1分)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值