ICS2019大作业

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学   号 1180300313
班   级 1803003
学 生 王成龙
指 导 教 师 史先俊

计算机科学与技术学院
2019年12月
摘 要
本文是在Linux系统下,hello.c文件从.c文件最终经过预处理、编译、汇编、链接生成可执行文件的过程,然后启动程序生成进程到最后被回收。以hello的一生为例,描述计算机系统的软硬件是如何互相协调工作的。
关键词:计算机系统;编译;进程;P2P;020

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

目 录

第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
2.1 预处理的概念与作用 - 5 -
2.2在UBUNTU下预处理的命令 - 5 -
2.3 HELLO的预处理结果解析 - 5 -
2.4 本章小结 - 6 -
介绍了HELLO.C的预处理阶段,得到了HELLO.I程序,理解了预处理器读取系统头文件中内容并把它插入程序文本中的过程。 - 6 -
第3章 编译 - 7 -
3.1 编译的概念与作用 - 7 -
3.2 在UBUNTU下编译的命令 - 7 -
3.3 HELLO的编译结果解析 - 7 -
3.4 本章小结 - 10 -
第4章 汇编 - 11 -
4.1 汇编的概念与作用 - 11 -
4.2 在UBUNTU下汇编的命令 - 11 -
4.3 可重定位目标ELF格式 - 11 -
4.4 HELLO.O的结果解析 - 13 -
4.5 本章小结 - 13 -
第5章 链接 - 14 -
5.1 链接的概念与作用 - 14 -
链接是指在程序的各模块之间传递参数和控制命令,并把它们组成一个可执行的整体的过程。 - 14 -
5.2 在UBUNTU下链接的命令 - 14 -
5.3 可执行目标文件HELLO的格式 - 14 -
5.4 HELLO的虚拟地址空间 - 15 -
5.5 链接的重定位过程分析 - 16 -
5.6 HELLO的执行流程 - 16 -
5.7 HELLO的动态链接分析 - 16 -
5.8 本章小结 - 16 -
第6章 HELLO进程管理 - 17 -
6.1 进程的概念与作用 - 17 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 17 -
6.3 HELLO的FORK进程创建过程 - 17 -
6.4 HELLO的EXECVE过程 - 17 -
6.5 HELLO的进程执行 - 18 -
在HELLO执行的某些时刻,比如SLEEP函数,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度。当内核调度了一个新的进程运行后,它就抢占HELLO进程,并且使用上下文切换机制来将控制转移到新的进程。 - 18 -
HELLO进程初始运行在用户模式中,直到HELLO进程中的SLEEP系统调用,它显式地请求让HELLO进程休眠,内核可以决定执行上下文切换,进入到内核模式。当定时器2.5后中断时,内核就能判断当前HELLO休眠运行了足够长的时间,切换回用户模式。下面通过图6-3详细说明。 - 18 -
6.6 HELLO的异常与信号处理 - 18 -
6.7本章小结 - 18 -
第7章 HELLO的存储管理 - 19 -
7.1 HELLO的存储器地址空间 - 19 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 19 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 19 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 19 -
7.5 三级CACHE支持下的物理内存访问 - 19 -
7.6 HELLO进程FORK时的内存映射 - 19 -
7.7 HELLO进程EXECVE时的内存映射 - 19 -
7.8 缺页故障与缺页中断处理 - 19 -
7.9动态存储分配管理 - 19 -
7.10本章小结 - 20 -
第8章 HELLO的IO管理 - 21 -
8.1 LINUX的IO设备管理方法 - 21 -
8.2 简述UNIX IO接口及其函数 - 21 -
8.3 PRINTF的实现分析 - 21 -
8.4 GETCHAR的实现分析 - 21 -
8.5本章小结 - 21 -
结论 - 22 -
附件 - 23 -
参考文献 - 24 -

第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P: hello.c经过预处理(cpp)、编译(ccl)、汇编(as)、链接(ld)成为可执行目标程序hello,在启动命令后通过fork产生子进程
020: shell通过execve()加载并执行hello,进入程序入口载入物理内存,进入main函数执行代码,CPU为运行的hello分配时间片,程序结束后,shell父进程通过wait或waitpid回收hello进程,内核删除相关数据结构。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:Intel i5-8400 CPU;16G RAM;NVIDIA GeForce 1060ti
软件环境:Windows 10 64位;VMware Workstation 15 Pro;Ubuntu 16.04
开发与调试工具:gcc gdb edb codeblocks
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c:源代码
hello.i :预处理后的文本文件
hello.s :hello.i编译后的汇编文件
hello.o :hello.s汇编后的可重定位目标文件
hello_objdump :hello的反汇编代码
hello:链接后的可执行文件
1.4 本章小结
对于hello的P2P与020做了介绍,列出了本次实验所用的环境、工具,以及生成的文件
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理器根据以#开头的命令,修改原始的C程序。以得到另一个C程序,通常以.i作为文件拓展名
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析
475个字节增加到66079个字节,用gedit打开hello.i,发现在main函数在文件的最后部分。

在main函数之前,预处理器读取头文件stdio.h 、stdlib.h 和unistd.h中的内容并依次展开,若展开的头文件中也有头文件,则预处理器对此继续展开,最终.i中没有#define。
2.4 本章小结
介绍了hello.c的预处理阶段,得到了hello.i程序,理解了预处理器读取系统头文件中内容并把它插入程序文本中的过程。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译器首先检查代码是否有错误,确定代码接下来要做的工作,检查无误后,编译器将hello.i翻译成hello.s。该程序包含函数main的定义,以.s这种文本格式描述了低级机器语言指令,为不同高级语言的不同编译器提供了通用的输出语言。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析
3.3.1开头部分

开头部分为hello.c各节
3.3.2 数据
整型变量:

  1. int argc:函数传入的第一个int型参数,存储在%edi中
  2. int i:局部变量,通常保存在寄存器或是栈中。由图可知i的数据类型占用4字节栈空间。

常量:在hello.s中一些常量以立即数的形式出现。
字符串:argv[1]和argv[2]都声明在.rodata只读数据段中

3.3.3 赋值

使用movl对局部变量i赋初值0
3.3.4 类型转换

使用atoi函数将argv[3]字符串转换为整型数
3.3.5 算术操作
i++编译为

3.3.6 关系操作
i<8编译为

argc!=4编译为

3.3.7 数组/指针/结构操作
argv[] argv[0]指向程序路径和名称 argv[1]和argv[2]则表示两个字符串,指针占8字节 所以如图
通过%rax和%rax+8得到两个字符串
3.3.8 控制转移
1.if(argc!=4)
argc!=4时跳转到.L2,否则继续执行

for(i=0;i<8;i++) 通过cmpl $7, -4(%rbp)进行比较,i<8跳转.L4

3.3.9 函数操作
1.main函数:
参数传递: argc和argv[],分别用%rdi和%rsi存储。
函数调用:被系统启动函数调用。
函数返回:设置%eax为0并且返回(return 0)

2.printf函数:
参数传递:第一个为字符串参数首地址;第二个为argv[1]和argc[2]的地址
函数调用:for循环中被调用

3.exit函数:
参数传递:参数为1
函数调用:argc!=4时被调用

4.sleep函数:
参数传递:atoi(argv[3])
函数调用:for循环下被调用

5.getchar
函数调用:在main中被调用

3.4 本章小结
编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。分析过程中发现有语法错误,给出提示信息。无错误信息则从源语言编写的源程序产生目标程序。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编器将.s文件翻译成机器语言指令并生成可重定位目标程序,保存于.o文件中。hello.o文件是包含程序指令编码的二进制文件
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

  1. ELF头描述生成该文件的系统一些信息与帮助链接器的信息。

  2. 节头部表描述不同节的位置和大小,节的名称、类型、地址和偏移量等。每个节都有一个固定大小的条目。
    3.当汇编对位置未知的目标引用时会生成一个重定位条目,重定位条目放在.rel.text中,已初始化数据的重定位条目放在.rel.data中。
    两种最基本的重定位类型:
    R_X86_64_PC32 :重定位一个使用32位PC相对地址的引用。
    R_X86_64_32 :重定位一个使用32位PC绝对地址的引用。

  3. 符号表:.symtab存放在程序中定义和引用的函数和全局变量的信息。

4.4 Hello.o的结果解析

  1. 操作数:hello.s是十进制,hello.o反汇编代码是十六进制。
  2. 分支转移:hello.s中是.L2和.L3等段名称,而hello.o中是相对偏移的地址。
  3. 函数调用:hello.s中,call指令后是函数名称,而hello.o中call指令后是函数的相对偏移地址。在.rela.text节中为其添加了重定位条目。
  4. 全局变量的访问:在hello.s文件中,对于.rodata的访问,是$.LC0,而在反汇编代码中是$0x0,因为访问也需要重定位,机器语言时需要将操作数全部置为0,并且添加重定位条目。

4.5 本章小结
通过汇编操作将汇编语言转化为机器语言,.o文件作为可重定位目标文件为链接做准备。hello.s与hello.o进行对比,了解了汇编语言是如何转变成机器语言的,以及重定向是如何实现的。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
链接是指在程序的各模块之间传递参数和控制命令,并把它们组成一个可执行的整体的过程。
作用:当程序调用函数库中的一个函数,这个函数必须通过链接器将这个文件合并到o程序中,结果得到可执行文件hello,可以被加载到内存中并由系统执行。
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格式
地址栏中可看到各段的起始地址,大小一栏中看到各段大小

5.4 hello的虚拟地址空间

两种方法查看到的数据完全相同,这是ELF整个文件的基本信息。
5.3中在第17项dynamic开始的时候,地址就发生了一个比较大的变化,这些节中保存了共享库中的信息。在edb中需要单独进行查看

5.5 链接的重定位过程分析
hello与hello.o主要有以下的不同:
1.链接增加新的函数:在hello中链接加入了在hello.c中用到的函数
2. hello中增加了.init和.plt节和一些节中定义的函数。
3.函数调用:hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。
4.地址访问:hello.o中的相对偏移地址变成了hello中的虚拟内存地址。而hello.o文件中对于.rodata的访问是$0x0,它们的地址也是在运行时确定的,因此访问也需要重定位,在汇编成机器语言时,将操作数全部置为0,并且添加重定位条目。
链接就是链接器ld将各个目标文件组装在一起,把.o文件中的各个函数段按照一定规则累积在一起并生成可执行文件。

5.6 hello的执行流程
0x4004c0 init;
0x4004f0 puts@plt;
0x400500 printf@plt;
0x400510 getchar@plt;
0x400520 atoi@plt;
0x400530 exit@plt;
0x400540 slpeep@plt;
0x400550 _start;
0x400582 main;
0x400610 __libc_csu_init;
0x400680 __libc_csu_fini;
0x400684 _fini;
5.7 Hello的动态链接分析
dl_init前

dl_init后

5.8 本章小结
这一章介绍了链接重定位的过程,以hello.o链接静态库形成可执行文件hello。使用edb调试工具和objdump分析hello的虚拟地址空间,hello的可重定位的过程和对动态链接的分析。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
狭义定义:进程是正在运行的程序的实例
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的作用:一个独立的逻辑控制流,好像我们的程序独占地使用处理器;一个私有的地址空间,好像我们的程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
1.作用
shell解释用户输入的命令并把它们送到内核。它允许用户编写shell命令组成的程序,用shell语言编写的程序与其他应用程序在效果上相同
2.shell的处理流程
①shell检查命令是否是内部命令,若不是再检查是否是应用程序。
②shell在搜索路径里寻找这些应用程序,如果命令不是一个内部命令并且在路径里没有这个可执行文件,将会提示错误信息。
③如果成功找到命令,该内部命令或应用程序将转换为系统调用并传给Linux内核。
6.3 Hello的fork进程创建过程
在命令行输入命令,shell首先检查该命令是否为内置命令,若不是内置命令,shell将调用fork函数创建一个新的子进程hello,hello得到与父进程用户级虚拟空间相同的一份副本,这意味着父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大区别在于有不同的PID。
6.4 Hello的execve过程
作用:在当前进程的上下文中加载并运行一个新的程序。execve直接在当前的进程中删除当前进程中现有的虚拟内存段,为Hello的代码、数据、bss和栈区域创建新的私有区域,代码段与数据段初始化为可执行文件中的内容,最后设置程序计数器(PC),使之指向代码区域的入口点。
6.5 Hello的进程执行
在hello执行的某些时刻,当内核调度了一个新的进程运行后,它就抢占hello进程,并且使用上下文切换机制来将控制转移到新的进程。
hello进程一开始运行于用户模式,当sleep函数显式地请求让hello进程休眠,内核执行上下文切换,hello进入到内核模式。定时器中断后,内核就能将hello切换回用户模式。

6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
hello执行过程中会出现的异常:
中断:信号SIGTSTP,默认行为是停止直到下一个SIGCONT
终止:信号SIGINT,默认行为是终止
1.运行后什么都不按 程序正常执行 循环输出8次 再按回车结束

  1. 运行过程中按Ctrl+C。父进程收到SIGINT信号,终止并回收hello进程

3.运行时乱按,并不会影响进程的执行,当按到回车键时,getchar读入回车符,后面的字符串当作shell的命令行输入。
4. 按下Ctrl+Z后,父进程收到SIGTSTP信号,将hello进程挂起,再运行ps命令列出当前系统中的进程

  1. 按下Ctrl+Z后运行jobs命令,列出当前shell中已启动的任务状态。

6.按下Ctrl+Z后运行pstree命令,以树状图显示进程关系。

  1. fg命令将进程调到前台

8.kill发送信号给一个进程或多个进程。通过kill -9 3688杀死pid为3688的进程。

6.7本章小结
本章了解了hello进程的创建、加载和终止等过程,对hello执行过程中产生信号和信号的处理过程有了更多的认识。

(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:包含在机器语言中用来指定一个操作数或一条指令的地址。每一个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。(hello.o里的相对偏移地址)。
线性地址:逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。(hello中的虚拟内存地址)
虚拟地址:一个带虚拟内存的系统中,CPU从一个有N=2^n个地址空间中生成虚拟地址。
物理地址:用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。地址翻译会将hello的一个虚拟地址转化为物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部份组成,段标识符、段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节

索引号,是“段描述符(segment descriptor)”,段描述符具体地址描述了一个段。这样,很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,由8个字节组成

代码段和数据段描述符中各个位的含义如下所示:

Base字段描述了一个段的开始位置的线性地址。一些全局的段描述符,就放在GDT中,一些局部的段描述符就放在LDT中。由段选择符中的T1字段决定的,T1=0为GDT,T1=1为LDT。 GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中

将段描述符表中的段地址(base字段)加上段偏移量,即为线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
将程序的逻辑地址空间划分为固定大小的页,而物理内存划分为同样大小的页框。程序加载时,可将任意一页放入内存中任意一个页框。通过CPU的硬件支持来实现逻辑地址和物理地址之间的映射。在页式存储管理方式中地址结构由两部构成,前一部分是页号,后一部分为页内地址w(位移量)

CPU芯片上有内存管理单元(MMU),其功能是将虚拟地址翻译成物理地址。N位的虚拟地址包含两个部分,p位的VPO和n-p位的VPN。MMU利用VPN来选择适当的PTE。接下来在对应的PTE中获得PPN,将PPN与VPO串联起来得到相应的物理地址。

7.4 TLB与四级页表支持下的VA到PA的变换
CPU产生虚拟地址VA,将其传送给MMU,MMU使用前36位VPN作为TLBT匹配TLB
①如果命中,40位PPN +12位VPO=52位 PA
②如果未命中,MMU 向页表中查询,CR3确定第一级页表的起始地址,9位的VPN1确定在第一级页表中的偏移量,查询出 PTE,如果在物理内存中且符合,则确定第二级页表的起始地址,直至在第四级页表中查询到 PPN,与VPO组合成PA,并且向TLB 中添加条目。

7.5 三级Cache支持下的物理内存访问
知道虚拟地址对应的物理地址后要对其进行访问。CPU通过访问L1、L2、L3三级cache来访问物理地址。MMU将物理地址发送给L1缓存,从物理地址中得出CT、CI、CO。根据缓存组索引找到L1缓存中对应的组,若缓存标记为1,根据缓存偏移直接从缓存中读取数据并返回。如果缓存不命中,需要从L2、L3中去读取,如果在三级中都不存在,需要到主存中读取。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的区域结构和页表的副本,将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
exceve函数加载和执行程序Hello
1.删除已存在的用户区域。
2.映射私有区域,为Hello的代码、数据、bss和栈区域创建新的区域结构
3.映射共享区域到用户虚拟地址空间中的共享区域内。
4.在当前进程的上下文中设置程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
DRAM缓存不命中称为缺页,对于一个访问虚拟内存的指令来说,如果发生了缺页现象,CPU就会触发一个缺页异常。缺页异常会调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,例如图中的VP4,如果VP4已经被更改,那就先将他存回到磁盘中。
找到了要存储的页后,内核会从磁盘中将需要访问的内存,并且将PTE中的信息更新,这样就成功的将一个物理地址缓存在了页表中。当异常处理返回的时候,CPU会重新执行访问虚拟内存的操作,这个时候就可以正常访问
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合,来维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
隐式空闲链表:
主要是由三部分组成:头部、有效载荷、填充(可选);
头部:是由块大小+标志位(a已分配/f空闲);有效载荷:实际的数据

简单的放置策略:
1> 首次适配:从头搜索,遇到第一个合适的块就停止;
2> 下次适配:从头搜索,遇到下一个合适的块停止;
3> 最佳适配:全部搜索,选择合适的块停止。

显式空闲链表
显示结构在空闲块中增加了8个字节,分别保存当前空闲块的前驱空闲块的地址和后继空闲块的地址。也就是说,显式的结构比隐式结构多维护了一个链表,就是空闲块的链表。所以只需要在空闲块中维护的链表检索就可以了,降低了在malloc的复杂度。
关于空闲块的维护方式一共有两种,一种是后进先出的方式,另一种是按照地址的方式。按照地址维护很好理解,与隐式的结构大致相同。后进先出的方式的思想是,当一个分配块被free之后,将这个块放到链表的最开头,这样在malloc的时候会首先看一下最后被free的块是否符合要求。这样的好处是释放一个块的时候比较高效,直接放在头部就可以。

7.10本章小结
本章介绍了虚拟地址、物理地址、线性地址、逻辑地址的概念;进程fork和execve时的内存映射的内容。描述了系统如何应对缺页异常,最后描述了malloc的内存分配管理机制。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
Unix IO接口:
打开文件,内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操作。
Linux shell创建的每个进程开始时都有三个打开的文件:stdin(文件描述符0)、stdout(描述符为1),stderr(描述符为2)。
文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。
读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;
写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k
关闭文件。当应用完成对文件的访问后,通知内核关闭这个文件。内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中
Unix IO函数:

  1. open()函数
    功能描述:用于打开或创建文件,可以指定各种参数。
    函数原型:int open(const char *pathname,int flags,int perms)
  2. close()函数
    功能描述:用于关闭一个被打开的文件
    函数原型:int close(int fd)
  3. read()函数
    功能描述: 从文件读取数据。
    函数原型:ssize_t read(int fd, void *buf, size_t count);
  4. write()函数
    功能描述: 向文件写入数据。
    函数原型:ssize_t write(int fd, void *buf, size_t count);
    8.3 printf的实现分析
    定义va_list型变量args,指向参数的指针。首先调用va_start函数初始化args指针,通过对va_arg返回可变的参数,然后va_end结束可变参数的获取。
    vsprintf函数的作用是以fmt为格式字符串,根据args中的参数,向printfbuf输出格式化后的字符串。然后调用write函数,在屏幕上输出长度为i的在printfbuf处的内容。它将栈中参数存入寄存器,然后执行 INT_VECTOR_SYS_CALL,代表通过系统调用syscall,syscall将寄存器中存储的字符串通过总线复制到显卡的现存中,字符显示驱动子程序通过ASCII码在字模库中找到点阵信息并将其存储到vram中。接下来显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点。此时在屏幕上显示一个字符串。

8.4 getchar的实现分析
当程序调用getchar函数,程序等待用户的按键。用户按键时,键盘接口会得到一个键盘扫描码,同时产生一个中断请求,进行上下文切换,运行键盘中断子程序,该程序将按键扫描码转换为ASCII码,保存到键盘缓存区。直到用户按回车为止,然后将用户输入的字符显示到屏幕。

8.5本章小结
这一章介绍了Unix系统的I/O接口及其函数,以及通过阅读printf函数和getchar函数的代码了解如何通过Unix I/O实现功能。
(第8章1分)
结论
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1.通过文本编辑器用高级语言写下hello.c
2. 预处理器将hello.c文件经过初步的修改变成hello.i
3. 编译器将hello.i文件处理为汇编代码并保存在hello.s
4. 汇编器将hello.s文件处理为可重定位的目标程序hello.o
5. 链接器将hello.o与外部文件进行链接得到可执行程序hello
6. 在shell中输入运行hello文件的命令,内核分配好运行程序所需要的堆、用户栈、虚拟内存等一系列信息。
7. 在键盘上给一个相应的信号,hello程序就可被外部操控
8. hello需要访问磁盘中的信息时,CPU利用MMU将虚拟地址翻译成物理地址,从而可以被访问
9. hello在运行过程中会有异常和信号等
10.hello结束时,shell通过父进程回收hello子进程,内核删除为hello创建的所有数据结构
通过这次大作业,我对于计算机系统的理解更加深入,加深了对书本中知识的理解,程序的正常运行需要许多部分密不可分的工作与配合。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c :hello源代码
hello.i :预处理后的文本文件
hello.s :hello.i编译后的汇编文件
hello.o :hello.s汇编后的可重定位目标文件
hello :链接后的可执行文件
(附件0分,缺失 -1分)

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 深入理解计算机系统 Randal E.Bryant David R.O’Hallaron 机械工业出版社
[6] 博客园 内存管理
https://www.cnblogs.com/xavierlee/p/6400230.html
[3] Linux内核中的printf实现
https://blog.csdn.net/u012158332/article/details/78675427
(参考文献0分,缺失 -1分)

你可能感兴趣的:(ICS2019大作业)