本大作业从基本的hello.c源程序出发,从预处理、编译、汇编、链接到后来的进程创建与回收实现了由点及面的概述与回顾。同时从这一系列过程中体会程序代码的撰写到最后的执行的计算机底层的机制。从内存的存储管理到I/O设备的应用。利用hello的一生帮我们对计算机系统有了全面的回顾。
关键词:计算机系统;程序执行;系统管理
目 录
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简介
P2P(Program to Progress):指由程序员根据C语言hello.c这个最初的高级语言源程序(Program)首先经过预处理器将源程序进行修改通过预处理过程生成预处理文件hello.i,随后提交给编译器将预处理文件翻译成文本文件hello.s,.s文件中包含了用汇编语言组成的源程序翻译文本,实现为不同的高级语言和不同的编译器提供通用的输出语言。形成汇编程序后,.s文件将被提交给汇编器,汇编器将其中的文本翻译成二进制的机器指令形式并打包成可重定位目标程序hello.o。随后,由于hello.c中调用了外部库中的printf函数,为此需要链接器将printf.o和hello.o进行链接,实现合并,最后生成可执行程序hello。有了可执行程序后,经过shell这个命令行解释器将输入的指令进行执行,把该程序放置与内存中,结束了一个指令的输入后,hello中的代码和数据被从磁盘复制到主存,处理器便开始执行hello中的main指令shell调用函数为hello创建一个新的进程和上下文,从而实现了从Program到Progress的转变。
O2O(Zero-0 to Zero-0):程序从最开始的被子进程调用execve函数加载并执行hello,OS为其分配对应的虚拟内存、运行结束后shell利用信号机制回收该进程。实现从无到有再到无的过程。
1.2 环境与工具
硬件环境:
- 处理器:AMD Ryzen 7 4700U with Radeon Graphics 2.0Ghz 8CPU
- 操作系统:Windows 10 家庭中文版 64位
- BIOS:RMARN6B0P0909
- 内存:16384MB RAM
Ubuntu 16.04 LTS 64位
软件环境:
-
-
- Code::Blocks
- Visual Studio
- VMWare Workstation Pro
-
开发工具:gcc
1.3 中间结果
hello.i:预处理器根据’#’开头的命令将原始的.c文件进行修改,根据提供的指令进行对应的操作,并将其添加至原有的文件中从而产生了.i文件,为下一阶段编译器进行编译做准备。
hello.s:汇编器通过读取.i文件中的指令,并将其语句以低级机器语言指令形式进行修改,.s文件为不同的编译器提供了通用的输出语言,为下一阶段翻译成机器指令做准备。
hello.o:经过hello.s汇编而成,为可重定位二进制目标程序,内容为机器可执行的二进制指令,等待下一步与调用的其他.o文件一同合并。
hello:是可执行目标文件,可以被加载到内存中,等待系统执行。
1.4 本章小结
从整体角度解析了hello.c源程序是如何一步步转换成系统中的一条进程(Progress)。源程序经过预处理-》编译-》汇编-》链接-》读入主存被处理器执行-》创建相应进程实现代码的执行过程。此为P2P(Program -> Progress)。0->0指一个程序由进程被创建到执行到最后被销毁的过程。从整体上概述了计算机系统对于程序的执行过程。
第2章 预处理
2.1 预处理的概念与作用
概念:预处理器(cpp)根据以字符’#’开头的命令,修改原始的C程序。Hello.c中# include <stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容并把它直接插入程序文本中,获得另一个C程序:hello.i
作用:为下一步编译提供准备文件
2.2在Ubuntu下预处理的命令
Gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
代码被大量扩充,源程序.c中的代码被放在最后部分进行展示。
同时看到了调用的头文件的地址:
2.4 本章小结
了解了预处理的概念与作用,通过生成的.i文件与.c文件进行了比对,从而了解二者的差异。通过学习了unbuntu系统下的gcc预处理命令,并对hello.c进行预处理生成hello.i
第3章 编译
3.1 编译的概念与作用
概念:编译器识别代码中的词汇、句子以及各种特定的格式,并将他们转换成计算机可以识别的二进制形式
作用:将预处理后的程序翻译成汇编语言程序,为不同高级语言的不同编译器提供了通用的输出语言
3.2 在Ubuntu下编译的命令
Gcc -Og -S hello.c
3.3 Hello的编译结果解析
3.3.1 常量的解析
字符串常量:程序中设计到的字符串常量有两处如下
其中中文字符串在汇编语言中以UTF-8的形式进行编码存储,而英文则是保持原样,在汇编代码中形式如下:
整型常量:整型常量在程序中出现的位置多用于表达式比较,从而我们可以在如下的汇编代码中找到:
3.3.2 变量的解析
局部变量的解析:在源程序中i作为循环结构中的循环变量进行使用,我们可以在汇编语言中找到其对应的寄存器及其在上面的一些操作。
可知i在这里是在寄存器%ebp上操作的。
3.3.3 赋值的解析
程序中需要赋值的地方为循环变量中i的初始化。在汇编语言中用movl指令呈现,如下图所示:
3.3.4 算术操作的解析
++操作:此为每次i执行i++的操作
3.3.5 关系操作的解析
!=关系:在汇编语言中用连续的两条指令实现,分别是cmpl和jne
<关系:在汇编语言中用subq指令呈现
3.3.6 数组/指针/结构操作的解析
main函数的参数中含有指针数组,每个元素都是char格式的指针,其首地址被存在寄存器中
3.3.7 控制转移的解析
If结构:程序中用不等关系进行判定,如果不等则跳转至if结构体内,与关系操作有异曲同工之妙。If结构体内的函数在.L6块内
.L6内容为:
For结构:for中首先对于循环变量i中进行定义,之后跳转至对应的循环体代码块.L2中,整体程序如下:
L3中是执行的循环体,L2 中前两行指令用来判断循环是否结束
3.3.8 函数操作的解析
Main:首先先将函数所需的参数放入相应的寄存器中,随后根据不同结构的函数进行不同的条件跳转,最后返回程序中设定的值
Printf:对应的汇编代码如下:
Exit:对应的汇编指令如下,首先把1放入寄存器中,之后调用exit
Sleep:首先需要读取sleep函数所调用的参数,为数组中的元素,故首先调用atoi将参数进行存储到寄存器中,再调用sleep
Getchar:
3.4 本章小结
了解了编译的概念及作用,同时通过查看.s文件的内容知晓编译器是如何将程序进行汇编语言的翻译。了解函数及数据如何通过寄存器等方式进行调用和处理。学习一定的汇编语言。
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器将.s文件翻译成机器语言指令,并将其打包成可重定位目标程序保存在.o文件中
作用:汇编语言直接面向机器硬件,故可以通过底层提高程序运行性能。
4.2 在Ubuntu下汇编的命令
Gcc -Og -c hello.c
4.3 可重定位目标elf格式
Elf格式的hello可重定位目标文件的分块情况如下
.text(已编译程序的机器代码) |
.rodata(只读数据) |
.data(已初始化的全局和静态变量) |
.bss(未初始化的全局和静态变量,初始化为0的全局或静态变量) |
.symtab(符号表) |
.rel.text(.text节的位置列表) |
.debug(调试符号表) |
.line(C源程序与.text指令间的映射) |
.strtab(字符串表) |
节头部表 |
4.3.1 ELF头
可以看出elf头中描述了我们程序的一些基本信息,是可重定位文件,以小端序存放,程序运行在x86-64系统之上。
4.3.2 节部头表
输入readelf -S hello.o后查看各节的基本信息:
可以清晰的显示我们elf格式下每个节的名称、类型、节头号、对应地址和链接情况。
4.4 Hello.o的结果解析
与编译阶段生成的汇编语言翻译版本的最主要不同在于每条指令前面都有其相应的十六进制表示的机器代码,证明机器指令在机器中是以数字的方式读取的,并且前面还附上了不同指令对应的地址。从内容上看我认为整体的汇编结果更加清晰,同时对于寄存器的分配也稍有不同。
机器代码利用更简单的抽象模型隐藏实现的细节。他利用了两种抽象,首先是由指令集体系结构来定义机器级程序的格式和行为,第二种抽象是通过将内存地址以虚拟地址的方式实现。汇编语言接近机器代码,但是机器代码以二进制的形式存在,而汇编代码则是可读性更好的文本形式。机器代码只是简单的将内存看作一个极大的按字节寻址的数组,C语言中的聚合类型被用一组连续的字节进行表示。机器代码提供一些低级机制来实现有条件的行为:测试数据值并根据其测试结果改变控制/数据流。通过利用jump来改变代码的执行顺序。例如switch结构中通过引入跳转表概念,将情况进行连续的内存分配,从而利用跳转指令和条件码进行匹配。当控制从函数跳转至另一个函数的时候,处理器会设置好原函数继续执行的代码地址,通过栈结构来实现。
4.5 本章小结
在本章我们从汇编器将hello.s转换成的hello.o文件进行了分析,比较了其与编译器生成的hello.s的不同,从侧面体会了机器语言和汇编语言的差异性,了解了二者转换的映射关系,程序结构在机器中的实际情况。同时使用gcc指令实现汇编过程,并利用相应工具进行反汇编的解析。对可执行重定位程序分节进行分析。从elf角度对于程序的结构有了了解。
第5章 链接
5.1 链接的概念与作用
概念:是将各种代码和数据片段收集并组合成为一个单一文件的过程。
作用:可以被加载到内存并执行,使得分离编译成为可能。
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
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
ELF头信息:起始地址为0x4010f0,大小为64字节。是x86-64架构
节头部表:有28节,其中起始地址为0x37c8
符号表:存放程序中定义和引用的函数和全局变量
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
虚拟地址从0x401000开始,到0x402000结束。
5.5 链接的重定位过程分析
Hello反汇编后的结果如下:
可以看到整体的节数变多了,由于链接中加载了调用的printf.o模块,使得程序整体的节数增加。同时虚拟内存的地址也稍有不同。相当于为其重新设定了内存地址。
重定位将合并输入模块,并为每个符号分配运行时的地址。首先是重定位节和符号定义。Hello中将hello.o和printf.o这两个输入模块的.data节被合并成一个节并将运行时的内存地址赋给新的聚合节,赋给输入模块的每个节以及每个模块定义的每个符号。这使得程序中的每条指令和全局变量都有唯一的运行时的内存地址。随后进行重定位节中的符号引用。相当于把虚拟地址映射为物理真实地址。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
Shell通过调用加载器的操作系统代码来运行。在程序头部表的引导下,加载器将hello可执行文件的片复制到代码段和数据段。随后,加载器跳转到程序的入口点,这里就是_start的地址,随后首先加载库函数,最后开始执行main函数。
程序名 | 程序地址 |
_init | 0x401000 |
.pit | 0x401020 |
.pit.sec | 0x401090 |
_start | 0x4010f0 |
_dl_relocate_static_pie | 0x401120 |
main | 0x401125 |
_libc_csu_init | 0x4011b0 |
_libc_csu_fini | 0x401220 |
_fini | 0x401228 |
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。首先找到elf中关于动态链接的地址
进入edb中查找对应的地址并查看。
GOT数组同PLT联合使用,GOT[0]和GOT[1]包含动态链接器在解析函数地址时使用的信息。在dl_init前后进行值得改变。
5.8 本章小结
本章介绍了链接的概念和作用,以Hello.o的链接过程实现。从hello的ELF格式入手,分析了其虚拟内存,重定位过程、执行过程和动态链接过程。链接后的文件可以在随后被创建进程而执行。
第6章 hello进程管理
6.1 进程的概念与作用
概念:一个执行中程序的实例
作用:文件在被创建的其对应的进程的上下文中运行。让程序可以对由程序变量表示的内部程序状态中的变化做出反应。
6.2 简述壳Shell-bash的作用与处理流程
Shell是一个应用程序,它链接了用户和Linux内核,让用户能更加高效、安全、低成本地使用Linux内核。
Shell执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行。求值步骤解析命令行,并代表用户运行程序。
对于基本程序逻辑:shell在其循环期间会通过三个步骤处理命令:
- 读取:从标准输入读取命令。
- 解析:将命令字符串分成程序和参数。
- 执行:运行已解析的命令。
处理流程:
- Shell首先从命令行中找出特殊字符(元字符),在将元字符翻译成间隔符 号。元字符将命令行划分成小块tokens。
- 程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
- 当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令 的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程 回到第一步重新分割程序块tokens。
- Shell对~符号进行替换。
- Shell对所有前面带有$符号的变量进行替换。
- Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用$(command) 标记法。
- Shell计算采用$(expression)标记的算术表达式。
- Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割 符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。
- Shell执行通配符* ? [ ]的替换。
- Shell把所有从处理的结果中用到的注释删除,并且按照下面的顺序实行命令的检查:
- 内建的命令
- shell函数(由用户自己定义的)
- 可执行的脚本文件(需要寻找文件和PATH路径)
- 在执行前的最后一步是初始化所有的输入输出重定向。
- 最后执行命令
6.3 Hello的fork进程创建过程
父进程通过调用该fork()函数创建一个新的子进程。子进程会得到与父进程用户级虚拟地址空间相同但是独立的一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。Fork调用一次会返回两次。通过返回值的不同区分子进程和父进程。进程的执行是并发的。
6.4 Hello的execve过程
Execve函数在当前进程的上下文中加载并运行一个新程序。它加载目标可执行文件并附带参数列表和环境变量列表,只调用一回且不返回。在加载了文件后调用启动代码,设置对应的栈并将控制传递给新程序的主函数。用户栈的顶部是系统启动函数的栈帧。
6.5 Hello的进程执行
处理器将同时运行中的进程分成不同的逻辑流,他们的执行是交错的,每个进程执行其流的一部分。操作系统通过利用上下文切换这种异常控制流实现任务并发进行。内核为每个进程维持一个上下文(内核重新启动一个被抢占的进程所需的状态)。内核通过调度来调整进程的上下文实现一直的高效率。利用其中出现的异常信号来决定不同进程的执行。进程有三种状态:运行、停止、终止。一个进程通过信号来控制这三种状态的切换。程序对于不同区域的读写权限也会有一定的划分,分为用户模式和核心模式(模式识别用当前的)。当处于故障、中断或是系统调用时才切换成核心模式。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
异常种类:一共是四类异常
- 中断:是异步发生的,来自处理器外部I/O设备的信号结果。通过向处理器芯片发送信号触发中断。使得当前指令执行完毕后,处理器感知到电压的升高,从而从系统总线中读取异常号并中断处理程序。如果是程序返回,那么会继续执行。
- 陷阱和系统调用:是一条指令的结果,将返回下一条指令。是为了在用户程序和内核间提供一个像过程一样的接口(系统调用)。运行在内核模式中,允许系统调用执行特权指令,并访问定义在内核中的栈。
- 故障:是由错误引起的,可能被修正。如果程序修正了该错误,则会继续执行,否则会引起程序的终止。
- 终止:是不可恢复的致命错误的结果,终止处理程序,同时不将控制返回给应用程序。
信号处理机制:信号允许进程和内核中断其他进程。一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。信号提供了一种机制,通知用户进程发生了这些异常。
发送信号:内核通过更新目的进程上下文中的某个状态,以发送信号的方式实现。
接收信号:目的进程内核被迫以某种方式堆信号发送作出反应
阻塞/解除阻塞信号:停止执行某个接收的信号,并在一定情况下接触阻塞
针对不同输入的情况:
-
-
- 正常输入:输入所需参数,运行图如下
-
-
-
- Ctrl c:此时接收信号SIGINT,结束程序运行。进行hello进程的查询无法查到
-
-
-
- Ctrl z:当前进程接收SIGSTP信号,使得其被挂起。
- Ps:查看其进程PID,
- Ctrl z:当前进程接收SIGSTP信号,使得其被挂起。
-
-
-
-
- Jobs
-
-
-
-
-
- Pstree
-
-
-
-
-
- Fg:调回前台继续执行
-
-
-
-
-
- Kill:终止进程
-
-
信号种类:
6.7本章小结
了解了进程的概念和作用,计算机系统中使用异常控制流来执行程序。熟悉并深入探查其中的信号机制并通过在Linux系统中实践查看不同信号的接收对于进程状态的改变。熟悉异常和信号的相互关系并根据理论进行实地探查。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序编译后出现在汇编代码中的地址,用来指定一个操作数或一条指令的地址,由段标识符加上偏移量表示。
线性地址:地址空间的整数是连续的
虚拟地址:为每一个进程提供的私有的、大的和一致的地址空间。
物理地址:计算机系统的主存被组织成一个由连续字节大小的单元的数组。为每个字节分配唯一的物理地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
1.看段选择符的T1=0还是1,判断当前要转换是GDT中的段还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组。
2.拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样基地址就知道了。
3.把Base + offset,就是要转换的线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
页表基址寄存器指向当前页表。虚拟地址包含两部分,一是虚拟页面偏移,另一个是虚拟页号。MMU利用VPN来选择适当的PTE,将页表条目中物理页号和虚拟地址中的VPO串联起来,得到了对应的物理地址。物理页面偏移和VPO是相同的。
CPU硬件执行的步骤如下:
- 处理器生成一个虚拟地址,并把它传送给MMU
- MMU生成PTE地址,并从高速缓存/主存请求得到它
- 高速缓存/主存向MMU返回PTE
- MMU构造物理地址,并把它传送给高速缓存/主存
- 高速缓存/主存返回所请求的数据字给处理器
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:TLB中每一行都保存着一个由单个PTE组成的块。TLB具有高度的相联度。用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中提取出来的。命中时会包括以下步骤:
- CPU产生一个虚拟地址
- MMU从TLB中取出相应的PTE
- MMU将这个虚拟地址翻译成一个物理地址,并将其发送到高速缓存/主存
- 高速缓存/主存将所请求的数据字返回给CPU
而当不命中时,MMU会从L1缓存中取出相应的PTE。并将其放在TLE中。
四级页表:虚拟地址被划分成4个VPN和1个VPO。每个VPN i都是一个到第i级页表的索引。前三级页表每个PTE都指向下一级的某个页表的基址。第四级页表中的每个PTE包含某个物理页面的PPN或一个磁盘块的地址。
7.5 三级Cache支持下的物理内存访问
L1Cashe的物理访存大致过程如下:
(1) 组选择:取出虚拟地址的组索引位,把二进制组索引转化为一个无符号整数,找到相应的组
(2) 行匹配:把虚拟地址的标记为拿去和相应的组中所有行的标记位进行比较,当虚拟地址的标记位和高速缓存行的标记位匹配时,而且高速缓存行的有效位是1,则高速缓存命中。
(3) 字选择:一旦高速缓存命中,我们就知道我们要找的字节在这个块的某个地方。因此块偏移位提供了第一个字节的偏移。把这个字节的内容取出返回给CPU。
(4)不命中:如果高速缓存不命中,那么需要从存储层次结构中的下一层取出被请求的块,然后将新的块存储在组索引位所指示的组中的一个高速缓存行中。一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,产生冲突,则采用最近最少使用策略 LFU进行替换。
7.6 hello进程fork时的内存映射
当调用fork被Hello这个进程调用时,内核为该新进程创建了相应的数据结构并分配给它一个唯一的PID。他会创建当前hello进程的mm_struct、区域结构和页表的原样副本。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
首先删除已存在的用户区域,随后映射到私有区域。对于一些动态链接对象,会映射到共享区域。做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:程序会选择一个牺牲页,内核从磁盘复制对应的内容到DRAM中,更新对应的PTE,随后重新启动导致缺页的指令。
缺页中断处理:当缺页程序返回时CPU重新启动引起缺页的指令。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区块,称作堆。对于每个进程,内核维护着一个变量grk,指向堆的顶部。
分配器将堆视作一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么已分配,要么空闲。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲直到被显式地应用分配。一个已分配地块保持已分配状态,直到被释放。可以是显式或隐式。
显式分配器:应用显式地释放已分配的块
隐式分配器:检测一个块不再被程序所使用那么就释放
显式的分配器需要有一些严格的约束条件:
-
-
-
- 处理任意请求序列
- 立即响应分配请求
- 只使用堆
- 对齐块
- 不修改已分配的块
-
-
分配器使用隐式空闲链表来区别块边界、已分配块和空闲块。
放置已分配的块:放置策略如首次适配、下一次适配和最佳适配。
分割空闲块:一是选择用整个空闲块,但是会造成内部碎片。另一种是将空闲块分成两部分,一部分变成分配块,另一部分变成新的空闲块。
合并空闲块:立即合并或者延迟合并
7.10本章小结
本章对hello程序运行时虚拟地址的变化进行分析,解析了hello应用程序的虚拟存储地址空间,分析了虚拟地址,线性地址和虚拟物理线性地址之间的互相转换,页表的命中与不页表的命中,使用动态快表缓存作为页表的高速缓存以及如何加速页表,动态内存管理的操作,fork时的动态内存中断与映射、execve时的动态内存中断与映射、缺页的中断与缺页映射和中断的处理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件, 所有的I/O设备都被模型化为文件,所有的输入和输出都被当做对相应文件的读和写来执行
设备管理:unix io接口, 允许Linux内核引出一个简单、低级的应用接口,这让所有的输入和输出都可以以一种统一且一致的方式执行。
8.2 简述Unix IO接口及其函数
所有的I/O设备都被模型化为文件,所有的输入和输出都被当做对相应文件的读和写来执行,通过将设备优雅的映射为文件,允许Linux内核引出一个简单、低级的应用接口,这让所有的输入和输出都可以以一种统一且一致的方式执行。
打开文件(open):用于打开或创建文件,可以指定文件的信息。打开成功返回文件描述符,失败返回-1
改变当前的文件位置(lseek):将文件描述符指定的文件的指针位移到别处
读文件(read):从文件中读取数据,读到EOF返回0,成功返回文件字节数,否则返回-1
写文件(write):向文件写入数据,写入成功返回文件字节数,读到EOF返回0,失败返回-1
关闭文件(close):关闭被打开的文件,通过文件描述符确定,关闭成功返回0,否则返回-1
检索文件信息(stat):检索到关于文件的信息。以文件名作为输入。
8.3 printf的实现分析
Pritnf的函数体如下:
- 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;
- }
Va_list是一个字符指针,fmt也是一个指针,指向第一个const参数中的第一个元素。Vsprintf返回打印字符串的长度,将缓冲区中的字符一个个通过sys_call来实现打印。
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
Getchar由宏实现: #define getchar() getc()stdin。
Getchar以一个Int型作为返回值。当程序调用该函数时,将用户输入的字符存放在键盘缓冲区中,直到收到回车结束。返回值是用户输入字符的ASCII码,发生错误返回-1,且将用户输入的字符显示到屏幕上。一直读取知道缓冲区的所有输入字符都被读取完毕。
8.5本章小结
介绍了Linux下I/O的处理机制,认识了UNIX I/O中的一些基本函数,了解了文件这个抽象出的概念。同时对于C语言中的高级I/O函数的机理有了更进一步的了解。
结论
源程序编写:从最开始我们利用C高级语言进行编写的hello.c程序,
预处理:首先经过预处理器预处理得到hello.i文件,
编译:随后交由编译器将其中内容进行向汇编语言的一次翻译,
汇编:之后转交给汇编器实现由文本文件向机器语言的转化。这时生成的时可重定位目标程序。
链接:在经过链接器将所有的会用到的可重定位目标程序进行链接后,生成了我们需要的hello可执行程序。
进程创建和回收:有了可执行程序,我们要在系统中对该程序进行执行。系统会为其创建一个进程,并通过虚拟内存和存储机制设立进程并执行。在进程结束后对其进行回收,完成一次hello的实现。在这个过程中,我们会利用系统的I/O机制将内容输出至屏幕上。
计算机系统中的原理和机制相当的复杂和精巧。虽然我们课程的学分不多,但是却可以从宏观角度对计算机的底层原理进行窥探。从我们程序员码代码的高级语言一步步转化成机器可以识别的机器语言,从近似人类语言到一串01数字,计算机系统的精巧结构让我们对已习以为常的计算机有了不一样的看法。未来我相信会是量子计算机的天下。届时计算机不会单纯的抽象到电路的两态(高低电平),而是通过量子具有的多态来实现计算的大突破。从而颠覆现有的计算机系统的组成。在量子世界里,量子比特可同时处于多种态,它可以是几种不同量子态当中的任意几种归一化线性组合,这种状态即我们常说的:量子叠加态。从而改变我们对于目前计算机的看法。
Hello.i: 预处理后的文本文件
Hello.s:编译后的文本文件
Hello.o:汇编后得到的可重定位目标程序
Hello:链接后得到的可执行文件
Hello_disassemble.s:hello.o文件反汇编得到的结果
Hello_objdumo.s:可执行文件hello反汇编得到的文件
helloELF.txt:hello.o的ELF格式
helloELF1.txt:可执行文件hello的ELF格式
参考文献
[1] Randal E. Bryant & David R. O’Hallaron 《Computer Systems: A Programmer’s Persepective》
[2] C语言编译和链接详解(通俗易懂,深入本质) (biancheng.net)
[3] (1条消息) ELF格式可重定位目标文件_code-freshman的博客-CSDN博客_elf可重定位目标文件的格式