2021深入理解计算机系统大作业

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业         计算机           

学     号        1190202103        

班   级         1903002          

学       生         孟祥瑞           

指 导 教 师          郑贵滨           

计算机科学与技术学院

20216

摘  要

本文主要叙述了hello程序从产生到结束的整个生命周期,详细介绍了预处理、编译、汇编、链接、进程管理、存储管理和I/O管理七个过程,并通过调试工具去探索各过程中计算机系统内部的实现机制与原理,从而深入地理解计算机系统。

关键词:预处理编译,汇编,链接,进程,shell,内存,I/O;     

目  录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

5.3 可执行目标文件hello的格式

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

6.2 简述壳Shell-bash的作用与处理流程

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.2 Intel逻辑地址到线性地址的变换-段式管理

7.3 Hello的线性地址到物理地址的变换-页式管理

7.4 TLB与四级页表支持下的VA到PA的变换

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献

第1章 概述

1.1 Hello简介

用户使用高级语言编写源代码文件hello.c,使用cpp对hello.c进行预处理得到文本文件hello.i,再通过编译器对hello.i进行编译得到汇编文件hello.s,然后通过汇编器对hello.s进行汇编得到可重定位目标文件hello.o,最后由链接器将hello.o与相应库函数进行链接得到可执行文件hello.out,使用“./hello 姓名 学号”指令启动该文件,此时操作系统会使用fork为其创建子进程,然后调用execuve函数映射虚拟内存,程序便开始执行,进入main函数执行程序代码,会在屏幕上输出你的姓名和学号,程序执行完后,父进程会将hello进程回收,hello生命即结束。

1.2 环境与工具

硬件环境: X64 CPU;2GHz;2G RAM;256GHD Disk 以上;

软件环境: Windows7 64位以上;Vmware 16.01;Ubuntu 16.04 LTS 64位;

开发与调试工具: GCC;GDB。

1.3 中间结果

hello.c:源代码文件

hello.i:预处理后的文本文件

hello.s:编译后的汇编代码文件

hello.o:汇编后的可重定位目标文件

hello.oelf:hello.o的ELF格式文件

hello.otxt:hello.o的反汇编代码文件

hello:链接后的可执行文件

hello.elf:hello的ELF格式文件

hello.txt:hello的反汇编代码文件

1.4 本章小结

    本章首先简述了hello的整个生命周期,包括P2P和020过程,交代了开发环境和调试工具,列出了中间过程中产生的主要文件。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理器cpp根据.c源文件中以#开头的命令,对源代码进行扩展,得到.i文件;

作用:插入#include后的头文件中的内容,进行宏替换,并删除注释,便于后续的编译和汇编等操作。

2.2 在Ubuntu下预处理的命令

使用gcc -E hello.c hello.i 或者 cpp hello.c > hello.i (二选一即可)

                                                             图2-1 预处理命令

2.3 Hello的预处理结果解析

                     图2-2 预处理结果片段(预处理文本文件太长,仅以开头一小部分为例)

2.4 本章小结

本章叙述了预处理的概念和作用,用Linux指令对hello.c源文件进行预处理得到hello.i文件,打开该文件观察预处理结果。


第3章 编译

3.1 编译的概念与作用

概念:读取预处理后的.i文件,对其进行语法分析,将高级语言指令等价转换成汇编指令,得到汇编代码形式的.s文件;

作用:将适合用户编写的高级语言转换成汇编指令,便于进行后续的汇编和链接操作;

3.2 在Ubuntu下编译的命令

使用gcc -S hello.i -o hello.s

图3-1 编译命令

3.3 Hello的编译结果解析

3.3.1开头部分

               图3-2 hello.s文件开头部分

.file 指明.c源文件

.text 代码段

.global 指出全局变量sleepecs

.data 数据段

.align 对齐方式

.type 指明类型

.size指明大小

.section .rodata 只读数据段

3.3.2数据

全局变量:存在.data段中

        图3-3 hello.s文件中的全局变量

局部变量:存在栈里

       图3-4 hello.s文件中的单个局部变量

                图3-5 hello.s文件中的数组全局变量

字符串:存在.rodata段中

                                       图3-6 hello.s文件中的字符串常量

3.3.3赋值

全局变量sleepecs在被赋值为long类型的2

       图3-7 对全局变量sleepecs的赋值

局部变量被赋值为0

              图3-8 对局部变量的赋值

3.3.4类型转换

源程序将float型的2.5赋值给int型的sleepecs,发生类型转换,变成2

图3-9 hello.c中对sleepecs的初始赋值

   图3-10 hello.s中sleepecs经过强转后的值

3.3.5 Sizeof

3.3.6 算术操作

算术即加减乘除,用于计算数值进行相应寻址或存储数据,下图的第41、44、47、53均有add加法运算

                    图3-11 hello.s中的算术操作

3.3.7逻辑/位操作

3.3.8关系操作

以cmp和test指令为主,根据比较结果设置条件码CF、ZF、SF和OF,根据得到的比较结果可以进行相应跳转,下图的第30和55行有cmp比较操作

          图3-12 hello.s中的关系比较操作

           图3-13 hello.s中的关系比较操作

3.3.9数组/指针/结构操作

Main函数的第二个参数char argv*[]即字符数组,在栈中为其分配存储空间

   图3-14 hello.s中对字符数组分配空间的操作

3.3.10控制转移

以jmp和jx(x为跳转条件)指令为主,根据cmp或test指令得到的比较结果,若符合跳转条件则触发跳转,下图第31和38行均有控制转移

              图3-15 hello.s中的控制转移操作

3.3.11函数操作

本程序涉及到的函数主要有:

main函数

                     图3-16 hello.s中的main函数

printf函数

                   图3-17 hello.s中的printf函数

sleep函数

            图3-18 hello.s中的sleep函数

exit函数

             图3-19 hello.s中的exit函数

3.4 本章小结

    本章叙述了编译的概念和作用,通过对预处理得到的hello.i文件进行编译,得到汇编代码形式的hello.s文件,打开后观察各部分,可以发现不同数据类型在程序中的存储方式和程序内部函数的调用,可以看到在源代码中看不到的执行细节,并作出总结。


第4章 汇编

4.1 汇编的概念与作用

概念:把.s文件中的汇编语言代码翻译成二进制的目标机器指令,形成可重定位的.o文件;

作用;将汇编语言等价转换成机器可识别的指令形式,经过后续的链接操作即可形成可执行程序。

4.2 在Ubuntu下汇编的命令

使用gcc -c hello.s -o hello.o或者as hello.s -o hello.o(二选一)

图4-1 汇编命令

4.3 可重定位目标elf格式

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

ELF Header:ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序:

图4-2 hello.o的ELF Header

    Section Headers:节头部表,描述了.o文件中各个节的名字、类型、地址、大小信息;

                                                  图4-3 hello.o的Section Headers

.rela.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置;

                                                   图4-4 hello.o的.rela.text节

.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息;

                                                     图4-5 hello.o的.symtab节

4.4 Hello.o的结果解析

(1)objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

                                                图4-6 对hello.o反汇编的部分代码

反汇编得到的hello.txt文件中的汇编代码和hello.s中的汇编代码内容大体一致,不同之处在于:涉及jmp分支跳转的地方,hello.s中体现为跳转到某个助记符,而在hello.txt中跳转到main函数+偏移量得到的地址;涉及call调用位于其他地方的函数时,在hello.s中使用函数名,而在hello.txt中使用call的下一条指令,至于具体地址要在链接后才能确定。

(2)说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

机器语言即二进制字节串,由操作码+地址码组成,操作码指明指令的具体功能,地址码对应操作所需的参数;机器语言与汇编语言为一对一的关系,相对于汇编语言可读性差,但可以直接被机器阅读并执行。

4.5 本章小结

本章叙述了汇编的概念和作用,对汇编代码文件hello.s进行汇编,得到可重定位目标文件hello.o;用readelf分析hello.o的ELF格式,了解到hello.o文件的构成;再使用objdump对hello.o文件进行反汇编得到反汇编代码文件hello.txt,通过与hello.s中的汇编代码对比观察,了解文件执行机制,也证明链接的必要性。


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-1 链接命令

5.3 可执行目标文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

ELF Header:ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序;

                                                        图5-2 hello的ELF Header

    Section Headers:节头部表,描述了.o文件中各个节的名字、类型、地址、大小信息;

                                                    图5-3 hello的Section Headers

    Program Headers:程序头部表,为链接器提供运行时的加载内容和提供动态链接的信息;

                                                  图5-4 hello的Program Headers

.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息;

                                                         图5-5 hello的.symtab节

5.4 hello的虚拟地址空间 

使用edb加载hello,得到本进程虚拟地址范围为0x401000~0x402000:

                                                图5-6 hello进程的虚拟地址范围

由5.3中Section Headers节头部表信息得,.init节的虚拟地址为0x401000,.rodata节的虚拟地址为0x402000,与用edb查看到的结果相符。

5.5 链接的重定位过程分析

hello的反汇编代码相比hello.o的反汇编代码拥有更多的节:.plt,.init,.fini;printf,getchar,exit等库函数也链接了进来;前面所提及的jmp和call后面的地址也具体化了,可寻址调试。

                                          图5-7 hello.o对应反汇编代码部分片段

                                          图5-8 hello对应反汇编代码部分片段

两图对比可以看出,链接前涉及地址的地方用偏移量相对寻址的方法,链接后已经体现为具体地址。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。                     

  

  

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

动态的共享链接库中存在着一个可以调用程序加载而动态链接无需重定位的位置无关代码,在调用共享库函数的时候,编译器是不能自动预测各个函数的开始运行时间和地址的,所以会为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它,而动态链接会采用延迟加载的策略,即在调用函数时才进行符号的映射。

dl_init前:

                                                         图5-9 dl_init前的内容

dl_init后:

                                                       图5-10 dl_init后的内容

5.8 本章小结

    本章叙述了链接的概念和作用,将可重定位文件hello.o与相应库函数进行链接得到可执行文件hello;使用readelf来查看hello的ELF格式文件,与hello.o的进行对比,观察结构组成上的不同之处;再用objdump来查看hello的反汇编代码,与hello.o的反汇编代码进行对比,发现地址的使用的明显差异;最后用edb调试和执行hello程序,观察整个执行过程,记录链接前后的变化。


6hello进程管理

6.1 进程的概念与作用

概念:进程是正在执行中程序的实例,是系统进行资源分配和调度的基本单位。系统中的每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需要的状态组成的。

作用:进程提供给应用程序的关键抽象:一个独立的逻辑控制流,提供一个假象,好像我们的程序独占地使用处理器和内存;一个私有的地址空间,提供一个假象,好像我们的程序独占地使用内存系统。

6.2 简述壳Shell-bash的作用与处理流程

作用:壳Shell-bash是一种交互型的应用级程序,它代表用户运行其他程序;

处理流程:从终端读取输入的命令行,通过识别命令行中的间隔字符来对命令行进行划分,进而获取参数,然后检查首个参数是否是内置的shell命令,若是则转交给内核处理,若不是则用fork为其创建子进程,在子进程中调用execve执行该进程,shell接受键盘输入信号,并对这些信号进行相应处理。

6.3 Hello的fork进程创建过程

向终端输入“./hello 孟祥瑞 1190202103”命令,shell会接收该命令并判断其是否为内置命令,若不是则调用fork为其创建一个子进程(子进程得到与父进程用户级虚拟地址空间相同的但是独立的一份副本,只是父进程和子进程拥有着不同的PID),进而执行hello程序。

6.4 Hello的execve过程

用fork创建完子进程后,会使用解析后的命令行参数调用execve函数,execve函数调用启动加载器来执行hello程序,然后启动代码设置栈,将控制传递给hello的主函数。

6.5 Hello的进程执行

上下文就是内核重新启动一个被抢占进程所需要的状态,包括通用目的寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构;而一个进程执行它的控制流的一部分的每一时间段叫做时间片;

在hello进程执行的过程中,开始是在用户模式下运行,收到系统异常信号后户进入内核模式,运行信号处理程序,处理完成后会回到用户模式,即用户态和核心态的切换;在运行过程中,cpu不断切换上下文,将运行过程分成若干时间片即逻辑控制流,与其他进程交替占用着cpu,实现进程调度。

6.6 hello的异常与信号处理

hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

(1)异常类型有:中断、陷阱、故障、终止;

中断:来自处理器外部I/O设备的信号的结果,处理完总是返回到下一条指令;

陷阱:是有意的异常,是执行一条指令的结果,也总是返回到下一条指令;

故障:由错误情况引起,可能被故障处理程序修复,可能返回到当前指令或终止;

终止:不可恢复的致命错误造成的结果,终止处理程序不会将控制返回给应用程序。

(2)产生信号有:SIGINT、SIGTRAP、SIGCONT、SIGCHLD;

SIGINT: Ctrl-Z命令会产生这个信号,使进程中断,切换到别的进程中;

SIGTRAP: sleep函数会触发这个信号,调用陷阱异常处理程序并跟踪陷阱;

SIGCONT: fg命令会向进程发送该信号,切换到该进程继续执行;

SIGCHLD: exit函数会发送这个信号,终止进程;

程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结果截屏,说明异常与信号的处理。

(1)不停乱按空格:

                                                      图6-1不停乱按空格的结果

(2)乱按Ctrl-C:

                                                      图6-2 乱按Ctrl-C的结果

(3)乱按Ctrl-Z及按后运行各命令:

ps:

                                                图6-3 按Ctrl-Z后运行ps命令的结果

jobs:

                                              图6-4 按Ctrl-Z后运行jobs命令的结果

pstree:

                                         图6-5 按Ctrl-Z后运行pstree命令的结果

fg:

                                           图6-6 按Ctrl-Z后运行fg命令的结果

kill:

图6-7 按Ctrl-Z后运行kill命令的结果

6.7本章小结

    本章首先叙述了进程的概念和作用,指出了逻辑控制流和虚拟内存的机制;然后简述了壳Shell-bash的作用与处理流程,了解了进程的执行逻辑;随后逐步介绍了hello程序运行过程中的fork进程创建过程和execve过程;然后结合时间片和上下文切换综述了hello进程调度的机制;最后列举了进程执行过程中会出现的异常及对应处理信号和处理方法。


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:是指由程序产生的与段相关的偏移地址部分,由段地址和偏移量组成,在汇编程序中用来制定一个操作数;

线性地址:是逻辑地址到物理地址变换之间的中间层,段中的偏移地址加上段的基地址就能得到线性地址;

虚拟地址:在保护模式下程序访问存储器所用的逻辑地址,并非实际可寻址的地址;

物理地址:是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

段式管理:逻辑地址由“段选择符+偏移量”组成,在保护模式下,段描述符集中存放在GDT或者LDT中,段寄存器负责存放段描述符在GDT或LDT中的索引值,段选择器的编码如下图所示,根据其找到段描述符,用段地址加上偏移量即可得到线性地址。

图7-1 段选择器的编码

7.3 Hello的线性地址到物理地址的变换-页式管理

页式管理:把内存划分成若干大小固定的页,虚拟内存被划分成若干虚拟页,物理内存也被划分成若干物理页,由内存管理单元MMU完成虚拟页到物理页的映射,地址翻译机制如下图:

图7-2 地址翻译机制

7.4 TLB与四级页表支持下的VA到PA的变换

首先有CPU产生虚拟地址VA,MMU会根据下面机制将VA翻译成对应的PA:

将高位的VPN分成TLBT和TLBI去寻找,根据组索引找到对应组块,若标记位匹配即找到,然后获取表中对应PPN,再结合低位不变的VPO组成物理地址PA。

图7-3 从VA到PA的变换

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

通过得到的PA地址的组索引找到对应组块,在组中查找,若标记位匹配且有效位为1,即为命中,获得块偏移量,进而得到相应字节内容;若未命中,则向下一级cache继续寻找,直到寻至内存中。

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核会为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当两个进程的任意一个后来进行写操作时,写时复制机制就会创建新页面,因此也就为每个进程保持了私有地址空间的抽象概念。

图7-4 fork函数的内存映射

7.7 hello进程execve时的内存映射

execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:

删除已存在的用户区域:删除当前进程虚拟地址的用户部分中的已存在的区域结构。

映射私有区域:为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。

映射共享区域:如果hello程序与共享对象(或目标)链接,比如标准C库libc.so,那么这些对象是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

设置程序计数器(PC):execve函数做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。

下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

图7-5 execve函数的内存映射

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

缺页故障:当内存管理单元MMU在页表中查找虚拟地址对应的物理内存不在主存中时,即为缺页故障,此时需要操作系统将该部分内存从磁盘调入主存中;

缺页中断处理:当发生缺页故障时,系统会调用内核中的缺页异常处理程序,程序会在物理内存中选择一个牺牲页,将其调回磁盘,然后将所缺的页调入,并更新内存中的PTE,随后返回程序中断处的指令,继续重新查找访问该虚拟地址。

图7-6 缺页处理

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。

分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

图7-7 动态内存分配(1)

分配器有两种基本风格,都要求显式分配块,不同之处在与怎样释放块;

显示分配器:要求显式地释放任何已分配的块,比如C标准库的malloc;

隐式分配器:也叫垃圾收集器,要求分配器检测一个已分配块何时不再被程序使用,此时释放块。

图7-8 动态内存分配(2)

7.10本章小结

本章先是分别阐述了逻辑地址、线性地址、虚拟地址和物理地址四个概念以及相互之间的联系;然后叙述了从逻辑地址到线性地址的变换-段式管理和线性地址到物理地址的变换-页式管理两种内存地址管理策略;随后交代了从虚拟地址VA到物理地址PA的转换过程,此部分的运算为考试重点;然后还有hello进程中fork和execve的内存映射策略;最后交代了缺页故障和处理,和动态内存分配管理的内容,本章内容丰富,知识体系庞大,需要多加学习才能充分理解。


8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件文件。

设备管理:所有的输入与输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

Unix I/O接口:

打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符;

Linux Shell创建的每个进程都有三个打开的文件:标准输入(描述符为0),标准输出(描述符为1),标准错误(描述符为2);

改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显式地设置文件的当前文件位置k;

读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,。给定一个大小为m字节的文件,当k>=m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测到这个条件;

类似地,写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k;

关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

Unix I/O函数:

(1)open函数:

函数原型: int open (const char *pathname, int flags, int perms);

功能描述:用于打开或创建文件,可以指定文件的属性及用户的权限等参数;

参数:pathname为被打开的文件名 ,flag为文件打开方式;

返回值:成功则返回文件描述符,失败则返回-1。

(2)close函数:

函数原型: int close (int fd);

功能描述:用于关闭一个被打开的的文件;

参数:fd为文件描述符;

函数返回值:成功则返回0,出错则返回-1。

(3)read函数:

函数原型:ssize_t read (int fd, void *buf, size_t count);

功能描述:从文件读取数据;

参数:fd为将要读取数据的文件描述词;

buf:指缓冲区,即读取的数据会被放到这个缓冲区中去;

count:表示调用一次read操作应该读取字符的数量;

返回值:返回所读取的字节数;若读到EOF则返回0,若出错则返回-1。

(4)write函数:

函数原型:ssize_t write (int fd, void *buf, size_t count);

功能描述:向文件写入数据;

返回值:成功则写入文件的字节数;出错则返回-1。

8.3 printf的实现分析

https://www.cnblogs.com/pianist/p/3315801.html网址找到printf函数的代码:

图8-1 printf函数的代码

函数主体的第一行的va_list arg = (va_list)((char*)(&fmt) + 4)不断接收传入参数,第二行的i = vsprintf(buf, fmt, arg)返回要打印出来的字符串的长度,然后write(buf,i)接收参数i,将字符串打印出来,实现printf的功能。

printf函数中调用的vsprintf函数负责接受一个格式化的命令,并把指定的匹配的参数格式化输出,代码如下:

图8-2 vsprintf函数的代码

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

调用getchar函数时,程序会接收用户的输入,而用户输入的字符被存放在键盘缓冲区中,直到用户按下回车,getchar函数开始从stdin流中每次读入一个字符。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回,返回值为用户输入的第一个字符的ASCII码,若出错则返回-1。

8.5本章小结

    本章先是简述了Linux系统下的IO设备管理方法,了解了不同于Windows下的机制;通过列举了Unix IO接口及其函数,展示了Linux下对I/O的处理策略;然后具体分析了printf函数和getchar函数的实现过程,扩充了知识的积累。

结论

首先用户使用高级语言编写hello.c源代码文件,通过预处理器对hello.c文件进行扩展得到预处理后的hello.i文本文件,再交给编译器将其转换成汇编代码形式的hello.s文件,然后交给汇编器将其翻译成机器语言指令形式的hello.o可重定位目标文件,经过链接器将其与所需库函数链接得到可执行程序hello;此时在shell中输入./hello的执行命令,操作系统就会调用fork为其创建子进程,再用execve函数将hello程序加载到该子进程,并映射虚拟内存;CPU会为其分配时间片,hello程序在其进程中执行自己的逻辑控制流;运行过程中hello程序可以通过MMU对地址的转换功能来访问内存,运行期间shell的信号处理函数可以接受程序的异常和用户的请求;程序运行结束时,shell父进程会回收子进程,同时内核删除为这个进程创建的所有数据结构,hello生命即结束。

计算机系统博大精深,每个部分各司其职,每个环节设计的也非常巧妙,集无数先辈之智慧,为当今世界造福。


附件

hello.c:源代码文件

hello.i:预处理后的文本文件

hello.s:编译后的汇编代码文件

hello.o:汇编后的可重定位目标文件

hello.oelf:hello.o的ELF格式文件

hello.otxt:hello.o的反汇编代码文件

hello:链接后的可执行文件

hello.elf:hello的ELF格式文件

hello.txt:hello的反汇编代码文件


参考文献

[1]   Randal E.byrant  &  David R.O'Hallaron . 深入理解计算机系统第三版教材

[2]  Farmwang .操作系统 内存地址(逻辑地址、线性地址、物理地址)概念 .https://blog.csdn.net/farmwang/article/details/42714683

[3]  机械中的AI混子. 哈工大计算机科学与技术大作业论文-程序人生-Hello’s P2P.docx .https://max.book118.com/html/2019/1229/6115144142002134.shtm

[4]  qq_41512063 . 哈工大2018计算机系统大作业——程序人生 .https://blog.csdn.net/qq_41512063/article/details/85488840

[5]  月华流 .哈工大深入理解计算机系统大作业 .https://blog.csdn.net/qq_34767140/article/details/103843792?utm_medium=distribute.pc_relevant_download.none-task-blog-2~default~searchFromBaidu~default-6.nonecase&depth_1-utm_source=distribute.pc_relevant_download.none-task-blog-2~default~searchFromBaidu~default-6.nonecas

                       

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值