计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 120L020815
班 级 2036011
学 生 赵美含
指 导 教 师 刘松波
计算机科学与技术学院
2021年5月
本文从简单的hello.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.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简介
- P2P过程:
作为用户的我们首先需要按照语法规则编写hello.c,当我们想要运行hello.c文件时,hello.c文件得首先经过预处理,编译,汇编,链接4个阶段从hello.c文本文件转变为可执行文件,这样我们的机器才能运行这个文件。
以linux系统为例,当我们在终端运行可执行文件时,shell解析这段命令,使用fork为我们的hello创建子进程,至此完成了从program到progress的过程。
- 020过程:
当程序完成p2p过程之后,就进入了020过程
在此过程中, shell 为我们的程序调用 execve函数,execve函数启动加载器为该进程分配独立的虚拟内存空间,程序运行在物理内存中,CPU 为其分配时间片执行指令,调用系统I/O,printf函数显示相应的功能。当程序运行结束后,shell 父进程负责回收 hello 进程。
至此,020过程也告一段落。
1.2 环境与工具
1.2.1 硬件环境
图1 硬件环境
1.2.2 软件环境
Windows: windows10、Visual studio 2022、VMware Workstation Pro、code blocks
Ubuntu: Ubuntu20.04.3(64位)、code blocks
1.2.3 开发工具
Visual studio 2022
Code Blocks
1.3 中间结果
Hello.c hello.i hello.o hello.s
1.4 本章小结
本章为本篇论文的一个概述,简单介绍了p2p过程和020过程,介绍了编写此论文使用的软硬件环境和开发调试工具以及编写论文过程中的中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。预处理器(cpp)根据以#开头的命令,修改显示的C程序。
作用:
1)将源文件中用#include形式声明的文件复制到新的程序中。比如hello.c第6-8行中的#include<stdio.h> 等命令告诉预处理器读取系统头文件stdio.h unistd.h stdlib.h 的内容,并把它直接插入到程序文本中。
2)用实际值替换用#define定义的字符串
3)根据#if后面的条件决定需要编译的代码
4)还包括此次hello中没有的#line,#error,#pragma,以及单独的空指令的处理。
2.2在Ubuntu下预处理的命令
预处理指令:gcc -E hello.c -o hello.i
如下图所示:
得到hello.i文件:
2.3 Hello的预处理结果解析
·针对如下三条语句的预处理
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
2.4 本章小结
本章介绍了预处理的概念和作用,以及预处理的命令和结果解析。预处理器cpp将一个hello.c源程序编译为hello.i,原理是根据以字符#开头的命令,修改原始的C程序。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译阶段是编译器ccl将文本文件hello.i翻译成hello.s,它包含一个汇编语言程序,该程序包含一个main的定义
作用:把高级语言翻译成机器可以“听懂”的机器语言,连接高级语言和机器语言的桥梁。
3.2 在Ubuntu下编译的命令
指令:gcc -S hello.i -o hello.s,下图为编译展示:
编译结果:
3.3 Hello的编译结果解析
3.3.1开头汇编指令:
.file——C文件声明
.text——代码段
.section .radata——只读数据段
.align 8——声明对指令或者数据的存放地址进行对齐的方式
.string——声明string型数据
3.3.2.cfi指令
Call Frame infromation的意思,.cfi_startproc 用在每个函数的开始,用于初始化一些内部数据结构,.cfi_endproc 在函数结束的时候使用与.cfi_startproc相配套使用。
3.3.3 常量
即字符串常量
保存在如下数据段中
3.3.4变量
- 局部变量
局部变量为for循环中的i,由于i最初被赋值为0,所以找到赋值0操作的,即为i,为第42行。
- 传递参数
- int argc是该函数的整型参数,函数参数被保存在寄存器中。它是函数第一个参数,被保存在%edi寄存器中,如下22行
由上图还可以看到第一个参数被复制到-20(%rbp)栈中
- Char *argc[]是第二个参数,被保存在%rsi中,由上图被复制到-32(%rbp)栈中
- 该代码中还包含sleep函数,该函数中参数是一个由字符串数组转化成的整数参数,在下图程序中调用atoi函数,返回值送给了%edi即函数参数。见第四十九行:
- 数组
该程序包含argv[]数组的三个元素:
数组中的元素一般保存在连续地址中,寻址方式即可找到数组:
3.3.5赋值操作
本代码中复制操作是在进行for循坏过程中,对局部变量i进行的赋值操作,把0赋值给i:
3.3.6算数操作
也就是for循环中每次的i++,见下图51行,每次都加一,每次都在L3中判断for循环中的条件,满足则跳到L4,执行一系列指令后将i+1。
3.3.7判断操作
- 在if语句中,判断argv是否等于4,等于4则不能执行。
- 在for循环中判断是否小于8,小于8则执行for循环。
3.3.8控制转移
在判断之后会有条件控制,不同的CC会跳转到不同的指令,控制转移分别分为if控制转移和for循环的控制转移。
- If条件中的控制转移
相等的话跳转到L2,L2赋值之后跳转到L3,不相等的话继续执行,打印语句。
- For循环中的控制转移
L2转移到L3,L3是一个for循环,每次都会比较i和7的大小,如果小于等于就会跳转到L4执行for循环中的指令,不相等则退出for循环。
3.3.9数组指针操作
数组元素有字符串数组argv[],指针有指向数组argv的指针。
对于数组元素的操作,通常是将数组首个元素的地址放入栈中,而在该程序中也是这样,将数组argv首元素,也是指向argv的指针的地址放入了-32(%rbp)中。
在for循环体中,用起始地址加上偏移量字节大小来访问的,采用这种寻址方式,就能访问到数组中的每个元素,如下图所示:
3.3.10函数操作
- 函数调用
函数调用使用call指令
- 函数返回
函数返回使用ret指令
3.4 本章小结
本章主要介绍汇编过程,使用指令gcc -S hello.i -o hello.s,把hello.i转为hello.s,即把hello.s翻译成机器能听懂的汇编语言,是高级语言和机器语言之间的桥梁,本章介绍了汇编语言的各种指令,如call ret move 判断等指令,使学生掌握了看懂汇编语言的能力。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编概念:汇编阶段是指汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并且保存在hello.o中。
汇编作用:将汇编语言翻译为机器语言,并将相关指令以可重定位目标程序格式保存在.o文件中
4.2 在Ubuntu下汇编的命令
命令:as hello.s -o hello.o
4.3 可重定位目标elf格式
- 在终端输入readelf -a hello.o > hello.txt,将hello.o文件读出到txt文件中
- 打开hello.txt文件,观察里面的信息
- 观察信息
- Elf
ELF头以一个16字节的序列开始,这个序列描述了生成该文件系统下的字的大小以及一些其他信息。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息:包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。具体ELF头的代码如下:
- 节头部表
描述了.o文件中每一个节出现的位置,大小,目标文件中的每一个节都有一个固定大小的条目。具体内容如下图所示:
- 重定位信息
重定位是将EFL文件中的未定义符号关联到有效值的处理过程。在hello.o中,对printf,exit等函数的未定义的引用和全局变量(sleepsecs)替换为该进程的虚拟地址空间中机器代码所在的地址。
- 符号表
符号表(.symtab)是用来存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。具体信息如下所示:
4.4 Hello.o的结果解析
通过objdump -d -r hello.o 生成hello.o的反汇编,下面与第3章的 hello.s进行对照分析。
与hello.s对比的差别如下:
- 分支转移:在汇编代码中,分支跳转是直接以.L0等助记符表示,但在反汇编代码中,分支转移不在依靠段名称,而是表示为主函数+段内偏移量。因段名称在汇编语言中为便于编写的助记符,所以在汇编成机器语言之后就不存在了,而是确定的地址。
- 访问全局变量:汇编代码中使用.LC0(%rip),反汇编代码中为0x0(%rip),因为访问时需要重定位,所以初始化为0并添加重定位条目等链接之后再确定。
- 函数调用:汇编代码中函数调用时直接使用函数名称,而在反汇编的文件中call之后定位到call的下一条指令,即用具体的地址表示。在.rela.text节中为其添加重定位条目等待链接。
4.5 本章小结
本章介绍了hello从hello.s到hello.o的汇编过程,查看了hello.o的ELF格式,使用objdump得到反汇编代码,还将其与hello.s进行了比较,了解到了从汇编到机器汇编的转换。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接概念:连接的概念是指连接器(ld)将存有printf等函数的预编译好的目标文件合并到hello.o程序的过程。如在hello函数中调用了printf函数,它是每个编译器都提供的标准C库中的一个函数,printf函数存在于一个预编译好的目标文件中,而这个文件要通过ld合并到我们的hello.o文件中。
链接的作用:将存有printf等函数的预编译好的目标文件合并到hello.o程序。
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 /usr/lib/x86_64-linux-gnu/crtn.o
生成可执行文件hello.o
5.3 可执行目标文件hello的格式
ELF Header格式如下:
类型为EXEC表示为可执行文件。
Section Headers格式如下:
Section Headers:节头部表,记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。根据Section Headers中的信息我们就可以用HexEdit定位各个节所占的区间(起始位置,大小)。
Symbol table的信息如下:
5.4 hello的虚拟地址空间
使用edb加载hello,结果如下:
虚拟地址信息如下:
5.5 链接的重定位过程分析
- 新增加的函数
链接加入了新的函数,如printf,getchar,sleep 等函数。
- 新增加的节
增加了.init和.plt节
- 地址访问
hello.o中标注了重定位信息的部分均被具体的地址,数据所代替
5.6 hello的执行流程
使用edb执行hello,下表展示从加载hello到_start,到call main,以及程序终止的所有过程并列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
在进行动态链接前,首先要进行静态链接,生成部分链接的可执行目标文件hello。动态链接采用了延迟加载的策略,即在调用函数时才进行符号的映射。使用偏移量表got+过程链接表plt实现函数的动态链接。got中存放函数目标地址,为每个全局函数创建一个副本函数,并将对函数的调用转换成对副本函数调用。
查看dl_init函数调用前后.got.plt节的变化
5.8 本章小结
链接是编译hello.c程序的最后一步,我们首先了解了连接的概念和作用,然后分析了hello的ELF格式,使用edb加载hello并进行了重定位分析。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程概念:进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像是单独的使用硬件,在大多数系统中,需要运行的进程数可以多于他们的CPU个数,一个CPU像是在并发的执行多个进程,这就是处理器在进程间切换来实现的。
进程作用:想hello这样的程序在现代系统上运行时,进程会提供一种假象,好像系统上只有这个程序在运行,程序看上去是独占的使用处理器,主存和io设备,处理器看上去好像在不间断地执行程序中的指令,这些假象都是通过进程中的概念来实现的。
6.2 简述壳Shell-bash的作用与处理流程
- 作用:
shell是一个应用程序,他在操作系统中提供了一个用户与系统内核进行交互的界面。它解释由用户输入的命令并且把它们送到内核。
- 处理流程
1.读取用户由键盘输入的命令行。
2.分析命令,以命令名作为文件名,并将其它参数改造为系统调用execve( )内部处理所要求的形式。
3.终端进程调用fork( )建立一个子进程。
4.终端进程本身调用wait4()来等待子进程完成(如果是后台命令,则不等待)。当子进程运行时调用execve(),子进程根据文件名到目录中查找有关文件,调入内存,执行这个程序。
5.如果命令末尾有&,则终端进程不用执行系统调用wait4(),立即发提示符,让用户输入下一条命令;否则终端进程会一直等待,当子进程完成工作后,向父进程报告,此时中断进程醒来,作必要的判别工作后,终端发出命令提示符,重复上述处理过程。
6.3 Hello的fork进程创建过程
当我们在终端运行之前得到的可执行文件,使用./hello的命令,shell会对输入的命令行进行解析,因为 hello 不是一个内置的shell 命令所以解析之后终端程序判断./hello的语义为执行当前目录下的可执行目标文件 hello,之后终端程序首先会调用 fork 函数创建一个新的运行的子进程。
Shell通过调用fork函数创建一个新的运行的子程序,新创建的子进程得到与父进程用户及虚拟内存空间相同的一份副本,包括代和数据段,堆栈和共享库。
6.4 Hello的execve过程
调用函数fork创建新的子进程之后,子进程会调用execve函数,在当前进程的上下文中加载并运行一个新程序hello。execve 函数从不返回,它将删除该进程的代码和地址空间内的内容并将其初始化,然后通过跳转到程序的第一条指令或入口点来运行该程序。将私有的区域映射进来,然后将公共的区域映射进来。后面加载器跳转到程序的入口点,即设置PC指向_start 地址。_start函数最终调用hello中的 main 函数,这样,就完成成了在子进程中的加载。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
- 上下文信息:操作系统内核使用一种称为上下文切换的较高层形式的一场控制流来实现多任务。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器,浮点寄存器,程序计数器,用户栈,状态寄存器,内核栈和各种内核数据结构。
- 用户模式和内核模式
处理器使用一个寄存器提供两种模式的区分。用户模式的进程不允许执行特殊指令,不允许直接引用地址空间中内核区的代码和数据;内核模式进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
- 进程时间片
一个进程执行它的控制流的一部分的每一时间段叫做时间片。
对于hello的进程执行,具体过程如下:键盘输入./hello zmh yeye 1
首先shell通过加载器加载可执行目标文件hello,操作系统进行上下文切换,切换到hello的进程中,此时为用户态,执行完相应函数后,调用sleep函数,进入内核态,当sleep的时间完成后时定时器发送一个中断信号,此时进入内核状态执行中断处理,将hello进程从等待队列中移出重新加入到运行队列,上下文切换再进入hello进程,回到用户态。
6.6 hello的异常与信号处理
hello的异常可以分为四类:中断,陷阱,故障和终止
1 中断
来自I/O设备的信号 异步 总是返回到下一条指令
2 陷阱
有意的异常 同步 总是返回到下一条指令
3 故障
潜在可恢复的错误 同步 可能返回到当前指令
4 终止
不可恢复的错误 同步 不会返回
键盘中各种操作导致的异常
- 正常操作
- 回车键
- Ctrl C和Ctrl Z(默认挂起前台作业)
- 输入ps(验证没有被回收)
- 输入jobs
- 输入pstree
- 输入fg(调到前台)
- 输入kill指令(结束程序)
6.7本章小结
本章了解了进程的概念与作用,叙述了shell的处理流程,说明了hello的进程处理和异常信号执行。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
- 逻辑地址
程序代码经过编译后出现在 汇编程序中地址。逻辑地址由选择符 (在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏 移部分)组成。
- 线性地址
也叫虚拟地址,是逻辑地址到物理地址变换之间的中间层,即连续的虚拟地址。
- 虚拟地址
虚拟地址是程序保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为[段:偏移量]的形式,这里的段是指段选择器。
- 物理地址
计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部份组成,段标识符:段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。转换过程如下所示:
- 看段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT中的段,还是LDT中的段,再根据指定的相应的寄存器,得到其地址和大小。
- 拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,这样就有了Base,得到基地址。
- 把基地址Base+Offset,得到下一个阶段的地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
通过分页机制完成,即对虚拟地址的内存空间进行分页。在任意时刻,虚拟页面的集合都分成三个不相交的子集
- 未分配的
- 缓存的
- 未缓存的
系统将每个段分割为被称为虚拟页(VP)的大小固定的块来作为进行数据传输的单元,虚拟地址分为虚拟页号VPN和虚拟页偏移量VPO,根据位数限制分析可以确定VPN和VPO分别占多少位是多少。
通过页表基址寄存器PTBR+VPN在页表中获得条目PTE,一条PTE中包含有效位、权限信息、物理页号。如果有效位是0+NULL则代表没有在虚拟内存空间中分配该内存。如果是有效位0+非NULL,则代表在虚拟内存空间中分配了但是没有被缓存到物理内存中。如果有效位是1则代表该内存已经缓存在了物理内存中,可以得到其物理页号PPN,与虚拟页偏移量共同构成物理地址PA。
7.4 TLB与四级页表支持下的VA到PA的变换
36位VPN被划分成四个9位的片,每个篇被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN1提供一个到L1 PTE的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2 PTE的偏移量,以此类推。
7.5 三级Cache支持下的物理内存访问
使用上一步得到的PA,首先取组索引对应位,向L1cache中寻找对应组。如果存在,则比较标志位,并检查对应行的有效位是否为1。如果上述条件均满足则命中。否则按顺序对L2cache、L3cache、内存进行相同操作,直到出现命中。然后向上级cache返回直到L1cache。如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,将目标块放到被驱逐块的原位置。
7.6 hello进程fork时的内存映射
当fork函数被shell调用时,会分配给hello一个唯一的PID。为了给hello创建虚拟内存,fork创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在hello中返回时,hello现在的虚拟内存刚好和调用shell的虚拟内存相同。当这两个进程中的任何一个进行写操作时,写时复制机制会创建新页面。因此也就为每个进程保持了私有地址空间的概念。
7.7 hello进程execve时的内存映射
Execve函数在当前进程中加载并运行包含目标文件a.out中的程序,用a.out程序有效的替换了当前的程序。加载并运行a.out需要以下几个步骤:
- 删除已存在的用户区域
- 映射私有区域
- 映射共享区域
- 设置pc
之后调度此进程时,它将从这个入口点开始执行。
7.8 缺页故障与缺页中断处理
当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出时就会发生故障。缺页故障后会有如下处理:
- 检查处理程序是否合法,不合法则终止
- 检查进程是否有读、写或执行该区域页面的权限不具有则触发保护异常,终止程序
- 若二者均正常则内核选择一个牺牲页面,如果该页面被修改过则内核会将它复制回磁盘,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。
7.9动态存储分配管理
- 动态存储分配
当运行时需要额外内存时,用动态内存分配器更方便,也有更好的移植性。
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。对于每个进程,内核维护一个变量brk,它指向堆的顶部。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器分为两种基本风格。两种风格都要求应用显示的分配块,它们的不同之处在于有哪个实体来负责释放已分配的块:
- 显式分配器:要求应用显式地释放任何已分配的块。例如malloc。
- 隐式分配器:也叫做垃圾收集器,要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。
- Malloc函数
malloc函数返回一个指针,指向大小至少为size字节的内存块,这个块可能包含在这个块内的任何数据对象类型做对齐。
- 隐式空闲列表
对比于显式空闲链表,代表并不直接对空闲块进行链接,而是将对内存空间中的所有块组织成一个大链表,在块的首尾的四个字节分别添加header和footer,负责维护当前块的信息(大小和是否分配)。由于每个块是对齐的,所以每个块的地址低位总是0,可以用该位标注当前块是否已经分配。可以利用header和footer中存放的块大小寻找当前块两侧的邻接块,方便进行空闲块的合并操作。优点是简单,缺点是搜索所需时间与堆中以分配块和空闲块的总数成线性关系。
- 显式空闲列表
显式结构在空闲块中增加了8个字节,分别保存当前空闲块的前驱空闲块的地址和后继空闲块的地址。显式的结构比隐式结构多维护了一个链表,就是空闲块的链表。这样做的好处就是在malloc的时候,隐式的方法是要遍历所有的块,包括空闲块了分配块。但是显式的结构只需要在空闲块中维护的链表检索就可以了,这样降低了在malloc时候的复杂度。
关于空闲块的维护方式一共有两种,一种是后进先出的方式,另一种是按照地址的方式。按照地址维护很好理解,与隐式的结构大致相同。后进先出的方式的思想是,当一个分配块被free之后,将这个块放到链表的最开头,这样在malloc的时候会首先看一下最后被free的块是否符合要求。这样的好处是释放一个块的时候比较高效,直接放在头部就可以。
7.10本章小结
本章介绍了hello的线性地址空间和物理到线性,线性到逻辑的变换,还介绍了hello进程fork,execve时的内存映射,以及缺页故障与缺页中断处理和动态存储分配管理。
(第7章 2分)
结论
Hello.c只是一个简单的代码文件,但是通过这个文件我们了解了一个C语言程序从被写出到执行的一生,平时运行代码文件只是简单的点个运行按钮,但是真正了解如何编译后才发现里面内容之丰富,知识之广泛。本文的hello.c一共经历了一下几种剖析:
- 对hello.c程序进行预处理
- 对于处理后的程序进行编译
- 对编译后的程序进行汇编
- 最后进行链接
- 以hello.c为例介绍了对进程的管理
- 以hello.c为例介绍了存储管理
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
(参考文献0分,缺失 -1分)