计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 航天
学 号 7203610504
班 级 2036016
学 生
指 导 教 师 史先俊
计算机科学与技术学院
2022年5月
本文从hello.c源程序开始,逐一介绍预处理/编译/汇编/链接等生成可执行目标文件的必经步骤,完整阐述了hello程序的整个生命周期,利用各种工具研究它的方方面面,同时介绍了存储器体系结构/控制控制流等hello运行时不可或缺的资源与支持,是对自己半年所学的整理,一次温故知新的能力提升。
关键词:编译;链接;虚拟内存;进程;异常控制流
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
6.2 简述壳Shell-bash的作用与处理流程 - 10 -
7.2 Intel逻辑地址到线性地址的变换-段式管理 - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理 - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 11 -
7.7 hello进程execve时的内存映射 - 11 -
第1章 概述
1.1 Hello简介
hello程序的生命周期是从一个高级C语言程序开始的,因为这种形式能够被人读懂。然后编译器驱动程序通过预处理,编译,汇编,链接四个阶段,将hello.c(program)翻译成一个可执行目标文件hello。hello文件存放在磁盘中,可以被加载到内存中,有系统执行。hello叫做程序(Program),用户通过shell,调用一系列函数将hello运行在内存中。他是通过一种叫做进程(Process)的抽象来实现的。
execve函数将hello加载至内存,顺着逻辑控制流,hello在硬件中取指译码执行,最终显示在屏幕上。最后程序终止,shell将子进程回收。
1.2 环境与工具
软件:windows10 64,vmvare11,Ubuntu 16.04
开发工具:Visual Studio 2010 64位以上;Codeblocks
调试工具:gdb,edb,readelf
1.3 中间结果
hello.s 汇编代码文件
hello.o 可重定位目标程序
hello 可执行目标程序
1.4 本章小结
本章首先介绍了hello.c的P2P与O2O,然后列出本文所需软硬件工具及处理hello.c的各项中间结果。实际上,发生在hello.c程序身上的一系列操作是精确而紧密结合的,关于这一点将于后文逐步展示。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
C语言标准规定,预处理是指前4个编译阶段(phases of translation)。
1.三字符组与双字符组的替换
2.行拼接(Line splicing): 把物理源码行(Physical source line)中的换行符转义字符处理为普通的换行符,从而把源程序处理为逻辑行的顺序集合。
3.单词化(Tokenization): 处理每行的空白、注释等,使每行成为token的顺序集。
4.宏扩展与预处理指令(directive)处理.
2.2在Ubuntu下预处理的命令
cpp hello.c > hello.i
(以下格式自行编排,编辑时删除)
应截图,展示预处理过程!
2.3 Hello的预处理结果解析
发现修改后的hello.i程序从源程序hello.c中的23行增加到了3042行,main函数在hello.i的最后一部分。hello.i程序处理了所有的“#include”预编译指令,并且从最后的main函数可以看出,hello.i程序删除了所有的注释。
(以下格式自行编排,编辑时删除)
2.4 本章小结
这一章介绍了编译系统中的预处理阶段的概念,以及具体的操作,并且分析了预处理结果hello.i文件。预处理阶段即预处理器cpp将源程序文件hello.c与其相关的头文件(例如stdio.h)预处理成一个修改了的源文件(.i文件)。
(以下格式自行编排,编辑时删除)
(第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的编译结果解析
.text | 代码段其实位置 |
.section .rodata | 下面是rodata部分 |
.align 8 | 对齐方式为8字节 |
.string “\347\224\250\346\263\225: Hello \345……\201” | 数据段中的字符串常量 |
.string “Hello %s %s\n” | 数据段中的字符串常量 |
.globl main | 全局变量(标签main是一个可以在其它模块的代码中被访问的全局符号) |
.type main, @function | 指定是对象类型或者函数类型 |
数据 说明
Int | i | 局部变量储存在寄存器或者栈里在hello.s中可知i储存在-4(%rbp)处 |
Int | argc | Main函数的第一个参数由hello.s可知储存在-20(%rbp)中 |
字符串(两个字符串都储存在rodata中,作为全局变量) | “用法hello学号姓名秒数!\n” .”hello %s%s!\n” | 第一个printf传入输出格式化的参数,在hello.s中字符串被编码成utf—8格式一个汉字占3个字节 第二个参数后两个字符放在argc(1)argc(2)中 |
指向字符串的指针数组 | argc[] | main函数的第二个参数执行命令行参数 |
立即数 | 源程序直接给到数字用立即数表示 | |
赋值 | ||
加载有效地址leaq | Leaq .LC0(%rip),%rdi | 将LC0的有效地址传给%rdi |
加法操作add | addl $1, -4(%rbp) | 循环变量的值加1 |
减法操作sub | subq $32, %rsp | 栈顶指针加32位,为main函数开辟栈帧 |
Jump指令 | cmpl $4, -20(%rbp) je .L2 | 当命令行参数为4时跳转到L2 |
movl $0, -4(%rbp) jmp .L3 | 初始化循环变量的值后跳转到L3 | |
cmpl $7, -4(%rbp) jle .L4 | 循环变量未达到上界时继续循环运行 |
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.4 本章小结
这一章总结了编译阶段的概念及具体操作指令,最后分析了编译结果hello.s文件。该文件包含一个汇编语言程序,汇编语言中的每条语句都以一种文本格式描述了一条低级机器语言指令。
汇编语言是直接面向处理器的程序设计语言,它所操作的对象不是具体的数据,而是寄存器或者存储器,因此执行速度要比其他语言快,但同时也使得编程更加复杂。汇编语言总体特性为机器相关性、高速度与高效率以及编写和调试的复杂性。
(以下格式自行编排,编辑时删除)
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编是指经过编译后,汇编器as将汇编语言翻译成机器语言,得到可重定位目标文件的过程。
作用:将高级语言转化为机器可直接识别执行的代码文件。即:将.s 汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序的格式,将结果保存在二进制目标文件.o中。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
(以下格式自行编排,编辑时删除)
应截图,展示汇编过程!
4.3 可重定位目标elf格式
如图4-2表示的是ELF头中的信息,开始的16字节的Magic序列描述了生成该文件的系统的字的大小和字节顺序,剩下的包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型(ELF64)、机器类型(X86-64)、节头部表的文件偏移以及节头部表中条目的大小和数量。
这里保存着各个节的信息,包括名称、大小、类型、地址等。由于是可重定位的,所以目前的地址都是0,用于重定位。根据下面的flags的提示信息可知,哪些是只读的,哪些是可写的。利用节头表的偏移和各个节的偏移量可以得到各个节在文件中的位置。
其中存放了在程序中定义和引用的函数和全局变量的信息,包括值,大小,种类,作用范围等等。例如main函数的值是0,大小为146字节,种类是FUNC也就是函数,GLOBAL表示全局变量
这里保存的是.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。表中包括偏移量、信息(前四个字节表示应该指向的符号后四个字节表示重定位的类型)、类型(告知链接器如何进行重定位)、符号值和符号名称以及加数。
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
通过对比,我们可以发现两者的结果几乎没有不同,只不过反汇编后多了机器代码的显示,同时立即数采用十六进制表示,而不是十进制。机器语言是用二进制数表示的语言,是电脑能够“读”的语言,机器指令由操作码和操作数构成。汇编语言是人们比较熟悉的词句直接表述CPU动作形成的语言,是最接近CPU运行原理的语言。每一条汇编语言操作码都可以用机器二进制数来表示,进而可以将所有的汇编语言和二进制机器语言建立一一映射的关系,因此可以将汇编语言转化为机器语言
不同:分支转移:反汇编中的分支转移是跳转到相对main的偏移地址,在hello.s中是跳转到段,如.L3
函数调用:在hello.s中函数调用call后面是函数的名称,而在反汇编的结果当中,call后面是下一条指令的地址,同时调用函数的重定位信息加到了.rel.text中,原因就在于,那些共享库中的函数,在链接阶段才会加入,此时没有确切的位置,需要重定位才可以
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章主要介绍了汇编过程,汇编概念及作用,并分析了可重定位目标文件格式.elf。同时,以hello.o文件为例,对其进行反汇编,通过与hello.s对比,详细展示了汇编的结果与细节。
(以下格式自行编排,编辑时删除)
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合为单一文件的过程,这个文件可以被加载(复制)到内存并执行。
作用:根据需要,链接可以执行于编译时,也可以执行于加载时,甚至执行于运行时。链接还使得分离编译(seperate compila)成为可能,便于把它分解为更小、更好管理的模块,并独立地修改和编译这些模块。
(以下格式自行编排,编辑时删除)
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
可执行目标文件格式类似于可重定位目标文件的格式。ELF头描述文件的总体格式。与可重定位目标文件hello.o的ELF格式文件hello_o.elf的ELF头相比,hello.elf文件的ELF头发生了一些改变:
(1)它包括了程序的入口点地址,即当程序运行时要执行的第一条指令的地址
(2).init节定义了一个小函数,叫做_init,程序的初始化代码会调用它。
(3).text、.rodata与.data节与可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时内存地址以外
(4)程序头大小与节头数量增加了
(5)ELF可重定位目标文件没有了.rel.text与.rel.data节,因为可执行目标文件中并不需要重定位信息,它是完全链接的(已被重定位)。
. rel.text :一个. text节中位置的列表,当链接器吧这个目标文件和其他文件组合时,需要修改这些位置
. rel.data :被模块引用或定义的所有全局变量的重定位信息
5.4 hello的虚拟地址空间
代码段开始于0x401090处。其中的节与每个节头表的声明一一对应
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
首先能够看到相比于hello.o的反汇编结果中main的地址是0,其他指令都是关于main的相对地址,hello的反汇编结果中每条指令和函数有了具体的虚拟地址除此之外,hello的反汇编代码中还多了调用的外部库中的很多节和函数的汇编代码
由以上结果,我们可以得出结论链接首先要进行符号解析,把每个符号引用正好和一个符号定义关联起来,这就是新增的部分节的作用。下一步要进行重定位,获取所需外部库的内容之后,把每个符号定义与一个内存位置关联起来,从而重定位这些节,使得符号引用指向这个内存位置。
重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
重定位节中的符号引用。在这一步中,链接器修改代码节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中成为重定位条目的数据结构。
重定位条目的数据结构定义
重定位算法
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
GOT表
对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行地址,需要添加重定位记录,等待动态链接器的处理。
链接器采用延迟绑定的策略避免运行时修改调用的代码段。使用数据结构:PLT+GOT实现函数的动态链接,其中GOT存放目标函数的地址,而PLT使用该地址跳转到目标位置。GOT与PLT条目之间的交叉映射使得动态链接(不对代码进行重定位)成为可能。
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章主要介绍了链接的概念及作用,结合实验中的hello可执行程序,阐述了hello的虚拟地址空间/重定位链接过程,并对动态链接做了一定介绍。
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是正在运行的程序的实例。从广义上讲,进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元。
在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
作用:(1)在现代计算机中,进程为用户提供了以下假象:程序好像是系统中当前运行的唯一程序一样,程序好像是独占的使用处理器和内存,处理器好像是无间断的执行指令,程序中的代码和数据好像是系统内存中唯一的对象;
(2)每次用户通过向shell输入一个可执行目标文件并运行时,shell 就会fork一个新的进程,并在其上下文中运行该可执行目标文件,同时,应用程序也能够自己创建新进程,并在其上下文中运行它们自己的代码,甚至其他应用程序;
(3)进程提供给应用程序两个关键抽象:一个独立的逻辑控制流/一个私有的地址空间。
6.2 简述壳Shell-bash的作用与处理流程
Linux实质上是一个操作系统内核,一般用户不能直接使用内核,而是通过外壳程序(shell)来与内核进行沟通。Shell(命令行解释器)是一个交互型的应用级程序,它代表用户运行其他程序,是用户和系统内核沟通的桥梁。用户可以通过shell向操作系统发出请求,操作系统选择执行命令。一般我们使用的shell是bash,在解释命令的时候,bash不会直接参与解释,而是创建新进程进行命令的解释,bash只用等待结果即可,这样可以保证bash进程的安全。
Shell的处理流程:
1.用户在命令行中键入命令
2.Shell通过parseline builtin函数将命令字符串分割填充到参数数组,传个main。
3.Shell检查命令是否是内置命令,若是则立即执行;若不是再检查是否是一个应用程序(这里的应用程序可以是Linux 本身的实用程序),然后shell在搜索路径里寻找这些应用程序,调用相应程序(execve和fork)为其分配子进程;如果输入的命令不是一个内部命令且在路径里没有找到这个可执行文件,将会显示一条错误信息。
6.3 Hello的fork进程创建过程
父进程通过调用fork函数创建一个新的运行的子进程。新创建的子进程几乎但不完全与父进程相同。Hello的fork进程创建过程可分为如下几步:
1.在终端输入命令行:./hello 1183710212 刘星宇 1
2.shell处理命令行,判断其是不是内置命令,shell判断其为当前目录文件下的可执行目标文件hello后,终端会调用fork函数创建一个新的子进程。
3.父进程和子进程是并发运行的独立进程,内核可以任意方式交替执行他们的逻辑控制流中的指令,所以这会导致我们不能简单的凭直觉判断指令执行的顺序。
4.父进程会默认等待子进程执行完之后回收子进程,但是也会有产生僵死进程的情况,父进程可以调用waitpid函数等待其子进程终止或停止。
(以下格式自行编排,编辑时删除)
6.4 Hello的execve过程
execve函数在当前进程的上下文加载并运行可执行目标文件Hello,且带列表argv和环境变量列表envp。只有当出现错误时,例如找不到Hello或filename,execve才会返回到调用程序。其中fork调用两次,返回两次不一样,execve调用一次且从不返回。在execve加载了Hello之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,该主函数有如下的原型:
int main(int argc , char **argv , char *envp);
结合虚拟内存和内存映射过程,可以更详细地说明exceve函数实际上是如何加载和执行程序Hello,需要以下几个步骤:
(1)删除已存在的用户区域;
(2)映射私有区域。为Hello的代码、数据、bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时复制的;
(3)映射共享区域。比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内;
(4)设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。内存映像如下:
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.5 Hello的进程执行
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
(以下格式自行编排,编辑时删除)
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
(以下格式自行编排,编辑时删除)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
1.逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 段标识符:段内偏移量。
2.线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式,分页机制中线性地址作为输入。
3.虚拟地址:就是线性地址。
4.物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
保护模式下:线性地址=段选择符+段内偏移地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
hello的线性地址到物理地址的转换需要查询页表。线性地址分为两个部分,虚拟页号VPN和虚拟页偏移量VPO。VPN用于在页表查询物理页号PPN,再与VPO结合获得物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
由一个页表大小4KB,一个PTE条目8B,共512个条目,使 用 9 位二进制索引,一共4个页表共使用 36 位二进制索引,所以VPN共36位,因为VA共 48位,所以VPO占12位;因为TLB共16组,所以 TLBI 需 4 位,因为 VPN共36 位,所以TLBT占32位。
变换地址时,CPU产生一个虚拟地址,MMU从TLB中取出相应的PTE。如果命中,则得到对应的物理地址。如果不命中,VPN会被分成4个部分。MMU向页表中查询,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出 PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成 PA,并且向TLB中添加条目。
(以下格式自行编排,编辑时删除)
7.5 三级Cache支持下的物理内存访问
由于有64组,所以组索引CI需要6 bit,块大小为64B故组内偏移CO需要6bit。因为PA共52 bit所以剩余部分CT共40 bit。
物理内存访问时,MMU发送PA给L1缓存,高速缓存根据CI找到组、CT找到地址,并根据标记位判断该地址是否已缓存数据。若命中,则根据偏移量CO找到值取出数据后返回。若不命中,则再一次查找缓存L2、L3。如果仍不命中,则要去主存中读取数据。
7.6 hello进程fork时的内存映射
mm_struct(内存描述符):描述了一个进程的整个虚拟内存空间。
vm_area_struct(区域结构描述符):描述了进程的虚拟内存空间的一个区间。
当用fork创建一个进程时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、vm_area_struct和页表的原样副本。然后将这两个两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
(1)删除当前的用户区域
(2)映射私有区域:为新程序的代码、数据、bss和栈区域创建新的区域结构。
(3)映射共享区域:hello程序与标准C库链接,这些对象动态链接到这个程序,然后再映射到用户虚拟地址空间中的共享区域内。
(4)设置程序计数器。execve做得最后一件事情是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
当指令引用一个虚拟地址,在MMU中查找页表 时发现与该地址相对应的物理地址不在内存中,此时即为缺页故障。
当出发缺页故障时,处理程序执行如下的步骤:
(1)判断虚拟地址是否合法
把该虚拟地址与每个区域结构中的vm_start和vm_end作比较。如果不合法,则发送段错误信号,终止这个进程。
(2)判断试图访问的内存是否合法
如果不合法则出发一个保护进程终止进程。
(3)判断地址和访问内存都合法后
选择牺牲一个页面,如果这个页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU就能正常翻译VA了。
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.9动态存储分配管理
动态内存管理每一个进程的堆,动态内存分配器将其看做不同大小的块来执行,块是分配或者空闲的。一个已分配块保持已分配的状态,直到它被释放。
分配器有两种类型:
1.显式分配器:要求应用显式地释放任何已分配的块。
2.隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,就释放这个块。也叫做垃圾收集器
在C语言中采用的是显式分配器。
其中显式分配有两种方法
1.隐式空闲链表:
将每一块的头部和尾部都设置一个4字节的指明块大小的和块是否分配的状态。其次还有要填充的块以满足对齐要求
2.显式空闲链表:
分配块的组成成分和原来一样,需要在空闲块的空闲处中添加空闲块的前继和后继指针,使首次适配的时间从块总数的线性时间减少到空闲块线性的时间。只不过空闲块要足够大以填充上两个指针。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
本章是系统程序的运行,阐明了CPU如何得到相应地址的数据。其中用到虚拟地址的翻译。在执行可执行文件时,是由shell创建子进程中运行。然后,还些许地解释了动态内存分配器的工作原理和数据结构。(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行,这就是Unix I/O接口。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访间一个I/O 设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.Linux shell 创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0) 、标准输出(描述符为1) 和标准错误(描述符为2) 。头文件< unistd.h> 定义了常量STDIN_FILENO 、STOOUT_FILENO 和STDERR_FILENO, 它们可用来代替显式的描述符值。
3.改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k, 初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek 操作,显式地设置文件的当前位置为K。
4.读写文件。一个读操作就是从文件复制n>0 个字节到内存,从当前文件位置k 开始,然后将k增加到k+n 。给定一个大小为m 字节的文件,当k~m 时执行读操作会触发一个称为end-of-file(EOF) 的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF 符号” 。类似地,写操作就是从内存复制n>0 个字节到一个文件,从当前文件位置k开始,然后更新k 。
5.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
Unix I/O函数:
1.进程是通过调用open 函数来打开一个已存在的文件或者创建一个新文件的:
int open(char *filename, int flags, mode_t mode);
open 函数将filename 转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags 参数指明了进程打算如何访问这个文件,mode 参数指定了新文件的访问权限位。
返回:若成功则为新文件描述符,若出错为-1。
2.进程通过调用close 函数关闭一个打开的文件。
int close(int fd);
返回:若成功则为0, 若出错则为-1。
3.应用程序是通过分别调用read 和write 函数来执行输入和输出的。
ssize_t read(int fd, void *buf, size_t n);
read 函数从描述符为fd 的当前文件位置复制最多n 个字节到内存位置buf 。返回值-1表示一个错误,而返回值0 表示EOF。否则,返回值表示的是实际传送的字节数量。
返回:若成功则为读的字节数,若EOF 则为0, 若出错为-1。
ssize_t write(int fd, const void *buf, size_t n);
write 函数从内存位置buf 复制至多n 个字节到描述符fd 的当前文件位置。图10-3 展示了一个程序使用read 和write 调用一次一个字节地从标准输入复制到标准输出。
返回:若成功则为写的字节数,若出错则为-1。
8.3 printf的实现分析
在形参列表里有这么一个token:…这个是可变形参的一种写法。当传递参数的个数不确定时,就可以用这种方式来表示。很显然,我们需要一种方法,来让函数体可以知道具体调用时参数的个数。先来看printf函数的内容:这句:va_list arg = (va_list)((char*)(&fmt) + 4);va_list的定义:typedef char va_list这说明它是一个字符指针。其中的: (char)(&fmt) + 4) 表示的是…中的第一个参数。 fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。fmt也是个变量,它的位置,是在栈上分配的,它也有地址。对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。接下来是vsprintf这个函数vsprintf返回的是一个长度,返回的是要打印出来的字符串的长度 ,write:写操作,把buf中的i个元素的值写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章主要讲述了linux的IO设备的管理方法,unixi/o接口及其函数,以及printf函数实现的分析和getchar函数的实现。特别地,在printf函数的底层实现的分析过程中,将原来只是简单的打印函数一层层地展开,清晰了底层的工作过程。(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1.编写程序。
2.源文件的预处理:将hello.c中头文件的内容都插入到文本中,将所有的宏都解释成具体的值。
3.编译:编译器将hello.i转化成汇编语言的ascii码文件hello.s。
4.汇编:汇编器将hello.s转化成二进制的机器代码,生成hello.o(可重定位目标文件)。
5.链接:对hello.o中引用的外部函数,全局变量等进行符号解析,重定位并且生成hello的可执行目标文件。
6.运行:在命令行中输入hello 1182910227 liufengzhe
7.创建子进程:通过fork创建。
8.执行:通过execxe调用加载器,建立虚拟内存映射,设置当前进程的上下文中的程序计数器,使之指向程序入口处。
9.访存:CPU上的MMU根据页表将CPU生成的虚拟地址翻译成物理地址,并用物理地址在主存的高速缓存中找相应的数据。
10.动态内存申请:printf调用malloc进行动态内存分配,在堆中申请所需的内存。
11.接收信号:中途接收ctrl+z挂起,ctrl+c终止。
12.程序返回后,内核向父进程发送SIGCHLD信号,终止的hello被父进程回收,内核将其上下文删除。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
Hello.i 预处理之后的ascii码程序
Hello.s 编译之后的汇编程序
Hello.o 汇编之后的可重定位目标程序(二进制程序)
Hello.elf hello.o的ELF格式
Helloexe.elf hello的ELF格式
Hello 可执行目标程序
Hello.s1 反汇编后输出的程序
Helloobj.txt hello可执行程序的反汇编代码
Temp.c 存放临时数据
(附件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分)