2024 hit ics 梁志

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业       未来技术学院      

学     号        2022111099      

班     级         22WL023        

学       生                     

指 导 教 师                       

计算机科学与技术学院

2024年5月

摘  要

本论文针对hello.c这一基础的C语言文件,在Linux系统环境下,对其完整的生命周期进行了系统性的研究。研究起始于hello.c的原始程序代码,随后逐步深入到编译、链接、加载、执行、终止以及资源回收等各个环节,旨在全面揭示hello.c文件从诞生到消亡的完整过程。

在研究过程中,本论文以hello.c文件为核心研究对象,结合《深入理解计算机系统》一书的理论内容与课堂教师的专业讲授,在Ubuntu操作系统环境下对hello程序的整个生命周期进行了详尽的剖析。通过对hello.c程序的深入剖析,本论文成功地将计算机系统的各个组成部分串联起来,实现了理论与实践的紧密结合,达到了学以致用的目的,并实现了对计算机系统知识的融会贯通。

关键词:生命周期;计算机系统;底层原理;体系结构;                           

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

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

Program 到 Process (P2P)

编写源代码(Program):使用编辑器(Editor)编写并保存程序代码,例如 hello.c

预处理(Preprocessing):编译器的预处理器将源代码中的宏展开、文件包含处理和条件编译指令处理,生成预处理后的代码。

编译(Compilation):编译器(Compiler)将预处理后的代码翻译成汇编代码。

汇编(Assembly):汇编器(Assembler)将汇编代码转换成机器代码,生成目标文件(.obj 或 .o)。

链接(Linking):链接器(Linker)将多个目标文件及库文件链接成一个可执行文件(例如 hello),解决符号引用问题。

加载(Loading):操作系统(OS)将可执行文件加载到内存,创建一个新的进程(Process)。这是通过系统调用 fork() 创建一个新进程,然后 execve() 替换进程的地址空间为可执行文件的内容。

从 Zero 到 Zero (O2O)

程序启动前的状态:在开始编写 hello.c 前,计算机系统中没有这个程序文件,处于 "Zero" 状态。

程序的编写、编译、链接和加载:程序员编写代码并通过一系列编译和链接步骤,将其转换为可执行文件。加载阶段将可执行文件转换为进程,赋予其生命。

程序执行和终止:程序被执行,显示 "Hello, World!" 后终止。操作系统通过进程调度、内存管理和 I/O 管理完成整个执行过程。程序终止后,操作系统回收资源,进程从系统中消失。

回到初始状态:程序执行完成后,所有资源被回收,系统回到与开始前相同的状态,依然是 "Zero"。

1.2 环境与工具

硬件环境:

处理器:R7-5800H   3.20 GHz

机带RAM16.0GB

系统类型:64位操作系统,基于x64的处理器

软件环境:Windows11 64位,VMwareUbuntu 20.04 LTS

开发与调试工具:Visual Studio 2021 64vim objump edb gcc readelf等工具

1.3 中间结果

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

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

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

hello.asm      反汇编hello.o得到的反汇编文件

hello1.asm     反汇编hello可执行文件得到的反汇编文件

1.4 本章小结

      本章对hello的P2P与O2O流程进行了阐述,涵盖了流程设计的核心理念和实现途径。随后,本章节列举了本实验所需的硬件配置、软件平台及开发工具,并对实验过程中生成的各中间结果文件进行了名称和功能说明。

(第10.5分)

第2章 预处理

2.1 预处理的概念与作用

C语言预处理是C语言编译过程的一个阶段,它在编译之前对源代码进行一系列的处理操作,包括宏替换、文件包含、条件编译等,最终生成经过预处理的代码,然后再进行编译。预处理过程主要进行代码文本的替换工作,用于处理以#开头的指令,还会删除程序中的注释和多余的空白字符。预处理指令可以简单理解为#开头的正确指令,它们会被转换为实际代码中的内容。

预处理过程中并不直接解析程序源代码的内容,而是对源代码进行相应的分割、处理和替换,主要有以下作用:

头文件包含:将所包含头文件的指令替代。

宏定义:将宏定义替换为实际代码中的内容。

条件编译:根据条件判断是否编译某段代码。

其他:如注释删除等。

简单来说,预处理是一个文本插入与替换的过程预处理器。

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

       经过预处理,代码被扩充为几千行,但除了以#开头的预处理指令外,代码的其他部分保持不变。

main函数代码出现之前的大段代码源自于原代码的头文件<stdio.h>  <unistd.h>  <stdlib.h> 的依次展开。以 stdio.h 的展开为例:预处理过程中,#include指令的作用是把指定的头文件的内容包含到源文件中。stdio.h是标准输入输出库的头文件,它包含了用于读写文件、标准输入输出的函数原型和宏定义等内容。

当预处理器遇到#include<stdio.h>时,它会在系统的头文件路径下查找stdio.h文件,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。然后把stdio.h文件中的内容复制到源文件中。stdio.h文件中可能还有其他的#include指令,比如#include<stddef.h>或#include<features.h>等,这些头文件也会被递归地展开到源文件中。

预处理器不会对头文件中的内容做任何计算或转换,只是简单地复制和替换。

2.4 本章小结

本章阐述了在Linux环境下,如何通过命令行工具对C语言程序进行预处理操作,以及预处理步骤所承载的意义与功能。预处理作为编译过程的首要环节,对于确保源代码的规范性和编译过程的顺利进行具有至关重要的作用。

同时通过一个简单的hello程序实例,展示了从hello.c源文件到hello.i预处理文件的转换过程。对预处理后文件hello.i进行分析,其中不仅包含了标准输入输出库stdio.h的完整内容,同时亦囊括了程序中使用的宏定义和常量声明。此外,预处理后的文件还保留了行号信息,并处理了条件编译指令,以确保在后续的编译阶段能够正确识别和处理这些元素。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

C语言中的编译是指将源代码从高级编程语言转换为机器语言或中间代码的过程。这个过程通常分为多个阶段,包括词法分析、语法分析、语义分析、中间代码生成、优化和目标代码生成。

编译过程的各个部分及其作用:

词法分析:将预处理后的源文件(.i文件)中的字符序列转换为词法单元。词法分析器识别出程序中的基本构件如关键字、标识符、操作符和分隔符,并为后续的语法分析准备好输入。

语法分析:根据词法分析生成的词法单元,构建抽象语法树(AST),确保源代码的结构符合语言的语法规则。语法分析器检查源代码的结构,确保它们按照C语言的语法规则进行排列,并生成抽象语法树。

语义分析:在抽象语法树的基础上,进行语义检查,如类型检查、作用域检查和一致性检查。语义分析器验证代码的语义正确性,确保没有类型错误、作用域问题和其他语义相关的错误。

中间代码生成:将抽象语法树转换为中间表示形式,通常是与具体机器无关的中间代码,如三地址码。生成一种便于优化和进一步转换的中间表示,这样的表示通常比源代码更接近机器代码,但仍然独立于具体的硬件平台。

优化:对中间代码进行优化,包括局部优化和全局优化。提高代码的执行效率和减少代码体积。优化过程包括消除公共子表达式、常量折叠、死代码消除等。

目标代码生成:将中间代码转换为汇编代码(.s文件),这些汇编代码是特定目标机器的汇编语言表示。生成与具体机器相关的汇编代码,这些代码可以直接用于生成机器码。目标代码生成器依据目标体系结构的特性,将中间表示映射到特定的机器指令集。

编译的作用是使高级语言源程序变为汇编语言,提高编程效率和可移植性。计算机程序编译的基本流程包括词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成等阶段。

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

hello.s文件结构

内容

含义

.file

源文件

.text

代码段

.global

全局变量

.data

存放已经初始化的全局和静态C 变量

.section  .rodata

存放只读变量

.align

对齐方式

.type

表示是函数类型/对象类型

.size

表示大小

.long  .string

表示是long类型/string类型

3.3.1汇编初始部分

在main函数前有一部分字段展示了节名称:

.file               声明出源文件

.text               表示代码节

.section   .rodata    表示只读数据段

.align              声明对指令或者数据的存放地址进行对齐的方式

.string              声明一个字符串

.globl              声明全局变量

.type               声明一个符号的类型

3.3.2 数据部分

(1)字符串程序有两个字符串存放在只读数据段中,如图:

hello.c中唯一的数组是main函数中的第二个参数(即char**argv),数组的每个元素都是一个指向字符类型的指针。由知数组起始地址存放在栈中-32(%rbp)的位置,被两次调用作为参数传到printf中。

如图,分别将rdi设置为两个字符串的起始地址:

(2)参数argc

参数argc是main函数的第一个参数,被存放在寄存器%edi中,由语句

可见寄存器%edi地址被压入栈中,而该地址上的数值与立即数5判断大小,从而得知argc被存放在寄存器并被压入栈中。

(3)局部变量

程序中的局部变量只有i,我们根据

可知局部变量i是被存放在栈上-4(%rbp)的位置。

3.3.3全局函数

hello.c中只声明了一个全局函数int main(int arge,.char*argv[]),我们通过汇编代码

可知。

3.3.4赋值操作

hel1o.c中的赋值操作贝有for循环开头的i-0,该赋值操作体现在汇编代码上,则是用mov指令实现,该程序内为  movl      $0, -4(%rbp)。由于int型变量i是一个32位变量,使用movl传递双字实现。

3.3.5算术操作

hello.c中的算术操作为for循环的每次循环结束后i++,该操作体现在汇编代码则使用指令add实现,同样,由于变量i为32位,使用指令addl。指令如下:

3.3.6关系操作

hello.c中存在两个关系操作,分别为:

  1. 条件判断语句if(argc!=5):汇编代码将这条代码翻译为:

使用了cmp指令比较立即数4和参数argc大小,并且设置了条件码。根据条件码,如果不相等则执行该指令后面的语句,否则跳转到.L2。

  1. 在for循环每次循环结束要判断一次i<10,判断循环条件被翻译为:

同(1),设置条件码,并通过条件码判断跳转到什么位置。

3.3.7控制转移指令

设置过条件码后,通过条件码来进行控制转移,在本程序中存在两个控制转移:

(1)

判断argc是否为4,如果不为4,则执行if语句,否则执行其他语句,在汇编代码中则表现为如果条件码为1,则跳到.L2,否则执行cmpl指令后的指令。

(2)

在for循环每次结束判断一次i<10,翻译为汇编语言后,通过条件码判断每次循环是否跳转到.L4。而在for循环初始要对i设置为0,如下:

然后直接无条件跳转到.L3循环体。

3.3.8函数操作

(1)main函数

参数传递:该函数的参数为int argc,,char*argv[]。具体参数传递地址和值都在前面阐述过。

函数调用:通过使用call内部指令调用语句进行函数调用,并且将要调用的函数地址数据写入栈中,然后自动跳转到这个调用函数内部。main函数里调用了printf、exit、sleep函数。

局部变量:使用了局部变量i用于for循环。具体局部变量的地址和值都在前面阐述过。

(2)printf函数

参数传递:printf函数调用参数argv[1],argv[2]。

函数调用:该函数调用了两次。第一次将寄存器%rdi设置为待传递字符串"用法:Hello学号姓名 秒数!\n"的起始地址;第二次将其设置为“Hello %s  %s\n”的起始地址。具体已在前面讲过。使用寄存器%rsi完成对argv[1]的传递,用%rdx完成对argv[2]的传递。

(3)exit函数

参数传递与函数调用:

将rdi设置为1,再使用call指令调用函数。

(4)atoi、sleep函数

参数传递与函数调用:

可见,atoi函数将参数argv[3]放入寄存器%rdi中用作参数传递,简单使用call指令调用。

然后,将转换完成的秒数从%eax传递到%edi中,edi存放sleep的参数,再使用call调用。

(5)getchar函数

无参数传递,直接使用call调用即可。

3.3.9类型转换

atoi函数将字符中转换为sleep函数需要的整型参数.

3.4 本章小结

这一章介绍了C编译器如何把hello.i文件转换成hello.s文件的过程,简要说明了编译的含义和功能,演示了编译的指令,并通过分析生成的hello.s文件中的汇编代码,探讨了数据处理,函数调用,赋值、算术、关系等运算以及控制跳转和类型转换等方面,比较了源代码和汇编代码分别是怎样实现这些操作的。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

汇编是指汇编器(as)将包含汇编语言的.s文件翻译为机器语言指令,并把这些指令打包成为一个可重定位目标文件的格式,生成目标文件.o文件。.o文件是一个二进制文件,包含main函数的指令编码。

汇编就是将高级语言转化为机器可直接识别执行的代码文件的过程,汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式。 .o 文件是一个二进制文件,它包含程序的指令编码。

4.2 在Ubuntu下汇编的命令

gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o

4.3 可重定位目标elf格式

在shell中输入readelf -a hello.o > hello.elf 指令获得 hello.o 文件的 ELF 格式:

4.3.1生成ELF格式的可重定位目标文件

典型的ELF格式的可重定位目标文件的结构及其对应内容如下:

(1)ELF头

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

(2)节头(section header)

记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。

(3)重定位节

.rel.text节是一个.text节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改,而调用本地函数的指令不需修改。可执行目标文件中不包含重定位信息。

.rela.text节包含的信息

偏移量

代表需要进行重定向的代码在.text或.data节中的偏移位置

信息

包括symbol和type两部分,其中symbol占前半部分,type占后半部分,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型

类型

重定位到的目标的类型

加数

计算重定位位置的辅助信息

如图,需要重定位的内容如下:

(4)符号表

.symtab节中包含ELF符号表,这张符号表包含一个条目的数组,存放一个程序定义和引用的全局变量和函数的信息。该符号表不包含局部变量的信息。

符号表如下:

4.4 Hello.o的结果解析

shell中输入 objdump -d -r hello.o > hello.asm 指令输出hello.o的反汇编文件。

Hello.o与hello.s的对照分析如下:

(1)增加机器语言

每一条指令增加了一个十六进制的表示,即该指令的机器语言。例如,在hello.s中的一个cmpl指令表示为

而在反汇编文件中表示为

(2)操作数进制

反汇编文件中的所有操作数都改为十六进制。如(1)中的例子,立即数由hello.s中的$4变为了$0x4,地址表示也由-20(%rbp)变为-0x14(%rbp)。可见只是进制表示改变,数值未发生改变。

(3)分支转移

反汇编的跳转指令中,所有跳转的位置被表示为主函数+段内偏移量这样确定的地址,而不再是段名称(例如.L3)。例如下面的jmp指令,反汇编文件中为

而hello.s文件中为

(4)函数调用

反汇编文件中对函数的调用与重定位条目相对应。观察下面两个call指令调用函数,在hello.s中为

而在反汇编文件中调用函数为

在可重定位文件中call后面不再是函数名称,而是一条重定位条目指引的信息。

4.5 本章小结

本章着重阐述了汇编语言的定义及其所承担的功能角色。以Ubuntu系统下的hello.s文件作为范例,详细描述了将其汇编为hello.o文件的具体步骤,并进一步生成ELF格式的可执行文件hello.elf。进而将可重定位目标文件转换为ELF格式,以便更深入地观察文件内容,并对文件中的各个节进行逐一解析。

通过对hello.o的反汇编代码(已保存至hello.asm文件中)与原始hello.s文件的对比分析,揭示了汇编语言与机器语言之间的转换过程及其细节。此外还探讨了机器在链接过程中所进行的一系列准备工作,从而更加清晰地理解汇编语言的工作原理及其在软件开发流程中的关键作用。

(第41分)

5章 链接

5.1 链接的概念与作用

5.1.1链接的概念

链接(linkng)是将各种代码和数据片段收集并组合为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行与编译时(compile time),也就是在源代码被翻译为机器代码时;也可以执行与加载时(load time),也就是程序被加载器加载到内存并执行时:甚至执行于运行时。

在现代系统中,链接是由叫做链接器(1iker)的程序自动执行的,它们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用。

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.3 可执行目标文件hello的格式

  1. ELF 头(ELF Header)

hello2.elf中的ELF头与hello.elf中的ELF头包含的信息种类基本相同,以 描述了生成该文件的系统的字的大小和字节顺序的16字节序列 Magic 开始,剩下的部分包含帮助链接器语法分析和解释目标文件的信息。与hello.elf相比较,hello2.elf中的基本信息未发生改变(如Magic,类别等),而类型发生改变,程序头大小和节头数量增加,并且获得了入口地址。

2.节头

hello2.elf中的节头包含了文件中出现的各个节的语义,包括节的类型、位置、偏移量和大小等信息。与hello.elf相比,其在链接之后的内容更加丰富详细。

3.程序头

程序头部分是一个结构数组,描述了系统准备程序执行所需的段或其他信息。

4.Dynamic section

5.Symbol table

符号表中保存着定位、重定位程序中符号定义和引用的信息,所有重定位需要引用的符号都在其中声明(此处仅截取部分展示)。

5.4 hello的虚拟地址空间

根据计算机系统的特性,程序被载入至地址0x400000~0x401000中。在该地址范围内,每个节的地址都与前一节中节对应的 Address 相同。根据edb查看的结果,在地址空间0x400000~0x400fff中存放着与地址空间0x400000~0x401000相同的程序,在0x400fff之后存放的是.dynamic到.shstrtab节的内容。

程序从地址0x400000开始到0x401000被载入,虚拟地址从0x4000000x400f0结束,根据5.3中的节头部表,可以通过edb找到各段的信息。

如.interp节,在hello.elf文件中能看到开始的虚拟地址:

在edb中找到对应的信息:

同样的,我们可以找到如.text节的信息:

5.5 链接的重定位过程分析

在Shell中使用命令objdump -d -r hello > hello2.asm生成反汇编文件hello2.asm,与第四章中生成的hello.o.asm文件进行比较,其不同之处如下:

(1)链接后函数数量增加

       链接后的反汇编文件hello2.asm中,多出了.pltputs@pltprintf@pltgetchar@pltexit@pltsleep@plt等函数的代码。这是因为动态链接器将共享库中hello.c用到的函数加入可执行文件中。

(2)函数调用指令call的参数发生变化

       在链接过程中,链接器解析了重定位条目,call之后的字节代码被链接器直接修改为目标地址与下一条指令的地址之差,指向相应的代码段,从而得到完整的反汇编代码。

(3)跳转指令参数发生变化

       在链接过程中,链接器解析了重定位条目,并计算相对距离,修改了对应位置的字节代码为PLT 中相应函数与下条指令的相对地址,从而得到完整的反汇编代码。

5.5.2重定位过程

重定位由两步组成:

(1)重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的聚合节。然后链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。至此程序中每条指令和全局变量都有唯一的运行内存地址。

(2)重定位节中的符号引用。这一步中链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目的数据结构。

(3)重定位过程地址计算方法如下:

5.6 hello的执行流程

5.6.1过程

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

(I)开始执行:_start、_libe_start_main

(2)执行main:_main、printf、_exit、_sleep、getchar

(3)退出:exit

5.6.2子程序名或地址

程序名               程序地址

_start                0x4010f0

_libc_start_main       0x2f12271d

main                0x401125

_printf               0x4010a0

_sleep               0x4010e0

_getchar             0x4010b0

_exit                0x4010d0

5.7 Hello的动态链接分析

5.7 Hello的动态链接分析

   动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。延迟绑定是通过GOT和PLT实现的,根据hello.elf文件可知,GOT起始表位置为:0x404000:

GOT表位置在调用dl_init之前0x404008后的16个字节均为0:

调用了dl_init之后字节改变了:

对于变量而言,利用代码段和数据段的相对位置不变的原则去计算正确地址。

对于库函数而言,需要pltgot合作。plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。接下来执行程序的过程中,就可以使用过程链接表plt和全局偏移量表got进行动态链接。

5.8 本章小结

本章首先对链接的基本概念及其所发挥的核心作用进行了详细阐述。随后,通过实际操作展示了如何利用命令链接生成名为hello的可执行文件。进一步对hello文件的ELF格式内容进行了深入观察,以揭示其内在结构和组织方式。为了更全面地理解hello文件的执行特性,借助edb工具对其虚拟地址空间的使用情况进行了细致的观察与分析。最后以hello程序为典型案例,对重定位过程、执行过程以及动态链接机制进行了全面而深入的剖析,从而加深了对链接机制的理解与掌握。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

进程的经典定义就是一个执行中程序的实例。进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程为程序提供了一种假象,程序好像是独占的使用处理器和内存,处理器好像是无间断地一条接一条地执行我们程序中的指令。进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中。

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

Shell是一个交互型应用级程序,也被称为命令解析器,它为用户提供一个操作界面,接受用户输入的命令,并调度相应的应用程序。

首先从终端读入输入的命令,对输入的命令进行解析,如果该命令为内置命令,则立即执行命令,否则调用fork创建一个新的子进程,在该子进程的上下文中执行指定的程序。判断该程序为前台程序还是后台程序,如果为前台程序则等待程序执行结束,若为后台程序则将其放回后台并返回。在过程中shell可以接受从键盘输入的信号并对其进行处理。

6.3 Hello的fork进程创建过程

首先用户再shel1界面输入指令:./hel1o 2022111099 梁志

Shell判断该指令不是内置命令,于是父进程调用fork函数创建一个新的子进程,该子进程得到与父进程用户级虚拟地址空间相同的一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程与父进程最大的区别就是具有不同的PID。在父进程中,fork返回子进程的PID,而在子进程中fork返回0,返回值提供一个明确的方法来分辨程序是父进程还是在子进程中执行。

6.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行一个程序。函数声明如下:

int execve(const char *filename, const char *argv[], const char *envp[]);

execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并不返回。main函数运行时,用户栈的结构如图所示:

6.5 Hello的进程执行

hello程序在运行时,进程提供给应用程序的抽象有:(1)一个独立的逻辑控制流,它提供一个假象,好像我们的进程独占地使用处理器;(2)一个私有的地址空问,它提供一个假象,好像我们的程序独占地使用CPU内存。

操作系统提供的抽象有:

(1)逻辑控制流。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称为逻辑流。一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发地运行。

(2)上下文切换。操作系统内核使用一种称为上下文切换的叫高层形式的异常控制流来实现多任务。内核为每一个进程维持一个上下文。上下文就是内核重新启动一个被抢占的进程所需状态。

(3)时间片。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。

(4)用户模式和内核模式。处理器通常使用某个控制寄存器中的一个模式位来提供这种功能。当设置了模式位时,进程就运行在内核模式里。一个运行在内核模式的进程可以执行指令集中的所有指令且可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,也不能直接引用地址空间中内核区内的代码和数据。

(5)上下文信息。上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。hello程序执行过程中,在进程调用execve函数后,进程就为hello程序分配新的虚拟地址空间,开始时程序运行在用户模式中,调用printf函数输出“Hello  2021113211 郑文翔”,之后调用sleep函数,进程进入内核模式,运行信号处理程序,再返回用户模式,运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。

6.6 hello的异常与信号处理

6.6.1异常的分类

6.6.2异常的处理方式

6.6.3运行结果及相关命令

1)正常运行状态

在程序正常运行时,打印8次提示信息,以输入回车为标志结束程序,并回收进程。

2)运行时按下Ctrl + C

按下Ctrl + CShell进程收到SIGINT信号,Shell结束并回收hello进程。

3)运行时按下Ctrl + Z

按下Ctrl + ZShell进程收到SIGSTP信号,Shell显示屏幕提示信息并挂起hello进程。

ps

(4)hello进程的挂起可由psjobs命令查看,可以发现hello进程确实被挂起而非被回收,且其job代号为1

5Shell中输入pstree命令,可以将所有进程以树状图显示:

(6)输入kill命令,则可以杀死指定(进程组的)进程:

(7) 输入fg 1则命令将hello进程再次调到前台执行,可以发现Shell首先打印hello的命令行命令,hello再从挂起处继续运行,打印剩下的语句。程序仍然可以正常结束,并完成进程回收。

8)不停乱按

       在程序执行过程中乱按所造成的输入均缓存到stdin,当getchar的时候读出一个’\n’结尾的字串(作为一次输入),hello结束后,stdin中的其他字串会当做Shell的命令行输入。

6.7本章小结

本章主要聚焦于对计算机系统中进程与shell的深入探讨。起始借助一个基础的hello程序,简明扼要地阐释了进程的基本概念及其在系统中所发挥的关键作用,同时亦对shell的功能及其处理流程进行了概述。随后详尽剖析了hello程序的进程创建、启动以及执行过程,旨在深入理解进程的生命周期及其与系统资源的交互方式。最后本章还对hello程序可能遭遇的异常情况进行了梳理,并对运行结果中出现的各类输入进行了详尽的解释与说明,旨在提升对程序异常处理及结果分析的能力。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

7.1.1逻辑地址

在具有地址转换功能的计算机中,访问指令给出的地址称为逻辑地址或相对地址。逻辑地址需要经过计算或转换才能得到内存中的物理地址。逻辑地址由段标识符和段内相对地址的偏移量组成,是由程序hello产生的与段相关的偏移地址部分。

7.1.2线性地址

线性地址是从逻辑地址到物理地址转换过程中的一个中间步骤。程序hello的代码生成逻辑地址,在分段机制中,逻辑地址是段内的偏移地址,加上基地址就得到线性地址。

7.1.3虚拟地址

程序访问内存时使用的逻辑地址即虚拟地址。虚拟地址通过地址翻译得到物理地址。虚拟地址与实际物理内存容量无关,是hello程序中的虚拟地址。

7.1.4物理地址

存储器以字节为单位存储信息,每个字节单元都有一个唯一的存储器地址,称为物理地址。物理地址是hello程序的实际地址或绝对地址。

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

Intel处理器从逻辑地址到线性地址的变换通过段式管理的方式实现。每个程序在系统中都保存着一个段表,段表保存着该程序各段装入主存的状况信息,包括段号或段名、段起点、装入位、段的长度、主存占用区域表、主存可用区域表等,从而方便进行段式管理。

在段寄存器中,存放着段选择符,可以通过段选择符来得到对应段首地址。段选择符的结构如下:

图 48 段选择符的情况

其包含三部分:索引,TI,RPL

索引:用来确定当前使用的段描述符在描述符表中的位置;

TI:根据TI的值判断选择全局描述符表(TI=0,GDT)或选择局部描述符表(TI=1,LDT);

RPL:判断重要等级。RPL=00,为第0级,位于最高级的内核,RPL=11,为第3级,位于最低级的用户状态;

通过一个索引,可以定位到段描述符,进而通过段描述符得到段基址。段基址与偏移量结合就得到了线性地址,虚拟地址。

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

线性地址(VA)到物理地址(PA)之间的转换通过对虚拟地址内存空间进行分页的分页机制完成。

通过7.2节中的段式管理过程,可以得到了线性地址/虚拟地址,记为VA。虚拟地址可被分为两个部分:VPN(虚拟页号)和VPO(虚拟页偏移量),根据计算机系统的特性可以确定VPN与VPO的具体位数,由于虚拟内存与物理内存的页大小相同,因此VPO与PPO(物理页偏移量)一致。而PPN(物理页号)则需通过访问页表中的页表条目(PTE)获取,如下图所示。

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

若PTE的有效位为1,则发生页命中,可以直接获取到物理页号PPN,PPN与PPO共同组成物理地址。

若PTE的有效位为0,说明对应虚拟页没有缓存到物理内存中,产生缺页故障,调用操作系统的内核的缺页处理程序,确定牺牲页,并调入新的页面。再返回到原来的进程,再次调用导致缺页的指令。此时发生页命中,获取到PPN,与PPO共同组成物理地址。

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

针对Intel Core i7 CPU研究VA到PA的变换。

Intel Core i7 CPU的基本参数如下:

  1. 虚拟地址空间48位(n=48)
  2. 物理地址空间52位(m=52)
  3. TLB四路十六组相连
  4. L1,L2,L3块大小为64字节
  5. L1,L2八路组相连
  6. L3十六路组相连
  7. 页表大小4KB(P=4x1024=2^12),四级页表,页表条目(PTE)大小8字节

由上述信息可以得知,VPO与PPO有p=12位,故VPN为36位,PPN为40位。单个页表大小4KB,PTE大小8字节,则单个页表有512个页表条目,需要9位二进制进行索引,而四级页表则需要36位二进制进行索引,对应着36位的VPN。TLB有16组,故TLBI有t=4位,TLBT有36-4=32位。

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

如图所示, CPU产生虚拟地址VA,并将其传送至MMU,MMU使用前36位VPN作为TLBT(前32位)+TLBI(后4位)在TLB中进行匹配,若命中,则得到PPN(40bit)与VPO(12bit)组合成物理地址PA(52bit)。若TLB没有命中,则MMU向页表中查询,由CR3确定第一级页表的起始地址,VPN1(9bit)确定在第一级页表中的偏移量,查询出PTE,如果在物理内存中且权限符合,则执行下一步确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA,并向TLB中添加条目。多级页表的工作原理展示如下:

s

若查询PTE的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误。

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

因为三级Cache的工作原理基本相同,所以在这里以L1 Cache为例,介绍三级Cache支持下的物理内存访问。

L1 Cache的基本参数如下:

  1. 8路64组相连
  2. 块大小64字节

由L1 Cache的基本参数,可以分析知:

块大小64字节→需要6位二进制索引→块偏移6位

共64组→需要6位二进制索引→组索引6位

余下标记位→需要PPN+PPO-6-6=40位

故L1 Cache可被划分如下(从左到右):

CT(40bit)CI(6bit)CO(6bit)

在7.4中我们已经由虚拟地址VA转换得到了物理地址PA,首先使用CI进行组索引,每组8路,对8路的块分别匹配CT(前40位)如果匹配成功且块的valid标志位为1,则命中(hit),根据数据偏移量CO取出相应的数据后返回。

若没有匹配成功或者匹配成功但是标志位是1,则不命中(miss),向下一级缓存中请求数据(请求顺序为L2 Cache→L3 Cache→主存,若仍不命中才继续向下一级请求)。查询到数据之后,需要对数据进行读入,一种简单的放置策略如下:若映射到的组内有空闲块,则直接放置在空闲块中,若当前组内没有空闲块,则产生冲突(evict),采用LFU策略进行替换。

7.6 hello进程fork时的内存映射

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

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

7.7 hello进程execve时的内存映射

execve函数加载并运行hello需要以下几个步骤:

  1. 删除已存在的用户区域

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

  1. 映射私有区域

为新程序的代码、数据、bss和栈区域创建新的私有的、写时复制的区域结构。其中,代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。

  1. 映射共享区域

若hello程序与共享对象或目标(如标准C库libc.so)链接,则将这些对象动态链接到hello程序,然后再映射到用户虚拟地址空间中的共享区域内。

  1. 设置程序计数器

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

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

发生一个缺页异常后,控制会转移到内核的缺页处理程序。判断虚拟地址是否合法,若不合法,则产生一个段错误,然后终止这个进程。

若操作合法,则缺页处理程序从物理内存中确定一个牺牲页,若该牺牲页被修改过,则将它换出到磁盘,换入新的页面并更新页表。当缺页处理程序返回时,CPU 再次执行引起缺页的指令,将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面现在缓存在物理内存中,所以就会命中,主存将所请求字返回给处理器。

7.9动态存储分配管理

动态内存管理的基本方法与策略介绍如下:

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

具体而言,分配器分为两种基本风格:显式分配器、隐式分配器。

显式分配器:要求应用显式地释放任何已分配的块。

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

下面介绍动态存储分配管理中较为重要的概念:

  1. 隐式链表

堆中的空闲块通过头部中的大小字段隐含地连接,分配器通过遍历堆中所有的块,从而间接遍历整个空闲块的集合。

对于隐式链表,其结构如下:

图 51 隐式链表的结构

  1. 显式链表

在每个空闲块中,都包含一个前驱(pred)与后继(succ)指针,从而减少了搜索与适配的时间。

显式链表的结构如下:

图 52 显式链表的结构

  1. 带边界标记的合并

采取使用边界标记的堆块的格式,在堆块的末尾为其添加一个脚部,其为头部的副本。添加脚部之后,分配器就可以通过检查前面一个块的脚部,判断前面一个块的起始位置和状态。从而实现快速合并,减小性能消耗。

  1. 分离存储

维护多个空闲链表,其中,每个链表的块具有相同的大小。将所有可能的块大小分成一些等价类,从而进行分离存储。

7.10本章小结

本章详细阐述了hello程序的存储器地址空间结构,深入剖析了Intel架构下的段式内存管理机制,并对hello程序所应用的页式管理进行了系统介绍。同时,本章还着重探讨了虚拟地址(VA)到物理地址(PA)的转换过程,以及物理内存的访问方式。此外,本章还对hello进程在fork和execve操作时的内存映射机制进行了详细解析,并对缺页故障及其引发的缺页中断处理流程进行了深入阐述。最后,本章还涉及了动态存储分配管理的相关内容。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

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

8.3 printf的实现分析

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

https://www.cnblogs.com/pianist/p/3315801.html

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

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

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

8.4 getchar的实现分析

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

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

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

8.5本章小结

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

(第81分)

结论

hello所经历的过程:

首先由程序员将hello代码从键盘输入,依次要经过以下步骤:

1、预处理(cpp)。将hello.c进行预处理,将文件调用的所有外部库文件合并展开,生成一个经过修改的hello.i文件。

2、编译(ccl)。将hello.i文件翻译成为一个包含汇编语言的文件hello.s。

3、汇编(as)。将hello.s翻译成为一个可重定位目标文件hello.o。

4、链接(ld)。将hello.o文件和可重定位目标文件和动态链接库链接起来,生成一个可执行目标文件hello。

5、运行。在shel1中输入./hello 2021113211 郑文翔。

6、创建进程。终端判断输入的指令不是shell内置指令,于是调用fork函数创建一个新的子进程。

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

8、执行指令:CPU为进程分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流。

9、访问内存:MU将程序中使用的虚拟内存地址通过页表映射成物理地址。

10、信号管理:当程序在运行的时候我们输入Ctrl+c,内核会发送SIGINT信号给进程并终止前台作业。当输入Ctrl+z时,内核会发送SIGTSTP信号给进程,并将前台作业停止挂起。

11、终止:当子进程执行完成时,内核安排父进程回收子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有数据结构。

感悟:经过本次实验,我深刻体会到计算机系统的精细与强大。即便是看似简单的任务,也离不开计算机内部复杂而精确的操作过程,这充分展现了计算机系统严谨的逻辑性和现代工艺的精湛。此外,本次实验也让我对计算机技术的发展有了更为深刻的认识。在现代社会,计算机技术已经深入到各个领域,成为了推动社会进步和发展的重要力量。无论是科学研究、工业生产还是日常生活,都离不开计算机技术的支持。

同时,我也意识到计算机系统的稳定性和安全性至关重要。在本次实验中,我深刻体会到了计算机系统在处理任务时的严谨性和精确性,这也让我更加认识到在设计和使用计算机系统时,必须注重系统的稳定性和安全性。

附件

文件名

功能

hello.c

源程序

hello.i

预处理后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

汇编后得到的可重定位目标文件

hello.elf

readelf读取hello.o得到的ELF格式信息

hello.asm

反汇编hello.o得到的反汇编文件

hello1.asm

反汇编hello可执行文件得到的反汇编文件

hello

可执行文件

(附件0分,缺失 -1分)

参考文献

为完成本次大作业你翻阅的书籍与网站等

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值