步伐人生-Hello’s P2P

打印 上一主题 下一主题

主题 820|帖子 820|积分 2460






摘  要

本文以hello.c这一简朴c语言文件研究了步伐在Linux系统下的整个生命周期,以其原始步伐开始,依次深入研究了编译、链接、加载、运行、终止、回收的过程,从而了解hello.c文件的“一生”。这是一个最简朴的步伐,却和最复杂的步伐有着同样的生命周期履历。本文从一个步伐员的视角展示了计算机系统的机制。
关键词:计算机系统;步伐生命周期;预处理;进程               

(择要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的P2P是指hello.c文件从可执行步伐(Program)变为运行时进程(Process)的过程。在Linux系统下,hello.c 文件依次颠末cpp(C Pre-Processor,C预处理器)预处理、ccl(C Compiler,C编译器) 编译、as (Assembler,汇编器)汇编、ld (Linker,链接器)链接最终成为可执行目标步伐hello(在Linux下该文件无固定后缀)。打开shell,输入命令./hello后,shell 通过fork产生子进程,hello 便从可执行步伐(Program)变成为进程(Process)。

Hello的020是指hello.c文件“From 0 to 0”,初始时内存中并无hello文件的相干内容,这便是“From 0”。通过在Shell下调用execve函数,系统会将hello文件载入内存,执行相干代码,当步伐运行结束后, hello进程被回收,并由内核删除hello相干数据,这即为“to 0”。

1.2 环境与工具

X64 CPU;2GHz;2G RAM;256GHD Disk 以上

Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;

Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc

1.3 中间效果

文件名

功能

hello.i

预处理后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

汇编后得到的可重定位目标文件

hello.elf

用readelf读取hello.o得到的ELF格式信息

hello.asm

反汇编hello.o得到的反汇编文件

hello2.elf

由hello可执行文件生成的.elf文件

hello2.asm

a.out

反汇编hello可执行文件得到的反汇编文件

可执行文件

1.4 本章小结

本章概述了hello 的P2P,020,同时列出了研究时接纳的具体软硬件环境和中间效果。

(第1章0.5分)



第2章 预处理

2.1 预处理的概念与作用

预处理步调是指步伐开始运行时,预处理器(cpp,C Pre-Processor,C预处理器)根据以字符#开头的命令,修改原始的C步伐的过程。比方,hello.c文件6到8行中的#include 命令会告诉预处理器读取系统头文件stdio.h,unistd.h,stdlib.h 的内容,并把这些内容直接插入到步伐文本中。用实际值更换用#define界说的字符串。除此之外,预处理过程还会删除步伐中的注释和多余的空白字符。预处理通常得到另一个以.i作为拓展名的C步伐。

  预处理过程将#include后继的头文件内容直接插入步伐文本中,完成字符串的更换,方便后续处理。预处理过程中并未直接剖析步伐源代码的内容,而是对源代码进行相应的分割、处理和更换。简朴来说,预处理是一个文本插入与更换的过程,生成的hello.i文件仍然是文本文件。
2.2在Ubuntu下预处理的命令

在Ubuntu系统下,进行预处理的命令为: 

linux> gcc -E -o hello.i hello.c
运行效果如图1所示。

图1:对hello.c进行预处理


2.3 Hello的预处理效果剖析


图2:hello.i在文本查看器下的内容
由图2所示,在预处理后文件增加到了3104行,紧张是进行了宏更换和头文件的展开。以stdio.h头文件的展开为例,CPP先删除指令#include <stdio.h>,并到Ubuntu系统的默认的环境变量中寻找 stdio.h,最终打开路径/usr/include/stdio.h下的stdio.h文件。若stdio.h文件中利用了#define语句,则按照上述流程继续递归地展开,直到所有#define语句都被解释更换掉为止。除此之外,CPP还会进行删除步伐中的注释和多余的空白字符等操作,并对一些值进行更换。



2.4 本章小结

本章紧张介绍了预处理的概念及作用、并团结Ubuntu系统下hello.c文件实际预处理之后得到的hello.i步伐对预处理效果进行了剖析。
(第2章0.5分)


第3章 编译


3.1 编译的概念与作用

编译是指C编译器ccl通过词法分析和语法分析,将正当指令翻译成等价汇编代码的过程。通过编译过程,编译器将文本文件 hello.i 翻译成汇编语言文件 hello.s,在hello.s中,以文本的情势形貌了一条条低级呆板语言指令。

编译可以将文本文件翻译成汇编语言步伐,为后续将其转化为二进制呆板码做预备



3.2 在Ubuntu下编译的命令
命令行:linux> gcc -S hello.i -o hello.s
编译如图3所示。
图3:将hello.i编译为hello.s文件

3.3 Hello的编译效果剖析

3.3.1 数据范例
  一.整数型
1.立刻数
立刻数在文件中以$+数字的情势出现,一个例子如图4所示。

图4:立刻数示例

2.函数的局部变量
Int i作为函数的局部变量被编译器生存在栈当中,如图5所示。从图中可以看出其占用了四个字节。

5:局部变量i

3.main函数的参数argc
与局部变量雷同都生存在堆栈当中。

二.字符串
hello.s中字符串如图6所示,均存储在.text代码段中。

图6:文件中的字符串


三.数组


步伐中涉及的数组为char *argv[],即函数的第二个参数。在hello.s中,其首地址生存在栈中。访问时,通过寄存器寻址的方式访问。详细内容如图7、8所示

图7、8:*argv在文件中的位置和访问


赋值操作
int i
对局部变量的赋值在汇编代码中通过mov指令完成,而mov指令的后缀由传送的字节数确定,详细内容如表3所示。
表3 mov指令的后缀

后缀

b

w

l

q

大小(字节)

1

2

3

4



算数操作
汇编语言中,算数操作的指令包罗:
表格 4 算数操作指令

指令

效果

leaq s,d

d=&s

inc d

d+=1

dec d

d-=1

neg d

d=-d

add s,d

d=d+s

sub s,d

d=d-s

imulq s

r[%rdx]:r[%rax]=s*r[%rax](有符号)

mulq s

r[%rdx]:r[%rax]=s*r[%rax](无符号)

idivq s

r[%rdx]=r[%rdx]:r[%rax] mod s(有符号) r[%rax]=r[%rdx]:r[%rax] div s

divq s

r[%rdx]=r[%rdx]:r[%rax] mod s(无符号) r[%rax]=r[%rdx]:r[%rax] div s

在hello.s中,具体涉及的算数操作包罗:
subq $32, %rsp:开发栈帧
addq $16, %rax:修改地址偏移量
addl $1, -4(%rbp):实现i++的操作

图 9 算数操作示例

关系操作
在hello.s中,具体涉及的关系操作包罗:
不等于
检查argc是否不等于4。在hello.s中,利用cmpl $4,-20(%rbp),比较 argc与4的大小并设置条件码,为下一步je利用条件码进行跳转作预备。

图 10 检查argc!=4

小于:
检查i是否小于10。在hello.s中,利用cmpl $9, -4(%rbp)比较i与9的大小,然后设置条件码,为下一步jle利用条件码进行跳转做预备。

图 11 检查i<10


数组操作

对数组的访问操作通过寄存器寻址方式实现。
图12:对数组的访问

控制转移
步伐中控制转移的具体表现有两处:
控制转移:

当argc不等于3时,执行函数体内部的代码。在hello.s中,利用cmpl $3,-20(%rbp),比较 argc与3是否相等,若相等,则跳转至.L2,不执行后续部门内容;若不等则继续执行函数体内部对应的汇编代码。

图 13 控制转移

循环指令:
当i < 10时进行循环,每次循环i++。在hello.s中,利用cmpl $9,-4 (%rbp),比较 i与9是否相等,在i<=9时继续循环,进入.L4,i>9时跳出循环。

图 14 for循环的实现


函数操作
C语言中,调用函数时进行的操作如下:
传递控制:
进行过程 Q 的时间,步伐计数器必须设置为 Q 的代码的起始地址,然后在返回时,要把步伐计数器设置为 P 中调用 Q 后面那条指令的地址。
传递数据:
P 必须可以或许向 Q 提供一个或多个参数,Q 必须可以或许向 P 中返回一个值。
分配和开释内存:
在开始时,Q 大概须要为局部变量分配空间,而在返回前,又必须开释这些空间。

在hello.s中共调用了main, puts,printf,sleep,exit,getchar 等函数,对函数的调用都通过call指令进行。一个调用的实比方图12所示

图 12 call指令示例


3.4 本章小结

编译器通过词法分析和语法分析,在确认所有的代码都符合语法规则之后,对我们的代码做一些优化,将步伐翻译成汇编代码,生成我们须要的hello.s文件。

对于一些操作(比方for循环和if判断),编译器会智能地简化过程,并将其转换为汇编语言的控制逻辑;编译器还可以证明函数的调用和跳转,并在堆栈框架和操作中平衡它们。

(第3章2分)


第4章 汇编


4.1 汇编的概念与作用

汇编是指汇编器(assembler)将以.s末端的汇编步伐翻译成呆板语言指令,并把这些指令打包成可重定位目标步伐格式,最终效果生存在.o 目标文件中的过程。其作用是将汇编语言翻译为呆板语言,并将相干指令以可重定位目标步伐格式生存在.o文件中。

4.2 在Ubuntu下汇编的命令



命令行:linux> gcc -c hello.s -o hello.o
汇编后效果如图13所示。
图13:汇编效果

4.3 可重定位目标elf格式

4.3.1得到elf文件
指令:readelf -a hello.o > elf.txt
4.3.2分析elf文件
一.ELF头
Elf头以一个16进制的序列开始,这个序列形貌了生成该文件的系统的字的大小和字节序次,ELF头剩下的部门包罗资助链接器语法分析和解释目标文件的信息,其中包罗ELF头的大小、目标文件的范例、呆板范例、字节头部表的文件
偏移,以及节头部表中条目标大小和数量等信息。不同节的位置和大小是由节头部表形貌的,其中目标文件中每个节都有一个固定大小的条目。如图14所示。

图14:elf头

二. 节头部表。

节头部表包罗了文件中出现的各个节的语义,包罗节的范例、位置和大小等信息。目标文件中每个节都有一个固定大小的条目。如图15所示。


15:节头部表
三.rela.text节


一个.text节中位置的列表,包罗.text节中须要重定位的信息(比方hello.o中的getchar,exit等的重定位信息),须要利用链接器在组合时将这些位置链接。如图16所示。
图16:rela.text节

四.符号表
符号表中生存着定位、重定位步伐中符号界说和引用的信息,所有重定位须要引用的符号都在其中声明。如图17所示。

17:符号表
4.4 Hello.o的效果剖析


18:部门反汇编代码
反汇编内容如图18所示。可以看出反汇编代码与前文在分支转移、函数调用、全局变量访问中均有不同。
4.5 本章小结

本章介绍了汇编的概念与作用,在Ubuntu下通过实际操作将hello.s文件翻译为hello.o文件,并生成hello.o的ELF格式文件hello.elf,研究了ELF格式文件的具体结构。通过比较hello.o的反汇编代码(生存在hello.asm中)与hello.s中代码,
了解了汇编语言与呆板语言的异同之处。
(第4章1分)


5链接


5.1 链接的概念与作用

链接是指通过链接器,将步伐编码与数据块网络并整理成为一个单一文件,生成完全链接的可执行的目标文件(windows系统下为.exe文件,Linux系统下一般省略后缀名)的过程。链接提供了一种模块化的方式,可以将步伐编写为一个较小的源文件的集合,且实现了分开编译更改源文件,从而减少整体文件的复杂度与大小,增加容错性,也方便对某一模块进行针对性修改。

5.2 在Ubuntu下链接的命令

命令行: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
链接效果如图19所示。

19:链接后生成了可执行目标文件hello
5.3 可执行目标文件hello的格式

   在Shell中输入命令 readelf -a hello > hello2.elf 生成 hello 步伐的 ELF 格式文件,生存为hello2.elf(与第四章中的elf文件作区分)。详细的分析如下。
与可重定位文件结构类似。在ELF格式中,节头部表(Section Headers)对hello的各个节的信息做了说明,包罗各段的起始地址(Address),大小(size),范例(Type),偏移(Offset)对齐要求(Align)等信息。头大小和节头数量增加,而且得到了入口地址。如图20所示。

20:hello2.elf中的ELF头。紧张是范例发生改变、节头增加。
其他的部门与hello.elf类似,不再赘述。
5.4 hello的虚拟地址空间

   打开edb,通过 data dump 查看加载到虚拟地址的步伐代码。

图 21 加载到虚拟地址的步伐代码

根据计算机系统的特性,步伐被载入至地址0x400000~0x401000中。在该地址范围内,每个节的地址都与前一节中节对应的 Address 雷同。根据edb查看的效果,在地址空间0x400000~0x400fff中存放着与地址空间0x400000~0x401000雷同的步伐,在0x400fff之后存放的是.dynamic到.shstrtab节的内容。
  
5.5 链接的重定位过程分析

在Shell中利用命令objdump -d -r hello > hello2.asm生成反汇编文件hello2.asm,与第四章中生成的hello.o.asm文件进行比较,其不同之处紧张有①增加的节:hello中增加了.init和.plt节(以及一些节中界说的函数)②增加的函数:在hello中链接到场了在hello.c中用到的函数,如exit、printf、sleep等函数③函数调用:hello中无hello.o中的重定位条目,而且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。④地址访问:hello.o中的相对偏移地址变成了hello中的虚拟内存地址。部门反汇编代码如图32所示。


22:部门反汇编代码




5.6 hello的执行流程

详细内容参见表4。
表4 步伐名称与步伐地址

步伐名称

步伐地址

ld-2.27.so!_dl_start

0x7fce8cc38ea0

ld-2.27.so!_dl_init

0x7fce8cc47630

hello!_start

0x400500

libc-2.27.so!__libc_start_main

0x7fce8c867ab0

-libc-2.27.so!__cxa_atexit

0x7fce8c889430

-libc-2.27.so!__libc_csu_init

0x4005c0

hello!_init

0x400488

libc-2.27.so!_setjmp

0x7fce8c884c10

-libc-2.27.so!_sigsetjmp

0x7fce8c884b70

--libc-2.27.so!__sigjmp_save

0x7fce8c884bd0

hello!main

0x400532

hello!puts@plt

0x4004b0

hello!exit@plt

0x4004e0

*hello!printf@plt

--

*hello!sleep@plt

--

*hello!getchar@plt

--

ld-2.27.so!_dl_runtime_resolve_xsave

0x7fce8cc4e680

-ld-2.27.so!_dl_fixup

0x7fce8cc46df0

--ld-2.27.so!_dl_lookup_symbol_x

0x7fce8cc420b0

libc-2.27.so!exit

0x7fce8c889128

5.7 Hello的动态链接分析

   课堂中涉及到的动态链接部门不多,以下内容紧张参考CSAPP和Hello的一生_啾星的小猫猫的博客-CSDN博客、https://blog.csdn.net/m0_51180992/article/details/124783702中的内容。
在edb调试之后我们发现原先0x00600a10开始的global_offset表是全0的状态,在执行过_dl_init之后被赋上了相应的偏移量的值。这说明dl_init操作是给步伐赋上当前执行的内存地址偏移量,这是初始化hello步伐的一步。

5.8 本章小结

  本章中介绍了链接的概念与作用、并得到了链接后的hello可执行文件的ELF格式文本hello2.elf,据此分析了hello2.elf与hello.elf的异同;之后,根据反汇编文件hello2.asm与hello.asm的比较,加深了对重定位与动态链接的理解。(第5章1分)



6hello进程管理


6.1 进程的概念与作用

进程是一个正在运行的步伐的实例,系统中的每一个步伐都运行在某个进程的上下文中。进程最大的作用就是给应用步伐提供两个关键抽象:①一个独立的逻辑控制流,提供一个假象,似乎步伐独占地利用处理器②一个私有地址空间,提供一个假象,似乎步伐独占地利用内存系统。

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

Shell 的作用:Shell 是一个用C语言编写的交互型应用步伐,代表用户运行其他步伐。Shell 应用步伐提供了一个界面,用户可以通过这个界面进行系统的基本操作,访问操作系统内核的服务。
Shell的处理流程大抵如下:
从Shell终端读入输入的命令。
切分输入字符串,得到并辨认所有的参数
若输入参数为内置命令,则立刻执行
若输入参数并非内置命令,则调用相应的步伐为其分配子进程并运行
若输入参数非法,则返回错误信息
处理完当前参数后继续处理下一参数,直到处理完毕
6.3 Hello的fork进程创建过程

fork进程的创建过程如下:首先,带参执行当前目录下的可执行文件hello,父进程会通过fork函数创建一个新的运行的子进程hello。子进程获取了与父进程的上下文,包罗栈、通用寄存器、步伐计数器,环境变量和打开的文件雷同的一份副本。子进程与父进程的最大区别是有着跟父进程不一样的PID,子进程可以读取父进程打开的任何文件。当子进程运行结束时,父进程假如仍然存在,则执行对子进程的回收,否则就由init进程回收子进程。
6.4 Hello的execve过程

调用函数fork创建新的子进程之后,子进程会调用execve函数,在当前进程的上下文中加载并运行一个新步伐hello。execve 函数从不返回,它将删除该进程的代码和地址空间内的内容并将其初始化,然后通过跳转到步伐的第一条指令或入口点来运行该步伐。将私有的地区映射进来,比方打开的文件,代码、数据段,然后将公共的地区映射进来。后面加载器跳转到步伐的入口点,即设置PC指向_start 地址。_start函数最终调用hello中的 main 函数,如许,便完成了在子进程中的加载。

6.5 Hello的进程执行

在步伐运行时,Shell为hello fork了一个子进程,这个子进程与Shell有独立的逻辑控制流。在hello的运行过程中,若hello进程不被抢占,则正常执行;若被抢占,则进入内核模式,进行上下文切换,转入用户模式,调度其他进程。直到当hello调用sleep函数时,为了最大化利用处理器资源,sleep函数会向内核发送请求将hello挂起,并进行上下文切换,进入内核模式切换到其他进程,切换回用户模式运行抢占的进程。与此同时,将 hello 进程从运行队列到场等待队列,由用户模式变成内核模式,并开始计时。当计时结束时,sleep函数返回,触发一个中断,使得hello进程重新被调度,将其从等待队列中移出,并内核模式转为用户模式。此时 hello 进程就可以继续执行其逻辑控制流。
6.6 hello的异常与信号处理

6.6.1 正常运行
输入的参数依次为作者的学号、姓名、每次打印间隔时间,如图23所示。

23:正常运行。
6.6.2 在运行时按回车(或其他无意义字符)
可以看到,打印正常进行,只是会多出打进去的字符乱码,如图
24所示。

图24:在运行时按回车(或其他无意义字符)

在运行结束之后,这些字符会被当做下一条命令的输入。
6.6.3 在运行时按下Ctrl + C
Shell进程收到SIGINT信号,Shell结束并回收hello进程,如图25所示。

25:在运行时按下Ctrl + C
6.6.4 在运行时按下Ctrl + Z
Shell进程收到SIGSTP信号,Shell表现屏幕提示信息并挂起hello进程,如图26所示。

26:在运行时按下Ctrl + Z

6.6.5对进程的查看与回收
对hello进程的挂起可由ps和jobs命令查看,可以发现hello进程确实被挂起而非被回收,且其job代号为1。


图 27 用ps命令查看挂起进程


在Shell中输入pstree命令,可以将所有进程以树状图表现(此处仅展示部门):


图28:用pstree命令查看所有进程



输入kill命令,则可以杀死指定进程:
图29:利用Kill命令杀死指定进程

输入fg 1则命令将hello进程再次调到前台执行,可以发现Shell首先打印hello的命令行命令,hello再从挂起处继续运行,打印剩下的语句。步伐仍然可以正常结束,并完成进程回收。

6.7本章小结

本章初步讲述了进程的概念和作用讲述了shell如何在用户步伐运行时通过fork函数创建一个新的进程以及通过execve函数加载一个新的进程讲述了shell如何在用户和系统内核直接按通过上下文切换建立了一个交互运行的桥梁,还通过简朴间隔讲述了信号机制在步伐运行中的作用,了解到了前端步伐和背景步伐。

(第6章1分)


7hello的存储管理


7.1 hello的存储器地址空间

逻辑地址:源步伐里利用的地址,大概源代码颠末编译以后编译器将一些标号,变量转换成的地址,大概相对于当前段的偏移地址。

物理地址:计算机系统的主存被组织成一个由M个连续的字节大小的单元构成的数组。每个字节都有唯一的物理地址。

虚拟地址:虚拟地址就是逻辑地址,又叫虚地址。

线性地址:分段机制下CPU寻址是二维的地址即,段地址:偏移地址,CPU不大概认识二维地址,因此须要转化成一维地址即,段地址*16+偏移地址,如许得到的地址便是线性地址(在未开启分页机制的情况下也是物理地址)。

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

段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本构成方式是段号+段内偏移地址。每个段选择符大小为16位,段形貌符为8字节。GDT为全局形貌表,LDT为局部形貌符。段形貌符存放在形貌符表中,也就是GDT或LDT中,段首地址存放在段形貌符中。

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

线性地址(VA)到物理地址(PA)之间的转换通过对虚拟地址内存空间进行分页的分页机制完成。

通过7.2节中的段式管理过程,可以得到了线性地址/虚拟地址,记为VA。虚拟地址可被分为两个部门:VPN(虚拟页号)和VPO(虚拟页偏移量),根据计算机系统的特性可以确定VPN与VPO的具体位数,由于虚拟内存与物理内存的页大小雷同,因此VPO与PPO(物理页偏移量)同等。而PPN(物理页号)则需通过访问页表中的页表条目(PTE)获取。

若PTE的有用位为1,则发生页命中,可以直接获取到物理页号PPN,PPN与PPO共同构成物理地址。
若PTE的有用位为0,说明对应虚拟页没有缓存到物理内存中,产生缺页故障,调用操作系统的内核的缺页处理步伐,确定牺牲页,并调入新的页面。再返回到原来的进程,再次调用导致缺页的指令。此时发生页命中,获取到PPN,与PPO共同构成物理地址。详细过程如图30所示。

图30:Hello的线性地址到物理地址的变换-页式管理


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

TLB即翻译后备缓冲器,它是一个小的、虚拟寻址的缓存,其中每一行都生存着一个由单个PTE构成的块,TLB通常有高度的相连度。以是每次在翻译地址的过程时可以先在TLB中查询是否命中以直接获取物理地址。

多级页表:假如只用一个单独的页表进行地址翻译,会有一个占据内存大量空间的页表驻留在内存中,因此我们须要压缩页表,也就是利用了多级页表。一级页表中的每个PTE负责映射到虚拟地址空间中的一个片,这里每一个片都是由1024个连续的页面构成(也就是二级页表)再用这些二级页表的PTE覆盖整个空间。两级页表层次结构如下图所示:

当TLB与四级页表相团结其地址翻译过程如下:先将这个虚拟地址的VPN分为TLB标志部门和TLB索引部门检查是否再TLB命中假如命中直接取出物理地址,否则的化虚拟地址被划分为4个VPN和一个VPO每个VPN(i)对应了第i级页表的索引,通过这个索引最后对应了一个固定的PPN将这个PPN与VPO团结得到新的物理地址,并把这个物理地址的信息存入TLB缓存。

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

MMU发送物理地址PA给L1缓存,L1缓存从物理地址中抽取出缓存偏移CO、缓存组索引CI以及缓存标志CT。高速缓存根据CI找到缓存中的一组,并通过CT判断是否已经缓存地址对应的数据,若缓存命中,则根据偏移量直接从缓存中读取数据并返回;若缓存不命中,则继续从L2、L3缓存中查询,若仍未命中,则从主存中读取数据。详细结构如图31所示。


图31:三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

当fork函数被当前进程hello调用时,内核为新进程hello创建各种数据结构,并分配给它一个唯一的PID。为了给这个新的hello创建虚拟内存,它创建了当前进程的mm_struct、地区结构和页表的原样副本。它将两个进程中的每个页面都标志为只读,并将两个进程中的每个地区结构都标志为私有的写时复制。
当fork在新进程中返回时,新进程如今的虚拟内存刚好和调用fork时存在的虚拟内存雷同。当着两个进程中的任一个厥后进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射

execve函数加载并运行hello须要以下几个步调:
删除已存在的用户地区
删除当前进程hello虚拟地址的用户部门中的已存在的地区结构。
映射私有地区
为新步伐的代码、数据、bss和栈地区创建新的私有的、写时复制的地区结构。其中,代码和数据地区被映射为hello文件中的.text和.data区。bss地区是请求二进制零的,映射到匿名文件,其大小包罗在hello中。栈和堆地区也是请求二进制零的,初始长度为零。
映射共享地区
若hello步伐与共享对象或目标(如标准C库libc.so)链接,则将这些对象动态链接到hello步伐,然后再映射到用户虚拟地址空间中的共享地区内。
设置步伐计数器
最后,execve设置当前进程上下文中的步伐计数器,使之指向代码地区的入口点。
7.8 缺页故障与缺页中断处理

发生一个缺页异常后,控制会转移到内核的缺页处理步伐。判断虚拟地址是否正当,若不正当,则产生一个段错误,然后终止这个进程。
若操作正当,则缺页处理步伐从物理内存中确定一个牺牲页,若该牺牲页被修改过,则将它换出到磁盘,换入新的页面并更新页表。当缺页处理步伐返回时,CPU 再次执行引起缺页的指令,将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面如今缓存在物理内存中,以是就会命中,主存将所请求字返回给处理器。
7.9动态存储分配管理

本学期课程并未涉及到动态内存部门,以下紧张来自我的自学理解和csapp、【火炬】哈工大-CS-计算机系统-大作业: hello的一生 - 知乎等内容。
动态内存分配器维护着一个进程的虚拟内存地区,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块表现地保留为供应用步伐利用。空闲块用来分配,空闲块保持空闲,直到它表现地被应用所分配。一个已分配地块保持已分配状态直到它被开释,这种开释要么是应用步伐显式执行地,要么是内存分配器自身隐式执行地。

分配器分为两种基本风格:显式分配器、隐式分配器。显式分配器要求应用显式地开释任何已分配的块;隐式分配器要求分配器检测一个已分配块何时不再利用,那么就开释这个块,自动开释未利用的已经分配的块的过程叫做垃圾网络。
动态存储分配管理须要考虑地式分配速率和堆栈地利用绿,其中影响堆栈利用率低地紧张原因是一种称为碎片地征象,非为外部碎片和内部碎片。其中内部碎片是在一个已分配块比有用荷载大时发生地。外部碎片是当空西安内存合计起来可以或许满意一个分配请求,但他们不是连续地时间发生地。
7.10本章小结

本章紧张介绍了hello 的存储器地址空间、intel 的段式管理、hello 的页式管理, VA 到PA 的变换、物理内存访问,hello进程fork、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7章 2分)


8hello的IO管理


8.1 Linux的IO装备管理方法

装备的模子化:文件
装备管理:unix io接口
所有的IO装备都被模子化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。这种将装备映射为文件的方式,允许Linux内核引出一个简朴、低级的应用接口,称为Unix I/O。这使得所有的输入和输出都能以一种同一且同等的方式来执行:打开文件、改变当前的文件位置、读写文件、关闭文件。
8.2 简述Unix IO接口及其函数

8.2.1 Unix IO接口:

打开文件,内核返回一个非负整数的文件形貌符,通过对此文件形貌符对文件进行所有操作。

Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件形貌符0)、标准输出(形貌符为1),标准堕落(形貌符为2)。头文件<unistd.h>界说了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的形貌符值。

改变当前的文件位置,文件开始位置为文件偏移量,应用步伐通过seek操作,可设置文件的当前位置为k。

读写文件,读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k

关闭文件。当应用完成对文件的访问后,关照内核关闭这个文件。内核会开释文件打开时创建的数据结构,将形貌符规复到形貌符池中

8.2.2 Unix IO函数:



read和write–最简朴的读写函数;

readn和writen–原子性读写操作;

recvfrom和sendto–增加了目标地址和地址结构长度的参数;

recv和send–允许从进程到内核传递标志;

readv和writev–允许指定往其中输入数据或从其中输出数据的缓冲区;

recvmsg和sendmsg–团结了其他IO函数的所有特性,并具备接受和发送辅助数据的能力。



8.3 printf的实现分析
Windows下查看printf代码如图32所示。
图32: printf代码

(char*)(&fmt)+4)表示的是可变参数中的第一个参数的地址。vsprintf的作用是格式化。它接受确定输特殊式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。接着从vsprintf生成表现信息到write系统函数,直到陷阱系统调用int 0x80或syscall。Vsprintf函数代码如图33所示。

33:Vsprintf函数代码
字符表现驱动子步伐:从ASCII到字模库到表现vram(存储每一个点的RGB颜色信息)。表现芯片按照刷新频率逐行读取vram,并通过信号线向液晶表现器传输每一个点(RGB分量)。
8.4 getchar的实现分析

异步异常-键盘中断的处理:键盘中断处理子步伐。接受按键扫描码转成ascii码,生存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
getchar调用系统函数read,发送一个中断信号,内核抢占这个进程,用户输入字符串,键入回车后(字符串和回车都生存在缓冲区内),再次发送信号,内核重新调度这个进程,getchar从缓冲区读入字符。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结

本章紧张介绍了linux的IO装备管理方法和及其接口和函数,对printf函数和getchar函数的底层实现有了基本了解。
(第8章1分)

















结论

hello步伐的一生履历了如下过程:
预处理
将hello.c中include的所有外部的头文件头文件内容直接插入步伐文本中,完成字符串的更换,方便后续处理;
编译
通过词法分析和语法分析,将正当指令翻译成等价汇编代码。通过编译过程,编译器将hello.i 翻译成汇编语言文件 hello.s;
汇编
将hello.s汇编步伐翻译成呆板语言指令,并把这些指令打包成可重定位目标步伐格式,最终效果生存在hello.o 目标文件中;
链接
通过链接器,将hello的步伐编码与动态链接库等网络整理成为一个单一文件,生成完全链接的可执行的目标文件hello;
加载运行
打开Shell,在其中键入 ./hello 1190200208 李旻翀,终端为其fork新建进程,并通过execve把代码和数据加载入虚拟内存空间,步伐开始执行;
执行指令
在该进程被调度时,CPU为hello其分配时间片,在一个时间片中,hello享有CPU全部资源,PC寄存器一步一步地更新,CPU不断地取指,序次执行自己的控制逻辑流;
访存
内存管理单元MMU将逻辑地址,一步步映射成物理地址,进而通过三级高速缓存系统访问物理内存/磁盘中的数据;
动态申请内存
printf 会调用malloc 向动态内存分配器申请堆中的内存;
信号处理
进程时刻等待着信号,假如运行途中键入ctr-c ctr-z 则调用shell 的信号处理函数分别进行停止、挂起等操作,对于其他信号也有相应的操作;
终止并被回收
Shell父进程等待并回收hello子进程,内核删除为hello进程创建的所有数据结构。
我的感悟:做完了hello的一生,便是温习了整个《计算机系统》课程的所学。一路走来有困难,但终有所获。希望在以后的“码农”生存中,能时刻谨记这一份劳绩。
(结论0分,缺失 -1分,根据内容酌情加分)

附件

列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)



文件名

功能

hello.i

预处理后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

汇编后得到的可重定位目标文件

hello.elf

用readelf读取hello.o得到的ELF格式信息

hello.asm

反汇编hello.o得到的反汇编文件

hello2.elf

由hello可执行文件生成的.elf文件

hello2.asm

a.out

反汇编hello可执行文件得到的反汇编文件

可执行文件




参考文献


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

[1]  Randal E.Bryant, David O'Hallaron. 深入理解计算机系统[M]. 呆板工业出书社.2018.4
[2]  [转]printf 函数实现的深入剖析https://www.cnblogs.com/pianist/p/3315801.html

[3]  hello.c的前世此生https://zhuanlan.zhihu.com/p/401121362
[4]  空想之家xiao_chen.ELF文件头更详细结构[EB/OL].2017[2021-6-10].
https://blog.csdn.net/qq_32014215/article/details/76618649.
[5]  操作系统——存储管理https://blog.csdn.net/hanpiyo/article/details/124936542
[6]  C汇编Linux手册http://docs.huihoo.com/c/linux-c-programming

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



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

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

伤心客

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

标签云

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