程序人生-Hello’s P2P

☆樱花仙子☆ 提交于 2020-01-22 07:57:14

程序人生-Hello’s P2P[HITICS-大作业]

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机学院
学   号 L180300401
班   级 1803004
学 生 沈玟锡    
指 导 教 师 史先俊

计算机科学与技术学院
2019年12月
摘 要
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

关键词:关键词1;关键词2;……;

(摘要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简介
Hello是每个程序员最先接触到的程序,大部分IDE是直接默认生成的,程序员需要做的只需要按两下鼠标,完成编译,就可以运行了。屏幕上出现hello。。。
但实际上的过程不能用轻松两个字来描述。Hello.c是用高级语言C编写的,我们要经过预处理,编译,汇编等过程,才能作为机器能读懂的机器代码储存在磁盘中。Hello现在的状态叫程序(Program),用户通过shell,调用一系列函数将hello运行在内存中。他是通过一种叫做进程(Process)的抽象来实现的。
Execve函数将hello加载至内存,顺着逻辑控制流,hello在硬件中驰骋,最终出现在屏幕上。最终程序终止,shell将子进程回收。尘归尘,土归土,一切复原。
版权声明:本文为CSDN博主「motoight」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
软件环境:VMware Workstation Ubuntu18.04.1 LTS
开发调试工具:vim,gcc,as,ld,edb,readelf,hexedit
1.3 中间结果
在这里插入图片描述
1.4 本章小结
本章主要简单介绍了 hello 的 p2p,020 过程,列出了本次实验信息:环境、中间结果。
(第1章0.5分)

第2章 预处里
2.1 预处理的概念与作用
概念:预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。
预处理器(cpp)根据以字符#开头的命令(宏定义(#define)、文件包含(#include)、条件编译(#ifdef)),修改原始的C程序。比如将头文件从库中提取出来,然后插入到程序文本中,得到一个完整的源程序,通常以.i作为文件扩展名。
注意:宏定义的用法有许多,许多函数可以考虑通过宏定义的方式来实现,效率更高。条件编译和文件包含也有许多用法,自己拓展。
在这里插入图片描述

2.2在Ubuntu下预处理的命令
命令:cpp hello.c > hello.i
gcc -E test.c -o test.i
在这里插入图片描述

2.3 Hello的预处理结果解析
Cpp命令处理后的hello.c文件得到hello.i文件,可以看到hello.i也是一个文本文件,用gedit打开之后看到原先一二十行的代码被扩展到3118行增加了许多内容。
用vim打开hello.i,发现main函数在文本的最末端
在这里插入图片描述
但是程序之前的#include<stdlib.h><unistd.h><stdio.h>不见了。
以stdlib.h为例,cpp是如何将头文件展开的呢?
先看看头文件的组成:
在这里插入图片描述
在这里插入图片描述
Cpp到默认的环境变量下寻找stdlib.h,打开/usr/include/stdlib.h,其中可能仍然会有#define语句,cpp对此进行递归展开,最终hello.i文件中只有对外部变量的声明,函数声明,没有宏定义。
2.4 本章小结
本章介绍了hello.c程序在编译之前需要做的准备工作,我们每次在写main函数之前在文件头添加的头文件和宏定义其实都需要经过比较复杂的处理。简单的一个hello程序,也是有很长的代码去实现的,只不过前人帮我们做好的准备工作。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
概念:指将预处理后的程序转化成特定的汇编程序的过程。编译器将我们用高级语言C写的程序按照一定的语法规则分方法翻译成汇编代码,汇编语言一般包括mov赋值指令,jg条件跳转,comjg条件转移,逻辑运算指令等等。在用编译器编译C程序的时候可以指定编译器的优化等级,不同的优化等级对程序等处理不同,优化等级越高,程序越符合机器的思维方式,能够最大化利用CPU,但是不利于人的理解。
编译器的输入时预处理后的.i文件,输出是.s文件。
值得注意的是我们在学习CSAPP中做各种实验用到了一种很强的的反汇编工具——objdump,他可以将可执行文件(二进制文件)反汇编,得到汇编代码,但是两者的格式还是不尽相同,注意区别。
编译一般分为三个步骤:
1、 词法分析:编译器将程序中的字符串分离出来,转化成内部标准的表示结构。
2、 语法分析:将先前词法分析达到的token生成一个抽象语法树。
3、 优化和目标代码生成;编译器的后端会负责对代码进行优化,比如公共子式提取,循环优化,删除无用代码,得到目标代码。
编译过程涉及到许多复杂的概念以及相关知识,有兴趣可以去提前看看编译原理。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令
在这里插入图片描述
应截图,展示编译过程!
3.3 Hello的编译结果解析
3.3.0 汇编指令
编译后的得到的hello.s文件也是文本文件,但是内容比hello.i小了很多。
查看hello.i,在main函数之前多了一段程序头:
在这里插入图片描述
注意汇编程序由三个不同的元素组成:
指示(Directives) 以点号开始,用来指示对编译器,连接器,调试器有用的结构信息。指示本身不是汇编指令。例如,.file 只是记录原始源文件名。.data表示数据段(section)的开始地址, 而 .text 表示实际程序代码的起始。.string 表示数据段中的字符串常量。 .globl main指明标签main是一个可以在其它模块的代码中被访问的全局符号 。至于其它的指示你可以忽略。
标签(Labels) 以冒号结尾,用来把标签名和标签出现的位置关联起来。例如,标签.LC0:表示紧接着的字符串的名称是 .LC0. 标签main:表示指令 pushq %rbp是main函数的第一个指令。按照惯例, 以点号开始的标签都是编译器生成的临时局部标签,其它标签则是用户可见的函数和全局变量名称。
指令(Instructions) 实际的汇编代码 (pushq %rbp), 一般都会缩进,以便和指示及标签区分开来。
在这里插入图片描述
3.3.1数据
hello.c文件中涉及到的C数据类型有整型,数组,字符串。下面具体分析。
一、 字符串
程序中的字符串分别是:
1)“Usage: Hello 学号 姓名!\n”,printf传入的格式化参数。在hello.s中声明如下图,注意到字符串使用UTF-8的格式编码的,一个汉字在UTF-8中占三个字节。
2)“Hello %s %s\n”,仍然是由printf函数传入的格式化参数,hello.s声明如下。
可以看到,两个字符串都被存放在.rodata段,作为全局变量。(和书上说的完全一样)
在这里插入图片描述
在这里插入图片描述
第一为常量:以下常量存放在只读数据域中,第二为全局变量:存放在.data段,hello中无全局变量
局部变量:存储在堆栈段,如当前代码中的i,argv,argc
如图中的-20(%rbp)中存的就是argc
在这里插入图片描述
红色的四角形内存的是i,黑色的四角形将i赋值为0,黄色的四角形调用atoi函数将字符串转为整型。绿色的四角形内对局部变量i累加。
在这里插入图片描述
判断argc与4是否相等
在这里插入图片描述
判断i是否小于等于7
在这里插入图片描述
对argv数组进行操作,数组在存储空间中申请的是连续的内存。

函数操作
在这里插入图片描述
终止程序
在这里插入图片描述
从键盘输入内读取字符串
3.4 本章小结
汇编语言是高级语言和机器语言的中介,一方面具有可读性,但是不像高级语言那样易懂,但是一方面反映了机器的一些特征,汇编语言一定程度上翻译了指令集体系的架构。
但是从高级语言到汇编语言的映射转化是不容易的。
Ccl(编译器)将hello.i转换成hello.s文件。
(以下格式自行编排,编辑时删除)
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:可以被CPU直截执行的编程语言
作用:生成二进制目标代码文件
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
在这里插入图片描述
应截图,展示汇编过程!
4.3 可重定位目标elf格式
ELF格式
ELF头
段头部表
.init
.text
.rodata
.data
.bss
.symtab
.debug
.line
.strtab
节头部表
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.4 Hello.o的结果解析
机器语言的构成:由机器指令集构成。
与汇编语言的映射关系:一一对应。
操作数的不一致
在这里插入图片描述
在这里插入图片描述
前者是hello.o的反汇编代码,后者是hello.s。能明显看出前者的操作数是16进制,而后者是10进制。
分支转移函数调用:
在这里插入图片描述
在这里插入图片描述
hello.o中,跳转位置不再是hello.s中的符号,而是地址。同时给出地址偏移量。
4.5 本章小结
本章通过汇编器(as)将hello.s编译成机器语言指令,再通过readelf查看可重定位目标程序文件,通过对elf文件结构的分析,获得相关数据的运行时地址,以及不同节的、条目的大小、偏移量等信息。同时,通过.s文本文件与由机器语言反汇编获得的汇编代码比较,易得.s文件中,通过注记符寻址和经反汇编后,重定位表示的地址信息差异。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接的概念与作用
通过链接器,将程序调用的外部函数(.o文件)与当前.o文件以某种方式合并,并得到./hello可执行目标文件的的过程成为链接。且该二进制文件可被加载到内存,并由系统执行。
链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。基于此特性的改进,以提高程序运行时的时间、空间利用效率。
链接是由叫做链接器的程序执行的。链接器使得分离编译成为可能。它将巨大的源文件分解成更小的模块,易于管理。我么可以通过独立地修改或编译这些模块,并重新链接应用,不必再重新编译其他文件。
5.2 在Ubuntu下链接的命令
在这里插入图片描述
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
ELF格式
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.4 hello的虚拟地址空间
.init段:
在这里插入图片描述
在这里插入图片描述

.text段:
在这里插入图片描述
在这里插入图片描述

.rodata段:
在这里插入图片描述
在这里插入图片描述

.data段:
在这里插入图片描述
在这里插入图片描述

5.5 链接的重定位过程分析
一、hello与hello.o的不同
1、hello中加入了许多除.text外的节,而hello.o中只有.text节
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、hello由于与动态链接库中的库函数链接上,故比hello.o多了许多函数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二、链接的过程
符号解析、重定位。
在这里插入图片描述
第一步,重定位节和符号定义。链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。
第二步,重定位节中的符号引用。链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
我找不到
5.7 Hello的动态链接分析
我找不到
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章具体分析了链接的作用——将各种代码和数据片段收集并组合成为一个单一文件,链接的过程——符号解析和重定位。深入理解了重定位的过程以及作用。了解了可执行目标文件与.o文件的反汇编代码的不同。
(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用
概念:一个执行中程序的实例。
作用:每次用户通过向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。
6.2 简述壳Shell-bash的作用与处理流程
作用:接收用户命令,然后调用相应的应用程序。
处理流程:
1.从脚本或终端或bash -c选项后的字符串中获取输入
2.将获取的输入分解成词元,此步骤会执行别名展开
3.将词解析为简单命令或复合命令
4.执行各种shell展开
5.执行必要的重定向,
6.执行命令
如果命令中包含“/”,则执行制定路径的程序;如果命令中不包含“/”,会检查是否是shell函数,shell内建命令,如果都不是,则在PATH环境变量中的路径进行查找。
7.等待命令结束获取命令执行状态
6.3 Hello的fork进程创建过程
子进程得到与父进程用户级虚拟地址空间相同但独立的一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本。当父进程调用fork时,子进程可以读写父进程中打开的任何文件。
6.4 Hello的execve过程
(以下格式自行编排,编辑时删除)
execve函数在当前进程的上下文中加载并运行一个新的程序。它会覆盖当前进程的地址空间,但并没有创建一个新进程。新的程序仍然有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。
execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到hello,execve才会返回到调用程序。
6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。
内核为每个进程维持一个上下文。它由一些对象的值组成,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策叫做调度。
处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结

本章介绍了Hello程序是如何在shell中运行的:以进程形式。介绍了shell-bash的作用和一般处理流程。介绍了进程中两个关键的抽象:逻辑控制流和私有空间。并且通过分析hello进程的创建,加载和运行进一步介绍了上下文切换的机制,通过运行hello程序,介绍了异常控制流的通知机制信号。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:访问指令给出的地址。
线性地址:段地址+偏移地址。
虚拟地址:程序访问存储器所使用的逻辑地址。
物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
实模式下: 逻辑地址CS:EA =物理地址CS*16+EA
保护模式下:以段描述符作为下标,到GDT/LDT表查表获得段地址,
段地址+偏移地址=线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
分页管理机制通过上述页目录表和页表实现32位线性地址到32位物理地址的转换。控制寄存器CR3的高20位作为页目录表所在物理页的页码。首先把线性地址的最高10位(即位22至位31)作为页目录表的索引,对应表项所包含的页码指定页表;然后,再把线性地址的中间10位(即位12至位21)作为所指定的页目录表中的页表项的索引,对应表项所包含的页码指定物理地址空间中的一页;最后,把所指定的物理页的页码作为高20位,把线性地址的低12位不加改变地作为32位物理地址的低12位。
7.4 TLB与四级页表支持下的VA到PA的变换
1、CPU产生一个虚拟地址,并把它传送给MMU。
2、MMU生成PTE地址,并从告诉缓存/主存请求得到它。
3、高速缓存/主存向MMU返回PTE。
4、MMU构造物理地址,并把它传送给高速缓存/主存。
5、高速缓存/主存返回所请求的数据字给处理器。

7.5 三级Cache支持下的物理内存访问
CPU产生一个虚拟地址并发送给MMU,如果命中则发送给一级Cache,如果没有命中则回页表中查找,然后一级一级重复上述步骤。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
1、删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2、映射私有区域。为新程序的代码、数据、bss和栈区创建新的区域结构
3、映射共享区域。
4、设置程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
1、搜索区域结构的链表,把虚拟地址和每个区域结构中的vm_start和vm_end作比较。如果这个指令是不合法的,那么缺页处理程序就触发一个段错误,从而终止这个进程。
2、判断进程是否有读、写或者执行这个区域内页面的权限。如果试图进行的访问是不合法的,那么缺页处理程序会触发一个保护异常,从而终止这个进程。
3、到这一步,内核知道了这个缺页是由于对合法的虚拟地址进行合法的操作造成的。此时,缺页处理程序选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令将再次发送虚拟地址到MMU。
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
动态内存分配器维护者一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的。
函数原型:void *malloc(size_t size)
成功:
返回已分配块的指针,块大小至少 size 字节,对齐方式依赖编译模式:8字节(32位模式),16字节(64位模式)
If size == 0, returns NULL
出错:返回 NULL (0) ,同时设置 errno
目标:最大化吞吐量,最大化内存利用率
策略:利用首次适配、下一次适配和最佳适配放置空闲块;分割空闲块;合并空闲块。
7.10本章小结
本章具体分析了虚拟内存管理。在地址层面,由逻辑地址开始,首先转为线性地址,再转为虚拟地址,最后转为物理地址,再通过TLB、三级cache等对上述过程进行加速形成了完整的计算机地址翻译。同时,大致了解了动态内存分配的方法与策略。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
一个 Liunx 文件就是一个 m 个字节的序列:B0,B1,…,Bm-1。所有的 I/O 设 备都被模型化为文件。
文件的类型有:
1.普通文件:包含任何数据,分两类
i.文本文件:只含有 ASCII 码或 Unicode 字符的文件
ii.二进制文件:所有其他文件
2.目录:包含一组链接的文件。每个链接都将一个文件名映射到一个文件
3.套接字:用于与另一个进程进行跨网络通信的文件
而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅的映射为文件的方式,允许 Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
1、打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2、Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,它们可用来代替显式的描述符值。
3、改变当前的文件位置。对于每个打开的文件内核保持着一个文件位置k,初始为0.这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显式地设置文件的当前位置为k
4、读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k>=m时执行读操作会出发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处没有明确的“EOF符号”。
5、关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

函数:
1、open函数可以打开或者创建一个文件,函数原型如下

#include <fcntl.h>
int open(const char *pathname,int oflag,…/mode_t mode/)
返回值:若成功返回文件描述符,若出错返回-1
pathname不用说指的是文件的路径名,注意使用’/'来间隔目录。

对于第二个参数oflag,以下是需要使用到fcntl.h中的常用参数:

O_RDONLY(只读)
O_WRONLY(只写)
O_RDWR(读写)
//以上这三个常量只能选择一个
O_APPEND(末尾追加)
O_CREAT(如果文件不存在则创建文件,这时需要使用第三个参数mode,指定该文件的访问权限位)
O_EXCL(如果文件存在,则出错,用来检测文件是否存在)
O_TRUNC(如果文件存在,而且为只读或只写打开,则将其长度截短为1)
对于open函数,当且仅当创建新文件时才使用的三个参数。由open返回的文件描述符一定是最小的未用的文件描述符,这可以用先关闭标准输出,然后打开文件,则文件的描述符为1。

2、creat函数

creat函数用来创建一个新文件,函数原型如下

#includ <fcntl.h>
int creat(const *pathname,mode_t mode)
返回值:若成功则返回为只写打开的文件描述符,否则返回-1
等效于open(pathname.O_WRONLY | O_CREAT | O_TRUNC,mode)
由于creat只能以写的方式打开文件,要想读取该文件需要creat,close,open。可以直接采用如下方式open(pathname,O_RDWR | O_CREAT |O_TRUNC,mode)。

3、close函数

关闭文件并释放其所占资源,函数原型如下

#include<unistd.h>
int close(int filedes)
返回值:成功返回0,否则返回-1
4、lseek函数

每个打开的文件在内核中都维护了一个“当前文件偏移量”,指的是从文件开始处计算的字节数。除非使用O_APPEND,否则偏移量一般都为0。

调用lseek显示地为一个打开的文件设置偏移量,函数原型如下

#include <unistd.h>
off_t lssek(int filedes, off_t offset, int whence)
返回值:若成功则返回新的文件偏移量,若出错则返回-1
//若whence为SEEK_SET,则将文件的偏移量设置为距文件开始处offset个字节。////
//若whence为SEEK_CUR,则将文件的偏移量设置为当前值加offset,offset可正可负。
//若whence为SEEK_END,,则将文件的偏移量设置为文件长度加offse,offset可正可负,
//注意文件偏移量可以大于当前文件长度,系统并不会为之间的空洞分配磁盘空间,只为新的数据分配磁盘空间。

5、read函数

调用read函数从打开的文件中读取数据,函数原型如下:

#include <unistd.h>
ssize_t read(int filedes,void *buf,size_t nbytes)
返回值:若成功返回读到的字节数,若已到文件结尾则返回0,若出错返回-1
6、write函数

调用write函数向打开的文件写数据,函数原型如下:

#include <unistd.h>
ssize_t write(int filedes,const void* buff,size_t nbytes)
返回值:若成功返回已写的字节数,若出错返回-1
其返回值通常与参数nbytes相同,否则表示出错。常见的出错原因包括:磁盘已经写满、超过了文件的最大长度限制。对于普通文件,写操作从文件的当前偏移量处开始。执行一次成功的写之后,文件的偏移量增加实际写的字节数。
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
前提:printf 和 vsprintf 代码是 windows 下的。
查看 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;
}

首先 arg 获得第二个不定长参数,即输出的时候格式化串对应的值。
查看 vsprintf 代码:

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’: //只处理%x一种情况 itoa(tmp, ((int)p_next_arg)); //将输入参数值转化为字符串保存在tmp
strcpy(p, tmp); //将tmp字符串复制到p处 p_next_arg += 4; //下一个参数值地址
p += strlen(tmp); //放下一个参数值的地址 break;
case ‘s’:
break;
default:
break;
}
}
return (p - buf); //返回最后生成的字符串的长度
}
则知道 vsprintf 程序按照格式 fmt 结合参数 args 生成格式化之后的字符串,并 返回字串的长度。 在 printf 中调用系统函数 write(buf,i)将长度为 i 的 buf 输出。write 函数如下
write: mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
在 write 函数中,将栈中参数放入寄存器,ecx 是字符个数,ebx 存放第一个字符地址,int INT_VECTOR_SYS_CALLA 代表通过系统调用 syscall,查看 syscall 的实现
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

syscall 将字符串中的字节“Hello L180300401 沈玟锡 2”从寄存器中通过总线复
制到显卡的显存中,显存中存储的是字符的 ASCII 码。
字符显示驱动子程序将通过 ASCII 码在字模库中找到点阵信息将点阵信息存 储到 vram 中。
显示芯片会按照一定的刷新频率逐行读取 vram,并通过信号线向液晶显示器传输每一个点(RGB 分量)。 于是我们的打印字符串 “Hello L180300401 沈玟锡”
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章大致说明了linux系统下的I/O管理,包括文件操作函数、printf函数以及getchar函数。
(第8章1分)
结论
hello所经历的过程:

1.hello被IO设备编写,以文件的方式储存在主存中。

2.hello.c被预处理hello.i文件

3.hello.i被编译为hello.s汇编文件

4.hello.s被汇编成可重定位目标文件hello.o

5.链接器将hello.o和外部文件链接成可执行文件hello

6.在shell输入命令后,通过exceve加载并运行hello

7.在一个时间片中,hello有自己的CPU资源,顺序执行逻辑控制流

8.hello的VA通过TLB和页表翻译为PA

9.三级cache 支持下的hello物理地址访问

10.hello在运行过程中会有异常和信号等

11.printf会调用malloc通过动态内存分配器申请堆中的内存

12.shell父进程回收hello子进程,内核删除为hello创建的所有数据结构
(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)
hello.c 源程序文件
hello.i 源代码经过预处理后得到的ascii码文件
hello.s .i文件经过编译得到的汇编代码文件
hello.o 汇编后的二进制目标文件
hello 链接产生的文件
参考文献
为完成本次大作业你翻阅的书籍与网站等
[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分)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!