HIT 2018 CS:APP Hello的一生 大作业

HIT 2018 CS:APP Hello的一生 大作业
计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术学院
学   号 1170300214
班   级 1703002
学 生 刘雨杉    
指 导 教 师 史先俊

计算机科学与技术学院
2018年12月
摘 要
虽然hello是一个简单的程序,可是却有着不简单的执行过程,所谓“麻雀虽小五脏俱全”,本报告即将探讨hello的整个生命历程。做为本课程的结束。
关键词:程序的生命周期;进程;P2P;020;

(摘要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.5 三级Cache支持下的物理内存访问 - 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: 源程序通过预编译、编译、汇编、链接成为执行程序的过程
O2O:从进程的创建到最后进程被回收的全过程。作为shall的子进程被调用,又被shall所回收
1.2 环境与工具
硬件环境:
X64CPU; 8GHz; 8GRAM; 1TB HD
软件环境:
Windows10 64位;VMware14.12; Ubuntu 16.04 LTS 64位
使用工具
codeblocks,objdump,gdb,edb,hexedit
1.3 中间结果

hello.i
预处理之后文本文件
hello.s
编译之后的汇编文件
hello.o
汇编之后的可重定位目标执行
hello
链接之后的可执行目标文件
helloelf.txt
查看hello的elf文件并输出为txt格式以辅助分析
1.4 本章小结
本章主要解释了该报告的主要内容。
下一章即将正式开始

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
对相关命令或语句的含义和功能作具体分析。
1.
预处理pre-treatment,是指在进行最后加工完善以前进行的准备过程,具体应用在不同的行业或领域,会有不同的解释。在一些程序设计语言中,预处理是preprocessing的翻译。
2.

ANSI C 标准规定可以在C源程序中加入一些 预处理命令,以改进程序设计环境,提高编程效率,这此预处理伪政府是由ANSI C 统一规定的,但是它不是C语言本身的组成部分,不能直接对它们进行编译。
3.

在计算机技术中,“指令”是由指令集架构定义的单个的CPU操作。在更广泛的意义上,“指令”可以是任何可执行程序的元素的表述,例如字节码。
4.

在传统的构架上,指令包括一个操作码opcode,它指定了要进行什么样的操作,例如“将存储器中的内容与寄存器中的内容相加”,和零个或者更多的操作数operand,它可能指定了参与操作的寄存器、内存地址或者立即数literal data。
5.

操作数可能还包括寻址方式,它确定了操作数的含义。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

不难看出,hello.i比hello.c多了许多东西,
具体的说就是指明了库存在的位置,而且,申明了可能用到的函数。
2.4 本章小结
本章主要介绍了预处理的作用与方法,并且对预处理后的文件进行了查看

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用

从高级语言向机器语言的过渡,由于高级语言是以人的方便开发的,所以机器无法直接执行,需要转化为与机器更接近的汇编语言才能执行。编译即从高级语言到汇编语言的过程。

3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析
3.3.1 数据
有全局变量sleepsecs

3.3.2 赋值
对应 int sleepsecs=2.5;(由于是int 型,故舍弃小数)

循环控制变量i 赋初始值 i=0;(局部变量的符号编译时被舍弃)

中的
3.3.3 类型转换(显示或隐式)
由于sleepsecs是int型的而2.5是float类型的,这就有一个隐式的类型转换,编译器将2.5隐式地转换成了2存入sleepsecs。
3.3.4 算术操作
对应循环控制变量每次加一 i++

中的

3.3.5 关系操作
对应循环边界条件 i<10

中的
对应 if(argc!=3)

中的

3.3.6 数组/指针/结构操作
对应printf 中对指针进行操作

中的

3.3.7 控制转移
对应if(argc!=3)的边界

中的

对应for(i=0;i<10;i++)的边界

中的

3.3.8 函数操作
对应printf(“Usage: Hello 学号 姓名!\n”);

中的

对应printf(“Hello %s %s\n”,argv[1],argv[2]);

中的

对应sleep(sleepsecs);

中的

3.4 本章小结
本章主要进行了汇编语言如何实现具体函数功能的讨论
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
在汇编语言中,用助记符(Memoni)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码。这样用符号代替机器语言的二进制码,就把机器语言变成了汇编语言。于是汇编语言亦称为符号语言。用汇编语言编写的程序,机器不能直接识别,要由一种程序将汇编语言翻译成机器语言,这种起翻译作用的程序叫汇编程序,汇编程序是系统软件中语言处理的系统软件。
4.2 在Ubuntu下汇编的命令
gcc –c hello.s –o hello.o

4.3 可重定位目标elf格式

以上为节头

以上重定位信息

4.4 Hello.o的结果解析

与上一章对比,无太多的区别,区别在于,1.o中没有出现mov push sub 等注记符,而是全部替换为十六进制的数字。
2.在分支跳转中,原来以l2,l3,l4表示的地址,现在以具体的地址表示
3.在函数调用中,原来只需call 函数名,现在要call 具体的地址
4.5 本章小结
本节中,迈出了从.s到.o的一步,离最后的执行又近了一步。此外还分析了elf文件的内容。

(以下格式自行编排,编辑时删除)
(第4章1分)

第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的格式

5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

中的物理地址对应。
其中
PHDR:程序头表

INTERP:程序执行前需要调用的解释器

LOAD:程序目标代码和常量信息

DYNAMIC:动态链接器所使用的信息

NOTE::辅助信息

GNU_EH_FRAME:保存异常信息

GNU_STACK:使用系统栈所需要的权限信息

GNU_RELRO:保存在重定位之后只读信息的位置

右侧为5.3的名称,左侧为对应的地址
5.5 链接的重定位过程分析
如图,汇编hello

汇编hello.o

可看出,hello的汇编,远远长于hello.o的汇编
hello中导入了诸如puts、printf、getchar、sleep等在hello程序中使用的函数,而这些函数的在hello.o中并未出现
此外在hello中调用函数都是使用call+<main+偏移量的做法>,而在hello.o是直接使用call+<函数名> 的方法来直接调用的。
5.6 hello的执行流程

_dl_init

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
_GLOBAL_OFFSET_TABLE_发生改变

之前

之后

(以下格式自行编排,编辑时删除)
5.8 本章小结
本章对hello进行了链接,hello从代码到了可执行的东西。
初步了解了链接时所需的库
(以下格式自行编排,编辑时删除)
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
6.2 简述壳Shell-bash的作用与处理流程
shell 是一个交互性命令解释器。shell 独立于操作系统,这种设计让用户可
以灵活选择适合自己的 shell。shell 让你在命令行键入命令,经过 shell 解释后
传送给操作系统(内核)执行。
shell 是一个命令处理器(command processor)——是一个读入并解释你
输入的命令的程序。除了是一个命令中断器以外,shell 还是一个程序设计语言。
你可以编写 shell 可以解释的程序(被称为源程序),这些源程序可以包含 shell
程序设计命令等等。shell 除了解释命令以外,还有其他工作,它也可以配置和编
程。
shell 拥有自己的语言允许用户编写程序并以一种复杂方式运行。shell 编
程语言具有许多常用的编程语言的特征,例如:循环和控制结构等。用户可以生
成像其他应用程序一样复杂的 shell 程序
shell 功能:
查找命令的位置并且执行相关联的程序;
为 shell 变量赋新值;
执行命令替代;
处理 I/O 重定向和管道功能;
提供一个解释性的编程语言界面,包括 tests、branches 和 loops 等语句
流程
(1).读取输入的命令行。
(2).解析引用并分割命令行为各个单词,各单词称为 token。其中重定向所在的
token 会被保存下来,直到扩展步骤(5)结束后才进行相关处理,如进行扩展、截
断文件等。
shell 中有 3 种引用方式:反斜线引用、单引号引用和双引号引用。
◇ 反斜线转义:使得元字符变为普通的字面字符。但这只能对反斜线后一个字符
进行转义。
◇ 单引号引用:单引号内的所有字符全部变为字面符号符号。但注意:单引号内
不能再使用单引号,即使使用了反斜线转义也不允许。
◇ 双引号引用:使双引号内所有字符变为字面符号,但""、"$"、"`"(反引号)
除外,如果开启了"!"引用历史命令时,则感叹号也除外。
解析引用后,于是就可以将命令行进行单词分割,分割后的每一部分都称为一个
token。分隔时,不仅分割单个命令,还分割命令列表,所以分隔符包括:空格、
tab、分号、管道符号、&、&&、||、重定向符号、圆括号等。
如果分割时发现了管道符号,或者是命令列表等组合了多个命令的情况,则每个
命令都的 token 都相互独立。
(3).检查命令行结构。主要检查是否有命令列表、是否有 shell 编程结构的命令,
如 if 判断命令、循环结构的 for/while/select/until,这些命令属于保留关键字,
需要特殊处理。
(4).对第一个 token 进行别名扩展。如果检查出它是别名,则扩展后回到(2)再次
进行 token 分解过程。如果检查出它是函数,则执行函数体中的复合命令。如果
它既是别名,又是函数(即命令别名和函数同名称的情况),则优先执行别名。在
概念上,别名的临时性最强,优先级最高。
(5).进行各种扩展。扩展顺序为:大括号扩展;波浪号扩展;参数、变量和命令
替换、算术扩展(如果系统支持,此步还进行进程替换);单词拆分;文件名扩展

6.3 Hello的fork进程创建过程

父子进程共享代码段,但是分别拥有自己的数据段和堆栈段
Shell通过调用fork 函数创建一个新的运行的子进程。也就是Hello程序,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,这就意味着,当父进程调用fork时,子进程可以读写父进程中打开的任何文件。
(以下格式自行编排,编辑时删除)
6.4 Hello的execve过程
execve函数原型
int execve(const char *filename, char *const argv[],
char *const envp[]);

filename:包含准备载入当前进程空间的新程序的路径名。既可以是绝对路径,又可以是相对路径。
argv[]:指定了传给新进程的命令行参数,该数组对应于c语言main函数的argv参数数组,格式也相同,argv[0]对应命令名,通常情况下该值与filename中的basename(就是绝对路径的最后一个)相同。
envp[]:最后一个参数envp指定了新程序的环境列表。参数envp对应于新程序的environ数组。
还要注意的是:由于是将调用进程取而代之,因此对execve的调用将永远不能返回,也无需检查它的返回值,因为该值始终为-1,实际上,一旦返回就表明了错误,通常会有error值来判断,下面是几个常用的:

  • EACCES:参数pathname没有指向一个常规文件,未对该文件赋于可执行权限,或者因为目录没有x权限。
  • ENOTNT:pathname指定的文件并不存在。
  • ETXTBSY:存一个或者多个进程已经以写入的方式打开pathname所指定的文件。
    当fork之后,子进程调用execve函数(传入命令行参数)在当前进程的上下文中加载并运行一个新程序即hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。

6.5 Hello的进程执行

箭头代表事件为调用sleep
在内核和前端之前切换的动作被称为上下文切换
6.6 hello的异常与信号处理
1.中断:SIGSTP:挂起程序
2.终止:SIGINT:终止程序

Ctrl +c: 停止
Ctrl +z: 挂起
fg:发送SIGCONT信号继续执行停止的进程
kill -9 pid:发送SIGKILL信号给指定的pid杀死进程
ps:查看进程号
Pstree:查看进程树

6.7本章小结

本章进行了异常处理的讲解,并且介绍了fork()和shall,此外讲解了一些进程切换与调度的问题
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
物理地址(physical address)
用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。就是hello在运行时虚拟内存地址对应的物理地址。
虚拟地址(即线性地址)
这是对整个内存的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;
之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。就是hello里面的虚拟内存地址。
逻辑地址(logical address)
Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。就是hello.o里面的相对偏移地址。

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,如图:

最后两位涉及权限检查。 索引号,或者直接理解成数组下标——那它总要对应一个数组吧,它又是什么索引呢?这是“段描述符(segment descriptor)”,段描述符具体地址描述了一个段。这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,由8个字节组成,如下图:

图示比较复杂,可以利用一个数据结构来定义它,不过,在此只关心一样,就是Base字段,它描述了一个段的开始位置的线性地址。 Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。 GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。 再看这张图比起来要直观些:

首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],

1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。

2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。

3、把Base + offset,就是要转换的线性地址了。 还是挺简单的,对于软件来讲,原则上就需要把硬件转换所需的信息准备好,就可以让硬件来完成这个转换了。
7.3 Hello的线性地址到物理地址的变换-页式管理
PU的页式内存管理单元,负责把一个线性地址,转换为物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。 另一类“页”,我们称之为物理页,或者是页框(frame)、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。 这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。文字描述太累,看图直观一些:

如上图,

  分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。

  每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。

  每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位) 依据以下步骤进行转换:

(1) 从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);

(2) 根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。

(3) 根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;

(4) 将页的起始地址与线性地址中最后12位相加,得到最终我们想要的
7.4 TLB与四级页表支持下的VA到PA的变换

7.5 三级Cache支持下的物理内存访问

得到物理地址之后,先将物理地址拆分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。(如图)

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

7.8 缺页故障与缺页中断处理

7.9动态存储分配管理

首先分配器分为两种
显式分配器:要求应用显式地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。

隐式空闲链表如图

显示空闲链表如图

分离的空闲链表

合并时有以下四种情况

7.10本章小结
本章介绍了储存器的地址空间,讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念,还有进程fork和execve时的内存映射的内容。
渐渐的我们不再仅仅只考虑程序本身,而开始考虑一些底层的实现。

(以下格式自行编排,编辑时删除)
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
在分时并行多任务系统中,为了合理利用系统设备,达到一定的目标,不允许进程自行决定设备的使用,而是由系统按一定原则统一分配、管理。进程要进行IO操作时,需向操作系统提出IO请求,然后由操作系统根据系统当前的设备使用状况,按照一定的策略,决定对改进程的设备分配。设备的应用领域不同,其物理特性各异,但某些设备之间具有共性,为了简化对设备的管理,可对设备分类,或对同类设备采用相同的管理策略,比如Linux主要将外部IO设备分为字符设备和块设备(又被称为主设备),而同类设备又可能同时存在多个,故而要定位具体设备还需提供“次设备号”。

根据主设备号+次设备号可以去相应的设备开关表中定位具体的设备驱动程序。 内核和设备驱动程序的接口是块设备开关表和字符设备开关表。每一种设备类型在表中占用一个表项,每个表项含有若干数据项,其中有一项为该类设备驱动程序入口地址,在系统调用时引导核心转向适当的驱动程序接口。

Linux系统为各类设备分别配置不同的驱动程序,在用户程序中通过文件操作方式使用设备,如open\close\read\write等,由文件系统根据用户程序指令转向调用具体的设备驱动程序。

对设备特殊文件的系统调用,根据文件类型转入块设备开关表或字符设备开关表进行打开、关闭设备的操作,字符设备特殊文件的系统调用read、write转向字符设备开关表中指示的设备驱动程序,而对普通文件或目录文件的read\write系统调用则通过高速缓冲模块(缓冲区)转向设备驱动模块中的strategy过程。Linux中关于不同设备种类的管理架构如下

8.2 简述Unix IO接口及其函数
Unix IO接口分为5类
阻塞式IO
阻塞式IO模型是最一般的IO模型。在这种模型下,IO函数调用(read & write等等)都会在操作完成或者发生中断以后才会返回。如果指定的操作数据没有就绪,或者操作需要的外部条件(比如缓冲区)尚未符合要求,操作会一直阻塞。
非阻塞式IO
相对于阻塞式IO模型,非阻塞式IO的特点就是:当所请求的IO操作暂无法如期完成时,不要把本进程投入睡眠时,而是直接返回一个错误(EWOULDBLOCK)。这样做避免了程序长时间被挂起。但是由于在这种模式下,程序需要不断去轮询,会耗费大量的CPU时间。
IO复用
IO复用是指在一个IO请求内,等待多个可能的IO对象可用(在unix下由文件描述符来标记)。虽然IO复用还是会导致阻塞,但是当IO操作对象较多时,就能够避免产生很大一部分的阻塞时间。在Unix下,我们可以用select或poll函数实现IO复用。
信号驱动式IO
在信号驱动式IO模型下,我们可以让内核在描述符就绪时发送SIGIO信号通知我们。使用这种模型,我们需要首先开启套接字的信号驱动式IO功能,并通过sigaction系统调用来安装一个信号处理函数。
异步IO
异步IO的特点是:让内核启动某个IO操作,并让内核在这个操作完成后通知我们。在这期间,调用异步IO操作的进程不会被阻塞。
异步IO与信号驱动式IO的区别在于:信号驱动式IO是由内核通知我们何时可以启动IO操作,而异步IO模型是由内核通知我们操作何时完成。
五种模型的相互比较:
从上面的描述可以看出,前四种模型的第二阶段是一样的:数据从内核复制到调用者的缓冲区期间,进程阻塞于selectfrom等调用。
根据POSIX对同步IO的定义:IO操作导致进程阻塞直到操作完成。我们可以得出结论:前四种模型都是同步IO模型。
剩下的special one——异步IO模型,由于不导致请求进程阻塞,则属于POSIX定义的异步IO。
接口函数
open函数 函数原型 int open(char * path,int oflag,…) 返回值是一个文件描述符 path顾名思义就是文件名 oflage文件是打开方式 第三个形参应用于创建文件时使用 /创建文件其实还有一个create函数使用 以及openat由于还未使用过这个函数和open的差异 所以不在此处累赘/ open函数 使用if 判断的时候 注意小细节
  read函数 函数原型 ssize_t read(int fd , void* buf , size_t nbytes) 返回值是文件读取字节数 在好几种情况下会出现返回值不等于文件读取字节数 也就是第三个参数nbytes的情况 第二个形参buf读取到buf的内存 文件偏移量(current file offset)受改变
  write函数 函数原型 ssize_t write(int fd , const void* buf, size_t nbytes) 返回值是文件写入字节数 fd是文件描述符 将buf内容写入nbytes个字节到文件 但这里需要注意默认情况是需要在系统队列中等待写入(打开方式不同也会不同) //以上三个出错都返回-1
  lseek函数off_t lseek(int fd, off_t offset , int whence) 返回值成功函数返回新的文件偏移量 失败-1 fd文件描述符 off_t是有符号的整数 whence其实是和off_t配套使用的 SEEK_SET文件开始处 SEEK_CUR当前值的相对位置 SEEK_END文件长度±
  close函数原型 int close(int fd) 返回值 成功返回0 失败—1 关闭文件描述符

8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
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; 
} 

在形参列表里有这么一个token:... 
这个是可变形参的一种写法。 
当传递参数的个数不确定时,就可以用这种方式来表示。 
很显然,我们需要一种方法,来让函数体可以知道具体调用时参数的个数。 

先来看printf函数的内容: 

va_list arg = (va_list)((char*)(&fmt) + 4);

va_list的定义: 
typedef char *va_list 
这说明它是一个字符指针。 
其中的: (char*)(&fmt) + 4) 表示的是...中的第一个参数。 

C语言中,参数压栈的方向是从右往左。
也就是说,当调用printf函数的适合,先是最右边的参数入栈。
fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。
fmt也是个变量,它的位置,是在栈上分配的,它也有地址。
对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。
分析 i = vsprintf(buf, fmt, arg);
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); 

}
返回的是要打印出来的字符串的长度
分析 write(buf, i);

write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL

sys_call:
call save

 push dword [p_proc_ready] 

 sti 

 push ecx 
 push ebx 
 call [sys_call_table + eax * 4] 
 add esp, 4 * 3 

 mov [esi + EAXREG - P_STACKBASE], eax 

 cli 

 ret 

总的来说
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
正所谓独木不成林,强如计算机也需要其他设备的支持,这便引出了IO端口的问题。
本章就IO端口做了一些浅显的介绍。
(第8章1分)
结论
从某个时刻,hello.c诞生
预处理,hello.c预处理后成为hello.i,离机器近了一步
编译,hello.i编译后成为hello.s
汇编,hello.s 汇编成为hello.o
链接,hello.o失散在各方的兄弟(库)终于得以重聚,距离登台表演近一步之遥
运行,终于到了这一天,响应shall的号召,作为一个子进程闪亮登场
异常,想必是hello最开心的时刻吧,你与它总总互动好不惬意,你好奇的乱按键盘,ctrl+c,ctrl+z,好奇的执行各种指令,ps,pstree ,fg,kill……。
IO接口,内存动态分配,你好奇的问hello是如何做到的,它说,全靠了IO接口以及内存动态分配的函数,它才能展现自己
回收,天下无不散的宴席,到了说再见的时刻了,shall对其进行了回收,就好像它从来没有出现过一样,内存里不再有它存在的痕迹,只有硬盘的某个小角落,一个不起眼的文件夹证明过它的存在。最后的最后它笑着说:“我已经不是一个为了被回收而存在的程序,因为遇见了你,我变得有意义……”
一切回归平静

感悟:古人云“天下难事,必作于易 天下大事 必作于细”hello虽然简单可是一旦细究其实现发现还是有很多欠缺的地方,完成此次大作业我查阅了一些资料,加深了对计算机系统的理解,有了更全面的认识。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
hello.c
hello的源代码
hello.i
预处理后的hello.c的代码
hello.s
hello.i编译后的代码
hello.o
hello.s汇编后的代码
hello
hello.o链接后的代码
helloelf.txt
hello的反汇编文件

(附件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分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值