HIT 深入理解计算机系统CSAPP 大作业 --程序人生 Hello’s P2P

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业       航天               

学     号     7203610313           

班   级    2036017               

学       生      张元瑞            

指 导 教 师      史先俊               

计算机科学与技术学院

2022年5月

摘  要

本文主要研究了hello程序的执行过程,从处理、编译、汇编、链接、流程管理、内存管理和I/O管理几个方面分析Hello过程中出现的各种现象,理解计算机的各种行为。通过对hello程序实现过程的分析,我们更好地了解了计算机的底层实现,并且更深入地说明了整个程序的生命周期。

关键词:hello执行过程、编译、汇编、链接、底层实现                           

目  录

第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:源程序hello.c经预处理器(cpp)生成修改了的源程序hello.i,hello.i经编译器(ccl)生成汇编程序hello.s。Hello.s经汇编器(as)生成可重定位目标程序hello.o。Hello.o经链接器(ld)生成可执行目标程序hello。系统在执行可执行目标程序时会调用fork函数创建子进程。此为由程序到进程的P2P过程。

020:shell执行可执行目标文件,使用execve函数加载进程,映射虚拟内存,进入程序入口后载入物理内存,然后进入main函数执行程序,执行完后shell父进程回收shell进程,内核删除hello相关数据。此外020过程。

1.2 环境与工具

软件环境:Windows10 64位、VMware workstation、Ubuntu 20.04

硬件环境:Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz、2GHz、2G RAM

调试工具:gcc、edb

1.3 中间结果

hello.i:修改了的源程序;

hello.s:汇编程序;

hello.o:可重定位目标程序

hello:可执行目标程序

hello.out:hello反汇编之后的可重定位文件

1.4 本章小结

本章主要介绍了hello的P2P和020过程,实验中用到的环境与工具,执行hello程序产生的中间结果。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

概念:预处理在编译之前对源文件进行简单处理。处理是以#号开头的代码行,在编译器进行编译之前对源代码做某些转换。

作用:宏定义、文件包含、条件编译。

2.2在Ubuntu下预处理的命令

命令:gcc hello.c -E -o hello.i

图2.1   预处理命令

预处理后生成hello.i文件

2.3 Hello的预处理结果解析

图2.2   hello.i文件

Hello.i文件有3000多行,比hello.c文件大了许多。其中main函数在文件最后部分,main函数前出现的是stdio.h unistd.h stdlib.h头文件。程序中还有extern和typedef函数。

2.4 本章小结

本章主要介绍了预处理的概念和作用,并对hello.c文件预处理所生成的hello.i文件进行解析,分析预处理的相关作用。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

概念:编译是利用编译程序将修改了的源程序生成目标程序的过程。主要包括五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。

作用:把高级语言变成更接近机器语言的汇编语言,并可以进行代码优化。

主要过程如图。

图3.1   编译过程

3.2 在Ubuntu下编译的命令

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

图3.2   Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1 编译文件指令

.file        源文件

.text        代码段

.section  .rodata   只读代码段

.align   对齐格式   

.string 字符串类型数据

.globl 全局变量

.type 符号类型

.long 长整型数据

.size 数据空间大小

图3.3   编译文件指令

3.3.2 数据

1.常量

(1).字符串

原程序中字符串printf("用法: Hello 学号 姓名 秒数!\n"),在hello.s文件中变为

图3.4   hello.s中字符串

  1. .整形常量

原程序中整形常量有argc!=4、i<8。在hello.s文件中为立即数。

图3.5   hello.s中整形常量

  1. 变量

(1).局部变量i

mian函数中的局部变量i在栈上-4(%rbp)的位置

图3.5   局部变量

3.3.3 操作

1. 赋值操作

原程序中i = 0,在hello.s中赋值操作为

图3.6   hello.s中赋值操作

2.算数操作

原程序总i++为算数操作,hello.s中为addl操作

图3.7   hello.s中算数操作

3.关系操作

原程序中argc!=4和i<8,hello.s中为cmpl操作

图3.8   hello.s中关系操作

4.控制转移

原程序中if语句判断argc!=4,hello.s根据关系操作的条件码判断控制转移

图3.9   hello.s中控制转移操作1

对i<8的判断和控制转移的过程与之类似

图3.10   hello.s中控制转移操作2

5.类型转换

原程序中atoi(argv[3])为类型转换,将字符串转换成整形数

6.数组/指针/结构操作

字符指针数组 char *argv[]:存储用户输入的命令行信息地址,两次rax分布取出了argv[1]和argv[2]的值。

图3.12   hello.s中数组/指针/结构操作

7.函数操作

main函数、printf函数、exit函数、sleep函数、getchar函数、atoi函数。

图3.13   hello.s中main函数

图3.14   hello.s中printf函数

图3.15   hello.s中exit函数

图3.16   hello.s中sleep函数

图3.17   hello.s中getchar函数

图3.18   hello.s中atoi函数

3.4 本章小结

本章主要介绍了编译的概念和作用,分析了C语言中数据和操作编译生成的hello.s文件中的实现。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

概念:汇编是指将汇编语言翻译成机器语言的过程。

作用:将高级语言转化为机器可直接识别执行的代码文件,将这些指令打包成一种叫做可伸缩目标程序的格式,并将结果保存到二进制目标文件hello.o。

4.2 在Ubuntu下汇编的命令

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

图4.1   汇编的命令

4.3 可重定位目标elf格式

命令:readelf -a hello.o >elf.txt可得到elf.txt文件。

  1. ELF Header

ELF头包括一个16字节的序列、ELF Header的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

图4.2   ELF Header

2.Section Headers

从ELF Header可以看出每个Section Header大小为64bytes,一共有14个。

节头部表描述各节的类型、位置、大小等信息。

图4.3   Section Headers

3.重定位节

重定位节说明如何修改文件中的其他节。这8条重定位信息分别是对.第一个只读代码段、puts函数、exit函数、第二个只读代码段、printf 函数、atoi函数、sleep函数、getchar函数进行重定位声明。

图4.4   重定位节

4.符号表

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

图4.5   符号表

4.4 Hello.o的结果解析

用命令objdump -d -r hello.o得到hello.o的反汇编码。

图4.6、4.7   hello.o的反汇编

hello.s与hello.o的反汇编基本相同,主要区别有:

  1. 反汇编中包括机器语言,hello.s中不包括。
  2. 反汇编的操作数为16进制,hello.s中操作数为10进制。
  3. 调用函数时,反汇编中call指令后接地址,hello.s中接函数名。
  4. 控制转移,反汇编中接地址,hello.s中接段名。

4.5 本章小结

第四章主要介绍了汇编的概念和作用,并将hello.o反汇编与hello.s对照分析,对二者的理解更深刻。

(第4章1分)


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的格式

使用命令:readelf -a hello > hello.elf生成hello.elf文件。

  1. ELF Header

ELF Header中Type为EXEC表示hello为一个可执行文件。Hello中有27个节,每个节头大小为64bytes。

图5.2   hello的ELF Header

  1. Section Headers节头表

节头包含各节的名称、类型、地址、偏移量等信息。

图5.3、5.4   hello的Section Headers

5.4 hello的虚拟地址空间

用edb加载hello可以看到hello的虚拟地址从0x401000开始到0x401240结束,与5.3中程序开始的地址一致。

图5.5、5.6   hello的虚拟地址

    使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。   

5.5 链接的重定位过程分析

objdump -d -r hello 得到hello的反汇编,与hello.o的反汇编相比,有以下不同:

  1. 多了.init、.plt、.plt.sec、.fini等节。
  2. 在hello的.plt.sec节中链接加入了put、printf、getchar、atoi、exit、sleep等函数。
  3. hello中的函数调用是虚拟内存地址。

对于hello.o中的反汇编码,进行了重定位,因此在链接之后才能执行。

图5.7   .plt.sec节中的函数

图5.8   hello反汇编的main函数

5.6 hello的执行流程

子程序名:

ld-2.31.so!_dl_start

ld-2.31.so!_dl_init

hello!_start

libc-2.31.so!_libc_start_main

hello!printf@plt

hello!sleep@plt

hello!getchar@plt

libc-2.31.so!exit

5.7 Hello的动态链接分析

dl_init前GOT表为

图5.9  dl_init前GOT表

dl_init后GOT表为

图5.10  dl_init后GOT表

5.8 本章小结

本章介绍了链接的概念和作用,分析了hello的虚拟地址空间,执行流程和动态链接。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

概念:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。

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

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

作用:在计算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(command interpreter,命令解析器)。它接收用户命令,然后调用相应的应用程序。

处理流程:1.从终端读入命令。2.将输入字符串分解获得。3。对分解的小块添加注释。4.把所有从处理的结果中用到的注释删除,并按照下面的顺序实行命令的检查:A.内建的命令B. shell函数(由用户自己定义的)C.可执行的脚本文件(需要寻找文件和PATH路径)。5.执行命令。

6.3 Hello的fork进程创建过程

在终端输入./hello后,终端会对输入的命令进行解析,因为hello不是内置的shell,命令解析后判断./hello的意思为执行可执行文件hello,之后终端调用fork函数创建一个新的子进程,子进程与父进程用户级虚拟地址空间相同。在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。

图6.1   fork进程图

6.4 Hello的execve过程

execve用于加载并运行可执行程序hello。execve函数在当前进程中删除现有用户区域,生成现有用户区域并执行 hello.out,新的代码和数据段被初始化为可执行文件中的内容。

6.5 Hello的进程执行

进程上下文:当一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。

进程时间片:进程运行其控制流的各时间段叫进程时间片。

进程调度:在多道程序环境下,主存中有着多个进程,其数目往往多于处理机数目。这就要求系统能按某种算法,动态地把处理机分配给就绪队列中的一个进程,使之执行,这一过程称为调度。调度的实质是一种资源分配。调度过程是指处理机根据调度策略从就绪队列中选择一个进程运行的过程。

用户态与核心态的转换:发生中断、故障、或者陷入系统调用这样的异常时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用用户模式。

图6.2   hello正常执行

6.6 hello的异常与信号处理

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

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

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

  1. hello执行过程中会出现的异常:

中断    来自I/O设备的信号

陷阱    有意的异常

故障    潜在可恢复的错误

终止    不可恢复的错误

2.程序执行过程中按键盘

(1).不停乱按

执行过程中不停乱按,不会导致异常和进程终止。

图6.3   执行过程中不停乱按

(2).回车

执行过程中按回车,会在hello程序执行结束后执行回车命令。

图6.4   执行过程中按回车

  1. .Ctrl-Z

执行过程中按Ctrl-Z程序停止,用ps指令可以观察到进程没有结束,之后可用fg恢复运行。

图6.5   执行过程中按Ctrl-Z

按Ctrl-Z后用jobs,可以观察到进程号为1的hello停止

图6.6   进程号为1的hello停止

(4).Ctrl-C

执行过程中按Ctrl-C,会使程序终止。

图6.7   执行过程中按Ctrl-C

6.7本章小结

本章介绍了进程的概念与作用,壳Shell-bash的作用与处理流程,Hello的fork进程创建过程和execve过程,以及hello的进程执行,hello的异常与信号处理。

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

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

线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。

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

物理地址:物理地址又叫实际地址或绝对地址,是字节在储存器中储存的实际存储器地址。

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

段寄存器为16位,用于存放段选择符,包括CS(代码段):程序代码所在段、SS(栈段):栈区所在段、DS(数据段):全局静态数据区所在段其他3个段寄存器ES、GS和FS可指向任意数据段。

段描述符是一种数据结构,实际上就是段表项,分两类:用户的代码段和数据段描述符、系统控制段描述符。

图7.1   段描述符的定义

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

图7.2   逻辑地址向线性地址的变换

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

页式管理是指将各进程的虚拟空间划分成若干个长度相等的页(page),把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

MMU利用页表进行由虚拟地址到物理地址的映射。CPU中的一个控制寄存器,页表基址寄存器指向当前页表。n位虚拟地址包含两部分,一个p位的虚拟页面偏移(VPO)和一个(n-p)位的虚拟页号(VPN),MMU利用VPN来选择适当的PTE,例如,VPN 0选择PTE 0,VPN 1选择PTE 1,以此类推。将页表中的物理页号PPN与虚拟地址中的VPO串联起来,就得到相应的物理地址。

图7.3   使用页表的地址翻译

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

TLB是一个小的、虚拟地址的缓存,其中每一行都保存着一个由单个PTE组成的块。

TLB命中时(通常情况)的步骤为

  1. CPU产生一个虚拟地址。
  2. and3.  MMU从TLB中取出相应的PTE。

4.MMU将中国虚拟地址翻译成一个物理地址,并将它发送到高速缓存/主存。

5.高速缓存/主存将所请求的数据字返回给CPU。

TLB不命中时,MMU必须从L1缓存中取出相应的PTE,新取出的PTE存放在TLB中,可能会覆盖一个已经存在的条目。

图7.4   TLB命中

图7.5   TLB不命中

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

Core i7采用四级页表层次结构。每个进程有它自己私有的页表层次结构。当一个Linux进程在运行时,虽然Core i7体系结构允许页表换进换出,但是与已分配了的页相关联的页表都是驻留在内存中的。

图7.8给出了Core i7 MMU如何使用四级的页表来将虚拟地址翻译成物理地址。36位VPN被划分成四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN1提供到一个L1 PET的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2 PTE的偏移量,以此类推。

图7.6   Core i7的内存系统

图7.7   Core i7的地址翻译

图7.8   Core i7的页表翻译

7.6 hello进程fork时的内存映射

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

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

7.7 hello进程execve时的内存映射

当前进程中的程序如下调用execve函数时

execve(“hello.out”,NULL,NULL);

加载并运行hello.out需要以下几个步骤:

  1. 删除已存在的用户区域。
  2. 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。
  3. 映射共享区域。
  4. 设置程序计数器(PC)。execve做的最后一件事就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

下一次调度这个进程时,它将从这个入口点开始执行。

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

缺页是指虚拟内存中的字不在物理内存中 (DRAM 缓存不命中)

CPU引用了VP3中的一个字,VP3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE3,从有效位推断出VP3未被缓存,并触发缺页异常。缺页异常处理程序选择一个牺牲页,此例中为VP4。接下来,内核从磁盘复制VP3到内存中的PP3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但现在VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。

图7.9   页面调度

7.9动态存储分配管理

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

C语言中主要用malloc、free、calloc函数进行动态内存管理。

1.malloc是C语言提供的一个动态内存开辟的函数:

void* malloc(size_t size); 

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。如果开辟成功,则返回一个指向开辟好空间的指针;如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要检查。

2.free是C语言中专门来做动态内存的释放和回收的函数:

void free(void*ptr);

注意:如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的;如果参数ptr是NULL指针,则函数什么事都不做。

3.calloc是C语言中另一个动态内存分配函数:

void* calloc(size_t num,size_t,size);

calloc与malloc的区别为calloc会在返回地址之前把申请的空间的每个字节初始化为全0。

7.10本章小结

本章介绍了Hello从存储器地址空间、段式管理、页式管理、VA(虚拟地址)到PA(物理地址)的转换、三级cache支持下的物理内存访问、hello进程fork与execve时的内存映射、缺页故障的处理方法及动态存储分配管理。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

Linux下所有的I/O设备(例如网络、磁盘、和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备映射为文件的方法,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

1.打开文件

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

函数:open();

2.改变当前文件的位置

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

函数:lseek();

3.读写文件

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

函数:read();

   write();

4.关闭文件

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

函数:close();

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;
    }

其中调用了vsprintf函数,vsprintf函数为:

int vsprintf(char *buf, const char *fmt, va_list args)

   {

    char* p;

    char tmp[256];

    va_list p_next_arg = args;

   

    for (p=buf;*fmt;fmt++) {

    if (*fmt != '%') {

    *p++ = *fmt;

    continue;

    }

   

    fmt++;

   

    switch (*fmt) {

    case 'x':

    itoa(tmp, *((int*)p_next_arg));

    strcpy(p, tmp);

    p_next_arg += 4;

    p += strlen(tmp);

    break;

    case 's':

    break;

    default:

    break;

    }

    }

   

    return (p - buf);

   }

可以看出vsprintf函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。

printf函数中还调用了write(buf, i)函数:

write:
     mov eax, _NR_write
     mov ebx, [esp + 4]
     mov ecx, [esp + 8]
     int INT_VECTOR_SYS_CALL

Write中sys_call的实现为:

  sys_call:
     call save
   
     push dword [p_proc_ready]
   
     sti
   
     push ecx
     push ebx
     call [sys_call_table + eax * 4]
     add esp, 4 * 3
   
     mov [esi + EAXREG - P_STACKBASE], eax
   
     cli
   
     ret

sys_call函数的功能就是不断的打印出字符,直到遇到:'\0'

于是可知write的作用为将buf中的i个元素写到终端。

综上可得printf的实现过程:

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

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

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

8.4 getchar的实现分析

Getchar()函数为

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等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

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

8.5本章小结

本章介绍了Linux的IO设备管理办法、Unix接口及其函数、并分析了printf函数和getchar函数的实现过程。

(第8章1分)

结论

  1. 源程序hello.c经预处理器(cpp)生成修改了的源程序hello.i。
  2. hello.i经编译器(ccl)编译生成汇编程序hello.s。
  3. Hello.s经汇编器(as)汇编生成可重定位目标程序hello.o。

4.Hello.o经链接器(ld)链接生成可执行目标程序hello。

5.Shell在处理命令行时调用fork函数创建子进程。

6.通过exceve加载并运行hello。

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

学习计算机系统使我对计算机有了更深刻的认识,简单的hello程序的运行需要硬件、软件、操作系统相互配合。在分析了hello程序的实现过程之后,我对程序的底层实现更加了解。


附件

hello.c   源程序

hello.i   预处理后文本文件

hello.s   汇编程序

hello.o    可重定位目标程序

hello       可执行目标程序

elf.txt   hello.o的ELF

hello.txt   hello的ELF


参考文献

[1]  深入理解计算机系统

[2]  百度百科——编译

[3]  百度百科——汇编

[4]  百度百科——链接

[5]  百度百科——虚拟地址

[6]  百度百科——物理地址

[7] 百度百科——线性地址

[8] 百度百科——段式管理

[9] 百度百科——页式管理

(参考文献0分,缺失 -1分)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值