【无标题】程序人生-Hello‘s P2P

打印 上一主题 下一主题

主题 836|帖子 836|积分 2508





计算机系统


大作业



题     目   程序人生-Hellos P2P 
专       业   未来技术学院           
学     号   2021111385             
班   级   21WL026               
学       生   赵梁               
指 导 教 师   吴锐                  






计算机科学与技术学院

2023年5月

摘  要

本文从每个程序员编写的第一个程序——Hello.c为例,从程序Hello的视角展示了Hello.c从预处置惩罚、编译、汇编再到链接等一系列流程,以此体现程序从.c到可执行文件、再到运行完毕到回收的一系列变化,即一个程序的一生:P2P(From Program to Process)和020(From Zero to Zero),展现了计算机系统运行的基本原理和底层逻辑。

关键词:P2P;020;计算机系统;Hello.c;                            








目  录


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

Hello的P2P过程:程序员通过I/O设备键盘编写C程序Hello.c,随后对其进行预处置惩罚(cpp)、编译(ccl)、汇编(as)到链接(ld),末了生成可执行文件Hello的一系列过程,通过在终端输入以下语句实现:
Linux> gcc -o Hello Hello.c

流程图如下所示:


Hello的020过程:得到Hello的可执行文件后,可在终端输入以下语句完成Hello的020过程:
Linux> ./Hello

随后经历以下步调:

  •  shell下令行解释器构造argv和envp;
  •  shell 加载可执行文件 hello,复制目标文件 hello 中的代码和数据到内存中。
  •  调用fork()函数创建子历程,其地址空间与shell父历程完全雷同,包罗只读代码段、读写数据段、堆及用户栈等
  •  调用execve()函数在当前历程(新创建的子历程)的上下文中加载并运行hello程序。将Hello中的.text节、.data节、.bss节等内容加载到当前历程的捏造地址空间
  • 调用Hello程序的main()函数,hello程序开始在一个历程的上下文中运行。
  • 历程终止后,shell将回收历程,同时OS开释捏造空间


1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows11 64位 Ubuntu 18.04
开辟工具:Visual Studio 2021;CodeBlocks 64位;gedit+gcc
1.3 中间结果

hello.c   源文件

hello.i 预处置惩罚文件

hello.s 汇编文件

hello.o 可重定位目标文件

hello 可执行文件

hello.elf hello.o的elf文件

hello_rel.elf 链接后的hello的elf文件

asm.txt hello的反汇编代码


1.4 本章小结

本章先容了hello的P2P和020过程,以及这个过程中产生的各个文件的作用说明


第2章 预处置惩罚

2.1 预处置惩罚的概念与作用

概念:预处置惩罚器(cpp)不是编译器的构成部分,它是编译过程中一个单独的步 骤。预处置惩罚器会根据以字符#开头的下令,修改原始的C程序。例如将 Hello.c第一行的#include <stdio.h>下令告诉预处置惩罚器读取系统头文件 stdio.h的内容,并把它直接插入到程序文本,结果得到了另一个C程 序,通常以.i作为文件拓展名.
作用:C语言提供了多种预处置惩罚功能,如宏界说、文件包含、条件编译等。合 理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利 于模块化程序设计.预处置惩罚固然并不会对程序文本进行直接编译,但会 对程序文本进行一些标志与修改,对一个资源进行宏替换等价替换, 将其转化为易于编译的形式,为后续的步调打下根本


2.2在Ubuntu下预处置惩罚的下令

Linux> gcc -E hello.c -o hello.i

图2-1 Ubuntu下预处置惩罚下令

2.3 Hello的预处置惩罚结果解析


图2-2 预处置惩罚前的内容(hello.c)



图 2-3 预处置惩罚后的内容(hello.i)

通过预处置惩罚器的预处置惩罚之后,生成的hello.i文件的开头部分和结尾部分如上图,可以看到容量扩大了许多。hello.i文件中不再包含#include下令,而是将其展开。预处置惩罚器在对应位置找到文件并展开后,如果有包含其他文件,就递归展开,直到将所有文件全部展开为止,将这些全部插入程序文本。而文本末了可以看到仍然是hello.c的内容
2.4 本章小结


本章先容了预处置惩罚过程,展示了预处置惩罚器是如何对hello.c中#开头的下令展开生成hello.i文件,以及两者之间的对应关系.

第3章 编译


3.1 编译的概念与作用

概念:编译是把代码转化为汇编指令的过程,即通过编译器(ccl)将文本文件 hello.i翻译成文本文件hello.s,汇编指令只与CPU相干的,也就是说C 代码和python代码,代码逻辑如果雷同,编译完的结果着实是一样的
作用:将高级语言源程序翻译成汇编语言的目标程序

3.2 在Ubuntu下编译的下令

Linux> gcc -S hello.i -o hello.s

图 3-1 Ubuntu下的编译下令

3.3 Hello的编译结果解析



图 3-2 hello.s代码及注释

3.3.1 数据
1 常量

图 3-3 常量字符串的八进制编码(utf-8编码格式)


对.LC0进行解码后可以得到字符串"用法: Hello 学号 姓名 秒数!\n",恰好对应hello.c中第一个printf函数打印的字符串内容,而.LC1直接保存了字符串”Hello %s %s\n”,对应第二个printf函数打印的内容.
2 变量
由于hello.c中并没有出现全局变量,因此我们只讨论局部变量
main函数中存在的局部变量有int argc、char **argv、int i,它们的首地 址分别存储于(%rsp-20)、(%rsp-32)、(%rsp-4),此中argc和i是整型变量, **argv 为整型数组,argc表现输入参数的个数,**argv是输入参数的字符串 数组,i为函数内界说的局部变量

3.3.2 赋值
此处赋值语句比力多,注释中已经体现出来了,此处举两个例子说明:
①为(%rbp-4)赋值0,对应局部变量i赋初值0
②为%eax赋值0,对应return 0语句

3.3.3 类型转换
该汇编代码中仅存在一处类型转换,即atoi函数将*argv[3](char *转换成int型变量

3.3.4 算术操作
此处在代码中有多处体现,均已在注释中说明,以下仅举出两个例子:
①在.L4中使用语句addq $16 %rax,使%rax += 16,跳转至*argv[2]
②.L4末了一句进行i = i+1的操作



3.3.5 关系操作
在.LFB5中有语句cmpl $4, -20(%rbp)以及je .L2,对应if(argc != 4),如果argc = 4则跳转至.L2,否则顺序执行跳过je .L2语句

3.3.6 控制转移
代码中有多处控制转移操作,比如je .L2、jmp .L3、jle .L4,详细说明均在注释中体现

3.3.7 函数操作
代码多次将第一、第二、第三个参数传递给了%rdi、%rsi、%rdx.并且多次调用函数,如printf、atoi、sleep,同时函数返回值均存储在%rax中,详细说明已在注释中体现
3.4 本章小结

本章先容了编译的概念和作用,同时详细解释了编译器是按照什么样的逻辑将hello.i文件翻译成hello.s文件,以及汇编代码中差别语句代表的含义,可以看出汇编代码相比于源代码更高效,同时变得更加难懂,更适合给呆板明白


第4章 汇编


4.1 汇编的概念与作用


概念:将汇编语言转化成呆板语言的过程,汇编器会将这些呆板语言打包成可 重定位目标程序,并将结果保存在hello.o中,此中的呆板代码由01字符 串构成
作用:汇编这一步调真正实现了将程序由高级程序设计语言转化为呆板可以读 懂的呆板语言的过程。在完成了这一步之后,呆板终于可以开始执行输入 的程序了

4.2 在Ubuntu下汇编的下令

Linux> gcc -c hello.s -o hello.o

图4-1 ubuntu下的汇编下令

4.3 可重定位目标elf格式

下令:readelf -a hello.o > hello.elf生成elf文件

图 4-2 生成ELF文件

   
图4-3 ELF头


 ELF头包含了有关可重定位目标文件以及有关呆板的许多信息,包罗幻数、种别、数据、版本等信息。

图4-4 节头


节头包含了有关差别节的所有信息,包罗巨细、类型、起始地址、偏移量等


图4-5 重定位节


重定位节包含了一个.text 节中位置的列表,当链接器把这个目标文件和其他文件组合时,必要修改这些位置。一般而言,任何调用外部函数大概引用全局变量的指令都必要修改。另一方面,调用当地函数的指令则不必要修改。注意,可执行目标文件中并不必要重定位信息,因此通常省略,除非用户显式地指示链接器包含这些信息。分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

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

4.4 Hello.o的结果解析

反汇编指令:objdump -d -r hello.o

图 4-7 反汇编指令得到的汇编代码


由对比可知,数字的进制有所变化,由原先的十进制转换成了十六进制
objdump -d -r hello.o  分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。而且函数调用采用了绝对寻址和相对寻址的方式,并且在分支转移时并没有使用.L
4.5 本章小结


本章实现了从 hello.s到hello.o的转化,同时查看hello.o的elf格式,发现其由ELF头、节头、符号表等内容构成,随后使用 objdump得到反汇编代码,并将其与原先的汇编代码hello.s进行比力

5链接


5.1 链接的概念与作用

链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成呆板代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。链接器使得分离编译成为大概。
5.2 在Ubuntu下链接的下令

Linux> ld -dynanic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o -o hello

图5-1 Ubuntu下链接的下令

使用ld的链接下令,应截图,展示汇编过程! 注意不但连接hello.o文件
5.3 可执行目标文件hello的格式

    
图5-2 查看可执行文件hello的elf文件



图5-3 ELF头

我们仍然得到了ELF头,与之前差别的是,该ELF头多收支口点地址这一条信息,这表明我们完成了链接


图5-4 节头

节头:节头席卷了程序中所有节的信息,包罗每个节的名称、巨细、偏移地址等。然而,与hello.oELF程序相比,helloELF程序在许多方面有所差别,包罗但不限于节的个数,节的巨细,以及地址的偏移值。这三种现象的前两种可以用差别可重定位文件的合并来解释,而末了一种可以用重定位本身来解释。

图5-5 程序头


程序头:这一部分在hello.o的ELF文件中未出现过,其主要作用是给出hello这个可执行文件中存在的程序信息,譬如程雪的偏移地址、文件的巨细和内存的巨细等等


图5-6 符号表

 符号表:helloELF格式的符号个数也相比之前有明显的增加,而每个符号也有了对应的值(Value),这无疑是重定位的结果。
5.4 hello的捏造地址空间


图5-7 edb加载hello可执行文件



    上图给出了elf文件中的程序头部表,描述了可执行文件连续的片和连续的内存段的映射关系。此中包罗段:

PHDR:程序头表

INTERP:程序执行前必要调用的解释器

LOAD:程序目标代码和常量信息

DYNAMIC:动态链接器使用信息

NOTE:保存辅助信息

GNU_EH_FRAME:保存非常信息

GNU_STACK:标志栈是否可用的标志信息

GNU_RELRO:保存在重定位之后只读信息的位置

以PHDR为例,在edb中查看PHDR段。

   
图5-8 edb查看Data Dump

对比后可发现起始位置与ELF同等
5.5 链接的重定位过程分析


图5-9 hello反汇编代码

hello汇编代码中出现了更多函数,hello.o的汇编代码中只出现了main函数的名字,而hello的汇编代码中出现了_init,.plt等函数名。这体现了许多非hello.c源程序中生命的函数被链接到了hello中。
5.6 hello的执行流程

hello执行流程如下:
(1)ld-linux-x86-64.so!_dl_start
(2)ld-linux-x86-64.so!_dl_init
(3)hello!_start
(4)hello!__libc_csu_init
(5)hello!_init
(6)libc.so!_setjmp
(7)hello!main
(8)hello!puts@plt
(9)ld-linux-x86-64.so!_dl_runtime_resolve_xsave
(10)ld-linux-x86-64.so!_dl_fixup
(11)ld-linux-x86-64.so!_dl_lookup_symbol_x
(12)hello!exit@plt
(13)libc.so!exit
(14)hello!_fini

5.7 Hello的动态链接分析

  动态链接库中的函数在程序执行的时候才会确定地址,所以编译器无法确定其地址。为避免运行时修改调用模块的代码段,链接器采用延迟绑定的计谋。延迟绑定通过两个数据结构之间简洁但又有些复杂的交互来实现,即过程链接表(PLT)和全局偏移量表(GOT)。
   过程链接表(PLT):PLT是一个数组,此中每个条目是16字节代码。PLT [0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。每个条目都负责调用一个具体的函数。
全局偏移量表(GOT):GOT是一个数组,此中每个条目是8字节地址。和PLT团结使用时,GOT [0]和GOT [1]包含动态链接器在解析函数地址时会使用的信息。GOT [2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址必要在运行时被解析。每个条目都有一个相匹配的PLT条目。
在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向 PLT中的代码逻辑,GOT存放的是PLT中函数调用指令的下一条指令地址
5.8 本章小结

在本章中主要先容了链接的概念与作用、hello 的 ELF 格式,hello 的 捏造地址空间、重定位过程、执行流程、动态链接过程


6hello历程管理


6.1 历程的概念与作用

历程是一个执行中的程序的实例,每一个历程都有它自己的地址空间,一般 情况下,包罗文本区域、数据区域和堆栈。文本区域存储处置惩罚器执行的代码;数据区域存储变量和历程执行期间使用的动态分配的内存;堆栈区域存储区着活动 过程调用的指令和当地变量。历程为用户提供了以下假象:我们的程序似乎是系统中当前运行的唯一程序一样,我们的程序似乎是独占的使用处置惩罚器和内存,处置惩罚器似乎是无中断的执行我们程序中的指令,我们程序中的代码和数据似乎是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处置惩罚流程

Shell俗称壳,是指"为使用者提供操作界面"的软件。同时它又是一种程序设计语言。作为下令语言,它交互式解释和执行用户输入的下令大概自动地解释和执行预先设定好的一连串的下令。它作为用户操作系统与调用其他软件的工具。
处置惩罚流程:
(1)从终端读入输入的下令。
(2)将输入字符串切分,分析输入内容,解析下令和参数。
(3)如果下令为内置下令则立即执行,如果不是内置下令则创建新的历程调用相应的程序执行。
(4)在程序执行期间始终担当键盘输入信号,并对输入信号做相应处置惩罚。
6.3 Hello的fork历程创建过程

在终端输入./hello的输入下令后,shell进行下令行解释,由于不是内部指令,通过fork函数创建子历程,新创建的子历程险些但不完全与父历程雷同,子历程得到与父历程用户及捏造地址空间雷同的(但是独立的)一份副本,包罗代码和数据段、堆、共享库以及用户栈,子历程还获得与父历程任何打开文件,描述符雷同的副本,这就意味着,当父历程调用fork函数时,子历程可以读写父历程中打开的任何文件。

4 Hello的execve过程

子历程创建后,shell调用execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。之后当出现错误时,例如找不到hello,execve才会返回到调用程序。
在execve加载了hello后,它调用启动代码,启动代码设置栈,并将控制转移给新程序的主函数main,此时用户栈已经包含了下令行参数和环境变量,进入main函数后开始逐步运行程序

6.5 Hello的历程执行

上下文信息:上下文就是内核重新启动一个被抢占的历程所必要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象构成。
时间片:一个历程执行它的控制流的一部分的每一个时间段。
调度:在执行过程中,内核可以决定抢占当前历程,并重新开始一个先前被抢占的历程。
用户态:历程运行在用户模式中时,不允许执行特权指令,比如克制处置惩罚器、改变模式位,大概发起一个I/O操作,也不允许用户模式中的历程直接引用地址空间中内核区内的代码和数据。
核心态:历程运行在内核模式中时,可以执行指令集中的任何指令,并且可以访问内存中的恣意位置。
用户态与核心态转换:程序在涉及到一些操作时,例如调用一些系统函数,内核必要将当前状态从用户态切换到核心态,执行结束后再改回用户态。
 
hello执行时存在逻辑控制流,多个历程的逻辑控制流在时间上可以交错,表现为交替运行。历程控制权的互换必要上下文切换。操作系统内核使用一种成为上下文切换的较高层形式的非常控制流来实现多任务。内核为每个历程维持一个上下文。上下文就是内核重新启动一个被抢占的历程所需的状态。
例如hello中对sleep的调用,内核中的调度器将hello历程挂起,进入内核模式,在执行结束后,内核会恢复hello被抢占时的上下文,回到用户模式。

6.6 hello的非常与信号处置惩罚



非常类型:



克制(interrupt):来自I/O设备的信号,而并不是来自与程序指令,因此是异步的。

陷阱(trap)和系统调用:是执行一条指令的结果,譬如创建一个历程、加载一个程序、读一个文件等等。

故障(fault)由错误情况引起,但是可以被故障处置惩罚程序修正,如缺页非常,即必要从磁盘而不是从内存中取出必要的信息。

终止(abort)是不可恢复的致命错误所造成的结果,唯一的办理计谋就是终止这个程序。

hello.c产生的信号:STGINT,SIGSTP


图6-1 Ctrl+C对历程的影响

可以看到,在程序运行时,输入Ctrl+C,程序终止


图 6-2 Ctrl+Z对程序的影响

输入Ctrl+Z,程序克制

6.7 本章小结

本章先容了历程的概念和作用,团结fork和execve函数说明了hello历程的执行过程,之后分析了历程执行过程中非常和信号的处置惩罚问题。至此,可执行目标文件成功被加载至内存并执行
7hello的存储管理


7.1 hello的存储器地址空间

图7-1 捏造地址寻址方式

逻辑地址:在带有地址变更功能的计算机中,当用户查询地址时,呆板给出的地址就是逻辑地址。逻辑地址不肯定对应着存储信息的真实地址,大概还必要经过肯定的转换。

线性地址:线性地址是逻辑地址转酿成物理地址的中间层,通过逻辑地址变更产生。在没有采取分页机制的系统中,线性地址即为物理地址。

捏造地址:捏造地址是带捏造内存的计算机中捏造空间所对应的地址。在某些情况下捏造地址就是线性地址。

物理地址:物理地址又称实际地址、真实地址,是计算机真实储存文件的地址,是逻辑地址经过一系列变更之后得出的。
7.2 Intel逻辑地址到线性地址的变更-段式管理

段式管理,即把一个程序分成多少段,此中每一段都是一个逻辑实体,这有点雷同于程序的模块化。每个段地址都又两部分构成:其一是段名(也是地址形式的),其二是段偏移量。而段名又分为两部分:索引号以及种类标识符。索引号就是单纯的地址,而种类标识符标识的是寄存器存储数据的种类。于是,就可以根据这些信息确定地址。其缺点是大概有碎片化残余。
7.3 Hello的线性地址到物理地址的变更-页式管理

分页机制是实现捏造存储的关键,位于线性地址与物理地址的变更之间设置。捏造内存被构造为一个由存放在磁盘上的N个连续的字节巨细的单元构成的数组。每字节都有一个唯一的捏造地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。和存储器层次结构中其他缓存一样,磁盘上的数据被分割成块,这些块作为磁盘和主存之间的传输单元。VM系统通过将捏造内存分割为称为捏造页为巨细固定的块来处置惩罚这个问题。每个捏造页的巨细固定。雷同地,物理内存被分割为物理页,巨细与捏造页雷同。
同任何缓存一样,捏造内存系统必须用某种方法来判断一个捏造页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个捏造页存放在哪个物理页中。如果不掷中,系统必须判断这个捏造页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将捏造页从磁盘复制到DRAM,替换这个牺牲页。
页表是一个存放在物理内存中的数据结构,将捏造页映射到物理页。每次地址翻译硬件将一个捏造地址转换为物理地址时读取页表。操作系统负责维护页表中的内容,以及再磁盘与DRAM之间来回传送页。
内存分页管理的基本原理是将整个内存区域划分成固定巨细的内存页面。程序申请使用内存时就以内存页位单位进行分配。转换通过两个表,页目录表PDE(也叫一级目录)和二级页表PTE。历程的捏造地址必要首先通过其局部段描述符变更为CPU整个线性地址空间中的地址,然后再使用页目录表和页表PTE映射到实际物理地址上。

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

每次CPU产生一个捏造地址,MMU就必须查阅一个PTE,以便将捏造地址翻译为物理地址。为了降低时间开销,MMU中包罗了一个关于PTE的小的缓存,称为翻译后备缓冲器。
TLB是一个小的、捏造寻址的缓存,此中每一行都保存着一个由单个PTE构成的块,下述为一个从TLB中获取物理地址的过程:
(1)CPU产生一个捏造地址。
(2)MMU从TLB中取出相应的PTE。
(3)MMU将这个捏造地址翻译成一个物理地址,并且将它发送到高速缓存/主存。
(4)高速缓存/主存将所请求的数据字返回给CPU。

图7-2 团结高速缓存和捏造内存的寻址方式

Inter Core i7实现支持48位捏造地址空间和52位物理地址空间,使用4KB的页。X64 CPU上的PTE为64位,所以每个页表一共有512个条目。512个PTE条目必要9位VPN定位。再四级页表的条件下,一共必要36位VPN,因为捏造地址空间是48位,故低12位是VPO。TLB四路组联,共有16组,必要4位TLBI,故VPN的低4位是TLBI,高32位是TLBT。
CPU产生捏造地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT+TLBI向TLB中匹配,如果掷中,则得到40位PPN+12位VPO组合成52位物理地址PA。如果没有掷中,MMU向页表中查询,CR3确定第一级页表的起始地址,9位VPN1确定在第一级页表中的偏移量,查询出第一部分PTE,以此类推终极在四级页表都访问完后获得PPN,与VPO团结获得PA,并向TLB中更新。

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

L1 Cache 8路64组相联,块巨细64B。解析前提条件:因为共64组,所以必要6bit CI进行组寻址,共有8 路,块巨细为64B所以必要6bit CO表现数据偏移位置。因为VA共 2bit,所以CT共40bit。 使用CI进行组索引,每组8路,对8路的块分别匹配CT(前40位)如果匹配成功 且块的valid 标志位为1,则掷中(hit),根据数据偏移量 CO(后六位)取出数据返回。如果没有匹配成功大概匹配成功但是标志位是1,则不掷中,向下一级缓存中查询数据(L2 Cache->L3 Cache->主存)。查询到数据之后,一种简单的放置计谋如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块, 产生辩论,则采用最近最少使用计谋 LFU 进行替换。

图7-3 Intel Core i7 高速缓存层次结构

7.6 hello历程fork时的内存映射

当hello历程被fork时,fork会创建当前历程的内存副本和地址副本,两个副本的标识符分别写作mm_struct和xm_area_struct,这两个标识符的存在保证了父历程和子历程共用雷同的地址空间和存储结构。而为了保证父历程和子历程不受干扰,这两个历程的xm_area_struct都会被设置成私有(private),历程中的每个页面也会被标志成只读。
7.7 hello历程execve时的内存映射

如上一章所述,execve函数的主要功能是在当前的历程中重新运行一个程序,在hello的例子中,就是删除分配给子历程那部分地址的程序,并运行hello程序,再先后映射到私有区域(譬如相应的栈和堆)和共享区域(譬如hello和共享对象的动态链接)。在execve这一步的末了,要设置程序计数器(PC),调整历程下一步的入口点,从hello程序开始的位置执行程序。
7.8 缺页故障与缺页克制处置惩罚

物理内存缓存不掷中称为缺页。假设CPU引用了磁盘上的一个字,而这个字所属的捏造页并没有缓存在DRAM中。地址翻译硬件会从内存中读取捏造页对应的页表,说明这个捏造页没有被缓存,触发一个缺页故障。
这个非常导致控制转移到内核的缺页处置惩罚程序,处置惩罚程序首先判断捏造地址A是否正当,如果不正当则触发段错误终止历程。如果正当则判断试图进行的内存访问是否正当,如果不正当则出发掩护非常终止历程。如果正当则根据页式管理的规则,选择一个牺牲页,用新页替换掉,更新页表并再次触发地址翻译硬件进行翻译

图7-4 缺页处置惩罚

7.9动态存储分配管理

 动态内存分配器维护着一个历程的捏造内存区域,称为堆。分配器将堆视为一组差别巨细的块的聚集来维护。每个块就是一个连续的捏造内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被开释,这种开释要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
    分配器分为两种:显式分配器和隐式分配器。显式分配器要求应用显式地开释人设已分配地块。隐式分配器要求分配器检测一个已分配块何时不再被程序所使用,那么就开释这个块。隐式分配器也叫做垃圾回收器,而自动开释未使用的已经分配的块的过程叫做垃圾收集。
    malloc使用的是显式分配器,通过free函数开释已分配的块。
    下面分别先容两种分配器:
    (1)隐式空闲链表分配器。我们可以将堆构造为一个连续的已分配块和空闲块的序列,空闲块是通过头部中的巨细字段隐含地连接着的,这种结构为隐式空闲表。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块地聚集。一个块是由一个字的头部、有效载荷、大概的填充和一个字的脚部,此中脚部就是头部的一个副本。头部编码了这个块的巨细以及这个块是已分配照旧空闲的。分配器就可以通过检查它的头部和脚部,判断前后块的起始位置和状态。
    (2)表现空闲链表分配器。将堆构成一个双向空闲链表,在每个空闲块中,都包含一个pred和succ指针。一种方法是用后进先出(LIFO)的顺序来维护链表,将新开释的块放置在链表的开始处。使用LIFO的顺序和首次适配的放置计谋,分配器会开始检查最近使用过的块。在这种情况下,开释一个块可以在常数时间内完成。如果使用了边界标志,那么合并也可以在常数时间内完成。另一种方法是按照地址顺序来维护链表,此中链表中每个块的地址都小于它后继的地址,在这种情况下,开释一个块必要线性时间的搜刮来定位合适的前驱。均衡点在于,按照地址地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,靠近最佳适配的利用。一般而言,显式链表的缺点是空闲块必须足够大,以包含所有必要的指针,以及头部和大概的脚部。这就导致了更大的最小块巨细,也潜在的进步了内部碎片的程度。

7.10本章小结

本章主要先容了hello的程序020过程以及它在运行过程中遇到的有关地址转换和内存映射的问题,包罗段式管理、页式管理、三级缓存管理、fork和execve时的内存映射等等。它们既是程序运行分析的重点,也是程序运行分析的难点。

8hello的IO管理


8.1 Linux的IO设备管理方法

设备的模型化:文件
设备管理:unix io接口
在Linux系统中,所有的I/O设备都被转化成文件,而所有的输入和输出就都被当做对对应文件的读和写,被同一到了一个名为Unix的I/O接口中。这样的操作无疑增加了Linux处置惩罚I/O设备的模式性和同等性。
8.2 简述Unix IO接口及其函数

1、Unixix IO接口:Unix IO接口以完成多少功能。首先是打开文件,操作系统内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操作。还可以改变当前的文件位置,文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。接口还能进行读写文件,完成信息传输。所有操作完成后关闭文件,当应用完成对文件的访问后,通知内核关闭这个文件。内核会开释文件打开时创建的数据结构,将描述符恢复到描述符池中。

2、Unixix IO函数

(1)open()函数:打开文件

(2)lseek()函数:将文件指针定位到相应位置

(3)read()函数:读文件

(4)write()函数:写文件

(5)close()函数:关闭文件
8.3 printf的实现分析

我们调用vsprintf函数后,vsprintf函数将我们必要输出的字符串格式化并把内容存放在缓冲区中。并返回要输出的字符个数i。然后调用系统函数write来在屏幕上打印缓冲区中的前i个字符,也就是我们要输出的字符串。调用write系统函数后,程序进入到陷阱,系统调用 int 0x80或syscall等,通过字符驱动子程序打印我们的字符串。

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

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

末了程序返回实际输出的字符数量i。
8.4 getchar的实现分析

异步非常-键盘克制的处置惩罚:当用户按键时,键盘接口会得到一个代表该按键 的键盘扫描码,同时产生一个克制请求,克制请求抢占当前历程运行键盘克制子 程序,键盘克制子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码 转换成ASCII码,保存到系统的键盘缓冲区之中。getchar函数落实到底层调用了系统函数read,通过系统调用read读取存储在键盘缓冲区中的ASCII码直到读到回车符然后返回整个字串,getchar进行封装,大体逻辑是读取字符串的第一个字符然后返回。
8.5本章小结

本章先容了Linux的IO设备管理方法、Unix IO接口及其函数,并分析了printf函数和getchar函数。
结论

Hello的一生看似简单,实则包含了许多复杂而又灵巧的运算,从P2P到020,Hello分别经历了预处置惩罚、编译、汇编、链接、创建历程、访问内存、申请动态内存、非常、终止等一系列过程
hello是一个非常渺小的程序,hello的一生也注定是短暂的一生,然而正是这转瞬即逝的一段旅程,涉及到了计算机系统方方面面的内容,这些内容是计算机运行程序最本质、最核心的方式。
附件

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

hello.i 预处置惩罚文件

hello.s 汇编文件

hello.o 可重定位目标文件

hello 可执行文件

hello.elf hello.o的elf文件

hello_rel.elf 链接后的hello的elf文件

asm.txt hello的反汇编代码


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

参考文献


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


  •  兰德尔·E·布莱恩特,大卫·R·奥哈拉伦著;深入明白计算机系统[M].北京:呆板工业出版社,2016.7
  • 捏造地址、逻辑地址、线性地址、物理地址_安卓视频必要捏造地址物理地址吗_rabbit_in_android的博客-CSDN博客
  • ELF构
  • 明白汇编语言的作用_汇编语言有什么用_东洋 Dongyang的博客-CSDN博客

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表