计算机系统大作业

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算学部
学   号 120L021530
班   级 2003002
学 生 牛博阳
指 导 教 师 史先俊

计算机科学与技术学院
2022年5月
摘 要
本文通过hello的自白分析hello.c程序的一生,来进一步了解一个程序从生成到结束这其中所经过的过程。对我们本学期学过的知识进行整理,总结与巩固。理解程序的预处理、编译、汇编、链接、进程管理、存储管理和IO管理等概念。
关键词:hello,预处理,编译,汇编,链接,进程管理,存储管理,IO管理;

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

目 录
第1章 概述 - 3 -
1.1 HELLO简介 - 3 -
1.2 环境与工具 - 3 -
1.3 中间结果 - 3 -
1.4 本章小结 - 3 -
第2章 预处理 - 4 -
2.1 预处理的概念与作用 - 4 -
2.2在UBUNTU下预处理的命令 - 4 -
2.3 HELLO的预处理结果解析 - 4 -
2.4 本章小结 - 6 -
第3章 编译 - 7 -
3.1 编译的概念与作用 - 7 -
3.2 在UBUNTU下编译的命令 - 7 -
3.3 HELLO的编译结果解析 - 8 -
3.4 本章小结 - 10 -
第4章 汇编 - 11 -
4.1 汇编的概念与作用 - 11 -
4.2 在UBUNTU下汇编的命令 - 11 -
4.3 可重定位目标ELF格式 - 11 -
4.4 HELLO.O的结果解析 - 14 -
4.5 本章小结 - 15 -
第5章 链接 - 16 -
5.1 链接的概念与作用 - 16 -
5.2 在UBUNTU下链接的命令 - 16 -
5.3 可执行目标文件HELLO的格式 - 16 -
5.4 HELLO的虚拟地址空间 - 20 -
5.5 链接的重定位过程分析 - 21 -
5.6 HELLO的执行流程 - 23 -
5.7 HELLO的动态链接分析 - 24 -
5.8 本章小结 - 25 -
第6章 HELLO进程管理 - 26 -
6.1 进程的概念与作用 - 26 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 26 -
6.3 HELLO的FORK进程创建过程 - 26 -
6.4 HELLO的EXECVE过程 - 27 -
6.5 HELLO的进程执行 - 27 -
6.6 HELLO的异常与信号处理 - 28 -
6.7本章小结 - 31 -
第7章 HELLO的存储管理 - 32 -
7.1 HELLO的存储器地址空间 - 32 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 32 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 33 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 34 -
7.5 三级CACHE支持下的物理内存访问 - 35 -
7.6 HELLO进程FORK时的内存映射 - 35 -
7.7 HELLO进程EXECVE时的内存映射 - 36 -
7.8 缺页故障与缺页中断处理 - 37 -
7.9动态存储分配管理 - 38 -
7.10本章小结 - 39 -
第8章 HELLO的IO管理 - 40 -
8.1 LINUX的IO设备管理方法 - 40 -
8.2 简述UNIX IO接口及其函数 - 40 -
8.3 PRINTF的实现分析 - 41 -
8.4 GETCHAR的实现分析 - 42 -
8.5本章小结 - 42 -
结论 - 42 -
附件 - 44 -
参考文献 - 45 -

第1章 概述
1.1 Hello简介
P2P:首先,程序员在Windows或Linux环境下,利用visual studio、CodeBlocks等编辑器敲入C语言代码(Program),然后,利用gcc编译器对C语言程序执行编译命令:hello.c文件先经过预处理生成hello.i文件,再经过编译生成hello.s汇编程序,然后经过汇编生成可重定位目标程序hello.o,最后通过链接器链接生成可执行文件hello。在Linux终端执行./hello命令,运行该可执行文件
020:可执行文件hello执行后,shell通过execve函数和fork函数将hello加载执行并创建子进程,通过内存映射等为其分配自己的运行空间,CPU在流水线上依次执行每一条指令,内存管理系统利用虚拟内存地址从TLB或高速缓存中取出相应数据,如果出现不命中则继续从其下一级缓存中读取数据,依次类推。最后程序运行结束后,shell接受到相应的信号,启动信号处理机制,对该进程进行回收处理,释放其所占的内存并删除有关进程上下文,hello程序重新回归0,即从0到0。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;4G RAM;256GHD Disk
软件环境:Windows11 64位;Vmware 16;Ubuntu 20.04 LTS 64位
开发与调试工具:Visual Studio 2022 64位;CodeBlocks 64位; vim+gcc,gdb, objdump, edb等
1.3 中间结果
hello.c:源代码文件
hello.i:预处理后生成的文件
hello.s:编译后生成的文件
hello.o:汇编后生成的可重定位目标程序
hello:链接之后生成的可执行程序
hello_o_asm.txt:hello.o的反汇编文件
hello_o_elf.txt:hello.o的ELF文件
hello_r_asm.txt:hello的反汇编文件
hello_r_elf.txt:hello的ELF文件
1.4 本章小结
本章解释了P2P,020的含义,列出相关环境和工具以及中间结果(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理的概念:在编译之前进行的处理。C语言的预处理主要有以下三个方面的内容:1. 宏定义;2. 文件包含;3. 条件编译
预处理的作用:预处理器根据以字符#开头的命令,修改原始的C程序。将#开头的头文件的内容直接加到原有的C程序中,结果就得到了另一个C程序,通常以“.i”作为文件扩展名
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
在这里插入图片描述
在这里插入图片描述

2.3 Hello的预处理结果解析
在这里插入图片描述
在这里插入图片描述

hello.i中代码有好多行,但前面的可以看出来都是复制的库函数的代码,最后几行是原来hello.c文件的代码内容,并未做任何改动
2.4 本章小结
本章介绍了预处理的概念和作用以及生成预处理文件的命令,并对预处理生成的文件进行了分析。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译概念:将之前预处理形成的“.i”文件通过语法分析、优化等产生的“.s”为结尾的汇编文件。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序。编译作用:生成的汇编文件中的每条语句都是针对原高级语言文件生成的汇
编语言文件,为不同语言的编译器提供了一个统一的语言,更加方便机器识别。
3.2 在Ubuntu下编译的命令
gcc -S hello.c -o hello.s
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3 Hello的编译结果解析
3.3.1数据
(1)在这里插入图片描述

全局变量main放在type字段中,align8表示要求8字节对齐,Hello %s %s标识了输出的形式:两个字符串
(2)
在这里插入图片描述

main的参数argc存储在%edi寄存器中,通过%rbp的-20偏移来表示,这个通过cmpl这一句可以看出来;argv存储在%rsi寄存器中,通过%rbp的-32偏移表示
(3)
在这里插入图片描述

循环变量i存储在-4(%rbp)中,从0开始进入循环
(4)
在这里插入图片描述

从循环语句L3中可知循环从0到7
3.3.2操作
(1)在这里插入图片描述

赋值操作用mov来进行,赋完的值用寄存器来存储,如果是全局或静态变量变量不赋初值,则会存放在.bss段;如果是已初始化的全局变量,会存放在.data段
(2)
在这里插入图片描述

关系操作用cmp来进行比较,分为cmpq,cmpl…,根据比较结果会设置标志位,之后利用标志位的结果来继续进行后面的操作
(3)
在这里插入图片描述

数组/指针/结构体的操作都是通过栈来完成,存储在栈中,利用栈指针的偏移量来取对应数组下标,指针位置结点的元素,在本例中,argv[1]存储在寄存器%rdx中,argv[2]存储在寄存器%rsi中
(4)
在这里插入图片描述

取地址操作由leaq完成,取出一个地址,括号外面可以写偏移量,最后将这个地址赋给一个寄存器
(5)
在这里插入图片描述
在这里插入图片描述

控制转移指令:通过jxx的操作进行有条件跳转或无条件1跳转到指定的位置
(6)
在这里插入图片描述

函数调用操作:通过call指令去调用相关指定的函数,将函数所需参数放在寄存器或栈中进行参数传递,并将函数返回值保存在寄存器%rax中
(7)
在这里插入图片描述

 压栈弹栈操作:push压入栈顶一个元素,pop弹出栈顶元素

3.4 本章小结
本章介绍了编译的概念与作用,进行编译的命令,并且对生成的汇编语言文件进行了解析,介绍了C语言的一些数据和基本操作的相关的汇编语言指令,深入理解了生成的汇编代码的意义。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编概念:汇编器将.s文件翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在以”.o”为后缀的目标文件中
汇编作用:将汇编代码转变为机器可以真正去识别的二进制代码
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。(将其重定位为hello_elf.txt文件)

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

ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF剩下的部分包含帮助连接器语法分析和解释目标文件的信息。包括ELF头的大小、目标文件的类型等。
一个典型的ELF可重定位目标文件包含下面几个节:
.txt:已编译程序的机器代码。
.rodata:只读数据。
.data:已初始化的全局和静态局部C变量。
.bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。
.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
.rel.text:一个.text节中位置的列表。
.rel.data:被模块引用或定义的所有全局变量的重定位信息。
.debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量以及原始的C源文件。
节头部表:
在这里插入图片描述

符号表:
在这里插入图片描述

可以从符号表中看出main的大小为152字节,类型为FUNC,Bind为GLOBAL
重定位节:
重定位条目常见有两种:
1.R_X86_64_PLT32:重定位绝对引用。重定位时使用一个32位的绝对地址的引用,通过绝对寻址,CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改。
2.R_X86_64_PC32:重定位PC相对引用。重定位时使用一个32位PC相对地址的引用。一个PC相对地址就是据程序计数器的当前运行值的偏移量。通过重定位算法,计算出新的重定位地址。
在这里插入图片描述

4.4 Hello.o的结果解析

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

和hello.s对比,首先是在指令前增加了十六进制表示,即机器语言。其次在操作数上,hello.s中操作数为十进制,而hello.o的反汇编中操作数为十六进制。
在分支转移语句上,hello.o的反汇编文件用的是相对main的偏移量,而hello.s中是直接跳转到函数名

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

在调用函数方面,hello.o反汇编文件通过重定向的方式进行跳转,链接时根据重定位条目来获得地址信息

在这里插入图片描述

objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章介绍了汇编的概念及作用,生成汇编文件hello.o的命令,解析了可重定位目标文件elf格式,并且对hello.o的结果进行分析,和hello.s结果进行了比较,说明了机器语言和汇编语言的区别。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
链接概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。
链接作用:使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解成更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
在这里插入图片描述

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
在这里插入图片描述

1.ELF头:hello的类型是一个可执行目标文件(EXEC),节头数量增加至27个,有关详细信息如下:
在这里插入图片描述

2.节头信息:初始地址已经进行了重定位,地址恰好是偏移量,这里的地址是程序载入到虚拟地址的起始地址。详细信息如下:
在这里插入图片描述

在这里插入图片描述

3.重定位节信息如下,偏移量发生了改变:
在这里插入图片描述

  1. 符号表:可执行文件多出了.dynsym节,其中包含动态链接解析出来的符号。可以看到是程序引用的是头文件的函数,详细信息如下:
    在这里插入图片描述

5.程序头表:
在这里插入图片描述

6.段节:Section to segment mapping:
在这里插入图片描述

7.Dynamic section:
在这里插入图片描述

8.版本信息:
在这里插入图片描述

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.4 hello的虚拟地址空间
1.可以看出起始地址为0x400000
在这里插入图片描述

2.由elf文件可以看出偏移量为0x2e0,在edb对应位置找到
在这里插入图片描述
在这里插入图片描述

  1. text段和rodata段也都能找到:

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

在这里插入图片描述

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

5.5 链接的重定位过程分析
在这里插入图片描述

hello和hello.o相比,首先加了所调用函数的汇编代码和辅助信息,如_init、puts@plt等;hello.o的地址是从0开始的,是相对地址,而hello的地址是从0x401000(init的地址)开始的,是已经进行重定位之后的虚拟地址,其引用的全局变量的地址及偏移量都转换为虚拟地址;在hello的main函数中,条件跳转指令和call指令后均为绝对地址,而hello.o中是相对于main函数的相对地址
hello:
在这里插入图片描述

hello.o:
在这里插入图片描述在这里插入图片描述

链接的过程主要是符号解析和重定位。
符号解析就是目标文件定义并引用符号,每个符号对于函数或全局变量或静态变量,作用是将符号引用和符号定义关联。
重定位就是编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。
重定位分两步:重定位节和符号定义:id将相同类型的节合并为同一类型的新聚合节。然后,链接器将运行时内存地址传递给新聚合节,赋给输入模块定义的每个节,和输入模块定义的每个符号。作用是给程序中的每条指令和全局变量定义唯一的运行时内存地址;重定位节中的符号引用:链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址,链接器依赖于可重定位目标模块的重定位条目
具体实例:
比如说对printf函数的重定位如下

在这里插入图片描述

这一行的二进制编码为e8 00 00 00 00
addr(test)=0x4011f4-0x63=0x401191,offset=0x68-0x4=0x64
refaddr=addr(test)+offset=0x4011f5
addr(r.symbol)=0x401090, r.addend=-4
*refptr=(unsigned)(addr(r.symbol)+r.addend-refaddr)=(unsigned)(0x401090+0x(-4)-0x4011f5)=ff ff fe 97
由小端存储法可知为97 fe ff ff,与正常反汇编代码一致。
5.6 hello的执行流程
用edb执行hello,程序的初始地址在0x7f3ee58d80d0处,表明hello使用的动态链接库ld-2.31.so的入口点dl_start位于此地址
之后程序跳转到_dl_init进行初始化,之后会调到hello程序入口_start处,接着call指令跳转到_libc_start_main调用main函数。在其内部会调用_cxa_atexit函数用来设置程序结束时需要调用的函数表。跳出_cxa_atexit之后,调用hello中的_lib_csu_init函数来进行初始化。
上述准备工作完成后,会调用main函数,开始执行hello程序,其中main会调用_printf,_exit,_sleep,_getchar等函数用来完成程序功能。
如下表所示:

程序功能
子程序名

载入 0x7f3ee58d80d0
_dl_start
_dl_init

执行 _start

_libc_start_main

运行 _main
_printf
_exit
_sleep
_getchar
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
退出 _exit

程序功能
子程序名

载入 0x7f3ee58d80d0
_dl_start
_dl_init

执行 _start

_libc_start_main

运行 _main
_printf
_exit
_sleep
_getchar
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
退出 _exit

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
共享链接库代码是一个动态的目标模块,在程序开始运行或者调用程序加载时,可以自动加载该代码到任意的一个内存地址,并和一个在目标模块内存中的应用程序链接,这个过程就是对动态链接的重定位过程。
而在一个动态的共享链接库中仍然存在着一个可以调用程序加载而动态链接无需重定位的位置无关代码,编译器在程序中的函数开始运行时是不能自动预测各个函数的开始运行时间和地址的,这就可能需要系统添加重定位的记录,交给一个动态共享链接器或者采用它来进行重定位的动态共享链接,动态共享链接器本身就是负责执行对动态链接的重定位过程,这样做能有效防止程序运行时自动修改或者调用目标模块的位置无关代码段。
1.首先找到.got地址,为0x403ff0
在这里插入图片描述

2.然后在edb中找到相应位置,分析在dl_init前后它的变化
_dl_init前:
在这里插入图片描述

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

动态的链接器在正常工作时链接器采取了延迟绑定的链接器策略,由于静态的编译器本身无法准确预测变量和函数的绝对运行时地址,动态的链接器需要等待编译器在程序开始加载时再对编译器进行延迟解析,这样的延迟绑定策略称之为动态延迟绑定。.got链接器叫做全局变量过程偏移链接表,在.plt和.got中分别存放着链接器的目标变量和函数的运行时地址。一个动态的链接器通过静态的过程偏移链接表.plt和.got链接器实现了函数的一个动态过程链接,这样一来,它就已经包含了正确的绝对运行时地址。
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章介绍了链接的概念和作用,链接的命令,分析了可执行目标文件hello的格式,通过edb展示了hello的虚拟地址空间,分析链接重定位过程,分析hello的执行流程,动态链接过程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。每次程序用户可以通过向系统中的shell应用程序输入一个可执行程序的英文名字,运行这个应用程序时,shell就可能会自动创建一个新的应用进程,然后在这个新应用进程的可执行上下文中自动运行这个可执行文件,应用程序也同样可以自动创建新的可执行进程,并且在这个新进程的可执行上下文中用户可以运行他们自己的可执行代码或者其他的应用程序。
作用:进程为程序提供了一种假象,程序好像是独占的使用处理器和内存。处理器好像是无间断地一条接一条地执行我们程序中的指令
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是指一个可以接受命令行操作的壳。shell应用程序提供了一个界面,用户通过访问这个界面访问操作系统内核的服务。Linux系统中所有的可执行文件都可以作为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调用一次并从不返回。
在书上的第9章我们了解到execve加载并运行需要以下步骤:
1.删除已存在的用户区域
2.创建新的区域结构
(1)私有的、写时复制
(2)代码和初始化数据映射到.text和.data区(目标文件提供)
(3).bss和栈堆映射到匿名文件,栈堆的初始长度0
3.共享对象由动态链接映射到本进程共享区域
4.设置PC,指向代码区域的入口点。Linux根据需要换入代码和数据页面
6.5 Hello的进程执行
进程为每个程序提供了一种假象,好像程序在独占的使用处理器。一个进程执行它的控制流的一部分的每一时间段叫做时间片。一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流称为并发地运行。
处理器刚开始运行在用户模式中,用户模式中的进程不允许执行特权指令,也不允许直接引用地址空间中内核区的代码和数据。而一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存的位置。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式改为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式改回用户模式。
上下文切换:当内核选择一个新的进程运行时,则内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程:
(1)保存以前进程的上下文。
(2)恢复新恢复进程被保存的上下文。
(3)将控制传递给这个新恢复的进程,来完成上下文切换。
在进程执行的某些时刻,内核可以决定抢占当前进程,并开始一个先前被抢占的进程,这种决策就叫调度。在内核调度了一个新的进程运行后,它他就抢占当前进程,并通过上下文切换机制来将控制转移到新的进程。
6.6 hello的异常与信号处理
1.不停乱按:
每隔指定的时间之后会定期返回输出Hello 学号 姓名,在定期期间之内如果乱按的话每按一次回车就会将按的内容全部输出,并且当确定不再按,程序结束之后会将以回车为分隔符的每一条输入作为一个命令来检验。
在这里插入图片描述

2.ctrl+c:
在这里插入图片描述

按ctrl+c之后,相当于内核发送一个SIGINT信号给每个进程,默认情况下是终止前台作业
3.ctrl+z:
在这里插入图片描述

在键盘下按下Ctrl-Z之后,会导致内核发送一个SIGTSTP信号到前台进程组的每个进程,默认情况下结果是停止(挂起)前台作业
4.ps:
在这里插入图片描述

5.jobs:
在这里插入图片描述

6.pstree:

在这里插入图片描述

7.fg:
在这里插入图片描述

8.kill:
在这里插入图片描述

6.7本章小结
本章介绍了进程的概念和作用,壳shell-bash的作用与处理流程,hello的fork和execve过程,以及hello的异常和信号的处理
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址用来指定一个操作数或指令,它由选择符和偏移量组成。逻辑地址是程序代码在编译之后出现在汇编代码中的地址。如图,在汇编.o用readelf解析后的代码中就是逻辑地址
在这里插入图片描述

线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
虚拟地址:程序访问存储器所使用的逻辑地址称为虚拟地址。在hello.out文件中,显示出的地址为虚拟地址:
在这里插入图片描述

物理地址:一个计算机操作系统的物理主存被自动组织为一个由m个连续的字节相同大小的单元内存组成的计算机数据。物理地址就是内存中每个内存单元的编号。大小取决于内存中内存单元的数量。
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理:指的是把一个程序分成若干个段进行存储,每个段都是一个逻辑实体。段式管理是通过段表进行的,包括段号(段名),段起点,装入位,段的长度等。程序通过分段划分为多个块,如代码段,数据段,共享段等。逻辑地址是程序源码编译后所形成的跟实际内存没有直接联系的地址,即在不同的机器上使用相同的编译器来编译同一个源程序则其逻辑地址是相同的,但在不同机器上生成的线性地址是不相同的。一个逻辑地址是两部分组成的,包括段标识符和段内偏移量。段标识符是由一个16位长的字段组成的,称为段选择符。前13位是一个索引号,后3位为一些硬件细节。索引号对应的是段描述符表的位置,其中的Base字段描述了一个段的开始位置的线性地址。要想看一个完整的逻辑地址的话分如下几步:

  1. 首先看段选择符的T1=0还是1,声明当前要转换是GDT中的段,还是LDT中的段
  2. 再根据相应寄存器,得到其地址和大小
  3. 使用段选择符中的前13位,在这个数组中查找到对应的段描述符,即可得到它的基地址,基地址Base + offset即要转换的线性地址
    7.3 Hello的线性地址到物理地址的变换-页式管理
    线性地址到物理地址的转换是通过分页完成的。首先CPU通过生成一个虚拟地址来访问主存,这个虚拟地址被送到内存之前首先转换为物理地址,CPU上的内存管理单元(MMU)利用主存中的查询表来动态翻译虚拟地址。VM系统将虚拟内存分割,称为虚拟页,类似地,物理内存也被分割成物理页。利用页表来管理虚拟页,页表就是一个页表条目(PTE)的数组,每个PTE由一个有效位和一个n位地址字段组成,有效位表明了该虚拟页当前是否被缓存在DRAM中,如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始位置。如果发生缺页,则从磁盘读取。
    在这里插入图片描述

当页命中时,CPU硬件按如下步骤进行:
(1)处理器生成一个虚拟地址,并将其传送给MMU
(2)MMU生成PTEA,并从高速缓存/主存请求得到它
(3)高速缓存/主存向MMU返回PTE
(4)MMU将物理地址传送给高速缓存/主存
(5)高速缓存/主存返回所请求的数据字给处理器
地址翻译:页面命中
在这里插入图片描述

当页不命中时,处理器将虚拟地址发送给MMU ,MMU使用内存中的页表生成PTE地址,有效位为零,因此 MMU 触发缺页异常,缺页处理程序确定物理内存中牺牲页 (若页面被修改,则换出到磁盘),缺页处理程序调入新的页面,并更新内存中的PTE,缺页处理程序返回到原来进程,再次执行缺页的指令。
地址翻译:缺页异常
在这里插入图片描述

7.4 TLB与四级页表支持下的VA到PA的变换
如图给出了Core i7 MMU如何使用四级的页表来将虚拟地址翻译成物理地址。36位VPN被划分成4个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1 PET的偏移量,这个PTE包含L2页表的基地址。VPN 2提供到一个L2 PTE的偏移量,以此类推。

7.5 三级Cache支持下的物理内存访问
在这里插入图片描述

将m个地址位分成t个标记位、s个组索引位和b个块偏移位。S个组索引位是一个到s个组的数组的索引。组索引位被解释为一个无符号整数,它告诉我们这个字存储在哪个组中。t个标记位告诉我们这个组中的哪一行包含这个字(如果有的话),当且仅当设置了有效位并且该行的标记位与地址a中标记位相匹配时,组中的这一行才包含这个字。一旦我们在由组索引标识的组中定位了由标号所标识的行,那么b个块偏移位给出了在b个字节的数据块中的字偏移。
7.6 hello进程fork时的内存映射
当fork函数被shell进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。
fork内存映射:
在这里插入图片描述

7.7 hello进程execve时的内存映射
execve函数加载并运行可执行目标文件,且带参数列表argv和环境变量列表envp。只有出错时才返回调用程序,否则execve调用一次且不返回。
execve加载并运行需要以下步骤:
(1) 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。
(2) 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些区域结构都是私有的,写时复制的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
(3) 映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。
(4) 设置程序计数器(PC)。exceve设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
execve的内存映射:
在这里插入图片描述

7.8 缺页故障与缺页中断处理
假设MMU在试图翻译某个虚拟地址A时触发了一个缺页,这个异常会导致控制转移到内核的缺页处理程序并执行以下步骤:
(1)虚拟地址A是合法的吗?换句话说就是A在某个区域结构定义的区域内吗?如果这个指令不合法,那么缺页处理程序就会触发一个段错误,进而终止这个进程。
(2)进程是否有读、写或者执行这个区域内页面的权限?例如这个缺页是不是因为一个运行在用户模式中的进程试图从内核虚拟内存中读取字造成的?如果试图进行的访问是不合法的,那么缺页处理程序会触发一个保护异常,终止进程。
(3)如果缺页是由于对合法的虚拟地址进行合法的操作造成的,内核会选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令将再次发送A到MMU。这次,MMU就能正常地翻译A,而不会缺页中断了。
缺页中断处理:
在这里插入图片描述

7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk, 它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格。两种风格都要求应用显式地分配块,它们的不同之处在于由哪个实体来负责释放已分配的块。

  1. 显式分配器(explicit allocator):要求应用显式地释放任何已分配的块。例如,C标准库提供一种叫做malloc程序包的显式分配器。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。C++中的new和delete操作符与C中的malloc和free相当。
  2. 隐式分配器(implicit allocator):要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器(garbage collector),而自动释放未使用的已分配的块的过程叫做垃圾收集( garbage collection)。例如,诸如Lisp、ML以及Java之类的高级语言就依赖垃圾收集来释放已分配的块。
    为实现动态内存分配器,可以使用隐式空闲链表。当一个应用请求k字节的
    块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。分配器执行这种搜索的方式是由放置策略确定的。一些常见的策略是首次适配、下一次适配和最佳适配。一旦分配器找到一个匹配的空闲块,就需要决定分配这个空闲块中多少空间。一个选择是用整个空闲块,但这样会造成内部碎片。如果匹配不太好,那么分配器会将这个空闲块分割,第一部分变成分配块,剩下的变成一个新的空闲块。利用边界标记,可以允许在常数时间内进行对前面块的合并。这种思想是在每个块的结尾添加一个脚部,其中脚部就是头部的一个副本。这样分配器就可以通过检查它的脚部,判断前面一个块的起止位置和状态,这个脚部总是在距离当前块开始位置一个字的距离。但是这种方法也存在潜在缺陷,就是在应用程序操作许多个小块时,会产生显著的内存开销。
    在这里插入图片描述

Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
本章介绍了hello的存储器的地址空间,hello的段式管理,页式管理,TLB与四级页表支持的情况下VA到PA的转换,三级cache的支持下的物理内存访问,hello进行fork,execve下的内存映射,并且分析了缺页故障与缺页中断管理以及动态存储分配管理
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
一个Linux文件就是一个m字节的序列:B0,B1,B2…Bk…Bm-1。所有的IO设备(如网路、磁盘、终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单低级的应用接口,称为 Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
接口:
(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) 关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。
函数:
(1) int open(char *filename,,int flags,mode_t mode)。open函数将filename转换为一个文件描述符,并且返回描述符数字。
(2) int close(int fd)。fd是需要关闭的文件的描述符,close返回操作结果。关闭一个已关闭的描述符会出错。
(3) ssize_t read(int fd, void *buf, size_t n)。read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。
(4) ssize_t write(int fd, const void *buf, size_t n)。write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
8.3 printf的实现分析

在形参列表里有这么一个token:…这个是可变形参的一种写法。当传递参数的个数不确定时,就可以用这种方式来表示。fmt是一个指针,指向第一个const参数中的第一个元素,(char*)(&fmt)+4)表示的是…中的第一个参数。fmt也是个变量,它的位置,是在栈上分配的,它也有地址。C语言中,参数压栈的方向是从右往左。也就是说,当调用printf函数的适合,先是最右边的参数入栈。fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。之后我们要继续看i = vsprintf(buf, fmt, arg);这一句,看vsprintf这个函数:
在这里插入图片描述

vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。代码中的vsprintf只实现了对16进制的格式化。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用int 0x80或syscall。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
https://www.cnblogs.com/pianist/p/3315801.html
8.4 getchar的实现分析
程序调用getchar后,等待用户按键,将输入的字符存储在电脑的缓冲区,等待回车键。按回车后,getchar从stdio流中每次读取一个字符,如果输入不止一个字符,则保存在缓冲区中,等待后续getchar调用,直到缓冲区清空后,等待用户按键。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
异步异常–键盘中断的处理:按键后,不仅产生该按键码,还产生一个中断信号,使正在运行的程序中断后运行一个子程序,读取按键的码并转为ASCII码保存到缓冲区内。都结束后回到下一条指令。键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
8.5本章小结
本章主要针对hello的IO设备管理,简述了Unix IO接口及其函数,并对相应的printf函数和getchar函数的实现进行了分析。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
(1) hello.c源程序
(2) 预处理:对hello.c进行预处理,得到hello.i
(3) 编译:生成hello.s文件,将高级语言转化为汇编语言
(4) 汇编:生成hello.o文件,机器语言转变为可重定位目标文件
(5) 链接:经动态链接,符号解析,重定位生成可执行文件hello
(6) 运行:shell中输入命令,通过fork创建子进程,execve加载并执行,将hello载入,为hello分配虚拟地址,并通过四级页表和TLB等结构,将虚拟地址翻译成物理地址,然后根据物理地址进行三级cache支持下的物理内存访问,取出相应信息。在程序运行过程中,还能够接受信号(SIGINT,SIGTSTP等)并进行异常处理。
(7) 执行:hello顺序执行自己的逻辑控制流
(8) 动态内存分配:malloc动态申请内存
(9) 终止:子进程结束后,父进程回收子进程,并回收内存空间
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
通过这次实验,我感受到了一个小小的程序居然有如此“辉煌的一生”,感受到计算机系统底层设计者的厉害,程序设计的精巧。如今良好的计算机编程环境离不开对底层系统的精妙设计,对每个信号的发送、接收与各种异常处理机制又展现了底层系统设计的严谨性。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
hello.c:源代码文件
hello.i:预处理后生成的文件
hello.s:编译后生成的文件
hello.o:汇编后生成的可重定位目标程序
hello:链接之后生成的可执行程序
hello_o_asm.txt:hello.o的反汇编文件
hello_o_elf.txt:hello.o的ELF文件
hello_r_asm.txt:hello的反汇编文件
hello_r_elf.txt:hello的ELF文件

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

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 兰德尔 E.布莱恩特(卡耐基-梅隆大学),大卫 R.奥哈拉伦(卡耐基-梅隆大学)
深入理解计算机系统
(参考文献0分,缺失 -1分)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值