程序人生-Hello’s P2P

摘  要

摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

关键词:关键词1;关键词2;……;                           

(摘要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简介

(根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。)

P2P指的是From Program to Process。是指一个程序hello.c经过预处理器(cpp)的预处理(hello.i),然后编译器(ccl)的编译(hello.s),之后通过汇编器(as)将转换成机器可执行的机器语言指令并打包成可重定位目标程序hello.o;接下来通过连接器将调用的标准C语言库中的函数对应的文件合并到hello.o中,生成了可执行目标文件hello,可以被加载到内存中,由系统执行。

020:在shell中使用execve函数加载并执行hello,将其映射为虚拟内存并通过程序入口后加载至物理内存,CPU开始为其分配时间片执行逻辑控制流,在程序运行完成后,shell父进程回收hello进程,内核删除对应数据结构。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

硬件环境

CPU:AMD Ryzen 7 5800H with Radeon Graphics

RAM:16G

Disk:WDC SN730

软件环境

Physical Machine:    Windows 10 21H2 x86_64

Virtual Machine:       Ubuntu 20.03 LTS x86_64 in VMWare Workstation 16 Player开发工具

Physical Machine:    Visual Studio 2019, mingw64

Virtual Machine:       CodeBlocks, gcc/gdb x64

1.3 中间结果

Hello.c

Hello源程序

Hello.i

Hello.c预处理生成的程序

Hello.s

Hello.i进过编译器编译后生成的程序

Hello.o

汇编器转换打包的可重定位目标程序

Hello

连接产生的可执行文件

hello_o_elf.txt

Hell.o的ELF

hello_elf.txt

链接生成的可执行文件的ELF表

hello_o_asm.txt

hello.o进行反汇编

Hello_obj.s

链接重定位的反汇编

1.4 本章小结

本章本次大作业进行了整体概述,介绍了实验环境与中间文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

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

概念:CPP预处理器通过源程序首行以#开头的命令对原始的C、程序进行一定的修改与处理,比如将相关头文件的的内容直接插入程序文本中,最终得到一个以.i为结尾的文件。

作用:

1.程序的预处理过程就是将预处理指令(可简单理解为以#开头的正确指令)转换为实际代码中的内容;

2.如果头文件中包含了其他头文件,也需要将头文件展开包含在内。

2.2在Ubuntu下预处理的命令

命令:cpp hello.c > hello.i 

2.3 Hello的预处理结果解析

打开hello.c与hello.i文件并对比,发现后者比前者多3037代码,hello.c的注释部分不在出现在hello.i中,并且main函数处于hello.i的结尾,前部分被插入大量的头文件(如stdio.h)。此外,hello.i中还插入了许多未被hello.c直接调用的头文件,后发现为递归调用的头文件。

2.4 本章小结

本章主要概括了预处理的流程以及操作步骤,并以一个hello.c的程序作为示例展示了这一过程:即预处理器对hello.c的文件进行插入直接或递归调用的头文件、删除代码中的注释部分,最终生成预处理文件的过程。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:利用编译程序从C语言编写的经过预处理的程序(hello.i)产生目标程序(hello.s)的过程。

作用:hello.i在编译器里经过词法分析、语法分析、源代码优化以及目标代码生成几个步骤,最终生成能够为计算机解读的汇编语言程序,在这个过程中对代码的优化包括但不局限与使用移位代替乘除操作。

3.2 在Ubuntu下编译的命令

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

3.3 Hello的编译结果解析

Hello.c中的数据类型由字符串、局部变量和数字常量构成。局部变量通常放在寄存器或栈之中以节约访问时间。例如局部变量int argc存放在-20(%rbp)中。

Hello.c中的数赋值操作为对局部变量进行赋值,如源程序的i=0的赋值语句使用movl进行编译。

Hello.c中的算数操作,如源程序的加法操作用addl实现。

Hello.c中的类型转换,如源程序中使用atoi()将argv[3]的返回值(字符串型)转换为整型,而编译器使用赋值movl和call指令调用函数完成操作。

Hello.c中的控制转移。源程序的if判断条件以cmp指令编译,for循环使用cmp+je实现。

Hello.c中的函数调用。源程序中多出使用getchar,exit等函数,在编译时使用call指令实现。

3.4 本章小结

本章主要概括介绍了编译器的作用,并以hello.c为历程探究了这一过程,编译器所执行的工作就是词法分析、语法分析、代码优化以及目标代码生成。在helllo.c的示例中,分析了生成的hello.s的语句以及对应hello.c中的语句,包括各种数据类型以及部分操作类型。通过这一章节的学习,对编译器的工作有了更深一步的了解。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器(as)将hello.s翻译成一个可重定位目标文件(relocatable object file)hello.o。

作用: 将编译后的文件翻译成机器语言二进制程序。

4.2 在Ubuntu下汇编的命令

指令:as hello.s -o hello.o

4.3 可重定位目标elf格式

       一个典型的EFL文件包含16字节的序列开始的ELF头、已编译程序的机器代码.text、已初始化的全局和静态C变量.data、未初始化的全局和静态C变量.bss、符号表.symtab、.text节中位置的列表.rel.txt、被模块引用或定义的所有全局变量的重定位信息.rel.data、调试符号表.debug、原始程序行号与.text中机器指令之间的映射.line、字符串表.strtab。在终端中输入指令readelf -a hello.o可以查看hello.o的elf,或使用readelf -a hello.o > hello_o_elf.txt将其保存在txt中。

       在hello_o_elf.txt中分析标注hello的elf。

ELF头:描述了生成该文件的系统的字的大小和字节顺序,以及ELF头的大小、目标文件的类型(REL可重定位文件)。

节头部表的偏移量、类型、地址。

重定位节rela.txt:包含.text节中位置的列表。当链接器把这个目标文件和其他文件组合时,需要修改这些位置。

重定位节,.rela.eh_frame',偏移量、信息、类型、符号值等。

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

_GLOBAL_OFFSET_TABLE:被模块定义的所有全局变量的重定位信息。

4.4 Hello.o的结果解析

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

使用objdump -d -r hello.o >hello_o_asm.txt对 hello.o进行反汇编,结果如下。

可以看出,反汇编代码每一句语句前都存在与之对应的地址与二进制指令(以十六进制展示),此外还发现:

1、反汇编文件中无.cfi_startproc等内容,该类型指令应该为进程开始、回收相应指令;

2、只读数据表示不同,反汇编文件中使用$0x0代替,比如对字符串的引用,反汇编为$0x0+%rip,而hello.s中为所在段+%rip,这是因为重定位文件还需要重新定位才可以确定其具体位置,故在反汇编文件中使用$0x0代替;

3、函数调用不同,hello.s文件中直接在call后加跳转指令的地址,而反汇编文件中jmp后接的是相对地址,这是因为反汇编文件需要进一步重新定位,此时无具体地址。

操作顺图

       4、操作数表示方法不同,hello.s中使用十进制而反汇编代码中使用十六进制表示。

机器语言是用二进制代码表示的计算机能直接识别和执行的一种机器指令的集合。 它是计算机的设计者通过计算机的硬件结构赋予计算机的操作功能。机器语言与计算机硬件结构密切相关。机器语言指令是一种二进制代码,由操作码和操作数两部分组成。操作码规定了指令的操作,是指令中的关键字,不能缺省。操作数表示该指令的操作对象。

汇编语言的主体是汇编指令。汇编指令和机器指令的差别在于指令的表示方法上,汇编指令是机器指令便于记忆的书写格式。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。

编译器能将汇编语言转换成机器指令。程序员用汇编语言写出源代码,再用汇编编译器将其编译为机器码,最后由计算机执行。

汇编语言中的操作数是具体要计算的数,而机器语言的操作数则代表操作的具体性质以及功能,一台计算机可能有几十条至几百条指令,每一条指令都有一个相应的操作码,计算机通过识别该操作码来完成不同的操作。

汇编语言的分支函数可以通过比较和跳转的组合实现,一般的两个步骤包含:第一步,用 CMP、AND 或 SUB 操作来修改 CPU 状态标志位;第二步,用条件跳转指令来测试标志位,并产生一个到新地址的分支。

4.5 本章小结

本章介绍了汇编器如何将一个预处理后的文件翻译成一个可重新定位的文件,并且在hello的ELF文件做了分析,对比了反汇编ELF文件与原ELF文件的异同,借此分析了汇编的作用

(第41分)

5章 链接

5.1 链接的概念与作用

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

作用:在软件开发中扮演重要角色,使分离编译(separate compilation)变为可能。开发大型应用程序时无需将整个程序作为一个巨大的源文件,而可以将其分解为更小的、更易于管理的多个模块,可以独立的修改和编译这些模块,当某个模块改变时,只需单独重新编译改变的某块并重新连接应用即可,而不必对整个巨大源文件进行重新编译。

5.2 在Ubuntu下链接的命令

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

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

命令: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的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

使用readelf -a hello > hello_elf.txt可以将链接生成的可执行文件的ELF表输出为txt格式以观察。

在hello_elf.txt中分析hello的ELF如下:

 

可以看到

5.4 hello的虚拟地址空间

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

在EDB中打开hello并于5.3中hello_elf.txt对照,首先将在EDB的Symbols窗口中各符号与5.3中hello_elf.txt对比,发现ELF头一致。

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

在终端中输入objdump -d -r hello > hello_obj.s,查看hello的反汇编文件并分析。

首先是汇编不同,在对hello.o进行反汇编得到的文件只包含.text这一节,而在链接后再反汇编得到的文件则包含了很多其他节,比如.init、.plt等。这是由于再链接器链接时的重定位会将该文件中使用的其他符号也定向到文件中。

另一个明显的是地址变化,在hello_o_asm.txt中重定位信息的部分均被链接后hello_obj.txt中的具体地址代替,这一类似现象还会在字符串引用、控制转移和函数调用中出现,这是由于链接器在工作中将重定向地址转换成具体地址而导致的。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

_dl_start (0x7f894f9badf0)→_dl_init (0x0x7f894f9cac10)→_start (0x401090)→_libc_start_main(0x7fce59403ab0)→_cxa_atexit(0x7f38b81b9430)→_libc_csu_init (0x4005c0)→_setjmp (0x7f38b81b4c10)→_sigsetjmp(0x7efd8eb79b70)→_sigjmp_save(0x7efd8eb79bd0)→main(0x401176)→(argc!=3)→puts (0x401030)→exit(0x401070)→print(0x401040)→sleep(0x401080) →getchar(0x4004d0)→_dl_runtime_resolve_xsave(0x7f5852241680)→_dl_fixup(0x7f5852239df0) →_uflow (0x7f593a9a10d0) →exit(0x7f889f672120)

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

动态链接:在程序运行或加载时,动态链接器将共享库加载到内存中并和程序链接起来。在hello_elf.txt中找到.got.plt所在的内存位置为0x404000,长度为0x48,在EDB中运行hello的执行情况,可以发现在dl_init前后一些想的变化。Z

在执行之前,在DateDump中看到0x404000处如左下,在执行dl_init之后,该处变为右下。分析说明在加载时动态链接时会重新定位动态链接库的相关内容。

5.8 本章小结

本章介绍了链接器如何工作。链接器如何解析引用,静态链接与动态链接以及之间的异同,分析了链接后的ELF的头以及具体内容的变化,同第四章一样通过分析反汇编后的变化分析了链接过程的具体操作。该章节还使用edb对hello进行了分析。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

概念:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

作用:lunix系统调用fork函数建立一个和父进程基本一模一样的子进程。由于代码编写的不完善,随着时间的推移,应用程序的性能会越来越低,有时会陷于某一循环中,导致不必要的 CPU 负载。这些应用程序还可能导致内存泄漏,这时应用程序不再将不需要的内存释放回操作系统。这些应用程序可能会导致服务器停止运行,因此需要重新启动服务器。进程回收就是为解决这些问题而创建的。子进程有父进程回收,孤儿进程有init回收,没有及时回收则出现僵尸进程。

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

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

作用:Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。shell充当命令解释器的作用,将用户输入的命令翻译给系统执行。

处理流程::从终端读入输入的命令→将输入字符串切分获得所有的参数→如果是内置命令则立即执行,否则调用相应的程序执行→重复执行上述过程直至退出。

6.3 Hello的fork进程创建过程

父进程通过调用 fork 函数创建一个新的运行的子进程。

fork 函数只被调用一次,但是会返回两次:一次返回是在父进程中,一次是在新创建的子进程中。父进程中返回子进程的 PID,子进程中返回 0。

因为 fork 创建的子进程的 PID 总是非零的,所以可以根据返回值是否为 0 来分辨是当前是在父进程还是在子进程。

在终端输入:./hello 7203610702 杨恒 1,此时会调用fork()函数创建子进程。 

6.4 Hello的execve过程

execve 函数在创建子进程成功后会在上下文中加载运行子程序,该函数不返回除非发生异常(返回主程序)。具体操作为首先创建内存映像将可执行文件的使用片复制至该部分,然后建立映射空间与当前进程的上下文,指向入口函数,将控制传递给子进程的主函数 。

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

当进程遇到以下两种情况时需要调度:

主动放弃:(1)进程正常终止 (2)运行过程中发送异常而终止(3)主动阻塞(如 等待I/O)

被动放弃:(1)分给进程的时间片用完 (2)有更紧急的事情需要处理(如I、O中断)(3)有更高级的进程进入就绪队列

初始执行hello时进程处于用户态,在hello内部调用sleep后进入内核模式,此时当前进程被释放加入等待队列,当参考时间信号发出中断信号时,hello进程从等待队列移出至运行队列,恢复保存的上下文,此时hello进程再次开始执行。这个过程会一致执行下去,除输入了中断信号则会发送进程的调度。

6.6 hello的异常与信号处理

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

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

乱按键盘的无关输入在程序正常执行过程中在键盘进行随机无关输入,发现不影响程序的正常执行。

回车:在程序执行过程中按回车键,发现程序能够正常执行。在打完八次以后程序会自己结束,这是因为结束时输入的回车键被getchar()读取。

Ctrl-Z:在程序执行过程中输入Ctrl-Z,此时发生异常中断,shell进程收到SIGSTP,终止hello进程并将其挂起,打印相关信息。在进程终止后,输入ps可以看到被挂起的hello。输入jobs会打印被挂起的hello

输入pstree可以看到当前进程树以及各个进程的pid,发现hello的父进程为zsh

输入fg后被挂起的hello再次被重新调回前台,继续打印剩余部分。

Ctrl-C,在hello执行时输入Ctrl-C,导致的异常使得内核产生信号SIGINT给hello的父进程从而收回它。,

6.7本章小结

本章介绍了helllo的进程管理。主要内容有进程的知识、shell的知识、进程的作用、fork和execve如何工作以及hello进程是如何被执行的和进程面对异常时的处理办法。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

主存被组织成一个由 M 个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。CPU 访问内存最自然的方式就是使用物理地址,称为物理寻址。

现代 CPU 使用的是虚拟寻址:CPU 通过生成一个虚拟地址(VA)来访问主存,这个虚拟地址首先通过地址翻译转换为物理地址。

地址翻译需要 CPU 硬件和操作系统之间的紧密合作。CPU 芯片上名为内存管理单元(MMU)的专用硬件利用存放在主存中的查询表来动态翻译虚拟地址。

逻辑地址往往不同于物理地址(physical address),通过地址翻译器(address translator)或映射函数可以把逻辑地址转化为物理地址。 在有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。逻辑地址=段选择符+偏移量。

线性地址(Linear Address)是 逻辑地址 到 物理地址 变换之间的中间层。 在分段部件中 逻辑地址 是段中的 偏移地址 ,然后加上基地址就是线性地址。 线性地址是一个32位 无符号整数 ,可以用来表示高达4GB的地址,也就是,高达4294967296个内存单元。 线性地址通常用十六进制数字表示,值的范围从0x00000000到0xffffffff)

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

逻辑地址=段选择符+偏移量. 每个段选择符大小为16位,段描述符为8字节,GDT为全局描述符表,用来存放系统内用来存放系统内每个任务共用的描述符,例如,内核代码段、内核数据段、用户代码段、用户数据段以及 TSS(任务状态段)等都属于 GDT 中描述的段;LDT为局部描述符表, 存放某任务(即用户进程)专用的描述符。段描述符存放在描述符表中,也就是GDT或LDT中,段首地址存放在段描述符中。

(段描述符:https://www.cnblogs.com/pipci/p/12403976.html)

段选择符由三个部分组成,从右向左依次是RPL、TI、index(索引)。TI=0时,表示段描述符在GDT中,当TI=1时表示段描述符在LDT中。Index中,将描述符表看成是一个数组,每个元素都存放一个段描述符,那index表示某个段描述符在数组中的索引。起始地址+段描述符大小*index,就获得了我们想要的段描述符,继而获取某个段的首地址,再将偏移量相加即可得到线性地址。

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

虚拟内存作为磁盘的高速缓存,和存储器层次结构中的其他缓存一样,磁盘(较低层)中的数据被分割成块,作为磁盘和主存(较高层)之间的传输单元。

VM(虚拟内存)系统通过将虚拟内存分割为虚拟页(Virtual Page,VP)来处理此问题。每个虚拟页的大小为 P=2^p。

类似的,物理内存被分割为物理页(PP),大小也是 P 字节。

任何时刻,所有的虚拟页都被分为了三个不相交的子集:未分配的:VM 系统还未分配(未创建)的页。未分配的块没有任何数据与它们相关联,因此不占用任何磁盘空间;已缓存的:当前已缓存在物理内存中的已分配页。

未缓存的:未缓存在物理内存中的已分配页。

(CSAPP图9-3)

地址翻译硬件使用虚拟地址作为索引从页表中查找相应的页表条目,然后读取条目中的内容来获取该虚拟页在 DRAM 中的物理地址。

下图中虚拟地址被划分为了 k 个 VPN 和一个 VPO。每个 VPN i 都是一个到第 i 级页表的索引。第 k 级页表中的每个 PTE 包含某个物理页面的 PPN(物理页号)或一个磁盘页的地址。其他页表中的 PTE 则包含对应的下一级页表的基址。

对于多级页表,要确定虚拟地址的物理页号,需要访问 k 个页表的 PTE。通过 TLB 将不同层次上页表的 PTE 缓存起来,但多级页表的地址翻译不比单级页表慢很多。

(CSAPP:图9-18)

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

36位VPN被划分为四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN 1提供到一个L1 PET的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个到L2 PTE的偏移量,以此类推。

(CSAPP:图9-25)

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

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

下图展示了一个物理寻址的高速缓存如何和虚拟内存结合起来。主要的思路是地址翻译发生在高速缓存查找之前。注意,页表条目可以缓存,就像其他的数据字一样。

(CSAPP:图9-11)

7.6 hello进程fork时的内存映射

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

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

7.7 hello进程execve时的内存映射

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

当存在execve( "hello.out",NULL,NULL); 如在第8章中学,execve函数在当前进程中加载并运行包含在可执行目标文件a.out中的程序,用a.out程序有效地替代了当前程序。加载并运行a.out需要以下几个步骤:

删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。映射私有区域。为新程序的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为a.out文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为零。图9-31概括了私有区域的不同映射。映射共享区域。如果 a.out程序与共享对象(或目标)链接,比如标准C库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

下一次调度这个进程时,它将从这个人口点开始执行。Linux将根据需要换入代码和数据页面。

(CSAPP:图9-31)

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

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

在虚拟内存的习惯说法中,DRAM缓存不命中称为缺页( page fault)。下图展示了在缺页之前我们的示例页表的状态。CPU引用了VP 3中的一个字,VP 3并未缓存在DRAM中。地址翻译硬件从内存中读取PTE 3,从有效位推断出VP 3未被缓存,并且触发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP 3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。

(CSAPP:图9-6)

接下来,内核从磁盘复制VP 3到内存中的PP 3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虛拟地址重发送到地址翻译硬件。但是现在,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。下图展示了在缺页之后我们的示例页表的状态。

(CSAPP:图9-7)

VM缺页(之后)。缺页处理程序选择VP 4作为牺牲页,并从磁盘上用VP 3的副本取代它。在缺页处理程序重新启动导致缺页的指令之后,该指令将从内存中正常地读取字,而不会再产生异常处理程序重新启动导致缺页的指令之后,该指令将从内存中正常地读取字,而不会再产生异常

7.9动态存储分配管理

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

动态内存分配器维护着一个进程的虚拟内存区域,称为堆( heap)。系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk(读做“break”),它指向堆的顶部。

(CSAPP:图9-33)

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

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

7.10本章小结

本章介绍了hello的储存管理、段式管理、页式管理、三级Cache支持下的物理内存访问、fork和execve运行时的内存映射以及动态储存分配管理。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件。一个 Linux 文件就是一个字节序列,所有的 I/O 设备都被模型化为文件,如网络、磁盘等,所有的输入和输出都被当做对相应文件的读和写来执行。Linux 使用 Unix I/O 来作为处理文件的接口,它以一种统一且一致的方式来执行所有的输入和输出

设备管理:unix io接口

打开文件时应用程序通过要求内核打开相应的文件来访问一个 I/O 设备,内核返回一个用非负整数表示的描述符来标识这个文件。内核会记录有关这个打开文件的所有信息,应用程序只需记住描述符。内核关闭文件时打开文件时创建的数据结构被释放,并将描述符恢复到可用的描述符池中。当一个进程终止,内核就会关闭所有打开的文件并释放它们的内存资源。读操作就是从文件复制数据到内存。当读取到文件末尾会触发一个 EOF(end of file) 条件。改变当前的文件位置:对于每个打开的文件,内核保持着文件位置 k,k是从文件开头起始的字节偏移量,初始为 0。可以通过 seek 函数来显式设置当前位置 k

8.2 简述Unix IO接口及其函数

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

打开文件:进程通过调用 open 函数打开一个已存在的文件或创建一个新文件。flag 参数指明进程如何访问这个文件:

int open(char *filename, int flags, mode_t mode); //若成功则返回文件描述符,若出错返回 -1

O_RDONLY

只读

O_WRONLY

只写

O_RDWR

可读可写

O_CREAT

如果文件不存在,就创建它的一个截断的(空)文件

O_TRUNC

如果文件已存在,就截断它

O_APPEND

在写操作前,设置文件位置到文件的结尾处

读写文件通过read 和 write 函数来执行输入和输出。

ssize_t read(int fd, void *buf, size_t n);          //若成功则返回读的字节数,若 EOF 则返回 0,若出错返回 -1

ssize_t write(int fd, const void *buf, size_t n);   //若成功返回写的字节数,若出错返回 -1

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

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

8.3 printf的实现分析

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

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

       内部调用了vsprintf函数,其主要作用时接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。随后调用系统函数write(buf,i)将长度为i的buf输出。

       在write中,回向寄存器传递部分参数,随后调用int INT_VECTOR_SYS_CALL

发现init_idt_desc(INT_VECTOR_SYS_CALL,DA_386IGate, sys_call, PRIVILEGE_USER);会通过系统调用sys_call, sys_call实现了显示格式化字符,从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。随后显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

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

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

8.5本章小结

本章介绍了linuxIO管理以及IO函数,解释了系统如何将各种IO设备抽象为文件,printf函数与getchar的实现分析。

(第81分)

结论

Hello.c的程序从在程序员键盘下以C语言这种高级语言诞生到在CPU执行,经历了漫长而又复杂的过程:预处理、编译、汇编、链接、在shell中运行,还涉及在存储器的存储以及在shell中的进程管理过程、动态储存分配以及IO接口的管理。

通过本次实验我认识到了原本一个以为很简单的程序在计算机中具体执行需要很复杂的过程,这次大作业使我对其有了总体的认识,对我今后编写程序起到了很大的作用。

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

附件

列出所有的中间产物的文件名,并予以说明起作用。

Hello.c

Hello源程序

Hello.i

Hello.c预处理生成的程序

Hello.s

Hello.i进过编译器编译后生成的程序

Hello.o

汇编器转换打包的可重定位目标程序

Hello

连接产生的可执行文件

hello_o_elf.txt

Hell.o的ELF

hello_elf.txt

链接生成的可执行文件的ELF表

hello_o_asm.txt

hello.o进行反汇编

Hello_obj.s

链接重定位的反汇编

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

参考文献

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

[2]gcc--编译的四大过程及作用_shiyongraow的博客-CSDN博客_gcc编译的作用

[3]深入理解计算机系统(十三):汇编语言和机器语言 - 知乎

[4]汇编语言(面向机器的程序设计语言)_百度百科

[5]机器语言_百度百科

[6]http://c.biancheng.net/view/3566.html               

[7]https://note.youdao.com/ynoteshare/index.html?id=f227ad68c05891737bde374e977b5dd7&type=note&_time=1653006929410

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

[9] 段页式访存——逻辑地址到线性地址的转换 - pipci - 博客园       

[10]https://note.youdao.com/ynoteshare/index.html?id=571afc0df48bcc5faedf009f92ea125d&type=note&_time=1653042645825

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值