大作业
题 目 程序人生-Hello’s P2P
专 业 计算机学院
学 号 120L020924
班 级 2003009
学 生 陈宇轩
指 导 教 师 郑贵滨
计算机科学与技术学院
2021年5月
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
摘 要
本文通过以hello.c程序为例,介绍了程序在计算机上的执行过程,包括预处理,编译,汇编,链接,进程管理等过程。
关键词:预处理;编译;汇编;链接;进程;存储;IO管理;P2P;020
目 录
2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式........................................................................ - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程......................................................................... - 10 -
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.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
Hello的P2P就是From Program to Process的整个过程。
1.通过编辑器编写hello的程序建立.c文件,得到hello.c的源程序。
2.运行C预处理器(cpp)进行预处理得到hello.i。
3.运行C编译器(ccl)将其进行翻译生成汇编语言文件hello.s。
4.运行汇编器(as)将其翻译成一个可重定位目标文件hello.o。
5.运行链接器程序ld将hello.o和系统目标文件组合起来,创建了一个可执行目标文件hello。
Hello的020就是From Zero-0 to Zero-0。
在bash调用fork函数创建子进程后,会调用execve函数来进行虚拟内存的映射,然后加载物理内存,进入到main函数当中执行相关的代码,打印出信息。在进程中,TLB、4级页表、3级Cache,Pagefile等等设计会加快程序的运行。程序运行完成后,bash会回收子进程,内核清楚数据痕迹。这就是020的过程。
1.2 环境与工具
硬件环境
X64 CPU;2.60GHz;32G RAM;1T Disk
软件环境
Windows10 64位;Vmware 11;Ubuntu 16.04 LTS 64位
开发工具
Visual Studio 2019 64位;CodeBlocks 64位
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c 源程序
hello.i 预处理后文件
hello.s 编译后的汇编文件
hello.o 汇编后的可重定位目标执行文件
hello 链接后的可执行文件
1.4 本章小结
第一章介绍了hello程序的P2P过程和020过程,让我们明白了hello程序是如何产生的,并且介绍了实验的软硬件环境以及开发工具。
第2章 预处理
2.1 预处理的概念与作用
概念:预处理会读取C语言程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。即扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。
作用:根据源代码中的预处理指令修改源代码,将宏和常量标识符用相应的代码和值替换,删去多余的空白字符,生成.i文件。
2.2在Ubuntu下预处理的命令
命令:gcc -E -o hello.i hello.c
图2.2 1
图2.2 2 hello.i
2.3 Hello的预处理结果解析
通过gcc的预处理阶段,源程序代码扩展到三千行,主要原因是预处理阶段,会进行头文件的展开、宏替换、去掉注释和多余的空白字符。
而代码行数增加的主要原因是因为进行了头文件展开。
2.4 本章小结
本章主要介绍了预处理的概念和作用。终端输入gcc -E hello.c -o hello.i命令生成了hello.i文件, hello.i于源代码对比能让我们更加理解预处理的方式。预处理主要进行了:头文件展开、宏替换、条件编译、去除多余字符。
第3章 编译
3.1 编译的概念与作用
概念:编译是从.i文件到.s文件的过程,此处是将hello.i转换为hello.o。编译过程将预处理得到的文件转换为汇编文件。
作用:编译所要做的工作就是通过词法分析和语法分析,确认是否语句都符合语法规则,有错误则提示,之后将预处理程序翻译成等价的中间代码表示或汇编代码。(此处是hello.i到hello.s)
3.2 在Ubuntu下编译的命令
输入gcc -S -o hello.s hello.i
图3.21
3.3 Hello的编译结果解析
3.3.1汇编指令介绍
.file:声明源文件
.text:代码节
.section:指示把代码划分成若干个段(Section)
.rodata:只读代码段
.align:数据或者指令的地址对其方式
.string:声明字符串
.global:声明全局变量
.type:声明一个符号是数据类型还是函数类型
在hello.s中用到的数据类型有整数,数组,字符串。
图3.31 hello.s
3.3.2整数
i为局部变量,局部变量通常被保存在栈或者寄存器中。代码中i被存储在栈 %rbp处,为int类型,空间大小为四个字节。
变量argc为形参,由寄存器%edi保存。
其他常数,形式为$+常数。
3.3.3数组
程序中定义了数组char *argv[]。保存在寄存器%rsi中。随后保存到寄存器%rbp中,同时申请32字节的空间。argv数组中一个元素的大小为8字节,加上偏移量进行访问数组中的各个元素。
3.3.4字符串
字符串形式为: Hello 学号 姓名 秒数!和Hello %s %s,存储在.rodata节中,汉字以UTF-8格式编码,一个汉字占3个字节,用\隔开。
3.3.5赋值
赋值操作i=0。
3.3.6 类型转换
本程序用atoi函数将argv[3]由字符串转换为了整型。
3.3.7算术运算
进行了i++的算术运算。
为数组进行空间扩展的算数运算。
找数组相应内容所加的偏移量也为算术运算。
3.3.8关系操作
if(argc!=4)和for(i=0;i<8;i++)两处进行了关系比较。判断后决定是否跳转。
3.3.9 地址运算
由初始地址加偏移量找到字符串。
3.3.10 循环
符合判断条件将进行循环操作。
3.3.11 函数操作
main函数传入初始参数。
函数调用。
参数一般存入%edi中,返回值存入%eax中,再用return返回。
return
3.4 本章小结
了解了编译的作用,由hello的编译结果,了解了c程序翻译成汇编语言时的处理方式。此时的hello程序变成了更底层的语言。
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as)将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序,将结果保存在.o 目标文件中,.o 文件是一个二进制文件,内含程序的指令编码。
作用:将汇编代码转换为机器指令,使其在链接后能被机器识别并执行。
4.2 在Ubuntu下汇编的命令
指令:gcc -c -o hello.o hello.s
图4.21
4.3 可重定位目标elf格式
生成指令:readelf -a hello.o > hello.elf
图4.31 ELF格式
elf文件中的内容:
1.ELF头:ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
2.节头:记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。
3. 重定位节,保存的是.text节中需要修正进行重定位的信息;任何调用外部函数或者引用全局变量的指令都需要被修正;调用外部函数的指令需要重定位;引用全局变量的指令需要重定位; 调用局部函数的指令不需要重定位;在可执行目标文件中不存在重定位信息。包括以下几个部分:Offset:需要被修改的引用节的偏移;Info:包括symbol和type两个部分,symbol在前面四个字节,type在后面四个字节;symbol:标识被修改引用应该指向的符号;Type:告知链接器应该如何修改新的应用;Attend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整Name:重定向到的目标的名称。
4. 符号表,存放程序中定义和引用的函数和全局变量的信息。每个可重定位目标文件在.symtab中都有一张符号表,name是符号名称, value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type数据或者函数。Bind字段表明符号是本地的还是全局的。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并与第3章的 hello.s进行对照分析。
图 4.41 hello.o
与hello.s的差异:
- 跳转地址
跳转指令用的不是段名称比如.L3,而是用的确定的地址。段名称只是在汇编语言中便于编写的助记符,在汇编成机器语言之后显然不存在,因而转换为反汇编语言是确定的地址。
- 函数调用
调用函数不再是函数名,而是重定位条目指引的信息。而汇编文件中call后面直接加文件名。
- 立即数转化为16进制
16进制与2进制之间的转换比十进制更加方便,因而转化时将十进制转换为了16进制
4.5 本章小结
本章主要介绍汇编的概念与作用,介绍了如何得到文件hello.o的操作命令。用readelf对elf文件做了详细的分析,并比较了hello.o的反汇编文件与之前得到的hello.s文件,体现了反汇编语言与最初的汇编语言的差别。
第5章 链接
5.1 链接的概念与作用
1.链接的概念:
链接是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。
2.链接的作用:
链接器使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其它文件。
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的格式
命令:readelf -a hello > hello.elf
elf头:
节头:
描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
重定位节:
Symbol table:
5.4 hello的虚拟地址空间
地址是从0x401000开始的,开头应为elf。由5.3中的节头部表,可以通过edb找到各个节的信息。
图 5.41 edb data dump
5.5 链接的重定位过程分析
命令: objdump -d -r hello > hello2.txt
不同:
- 在链接过程中,hello中加入了代码调用的一些库函数,例如getchar,puts,printf,等,并且给了相应地址。
- 新增加了.init和.plt。
- hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。(显然hello已经进行过定位)
- 链接完成,hello中的对于地址的访问或是引用都使用虚拟地址。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
载入:
_init 0x401000
开始:
_start 0x4010f0
__libc_start_main 0x7fbe71ac0bc0
执行:
main 0x401125
puts@plt 0x401030
exit@plt 0x401070
printf@plt 0x401040
sleep@plt 0x401080
getchar@plt 0x401050
5.7 Hello的动态链接分析
可以看出.got.plt节从虚拟地址0x404000开始。
5.8 本章小结
链接将各种代码和数据片段收集并合成为一个单一文件。利用链接器,分离编译称为可能,我们不用将应用程序组织为巨大的源文件,把它分解为更小、更好管理的模块,并在应用时将它们链接形成完成一个完整的任务。
链接得到了一个可执行文件,在shell中调用命令可以创建进程并执行该文件。
第6章 hello进程管理
6.1 进程的概念与作用
1.进程的概念:
经典定义就是一个执行中程序的实例。广义定义是进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
2. 进程的作用:
进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
6.2 简述壳Shell-bash的作用与处理流程
作用:Linux系统中,Shell是一个交互型应用级程序,为使用者提供操作界面,接收用户命令,然后调用相应的应用程序。
处理流程:
- 从终端读入命令。
②将输入字符串分模块处理。
③检查第一个命令行参数是否是一个内置的shell命令,如果是则执行,不是内部就调用fork( )创建新进程/子进程执行指定程序。
④shell应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
父进程调用fork函数创建一个新的运行的子进程。调用fork函数后,新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。fork被调用一次,却返回两次,子进程返回0,父进程返回子进程的PID。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。
图6.31 fork进程创建流程
6.4 Hello的execve过程
子进程通过调用execve函数来调用加载器,加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序。。exceve函数加载并运行可执行目标文件,并带参数列表和环境变量列表。只有当出现错误时,exceve才会返回到调用程序。
6.5 Hello的进程执行
进程调度:即使在系统中通常有许多其他程序在运行,进程也可以向每个程序提供一种假象,好像它在独占地使用处理器。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一的对应于包含在运行时动态链接到程序的共享对象中的指令。这个PC的序列叫做逻辑控制流,或者简称逻辑流。进程是轮流适用处理器的,每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
内核模式转变到用户模式:操作系统内核使用上下文切换来实现多任务。内核为每个进程维持一个上下文,它是内核重启被抢占的进程所需的状态,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构的值。
进程执行到某些时刻,内核可决定抢占该进程,并重新开启一个先前被抢占了的进程,这种决策称为调度。内核调度一个新的进程运行后,通过上下文切换机制来转移控制到新的进程:1)保存当前进程上下文;2)恢复某个先前被抢占的进程被保存的上下文3)将控制转移给这个新恢复的进程。当内核代表用户执行系统调用时,可能会发生上下文切换,这时就存在着用户态与核心态的转换。
图6.5 1进程执行流程
6.6 hello的异常与信号处理
正常
按下ctrl+z
输入ctrl-z默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下,用ps命令可以看到,hello进程并没有被回收。
输入fg
。此时他的后台 job 号是 5,调用 fg 5 将其调到前台,此时 shell 程序首先打印 hello 的命令行命令, hello 继续运行打印剩下的 8 条 info,之后输入字串,程序结束,同时进程被回收.
输入ctrl+c
在键盘上输入Ctrl+c会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业,用ps查看前台进程组发现没有hello进程.
不停乱按
无关输入被缓存到stdin,并随着printf指令被输出到结果。
6.7本章小结
本章阐述了进程的概念及作用,简要说明了Shell-Bash的概念和作用以及shell的处理流程。分析了hello的fork进程创建过程和execve过程,hello的进程执行。并且简要实践的hello的异常与信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址指由程序产生的与段相关的偏移地址部分,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。从hello的反汇编代码中看到的地址,它们需要通过计算,通过加上对应段的基地址才能得到真正的地址,这些便是hello中的逻辑地址。
线性地址:是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生逻辑地址,hello的反汇编文件中看到的地址(即逻辑地址)中的偏移量,加上对应段的基地址,便得到了hello中内容对应的线性地址。
虚拟地址:有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的,是hello中的虚拟地址。
物理地址:是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。在hello的运行中,在访问内存时需要通过CPU产生虚拟地址,然后通过地址翻译得到一个物理地址,并通过物理地址访问内存中的位置。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。全局的段描述符,放在全局段描述符表中,一些局部的段描述符,放在局部段描述符表(LDT)中。 给定一个完整的逻辑地址段选择符和段内偏移地址,确定要转换的是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟地址包含两个部分:VPN(虚拟页号)、VPO(虚拟页面偏移)
物理地址包含两个部分:PPN(物理页号)、PPO(物理页面偏移)
其中虚拟页面偏移和物理页面偏移是相同的。
页表基址寄存器指向当前的页表。MMU通过VPN在页表中找到对应的页表 项PTE。将PTE中的PPN与VPO串联,得到最终的物理地址。
图7.3 1线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。
虚拟地址VA虚拟页号VPN和虚拟页偏移VPO组成。若TLB命中时,所做操作与7.3中相同;若TLB不命中时,VPN被划分为四个片,每个片被用作到一个页表的偏移量,CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1 PTE的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2 PTE的偏移量,依次类推。最后在L4页表中对应的PTE中取出PPN,与VPO连接,形成物理地址PA。
7.5 三级Cache支持下的物理内存访问
物理地址的结构包括组索引位CI(倒数7-12位),使用它进行组索引,使用组索引位找到对应的组之后。假设我们的cache采用8路的块,匹配标记位CT(前40位)如果匹配成功且寻找到的块的有效位valid上的标志的值为1,则命中,根据数据偏移量CO(后6位)取出需要的数据然后进行返回。
如果没有数据被匹配成功或者匹配成功但是标志位是 1,这些都是不命中的情况,向 下一级缓存中查询数据(L2 Cache->L3 Cache->主存),并且将查找到的数据加载到cache里面。这时候我们面临一个问题,替换谁。一般我们会选择使用一种常见的简单替换策略,查询得到的数据之后,如果我们映射得到的组内已经有很多个空闲块,则直接在组内放置;否则组内都已经是有效块,产生了冲突,则我们会采用最近最少最少使用(lfu)的策略,然后确定将哪一个块替换作为牺牲块。
7.6 hello进程fork时的内存映射
当 fork 函数被 shell 进程调用时,内核为新进程创建各种数据结构,并分配给 它一个唯一的 PID,为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只 读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要:
(1)删除已存在的用户区域
(2)映射私有区域:为新程序hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。
(3) 映射共享区域:如果hello程序与共享对象(或目标)链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
(4) 设置程序计数器(PC) ,指向代码的入口点。
7.8 缺页故障与缺页中断处理
页面命中完全是由硬件完成的,而处理缺页是由硬件和操作系统内核协作完成的:
处理器生成一个虚拟地址,并将它传送给MMU
MMU生成PTE地址,并从高速缓存/主存请求得到它
高速缓存/主存向MMU返回PTE
PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
缺页处理程序页面调入新的页面,并更新内存中的PTE
缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆.系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址) .对于每个进程,内核维护着一个变量brk, 它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护.每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的.已分配的块显式地保留为供应用程序使用.空闲块可用来分配.空闲块保持空闲,直到它显式地被应用所分配.一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格. 两种风格都要求应用显式地分配块.它们的不同之处在于由哪个实体来负责释放已分配的块
显式分配器(explicit allocator):
要求应用显式地释放任何已分配的块.例如,C标准库提供一种叫做malloc程序包的显式分配器.C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块.C++中的new和delete操作符与C中的malloc和free相当.
隐式分配器(implicit allocator):
要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块.隐式分配器也叫做垃圾收集器(garbage collector),而自动释放未使用的已分配的块的过程叫做垃圾收集( garbage collection).例如,诸如Lisp、ML以及Java之类的高级语言就依赖垃圾收集来释放已分配的块。
一个块是由一个字的头部,有效载荷,以及可能的一些额外的填充组成的.头部编码了这个块的大小,以及这个块是已分配的还是空闲的.如果我们强加一个双字的对齐约束条件,那么块大小就总是8的倍数,且块大小的最低3位总是零.因此,我们只需要内存大小的29个高位,释放剩余的3位来编码其他信息.在这种情况中,我们用其中的最低位(已分配位)来指明这个块是已分配的还是空闲的。
7.10本章小结
本章简要介绍了hello的存储器的地址空间,介绍了四种地址空间的差别和地址的相互转换。同时介绍了hello的四级页表的虚拟地址空间到物理地址的转换。阐述了三级cashe的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
一个Linux文件就是一个m个字节的序列,所有的 IO 设备都被模型化为文件,而所有的输入和输出都被 当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单低级的应用接口,称为 Unix I/O。8.2 简述Unix IO接口及其函数
Unix I/O 接口:
(1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想 要访问一个 I/O 设备,内核返回一个小的非负整数,叫做描述符,它在 后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文 件的所有信息。
(2)Shell 创建的每个进程都有三个打开的文件:标准输入,标准输出,标 准错误。 (3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位 置 k,初始为 0,这个文件位置是从文件开头起始的字节偏移量,应用 程序能够通过执行 seek,显式地将改变当前文件位置 k。
(4)读写文件:一个读操作就是从文件复制 n>0 个字节到内存,从当前文 件位置 k 开始,然后将 k 增加到 k+n,给定一个大小为 m 字节的而文 件,当 k>=m 时,触发 EOF。类似一个写操作就是从内存中复制 n>0 个字节到一个文件,从当前文件位置 k 开始,然后更新 k。
(5)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢 复到可用的描述符池中去。
Unix I/O 函数:
(1)int open(char* filename,int flags,mode_t mode) ,进程通过调用 open 函 数来打开一个存在的文件或是创建一个新文件的。 open函数将filename 转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在 进程中当前没有打开的最小描述符,flags 参数指明了进程打算如何访 问这个文件,mode 参数指定了新文件的访问权限位。
(2)int close(fd),fd 是需要关闭的文件的描述符,close 返回操作结果。
(3) ssize_t read(int fd,void *buf,size_t n),read 函数从描述符为 fd 的当前文 件位置赋值最多 n 个字节到内存位置 buf。返回值-1 表示一个错误,0 表示 EOF,否则返回值表示的是实际传送的字节数量。
4) ssize_t wirte(int fd,const void *buf,size_t n),write 函数从内存位置 buf 复制至多 n 个字节到描述符为 fd 的当前文件位置。
8.3 printf的实现分析
Vsprintf:接受一个格式化的命令,并把制定的匹配的参数格式化输出。
Write:把字符串中n个元素的值写到终端(n为第二个参数)
系统调用:显示格式化的字符串。
https://www.cnblogs.com/pianist/p/3315801.html
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。
字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。
显示芯片会按照一定的刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
于是我们的打印字符串就显示在了屏幕上。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章中简单的描述了linux的io的接口及其设备和管理模式,unixio的接口及其使用的函数,还有常见的printf函数和pritgetchar函数,当然,如果真的对此展开了分析,printf函数的设计和实现其实看起来还是相当困难的,所以仅仅是简单的了解,对此的深入了解还是有些过浅,需要不断的分析和学习实践来增进自己的认识和了解。
结论
用计算机系统的语言,逐条总结hello所经历的过程。
Hello从原程序.c文件经过预处理,编译,汇编,链接得到了可执行文件。执行时,通过os对命令的处理结果,hello被分配到了自己的存储空间,虚拟内存地址和时间片。过程中,I/O不断接受信号并对其进行处理。运行结束后,内核清除已分配的数据空间,还原系统状态。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
这门课的学习,让我对计算机系统的底层有了更加深刻的认识,明白了为了实现计算机底层的功能优化付出的心血与汗水。同时也明白了实践出真知。
附件
Hello.c: 源文件
Hello.i: 预处理输出文件
Hello.s: 编译输出文件
Hello.o:汇编输出文件
Hello:链接输出文件
参考文献
[1] 兰德尔 E.布莱恩特,大卫 R.奥哈拉伦. 深入理解计算机系统. 机械工业出版社
[2]内存地址转换与分段_drshenlei的博客-CSDN博客 地址转换与分段
[3]CSDN博客 ELF可重定位目标文件格式
[4]CSDN博客 execve系统调用_进程装载过程分析(execve系统调用分析)