计算机系统

大作业

题 目程序人生-Hellos P2P

专 业 计算机科学与技术学院

学   号 120L021419

班 级2003011

学 生 丁先鸣

指 导 教 师郑贵滨

计算机科学与技术学院

2021年5月

摘 要

摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

关键词:关键词1;关键词2;……;

(摘要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的整个过程。

P2P:即Program to Process。对源程序文件(Program)hello.c,GCC编译器对其进行一系列处理:首先是预处理器将hello.c修改生成hello.i文件(gcc -E hello.c -o hello.i);然后编译器将hello.i翻译生成hello.s文件(gcc -S hello.c -o hello.s);然后汇编器将hello.s翻译成机器语言指令生成hello.o文件(gcc -c hello.s -o hello.o);最后链接器处理hello.o以及其他.o文件的合并,生成hello可执行文件(gcc hello.o -o hello)。在shell中运行hello,便创建了一个新的进程(Process)。

020:即From Zero to Zero。在shell中运行hello,创建了一个子进程进程(Process),然后调用execve()加载程序,便创建虚拟内存并对应载入物理内存,所有相关数据加载其中,在CPU的控制下执行逻辑控制流。程序完全运行结束后,父进程回收该子进程,子进程的所有数据和空间被系统删除,即完成了从无到无的过程。

1.2 环境与工具

1.2.1 硬件环境

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

1.2.2 软件环境

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

1.2.3 开发工具

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

1.3 中间结果

hello.i 预处理器将hello.c修改生成的文件(gcc -E hello.c -o hello.i)

hello.s 编译器将hello.i翻译生成的文件(gcc -S hello.c -o hello.s)

hello.o 汇编器将hello.s翻译成机器语言指令生成的文件(gcc -c hello.s -o hello.o)

hello 链接器处理hello.o以及其他.o文件的合并,生成的可执行文件(gcc hello.o -o hello)。

disas.txt hello.o的反汇编文件

1.4 本章小结

程序人生,即hello的人生。即可以说是p2p,也可以说是020。从hello.c源程序到hello可执行程序生成子进程,再到程序运行结束,被系统全部清除,hello走完了它的一生。


第2章 预处理

2.1预处理的概念与作用

概念:预处理器cpp根据以字符#开头的命令,修改原始的C程序。

作用:

处理文件包括:#include

处理宏定义:#define

处理条件编译:#if/#ifdef/#ifndef/#else/#elif/#endif

处理行控制:#line

处理错误指令:#error

处理和实现相关的杂注:#pragma

处理空指令:#

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

图 2-1

2.3 Hello的预处理结果解析

以字符#开头的命令,被换成了大量修改后的代码,其余源代码放到了最后。

图 2-2

hello.c中的三个头文件stdio.h , unistd.h , stdlib.h都被从系统中读取并展开插入到了程序文本中。

图 2-3

图 2-4

图 2-5

其中还出现了很多源程序中没有的.h头文件,是因为这三个.h头文件中存在其他的头文件,这是递归展开并插入。

2.4 本章小结

预处理的过程中,预处理器删除了hello.c中的注释,替换了宏定义,

将引用的头文件展开并插入到程序文本中。

第3章 编译

3.1 编译的概念与作用

概念:编译器ccl将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。

作用:编译器对hello.i进行检查,做一些语法分析、词法分析、语义分析等。若无错误,便将程序转变成汇编语言程序并适当优化。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

gcc -S hello.c -o hello.s

正在上传…重新上传取消

图 3-1

3.3 Hello的编译结果解析

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析

3.3.1 数据

1. 常量

字符串常量:

图 3-2

2)变量

整型:

图 3-3

图 3-4

数组:

图 3-5

3.3.2 赋值

对局部变量i赋值0,i存在栈中-4(%rbp)的位置

图 3-6

3.3.3 算术操作

算术操作i++,通过add每次对-4(%rbp)中内容加1:

图 3-7

3.3.4 关系操作

存在两个关系操作,!=和<

图 3-8

图 3-9

3.3.5 数组/指针/结构操作

对数组argv的操作,寻址方式为基址+偏移。

-32(%rbp)存放的是argv的首地址,偏移量为8。

图 3-10

3.3.6 控制转移

源文件是通过if和for,而.s文件是通过cmp和jxx指令实现的。

图 3-11

图 3-12

图 3-13

3.3.7 函数调用

图 3-14

图 3-15

图 3-16

图 3-17

3.4 本章小结

将hello.i编译为hello.s。

介绍解析了hello.s中的各种操作及数据类型具体体现。

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器as将hello.s中的汇编语言翻译为机器指令,把这些指令打包生成可重定位目标程序,将结果保存为目标文件hello.o。

作用:将汇编代码转变为机器指令。

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

4.2 在Ubuntu下汇编的命令

图 4-1

应截图,展示汇编过程!

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

使用readelf -a hello.o指令,查看hello.o的ELF格式相关信息:

1. ELF头

ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,节头部表中条目的大小和数量等。

图 4-2

2. 节头

节头部表,包含了文件中出现的各个节的语义。记录了每个节的名称、类型、属性(读写权限)、在ELF文件中所占的长度、对齐方式和偏移量。

图 4-3

3. 重定位节

.rela.text ,一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。

图 4-4

图中 8 条重定位信息分别是对.L0(第一个 printf 中的字符串)、puts 函数、exit 函数、.L1(第二个 printf 中的字符串)、printf 函数、 atoi函数、sleep 函数、getchar 函数进行重定位声明。

重定位条目告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。如图,偏移量是需要被修改的引用的节偏移,符号标识被修改引用应该指向的符号。类型告知链接器如何修改新的引用,加数是一个有符号常数,一些类型的重定位要用它对被修改引用的值做偏移调整。

4. 符号表

它存放在程序中定义和引用的函数和全局变量的信息,.symtab符号表不包含局部变量的条目。

图 4-5

4.4 Hello.o的结果解析

objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

disas.txt:

图 4-6

图 4-7

hello.s:

图 4-8

正在上传…重新上传取消

图 4-9

正在上传…重新上传取消

图 4-10

对照分析:

1. 操作数:

hello.s中是十进制,disas.txt中是十六进制。

2. 分支转移:

hello.s中使用标号作为分支跳转的地址,disas.txt中用相对main函数起始地址的偏移表示跳转的地址。

3. 函数调用:

hello.s中函数调用后直接跟着函数的名字,disas.txt中call指令之后是main函数的相对偏移地址。因为函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。在机器语言中call后的地址为全0.在重定位节中有对应的重定位条目,链接之后确定地址。

4.5 本章小结

对hello.s汇编产生的 hello.o 的可重定位的 ELF 格式进行分析,反汇编文件disas.txt与 hello.s 对比,更深入了解了汇编的过程。

5链接

5.1 链接的概念与作用

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

作用:将函数库中相应的代码组合到目标文件中。

注意:这儿的链接是指从 hello.o 到hello生成过程。

5.2 在Ubuntu下链接的命令

gcc hello.o -o hello

正在上传…重新上传取消

图 5-1

正在上传…重新上传取消

图 5-2

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

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

readelf -a hello指令

正在上传…重新上传取消

图 5-3

正在上传…重新上传取消

图 5-4

5.4 hello的虚拟地址空间

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

正在上传…重新上传取消

图 5-5

正在上传…重新上传取消

图 5-6

反汇编结果中.text节的机器代码与edb中使用Data Dump查看对应地址空间各段信息相同。

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

正在上传…重新上传取消

图 5-7

hello的main是以11e9为基

hello.o的main是以0000的虚拟地址为基

关于节数:hello.o中只有一个.text节,并且只有一个主要函数,函数地址也是默认的0x000000。 Hello中的.init,.plt和.text分为三个部分,每个部分都有许多功能。这些外部函数都是我们用来链接命令的链接系统对象文件(crti.o,crtend.o等)。的。还需要注意.plt节,即进程链接表。该表与GOT(全局偏移表)结合使用,以通过延迟绑定机制在共享库中动态重定位功能。

函数调用:hello.o中的函数调用为main加相对偏移量,该函数的虚拟地址直接在hello中使用。

Hello中的地址全部替换为虚拟地址,请参见ELF文件中的段表。

链接后,Hello.o中的重定位条目不再显示在hello中。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

函数名

地址

hello!.plt.got+0x10

00005597f10070a0

hello!start

0000562b65e3b100

libc-2.31.so!__libc_start_main

00007f4555aabfc0

libc-2.31.so!_setjmp

00007f4555acacb0

libc-2.31.so!__sigsetjmp

00007f4555acabe0

hello!main

0000562b65e3ble9

libc-2.31.so!puts

00007f4555b0c450

libc-2.31.so!exit

00007f4555acea70

5.7 Hello的动态链接分析

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

对于动态共享链接库中的PIC函数,编译器无法预测该函数的运行时地址,因此您需要添加重定位记录并等待动态链接器处理。 为避免在运行时修改调用模块的代码段,链接器使用延迟绑定策略。

正在上传…重新上传取消

图 5-8

5.8 本章小结

深入了解了链接的概念以及作用

具体对可执行文件 hello 的 ELF 格式及其虚拟地址空间进行分析,以及重定位过程、加载以及运行时函数调用顺序以及动态链接过程

深入理解了链接和重定位的过程

6hello进程管理

6.1 进程的概念与作用

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

作用:进程的概念为我们提供这样一种假象,就好像我们的程序是系统中当前运行的唯一程序一样,我们的程序好像是独占地使用处理器和内存,处理器好像是无间断地一条接一条地执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

作用:壳Shell-bash是由C编写的程序,是用户使用Linux的桥梁,并且为用户访问操作系统内核提供了一个交互界面。用户可以通过shell向操作系统发出请求,操作系统选择执行命令。

处理流程:

用户键入命令,shell读取该命令。
通过praseline builtin函数分割字符串,获取命令。
判断是否为内置命令:

若是,则立即执行。
若不是,则调用相应的程序为其分配子进程执行。
Shell可以异步接收来自I/O设备的信号,并对这些中断信号进行处理。

6.3 Hello的fork进程创建过程

当在终端中输入“./hello 学号 姓名秒数”时。shell会通过上述流程处理,首先判断出它不是内置命令,所以会认为它是一个当前目录下的可执行文件hello。在加载此进程时shell通过fork创建一个新的子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库和用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。子进程与父进程有不同的pid。父进程和子进程是并发运行的独立进程,内核可以任意方式交替执行他们的逻辑控制流中的指令,所以这会导致我们不能简单的凭直觉判断指令执行的顺序。父进程会默认等待子进程执行完之后回收子进程,但是也会有产生僵死进程的情况,父进程可以调用waitpid函数等待其子进程终止或停止。

6.4 Hello的execve过程

execve函数在新创建的子进程的上下文中加载并运行hello程序。execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。只有发生错误时execve才会返回到调用程序。所以,execve调用一次且从不返回。

6.5 Hello的进程执行

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

进程上下文包含了进程执行需要的所有信息:用户地址空间;控制信息;硬件上下文。为了控制进程的执行,内核挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,从而完成上下文切换;中断CPU上执行的进程前后是在同一个进程上下文中,只是由用户态转向内核态执行。

6.6 hello的异常与信号处理

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

正常:

正在上传…重新上传取消

图 6-1

异常:

1. 乱按

正在上传…重新上传取消

图 6-2

实时显示结果

2. 回车

正在上传…重新上传取消

图 6-3

换行即实时显示结果

3. Ctrl-C

正在上传…重新上传取消

图 6-4

终止程序

4. Ctrl-Z

正在上传…重新上传取消

图 6-5

挂起程序

接ps

正在上传…重新上传取消

图 6-6

查看当前正在运行的一些进程

接jobs

正在上传…重新上传取消

图 6-7

查看任务列表

接pstree

正在上传…重新上传取消

图 6-8

查看进程树

接fg

正在上传…重新上传取消

图 6-9

将一些停止的进程恢复到前台运行:向处于停止状态的进程发送SIGCONT信号,恢复这些进程,并在前台开始运行

接kill

正在上传…重新上传取消

图 6-10

如果pid大于零,则kill函数发送信号号码sig给进程pid;如果pid等于零,那么kill发送信号sig给调用进程所在进程组中的每个进程,包括调用进程自己;如果pid小于零,kill发送信号sig和进程组|pid|中的每个进程

6.7本章小结

hello程序在计算机中具体的运行。

hello是以进程的形式运行,每个进程都处在某个进程的上下文中,每个进程也都有属于自己的上下文,用于操作系统进行进程调度。用户通过shell和操作系统交互,向内核提出请求,shell通过fork函数和execve函数来运行可执行文件。操作系统中有一套异常控制的系统,用于保障程序运行。异常的种类分为较低级的中断,终止,陷阱和故障,还有较高级的上下文切换和信号机制。

7hello的存储管理

7.1 hello的存储器地址空间

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

逻辑地址:

由程序产生的与段相关的偏移地址部分。

在这里是hello.o里的相对偏移地址。

线性地址:

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

在这里是hello里的虚拟内存地址。

虚拟地址:

CPU通过生成一个虚拟地址。

在这里是hello里的虚拟内存地址。

物理地址:

用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。计算机系统的主存被组织成一个由M 个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。

在这里是hello在运行时虚拟内存地址对应的物理地址。

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

正在上传…重新上传取消

图 7-1

段式管理通过段寄存器(如CS等)与偏移地址的组合来完成逻辑地址到线性地址的变换。在实模式下,逻辑地址CS: EA所转换得到的物理地址为CS * 16 + EA。在保护模式下,以段描述符作为下标,到GDT/LDT表中查表获得短地址,将段地址加上偏移地址,得到线性地址,完成转换。

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

正在上传…重新上传取消

图 7-2

虚拟内存被组织为N个连续字节大小的单元的阵列,存储在磁盘上。 每个字节都有一个唯一的虚拟地址作为数组的索引。 磁盘上的数据分为多个块,这些块充当磁盘和主存储器之间的传输单元。 虚拟页是分为固定大小的块的虚拟内存。 物理内存分为物理页,其大小与虚拟页的大小相同。

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

正在上传…重新上传取消

图 7-3

TLB是一个小的虚拟可寻址缓存,其中每一行都包含一个由单个PTE组成的块,TLB通常具有高度的关联性。 对于虚拟地址,如果TLB命中,则MMU从TLB中获取PTE,否则它将在L1缓存中查找PTE。 使用多级页表,仅第一级页表需要驻留在内存中,其他页表可以根据需要创建,传入或传出。 虚拟地址分为多个VPN和一个VPO。 在四级页表中,需要访问四个页表以获得PPN并连接到PPO以获取物理地址。

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

正在上传…重新上传取消

图 7-4

图示Intel Core i7 L1的缓存结构。 64组,每组8行,每行64个字节,我们刚得到的物理地址,PPN和组索引CT相等,通过CT查找相应的组,继续使用标记位CI和偏移位CO查找相应的内存 。

7.6 hello进程fork时的内存映射

Linux将虚拟内存区域与磁盘上的对象相关联,以初始化该虚拟内存区域的内容。此过程称为内存映射。 虚拟内存区域可以映射到两种类型的对象之一。

当前进程调用fork函数时,内核会为新进程创建虚拟内存并分配唯一的PID。 为了创建虚拟内存,它创建了当前进程的mm_struct,段结构和页表的完整副本。 它将两个进程中的每个页面都标记为只读,并将两个进程中的每个段结构都标记为私有写时复制。

当fork在新进程中返回时,新进程的虚拟内存与调用fork时存在的虚拟内存完全相同。 当这两个进程中的任何一个稍后写入时,写时复制机制将创建一个新页。因此,将为每个进程维护私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

以下格式自行编排,编辑时删除

删除已存在的用户区域:

删除当前进程虚拟地址的用户部分中的已存在的区域结构。

映射私有区域:

为新程序的代码、数据、bss 和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域是请求二进制零的,映射到匿名文件,其大小包含在 hello 中,栈和堆地址也是请求二进制零的,初始长度为零。

映射共享区域:

hello 程序与共享对象 libc.so 链接,libc.so 是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。

设置程序计数器(PC):

execve 做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。

7.8 缺页故障与缺页中断处理

假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页.这个异常导致控制转移到内核的缺页处理程序,处理程序随后就执行下面的步骤:

处理器将虚拟地址发送给MMU

MMU使用内存中的页表生成PTE地址

有效位为零,因此MMU触发缺页异常

缺页处理程序确定物理内存中牺牲页(若页面被修改,则换出到磁盘)

缺页处理程序调入新的页面,并更新内存中的PTE

缺页处理程序返回到原来进程,再次执行缺页的指令

7.9动态存储分配管理

动态内存分配器维护进程的虚拟内存区域,称为堆。不同系统之间的细节有所不同,但不会失去多功能性。假定堆是一个请求二进制零的区域,该区域在未初始化的数据区域之后立即开始并向上增长。对于每个进程,内核都维护一个变量brk,该变量指向堆的顶部。

分配器将堆维护为不同大小的块的集合。每个块都是已分配或可用的连续虚拟内存。分配的块已明确保留供程序使用。空闲块可用于分配。空闲块将保持空闲状态,直到由应用程序明确分配为止。分配的块将保持分配状态,直到被释放。此版本由应用程序明确执行。 ,由内存分配器本身隐式地执行。

分配器有两种基本样式,即显示分配器和隐式分配器。两种样式都要求应用程序显式分配块:

显式分配器:

需要应用程序显式释放所有分配的块。例如,C标准库提供了一个称为malloc包的显式分配器。 C程序通过调用malloc函数分配一个块,并通过调用free函数释放一个块。.C++中的new和delete运算符等效于C中的malloc和free。

隐式分配器:

要求分配器检测程序何时不再使用分配的块,然后释放该块。隐式分配器也称为垃圾收集器,自动释放未使用的已分配块的过程称为垃圾收集。例如,Lisp,ML和Java等高级语言都依赖垃圾回收来释放分配的块。

7.10本章小结

深入理解了hello在运行过程中的储存空间

初步了解逻辑地址,线性地址,虚拟地址以及物理地址的概念

以及逻辑地址到线性地址再到物理地址的转换,基于3级cache的内存访问以及动态内存分配的有关内容,fork和exceve是的内存映射

8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

open():用来打开文件。打开一个已经存在的文件或者创建一个新文件。

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

read()::用来读取文件。从当前文件位置复制字节到内存,函数从描述符为fd 的当前文件位置复制最多n个字节到内存位置buf。返回值一1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。

write():用来写入文件。从内存复制字节到当前文件位置,write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。

lseek():用来改变文件位置。

8.3 printf的实现分析

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

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

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

8.4 getchar的实现分析

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

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

8.5本章小结

深入了解了Linux的I/O设备管理机制,Unix I/O接口及函数

简要分析了printf函数和getchar函数的实现。

结论

hello程序往往是计算机学习者所写的第一个程序,虽然它的源代码很简单,但是整个程序的执行过程却是一个完整而又复杂的过程。

总的来说,hello的人生如下:

编写源代码hello.c

预处理生成hello.i

编译生成hello.s

汇编生成hello.o

链接生成hello

在shell中运行hello

fork函数创建子进程

execve函数加载子进程

系统创建虚拟地址空间,MMU 把虚拟地址翻译成物理地址,通过三级 cache 访问内存。

系统处理程序的信号和异常

系统收到SIGINT信号,进程终止,hello被shell父进程回收。


附件

hello.c:源代码

hello.i:预处理结果

hello.s:编译结果

hello.o:汇编产生的可重定位目标文件o

hello:hello.o经过链接的形成的可执行目标文件hello

disas.txt:hello.o 的反汇编

参考文献

[1] 深入理解计算机系统原书第3版 兰德尔E.布莱恩特,大卫R 14082079

[2]https://blog.csdn.net/ANooice/article/details/106320848?ops_request_misc=&request_id=&biz_id=102&utm_term=%E7%A8%8B%E5%BA%8F%E4%BA%BA%E7%94%9F&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-6-106320848.142^v10^pc_search_result_control_group,157^v4^control&spm=1018.2226.3001.4187