程序人生-Hello’s P2P

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业           计算机类            

学     号                    

班     级                        

学       生                     

指 导 教 师                   

计算机科学与技术学院

2022年5月

摘  要

本文记录了一个简单的hello.c文件从源码到可执行文件并且执行、结束的全过程,包括预处理、编译、汇编、链接、进程管理、存储管理和IO管理部分,在每一小结具体分析了文件变换、生成和执行的过程,对系统中文件的P2P和020过程进行了详述。

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

目  录

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

P2P:

  1. 编写program(hello.c)
  2. 预处理,通过cpp预处理生成hello.i
  3. 编译,通过ccl编译生成hello.s
  4. 汇编,通过汇编器as生成hello.o文件
  5. 链接,通过链接器ld链接多个文件生成可执行程序
  6. fork,生成在shell中生成子进程(Process)

020:

  1. 在shell中调用execve运行映射虚拟内存。
  2. Mmap创建程序运行空间。
  3. CPU为程序分配时间片执行逻辑控制流。
  4. 程序运行结束时,父进程回收hello子进程,删除其在系统中的痕迹。

1.2 环境与工具

硬件环境:联想笔记本电脑;AMD Ryzen 7 Mobile 4800U;

软件环境:Windows10 64位;Vmware 15;Ubuntu 20.04 64位

工具:Visual Studio;gdb;Objdump;

1.3 中间结果

hello.c 源代码

hello.i 预处理后文件

hello.s 编译后的汇编语言文件

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

hello 链接后的可执行文件

hello.elf :hello.o的ELF格式

hello1.txt :hello.o的反汇编

hello2.txt :hello的反汇编代码

hello1.elf :hello的ELF格式文件

1.4 本章小结

       对p2p、020过程进行了简述,列出了作业环境、工具和中间产生的过程文件。

第2章 预处理

2.1 预处理的概念与作用

概念:预处理过程由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

作用:预处理器根据以字符#开头的命令,修改原始的C语言程序。例如#include<studio.h>命令告诉预处理器读取系统头文件studio.h中的内容,并把它直接插入程序文本中。结果就得到另一个C程序,通常以.i作为文件拓展名。

2.2在Ubuntu下预处理的命令

使用gcc预处理指令gcc -E -o hello.i hello.c,执行后得到hello.i文件

2.3 Hello的预处理结果解析

预处理后得到hello.i文件,打开使用文本编辑器打开文件,可以发现程序仍然时基本可读的c语言格式,但代码行数拓展到了三千行,在最后几行可以看到我们源程序除去注释和三个#开头的include指令外的所有原文,因此也可以验证预处理过程中根据#后跟的指令,修改了程序,此处为将studio.h、unistd.h、stdlib.h三个头文件中的内容直接加入了程序文本形成了新文件hello.i。

2.4 本章小结

介绍了预处理的概念与功能,对hello.c文件实现了预处理步骤,并分析了所生成的文件hello.i。

第3章 编译

3.1 编译的概念与作用

概念:利用编译器从源语言编写的源程序产生汇编程序的过程。

作用:将文本文件hello.i翻译成汇编语言程序hello.s,为不同高级语言的不同编译器提供通用的输出语言,便于汇编器的处理。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1 数据

常量:编译器直接将源程序文本中的常量复制到汇编程序文本中,如源程序中与变量argc比较的4,在汇编程序中也以4直接出现

  

变量:源程序中的变量为局部变量,存储在栈中。如int型变量i,它存储在栈中,地址为-4(%rbp)

类型:对于int型数据,直接以二进制存储在栈或寄存器中,对于String或char型数据,则以ascii码的形式存储。

3.3.2 赋值

汇编语言中使用movl指令来直接赋值,例如对i进行初始化时,直接使用了movl    $0, -4(%rbp)指令将其值赋为0。

3.3.3 类型转换

源程序中将char类型的数据转换为整形数据,这里是通过调用atoi函数来实现,在汇编语言中,使用call指令,调用atoi转换了数据类型。

3.3.4 算数操作

源程序中的算数操作为”i++”操作,在汇编程序中则是直接对变量i所在的栈位置进行addl操作来加1.

3.3.5 关系操作与控制转移

源程序在if条件判断中进行了“argc!=4”的关系操作,在汇编程序中则是进行capl操作和je操作来实现条件判断与跳转

3.3.6 数组操作

源程序对argv数组进行操作,在汇编程序中,argv数组的值存储在栈中,如argv[3]存储在-32(%rbp)中。

3.3.7 函数操作

汇编程序中,对函数的调用采用了call指令,需要传递的变量值则是提前存储在对应的寄存器之中,函数返回时,也将返回值存储在指定寄存器上。

3.4 本章小结

介绍了编译的概念和作用,对hello.i文件执行了编译操作,得到文件hello.s,最后对所得到的汇编程序进行了与源程序相对应的分析。

第4章 汇编

4.1 汇编的概念与作用

       汇编器将hello.s文件翻译成机器语言指令,把这些结果指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o 中。

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

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

使用readelf将hello.o生成为elf格式文件,使用文本编辑器打开,可以看到其中存储的信息。

ELF头:首先是一个16字节的序列,描述了生成该文件的系统的字的大小和字节顺序。剩下的部分则包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。

节头:该ELF文件中包含13个节,与书中所列的典型ELF节相比,少了.rel.text、.rel.data、.debug、.line,多了.rela.text,.comment,.note.GNU-stack,.note.gnu.propert,.eh_frame,.rela.eh_frame,.shstrtab,多出的和少的节功能之间推测有重叠,不过整体所有的节的功能都是完整的描述目标文件。

重定位节:节.rela.text和.rela.eh_frame分别对.text和.eh_frame进行重定位,将其中的符号和正确的内存位置进行关联。

符号表:.symtab中包含18个在程序中定义和引用的函数和全局变量的信息。

4.4 Hello.o的结果解析

反汇编后的文本如下:

和hello.s相比,反汇编代码在跳转时均使用虚拟地址进行跳转,如在进行je,jle或者call操作时,均是使用的对应的虚拟地址,而在hello.s中则是使用段名和函数名来进行跳转。

其次,在反汇编代码中立即数均使用16进制进行表示,与hello.s中的十进制不同。

4.5 本章小结

将hello.s文件进行了汇编,生成了可重定位目标文件hello.o。分析了该文件的elf格式所存储的信息,之后对hello.o进行了反汇编,比较了反汇编代码与hello.s的区别。

5章 链接

5.1 链接的概念与作用

概念:链接是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。

作用:链接器使得软件可以分离编译,我们不用把一个巨大的应用程序组织为一个巨大的源文件,而是可以将他们分解为更小、更好管理的模块,可以独立地修改和编译这些模块,当我们修改这些模块中的一个式,只需要简单的重新编译它,并重新链接应用,而不用重新编译其他文件。

5.2 在Ubuntu下链接的命令

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

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

ELF头:这里描述了文件的总体格式,还包括程序的入口点,即当程序运行时要执行的第一条指令的地址。

节头:和可重定位目标文件相比,共有27个节,.text、.rodata、.data等节相似,多出.hash等链接后独有的节。

程序头:描述可执行文件的连续的片被映射到连续的内存段的这种映射关系

5.4 hello的虚拟地址空间

edb加载hello之后,可以在左下角Data Dump处查看到本进程的虚拟地址空间各段信息,从.elf文件中,可以看到程序入口点地址0x4010f0在虚拟内存中的具体存储内容

此外,还可以根据各个程序头段所标记的虚拟内存地址,看到其具体的村塾信息。

5.5 链接的重定位过程分析

相比hello.o的反汇编程序文本,此时的反汇编程序文本大大增长了,不过main函数部分的代码量与原来大致相同,多出的部分为.init、.plt、.fini和.text节,还有其中所定义的函数的汇编代码。

此外在进行挑转和函数调用时,从原本的函数名和标号,变为了虚拟地址。

5.6 hello的执行流程

在edb中运行时,argc数组为空,所以进入了if分支之中,并在其中调用exit退出程序。

_start 0x4010f0 //程序入口

_libc_start_main  0x7fldb3512fc0

_libc_csu_init 0x4011c0

_init 0x401000

main 0x401125

puts 0x401030

//printf输出的值为字符串时会自动转会为调用puts输出

exit 0x401070

5.7 Hello的动态链接分析

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

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

Hello中的动态链接项目有got.plt.got,在init之前,在data dump中可以查看他们存储的内容如下:

Init之后:

5.8 本章小结

链接了hello.o文件生成了可执行程序,并且分析了其文件格式、虚拟空间地址、重定位、执行流程和动态链接。

6章 hello进程管理

6.1 进程的概念与作用

进程的经典定义就是一个执行中程序的实例。

作用:给应用程序提供两个关键抽象:

  1. 一个独立的逻辑控制流,提供一个假象,好像程序独占地使用处理器
  2. 一个私有地址空间,提供一个假象,好像程序独占地使用内存系统

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

Shell壳:shell  是一个交互型应用级程序,代表用户运行其他程序。如Windows下的命令行解释器,cmd、powershell,图形界面的资源管理器。Linux下的Terminal/tcsh、bash等等,当然也包括图形化的GNOME桌面环境

Shell是信号处理的代表,负责各进程创建与程序加载运行及前后台控制,作业调用,信号发送与管理等。

Shell处理输入时,首先读入用户输入的指令,之后检测指令是否是内置指令,若是则立刻执行,不是则调用相应程序来执行。

6.3 Hellofork进程创建过程

在shell中运行hello时,shell fork了一个子进程来执行hello程序,新创建的子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。

在创建子进程时,父进程得到子进程的PID,子进程的fork返回值则是0。

6.4 Hello的execve过程

Execve函数在当前进程的上下文中加载并运行一个新程序,即可执行目标文件hello,且带参数列表argv和环境变量列表envp,它会覆盖当前进程的地址空间,但并没有创建一个新进程。

argv指向一个以null结尾的指针数组,按照惯例argv[0]是可执行目标文件的名称。

Execve函数首先调用加载器来加载目标文件代码,加载器将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序。

6.5 Hello的进程执行

上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由 通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内 核数据结构等对象的值构成。

进程时间片:一个进程和其他进程轮流运行的概念成为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片,因此多任务也叫做时间分片,在运行hello程序时,hello在前台运行,所以不会和shell并发地运行,但是会和系统的其他进程一起运行,产生时间分片。

进程调度:无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行,而运行hello程序时其就是其中的一个用户进程。

用户态和核心态的转换:本质是就是一种上下文切换,在切换时系统会自动保存前一个进程的上下文信息来保证之后该进程可以恢复,在用户进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。例如如果系统调用因为等待某个事件而发生堵塞,那么内核可以让当前进程休眠,切换到另一个进程。

在hello程序中,在一些语句执行中需要调用到磁盘内容,耗费大量时间,就可能会发生这种转换。

6.6 hello的异常与信号处理

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

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

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

  1. ctrl+c向进程发送SIGINT信号,终止了前台作业。

  1. ctrl+z向进程发送SIGTOP信号,挂起前台作业放入后台,可以使用ps、jobs指令看到其在后台的状态和pid,使用fg来继续运行,使用kill来杀死该后台进程。

  1. 在程序执行时不停乱按,包括回车等字符,这是无关的输入均存入了缓冲区,当hello前台程序运行结束时,这些输入被当作了shell的命令行输入。

6.7本章小结

这一章中,介绍了进程的概念和作用,分析了在hello运行中进程相关的操作,最后测试和分析了hello进程对于异常的处理。

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:逻辑地址是指在计算机体系结构中是指应用程序角度看到的内存单元、存储单元、网络主机的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量。

线性地址:线性地址是逻辑地址到物理地址转换的中间层。程序代码经编译后会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。若启用了分页机制,则线性地址会再此转换产生一个物理地址。若没有启用分页机制,则线性地址就是物理地址。

虚拟地址:虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。

物理地址:是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。

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

       段选择符:段寄存器中存放着段选择符

段描述符:段描述符是一种数据结构,实际上就是段表项。

在变换时,通过逻辑地址中的段选择符来选中一个段描述符,被选中的逻辑地址的段描述符先被送至描述符cache,每次从描述符cache中取32位段基址,与32位段内偏移量(有效地址)相加得到线性地址。

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

线性地址即虚拟地址到物理地址之间的转换通过分页机制完成,而分页机制是对虚拟地址内存空间进行分页。页式管理把内存空间按页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。

页表 是一个页表条目 (Page Table Entry, PTE)的数组,将虚拟页地址映射到物理页地址。

在hello进行线性地址到物理地址的变换中时,每次转换时,都会查询页表来判断一个虚拟页是否缓存在DRAM的某个地方,如果不在DRAM的某个地方,通过查询页表条目可以知道虚拟页在磁盘的位置。页表将虚拟页映射到物理页。

具体的转换过程可见该图。

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

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

多级页表:以二级页表为例,一级页表中的每个PTE将指向一个页表,而二级页表中的每个PTE才指向每个页。

在变换时,先在TLB中寻找匹配,若TLB命中,则直接得到物理地址PA,若不命中,则MMU确定偏移量,从而在各级页表中确定我们锁要找的PPN所在的页表位置,最终会在第四级页表中找到PPN,和VPO组合成为物理地址PA。

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

获得物理地址之后,首先将其分割,变为40位的标记位CT、6位的索引位CI和6位的块偏移CO。首先通过组索引位CI来找到相应的组,之后把标记位于L1中的标记位进行比较,若成功匹配,则命中,通过块偏移位可以找到我们需要访问的物理内存,若不命中则进入L2 cache中继续以相同的模式来寻找,若命中则将其存入上级cache中,若不命中则继续在L3 cache中寻找,仍不命中则需前往磁盘中寻找。

7.6 hello进程fork时的内存映射

当shell父进程进行fork创建子进程时,内核会为该子进程创建一份与父进程相同的数据结构,并为其创建虚拟内存,并分配一个新的PID。

同时,子进程会得到一个当前进程的mm_struct、vm_area_struct和页表的副本,内核会将这两个进程的每个界面都标记为只读,两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)

在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存。

7.7 hello进程execve时的内存映射

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

Execve函数在当前进程中加载并允许新程序hello时,首先会删除以及存在的用户区域,之后将创建新的区域结构,包括代码、数据、bss和栈区域,并且这些区域是私有的和写时复制的,代码和初始化数据映射到.text和.data区(目标文件提供).bss和栈堆映射到匿名文件 ,栈堆的初始长度0。

之后将映射共享区域,共享对象将由动态链接映射到本进程的共享区域,例如hello程序于libc.so链接,因此libc.so动态链接到这个程序中,然后再映射到用户虚拟地址空间中的共享区域内。

最后execve设置PC,使之指向代码区域的入口点。

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

缺页是指:虚拟内存中的字不在物理内存中,本质是就是DRAM缓存不命中。这时所进行的处理其实和cache不命中类似,缺页时,会导致界面出错,即缺页异常,这时缺页异常处理程序就会选择一个牺牲页,并换入虚拟内存所在的页。

之后缺页处理程序会回到导致缺页的指令处重新执行,CPU将引起缺页的虚拟地址重新发送给MMU,这时成功命中。

7.9动态存储分配管理

Printf会调用malloc,请简述动态内存管理的基本方法与策略。

动态内存分配器维护着一个进程的虚拟内存区域,称为堆,系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高地址)。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。

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

分配器有两种基本风格。两种风格都要求应用显示地分配块。他们的不同之处在于由哪个实体来负责释放已分配的块。

  1. 显示分配器:要求应用显示的释放任何已分配的块。

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

在hello程序中的malloc时一种显式分配器,程序通过调用malloc函数来从堆中分配块。

Malloc函数返回一个指针,指向大小至少为size字节的内存块,这个块会为可能包含在这个块内的任何数据对象类型做对齐。

我们可以将块组织成一个连续的已分配的块和空闲块的序列,如图所示

这种结构就称为隐式空间链表,因为空闲块时通过头部中的大小字段隐含地连接着的。

对于通用的分配器,一种更好的方法是将空闲块组织为某种形式的显示数据结构,在空闲块主体中放入指针。这样,我们可以快速定位到前、后的空闲链表,使首次适配的时间减少为空闲块的个数的线性时间。

7.10本章小结

本章中首先介绍了程序运行中的各种地址,之后分析了他们之间进行转换的一些过程。同时又介绍了存储管理中的三级cache下的物理内存访问广场,execve内存映射过程,缺页故障处理的操作,最后介绍了动态存储分配管理的基本方法和策略。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入输出都被当做对相应文件的读和写来执行。

设备管理:Linux内核有一个简单、低级的接口,称为Unix I/O,这使所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

接口:

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

2.Shell 创建的每个进程都有三个打开的文件:标准输入,标准输出,标 准错误。

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

4.读写文件:一个读操作就是从文件复制 n>0 个字节到内存,从当前文 件位置 k 开始,然后将 k 增加到 k+n,给定一个大小为 m 字节的而文 件,当 k>=m 时,触发 EOF。

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

5.关闭文件:当应用完成了对文件的访问之后,通知内核关闭这个文件。

函数:

1. int open(char *filename, int flags, mode_t mode);

Open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程当中没有打开的最小描述符。Flags参数指明了进程打算如何访问这个文件

2. int close(int fd);

调用close函数,通知内核结束访问一个文件,关闭打开的一个文件。

3. ssize_t read(int fd, void *buf, size_t n);

调用read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示错误,返回值0表示EOF,否则返回值表示的是实际传送的字节数量。

4. ssize_t write(int fd, const void *buf, size_t n);

从内存位置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;
    }

其中,函数体的…表示参数的个数不确定,接下来令一个字符指针arg指向我们输入printf的第一个参数,之后调用vsprintf函数。

之后的一条语句write的操作是把buf中的第i个元素的值写到终端,所以vsprintf的作用应该就是格式化,它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。并且返回字符串的长度。

Write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置,在这里就是将字符输出到标准输出,调用了字符显示驱动子程序

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

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

8.4 getchar的实现分析

getchar是读入函数的一种。它从标准输入里读取下一个字符,相当于getc(stdin)。返回类型为int型,为用户输入的ASCII码或EOF。

函数内容:

int getchar(void)

{

    static char buf[BUFSIZ];

    static char* bb=buf;

    static int n=0;

    if(n==0)

    {

        n=read(0,buf,BUFSIZ);

        bb=buf;

    }

    return(--n>=0)?(unsigned char)*bb++:EOF;

当程序调用 getchar 时,程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止(回车字符 \n 也放在缓冲区中),当用户键入回车之后,getchar() 函数才开始从输入缓冲区中每次读取一个字符。

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

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

8.5本章小结

本章首先介绍了Linux下的系统级IO的管理方法和接口的实现,之后对printf和getchar函数的实现进行了具体的分析。

结论

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

hello.c文件从源码到可执行文件并且执行、结束的全过程中,经历了一系列操作,首先进行预处理,指向#标记的预处理指令,之后编译,生成汇编文本文件,之后汇编将汇编文件翻译为机器语言生成可重定位目标程序,之后进行链接,将hello.o与库链接成可执行目标程序。

在执行时,首先在shell中fork出子进程,然后调用execve函数来运行hello程序,在这其中CPU为进程分配时间片,执行过程中,系统进行IO操作,并且和hello的输入输出相关联,hello接受到输入后也会执行相应的操作,如对于异常的响应等,最后hello进程被shell父进程回收,其有关的数据被内核删除。

了解hello的一生之后,我对计算机系统的总体运行框架和设计理念有了深刻的理解,虽然还有很多地方并不了解其具体实现但是却有了一个总体的把握,感觉对于之前所学习的计算机相关知识也与其串联在了一起,更加地有了程序设计、运行、调试的全局观,让我感到受益匪浅。

在计算机系统之中,各个流程都不可或缺,从将高级语言设计的程序变为可执行目标程序,再到将程序能够成功地在系统中运行、管理、退出,这些都是千千万计算机工程师穷尽其所学设计出的流程,虽然我现在的水平还不足以在这之上对任意一点做出改进、创新,但是我却从这些流程中学到了计算机的原理,感受到计算机的神奇,让我对自己的专业更加感兴趣,也体悟到计算机这个大系统中所蕴含的合作、分工、模块化的设计理念,相信这无论是在专业领域还是在社会生活上都能使我受益良多。

附件

hello.c 源代码

hello.i 预处理后文件

hello.s 编译后的汇编语言文件

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

hello 链接后的可执行文件

hello.elf :hello.o的ELF格式

hello1.txt :hello.o的反汇编

hello2.txt :hello的反汇编代码

hello1.elf :hello的ELF格式文件

参考文献

[1]  布赖恩特. 深入理解计算机系统[M]. 3. 机械工业出版社, 2016年.

[2]  蔡平胜, 闫乐林. 《计算机网络》课程中通信地址的讲解与探讨[J]. 中国科技信息, 2010(13):201-202.

[3] 君的名字. C语言 execve()函数使用方法[EB/OL]. 2016[]. https://blog.csdn.net/chichoxian/article/details/53486131.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值