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

 

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业  信息安全                     

学     号  2021111602                     

班     级  2103201                     

学       生  刘宇昂                   

指 导 教 师  刘宏伟                     

计算机科学与技术学院

2023年5月

摘  要

    本篇论文介绍了hello.c程序从预处理、编译到汇编、链接的过程,其中涉及到进程管理、存储管理、I/O管理等诸多问题,完整地展示了hello.c程序从生成到被回收的全部过程。本论文立足于hello.c程序,旨在见微知著,展现计算机对程序进行处理的全部过程。

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

目  录

第1章 概述............................................................................................................. - 4 -

1.1 Hello简介...................................................................................................... - 4 -

1.2 环境与工具..................................................................................................... - 4 -

1.3 中间结果......................................................................................................... - 5 -

1.4 本章小结......................................................................................................... - 5 -

第2章 预处理......................................................................................................... - 6 -

2.1 预处理的概念与作用..................................................................................... - 6 -

2.2在Ubuntu下预处理的命令.......................................................................... - 6 -

2.3 Hello的预处理结果解析.............................................................................. - 6 -

2.4 本章小结......................................................................................................... - 7 -

第3章 编译............................................................................................................. - 8 -

3.1 编译的概念与作用......................................................................................... - 8 -

3.2 在Ubuntu下编译的命令............................................................................. - 8 -

3.3 Hello的编译结果解析.................................................................................. - 8 -

3.4 本章小结....................................................................................................... - 10 -

第4章 汇编........................................................................................................... - 11 -

4.1 汇编的概念与作用....................................................................................... - 11 -

4.2 在Ubuntu下汇编的命令........................................................................... - 11 -

4.3 可重定位目标elf格式............................................................................... - 11 -

4.4 Hello.o的结果解析.................................................................................... - 13 -

4.5 本章小结....................................................................................................... -14 -

第5章 链接........................................................................................................... - 15 -

5.1 链接的概念与作用....................................................................................... - 15 -

5.2 在Ubuntu下链接的命令........................................................................... - 15 -

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

5.4 hello的虚拟地址空间................................................................................ - 18 -

5.5 链接的重定位过程分析............................................................................... - 18 -

5.6 hello的执行流程........................................................................................ - 19 -

5.7 Hello的动态链接分析................................................................................ - 19 -

5.8 本章小结....................................................................................................... - 20 -

第6章 hello进程管理................................................................................... - 21 -

6.1 进程的概念与作用....................................................................................... - 21 -

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

6.3 Hello的fork进程创建过程..................................................................... - 21 -

6.4 Hello的execve过程................................................................................. - 22 -

6.5 Hello的进程执行........................................................................................ - 22 -

6.6 hello的异常与信号处理............................................................................ - 22 -

6.7本章小结....................................................................................................... - 25 -

第7章 hello的存储管理............................................................................... - 26 -

7.1 hello的存储器地址空间............................................................................ - 26 -

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

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

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

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

7.6 hello进程fork时的内存映射.................................................................. - 28 -

7.7 hello进程execve时的内存映射.............................................................. - 28 -

7.8 缺页故障与缺页中断处理........................................................................... - 28 -

7.9动态存储分配管理....................................................................................... - 29 -

7.10本章小结..................................................................................................... - 30 -

第8章 hello的IO管理................................................................................. - 31 -

8.1 Linux的IO设备管理方法.......................................................................... - 31 -

8.2 简述Unix IO接口及其函数....................................................................... - 31 -

8.3 printf的实现分析........................................................................................ - 32 -

8.4 getchar的实现分析.................................................................................... - 33 -

8.5本章小结....................................................................................................... - 34 -

结论......................................................................................................................... - 35 -

附件......................................................................................................................... - 36 -

参考文献................................................................................................................. - 37 -

第1章 概述

1.1 Hello简介

P2P(From Program to Process)过程:

Hello.c的P2P过程大体可分为四个阶段:预处理阶段、编译阶段、汇编阶段、链接阶段。预处理阶段通过预处理器,把源程序处理为修改之后的源程序hello.i;编译阶段通过编译器编译,将hello.i程序编译为汇编程序hello.s;汇编阶段则使用汇编器生成重定位目标文件hello.o;最后在链接阶段经由连接器与printf.o文件链接,生成可执行文件hello。系统会为它创建一个新进程,实现由程序到进程的转化。

 

图1-1-1 hello.c流程

O2O(From Zero-0 to Zero-0)过程:

在运行过程中,系统通过shell调用execve函数,将hello程序加载到当前进程的上下文中,为程序分配物理内存,进行虚拟内存的映射。当程序运行结束后,shell会作为父进程负责将其回收,删除程序相关数据,释放物理内存。

1.2 环境与工具

(1)硬件信息

 

                                                       图1-2-1 硬件配置

(2)软件环境

Windows 11 64位;VMware Workstation Pro;Ubuntu 20.04

(3)使用工具

gdb;edb;readelf;objdump;Code::Blocks20.03

1.3 中间结果

hello.c:源代码

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

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

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

hello:链接之后的可执行文件

hello0.elf:hello.o的ELF格式

hello.elf:hello的ELF格式

hello0.txt:hello.o反汇编代码

hello.txt:hello的反汇编代码

1.4 本章小结

本章介绍了hello.c程序的P2P及O2O过程,给出了实验进行的环境配置及实验过程中生成的中间结果。

第2章 预处理

2.1 预处理的概念与作用

预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。

当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

2.2在Ubuntu下预处理的命令

       使用gcc hello.c -E -o hello.i指令对hello.c程序进行预处理。

 

图2-2-1 预处理命令

2.3 Hello的预处理结果解析

使用文本编辑器打开hello.i程序,对所得结果进行分析:

 

图2-3-1 hello.i 程序

hello.i程序相对于hello.c进行了相对巨大的扩展,将原本的hello.c中的库文件与内部函数进行了展开,方便进一步的编译处理工作,而原本的代码则被保留在了文件的最后。

2.4 本章小结

本章主要介绍了预处理的概念及作用,同时生成hello.i文件,与原文本hello.c进行比较,分析预处理阶段进行的工作。

第3章 编译

3.1 编译的概念与作用

编译的概念:

编译是指利用编译器ccl从预处理文件(.i文件)产生汇编程序(.s文件)的过程,其中包含词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成这六个阶段。

编译的作用:

将文本文件hello.i翻译成文本文件hello.s。

3.2 在Ubuntu下编译的命令

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

 

图3-2-1 编译命令

3.3 Hello的编译结果解析

3.3.1 局部变量i

 

图3-3-1 局部变量

通过movl指令为局部变量int i在栈上分配空间。

3.3.2 字符串常量

 

图3-3-2 字符串常量

printf中函数中的字符串在main函数之前处理,表明它被作为局部的字符串常量处理。

3.3.3 数组

 

图3-3-3 数组

main函数中的char*argv[]数组被储存在栈中,可见,它里面的每一个元素都是一个指向字符类型数据的指针。

3.3.4 赋值操作

 

图3-3-4 赋值操作

hello.c中为i进行了赋值操作。

3.3.5 算术操作

 

图3-3-5 算术操作

hello.c中的i++,通过addl指令实现。

3.3.6 关系判断及转移操作

 

图3-3-6 关系判断及转移操作

       hello.c中,此处判断i与8的大小关系,如果i大于等于8则进行转移

       3.3.7 函数调用操作

                                                         图3-3-7 函数调用操作

通过call指令,实现对printf、getchar等函数的调用。

3.4 本章小结

本章实现了从hello.i到hello.s的编译过程,生成汇编代码,有助于进一步的汇编与链接操作,是hello.c程序执行过程中不可缺少的环节。

第4章 汇编

4.1 汇编的概念与作用

汇编的概念:

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

汇编的作用:

完成从汇编语言文件到可重定位目标文件的转化过程,便于进一步的链接过程。

4.2 在Ubuntu下汇编的命令

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

 

图4-2-1 汇编命令

4.3 可重定位目标elf格式

使用命令:readelf -a hello.o > hello0.elf,将hello.c写入到elf文件hello0.elf中。

(1)ELF头

 

图4-3-1 ELF头

ELF头以一个16字节的序列(Magic,魔数)开始,这个序列描述了生成文件的系统的字的大小和字节顺序。其余部分则介绍了该文件的类别、数据、类别、系统架构、标志等内容。

(2)节头表

 

图4-3-2 节头表

节头表中包含了各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等信息。

(3)重定位节

 

图4-3-3 重定位节

重定位节中包含.text节中需要被修正的信息,可以看到.rodata、puts、exit、printf、atoi、sleep、getchar符号的偏移,这会指导链接器进行下一步的链接工作。

4.4 Hello.o的结果解析

执行命令objdump -d -r hello.o>hello0.txt,将hello.o的反编译结果保存在文件hello0.txt中。

 

图4-4-1 hello0.txt

将该文件与hello.s进行比较:

1)分支跳转

hello.s的分支跳转会用助记符(.L3等)表示要转移到的位置,而hello.o则用实际的偏移地址来表示转移的目的地址。

 

图4-4-2 hello.o的分支跳转

2)函数调用

hello.s的函数调用会用函数的名称指代要转移到的位置,而hello.o则直接用计算出的地址表示实际的转移地址。

 

图4-4-3 hello.o的函数调用

3)立即数格式

hello.s采用十进制数来表示立即数,而hello.o采用十六进制数来表示。

 

图4-4-4 hello.o的立即数格式

4.5 本章小结

本章介绍了汇编阶段产生的可执行文件hello.o,对其产生的elf格式文件进行分析,并将其与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-2-1 链接命令

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

使用命令readelf -a hello > hello.elf将hello写入文件hello.elf中。

1)ELF头

 

图5-3-1 ELF头

hello的ELF头中,包含着magic、类别、数据等基本信息。

2)节头

 

图5-3-2 节头

节头中包含每个节的编号、名称、类型、地址、偏移量、大小等基本信息。

3)程序头

 

图5-3-3 程序头

程序头中包含着段的类型、偏移量、物理地址、虚拟地址等内容。

5.4 hello的虚拟地址空间

     

 

图5-4-1 EDB查看虚拟空间

通过EDB查看hello可知,hello的虚拟地址空间为0x401000-0x402000。

与5.3中的ELF文件进行比较,发现.init节的位置在0x401000处,刚好与EDB查询到的起始位置相同。

 

图5-4-2 .init节的位置

又如.text节的位置在0x4010f0处,在EDB中也能找到对应的内容。

 

图5-4-3 .text节的位置

5.5 链接的重定位过程分析

执行指令objdump -d -r hello > hello.txt

 

图5-5-1 链接的重定位

1)分支跳转

在hello.o中,分支跳转是采用偏移量的方式进行跳转的,而在hello .out中,分支跳转时采用直接跳转到具体的地址实现的。

 

图5-5-2 hello.out的分支跳转

2)函数调用

在hello.o中,函数的调用时直接用当前的下一个地址占位,而在hello.out文件中是跳转到[函数名@plt]的函数的地址处。

 

图5-5-2 hello.out的函数调用

3)数据访问

在hello.o中,对rodata节的访问跳转用0x0占位,而在hello.out中,则给出了具体的跳转长度。

 

图5-5-3 hello.out的数据访问

5.6 hello的执行流程

执行流程:

1)开始执行时调用的函数:_start、_libc_start_main

2)执行main时调用的函数:_main、_printf、_exit、_sleep、_getchar

3)退出时调用的函数:exit

整个执行过程中运行的函数及其地址:

_start:0x4010f0

_libc_start_main:0x2f12271d

main:0x401125

_printf:0x401040

_exit:0x401070

_sleep:0x401080

_getchar:0x401050

5.7 Hello的动态链接分析

 动态链接的基本思想是把程序按照模块拆分为相互独立的部分,而在程序运行时才把它们链接在一起形成一个完整的程序,这样可以更好体现程序的灵活性,便于随时对程序进行修改。

在调用共享库函数时,由于编译器无法提前预测函数运行时地址,因此要为该引用生成一条重定位记录,再在程序加载时用动态链接器解析,这一过程在got节中实现。为了观察在引用前后程序的变化,我们需要关注got节发生的变化。

从elf文件中得知,got节的位置为0x404000。

                                                         图5-7-1 got节的位置

实际运行程序,观察到调用dl_init后,got节的内容发生了变化。

 

图5-7-2 运行前got节的内容

 

图5-7-3 运行后got节的内容

5.8 本章小结

本章介绍了链接的过程,对比了可执行文件hello.out与hello.o的区别,分析了在链接过程中链接器的操作,并对hello的执行流程和动态链接过程进行了分析。

6章 hello进程管理

6.1 进程的概念与作用

进程的概念:

进程是计算机中的程序关于数据集合上的运行活动,既是系统进行资源分配和调度的基本单位,也是操作系统结构的基础。

进程的作用:

所有程序都运行在某个进程的上下文中,操作系统通过管理进程来实现对程序的正确执行。

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

Shell-bash的作用:

Shell 是一个命令解释器,它负责为使用者提供操作界面,解释由用户输入的命令并且把它们送到内核,并且调用相应的应用程序。

Shell-bash的处理流程:

(1)终端进程读取用户从键盘输入的命令行。

(2)shell通过分析命令行字符串,获取到命令行参数,并构造传递给execve的argv向量。

(3)检查第一个命令行参数是否是一个内置的shell命令。如果不是内部命令,调用fork( )创建新进程/子进程;如果是内部命令,则直接调用相应的程序进行处理。

(4)在子进程中调用execve(执行指定程序,execve的参数从之前获取到的命令行参数中取得。

6.3 Hello的fork进程创建过程

当在终端中输入./hello 学号 姓名 秒数的指令后,终端对输入的命令行进行解析,当发现这不是一个内置命令时,终端会调用fork函数创建一个新的运行的子进程。

子进程将会得到与父进程完全相同但是彼此独立的副本,同时获得与父进程任何打开文件描述符相同的副本。

6.4 Hello的execve过程

进程创建成功后,系统调用execve将一个可执行文件加载到当前进程的上下文中,替换掉原来的程序。execve实际上并不会直接把可执行文件的内容复制到物理内存,而是使用了内存映射机制,代码和数据区直接映射到可执行文件中的.text、.data、.rodata等节。.bss、栈、堆都将映射到匿名文件。在这之后,execve会将参数列表argv[]以及环境变量列表envp[]传递给加载的程序,程序可以使用带参数的main接受它们。

6.5 Hello的进程执行

       1.上下文切换:如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程,这就是上下文切换的过程。

2.调度:加载保存的寄存器,切换到虚拟地址空间。

在进程调用execve函数之后,进程会为hello程序分配了新的虚拟的地址空间。最初,hello在用户模式下运行,输出内容,然后调用sleep函数进程进入内核模式,运行信号处理程序,信号处理程序运行完毕后再返回用户模式。

运行过程中,cpu不断切换上下文,使运行过程被切分成许多时间片,与其他进程交替占用cpu,实现进程的调度过程。    

 

图6-5-1 进程的切换过程

6.6 hello的异常与信号处理

异常共分为以下四种:

 

图6-6-1 异常的分类

常见的信号有以下几种:

 

图6-6-2 信号的种类

在正常运行的情况下,程序会正常输出学号、姓名等信息。

 

图6-6-3 正常运行

在程序运行的时候键入Ctrl+C,hello会直接结束,这是因为Ctrl+C会发送一个SIGINT信号给hello,该信号会直接终止进程:

 

图6-6-4 终止进程

按下Ctrl+Z:进程收到 SIGSTP 信号,hello 进程会暂时挂起。

 

图6-6-5 暂停进程

此时可以输入指令ps查看后台进程,jobs指令查看当前处于暂停状态的进程,fg指令使进程继续,kill指令杀死进程。

 

图6-6-6 暂停时执行的指令

尝试在进程执行过程中输入回车或乱按,发现不会对程序的运行产生影响。

 

图6-6-7 进程执行时尝试干扰

6.7本章小结

在本章中,着重于hello.c程序运行过程中的进程管理,介绍了shell的运行机制及fork,execve函数的工作,以及异常处理和信号处理程序的运行规则。

7章 hello的存储管理

7.1 hello的存储器地址空间

1、逻辑地址:程序经过编译后出现在汇编代码中的地址,在这里指的是hello.o中的地址。

2、线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,线性地址表现为“描述符:偏移量”的形式,在hello程序中用逻辑地址加上程序中给出的偏移量即为线性地址。

3、虚拟地址:与线性地址相同。

4、物理地址:CPU通过地址总线寻址,找到物理内存对应的真实存在的地址,在hello程序中指其虚拟地址映射到的实际上的物理内存对应的地址。

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

一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。

全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。如果通过selector在全局描述符表中获取到段基址,再与段内偏移相加,就得到了线性地址。这个过程就被称作段式内存管理。

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

页表是一个包含许多页表条目(PTE)的数组,虚拟地址空间中的每个页在页表中都有一个PTE。PTE由一个有效位和一个n位地址字段组成,有效位表明该虚拟页是否被缓存在DRAM中。如果设置了有效位,那么地址字段表示相应的物理页的起始位置;如果没有设置有效位,那么空地址表示虚拟页还未被分配,否则这个地址指向该虚拟页在磁盘的起始位置。

MMU利用页表实现从虚拟地址到物理地址的变换。n位的虚拟地址包含一个p位的虚拟页面偏移VPO和一个n-p位的虚拟页号VPN。MMU利用VPN选择适当的PTE,如果这个PTE设置了有效位,则页命中,将页表条目中的物理页号和虚拟地址中的VPO连接起来就得到相应的物理地址。否则会触发缺页异常,控制传递给内核中的缺页异常处理程序。缺页处理程序确定物理内存中的牺牲页,调入新的页面,并更新内存中相应PTE。处理程序返回到原来的进程,再次执行导致缺页的指令,MMU重新进行地址翻译,此时和页命中的情况一样。同时,也可以利用TLB缓存PTE加速地址的翻译。

 

图7-3-1 MMU的结构

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

处理器生成一个虚拟地址,并将其传送给MMU。MMU用VPN向TLB请求对应的PTE,如果命中,则跳过之后的几步。MMU生成PTE地址(PTEA).,并从高速缓存/主存请求得到PTE。如果请求不成功,MMU会向主存请求PTE,高速缓存/主存向MMU返回PTE。PTE的有效位为零, 此时会触发缺页异常,缺页处理程序确定物理内存中的牺牲页。缺页处理程序调入新的页面,并更新内存中的PTE。缺页处理程序返回到原来进程,再次执行导致缺页的指令。

在四级页表下,会多次执行该流程,直到找到虚拟地址对应的物理地址为止。

 

图7-4-1 虚拟页向物理页的映射

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

在三级Cache支持下,CPU在访问物理内存前首先会查询Cache。首先确定L1中是否已缓存需要访问的内存,该块是否有效,如果已被缓存且有效就称为Cache命中,直接就对其进行读写即可,如果Cache L1没有找到,则以此类推向L2,L3中查询,如果依旧没有,才会访问内存,访问时需要把目的数据附近的整个Cache line长度的数据都加载进入Cache L3,L2,L1中后再进行读写操作。

7.6 hello进程fork时的内存映射

在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,这样就为每个进程保持了私有地址空间。

7.7 hello进程execve时的内存映射

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

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

映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结构。

映射共享区域,将hello程序与共享对象 libc.so 链接,然后再映射到用户虚拟地址空间中的共享区域内。

设置程序计数器,execve需要设置当前进程上下文的程序计数器,使之指向代码区域的入口点。

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

缺页故障:

当虚拟内存中的字不在物理内存中即为缺页。若CPU尝试引用某个字,但该字并未缓存在主存中。此时当地址翻译硬件从内存中读取该字时,会从有效位推断出它并未被缓存,并且触发一个缺页异常。

缺页中断处理:

缺页处理程序是系统内核中的代码,选择一个牺牲页,如果这个牺牲页被修改过,那么就将它交换出去,换入新的页并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令会再次发送VA到MMU。

 

图7-8-1 缺页的处理方法

7.9动态存储分配管理

动态内存管理的基本方法与策略:

动态内存分配器维护着一个进程的虚拟内存区域,称为堆,系统之间细节不同,但是不失通用性,对于每个进程,内核维护着一个变量brk,它指向堆的顶部。

分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个个连续的虚拟内存片,要么是已分配的,要么是空闲的。一个已分配的块会保持已分配状态,直到它被释放。

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

1.显示分配器:要求应用显示的释放任何已分配的块。例如C标准库提供一个叫做malloc程序包的显示分配器。

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

7.10本章小结

本章详细介绍了hello程序执行过程中包括地址变换、虚拟地址到物理地址的转变、cache与页表的访问原理、fork与execve函数的内存分配等内容,较为详细地介绍了hello的存储管理过程。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种将设备映射为文件的方式被称为Unix I/O。

8.2 简述Unix IO接口及其函数

Unix IO接口有着以下的功能:

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

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

3.读写文件。读操作就是从文件复制n>0个字节到内存,复制的位置从当前文件位置k开始,连续复制n个字节,给定一个大小为m字节的文件,当k>=m时,触发EOF。

4.关闭文件。当应用完成了对文件的访问之后,它就会通知内核关闭这个文件,作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。

常用Unix I/O函数:

①int open(char* filename,int flags,mode_t mode)

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

②int close(fd)

进程通过调用close函数关闭一个打开的文件,fd是需要关闭的文件的描述符。

③ssize_t read(int fd,void *buf,size_t n)

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

④ssize_t wirte(int fd,const void *buf,size_t n)

write函数从内存位置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;

    }

引用的vprintf函数:

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,并返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall,之后由ASCII进入到字模库生成vram。显示芯片按照刷新频率逐行读取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实现的。getchar通过read函数从缓冲区读入一行,并返回读入的第一个字符。若读入失败,则返回EOF。

异步异常-键盘中断的处理:当用户通过键盘输入时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得扫描码,再将其转换为ASCII码,保存到系统的键盘缓冲区中。

8.5本章小结

本章介绍了unix的I/O管理内容,并分析了unix接口及其调用的函数,并将printf和getchar函数的实现进行了详细的分析。

结论

hello函数在执行过程中经历了以下步骤:

预处理:对hello.c进行预处理,将c文件调用的库全部展开合并得到到hello.i文本文件;

编译:将hello.i编译生成汇编语言文件hello.s;

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

链接:将hello.o与可重定位目标文件和动态链接库链接生成可执行文件hello;

创建子进程:shell调用fork函数,创建子进程;

加载程序:shell调用execve函数,启动加载器,进行虚拟内存的映射,进入程序入口之后开始载入物理内存,然后进入main函数。

交互:hello的输入输出与外界交互,在这里会用到unix I/O函数;

终止:hello程序终止后,会被作为父进程的shell回收。

经过本课程的学习,我了解到了计算机实际运行过程中各模块的内部原理,从简单的数据表示,汇编代码的执行,到程序的链接、内存分配,让我感叹计算机不愧是一种极其精密的机器,也坚定了我未来继续学习的决心。

从最初学习编程的hello.c程序入手,让我明白了每一个看似简单的程序都有着复杂且合理的运行过程,每一个过程都是在无数次尝试与优化中确立起来的,每一个从事计算机行业的先驱者与后继者都值得我们尊敬!

附件

hello.c:源代码

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

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

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

hello:链接之后的可执行文件

hello0.elf:hello.o的ELF格式

hello.elf:hello的ELF格式

hello0.txt:hello.o反汇编代码

hello.txt:hello的反汇编代码
参考文献

[1] Randal E.Bryant, David O’Hallaron. 深入理解计算机系统[M]. 机械工业出版社.2018.4

[2] 袁春风. 计算机系统基础. 北京:机械工业出版社,2018.7(2019.8重印)

[3] https://www.cnblogs.com/pianist/p/3315801.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值