哈工大计算机系统大作业 程序人生-Hello‘s P2P

摘  要

本文对hello程序的生命周期进行分析,从预处理开始对编译、汇编、链接、生成可执行文件并运行等一系列过程进行探讨,并且对进程管理、存储管理和I/O管理的全过程进行了分析。

关键词:预处理,编译,汇编,链接,程序运行,进程管理,存储管理,I/O管理                           

目  录

第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. 在IDE中编写代码,得到hello.c的源程序

2. 预处理器cpp生成hello.i

3. 编译器ccl将生成汇编程序hello.s

4. 汇编器as将hello.s生成可重定位目标文件hello.o

5. 链接器ld将hello.o和printf.o组合起来,生成可执行目标文件hello

6. 在shell中输入./hello,就可以fork一个新的子进程,调用execve函数将程序加载到新的子进程中,以此来完成P2P,即program to process

020:

  1. 在创建新的进程之后,使用execve将程序载入,然后进行虚拟内存映射。
  2. CPU为这个进程分配时间片,执行对应的逻辑控制流。
  3. 程序运行结束后,进程向父进程发送一个SIGCHLD信号,随后父进程会对该进程进行回收,最终释放相关内存,完成020

1.2 环境与工具

硬件环境:CPU:Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz   2.30 GHz

软件环境:Windows 10;VMware® Workstation 16 Pro; Ubuntu 20.04

开发与调试工具:codeblocks;Objdump

1.3 中间结果

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

中间结果:

hello.i        hello.c预处理得到的文本文件

hello.s       hello.i编译程序的汇编文件

hello.o       hello.s得到的可重定位目标文件

hello          链接得到的可执行目标文件

1.4 本章小结

本章介绍了hello的P2P,020过程,并且对环境与工具及过程中的中间文件做了分析

第2章 预处理

2.1 预处理的概念与作用

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

概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。

作用:使得程序便于阅读、修改、移植和调试,也有利于模块化程序设计

2.2在Ubuntu下预处理的命令

预处理命令:gcc -E hello.c -o hello.i

 

图1-预处理结果

2.3 Hello的预处理结果解析

如下图所示

图2-预处理产物内容

原来在.c文件中很少的几行在.i文件中变成3060行,因为预处理器在处理过程中实现了头文件的展开,宏的替换,并删除了注释信息。这就导致行数大大增加。而在最后才是我们原本的程序,与.c文件中的相差不大。

2.4 本章小结

本章主要介绍了预处理的概念与作用,使用预处理指令对目标文件进行处理并查看,验证了预处理中的过程,即头文件的展开、宏替换、去掉注释。

第3章 编译

3.1 编译的概念与作用

概念:编译过程以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出

作用:将预处理后的程序编译为更接近计算机语言,更容易让计算机理解的语言   

3.2 在Ubuntu下编译的命令

编译命令:gcc -S hello.i -o hello.s

图3-编译结果

3.3 Hello的编译结果解析

3.3.1 数据

      1.常量

              (1)字符串

 

图4-字符串

如图,存在两个常量,均是字符串,分别是“用法: Hello 学号 姓名 秒数!\n”与“Hello %s %s\n”,他们是printf函数的参数。

      2. 变量

      (1)局部变量i

 

图5-局部变量

程序在这里定义了一个局部变量i,它被保存在-4(%rbp)内。

      (2)数组变量

 

图6-数组变量

main函数的参数argc与argv,被存放在寄存器%edi和%esi中,接着他们被存放在-20(%rbp)和-32(%rbp)中。

3.3.2 赋值操作

      1. i=0

 

图7-赋值操作

在这里i的值被赋为0.

      2. i++

 

图8-赋值操作

在这里i的值随着每次循环会加上1

3.3.3 类型转换

c程序中,语句atoi(argv[3])使得字符型强制转换为整形。

3.3.4 算术操作

 

图9-算术操作

使用到的算术操作是i++,使用addl指令addl   $1, -4(%rbp)

3.3.5 关系操作

    1. argc!= 4

 

图10-关系操作

该语句被编译为cmpl    $4, -20(%rbp)。比较完成后会设置条件码,凭借条件码判断跳转位置。

      2. i<8

 

图11-关系操作

该语句被编译为cmpl    $7, -4(%rbp),其作用是作为for循环结束的条件,比较完成后会设置条件码,凭借条件码判断跳转位置。

3.3.6 数组/指针/结构操作

1. 数组存在一个argv[],是一个指针数组

3.3.7 控制转移

      1. if语句

      if(argc!=4),如果满足括号内的内容,就执行if内的语句,否则跳过if内的语句。

 

图12-if语句

      2. for循环

      在for循环中,首先给i赋值为0,然后跳转到L3中。

 

图13-for循环

      在L3内,会对i的值与8进行对比,如果满足i<8,就跳转到L4中,然后继续执行下面的指令。

 

图14-for循环

3.3.8 函数操作

1. main函数

main函数的参数是argc和*argv。argc存储在%edi中,argv存储在%rsi中。返回值存储在%eax中,如下图所示

 

 

图15-main函数

2. printf函数

在main函数中调用了两次printf函数,他们传入的参数各不相同。

第一个printf参数传入的是.LC0中的字符串

 

图16-printf函数

第二个printf传入的是.LC1处的字符串,argv[1],argv[2]的值

 

图17-printf函数

前面几行是在从-32(%rbp)中取argv[1],argv[2]的值,41行是传入字符串。

3.exit函数

该函数实现从main函数退出。传入的参数是1,表示非正常退出。

 

图18-exit函数

4. atoi函数

该函数实现将字符串类型转变成int类型,传入的参数为argv[3],前面几行在取argv[3]的值。

 

图19-atoi函数

5. sleep函数

该函数实现程序休眠,传入的参数为atoi(argv[3]),具体内容同上。

 

图20-sleep函数

6. getchar函数

getchar函数实现读取缓冲区字符,直接调用即可。call       getchar@PLT

3.4 本章小结

本章解释了编译的原理与作用,并对编译出来的结果进行逐条分析,展示了编译语句的代码,对各部分编译结果有了更深一层的认识。

第4章 汇编

4.1 汇编的概念与作用

概念:把汇编语言翻译成机器语言的过程称为汇编

作用:过程将汇编代码转换为计算机能够理解并执行的二进制机器代码。

4.2 在Ubuntu下汇编的命令

汇编命令:gcc hello.s -c -o hello.o

 

图21-汇编结果

4.3 可重定位目标elf格式

4.3.1 ELF头

命令如下:readelf -h hello.o

 

图22-ELF头

开头是一个16字节的magic序列,描述了系统的字的大小和字节顺序。剩下包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

4.3.2 节头

命令如下:readelf -S hello.o

 

图23-节头

节头部分记录了各节的名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等信息。使用节头表中的字节偏移信息可以得到各节在文件中的起始位置,以及各节所占空间的大小,这样方便重定位。

4.3.3 重定位节

命令如下:readelf -r hello.o

 

图24-重定位节

重定位节中包含了.text 节中需要进行重定位的信息,我们可以发现需要重定位的函数有: .rodata, puts, exits, printf, atoi, sleep, getchar

4.4.4 符号表

命令如下:readelf -s hello.o

 

图25-符号表

符号表存放了程序中定义和引用的函数和全局变量的信息

4.4 Hello.o的结果解析

hello.o的反汇编代码如下:

 

图26-反汇编代码

反汇编代码中,除了.s文件中已经出现过的代码,还包含了它们对应的机器语言的代码。

比如说分支转移结构中,hello.s表示为

 

图27-分支转移结构

而在hello.o中表示为

 

图28-分支转移结构

这是因为.s文件中可以用段名称L3来进行助记,而在.o文件中需要它的真实地址以便于下一步操作。

而在函数调用方面,.s文件在call后可直接跟上函数名称,如call  printf@PLT,但是.o文件call 后跟的是一条重定位条目指引的信息,如callq  62 <main+0x62>

4.5 本章小结

本章介绍了汇编的概念和作用,讲述了可重定位文件的ELF头,节头,重定位节和符号表的内容。同时比较了反汇编结果与.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

 

图29-链接结果

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

5.3.1 ELF头

hello与hello.o的ELF头大致相同,不同之处在于hello的类型为EXEC可执行文件,有27个节

 

图30-ELF头

5.3.2 节头

与.o的节头不同的是,在这里每一节都有了实际地址,说明重定向已经完成。

 

图31-节头

5.3.3 重定位节

 

图32-重定位节

5.3.4 符号表

 

图33-符号表

5.4 hello的虚拟地址空间

从5.3的程序头来看,LOAD可加载的程序段第一段的地址为0x400000,那么虚拟内存地址从由0x401000开始,我们从Data Dump里面看,最后得到0x401ff0结束

 

 

图34- hello的虚拟地址空间

5.5 链接的重定位过程分析

使用的命令:objdump -d -r hello > hello2.txt

我们比较hello1和hello2之后可得出以下不同:

  1. hello1的反汇编代码的地址从0开始,而hello2的反汇编代码从0x400000开始。这说明hello1还未实现重定位,采用的是相对偏移地址,而hello2已经实现了重定位,采用的是虚拟地址。

 

图35-hello1反汇编代码

 

图36-hello2反汇编代码

  1. hello2中多了很多别的函数的汇编代码,比如说.init节

 

图37-hello2反汇编代码

重定位的过程:

  1. 重定位节和符号定义链接器将所有类型一致的节合并,赋予新的地址,那么程序中每条指令和全局变量都有唯一运行时的地址
  2. 如果遇见未知目标的引用,那么就新生成一个重定位条目。

5.6 hello的执行流程

      名称                      地址

ld-2.31.so!_dl_start             0x7f8e7cc34ed0

ld-2.31.so!_dl_init              0x7f8e7cc486a0

libc-2.31.so!_libc_start_main    0x7ff 825425fc0

hello!_start                      0x4010f0

libc-2.31.so!_cxa_atexit         0x7ff 825448f60

hello!_libc_csu_int                0x4011c0

libc-2.31.so!_setjmp            0x7ff 82fdb2e00

libc-2.27.so!exit                 0x7ff 82fdc3bd0

5.7 Hello的动态链接分析

动态链接器使用过程链接表PLT和全局偏移量表GOT实现函数的动态链接。查阅节头可以得知,.got.plt起始位置为0x404000

在调用之后该处的信息发生了变化

 

图38-调用前Data Dump

 

图39-调用后Data Dump

5.8 本章小结

本章介绍了链接的概念及作用,展示了hello的elf,分析了虚拟地址,分析了hello的重定位过程、执行流程、动态链接过程。

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

6章 hello进程管理

6.1 进程的概念与作用

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

作用:向用户提供了一种假象。 我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。

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

作用:Shell是一个交互型应用级程序,为使用者提供操作界面,接收命令,然后调用相应的程序。

处理流程:

1.终端进程读取命令行。

2.分析命令行,获取命令行参数,并构造传递给execve的argv

3.检查第一个命令行参数是否是一个内置的shell命令,如果不是内部命令,调用fork( )创建新进程/子进程

4.在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

5.如果用户没要求后台运行否则shell使用waitpid等待作业终止后返回。

6.如果用户要求后台运行,则shell返回。

6.3 Hello的fork进程创建过程

当在shell上输入./hello命令时,命令行会首先判断是否为内置命令,如果是内置命令则立即对其进行解释。否则会调用fork创建一个新进程并在其中执行。

6.4 Hello的execve过程

1. 删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。

2. 映射私有区域。创建新的代码、数据、堆和栈段。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。

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

4. 设置程序计数器(PC)。

6.5 Hello的进程执行

hello的进程执行过程如下:

上下文信息:内核在一个进程正在运行的时候,调度了另一个新的进程运行后,它就抢占当前进程,并使用上下文切换来控制转移到新的进程。我们需要保存以前进程的上下文,恢复新恢复进程被保存的上下文,将控制传递给这个新恢复的进程来完成上下文切换。

进程时间片:进程执行它的控制流的一部分的每一时间段。

进程调度的过程:在进程执行时,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种行为就叫做调度。

用户态与核心态转换:进程只有故障、中断或陷入系统调用时才会得到内核访问权限,否则将一直处于用户权限之中,以保证系统的安全性。

6.6 hello的异常与信号处理

出现的异常有:

 

图40-异常类型与处理

6.6.1 正常情况

 

图41-程序正常运行

6.6.2 不停乱按

1. 不包括回车

只会在屏幕上显示输入的内容

 

图42-运行过程中乱按

2. 回车

getchar读回车,回车前的字符串当作shell输入的命令

 

图43-运行过程中按回车

2.Ctrl-Z,Ctrl-C

Ctrl-Z如下图:使用SIGTSTP信号,停止前台作业

 

图44-运行过程中按ctrl-z

Ctrl-C如下图:使用SIGINT信号终止前台进程。

 

图45-运行过程中按ctrl-c

        6.6.3 Ctrl-z后运行ps jobs pstree fg kill 等命令

 

 

图46- Ctrl-z后运行ps jobs pstree fg kill 等命令

6.7本章小结

本章了解了hello进程的执行过程,展示了hello进程的执行以及hello的异常和信号处理。                 

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:程序经过编译后出现在汇编代码中的地址,用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量。

线性地址:逻辑地址向物理地址转化过程中的一步

虚拟地址:就是线性地址

物理地址:CPU外部地址总线上的地址信号,是地址变换的最终结果地址

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

逻辑地址表示为[段标识符:段内偏移量],段标识符是一个16位长的字段(段选择符)。在获得了一个逻辑地址之后,看段选择符的T1=0/1,明确待转换的段在GDT中,还是在LDT中,就可以根据相应寄存器,得到其地址和大小。

拿出段选择符中前13位,在这个数组中,查找到对应的段描述符,就可以得到Base,把Base + offset,就可以获得需要的线性地址。

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

虚拟地址用VA来表示。处理虚拟地址即处理线性地址。VA分为虚拟页号(VPN)与虚拟页偏移量(VPO),CPU取出VPN,通过页表基址寄存器来定位页表条目,在有效位为1时,从页表条目中取出信息物理页号(PPN),通过将物理页号与虚拟页偏移量(VPO)结合,得到由物理地址(PPN)和物理页偏移量(PPO)组合的物理地址。

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

每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。

多级页表:将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。VPN被分为k个部分,第一级VPN结合基址寄存器得到一个页表条目,其中存放下一级页表的基址,再结合VPN2,得到第三级页表基址,继续寻找,以此类推,直到最后确定对应的物理页号,如下图所示:

 

图47-多级页表

这样的话,在多级页表的情况下,不断通过索引 – 地址 – 索引 - 地址重复进行寻找就行了。

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

得到物理地址后,根据物理地址从cache中寻找。到了L1后,寻找物理地址检测是否命中,不命中则紧接着寻找下一级cache L2,接着L3,如果L3也不命中,则需要从内存中将对应的块取出放入cache中,直到出现命中。

7.6 hello进程fork时的内存映射

在fork被调用来创建子进程后,会为hello程序的运行创建各种数据结构,并分配一个与父进程不同的PID。同时为这个新进程创建虚拟内存,创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

    当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。

7.7 hello进程execve时的内存映射

在6.4节中已经给出,如下:

1. 删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。

2. 映射私有区域。创建新的代码、数据、堆和栈段。虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区。bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。

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

4. 设置程序计数器(PC)。

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

缺页故障是指:当指令引用一个相应的虚拟地址,而与改地址相应的物理页面不再内存中,会触发缺页故障。

缺页中断处理:

1)处理器生成一个虚拟地址,并将它传送给MMU

 2)MMU生成PTE地址,并从高速缓存/主存请求得到它

3)高速缓存/主存向MMU返回PTE

4)PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。

5)缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。

6)缺页处理程序页面调入新的页面,并更新内存中的PTE。

7)缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同的大小的块的集合来维护。每个块就是一个连续的虚拟内存片,这些内存片要么是已分配的,要么是空闲的。分配器有两种基本风格。1.显式分配器:要求应用显示的释放任何已分配的块。2.隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,那么就释放这个块。

隐式空闲链表:

每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的,最后一位指明这个块是已分配的还是空闲的。具体的隐式空闲链表形式如下:

 

图48-隐式空闲链表

应用请求一个k字节的块时,分配器搜索空闲链表。查找一个足够大可以放置所请求的空闲块,找到一个匹配的空闲块时,通常将空闲块分割为两部分。第一部分变为了已分配块,第二部分变为了空闲块

显示空闲链表:

显示空闲链表是将空闲块组织为某种形式的显示数据结构。在每个空闲块中,都包含一个前驱和后继的指针。

 

图49-显式空闲链表

7.10本章小结

本章主要有关内存管理。介绍了hello程序的存储空间,如何得到最终的地址,介绍了hello的四级页表的虚拟地址空间到物理地址的转换,三级cashe的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理等内容。

8章 helo的IO管理

8.1 Linux的IO设备管理方法

所有的I/O设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

Unix IO接口:

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

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

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

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

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

Unix I/O函数:

1.int open(char* filename,int flags,mode_t mode) ,进程通过调用 open 函 数来打开一个存在的文件或是创建一个新文件的。open函数将filename 转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags 参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。

2.int close(fd),fd 是需要关闭的文件的描述符,close 返回操作结果。

3.ssize_t read(int fd,void *buf,size_t n),read 函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置 buf。返回值-1 表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。

4.ssize_t wirte(int fd,const void *buf,size_t n),write 函数从内存位置 buf 复制至多 n个字节到描述符为 fd的当前文件位置。

8.3 printf的实现分析

printf函数如下:

 

图50-printf函数

printf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。而vsprintf函数作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

write函数将buf中的i个元素写到终端。

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

字符显示驱动子程序:从ASCII到字模库到显示vram。

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

8.4 getchar的实现分析

getchar函数如下:

 

图51-getchar函数

当程序调用getchar时,程序等待用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车(回车也在缓冲区中)。

当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。

8.5本章小结

本章主要介绍了Linux的IO设备管理方法、Unix IO接口及其函数,分析了printf和getchar函数的实现。

结论

hello的一生:

1. hello.c预处理到hello.i文本文件;

2. hello.i编译到hello.s汇编文件;

3. hello.s汇编到二进制可重定位目标文件hello.o;

4. hello.o链接生成可执行文件hello;

5.在终端里输入“./hello 7203610128 李昌昊 1”,判断输入命令是否为内置命令。经过检查后发现其不是内置命令,则shell将其当作程序执行

6. bash进程调用fork,生成子进程;

7. execve函数加载运行当前进程的上下文中加载并运行新程序

8.运行hello时,内存管理单元MMU、翻译后备缓冲器TLB、多级页表机制、三级cache协同工作,完成对地址的翻译和请求。

9. 调用sleep函数后进程休眠进入停止状态,调用完成后,内核再次进行上下文切换重新执行hello进程。

10.hello的输入输出与外界交互,与linux I/O息息相关;

11.hello最终被shell父进程回收,内核会收回为其创建的所有信息。

附件

中间产物如图所示:

 

hello.c:源程序

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

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

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

hello1.txt:hello.o得到的反汇编文件

hello2.txt:hello得到的反汇编文件

hello:链接后的可执行目标文件

参考文献

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值