CS大作业论文

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学   号 1180300315
班   级 1836101
学 生 周牧云   
指 导 教 师 刘宏伟

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

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

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)

摘要:本文通过运用在计算机系统课程上学习的知识,分析研究hello程序在Linux下的P2P和020过程,利用gcc等工具,演示Linux框架下程序的声明周期。
关键词:预处理、编译、汇编、链接、进程、存储

目 录

第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.c。预处理器根据以字符#开始的命令修改hello.c得到另一个C程序hello.i。编译器将hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编器将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。再经过链接器的处理,就得到了可执行目标文件hello。
在shell中键入启动命令后,shell会使用fork为其创建并生成子进程,这样hello从Program转换为Process,然后shell为其execve映射虚拟内存。进入程序入口后,程序开始加载物理内存,然后进入main函数执行目标代码。 CPU为正在运行的hello分配时间片以执行逻辑控制流。程序完成后,shell父进程负责恢复hello进程,内核删除相关的数据结构。

1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件:
Intel® Core™ i7-8750H CPU; 8.00GB RAM
软件:
Windows10 64位;Vmware 14;Ubuntu 16.04 LTS 64位;
开发工具:
Visual Studio 2019 64位;CodeBlocks;vi/vim/gpedit+gcc; objdump
1.3 中间结果
hello.i (hello.c预处理之后的程序文本)
hello.s (hello.i编译成汇编语言之后的程序文本)
hello.o (hello.s生成的二进制文件)
hello (hello.o链接之后的二进制文件)
1.4 本章小结
本章总结了所使用的开发环境与工具,实验的中间产物。

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。
结果是得到另一个C程序,通常是以.i作为文件扩展名。
作用:
C语言的预处理主要有三个方面的内容:宏定义、文件包含和条件编译。
1.宏定义:将宏名替换为文本(字符串或代码)。
2.文件包含:预处理程序将查找指定的被包含文件,并将其复制插入到#include命令出现的位置上。比如hello.c中第1行的#include命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插人程序文本中。
3.条件编译:有些语句希望在条件满足时才编译,预处理过程中根据条件决定需要编译的代码。

2.2在Ubuntu下预处理的命令
在这里插入图片描述

2.3 Hello的预处理结果解析
在这里插入图片描述

预处理后,头文件被引入,注释被去除,宏都被替换,添加了行号,编译指令被保留。

2.4 本章小结
本章阐述了预处理的概念,作用和命令,并对hello.c的预处理结果进行了解析。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
概念:编译是编译器(ccl)将文本文件hello.i翻译成文本文件hello.s的过程, hello.s包含一个汇编语言程序。
作用:把代码翻译成汇编语言。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令
在这里插入图片描述

3.3 Hello的编译结果解析
3.3.1 数据
1.int sleepsecs:
sleepsecs是一个全局变量,存在.data段中。
在这里插入图片描述

  1. int i:
    i是一个局部变量。由i的赋值语句可以看出i存放的位置是-4(%rbp).
    在这里插入图片描述

  2. int argc:
    作为第一个参数被压栈pushq %rbp传入main函数
    在这里插入图片描述

4.字符串:
程序中有两个字符串:“Usage: Hello 学号 姓名!\n"和"Hello %s %s\n”
都存在只读数据段中。
在这里插入图片描述

5.数组:
程序中有一个数组argv[],argv[[1]]和argv[[2]]作为for循环中printf的参数。
由取argv[[1]]和argv[[2]]值的汇编语句可知argv的首地址是-32(%rbp)。
在这里插入图片描述

6.立即数:
其他数据以立即数的形式出现。

3.3.2 赋值
1.int sleepsecs=2.5
全局变量直接在.data中被声明为long类型值为2(因为int类型忽略掉了小数部分)
在这里插入图片描述

2.i=0
在这里插入图片描述
通过这句mov指令将0赋给了i

3.3.3 类型转换
将float类型的2.5赋值给int类型的sleepsecs时发生了强制类型转换,即将浮点数2.5转换为int类型。遵从向零舍入的原则,2.5被向下取整为2。

3.3.4 算数操作

  1. i++:
    在这里插入图片描述
  2. leaq .LC1(%rip),%rdi
    在这里插入图片描述
    使用了加载有效地址指令leaq计算LC1的段地址%rip+.LC1并传递给%rdi

3.3.5 关系操作

  1. argc != 3:
    在这里插入图片描述
    条件判断if(argc!=3),第一排是读取主函数参数argc,第三排是比较3与argc的关系,相等跳到L2,否则执行L2后面的代码。
  2. i<10:
    在这里插入图片描述
    条件判断语句(i<10),9与-4(%rbp)比较,小于或等于跳转到L4.

3.3.6 函数操作
1.main函数
2.printf函数
printf(“Usage: Hello 学号 姓名!\n”):
在这里插入图片描述
printf(“Hello %s %s\n”,argv[1],argv[2]):
在这里插入图片描述
3.exit函数
exit(1):
在这里插入图片描述
4.sleep函数
Sleep(sleepsecs):
在这里插入图片描述
5.getchar函数
getchar():
在这里插入图片描述

3.4 本章小结
本章阐述了编译的概念,作用和命令。并结合hello.i的编译结果,就C语言中的数据与操作如何被翻译成汇编语言进行了总结和分析。

(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
作用:将汇编代码转变为机器指令,生成目标文件。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
在这里插入图片描述
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
在这里插入图片描述
ELF header:总体描述ELF信息的段
在这里插入图片描述

Section Headers: 描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息
在这里插入图片描述

.rela.text:一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置
在这里插入图片描述

rela.eh_frame: eh_frame 节的重定位信息。

在这里插入图片描述
.symtab符号表
在这里插入图片描述

4.4 Hello.o的结果解析
hello.o反汇编后与hello.s的区别并不大,主要区别如下:
1) hello.o多了机器码,这是机器能直接识别的机器指令
2) 分支跳转语句:call后面从L1……变成了具体的地址
3) 函数调用:call后面的函数调用从.s文件中直接call函数的名字变成了call下一条语句的地址。这是因为许多函数需要从链接的库中调用,现在还不知道他们的具体地址,而此时call将他们的相对地址全都设置为0,在.rela.text中为他们设置重定位条目,等待静态链接的进一步确定。
在这里插入图片描述

在这里插入图片描述
4.5 本章小结
本章简述了汇编器的相关内容。通过readelf、objdump等方式比较了hello.s与hello.o的区别,了解了汇编指令转为机器指令发生的变化。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以加载(复制)到内存并且执行。
作用:使得分离编译成为可能,不用将大型的应用程序组织成为一个巨大的源文件,而是可以将它分为更小,更容易管理的模块,可以独立的修改和编译这些模块。当我们改变其中的一个时,只要重新编译它,再链接上去应用就可以了。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令】
在这里插入图片描述

5.3 可执行目标文件hello的格式
在这里插入图片描述
ELF Header:
在这里插入图片描述

Section Headers:
在这里插入图片描述
在这里插入图片描述

5.4 hello的虚拟地址空间
Hello起始地址为0x401000,结束地址为0x4011bc.
在这里插入图片描述

5.5 链接的重定位过程分析
1.hello中增加了许多节和被调用的函数。
2.对rodata的引用:在hello.o的反汇编文件中对printf参数字符串的引用使用全0替代。在hello中则使用确定地址,这是因为链接后全局变量的地址能够确定。
3.hello.o中main 的地址从0开始,hello中main的地址不再是0.库函数的代码都链接到了程序中。
在这里插入图片描述
在这里插入图片描述

5.6 hello的执行流程
载入:
_dl_start
_dl_init
开始执行
_start
_libc_start_main
_init
执行main:
_main
_printf
_exit
_sleep
_getcha
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
退出:
exit

5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章学习了链接的相关内容,分析了hello的elf文件,分析了hello 的虚拟地址空间、重定位过程、执行流程、动态链接过程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是一个执行中程序的实例。是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
作用:进程的概念为我们提供这样一种假象,就好像我们的程序是系统中当前运行的唯一程序一样,我们的程序好像是独占地使用处理器和内存,处理器好像是无间断地一条接一条地执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell 是一个交互型的应用级程序,它代表用户运行其他程序。
处理流程:
1)当用户提交了一个命令后,Shell首先判断它是否为内置命令,如果是就通过Shell内部的解释器将其解释为系统功能调用并转交给内核执行。
2)若是外部命令或应用程序就试图在硬盘中查找该命令并将其调入内存,再将其解释为系统功能调用并转交给内核执行。
6.3 Hello的fork进程创建过程
在bash中输入 ./hello 1* * 并敲击回车后,bash解析此条命令,发现./hello不是bash内置命令,于是在当前目录尝试寻找并执行hello文件。此时bash使用fork函数创建一个子进程(这个子进程得到与父进程用户级虚拟地址空间相同但是独立的一份副本),并更改这个子进程的进程组编号。并准备在这个子进程执行execve。
6.4 Hello的execve过程
在新创建的子进程中,execve函数加载并运行hello,且带参数列表argv和环境变量envp。在execve加载了hello之后,它调用_start,_start设置栈,并将控制传递给新程序的主函数。
6.5 Hello的进程执行
Linux 系统中的每个程序都运行在一个进程上下文中,有自己的虚拟地址空间。当shell 运行一个程序时,父shell 进程生成一个子进程,它是父进程的一个复制。子进程通过execve 系统调用启动加载器。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk), 新的代码和数据段袚初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用程序的main 函数。

6.6 hello的异常与信号处理
在这里插入图片描述

1.正常退出
在这里插入图片描述
执行完毕后进程被回收,再ps并看不到hello的身影
2.ctrl+z:
在这里插入图片描述
在这里插入图片描述

3.ctrl+c:
在这里插入图片描述
在这里插入图片描述
6.7本章小结
本章阐述了进程的定义和作用,shell的作用和处理流程,执行hello时的fork和execve过程。分析了hello的进程执行和异常与信号处理过程。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:在有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。是hello.o中的相对偏移地址。
线性地址:线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
虚拟地址:程序访问存储器所使用的逻辑地址称为虚拟地址。是hello里的虚拟内存地址。
物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址。是hello里虚拟内存地址对应的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本组成方式是段号+段内偏移地址。
在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。
首先给定一个完整的逻辑地址[段选择符:段内偏移地址],
1.看段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT中的段,还是LDT中的段,再根据指定的相应的寄存器,得到其地址和大小,我们就有了一个数组了。
2.拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,这样就有了Base,即基地址就知道了。
3.把基地址Base+Offset,就是要转换的下一个阶段的地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。
为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。32位的线性地址被分成3个部分:最高10位 Directory 页目录表偏移量,中间10位 Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位),项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。
每个活动的任务,必须要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器。页表可以提前分配好,也可以在用到的时候再分配。

7.4 TLB与四级页表支持下的VA到PA的变换
每次cpu产生一个虚拟地址,MMU需要查询一个PTE,如果运气不好,需要从内存中取得,这需要花费很多时间,通过TLB(翻译后备缓冲器)能够消除这些开销。TLB是一个小的,虚拟寻址的缓存,在MMU里,其每一行都保存着一个单个PTE组成的块,TLB通常具有高度相联度
用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中提取出来的。
如果是32位系统,我们有一个32位地址空间,4KB的页面和一个4字节的PTE,我们总需要一个4MB的页表驻留在内存中,而对于64位系统,我们甚至需要8PB的空间来存放页表,这显然是不现实的。用来压缩页表的常见方式就是使用层次结构的页表。
如果是二级页表,第一级页表的每个PTE负责一个4MB的块,每个块由1024个连续的页面组成。二级页表每一个PTE负责一个4KB的虚拟地址页面。这样的好处在于,如果一级页表中有一个PTE是空,那么二级页表就不会存在,这样会有巨大的潜在节约,因为4GB的地址空间大部分都是未分配的。
现在的64位计算机采用4级页表,36位的VPN被封为4个9位的片,每个片被用作一个页面的偏移,CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1PET的偏移量,这个PTE包含L2页表的基地址,VPN2提供一个到L2PTE的偏移量,以此类推。

7.5 三级Cache支持下的物理内存访问
先访问一级缓存,不命中时访问二级缓存,再不命中访问三级缓存,再不命中访问主存,如果主存缺页则访问硬盘

7.6 hello进程fork时的内存映射
1.创建当前进程的内存描述符mm_struct、区域结构描述符vm_area_struct和页表的原样副本
2.两个进程的每个页面都标记为只读页面
3.两个进程的每个vm_area_struct都标记为私有

7.7 hello进程execve时的内存映射
1.删除已存在的用户区域
2.创建新的私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构
3.创建新的共享区域
4.设置PC指向代码的入口

7.8 缺页故障与缺页中断处理
当虚拟地址在MMU中查找页表时发现对应的物理内存不在主存中,需要从需要操作系统将其调入主存就称为缺页故障。
1.请求调页: 当进程调用malloc()之类的函数调用时,并未实际上分配物理内存,而是仅仅分配了一段线性地址空间,在实际访问该页框时才实际去分配物理页框,这样可以节省物理内存的开销,还有一种情况是在内存回收时,该物理页面的内容被写到了磁盘上,被系统回收了,这时候需要再分配页框,并且读取其保存的内容。
2.写时复制:当fork()一个进程时,子进程并未完整的复制父进程的地址空间,而是共享相关的资源,父进程的页表被设为只读的,当子进程进行写操作时,会触发缺页异常,从而为子进程分配页框。
3.地址范围外的错误:内核访问无效地址,用户态进程访问无效地址等。
4.内核访问非连续性地址:用于内核的高端内存映射,高端内存映射仅仅修改了主内核页表的内容,当进程访问内核态时需要将该部分的页表内容复制到自己的进程页表里面。
缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果 这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺 页处理程序返回时,CPU 重新启动引起缺页的指令,这条指令再次发送 VA 到 MMU,这次 MMU 就能正常翻译 VA 了。

7.9动态存储分配管理
基本方法:维护一个虚拟内存区域“堆”,将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的,需要时选择一个合适的内存块进行分配。
1 记录空闲块,可以选择隐式空闲链表,显示空闲链表,分离的空闲链表和按块大小排序建立平衡树
2 放置策略,可以选择首次适配,下一次适配,最佳适配
3 合并策略,可以选择立即合并,延迟合并
4 需要考虑分割空闲块的时机,对内部碎片的忍耐阈值.

7.10本章小结
本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理, 以intel Core7在指定环境下介绍了VA 到PA 的变换、物理内存访问,还介绍了hello 进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行,这就是Unix I/O接口。

8.2 简述Unix IO接口及其函数
I/O接口操作
1.打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.Linux shell创建的每个进程开始时都有三个打开的文件:标准输入、标准输出和标准错误。
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.关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
函数:
1.int open(char *filename, int flags, mode_t mode)
进程通过调用open函数来打开一个已存在的文件或者创建一个新文件。open函数将filename转换为一个文件描述符,而且返回描述符数字。flags参数指明了进程打算如何访问这个文件。mode参数指定了新文件的访问权限位。
2.int close(int fd)
进程通过调用close函数关闭一个打开的文件。
3.ssize_t read(int fd, void *buf, size_t n)
应用程序通过调用read函数来执行输入。read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,返回值0表示EOF。否则返回值表示的是实际传送的字节数量。
4.ssize_t write(int fd, const void *buf, size_t n)
应用程序通过调用write函数来执行输出。write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。

8.3 printf的实现分析
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;

}
其中,va_list是一个字符指针,通过va_list_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': 
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); 

}
这个函数最后返回的是打印字符串的长度,也就是说这句话得到了字符串的长度i在下一句传给write函数。
write函数如下:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
先给寄存器传了几个参数,然后通过系统调用sys_call
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
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。

https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数调用read函数,将整个缓冲区都读到buf里,并将缓冲区的长度赋值给n。返回时返回buf的第一个元素,除非n<0。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章简述了Linux的I/O设备管理机制,Unix I/O接口及函数,并简要分析了printf函数和getchar函数的实现。
(第8章1分)
结论
1.程序员通过编辑器创建hello.c。
2.预处理器根据以字符#开始的命令修改hello.c得到另一个C程序hello.i。
3.编译器将hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
4.汇编器将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
5.再经过链接器的处理,就得到了可执行目标文件hello。
6.用户键入命令,shell会fork一个子进程。
7.在这个子进程中调用execve加载hello。
8.然后程序会跳转到_start地址,最终调用hello的main函数。
9.hello通过调用sleep getchar exit等系统函数运行程序。
10.进程结束后会被shell回收。

(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
(附件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、付费专栏及课程。

余额充值