HIT CSAPP 大作业

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业         计算机类       

学     号        1190200921      

班   级         1903012        

学       生          徐放    

指 导 教 师          史先俊     

计算机科学与技术学院

2021年5月

摘  要

本文介绍了hello的一生,研究了hello在操作系统下预处理、编译、汇编链接等过程,并探讨了hello进程、存储以及IO管理方面的内容。

关键词:预处理、编译、汇编、链接、进程、存储、IO                            

目  录

第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简介

P2P:即From Program to Process。首先是通过代码编写完成源文件代码hello.c,之后点击编译器的运行键时,就开始了这一过程。

首先是预处理阶段,预处理器会根据以字符#开头的命令,修改原始的C程序,得到另一个C程序,通常是以.i作为文件拓展名。

之后是编译阶段,编译器将文本文件hello.i翻译为文本文件hello.s,它包含了一个汇编语言程序。汇编语言程序中每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。

再之后是汇编阶段,汇编器将hello.s翻译成机器语言指令,将这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中,此时的hello.o是一个二进制文件,它的字符编码是机器语言指令而不是字符。

再之后是链接阶段。连接器(ld)就负责处理这种合并,结果得到hello文件,它是一个可执行目标文件(即可执行文件),可以被加载到内存中被系统执行。

最后运行该文件时,操作系统会使用dork形成一个子进程,再调用execve函数加载进程,至此P2P算是圆满完成。

O2O:即From Zero-0 to Zero-0。当shell通过execve函数执行该程序时,操作系统为该进程分配虚拟空间,将程序加载到虚拟空间对应的物理空间中。执行完成后,需要回收该进程,并删除内存内其对应的数据,至此O2O完成。

1.2 环境与工具

硬件:

CPU:Intel(R) Core(TM) i5-9300U CPU @ 2.40GHz

内存:8GB

软件:

VMware,Ubuntu,windows

开发工具:

gcc,gdb,vscode,codeblocks

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

名字

作用

hello.c

源代码

hello.i

预处理后生成的文本文件

hello.s

编译后生成的汇编文件

hello.o

汇编后生成的可重定位文件

hello

链接后生成的可执行文件

1.4 本章小结

本章阐明了P2P以及O2O的原理,对于实验环境与工具有了初步的说明,并罗列了中间结果。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

概念:预处理器会根据以字符#开头的命令,修改原始的C程序,得到另一个C程序,通常是以.i作为文件拓展名。

作用:预先将源代码进行处理,为后来的编译做好预先工作。

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

应截图,展示预处理过程!

2.3 Hello的预处理结果解析

hello.c只有短短二十三行,而hello.i却有3060行。

此外原本在hello.c中出现的#include的头文件在hello.i中已经不复存在,而且注释也不见踪影。但hello.i中后面几行代码与hello.c的代码基本一致(除了少了头文件以外)

2.4 本章小结

本章完成了对hello.c的预处理,并对hello.i进行了一系列的分析。


第3章 编译

3.1 编译的概念与作用

概念:编译器将文本文件hello.i翻译为文本文件hello.s,它包含了一个汇编语言程序。汇编语言程序中每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。        

作用:将高级语言翻译成汇编语言。

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

3.3.1数据:

  1. 变量名:i

类型:int,局部变量。

操作:编译器将其存储在栈中%rbp-4的位置,并赋初值为0.

  1. 变量名:argv

类型:char **,传入参数

操作:是字符串数组,存储在栈中,初始位置在%rbp-32处

  1. 变量名:argc

类型:int,传入参数

操作:是整形,存储在栈中,初始位置为%rbp-20处

  1. 常量:在循环中出现的立即数,汇编语言中对其直接调用:

还有输出的字符串,都声明在程序初始处:

3.3.2赋值:

本实验中赋值操作基本上由mov来完成。

3.3.3算术操作:

在循环中出现了i++,即i=i+1,属于+操作。在hello.s中,调用了addl

,其中-4(%rbp)即是i。

3.3.4关系操作:

hello.c中用到了如下两个关系操作:’!=’以及’<’ ,来源见下图:

  1. !=

比较通过cmp来进行。下图为来源。

je表示如果上面两项相等则跳转到L2,否则继续进行下面的操作。!=主要是标记条件码ZF来判断的。若ZF=1,就表示两数相等。

  1. <

比较通过cmp来进行,下图为来源:

jle表示如果i<=7则转移否则继续进行下面的操作。<主要是靠标记码SF来判断。

3.3.5数组操作

本操作中主要是指argv,本次实验对于argv进行访问时,会将位置记录在rax中,如%rax=-32(%rbp)+8来表示argv[1]。然后通过(%rax)访问argv[x]。

3.3.6转移操作

本实验两次转移操作都是基于cmp来实现的。

跳转的话,比如jle .L3即直接到L3:下面进行操作。

3.3.7函数操作

  1. main函数:

main函数是整个函数的入口,它传递了两个参数argv以及argc。其返回值为0。

  1. printf函数:

第一次调用printf函数的汇编语言如下:

其原语句为:

我们再看看LC0:

其就是对应语句转化而来的。

第二次调用printf函数的汇编语言如下:

其原语句为:

再看看LC1:

正式我们需要输出的。

  1. sleep函数

Sleep的参数是atoi函数的返回值。

其原语句如下:

汇编语句中:

这里先是求atoi的返回值再求sleep的函数值。

Sleep函数返回值储存在%rax中,但本实验未去调用。

  1. atoi函数

参数是argv[3],

原语句:

汇编语句:

Atoi函数返回值储存在%rax中,作为sleep函数的参数使用。

  1. exit函数

无参数无返回,其作用在于中止程序。

  1. getchar函数

无参数,有返回,但本实验未使用。

原语句:

汇编语句:

3.4 本章小结

本章讲解了汇编的概念及作用,并对编译器各类操作以及对汇编进行了深度地剖析。


第4章 汇编

4.1 汇编的概念与作用

概念:汇编器将hello.s翻译成机器语言指令,将这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中,此时的hello.o是一个二进制文件,它的字符编码是机器语言指令而不是字符。

作用:将汇编指令翻译为机器代码。

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式

ELF格式如上图所示。

接下来是hello.o的elf。首先先获取hello.elf

  1. ELF头

ELF头包含了文件结构的说明信息。具体内容如下:

  1. 节头部表

节头部表描述了不同节的位置以及大小,具体内容如下:

  1. 重定位节

重定位节包含了以下两部分:

4.4 Hello.o的结果解析

反汇编结果如下图所示:

与hello.s进行比较,发现了很多的出入:

  1. 很多伪指令消失了。

截取两个截图,不难发现.cfi_startproc等指令都消失了。

  1. 调用函数的方式发生了改变。

上面两个图分别来自两个文件,可以明显发现调用函数的方式发生了改变。

  1. 一个是用十六进制表示数,另一个是用十进制表示数。

这两截图就可以看出来。

4.5 本章小结

本章分析了汇编的概念和作用,学习了汇编的指令,并对elf文件结构进行了剖析。最后还比较了hello.o的反汇编与hello.s的差异。


5链接

5.1 链接的概念与作用

概念:连接器(ld)就负责处理这种合并,结果得到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的格式

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

  1. ELF头

节头表:

程序头:

5.5 链接的重定位过程分析

以下格式自行编排,编辑时删除

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

输入objdump -d -r hello > hello.az得到反汇编代码:

与hello.o对比发现

  1. hello的反汇编函数拥有更多的函数,很多被程序调用的系统函数都被列了今来。
  2. 数据引用变化。和函数类似,hello.o中一些数据的地址是不确定的,但是在hello的反汇编中它们的地址确定了。如.rodata+0x22的地址就明确为0x40202e。

重定位:

分为两步

  1. 首先是重定位节和符号定义,链接器将所有输入到相同类型的节合并为同一类型的新的聚合节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每一个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
  2. 然后是重定位节中的符号引用,链接器会修改代码节和数据节中对每一个符号的引用,使得他们指向正确的运行地址。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。

5.7 Hello的动态链接分析

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

5.8 本章小结

本章介绍了链接的概念以及作用,分析了hello的ELF格式,探讨了hello.o的重定位等方面的内容。


6hello进程管理

6.1 进程的概念与作用

概念:进程是一个执行中的程序的实例。是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

作用:每次用户通过shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,应用程序也可以创建新进程,并且在这个新进程的上下文中运行它们自己的代码或者其他应用程序。这使得我们可以同时运行多个程序。

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

作用:提供用户与操作系统交互的界面,用户通过这个界面访问操作系统内核的服务。可以输入命令行,执行相应的指令。

处理流程:

1.Shell首先从命令行中找出特殊字符(元字符),在将元字符翻译成间隔符号。元字符将命令行划分成小块tokens。Shell中的元字符如下所示:
SPACE , TAB , NEWLINE , & , ; , ( , ) ,< , > , |
2. 程序块tokens被处理,检查看他们是否是shell中所引用到的关键字。
3. 当程序块tokens被确定以后,shell根据aliases文件中的列表来检查命令的第一个单词。如果这个单词出现在aliases表中,执行替换操作并且处理过程回到第一步重新分割程序块tokens。
4.Shell对~符号进行替换。
5.Shell对所有前面带有$符号的变量进行替换。
6.Shell将命令行中的内嵌命令表达式替换成命令;他们一般都采用$(command)标记法。
7.Shell计算采用$(expression)标记的算术表达式。
8.Shell将命令字符串重新划分为新的块tokens。这次划分的依据是栏位分割符号,称为IFS。缺省的IFS变量包含有:SPACE , TAB 和换行符号。
9.Shell执行通配符* ? [ ]的替换。
10.shell把所有從處理的結果中用到的注释删除,並且按照下面的顺序实行命令的检查:
A. 内建的命令
B. shell函数(由用户自己定义的)
C. 可执行的脚本文件(需要寻找文件和PATH路径)
11.在执行前的最后一步是初始化所有的输入输出重定向。

12.最后,执行命令。 

6.3 Hello的fork进程创建过程

1.在shell输入.hello之后,它会判断是不是内置函数。

2.用fork创建一个子进程,之后运行hello。

6.4 Hello的execve过程

execve函数加载并运行可执行目标文件execve将hello加载到当前的进程中,将原先程序覆盖,释放掉原先程序的用户空间,加载hello程序到用户空间中,hello程序和原程序的进程相同,拥有相同的PID。首先execve加载了可执行程序的名字hello后调用启动代码.启动代码设置栈(用户栈的组织如下,其中argv指向参数字符串,envp指向环境变量字符串),并将控制传递给新程序主函数。

6.5 Hello的进程执行

系统中每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

内核为每个进程维护了一个上下文。当内核选择的一个新的进程运行时,我们说内核调度了这个进程。所以当内核调度了hello这个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换会首先保存当前进程的上下文,然后恢复新恢复进程被保存的上下文,最后控制传递给这个新恢复的进程 ,来完成上下文切换。过程如下:

6.6 hello的异常与信号处理

以下格式自行编排,编辑时删除

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

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

Hello执行过程有四种异常:

  1. 中断:中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。硬件中断的异常处理程序通常称为中断处理程序。

2)陷阱:陷阱是有意的异常,是执行一条指令的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。

3)故障:故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。一种经典案例便是缺页异常。

4)终止:终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。

按键盘:

  1. Ctrl-C

可以看出hello已被回收

  1. Ctrl-Z

此时进程被挂起,可用fg恢复

  1. Fg

承接上面的内容,输入fg后发现hello恢复了工作。

  1. Jobs

可以查询进程内容

  1. Kill

成功杀死了进程。

6.7本章小结

本章介绍了进程的概念及作用,并探讨了shell、fork、execve的工作过程,又对异常和信号进行了初步的探讨。


7hello的存储管理

7.1 hello的存储器地址空间

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

逻辑地址:逻辑地址是指在计算机体系结构中是指应用程序角度看到的内存单元(memory cell)、存储单元(storage element)、网络主机(network host)的地址。

线性地址:是逻辑地址物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。

虚拟地址:虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址

物理地址:网卡物理地址存储器中存储单元对应实际地址称物理地址,与逻辑地址相对应。

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

段式管理是指把一个程序分成若干个段(segment)进行存储,每个段都是一个逻辑实体(logical entity),程序员需要知道并使用它。它的产生是与程序的模块化直接有关的。段式管理是通过段表进行的,它包括段号或段名、段起点、装入位、段的长度等。此外还需要主存占用区域表、主存可用区域表。每个段地址的说明为两个量:一个段名和一个位移。段式管理是不连续分配内存技术中的一种。其最大特点在于他按照用户观点,即按程序段、数据段等有明确逻辑含义的“段”,分配内存空间。段的最大好处是可以充分实现共享和保护,便于动态申请内存,管理和使用统一化,便于动态链接;其缺点是有碎片问题。

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

页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。其主要分为静态和动态。页式管理可以为内存提供两种方式的保护。一种是地址越界保护,另一种是通过页表控制对内存信息的操作方式以提供保护。地址越界保护可由地址变换机构中的控制寄存器的值一一页表长度和所要访问的虑地址相比较完成。存取控制保护的实现则是在页表中增加相应的保护位即可。由于它不要求作业或进程的程序段和数据在内存中连续存放,从而有效地解决了碎片问题。动态页式管理提供了内存和外存统一管理的虚存实现方式,使用户可以利用的存储空间大大增加。这既提高了主存的利用率,又有利于组织多道程序执行。但其缺点也很明显,要求有相应的硬件支持。例如地址变换机构,缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持。这增加了机器成本。此外,还增加了系统开销,例如缺页中断处理机。并且若请求调页的算法如选择不当,有可能产生抖动现象。

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

上图给出了如何用四级页完成VA到PA的变换。36位VPN 被划分成四个9 位的片,每个片被用作到一个页表的偏移量。CR3 寄存器包含Ll页表的物理地址。VPN 1提供到一个Ll PET的偏移量,这个PTE包含L2页表的基地址。VPN 2 提供到一个L2 PTE 的偏移量,以此类推。

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

得到物理地址之后,我们可以将物理地址拆分为CT+CI+CO,然后在一级的cache查找,先找组索引,然后再看看标记为有没有,没有的话再去二级去找,二级cache仍未找到的话以此推类。

7.6 hello进程fork时的内存映射

在用fork创建虚拟内存的时候,要经历以下步骤:

1.创建当前进程的mm_struct,vm_area_struct和页表的原样副本

2.两个进程的每个页面都标记为只读页面

3.两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制。

7.7 hello进程execve时的内存映射

通过execve函数加载并运行hello需要以下步骤:

  1. 删除已存在的用户区域。删除shell虚拟地址的用户部分中的已存在的区域结构。
  2. 射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。下图概括了私有区域的不同映射。
  3. 映射共享区域。如果hello与共享对象(或目标)链接,比如标准C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
  4. 设置程序计数器。Execve做的最后的一件事情就是设置当前进程上下文的程序计数器,使之指向文本区域的入口点。

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

在虚拟内存的习惯说法中,DRAM缓存不命中称为缺页。就比方说,CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3,从有效位推断出VP3未被缓存,并且触发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。在缺页之前,如下图所示:

接下来,内核从磁盘拷贝VP3到内存中的PP3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬体。但是现在,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。情况如下所示:

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap)。如下图:

系统之间细节不同,但是不失通用性。分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟存储器片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显示的被应用所分配。一个已分配的块保持已分配状态,直到它被释放。这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。分配器有两种基本风格,如下所示:

  1. 显示分配器:要求应用显式地释放任何已分配的块。
  2. 隐式分配器:要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾回收器,而自动释放未使用的已经分配的块的过程叫做垃圾收集。

实现方面:

1隐式空闲链表:

任何实际的分配器都需要一些数据结构,允许他来区别块边界,以及区别已分配块和空闲块。大多数分配器将这些信息嵌入在这个快本身。在这种情况下,一个块就是由一个字的头部、有效载荷、以及可能的一些额外的填充组成。头部编码了这个快的大小(包括头部和所有填充),以及这个块是已分配还是空闲的。

2显示空闲链表:

将堆组成一个双向空闲链表,在每个空闲块中,都包含一个pred和succ指针。

一种方法是用后进先出(LIFO)的顺序来维护链表,将新释放的块放置在链表的开始处。使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块。在这种情况下,释放一个块可以在常数时间内完成。如果使用了边界标记,那么合并也可以在常数时间内完成。

7.10本章小结

本章主要介绍了程序的存储结构,首先介绍了分段管理和分页管理,有探讨了cache,页表,fork等一系列的内容。并对映射,缺页、以及动态存储分配有了进一步的认知。


8hello的IO管理

8.1 Linux的IO设备管理方法

IO设备都被模型化为文件,而输入和输出则被视为读和写。这种将装置优雅地映射为文件的方式,允许Linux核心引出一个简单、低阶的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

输入输出都以以下方式执行:

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

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

3)读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能够检测到这个条件。在文件结尾处并没有明确的“EOF符号”。

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

对应的函数:

  1. open:通过调用open函数打开一个已存在的文件或者创建一个新文件:

  1. close:close函数关闭一个打开的文件:

  1. read和write:应用程序是通过调用read和write函数来执行输入和输出。

8.3 printf的实现分析

https://www.cnblogs.com/pianist/p/3315801.html

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

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

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

Printf代码如下:

vsprint代码如下:

vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。这里的代码中的vsprintf只实现了对16进制的格式化。

Write汇编代码:

一个int INT_VECTOR_SYS_CALL表示要通过系统来调用sys_call这个函数。

8.4 getchar的实现分析

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

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求。键盘中断服务程序先从键盘接口取得按键的扫描码,然后根据其扫描码判断用户所按的键并作相应的处理,最后通知中断控制器本次中断结束并实现中断返回。
若用户按下双态键(如:Caps Lock、Num Lock和Scroll Lock等),则在键盘上相应LED指示灯的状态将发生改变;
若用户按下控制键(如:Ctrl、Alt和Shift等),则在键盘标志字中设置其标志位;
若用户按下功能键(如:F1、F2、…等),再根据当前是否又按下控制键来确定其系统扫描码,并把其系统扫描码和一个值为0的字节存入键盘缓冲区;
若用户按下字符键(如:A、1、+、…等),此时,再根据当前是否又按下控制键来确定其系统扫描码,并得到该按键所对应的ASCII码,然后把其系统扫描码和ASCII码一起存入键盘缓冲区;

若用户按下功能请求键(如:Print Screen等),则系统直接产生一个具体的动作。

8.5本章小结

本章主要介绍了Linux下IO设备管理方法,,并简述了IO接口及其函数。对printf和getchar的实现进行了实现的分析。

结论

用计算机系统的语言,逐条总结hello所经历的过程。

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。

hello的一生短暂且伟大。前面文章写了一大段,在计算机里运行甚至不超过1s。即便如此,仔细分析,我们仍可以从hello文件的运行中探讨出计算机系统的奥秘。其实不止hello程序,大部分程序都是如此,它们经过预处理变成了.i文件,再通过编译变成了.s文件,又经过汇编变成了o文件,最后经过链接成了可执行文件。代码走到了这里,但运行却尚未开始。我们通过shell创建了hello的进程,通过fork创建子进程,用execve来运行hello。此外


附件

文件名

作用

hello.c

源代码

hello.i

预处理后生成的文本文件

hello.s

编译后生成的汇编文件

hello.o

汇编后生成的可重定位文件

hello

链接后生成的可执行文件

bao.txt

hello的反汇编

hello.az

hello.o的反汇编

hello.elf

hello的elf文件


参考文献

[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.

[7]  兰德尔·E·布莱恩特等著;深入理解计算机系统[M]. 北京:机械工业出版社,2016.7.

[8]  百度百科

[9]  C library - C++ Reference http://www.cplusplus.com/reference/clibrary/

[10]  Linux man pages https://linux.die.net/man/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值