计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。本文通过对hello程序进行一次深度的漫游,来解释hello程序的P2P和O2O两个过程的实现,深入挖掘硬件与系统软件是如何协调工作的,以及它们又是如何影响程序的正确性和性能的。通过计算机系统课程的学习,运用系统的观点来审视hello程序的运行,先自底向上的分析来解释hello程序每一阶段的实现,然后再自顶向下地运用系统的观点将这些阶段组织起来,从而真正地达到能够深入理解计算机系统,将教材CSAPP与实践有机地联系起来。
关键词:计算机系统;编译;链接;异常控制流;虚拟内存;I/O
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
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 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
关键词:hello;计算机系统;
目的:了解hello的一生
主要内容:hello完整的生命周期的各个阶段。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
Ubuntu20.04
edb
gdb
hexedit
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c 源程序
hello.i ASCII中间文件
hello.s ASCII汇编语言文件
hello.o 可重定位目标文件
hello 可执行文件
1.4 本章小结
认识了hello的产生过程
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:根据以字符#开头的命令,修改原始的C程序。
作用:比如大作业中的三个头文件。
这些头文件就告诉预处理器读取系统头文件中的内容,并把它直接插入程序文本中,结果就得到了另一个C程序,通常是以.i作为文件的后缀。
2.2在Ubuntu下预处理的命令
应截图,展示预处理过程!
如图,产生预处理程序多了许多行,应该是将头文件源码直接插入了文本中。
2.3 Hello的预处理结果解析
如图,产生预处理程序多了许多行,应该是将头文件源码直接插入了文本中。
2.4 本章小结
至此掌握了预处理阶段的概念和作用。其实就是根据#开头的命令修改原始的C程序,将C程序所需要的文件或是其他直接插入进来。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:一个将已经经过cpp处理生成的.i文件翻译成能够执行相等操作的接近机器语言格式的低级语言——汇编语言格式的文件的过程。
作用:编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
生成hello.s汇编语言文件:
(以下格式自行编排,编辑时删除)
应截图,展示编译过程!
3.3 Hello的编译结果解析
(以下格式自行编排,编辑时删除)
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
局部变量i:
由汇编代码可见局部变量保存在内存中地址为%rbp-4的位置,同时将它初始化为了0。
传入参数argc:
argc存放在%edi中,与立即数4做比较来判断命令行输入是否满足要求。
字符串常量:
源程序中打印出来的两个字符串:
数组操作argv[]:
argv[]保存在%rsi中。
整数运算操作i++:
通过addl指令将保存在-4(%rbp)中的局部变量i的值加1
关系判断i < 8:
用cmpl指令和立即数7比较,如果满足i < 8循环条件就跳转到.L4循环体中。
赋值操作:i = 0:
函数调用操作:
通过call指令来调用相关的函数。
3.4 本章小结
掌握了linux下gcc编译命令以及各个选项的应用来生成对应的文件。同时理解了编译过程的主要任务。
(以下格式自行编排,编辑时删除)
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:将hello.s汇编语言文件翻译成一个可重定位目标文件。
作用:输入汇编语言源程序。检查语法的正确性,如果正确,则将源程序翻译成等价的二进制或浮动二进制的机器语言程序,并根据用户的需要输出源程序和目标程序的对照清单;如果语法有错,则输出错误信息,指明错误的部位、类型和编号。最后,对已汇编出的目标程序进行善后处理。
4.2 在Ubuntu下汇编的命令
(以下格式自行编排,编辑时删除)
应截图,展示汇编过程!
gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
readelf -a hello.o
hello.o的elf格式由以下几个部分组成:
ELF头:
节头:
重定位节:
符号表:
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
汇编器会将符号引用的重定位条目存放到可重定位目标文件中。
4.5 本章小结
理解了汇编阶段AS做的工作,以及hello.o的反汇编代码和hello.s中有些地方的造成差异的原因。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
(以下格式自行编排,编辑时删除)
注意:这儿的链接是指从 hello.o 到hello生成过程。
概念:将汇编器生成的可重定位目标文件和所需的其他可重定位目标文件链接起来生成最终的可执行文件的过程。
作用:链接器主要完成以下两个任务:
符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
5.2 在Ubuntu下链接的命令
(以下格式自行编排,编辑时删除)
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
见第四章
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
hello的虚拟地址空间从0x400000开始。
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
hello.o中每条指令前面的地址是一个偏移量,而hello每条指令前面的地址是一个绝对的运行时地址,这说明链接的过程就是将hello.o中的每一个符号引用都和符号的运行时地址对应起来,生成可执行目标文件。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
(以下格式自行编排,编辑时删除)
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
掌握了链接过程的主要做的工作
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
(以下格式自行编排,编辑时删除)
概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
作用:让CPU并行完成多个任务,提高了内存和CPU的利用率。
6.2 简述壳Shell-bash的作用与处理流程
(以下格式自行编排,编辑时删除)
作用:shell是一个交互型的应用级程序,它代表用户运行其他程序,执行一系列的读/求值步骤,然后终止。
处理流程:
shell打印一个命令行提示符,等待用户在stdin上输入命令行。
用户输入一个命令行。
shell对命令行求值。
如果是一个内置的shell命令,调用内置的命令处理函数,否则调用fork创建一个子进程。
如果要求在前台运行,则调用wait等待前台作业停止后再返回等待用户输入下一个命令行。如果要求在后台运行,那么shell就不予以等待,直接返回。
6.3 Hello的fork进程创建过程
(以下格式自行编排,编辑时删除)
fork()函数:父进程通过调用fork函数创建一个新的运行的子进程,子进程几乎与父进程完全一样,得到与父进程相同的用户级虚拟地址空间,代码、数据段、共享库以及用户栈,同时子进程可以读写父进程中打开的任何文件,他们的不同就是有着不同的PID。例如,要是父进程打开了五个文件,那么子进程也有五个打开的文件,而且这些文件的当前读写指针也停在相同的地方。所以,这一步所做的是复制。这样得到的子进程独立于父进程, 具有良好的并发性。同时fork函数还会返回两次,在子进程中返回0,而在父进程中返回子进程的PID。
总结来说fork();函数有以下特点:
1).调用一次,返回两次 2).父进程与子进程并发执行 3).相同但是彼此独立的地址空间 4).共享文件
Hello的fork过程:
在终端输入./hello 因为./hello不是内置命令,而是当前目录下的可执行文件,于是终端调用fork函数在当前进程中创建一个新的子进程,该进程与父进程几乎完全相同。子进程得到与父进程用户级虚拟地址空间相同的副本,包括代码、数据、堆、共享库和用户栈。父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行他们的逻辑控制流中的指令。这样一个子进程就创建好了,之后会将可执行程序hello加载到内存中,开始执行了。
6.4 Hello的execve过程
(以下格式自行编排,编辑时删除)
Shell的fork子程序调用execve函数,execve函数在当前子进程的上下文中加载运行可执行文件hello,创建一个hello的映像,该程序会覆盖子进程fork的地址空间,Shell复制的hello拥有相同的PID。只有当出现错误时,例如找不到hello,execve才会返回到调用程序。
6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
内核为每一个进程维持一个上下文。
hello进程一开始运行在用户模式中,当hello进程执行到通过系统调用getchar时陷入到内核,由于等待用户输入的时间较长,所以内核执行从进程hello到其他进程的上下文切换,而不是在这个间歇时间内等待,什么都不做。切换之后内核代表其他进程在用户模式下执行指令。用户完成键盘输入后,内核执行一个从当前进程到进程hello的一个上下文切换,将控制返回给进程A中紧随在系统调用getchar之后的那条指令,进程hello继续执行。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
(以下格式自行编排,编辑时删除)
本章主要介绍了进程的概念以及进程在计算机中的调用过程。分析了hello的进程执行和异常与信号处理过程。计算机为每个程序抽象出一个概念为进程。Hello是以进程的形式运行,每个进程都处在某个进程的上下文中,每个进程也都有属于自己的上下文,用于操作系统通过上下文切换进行进程调度。通过对hello执行过程的分析,对各个信号的处理以形式理解更加深入。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
(以下格式自行编排,编辑时删除)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.1.1. 逻辑地址
逻辑地址是指由程序产生的与段相关的偏移地址部分。逻辑地址由一个段(segment)和偏移量(offset)组成,偏移量指明了从段开始的地方到实际地址之间的距离。即hello.o里相对偏移地址。
7.1.2. 线性地址
线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
7.1.3. 虚拟地址
虚拟地址是程序保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。就是hello里面的虚拟内存地址。
7.1.4. 物理地址
CPU通过地址总线的寻址,找到真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
(以下格式自行编排,编辑时删除)
一个逻辑地址由两部分组成,段选择符和段内偏移量。
段选择符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。TI:0为GDT,1为LDT。Index指出选择描述符表中的哪个条目,RPL请求特权级。
段寄存器(16位),用于存放段选择符CS(代码段):程序代码所在段SS(栈段):栈区所在段DS(数据段):全局静态数据区所在段其他3个段寄存器ES、GS和FS可指向任意数据段
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。
最初8086处理器的寄存器是16位的,为了能够访问更多的地址空间但不改变寄存器和指令的位宽,所以引入段寄存器,8086共设计了20位宽的地址总线,通过将段寄存器左移4位加上偏移地址得到20位地址,这个地址就是逻辑地址。将内存分为不同的段,段有段寄存器对应,段寄存器有一个栈、一个代码、两个数据寄存器。
分段功能在实模式和保护模式下有所不同。
实模式
即不设防,说逻辑地址=线性地址=实际的物理地址。段寄存器存放真实段基址,同时给出32位地址偏移量,则可以访问真实物理内存。
保护模式
线性地址还需要经过分页机制才能够得到物理地址,线性地址也需要逻辑地址通过段机制来得到。段寄存器无法放下32位段基址,所以它们被称作选择符,用于引用段描述符表中的表项来获得描述符。
在保护模式下,分段机制就可以描述为:通过解析段寄存器中的段选择符在段描述符表中根据Index选择目标描述符条目Segment Descriptor,从目标描述符中提取出目标段的基地址Base address,最后加上偏移量offset共同构成线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
(以下格式自行编排,编辑时删除)
线性地址(即虚拟地址VA)到物理地址(PA)之间的转换通过分页机制完成。而分页机制是对虚拟地址内存空间进行分页。
Linux系统有自己的虚拟内存系统,其虚拟内存组织形式如图7.3.1所示,Linux将虚拟内存组织成一些段的集合,段之外的虚拟内存不存在因此不需要记录。内核为hello进程维护一个段的任务结构即图中的task_struct,其中条目mm指向一个mm_struct,它描述了虚拟内存的当前状态,pgd指向第一级页表的基地址(结合一个进程一串页表),mmap指向一个vm_area_struct的链表,一个链表条目对应一个段,所以链表相连指出了hello进程虚拟内存中的所有段。
页表:
实现从虚拟页到物理页的映射,依靠的是页表,页表就是是一个页表条目 (Page Table Entry, PTE)的数组,将虚拟页地址映射到物理页地址。这个页表是常驻与主存中的,给定一个虚拟地址通过对页表的查询,有可能得到相对应的物理地址,如图7.3.2所示。
地址翻译:
例如32位的虚拟地址分成VPN+VPO,VPN是虚拟页号,这个值用来查询页表,VPO表示页内偏移。一个页面大小是4KB,所以VPO需要20位,VPN需要32-20=12位。CPU中的一个控制寄存器PTBR(页表基址寄存器)指向当前页表,MMU通过VPN来选择PTE(Page Table Entry),PTE(假设有效)中存放的即物理页号(PPN)。VPO和PPO是相同的。所以通过MMU,我们得到了线性地址相应的物理地址。这就是地址翻译的一个最基本的过程,之后的快表(TLB),多级页表都是在这个原理上对效率进行提高。
7.4 TLB与四级页表支持下的VA到PA的变换
(以下格式自行编排,编辑时删除)
在Intel Core i7环境下研究VA到PA的地址翻译问题。前提如下:
虚拟地址空间48位,物理地址空间52位,页表大小4KB,4级页表。TLB 4路16组相联。CR3指向第一级页表的起始位置(上下文一部分)。
解析前提条件:由一个页表大小4KB,一个PTE条目8B,共512个条目,使用9位二进制索引,一共4个页表共使用36位二进制索引,所以VPN共36位,因为VA 48位,所以VPO 12位;因为TLB共16组,所以TLBI需4位,因为VPN 36位,所以TLBT 32位。
CPU产生虚拟地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT(前32位)+TLBI(后4位)向TLB中匹配,如果命中,则得到PPN(40bit)与VPO(12bit)组合成PA(52bit)。
如果TLB中没有命中,MMU向页表中查询,CR3确定第一级页表的起始地址,VPN1(9bit)确定在第一级页表中的偏移量,查询出PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA,并且向TLB中添加条目。
如果查询PTE的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误
7.5 三级Cache支持下的物理内存访问
(以下格式自行编排,编辑时删除)
由于L1、L2、L3各级Cache的原理相同,只做L1 Cache的分析。L1 Cache是8路64组相连高速缓存。块大小64B。因为有64组,所以需要6 bit CI进行组寻址,共有8路,块大小为64B,所以需要6 bit CO表示数据偏移位置,因为VA共52 bit,所以CT共40 bit。
在上一步中已经获得了物理地址VA,使用CI进行组索引,每组8路,对8路的块分别匹配CT(前40位)。如果匹配成功且块的valid标志位为1,则命中(hit),根据数据偏移量CO(后六位)取出数据返回。
如果没有匹配成功或者匹配成功但是标志位是0,则不命中(miss),向下一级缓存中查询数据(L2 Cache->L3 Cache->主存)。查询到数据之后,一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生冲突(evict),则采用最近最少使用策略LFU进行替换。
7.6 hello进程fork时的内存映射
(以下格式自行编排,编辑时删除)
当fork函数被shell调用时,内核为hello创建各种数据结构,并分配给它一个唯一的PID。为了给hello创建虚拟内存,fork创建了当前进程的mm_struct、区域结构和页表的原样副本。
当fork在hello中返回时,hello现在的虚拟内存刚好和调用shell的虚拟内存相同。当这两个进程中的任何一个进行写操作时,写时复制机制会创建新页面。因此也就为每个进程保持了私有地址空间的概念
7.7 hello进程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。
7.7.1. 删除已存在的用户区域
删除当前进程虚拟地址的用户部分中的已存在的区域结构。
7.7.2. 映射私有区域
为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
7.7.3. 映射共享区域
hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
7.7.4. 设置程序计数器(PC)
设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
(以下格式自行编排,编辑时删除)
缺页故障:进程线性地址空间里的页面不必常驻内存,在执行一条指令时,如果发现他要访问的页没有在内存中(即存在位为0),那么停止该指令的执行,并产生一个页不存在的异常,对应的故障处理程序可通过从外存加载该页的方法来排除故障,之后,原先引起的异常的指令就可以继续执行,而不再产生异常。
会出现缺页异常的情况:
1.线性地址不在虚拟地址空间中
2.线性地址在虚拟地址空间中,但没有访问权限
3.没有与物理地址建立映射关系fork等系统调用时并没有映射物理页,写数据->缺页异常->写时拷贝
4.映射关系建立了,但在交换分区中,页面访问权限不足
缺页中断处理:
一个较为简单的表述为:确定缺页是由于对合法虚拟地址进行合法的操作造成之后,系统选择一个牺牲页面,如果这个牺牲页面被修改过,将其交换出去,换入新页面并更新页表,当缺页处理程序返回后,CPU重启引起缺页指令
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存分配器的基本原理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。不同系统有微小的差别但是一般来说堆存在于未初始化的数据后面并想高地址生长。对于每个进程,内核负责维护一个变量叫brk,它指向堆的顶部。
根据分配器的风格来分类可以分为显式分配器与隐式分配器:
显式分配器:
要求应用显式地释放任何已分配的块。例如C程序通过调用malloc函数来分配一个块,通过调用free函数来释放一个块。必须用户来主动来调用某些函数来进行内存的申请与释放。
隐式分配器:
这时需要分配器检测一个已分配块何时不再被程序所使用,那么久释放这个块,也叫做垃圾收集器,例如,诸如Lisp、ML、以及Java之类的高级语言就依赖垃圾收集来释放已分配的块。
碎片:
造成堆利用效率低的原因是碎片现象,碎片分为两种:
内部碎片:当一个已分配块比有效载荷的,由对齐要求产生
外部碎片:空闲内存合起来足够满足分配请求,但是处于不连续的内存片中
动态内存分配器首先需要实现的问题有:
1.空闲块如何组织 2.如何选择一个合适的空闲块来放置一个新分配的块
3.在空闲块中放置了一块已分配块之后如何处理空闲块的余下的位置呢
4.如何将刚刚释放的块与相邻的块进行合并
5.当前使用的堆的空间不够需要再分配
同时分配器在工作时有着严格的要求:
1.分配器必须可以处理任意请求序列,对于用户来说执行的malloc与free的数量未必对等,只要释放的块当前是已被分配的有应该响应。
2.为了提高分配的效率,分配器对于申请请求必须迅速响应,不能重新排列或者缓冲请求。
3.分配器必须对齐块,使得他们可以保存任何的数据类型。
4.分配器只能操作修改空闲块对于已分配的块,一旦一个块被分配,不允许改变大小及一些块的固有信息,即使块中有剩余也不能压缩。
分配器的设计可以有多种方式针对空闲块的组织方法有以下三种:
a.隐式空闲链表(implicit free list)
隐含链表方式即在每一块空闲或被分配的内存块中使用一个字的空间来保存此块大小信息和标记是否被占用。根据内存块大小信息可以索引到下一个内存块,这样就形成了一个隐含链表,通过查表进行内存分配。优点是简单,缺点就是慢,需要遍历所有。
b.显式空闲链表(explicit free list)
显示空闲链表的方法,和隐含链表方式相似,唯一不同就是在空闲内存块中增加两个指针,指向前后的空闲内存块。相比显示链表,就是分配时只需要顺序遍历空闲块,虽然在空间上的开销有所增大,但是放置以及合并操作所用到的时间会有所减少。
c.分离空闲链表(segregated free list)
分配器维护多个空闲链表,其中每个链表中的块大小大致相等,即讲这些空闲块分成一些等价类,先按照大小进行索引找到相应的空闲链表再,在链表内部搜索合适的块,这样相比于显式空闲链表时间效率更高。
针对查找空闲块的三个方法:
a.首次适应(first fit)
b.最佳适配(best fit)
c.下一次适配(next fit)
对于不同的空闲块组织方式,巧妙地设置头部和脚部,用指针可以有效地提升空闲块合并的效率
7.10本章小结
(以下格式自行编排,编辑时删除)
本章介绍了虚拟地址、线性地址、物理地址的概念与区别,从现代linux的内存系统出发,重点介绍了虚拟内存与物理内存之间的翻译转换通过虚拟地址到物理地址的转换,进一步加深了对虚拟地址空间的理解运作及其强大作用并且通过分析,对如何组织空闲区域、空间的申请、分割、合并、回收等具体过程,对动态分配函数malloc系列函数有了更深的认识
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口:一个Linux文件就是一个m个字节的序列:B0,B1,B2…….所有的I/O设备(如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对文件的读写操作。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为UnixI/O,这使得所有的输入输出都能以一种统一的方式且一致的方式来执行。
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.2.1、UNIX I/O接口
linux内核引出的一个简单、低级的应用接口被称为UNIX I/O接口,这使得所有的输入和输出都能以一种统一且一致的方式来执行:
l 打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
l Linux shell创建每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)、标准错误(描述符为2)。
l 文件位置:从文件开头起始的字节偏移量。
l 读写文件:读操作从文件赋值字节到内存,更改文件位置。写操作从内存复制字节到文件,也更新文件位置。
l 关闭文件:内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
8.2.2、UNIX I/O函数
下面举例最基本的四个函数:
l 打开文件open:int open(char* filename,int flags,mode_t mode)
l 关闭文件close:int close(fd)
l 读文件read:ssize_t read(int fd,void *buf,size_t n)
l 写文件write:ssize_t wirte(int fd,const void *buf,size_t n)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
int printf(const char* fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
int vsprintf(char* buf, const char* fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p = buf; *fmt; fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
本章主要讲述了Linux的IO设备管理方法,Unix I/O接口及其函数,以及printf函数实现的分析和getchar函数的实现。正是有了这些IO函数使得计算机能与用户良好的交互,也让我们进一步认识了底层的工作过程。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
hello历经了预处理、编译、汇编、链接形成一个可以加载到内存中的程序,这一步步的操作可谓是一环套一环。将hello加载到内存变成进程又要历经进程管理、内存管理、I/O管理,所以一个小小的hello想要实现也是真的不容易,但只要硬件与系统软件的协调合作能够实现hello,那么以后再大型的程序也就不在话下了。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c hello源程序
hello.i 预处理得到的文本文件
hello.s 编译得到的汇编文件
hello.o 汇编得到的可重定位目标文件
hello.out 链接后得到可执行文件