CSAPP大作业

摘  要

本文将以hello.c为例,探索hello的一生,从编写完成经过预处理、编译、汇编、链接等过程形成可执行文件,再将可执行文件加载进内存并运行,最终终止回收。解析期间将串联起整个计算机系统知识体系,对于计算机系统课程有更深层次的学习,进而窥探其它程序的生命历程,进而领略计算机系统设计的巧妙。

关键词:Linux系统;计算机系统;P2P;020;hello的生命历程                            

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

目  录

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

1.1.1 P2P

P2P是指from program to process,即从程序到进程。在Linux系统下,hello.c 文件依次经过cpp预处理、ccl编译、as汇编、ld链接最终成为可执行目标程序hello,最后在shell中键入命令(./hello)后,操作系统(OS)的进程管理为其fork创建子进程 (process)。

1.1.2 020

020是指from zero to zero。通过在Shell下调用execve函数,系统会将hello文件载入内存,执行相关代码,当程序运行结束后, hello进程被回收,并由内核删除hello相关数据。

1.2 环境与工具

硬件:12th Gen Intel(R) Core(TM) i7-12700H CPU @ 2.70GHz

   NVIDA GeForce RTX 3060 Laptop GPU

   32GB RAM

   1T 512GB SSD

软件:Windows11 23H2

Ubuntu 20.04.4 LTS 64位

调试工具:Visual Studio Code 64-bit;

gedit,gcc,notepad++,readelf, objdump, hexedit, edb

1.3 中间结果

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

表格 1 中间结果

文件名

功能

hello.i

预处理后得到的文本文件

hello.s

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

hello.o

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

hello_elf.txt

用readelf读取hello.o得到的文本文件

hello.asm

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

hello1.elf

由hello可执行文件生成的.elf文件

hello1.asm

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

1.4 本章小结

本章简要介绍了hello 的P2P,020的具体含义,同时列出了论文研究时采用的具体软硬件环境和中间结果。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

概念:预处理是指在编译阶段之前执行的一系列操作,是在计算机处理程序过程中的第一步处理,根据以#开头的命令,预处理器会修改源程序内容

作用:

宏替换:预处理阶段会执行宏替换,将代码中定义的宏展开为实际的代码。这样可以减少代码的重复性,提高代码的可读性和维护性。

文件包含:预处理阶段会处理包含在源文件中的头文件,将头文件的内容插入到源文件中。这样可以实现代码的模块化和复用,减少重复编写相同代码的工作量。

条件编译:预处理阶段还会执行条件编译指令,根据条件判断来决定是否包含特定的代码段。这样可以根据不同的编译条件来生成不同的代码,实现跨平台的代码编写。

符号常量定义:预处理阶段可以定义符号常量,用来代表特定的数值或字符串,方便代码中的使用和维护。

2.2在Ubuntu下预处理的命令

具体指令:cpp hello.c>hello.i

图2.2 预处理命令

2.3 Hello的预处理结果解析

源代码文件为24行,预处理后为3059行,main函数对应部分在预处理文件末尾,1到3042行是头文件stdio.h unistd.h stdlib.h依次展开。预处理器首先去掉注释,然后根据路径展开头文件,如果还有以#开头的内容,预处理器会继续进行处理,直到hello.i文件中没有宏定义、文件包含及条件解析等内容。                     

 

2.3-1 预处理后头文件信息

 

2.3-2 预处理后main函数部分

2.3-3 源代码

2.4 本章小结

本章首先了解了预处理的概念和作用,然后结合预处理hello.c文件分析在ubuntu系统下预处理的流程,并对结果进行解析。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

概念:将人类可读的C语言源代码翻译成机器可读的代码的过程,编译器会将hello.i文件翻译成hello.s汇编语言文件

作用:

词法分析:编译的第一步是词法分析,将源代码分解成关键字、标识符、运算符和字面量等词法单元。这一步有助于识别代码的基本构建块。

语法分析:在词法分析之后,编译器检查代码的语法,以确保其遵循C语言的规则和语法。这一步有助于识别代码中的语法错误。

语义分析:语法验证完成后,编译器进行语义分析,检查代码的含义和正确性。它检查类型兼容性、变量声明、函数定义和C语言中定义的其他语义规则。

代码生成:在分析和验证代码后,编译器生成相应的机器代码或汇编代码,这些代码可以由计算机的处理器执行。这些机器代码是针对程序运行的目标体系结构或平台特定的。

代码优化:作为一个可选步骤,编译器可以执行代码优化技术,以提高生成代码的效率和性能。这包括删除冗余代码、重新排列指令和应用各种优化算法。

3.2 在Ubuntu下编译的命令

具体指令:gcc hello.i -S -o hello.s

3.2 编译命令

3.3 Hello的编译结果解析

3.3-1 hello.s(1)

3.3-1 hello.s(2)

3.3.1伪指令

伪指令

含义

.file

源文件名称

.text

代码段

.global

全局变量

.section  .rodata

只读代码段

.align

对齐方式

.type

表示是函数类型

 .string

表示是string类型

3.3.2 对数据类型的操作

1.常量

(1)字符串常量:printf函数输出用到的字符串,被储存在 .rodata中

源代码:15行:用法: Hello 学号 姓名 手机号 秒数!\n

        19行:Hello %s %s %s\n

汇编代码:

3.3.2-1 汇编代码1

(2)数字常量:for循环终止条件值、if条件判断值,通常储存在.text段,存储时使用

源代码:14行:if(argc!=5)

        18行:for(i=0;i<10;i++)

汇编代码:

3.3.2-2 汇编代码2

3.3.2-3 汇编代码3

2.变量

局部变量:局部变量是储存在栈中的某一个位置的或是直接储存在寄存器中

(1)i:储存在栈中地址为-4(%rbp)的位置

3.3.2-4 汇编代码4

(2)argc:储存在栈中地址为-20(%rbp)的位置

3.3.2-5 汇编代码5

(3)argv:首地址保存在栈中

3.3.2-6 汇编代码6

3.3.3 赋值操作:给i赋值为0

3.3.3 赋值操作

3.3.4 算术操作:在for循环中,对循环变量i的更新使用了++自增运算,汇编代码翻译成addl指令

3.3.4 算术操作

3.3.5 关系操作与跳转

hello.c中共出现两出关系操作,其一为判断argc,当其等于5时进行条件跳转

源代码:14行:if(argc!=5)

汇编代码:

3.3.5-1 汇编代码1

其二为for循环处对i的判断,当i大于等于9的时候将进行条件跳转。

源代码: 18行:for(i=0;i<10;i++)

汇编代码:

3.3.5-2 汇编代码2

3.3.6 数组、指针、结构操作

主函数main()的第二个参数是char *argv[](参数字符串数组指针),在argv数组中,argv[0]为输入程序的路径和名称字符串起始位置,argv[1]、argv[2]和argv[3]为其后的三个参数字符串的起始位置,按照基址-变址寻址法访问argv[1]、argv[2]和argv[3](由于指针char*大小为8字节,分别偏移8、16、24字节来访问)。

3.3.6 汇编代码

3.3.7函数操作

1.main函数

参数:int argc, char *argv[]

返回:正确返回0,错误返回1

3.3.7-1 main汇编代码

     2.printf函数

参数:第一次调用传入字符串参数首地址,第二次传入 argv[1]和argc[2]地址。

调用:主函数通过call指令调用

返回:返回值被忽略

3.3.7-2 printf第一次调用

3.3.7-3 printf第二次调用

3.exit函数

参数:传入参数为1,执行退出命令

调用:主函数通过call指令调用

返回:无,直接退出

3.3.7-4 exit汇编代码

4.sleep函数

参数:使用atoi()作为参数

调用:主函数通过call指令调用

返回:返回值被忽略

3.3.7-5 sleep汇编代码

5.atoi

参数:argv[4]指针值的副本

调用:主函数通过call指令调用

返回:转换后字符串的整数值

3.3.7-6 atoi汇编代码

6.getchar函数

参数:无

调用:主函数通过call指令调用

返回:返回值被忽略

3.3.7-7 getchar汇编代码

3.4 本章小结

首先了解了编译的概念及作用,通过编译得到hello.s为例研究了编译器处理各个数据类型及各种操作,进行了详细分析。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

概念:汇编是一种低级语言,用于与计算机硬件进行直接交互。汇编语言是一种与特定处理器架构相关的语言,它使用助记符来表示机器指令,以便程序员能够更容易地理解和编写底层的操作。

作用:将汇编代码根据特定的转换规则转换为二进制代码

4.2 在Ubuntu下汇编的命令

具体指令:gcc hello.s -c -o hello.o

4.2 汇编命令

4.3 可重定位目标elf格式

使用readelf -a hello.o > hello_elf.txt导出elf的文件

将hello.o中ELF格式相关信息重定向至文件hello_elf.txt

    

4.3 readelf命令

4.3.1 ELF头

ELF头以一个16字节的序列(Magic)开始,这个序列描述了生成文件的系统的字的大小和字节顺序。ELF头剩下部分的信息包含帮助连接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型和机器类型等。

4.3.1ELF

4.3.2 节头

包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息

4.3.2 节头

4.3.3 重定位节

重定位节记录了各段引用的符号相关信息,在链接时,需要通过重定位节对这些位置的地址进行重定位。链接器会通过重定位条目的类型判断如何计算地址值并使用偏移量等信息计算出正确的地址。

在这里,8 条重定位信息分别是对.L0(第一个 printf 中的字符串)、puts 函数、exit 函数、.L1(第二个 printf 中的字符串)、printf 函数、sleepsecs、sleep 函数、getchar 函数进行重定位声明。

4.3.3重定位节

4.3.4 Symbol table

.symtab是一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。例如本程序中的getchar、puts、exit等函数名都需要在这一部分体现

4.3.4 Symbol table

4.4 Hello.o的结果解析

使用objdump -d -r hello.o > hello.asm 对hello.o进行反汇编

4.4-1 生成hello.asm

4.4-2 hello.asm

 与hello.s对照分析:

1.进制不同:hello.s对于数字的表示是十进制的,而hello.o反汇编之后数字的表示是十六进制的。

2.函数调用:hello.s中,call指令使用的是函数名,而反汇编代码中call指令使用的是待链接器重定位的相对偏移地址,这些调用只有在链接之后才能确定运行时的实际地址,因此在.rela.text节中为其添加了重定位条目

3.分支转移:hello.s中,跳转指令的目标地址直接记为段名称;而在hello.asm中,跳转的目标为具体的地址,在机器代码中体现为目标指令地址与当前指令下一条指令的地址之差

4.hello.s中的全局变量、printf字符串等符号被替换成了待重定位的地址

4.5 本章小结

本章对汇编的概念、作用、可重定向目标文件的结构及对应反汇编代码等进行了较为详细的介绍。对hello.o的ELF头,Section头以及符号表进行了分析,可以看到Hello的跟深处的信息。本节还对hello.o的反汇编文件进行了解析,比较了相对于hello.s文件.o文件是怎么让机器更加理解的。

(第4章1分)


5链接

5.1 链接的概念与作用

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

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

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 链接命令

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

使用 readelf -a hello > hello1.elf导出elf文件

5.3 获得elf文件

5.3.1 ELF头

hello1.elf中的ELF头与hello.elf中的ELF头包含的信息种类基本相同,不同处为文件的Type发生了变化,从REL变成了EXEC,节头部数量也发生了变化,变为了27个。

5.3.1 ELF头

5.3.2 节头

包含了文件中出现的各个节的语义,包括节的类型、位置、偏移量和大小等信息,offset表示每个section的起始位置,size表示section的大小。

5.3.2 节头

5.3.3 程序头

 ELF可执行文件的连续的片被映射到了连续的内存段,从而很容易被加载到内存中。程序头部表描述了这种映射关系。

5.3.3 程序头

5.3.4 Section to Segment mapping

5.3.4 Section to Segment mapping

5.3.5 Dynamic section动态链接表

5.3.5 Dynamic section

5.3.6 重定位节

5.3.6 重定位节

5.3.7 Symbol table

符号表中保存着定位、重定位程序中符号定义和引用的信息,所有重定位需要引用的符号都在其中声明

5.3.7 Symbol table

5.4 hello的虚拟地址空间

使用edb加载hello

5.4-1 edb加载hello

Data Dump窗口中显示hello的虚拟空间内容,起始地址为0x401000,结束地址为0x401ff0。

5.4-2 Data Dump窗口

通过与Symbols窗口对照,可以发现各段均一一对应

5.4-3 对照

   

5.5 链接的重定位过程分析

使用命令objdump -d -r hello > hello1.asm生成反汇编文件hello1.asm

5.5-1 生成反汇编文件hello1.asm

与hello.o的不同:

  1. 虚拟地址不同:hello的反汇编代码虚拟地址从0x400000开始,hello.o的反汇编代码虚拟地址从0开始

(2)反汇编节数不同:hello的反汇编代码比hello.o的反汇编代码多了一些节(如.init, .plt, .plt.sec等)

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

5.5-2 跳转指令(例)

  1. 函数数量不同:hello的反汇编代码多出了.plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等函数的代码。

     

5.5-3 hello中的函数

5.6 hello的执行流程

程序名称

程序地址

hello!_init

0x0000000000401000

hello!_start

0x00000000004010f0

hello!main

0x0000000000401125

hello!puts@plt

0x0000000000401030

hello!printf@plt

0x0000000000401040

hello!getchar@plt

0x0000000000401050

hello!atoi@plt

0x0000000000401060

hello!exit@plt

0x0000000000401070

hello!sleep@plt

0x0000000000401080

hello!_init_array_end

0x0000000000403e50

hello!_init_array_start

-

hello!_data_start

0x0000000000404048

hello!_edata

0x000000000040404c

hello!_end

-

5.6 edb执行hello

5.7 Hello的动态链接分析

当程序调用一个由共享库定义的函数时,由于编译器无法预测这时候函数的地址是什么,因此这时,编译系统提供了延迟绑定的方法,将过程地址的绑定推迟到第一次调用该过程时。通过GOT和过程链接表PLT的协作来解析函数的地址。在加载时,动态链接器会重定位GOT中的每个条目,使它包含正确的绝对地址,而PLT中的每个函数负责调用不同函数。那么,通过观察edb,便可发现dl_init后.got.plt节发生的变化。

GOT[1]和GOT[2]之间发生了变化,查询相关内容可知GOT[1]保存的是指向已经加载的共享库的链表地址。GOT[2]是动态链接器在ld-linux.so模块中的入口。这样,接下来执行程序的过程中,就可以使用过程链接表PLT和全局偏移量表GOT进行动态链接。

调用dl_init之前.got.plt段的内容:

5.8-1 调用之前

调用dl_init之后.got.plt段的内容:

5.8-2 调用之后

   

5.8 本章小结

本章介绍了连接的过程。解释了程序是如何进行重定位的操作,把相同类型的数据放在同一个节的过程,同时也说明了链接的工作原理。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

概念:进程是指正在运行的程序的实例,它是操作系统进行资源分配和管理的基本单位,它包括程序的代码、数据、堆栈、寄存器和其他与程序执行相关的状态信息。

作用:

并发执行: 进程允许多个程序同时执行,操作系统通过对进程的调度和管理来实现并发执行。

资源管理: 进程使操作系统能够有效地管理系统资源,包括内存、CPU时间、I/O设备等,以便多个程序能够共享系统资源。

隔离性: 进程之间相互隔离,一个进程的错误或崩溃通常不会影响其他进程的执行,从而提高系统的稳定性和安全性。

通信和同步: 进程之间可以通过进程间通信(IPC)进行数据交换和同步操作,这对于实现复杂的系统功能和协作任务非常重要。

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

作用:Shell 是一个用C语言编写的交互型应用程序,代表用户运行其他程序。Shell 应用程序提供了一个界面,用户可以通过这个界面进行系统的基本操作,访问操作系统内核的服务。

处理流程:当用户提交了一个命令后,Shell首先判断它是否为内置命令,如果是就通过Shell内部的解释器将其解释为系统功能调用并转交给内核执行;若是外部命令或实用程序就试图在硬盘中查找该命令并将其调入内存,再将其解释为系统功能调用并转交给内核执行。

6.3 Hello的fork进程创建过程

根据shell的处理流程,输入命令./hello 2022112239 王梓轩 12211221222 2

父进程通过调用fork函数创建一个新的运行的子进程。调用fork函数后,新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。fork被调用一次,却返回两次,子进程返回0,父进程返回子进程的PID。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。

6.3 hello运行

6.4 Hello的execve过程

当hello的进程被创建之后,他会调用execve函数加载并调用程序。exevce函数在被调用时会在当前进程的上下文中加载并运行一个新程序,同时带有参数列表argv和环境变量列表envp。当出现错误时,exceve才会返回到调用程序。execve函数在加载了hello之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,函数的执行过程会覆盖当前进程的地址空间,但并没有创建一个新进程。新的程序仍然具有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。

6.5 Hello的进程执行

 在hello的运行过程中,若hello进程不被抢占,则正常执行;若被抢占,则进入内核模式,进行上下文切换,转入用户模式,调度其他进程。当hello调用sleep函数时,sleep函数会向内核发送请求将hello挂起,并进行上下文切换,进入内核模式切换到其他进程,切换回用户模式运行抢占的进程。与此同时,将 hello 进程从运行队列加入等待队列,由用户模式变成内核模式,并开始计时。当计时结束时,sleep函数返回,触发一个中断,使得hello进程重新被调度,将其从等待队列中移出,并内核模式转为用户模式。此时 hello 进程就可以继续执行其逻辑控制流。                 

6.6 hello的异常与信号处理

1.正常执行:打印十次提示信息,以输入回车为标志结束程序

6.6-1 正常执行

2.回车

按回车后会有空行,结束时也会多空行

6.6-2 按回车

3.Ctrl-C

Shell结束并回收hello进程

6.6-3按Ctrl-C

4.Ctrl-Z

Shell显示提示信息 [1]  + 3206 suspended ,并挂起hello进程。

6.6-4 按Ctrl-Z

5.ps

Hello进程挂起后,输入ps可查看状态

6.6-5 ps

6.jobs

Hello进程挂起后,输入jobs可查看状态

6.6-6 jobs

7.pstree

可以将所有进程以树状图显示

6.6-7pstree

8.fg

hello进程再次调到前台执行,Shell首先打印hello的命令行命令,hello再从挂起处继续运行,打印剩下的语句

6.6-8 fg

9.Kill

可以杀死指定进程

6.6-9 kill

6.7本章小结

本章主要介绍了hello可执行文件的执行过程,包括进程创建、加载和终止,以及通过键盘输入等过程。从创建进程到进程并回收进程,这一整个过程中需要各种各样的异常和中断等信息。程序的高效运行离不开异常、信号、进程等概念,正是这些机制支持hello能够顺利地在计算机上运行。

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址(Logical Address):逻辑地址是由程序生成的地址,它是相对于程序的视角而言的。在程序中,逻辑地址用于访问内存中的数据和指令。逻辑地址是一个抽象的概念,它不直接对应于物理内存中的位置。逻辑地址通常是一个虚拟地址。

线性地址(Linear Address):线性地址是逻辑地址经过地址转换后得到的地址。在计算机系统中,使用地址转换机制将逻辑地址转换为线性地址。线性地址是一个虚拟地址,它对应于计算机系统中的虚拟内存空间。线性地址可以被操作系统和硬件直接使用。

虚拟地址(Virtual Address):虚拟地址是程序中使用的地址,它是相对于虚拟内存空间而言的。虚拟地址是一个抽象的概念,它不直接对应于物理内存中的位置。虚拟地址通过地址转换机制转换为线性地址或物理地址。

物理地址(Physical Address):物理地址是计算机系统中实际的内存地址,它对应于物理内存中的位置。物理地址是由硬件生成的,用于访问内存中的数据和指令。

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

在Intel的x86体系结构中,段式管理通过段描述符表(Global Descriptor Table,GDT)和局部描述符表(Local Descriptor Table,LDT)来实现。GDT是一个全局的段描述符表,而LDT是每个进程私有的段描述符表。段描述符包含了段的基地址、段限长、访问权限等信息。

逻辑地址到线性地址的转换过程如下:

首先,CPU使用段选择子从GDT或LDT中找到对应的段描述符。

然后,CPU将段描述符中的基地址与逻辑地址中的段内偏移量相加,得到线性地址。

最后,线性地址再经过分页机制转换为物理地址,以实际访问内存。

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

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

线性地址(虚拟地址)由虚拟页号VPN和虚拟页偏移VPO组成。首先,MMU从线性地址中抽取出VPN,并且检查TLB,看他是否因为前面某个内存引用缓存了PTE的一个副本。TLB从VPN中抽取出TLB索引和TLB标记,查找对应组中是否有匹配的条目。若命中,将缓存的PPN返回给MMU。若不命中,MMU需从页表中的PTE中取出PPN,若得到的PTE无效或标记不匹配,就产生缺页,内核需调入所需页面,重新运行加载指令,若有效,则取出PPN。最后将线性地址中的VPO与PPN连接起来就得到了对应的物理地址。

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

TLB是一个位于MMU中,关于PTE的一个缓存,被称为快表。快表是一个小的、虚拟寻址的缓存,其中每一行均保存了一个由单个PTE组成的块。TLB有高度的相联性。

四级页表是一种多级页表,多级页表的主要目的是用于压缩页表。在地址翻译过程中,虚拟的地址页号VPN被分为了k个,每一个VPNi都是一个指向第i级页表的索引。当1 <= j <= k-1时,都是指向第j+1级的某个页表。第k级页表中的每个PTE包含某个物理页面的PPN,或者时一个磁盘块的地址。为构造物理地址,MMU需要访问k个PTE,之后才能确定PPN。Intel Core i7采用的是一个四级页表层次结构,每个VPNi有9位,当未命中时,36位的VPN被分为VPN1、VPN2、VPN3、VPN4,每个VPNi被用作到一个页表的偏移量。CR3寄存器包含L1 页表的物理地址,VPN1提供到一个L1 PTE的偏移量,这个PTE包含某个L2页表的基址。VPN2提供到这个L2页表中某个PTE的偏移量,以此类推。最后得到的L4 PTE包含了需要的物理页号,和虚拟地址中的VPO连接起来就得到相应的物理地址。

TLB能够加速地址翻译,而多级页表能够对页表进行压缩,便于大量存储。

在从VA翻译得到PA的过程中,MMU首先用VPN向TLB申请请求对应的PTE,如果命中,那么直接跳过后面的步骤;之后MMU生成PTE地址,从高速主存请求得到PTE,高速缓存或主存会向MMU返回PTE。若PTE有效位为0,说明缺页,MMU触发缺页异常,缺页处理程序确定物理内存中的牺牲页(若页面修改,则换出到磁盘)。之后缺页处理程序调入新的页面,并更新PTE。之后却也处理程序返回原进程,并重新执行导致缺页的指令。

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

在寻找一个虚拟地址时,CPU会优先到TLB中寻找,查看VPN是否已经缓存。如果页命中的话,就直接获取PPN;如果没有命中的话就需要查询多级页表,得到物理地址PA,之后再对PA进行分解,将其分解为标记(CT)、组索引(CI)、块便宜(CO),之后再检测物理地址是否在下一级缓存中命中。若命中,则将PA对应的数据内容取出返回给CPU;若不命中,则重复上述操作,直到找到。

7.6 hello进程fork时的内存映射

当fork 函数被shell调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID。为了给hello进程创建虚拟内存,它创建了hello进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。通过 fork 创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。当 fork 在新进程中返回时,新进程现在的虚拟内存刚好和调用 fork 时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。

7.7 hello进程execve时的内存映射

execve加载和运行hello程序会经过以下步骤:

删除已存在的用户区域:这里指在fork后创建于此进程用户区域中的shell父进程用户区域副本。

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

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

设置程序计数器(PC):设置此进程上下文中的程序计数器,使之指向hello代码区域的入口点。

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

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

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

7.9动态存储分配管理

定义:一种内存管理方法。对内存空间的分配、回收等操作在进程执行过程中进行,以便更好地适应系统的动态需求,提高内存利用率。

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

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

基本方法与策略:

1.带边界标签的隐式空闲链表分配器管理

带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的。

当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个符合大小的空闲块来放置这个请求块。分配器有三种放置策略:首次适配、下一次适配合最佳适配。在释放一个已分配块的时候需要考虑是否能与前后空闲块合并,减少系统中碎片的出现。

2.显示空间链表管理

显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。放置策略与上述放置策略一致。

7.10本章小结

本章主要介绍了hello 的存储器地址空间、intel 的段式管理、hello 的页式管理, VA 到PA 的变换、物理内存访问,hello进程fork、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

1.设备的模型化:文件

在Linux中,每个IO设备都被表示为一个文件。这些设备文件位于/dev目录下,以不同的名称标识不同的设备。

常见的设备文件包括:

/dev/sda:硬盘设备

/dev/tty:终端设备

/dev/usb:USB设备

/dev/video:摄像头设备

2.设备管理:unix io接口

    Unix的IO接口是一种用于管理输入输出设备的编程接口。它提供了一组函数和系统调用,使开发人员能够与设备进行交互并进行数据的读取和写入操作。

8.2 简述Unix IO接口及其函数

Unix IO接口:

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

lLinux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。头文件< unistd.h> 定义了常量STDIN_FILENO、STOOUT_FILENO和STDERR_FILENO,它们可用来代替显式的描述符值。

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

l读写文件:一个读操作就是从文件复制个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当时执行读操作会触发一个称为end-of-file(EOF)的条件,应用程序能检测这个条件。在文件末尾处并没有明确的“EOF符号”。类似地,写操作就是从内存复制个字节到一个文件,从当前文件位置k开始,然后更新k。

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

相关函数:

(1)int open(char *filename, int flags, mode_t mode);

此函数打开一个文件,将filename转换为一个文件描述符,并返回描述符数字(总是进程中未打开的最小描述符)。flags参数指明进程如何访问文件,mode参数指定新文件的访问权限位。

(2)int close(int fd);

此函数关闭一个打开的文件,关闭一个已关闭的描述符会出错。

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

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

(4)ssize_t write(int fd, const void *buf, size_t n);

此函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。

8.3 printf的实现分析

1.printf函数体

8.3-1 printf函数体

printf函数调用了vsprintf函数,最后通过系统调用函数write进行输出

2.vsprintf函数

8.3-2 vsprintf函数

vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出

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

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

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

8.4 getchar的实现分析

getchar是读入函数的一种。它从标准输入里读取下一个字符,相当于getc(stdin)。返回类型为int型,为用户输入的ASCII码或EOF。getchar可用宏实现:#define getchar() getc(stdin)。getchar有一个int型的返回值。当程序调用getchar时.程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar才开始从stdin流中每次读入一个字符。getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕。 异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章主要介绍了linux系统中的I/O设备基本概念和管理方法,同时简单介绍了printf和getchar函数的实现。

(第8章1分)

结论

hello程序的一生经历了如下过程:

1.预处理

hello.c文件通过cpp的预处理,得到了扩展后的源程序文件hello.i

2.编译

hello.i通过编译器的处理,被翻译成了汇编语言程序hello.s

3.汇编

在汇编器as的处理下,hello.s生成了可重定位文件hello.o

4.链接

链接器将重定位目标文件链接为可执行目标文件hello

5.生成子进程

在shell中输入指定命令shell调用fork函数为hello生成进程。

6.执行指令

在该进程被调度时,CPU为hello其分配时间片,在一个时间片中,hello享有CPU全部资源,PC寄存器一步一步地更新,CPU不断地取指,顺序执行自己的控制逻辑流;

7.访存

内存管理单元MMU将逻辑地址,一步步映射成物理地址,进而通过三级高速缓存系统访问物理内存/磁盘中的数据;

8.动态申请内存

printf 会调用malloc 向动态内存分配器申请堆中的内存;

9.信号处理

进程时刻等待着信号,如果运行途中键入ctr-c ctr-z 则调用shell 的信号处理函数分别进行停止、挂起等操作,对于其他信号也有相应的操作;

10.终止并被回收

Shell父进程等待并回收hello子进程,内核删除为hello进程创建的所有数据结构。

感悟:了解了计算机的基本工作原理,同时对操作系统有了更深入的了解。在完成大作业的过程中相当于回顾了一遍这学期的学习内容,对于计算机系统设计与实现也有了更深切的感悟。

(结论0分,缺失 -1分,根据内容酌情加分)


附件

文件名称

说明

对应本文章节

hello.i

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

第2章

hello.s

hello.i经编译得到的汇编代码ASCII文本文件

第3章

hello.o

hello.s经汇编得到的可重定位目标文件

第4章

hello_elf.txt

hello.o经readelf分析得到的文本文件

第4章

hello_dis.txt

hello.o经objdump反汇编得到的文本文件

第4章

hello

hello.o经链接得到的可执行文件

第5章

hello1_elf.txt

hello经readelf分析得到的文本文件

第5章

hello1_dis.txt

hello经objdump反汇编得到的文本文件

第5章

(附件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.

[7] Pianistx.printf 函数实现的深入剖析[EB/OL].2013[2021-6-9].

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

  1.  深入理解计算机系统(原书第三版).机械工业出版社, 2016.
  2.  月光下的麦克 readelf指令使用.2023-0201 http://t.csdnimg.cn/mpcVG

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值