摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
关键词:关键词1;关键词2;……;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
1、编写程序(Program):
程序员(菜鸟)通过编辑器编写一个名为hello.c的C语言程序。
2、预处理:
预处理器(cpp)处理hello.c,包括宏替换、文件包含等,生成hello.i。
3、编译:
编译器(ccl)将hello.i编译成汇编语言,生成hello.s。
4、汇编:
汇编器(as)将hello.s汇编成机器语言,生成hello.o。
5、链接:
链接器(ld)将hello.o与其他必要的库文件链接,生成最终的可执行文件hello。
6、进程创建(Process):
(1)在Shell(Bash)中,操作系统(OS)通过fork()创建一个新的进程。
(2)通过execve()加载并执行hello程序。
(3)使用mmap()进行内存映射,分配时间片,使程序在硬件(CPU/RAM/IO)上运行。
020(From Zero-0 to Zero-0)
1、启动:
程序从零开始,通过一系列的系统调用和硬件操作,被加载到内存中执行。
2、执行:
(1)在操作系统(OS)的管理下,程序在CPU上执行指令,使用RAM存储数据,通过IO设备进行输入输出。
(2)操作系统通过存储管理(如虚拟内存、页表、TLB、Cache等)优化程序的执行效率。
3、结束:
程序执行完毕后,操作系统负责清理资源,程序的状态回到零,即从零开始,最终回到零。
4、历史记录:
虽然程序的生命周期短暂,但计算机系统(CS)记录了程序的整个生命周期,从诞生到消亡。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1、硬件环境
X64 CPU; 2GHz; 16G RAM; 512GHD Disk
2、软件环境
Windows10 64位; Vmware 14; Ubuntu20.04
3、开发工具
Visual Studio Code; vi/vim/gpedit+gcc
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c 源程序 hello.i 预处理后的修改的C程序
hello.s 汇编程序 hello.o 可重定位目标文件
hello 可执行目标文件 Objdump_hello.o hello.o的反汇编文件
Objdump_hello hello的反汇编文件 elf_hello hello的ELF格式
elf_hello.o hello.o的ELF格式
1.4 本章小结
理解了P2P和020的具体含义,对后续的大作业的内容有概括性的了解。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是C/C++等编程语言编译流程中的一个步骤,它发生在编译器真正处理源代码之前。预处理的主要作用是处理程序的源代码,使其在编译之前进行一些简单的文本处理,以便在编译时能够更好地处理程序的结构和特性。
2.2在Ubuntu下预处理的命令
预处理的命令:gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
通过预处理,我们可以发现hello.i当中的代码量相对于源程序剧增。这种剧增就是因为#预处理命令将头文件的程序、宏变量、特殊符号等插入到hello.c中。
2.4 本章小结
本节阐述了预处理的概念和具体的作用,并在Ubuntu中执行了预处理命令,生成了hello.i文件,通过浏览hello.i文件,更加深刻地理解了预处理的概念和作用。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是编程过程中的一步,它将源代码(通常是用高级编程语言编写的)转换成机器语言或汇编语言的过程。编译的作用是将人类可读的高级代码转换为计算机可执行的低级代码,使得计算机能够理解并执行这些指令。
3.2 在Ubuntu下编译的命令
命令:/usr/lib/gcc/x86_64-linux-gnu/11/cc1 hello.i -o hello.s
3.3 Hello的编译结果解析
1、汇编文件头部声明:
.file 源文件(指从hello.i汇编得来)
.text代码节
.rodata 制度代码段
.align 代码对齐方式
.global 全局变量
.type声明一个符号是数据类型还是函数类型
.string 声明了两个字符串分别为.LC0和 .LC1
- main函数:
(1)分支跳转(je .L2)对应源文件if语句,24行cmpl对应if语句的判断条件
(2)26-30行对应源文件调用printf函数打印字符串并退出exit(0)
(3).L3和.L4对应源文件for循环,.L4是循环体,.L3是循环终止条件
(4).L4中执行打印字符串操作并通过寄存器传参调用sleep函数
3.4 本章小结
当我们用高级语言编程的时候,机器屏蔽了程序的细节,即机器级的实现。但高级语言的抽象程度过高,计算机无法直接识别并执行,计算机只能执行机器级的指令。从高级语言到汇编语言,这项重要的工作就交由编译器执行,编译器承担了生成汇编代码的大部分工作。我们以hello.i -> hello.s的例子,直观地看到了编译的结果,并将起与C源程序的代码结合起来,理解汇编语言在整个程序当中发挥的作用。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编是一种低级编程语言,它与特定的计算机体系结构紧密相关,通常直接对应于计算机的机器语言指令。汇编语言使用助记符来代替机器语言的二进制代码,使得程序员能够以更易读和易写的方式来编写程序。
4.2 在Ubuntu下汇编的命令
命令:as hello.s -o hello.o
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
1、elf头部
命令:readelf -h hello.o
从ELF Header我们可以看到hello.oELF格式的一些基本信息。比如main函数的指令编码、操作系统的版本,节头文件的起始地址等等。
2、elf各section
命令:readelf -S hello.o
上图展示了elf整体section结构,包括section的大小、名称。
- 重定向 .rela.text
命令:readelf -r hello.o
.rela.text包含需要重定位的信息,当链接器链接.o文件时,会根据重定位节的信息计算正确的地址,重定位.rela.text中的信息。从上图中我们可以看到.rela.text中的不同类型的信息。
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
与hello.s进行对比:我们会发现,每行代码末尾的指令基本是相同的,但是在每条指令前面都会有一串十六进制的编码。从hello.o与hello.s文件内容分析,hello.s是由汇编语言组成的,相对于计算机能识别的机器级指令,汇编代码仍是抽象语言;而反汇编得到的代码不仅仅有汇编代码,还有机器语言代码。机器语言代码是计算机可识别执行的,是一种纯粹的二进制编码。
不同之处在于分支转移和函数调用,分支转移时,反汇编文件不存在汇编语言中的代码段地址,而是直接跳转在当前过程的起始地址加上偏移量得到的直接目标代码地址;函数调用时,反汇编文件中call调用的目标指令为call指令的下一个指令。
4.5 本章小结
本章通过分析hello.o的elf结构和反汇编代码剖析了一个可执行程序的基本组成结构,对比分析了机器语言与汇编代码的异同。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接的概念与作用主要涉及计算机程序的编译和构建过程,特别是在将多个目标文件和库文件合并成一个可执行文件或共享库时。链接是程序构建过程中的一个关键步骤,它确保了程序的正确性、可维护性和性能。通过链接,程序员可以利用模块化和代码重用的优势,同时减少可执行文件的大小并提高程序的运行效率。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
ld的链接命令:ld -o hello.o -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 /usr/lib/gcc/x86_64-linux-gnu/11/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/11/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
- elf头部
命令:readelf -h hello
- elf各section
命令:readelf -S hello
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
我们可通过各section的地址在Data Dump中找到相应的数据。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
可以看出,重定向后的hello.out比hello.o的反汇编左侧多了一列虚拟地址序号VPN,因为经过重定向每条指令的VA-PA映射关系已经确定。
同时经过链接该文件比hello.o多了许多库函数的代码如exit()等,因为许多调用的外部函数以可重定向文件的格式存在,在链接这步加入了这些文件。
同时在链接过程中,各个可重定向文件的相同elf section会被聚合在一起形成聚合结,例如所有的.rodata,.text,.bss,.data等等。
5.6 hello的执行流程
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
在_start打断点并单步执行
函数 | 地址 |
main | 0x4011e2 |
init | 0x401000 |
_start | 0x7ffff7fe3290 |
call_init | 0x7ffff7c29e68 |
执行顺序
_start->init->call_init->main
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb/gdb调试,分析在动态链接前后,这些项目的内容变化。要截图标识说明。
PLT和GOT是与动态链接最相关的两个节,观察他们所在地址段的变化即可反应动态链接的内容变化。
通过readelf找到他们的地址
在动态链接处打断点
发现程序没有正确停下,于是我在_start处与main处分别打断点,观察观察前后两变量变化,如下:
5.8 本章小结
本章我们通过ELF文件和edb调试,逐步分析链接的过程。链接是将Program转变成Process对文件进行的最后一步操作。链接将所有零碎的文件统一起来,汇聚到一个项目中。链接之后,我们得到了可以运行的可执行目标文件。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念与作用是操作系统中的核心概念之一,它涉及到程序的执行和管理。进程是操作系统中用于管理程序执行的基本概念,它通过资源分配、并发执行、隔离保护、抽象模块化等功能,提高了系统的效率和可靠性,支持了多任务处理和资源的高效利用。
6.2 简述壳Shell-bash的作用与处理流程
壳(Shell)是一种命令语言和程序设计语言,是用户与操作系统内核交互的桥梁。它提供了一个接口,用户可以通过这个接口输入命令,由操作系统执行。Bash(Bourne-again SHell)是Linux和Unix系统中最常用的Shell之一,它是Bourne shell的增强版。
Bash的作用:
1、用户界面(User Interface):Bash 提供一个用户界面,用户可以在这个界面输入命令,与操作系统交互。
2、命令解释和执行(Command Interpretation and Execution):Bash 解释用户输入的命令,并执行它们。它查看命令行,确定要执行的程序,然后调用该程序。
3、环境管理(Environment Management):Bash 管理用户的环境,包括变量、工作目录、历史记录等,为用户提供了个性化的操作环境。
4、脚本语言(Scripting Language):Bash 是一种脚本语言,用户可以用它来编写脚本,实现自动化任务。
5、进程管理(Process Management):Bash 可以启动、停止和管理进程。
6、文件操作(File Operations):Bash 提供了文件操作命令,允许用户创建、删除、移动和查看文件。
7、输入/输出控制(Input/Output Control):Bash 控制着输入和输出的重定向,以及管道命令的执行。
8、网络功能(Network Features):Bash 支持网络命令,允许用户访问和管理网络资源。
bash的处理流程:
1、读取命令(Read Command):Bash 从标准输入(通常是键盘)读取命令。用户输入命令后,按下 Enter 键,Bash 会将该命令读入到命令行缓冲区。
2、解释命令(Command Interpretation):Bash 解释命令,从命令行缓冲区中读取命令,并确定要执行的操作。Bash 会分析命令,并将其拆分成多个单词,以及可能的重定向和管道操作。
3、参数扩展(Parameter Expansion):Bash 对命令中的参数进行扩展,例如变量、命令替换、算术运算、文件名扩展等。这将替换命令行上的参数,生成最终要执行的命令。
4、执行命令(Execute Command):Bash 执行命令。如果命令是一个内置命令,Bash 会直接执行该命令;如果命令是一个外部命令,Bash 会创建一个新进程,并在该进程中执行命令。
5、等待命令完成(Wait for Command to Finish):Bash 会等待命令完成执行,并记录命令的退出状态(Exit Status)。
6、输出结果(Output Result):如果命令成功执行,Bash 会输出命令的结果,并将其显示在终端;如果命令失败,Bash 会输出错误信息。
7、处理输入/输出重定向(Handle I/O Redirection):Bash 会处理输入/输出重定向,将输入和输出重定向到文件或其他设备。
8、管理进程(Manage Processes):Bash 会管理进程,包括创建新进程、终止进程、暂停进程、继续进程等。
9、执行脚本(Execute Script):如果命令是一个脚本文件,Bash 会执行该脚本文件,并逐行读取和执行脚本中的命令。
10、重复上述过程(Repeat the Process):Bash 会继续重复上述过程,直到用户输入 "exit" 或 "logout" 命令,或者系统关闭。
6.3 Hello的fork进程创建过程
fork() 是一个在 Unix 和 Unix-like 系统(包括 Linux)中创建新进程的系统调用。在 Bash(一种 shell)中,虽然你可以使用 fork 命令来创建子进程,但这个命令在 Bash 中是不存在的,fork() 是 C 语言中的函数,用于在内核级别创建进程。
以下是 fork() 进程创建的基本过程:
1、系统调用:在 C 或 C++ 程序中,当你调用 fork() 函数时,操作系统会为调用者创建一个新的进程。这个过程通常在用户态下发生,但 fork() 会触发一个系统调用,将控制权交给内核。
2、复制进程:内核会为新创建的进程分配内存空间,复制调用者进程的用户空间上下文(包括程序计数器、堆栈、打开的文件描述符等)。子进程将拥有与父进程几乎完全相同的状态,除了进程 ID(PID)不同。
3、创建进程:内核创建一个新的进程 ID(PID),并将其与新的虚拟地址空间关联。同时,它会创建一个子进程,这个子进程被初始化为等待状态,因为它还没有执行任何代码。
4、返回值:在父进程中,fork() 函数返回新创建子进程的 PID(如果成功,返回子进程的 PID,0 表示新进程是父进程的自身);而在子进程中,fork() 返回 0。
5、继续执行:父进程继续执行从 fork() 返回后的代码,而子进程则从 fork() 函数调用点开始执行,通常会执行 exec 命令来替换自身为一个新的程序。
6、共享资源:父进程和子进程共享进程空间,包括相同的代码和数据。但它们有自己的内存空间,所以对数据的修改通常只影响一个进程。
在 Bash 中,你可以通过调用子进程(例如 bash script.sh)来间接使用 fork,但实际执行 fork 的是内核,而不是 Bash。同时,Bash 也提供了 & 运算符来在后台启动子进程。
6.4 Hello的execve过程
execve() 是一个在 Unix 和 Unix-like 系统中执行新程序的系统调用,它通常在父进程(如 Bash)调用 fork() 创建子进程后用来替换子进程的当前执行环境。execve() 的过程如下:
1、进程状态:在使用 fork() 创建子进程后,子进程(通常称为“执行进程”)处于等待父进程信号(如 execve() 的返回值)的状态。
2、函数调用:在父进程(通常在 Bash 中)中,调用 execve() 函数,提供以下参数:
filename: 要执行的程序的路径名。
argv[]: 一个指向程序参数的指针数组。
envp[]: 一个指向环境变量的指针数组。
3、验证权限:系统检查当前用户是否有执行指定文件的权限。如果权限不足,execve() 将返回错误。
4、替换进程映射:execve() 会终止子进程当前的代码和数据空间,然后将新程序的映射加载到内存中。这包括代码、数据、堆、栈和其他动态加载的库。
5、初始化新进程:新程序的堆栈和环境变量被初始化,程序计数器设置为程序的入口点。
6、执行新程序:子进程开始执行新程序的主函数,即 main() 函数,或者指定的入口点。
7、错误处理:如果 execve() 失败(例如,文件不存在或没有权限),子进程通常会继续执行,但可能以错误代码退出,或者父进程可能需要处理错误。
在 Bash 中,execve() 通常用于替换当前 shell,例如当你运行 bash script.sh 时,Bash 就会调用 execve() 来执行 script.sh,而它自己则被替换为新的 shell 进程。
6.5 Hello的进程执行
当你在终端中输入 "Hello" 并按回车键,通常会启动一个简单的命令解释器(比如在 Unix 系统中是 Bash 或者在 Windows 中是命令提示符)来处理这个命令。以下是这个过程的一个简要概述:
1、用户输入:你键入了 "Hello" 这个命令,可能是希望打印 "Hello, World!" 或者执行其他与 "Hello" 相关的操作。
2、命令解析:终端(或命令解释器)读取你的输入,将其解析为一个操作系统可以理解的命令。在你这个例子中,"Hello" 可能被解析为一个简单的命令,如 "echo Hello" 或者 "hello"(取决于你的系统和可能安装的命令)。
3、进程创建:命令解释器调用 fork() 系统调用创建一个新的子进程。在 Unix 系统中,fork() 会复制当前进程的所有状态,包括进程ID(PID)、环境变量等。
4、执行子进程:子进程运行,执行 execve() 系统调用。这个调用会替换子进程的当前进程上下文,加载指定的程序(如 echo)到内存,并将控制权交给新的程序。
5、程序执行:echo Hello 命令中的 echo 是一个标准的 Unix 工具,它接收一个参数(在这个例子中是 "Hello"),然后在终端输出这个参数。因此,"Hello" 这个字符串被打印到屏幕上。
6、进程结束:echo 命令执行完毕后,子进程结束。在 Unix 系统中,如果 echo 命令没有异常,子进程会以退出状态 0(表示成功)结束。
7、父进程处理:在父进程(命令解释器)中,如果子进程结束,它通常会检查子进程的退出状态并进行相应的处理。在你这个例子中,父进程可能继续等待用户输入或执行下一条命令。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
- ctrl-c
- ctrl-z
- 正常运行
6.7本章小结
本章我们逐步分析了如何通过shell执行我们得到的可执行目标文件。我们终于将Program转化成一个Process。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
在计算机系统中,"Hello" 这个字符串的存储和访问涉及到多个地址空间的概念,包括逻辑地址、线性地址、虚拟地址和物理地址。下面我将详细解释这些概念,并结合 "Hello" 这个字符串来说明它们之间的关系。
1、逻辑地址: 逻辑地址是由程序生成的地址,通常是编译后的程序中的地址。在现代操作系统中,逻辑地址通常指的是程序指令中的地址,这些地址指向程序的数据或代码段。例如,在编译后的程序中,"Hello" 字符串可能有一个逻辑地址,这个地址是相对于程序的基地址的偏移量。
2、线性地址(或称为虚拟地址): 线性地址是逻辑地址经过分段机制转换后的地址。在保护模式下的 x86 架构中,逻辑地址通过段寄存器和段描述符转换为线性地址。线性地址是一个连续的地址空间,它不直接对应到物理内存的地址,而是操作系统为每个进程提供的虚拟地址空间的一部分。对于 "Hello" 字符串,当程序尝试访问它时,会使用一个线性地址来表示它在虚拟地址空间中的位置。
3、虚拟地址: 虚拟地址是线性地址的同义词,特别是在不使用分段机制的现代操作系统中。虚拟地址空间是操作系统为每个进程提供的抽象,使得每个进程都认为自己独占了整个地址空间。虚拟地址需要通过页表转换为物理地址。对于 "Hello" 字符串,当程序使用虚拟地址访问它时,操作系统会负责将这个虚拟地址转换为实际的物理地址。
4、物理地址: 物理地址是实际的内存芯片上的地址,它是内存控制器用来访问实际内存单元的地址。物理地址是唯一的,并且直接对应到计算机内存条上的某个位置。当操作系统将虚拟地址转换为物理地址后,CPU 就会使用这个物理地址来读取或写入 "Hello" 字符串。
在实际操作中,当一个程序尝试访问 "Hello" 字符串时,它会使用逻辑地址(在编译后的程序中)。这个逻辑地址在运行时通过操作系统的分段或分页机制转换为线性地址(或虚拟地址)。然后,操作系统使用页表将线性地址转换为物理地址,CPU 最终使用这个物理地址来访问内存中的 "Hello" 字符串。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由段基址和偏移量构成。
首先检查TI字段,决定是使用GDT(TI=0)还是LDT(TI=1),再计算段描述符的地址,最后将逻辑地址中的偏移量与段描述符中的base相加得到线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
在Linux系统中,当一个进程通过fork()系统调用创建一个子进程时,子进程会继承父进程的内存映射。这意味着子进程会复制父进程的所有内存页,包括代码段、数据段、堆、栈以及任何共享库的映射。这种复制是通过写时复制(Copy-On-Write, COW)机制实现的,以提高效率。
下面是fork()时内存映射的详细过程:
1、创建子进程: 当父进程调用fork()时,内核会创建一个新的子进程。这个子进程在创建时几乎与父进程完全相同,包括进程ID、父进程ID、进程组ID、信号处理状态、优先级等。
2、内存映射复制: 子进程会复制父进程的内存映射表。这意味着子进程会有与父进程相同的虚拟内存空间布局,包括代码段、数据段、堆、栈等。
3、写时复制机制: 虽然子进程复制了父进程的内存映射,但实际上并不会立即复制物理内存页。相反,父进程和子进程共享相同的物理内存页,这些页被标记为“只读”。如果任何一方尝试写入这些共享页,内核会触发一个缺页异常(page fault),然后创建该页的一个私有副本,并将其标记为可写。这个过程称为写时复制。
4、独立执行: 一旦fork()返回,父进程和子进程将独立执行,它们可以修改自己的内存副本而不会影响对方。
5、文件描述符和文件表项: 子进程还会继承父进程的文件描述符和文件表项。这意味着如果父进程打开了某些文件,子进程将能够访问这些文件,并且它们共享文件偏移量。
6、资源清理: 如果父进程有任何资源(如信号量、消息队列等),子进程也会继承这些资源。但是,子进程通常需要负责清理这些资源,以避免资源泄漏。
总结来说,fork()创建的子进程会复制父进程的内存映射,但通过写时复制机制,它们共享物理内存页,直到需要修改这些页时才进行复制。这种机制极大地提高了创建进程的效率,尤其是在父进程和子进程不需要修改共享内存的情况下。
7.7 hello进程execve时的内存映射
7.8 缺页故障与缺页中断处理
缺页故障是指当一个进程试图访问一个不在物理内存中的页面时,由操作系统的内存管理单元(MMU)触发的异常。在虚拟内存系统中,每个进程都有一个虚拟地址空间,这个空间可以大于物理内存的大小。因此,进程的某些页面可能会存储在磁盘上的交换文件(swap file)或页面文件(page file)中,而不是物理内存中。当进程访问一个未在物理内存中的页面时,MMU无法找到对应的物理页面,于是产生一个缺页故障。这个异常会被发送到进程的信号处理程序,由操作系统来处理。
缺页中断是缺页故障的处理过程。当缺页故障发生时,操作系统介入,进行一系列的操作来恢复执行。这个过程通常被称为缺页中断处理,因为它涉及到从磁盘将缺失的页面加载到物理内存中,并更新页表等数据结构。
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存管理分为显式内存管理和隐式内存管理,显式内存管理常用的由malloc,calloc等等;隐式的内存管理是垃圾回收,即找出堆中不可达(不能从堆外部访问)的节点并释放对应内存。
7.10本章小结
本章主要介绍了hello程序的存储管理,辨析了逻辑地址、线性地址、虚拟地址和物理地址的关系;又分析了逐步地将地址翻译为最终物理地址的翻译;更深层次的理解了页表、Cache、内存映射的概念,对fork、execve有了新的理解视角;又介绍了动态内存管理的基本方法和策略。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
Linux将IO设备视为文件的一种,将Input视为读文件,将Output视为写文件。
8.2 简述Unix IO接口及其函数
常见函数:
open 打开文件
close 关闭文件
read 将文件读到内存
write 将内存数据写入文件
8.3 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;
}
printf会接收一个格式串并根据格式串决定接收参数类型及个数。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 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是stdio.h中的库函数,它的作用是从stdin流中读入一个字。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章我们介绍了I/O设备的管理。首先在Unix系统中,I/O设备模型化为文件,而对I/O设备的管理就在文件上实现。包括打开文件、关闭文件、读写文件、修改文件位置。而后我们又分析了printf和getchar函数的实现,对I/O设备的管理有进一步的理解。
(第8章1分)
结论
1、首先,我们在计算机的文本编辑器上用C语言编写hello.c的源文件;
2、然后,hello.c在预处理之后,将头文件的内容插入到程序文本中,得到hello.i;
3、编译器对hello.i进行编译,从而得到汇编文件hello.s;
4、计算机仍不能直接识别并执行汇编代码,还需经过汇编器汇编,得到与汇编语言一一对应的机器语言指令,在汇编之后,得到了可重定位目标文件hello.o.hello.o是一个二进制文件;
5、即使hello.o文件是机器语言文件,但指令尚未确定虚拟地址,还需链接器对hello.o中调用函数的指令进行重定位,将调用的系统函数如printf.o等链接到hello.o,得到可执行目标文件hello;
6、hello是可以在计算机运行的文件了。接下来我们要在计算机上正式运行hello;
7、首先在shell-Bash中输入运行hello的命令行./hello,OS就为hello创建一个子进程,hello就在这个独一无二的进程当中运行;
8、程序的运行一定伴随着对存储、地址的操作;首先,在hello中的地址为虚拟地址,要讲虚拟地址翻译映射到物理地址,才能对该地址进行操作;
hello程序要想正常运行,需要用户输入用户的学号、姓名和间隔时间,所以我们9、还要了解计算机是如何管理I/O设备的;
10、最后由shell父进程回收终止的hello进程。
计算机系统的设计需要合理协调系统各个部分,既要保证每个模块的正确运行,又要尽量的提高每个模块的执行速度,以及互相合作的效率。计算机系统是软件与硬件的统一,我们对计算机系统设计和实现的理解,既不能抛开硬件,也不能不顾软件层面。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.c 源文件
hello.i 预编译结果
hello.s 编译结果
hello.o 汇编结果
hello.exe 链接结果
(附件0分,缺失 -1分)