摘 要

本文按照哈尔滨工业大学计算机系统课程大作业要求,以hello程序为例,从预处理编译开始,通过在Linux下用各种工具对hello程序进行分析,详细的分析了hello从程序到进程的过程。分析了其中操作系统的处理方式,以此来加强自己对计算机系统的认识。

关键词:计算机系统;Linux;程序编译;进程管理

目 录

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

hello在程序员通过键盘输入保存在磁盘上以.c文件存储,之后经过预处理,编译,汇编,链接等一系列过程,它从人能看懂的文本文件变成了机器能够看懂的二进制文件。hello.c源代码通过预处理器生成预处理完成的.i文件。预编译过程中,会解析程序用到所有头文件的绝对路径,除此之外还会根据预处理器指令将不需要的代码删除等一系列操作。之后进入编译器,编译阶段将代码从C语言转化成了与机器指令一一对应的汇编语言,方便之后的一系列处理。然后通过汇编器将hello.s转换为.o可重定位目标文件,将汇编代码转化为机器代码。最后,连接器将hello.o与其他可重定位文件进行链接,形成可执行目标文件hello.out。

之后,在shell加载hello,shell根据输入判断,不是内置指令,于是先执行fork,变成了两个进程,此时复制了一份虚拟内存并且都映射到物理内存中相同的地址空间中,并把他们标记成为写复制,之后在子进程中调用execve装载hello的程序,此时会把虚拟内存中原有的区域结构删除,建立新的区域结构,至此hello成为了独立的进程。hello加载进入内存之后,首先会进行动态链接,动态链接器会根据hello的需要构建一个查表函数,而hello通过这个查表函数来进行对共享库函数的调用,在hello执行完毕之后,如果函数内部没有调用exit,__lib_start_main函数会帮我们调用exit退出,在退出后会给shell发送一个SIGCHLD信号,shell收到这个信息后会释放之前用于存储hello信息的一些内存空间,这就是hello从Zero到Zero的一生。

1.2 环境与工具

1.硬件环境:

处理器 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz,2304 Mhz,8 个内核,16 个逻辑处理器

已安装的物理内存(RAM) 16.0 GB

图形处理器 NVIDIA GeForce RTX 3060 Laptop GPU

2.软件环境:

版本 Windows 10 家庭中文版

版本号 21H2

安装日期 2021/7/24

操作系统内部版本 19044.2006

体验 Windows Feature Experience Pack 120.2212.4180.0

Ubuntu 22.04.1

GNOME Shell 42.2

窗口系统 Wayland

3.开发与调试工具:

VSCODE、objdump、gdb、edb、hexedit、gcc等

1.3 中间结果

hello.c:hello的源文件

hello.i:调用预处理器cpp后生成的文本文件,用于分析预处理过程

hello.s:调用编译器ccl后生成的汇编程序文本文件,用于分析编译过程

hello.o:调用汇编器as后生成的可重定位目标文件,用于分析汇编过程

hello:调用连接器ld后生成的可执行目标文件,用于分析链接过程

1.4 本章小结

仅是运行一个小小的hello程序,其背后也有复杂的过程,有着多个中间产物。这个过程离不开计算机的硬件和软件环境,也离不开编译系统和shell。

第2章 预处理

2.1 预处理的概念与作用

1.概念:预处理,或称预编译,是指在正式开编译前的操作,即在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。

预处理由预处理器cpp负责完成。预处理不对源代码做任何解析,但它把源代码分割或处理成为特定预处理记号,用来支持语言特性(如宏调用),以便后面的编译过程。

图2-1 预处理过程

2.作用:

预处理的功能有:宏定义、文件包含、条件编译三部分,分别对应宏定义命令、文件包含命令、条件编译命令。

(1)宏定义:#define 指令定义一个宏,预处理过程中用实际值替换用#define定义的字符串。

(2)文件包含:#include指令使一个指定文件的内容,如系统头文件,插入到程序文本中。

(3)条件编译:#if,#ifdef,#ifndef,,#elif,#else 和#dendif 指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外,即根据“#if”后面的条件决定需要编译的代码。

2.2在Ubuntu下预处理的命令

在Ubuntu下,使用gcc的-E参数对程序进行预处理,生成文本文件hello.i

图2-2 预处理指令

2.3 Hello的预处理结果解析

原23行的hello.c变成3091行,内容大大增加。在预处理过程中,头文件被展开,宏定义被展开,注释行被删除。hello.i最后一部分是可读的C代码。

图2-3 hello.i文件部分内容

在预处理中会把用#include所引入的头文件的实际地址解析出来,如图2-2第12行解析了stdio文件的位置。此外,预处理器也会解析该头文件所用#include引入的头文件,一直递归解析下去。

图2-4 解析头文件地址

图2-5 头文件stdio.h

2.4 本章小结

在C语言中,源代码通过gcc的-E参数可以选择只开启预处理选项生成预处理完成的.i文件。预编译过程中,会解析程序用到所有头文件的绝对路径,除此之外还会根据预处理器指令将不需要的代码删除等一系列操作。

第3章 编译

3.1 编译的概念与作用

1.概念:将预处理后的.i文件进行一系列的语法分析,同时进行一定程度上的优化,最终生成的相应的汇编代码的.s文本文件叫做编译。编译由编译器cll完成。

图3-1 编译过程

2.作用:

(1)进行语法分析:编译器的语法分析器以单词符号作为输入,分析输入的单词符号串是否形成符合语法规则的语法单位。

(2)生成中间代码:源程序的在编译器中的一种内部表示。表示成中间代码可使编译程序的结构在逻辑上更为简单明确,有利于目标代码的优化。

(3)进行代码优化:指对程序进行多种等价变换,使得优化后的程序性能更好。

(4)生成目标代码:编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。

3.2 在Ubuntu下编译的命令

在Ubuntu下,使用gcc的-S参数对程序进行编译,由预处理生成的hello.i生成文本文件hello.i(或由hello.c生成也行)。

图3-2 预处理指令

3.3 Hello的编译结果解析

下面举例分析将高级语言代码转换为汇编代码。

(1)局部变量int i存储在栈中:

图3-3 局部变量int i存储在栈中

(2)字符串常量的表示:

printf中格式化字符串被当作局部的字符串常量处理。而globl项说明了该常量作用范围为仅限在main函数内。

图3-4 字符串常量

(3)i++的实现:

使用汇编指令addl实现,将立即数1与存储在栈中的局部变量i相加。

图3-5 i++的实现

(4)if语句的实现:

在cmpl设置条件码后,通过条件转移指令je实现if语句。如果不跳转则执行if中的语句。

图3-6 if语句的实现

(5)for循环的实现:

使用条件控制实现循环。这里使用的是跳转到中间(jump-to-middle)格式。

图3-7 for循环的实现

(6)关系比较的实现:

使用CMP类语句设置条件码。注意这里i<9转换成了i<=8。

图3-8 关系比较的实现

(7)数组访问的实现:

使用变址寻址访问存储在内存中的数组。

图3-9 数组访问的实现

(7)函数调用的实现

图3-10 函数调用的实现

3.4 本章小结

本章介绍了编译阶段。编译阶段将代码从C语言转化成了与机器指令一一对应的汇编语言,方便之后的一系列处理。在编译过程中,会把局部变量储存在栈中,把字符串常量存储在堆中,对于算数运算符和关系运算符,汇编语言和C语言并无太多差别,不过在一些时候编译器会把小于(大于)转化成等价的小于等于(大于等于),下标运算符号[i]都是通过数组指针+8(指针长度)*i实现的,大多数函数传参都尽可能使用寄存器进行传递,而printf稍有例外。if控制语句的汇编语言与C语言的if结构基本类似,而for控制语句则更接近于go-to控制语句。

第4章 汇编

4.1 汇编的概念与作用

汇编是指将编译后的.s文件中的汇编指令逐条翻译成机器指令,生成.o的可重定位目标文件,此时的hello.o文件已成为一个二进制文件。汇编由汇编器as完成。

图4-1 汇编过程

4.2 在Ubuntu下汇编的命令

在Ubuntu下,使用gcc的-c参数对程序进行汇编,由编译生成的hello.s生成二进制的可重定位目标文件hello.o(或由hello.c生成也行)。

图4-2 汇编指令

4.3 可重定位目标elf格式

(1)ELF头:

ELF64 告诉我们这个文件是一个64位系统下的ELF文件;使用补码表示有符号数,使用小端法存储数据;Version: 1 表示ELF文件的版本是1;REL表示是可重定位目标文件;Start of section header指示了节头开始处。

图4-3 ELF头

(2)节头表:

节头描述了每个节的名字,类型,偏移地址,大小等基本信息。

图4-4 节头表

(3)重定位节:

连接器在处理目标文件时,须要对目标文件中某些部位进行重定位,重定位的信息都记录在ELF文件表里面,对于每个须要重定位的代码段和数据段,都会有一个相应的重定位表,例如.rel.text表对应.text节。

图4-5 重定位节

4.4 Hello.o的结果解析

  1. 对hello.o使用反汇编:

用命令objdump -d -r hello.o 分析hello.o的反汇编代码,结果如下图:

图4-6 hello.o的反汇编结果

  1. 机器语言的构成以及与汇编语言的对应关系:

机器语言由对应处理器的指令集中的指令构成。具体由指令指示符、寄存器指示符和常数字等。

一条汇编语言对应一条机器语言,如下图:

图4-7 机器语言的构成以及与汇编语言的对应关系

  1. 机器语言中的操作数与汇编语言不一致:

如下图中的机器语言和汇编语言,在jle行中,机器语言使用的是PC相对寻址,汇编语言使用的是跳转表。在call行中,机器语言的操作数也使用PC相对寻址,但此时还没有重定位,所以操作数是0。

图4-8 机器语言中的操作数与汇编语言不一致

4.5 本章小结

本章介绍了汇编的概念和作用;使用Ubuntu下的汇编指令将将hello.s转换为.o可重定位目标文件;并分析了hello.o文件的ELF头和节头表等;比较hello.s和hello.o的不同之处,了解了汇编代码和机器代码之间的区别和联系。

第5章 链接

5.1 链接的概念与作用

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

图5-1 链接过程

2.作用:链接使得分离编译成为可能,能够将一个大型的应用程序分解成为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

5.2 在Ubuntu下链接的命令

图5-2 链接命令

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

(1)hello的ELF头:

在ELF头中,记录了hello文件的类型版本信息、数据表示方式、程序入口地址等信息。

图5-3 ELF头

(2)节头表:

节头描述了每个节的名字,类型,偏移地址,大小等基本信息,其中.text段(存放程序的段)的地址与ELF头中程序入口地址一致。

图5-3 节头表

(3)程序头:

程序头中包括所指向段的类型、其在ELF文件中的偏移地址、大小,映射到内存的虚拟地址信息,段的读写权限等等。

图5-4 程序头

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息。

我们发现,程序的入口(.text段起始地址)0x4010f0并不是我们平时认为的main函数,而是一个_start函数。在该函数结束后会调用__libc_start_main,然后才会调用我们用户写的main函数。

图5-5 使用edb加载hello(1)

从0x401125开始便是main函数:

图5-6 使用edb加载hello(2)

5.5 链接的重定位过程分析

(1)分支跳转:

在可重定位文件中,分支跳转是使用PC相对寻址来实现;而在可执行文件中,分支跳转时绝对寻址。

图5-7 分支跳转

(2)访问内存:

在可重定位文件中,对字符串访问表现为还没有重定位之前的状态,操作数均为0,后方跟有重定位条目,指导连接器ld这里应该如何重定位;而在可执行文件中,对存储在内存中的字符串访问采用绝对寻址的方法。

图5-8 访问内存

5.6 hello的执行流程

使用edb执行hello,通过edb的调试,一步一步地记录下call命令进入的函数。

图5-9 hello的执行流程

5.7 Hello的动态链接分析

在hello文件中,调用共享库的函数时,它们都会指向同一个地址:0x401020。

图5-10 指向同一个地址:0x401020

在dl_init前,0x404010中的值为空。

在dl_init后,0x404010存储了一个地址,这个地址指向的是ld-2.33.so动态库中的一个函数地址,该函数会根据PLT压入的值调用对应的函数。

图5-11 在dl_init后,0x404010存储了一个地址

5.8 本章小结

本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。

调用共享库的函数则构建了一个PLT表,在程序运行前进行动态链接,确定查表的函数地址,通过这个查表函数来进行对共享库中函数的调用。这个查表函数是动态链接器在初始化时为程序构建的,构建完成后会把函数的调用地址存放到指定的内存空间中。

第6章 hello进程管理

6.1 进程的概念与作用

进程是正在运行的程序的实例。从理论角度看,进程是对正在运行的程序过程的抽象;从实现角度看,进程是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。

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

Shell是一种命令行解释器,它代替用户执行程序。Shell读取用户输入的字符串命令,解释并且执行命令。

Shell的处理流程:

1.读取从键盘输入的命令;

2.判断命令是否正确,且将命令行的参数改造为系统调用execve() 内部处理所要求的形式;

3.终端进程调用fork() 来创建子进程,自身则用系统调用wait() 来等待子进程完成;

4.当子进程运行时,它调用execve() 根据命令的名字指定的文件到目录中查找可行性文件,调入内存并执行这个命令;

5.如果命令行末尾有后台命令符号& 终端进程不执行等待系统调用,而是立即发提示符,让用户输入下一条命令;如果命令末尾没有& 则终端进程要一直等待。当子进程完成处理后,向父进程报告,此时终端进程被唤醒,做完必要的判别工作后,再发提示符,让用户输入新命令。

6.3 Hello的fork进程创建过程

输入字符串“./hello 2021 zy 3”,Shell判断不是这内置指令,执行执行fork系统调用,将创建一个子进程。此时hello还未成为进程。

6.4 Hello的execve过程

在新创建的子进程中, shell执行execve系统调用,将hello装载入内存,覆盖了原先子进程的内存信息。此时,hello正式成为进程,下一步开始执行。

6.5 Hello的进程执行

  1. 上下文信息

上下文信息是系统内核重新启动一个挂起的进程所需要恢复的原来的状态。由这个挂起的进程需要的寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的信息构成。

  1. 进程时间片

时间片是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间。由此决定每个进程应该执行多长时间,实现进程的调度。

  1. 进程调度

在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个决策称为调度。调度的过程是由调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,并进行:

1)保存以前进程的上下文

2)恢复新恢复进程被保存的上下文

3)将控制传递给这个新恢复的进程,来完成上下文切换。

图6-1 装载上下文

  1. 用户态与核心态转换

进程由常驻内存的操作系统代码块(称为内核)管理。UNIX系统把执行状态(UNIX内核为进程设置了9种状态:执行,就绪,睡眠,“创建”与“僵死”,“被抢占”等)分为两种:一种是用户态执行,表示进程正处于用户状态之中执行;另一种是核心态执行,表示一个应用进程执行系统调用后,或I/O中断、时钟中断后,进程便处于核心态执行。

这两种状态的主要差别是:处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的;而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。

从用户态转换到核心态有三种情况:

1)系统调用。这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。如调用read()函数。

2)异常。当CPU在执行运行在用户态下的程序时,发生了某些事件,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

3)外围设备的中断当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序。

图6-2 调用read()时用户态与核心态转换

6.6 hello的异常与信号处理

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

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

(1)在hello的正常执行过程中,一般只会在进程结束后,向父进程发送SIGCHLD信号,让父进程完成对自己的回收工作。

(2)运行期间键盘输入:

1)在hello运行过程中,按回车会引起中断,I/O中断,但由于程序内没有对应的处理程序,在中断进入内核态中又会回到之前执行的指令,所以程序看起来是没有任何变化的。

图6-3 hello运行过程按回车

2)在hello运行过程中,按Ctrl+C,hello会直接结束。这是由于Ctrl+C会导致内核发送一个SIGINT信号给hello,而SIGINT信号的默认操作是终止进程。

图6-4 hello运行过程中按Ctrl+C

3)在hello运行过程中,按Ctrl+Z,会发送SIGTSTP信号,该信号默认操作是暂停进程,即让进程处于等待态,可以通过fg命令让其回到前台进程继续执行,也可以通过kill命令发送SIGINT信号杀死进程。

图6-3 按Ctrl+Z后,继续执行hello进程、杀死hello进程

(3)Ctrl-z后运行ps 、jobs、pstree、fg、kill等命令演示:

图6-5 Ctrl-z后运行ps 、jobs、pstree、fg、kill

6.7本章小结

本章介绍了hello进程的创建和执行过程。shell在用户输入命令后,解析命令,执行fork,在子进程中execve加载hello程序,hello至此成为独立的进程。hello运行过程中反复进行了运行态,等待态,就绪态的切换,最终结束后向shell发送SIGCHLD信号结束了自己的一生。

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

结合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动态存储分配管理

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

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

7.10本章小结

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

(第7 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

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

8.3 printf的实现分析

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

[转]printf 函数实现的深入剖析 – Pianistx – 博客园

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

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

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

8.4 getchar的实现分析

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

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

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

8.5本章小结

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

(第81分)

结论

hello所经历的过程:

hello在程序员通过键盘输入保存在磁盘上以.c文件存储,之后经过预处理,编译,汇编,链接等一系列过程,它从人能看懂的文本文件变成了机器能够看懂的二进制文件。hello.c源代码通过预处理器生成预处理完成的.i文件。预编译过程中,会解析程序用到所有头文件的绝对路径,除此之外还会根据预处理器指令将不需要的代码删除等一系列操作。之后进入编译器,编译阶段将代码从C语言转化成了与机器指令一一对应的汇编语言,方便之后的一系列处理。然后通过汇编器将hello.s转换为.o可重定位目标文件,将汇编代码转化为机器代码。最后,连接器将hello.o与其他可重定位文件进行链接,形成可执行目标文件hello.out。

之后,在shell加载hello,shell根据输入判断,不是内置指令,于是先执行fork,变成了两个进程,此时复制了一份虚拟内存并且都映射到物理内存中相同的地址空间中,并把他们标记成为写复制,之后在子进程中调用execve装载hello的程序,此时会把虚拟内存中原有的区域结构删除,建立新的区域结构,至此hello成为了独立的进程。hello加载进入内存之后,首先会进行动态链接,动态链接器会根据hello的需要构建一个查表函数,而hello通过这个查表函数来进行对共享库函数的调用,在hello执行完毕之后,如果函数内部没有调用exit,__lib_start_main函数会帮我们调用exit退出,在退出后会给shell发送一个SIGCHLD信号,shell收到这个信息后会释放之前用于存储hello信息的一些内存空间,这就是hello从Zero到Zero的一生。

通过完成本次大作业,我深入了解了hello的一生,将课堂上所学到的知识串联了起来;我对程序的编译以及执行有了更深刻的理解,对计算机运行程序的本质进行了探究。

附件

hello.c:hello的源文件

hello.i:调用预处理器cpp后生成的文本文件,用于分析预处理过程

hello.s:调用编译器ccl后生成的汇编程序文本文件,用于分析编译过程

hello.o:调用汇编器as后生成的可重定位目标文件,用于分析汇编过程

hello:调用连接器ld后生成的可执行目标文件,用于分析链接过程

参考文献

[1] (美)布赖恩特(Bryant,R.E.)等. 深入理解计算机系统[M]. 北京:机械工业出版社,2016:1-556