计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 2022110903
班 级 2203101
学 生 李杰豪
指 导 教 师 史先俊
计算机科学与技术学院
2023年4月
本文通过介绍hello的一生从而从课本角度阐述各种计算机系统方面的知识。如预处理,编译,汇编等。
关键词:计算机系统;预处理;编译;汇编
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
在编译源文件的过程中,gcc通过调用cpp/cc1/as/ld,将C语言源文件进行预处理、编译、汇编、链接,最终形成可执行目标文件hello,由存储器保存在磁盘中。运行进程时,操作系统为其分配虚拟地址空间,随着一连串的缺页故障,hello被逐渐地载入物理内存。操作系统提供异常控制流等强大的工具,不断对系统中运行着的进程进行调度。Unix I/O为其提供与程序员和系统文件交互的方式,让它不再孤单。当程序从main中返回,意味着程序的终止。之后,shell作为其父进程会负责将其回收,操作系统内核删除相关数据结构,释放其占据的资源,hello的一生就此结束。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1.2.1 硬件环境
设备名称 DESKTOP-6GT2LRR
处理器 12th Gen Intel(R) Core(TM) i7-12700H 2.30 GHz
机带 RAM 16.0 GB (15.7 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
1.2.2 软件环境
Visual Studio 2010 64位以上;CodeBlocks
1.2.3 开发工具
vi/vim/gpedit+gcc
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
| 文件名 | 文件作用 |
| hello.i | 预处理后的文件 |
| hello.s | 编译后的汇编文件 |
| hello.o | 汇编之后的可重定位目标文件 |
| hello | 链接之后的可执行目标文件 |
| hello_o_elf.txt | hello.o的ELF格式 |
| hello_o_disassembler.txt | hello.o的反汇编代码 |
| hello_elf.txt | hello的ELF格式 |
| hello_disassembler.txt | hello的反汇编代码 |
1.4 本章小结
本章对hello的一生进行了简要的介绍和描述,介绍了P2P,020的整个过程,介绍了本计算机的硬件环境、软件环境、开发工具,介绍了为编写本论文的中间文件的名称和其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是什么?
程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。
典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——预处理记号(preprocessing token)用来支持语言特性(如宏调用)。
为什么预处理?
在集成开发环境中,编译,链接是同时完成的。其实,C语言编译器在对源代码编译之前,还需要进一步的处理:预编译。预编译的主要作用如下:
●将源文件中以”include”格式包含的文件复制到编译的源文件中。
●用实际值替换用“#define”定义的字符串。
●根据“#if”后面的条件决定需要编译的代码。
2.2在Ubuntu下预处理的命令
命令:gcc hello.c -E -o hello.i
![]()
图2.2 预处理的命令示意图
2.3 Hello的预处理结果解析

图2.3.1 hello.i文件展示
可以发现,整个.i文件相比于.c文件,扩展到了三千多行。
hello.c程序本来的内容出现在最后。在此之前,则是stdio.h unistd.h stdlib.h的源代码的依次展开。

图2.3.2 hello.c程序内容在hello.i文件中

图2.3.3 hello.i文件中stdio.h等的源代码
2.4 本章小结
本章介绍了预处理的相关概念及其所进行的一些处理,例如实现将定义的宏进行符号替换、引入头文件的内容、根据指令进行选择性编译等。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是什么?
编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
为什么要编译?
编译的过程可以达成以下效果:
1.语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,方法分为两种:自上而下分析法和自下而上分析法。
2.中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。
3.代码优化:指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。
4.目标代码:生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。此处指汇编语言代码,须经过汇编程序汇编后,成为可执行的机器语言代码。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s

图3.2 编译命令示意图
3.3 Hello的编译结果解析
- 数据
通过反汇编代码
可以看出argc被保存在rbp-20中
而i则被保存在rbp-4中

而这两句printf中的语句则被保存为全局的 - 赋值
对应的反汇编代码为
- 算术操作
i++操作的具体实现通过反汇编代码可看出是
- 关系操作和控制转移


je用于判断cmpl产生的条件码,若两个操作数的值不相等则跳转到指定的地方
而for循环的判断条件则是
使用了jle进行判断,如果小于等于则跳转,继续循环 - 函数操作
X86-64中,过程调用传递参数规则:
第1~6个参数一次储存在%rdi、%rsi、%rdx、%rcx、%r8、%r9这六个寄存器中,剩下的参数保存在栈当中。
对于main函数
①参数传递:传入参数argc和argv[],分别用寄存器%rdi和%rsi存储。
②函数调用:初始就调用
③函数返回:return 0 。
对于printf函数
①参数传递:传入了字符串参数首地址;
②函数调用:main函数调用
③函数返回:不太了解
对于sleep函数
①参数传递:传入参数sleepsecs,它通过sleepsecs(%rip)寻址。
![]()
②函数调用:main函数调用
![]()
③函数返回:不太了解
对于atoi函数
①参数传递:传入参数数组
![]()
②函数调用:被sleep调用
![]()
③函数返回:不太了解
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.4 本章小结
本章介绍了编译的概念以及过程。通过hello函数分析了c语言如何转换成为汇编代码。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编是什么?
驱动程序运行汇编器as,将汇编语言的ascii码文件(这里是hello.s)翻译成机器语言的可重定位目标文件(hello.o)的过程称为汇编。
为什么要汇编?
.o文件是一个二进制文件,它包含程序的指令编码。
汇编就是将高级语言转化为机器可直接识别执行的代码文件的过程,汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
命令:gcc hello.x -c -o hello.o

图4.2 汇编命令示意图
应截图,展示汇编过程!
4.3 可重定位目标elf格式
![]()
图4.3.1 获得elf文件示意图
ELF头:包含了系统信息,编码方式,ELF头大小,节的大小和数量等等

图4.3.2 ELF文件中的ELF头
节头:描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。

图4.3.3 ELF文件中的节头
重定位节:各个段引用的外部符号等在链接时需要通过重定位对这些位置的地址进行修改。链接器会通过重定位节的重定位条目计算出正确的地址。

图4.3.4 ELF文件中的重定位节
符号表:存放在程序中定义和引用的函数和全局变量的信息。

图4.3.5 ELF文件中的符号表
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.4 Hello.o的结果解析
![]()
图4.4 获得elf文件命令示意图
反汇编代码所显示的不仅仅是汇编代码,还有机器代码,机器语言程序的是二进制机器指令的集合,是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。机器指令由操作码和操作数构成,汇编语言是人们比较熟悉的词句直接表述CPU动作形成的语言,是最接近CPU运行原理的语言。每一条汇编语言操作码都可以用机器二进制数据来表示,进而可以将所有的汇编语言(操作码和操作数)和二进制机器语言建立一一映射的关系
在控制转移上,hello.s使用.L2和.LC1等段名称进行跳转,而反汇编代码使用目标代码的虚拟地址跳转。不过目前留下了重定位条目,跳转地址为零。它们将在链接之后被填写正确的位置。
在函数调用上,hello.s直接call函数名称,而反汇编代码中call的是目标的虚拟地址。但和上一条的情况类似,只有在链接之后才能确定运行执行的地址,目前目的地址是全0,并留下了重定位条目。
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章介绍了汇编。经过汇编器,汇编语言转化为机器语言,hello.s文件转化为hello.o可重定位目标文件。我们研究了可重定位目标文件elf格式,接触了了readelf命令、elf头、节头部表、重定位节、符号表。我们对比hello.s和hello.o,分析了汇编语言到机器语言的变化。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是什么?
链接是将各种不同文件(主要是可重定位目标文件)的代码和数据综合在一起,通过符号解析和重定位等过程,最终组合成一个可以在程序中加载和运行的单一的可执行目标文件的过程。
为什么要链接?
链接令分离编译成为可能,方便了程序的修改和编译:无需重新编译整个工程,而是仅编译修改的文件。
链接还有利于构建共享库。源程序节省空间而未编入的常用函数文件(如printf.o)进行合并,生成可以正常工作的可执行文件。
注意:这儿的链接是指从 hello.o 到hello生成过程。
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 链接命令示意图
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
ELF头:

图5.3.1 ELF文件中的ELF头
节头:

图5.3.2 ELF文件中的节头
程序头:

图5.3.3 ELF文件中的程序头
段节:

图5.3.4 ELF文件中的段节
重定位节和符号表

图5.3.5 ELF文件中的重定位节和符号表
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.4 hello的虚拟地址空间

图5.4.1 通过edb查看hello的虚拟空间
根据节头,可以通过edb找到各个节的信息,如.text在0x4010f0可以看到
![]()
图5.4.2 节头中text位置的信息
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
![]()
图5.5.1链接命令示意图
链接加入了在hello.c中用到的库函数,如puts,printf等函数。

图5.5.2 链接文件反汇编查看中的函数
hello中增加了.init和.plt节,和一些节中定义的函数。

图5.5.3 链接文件反汇编查看增多的节
hello实现了调用函数时的重定位,因此在调用函数时调用的地址已经是函数确切的虚拟地址。
![]()
图5.5.4 链接文件反汇编查看已明确虚拟地址的函数
hello重定位的过程:
(1)重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
(2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
(3)重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。代码的重定位条目放在.rel.txt
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.6 hello的执行流程


图5.6 各函数在虚拟内存中的地址
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
GOT表位置在调用dl_init之前0x601008后的16个字节均为0:

图5.7.1 ELF文件中的部分节头

图5.7.2 运行前.got中的内容
调用_start之后发生改变

图5.7.3 运行后.got中的内容
后面的字节变为0x7f2441e29dc0等,对应部分是共享库模块的入口点

图5.7.4 对应地址中的内容
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是什么?
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
为什么使用进程?
进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统。可以说,如果没有进程,体系如此庞大的计算机不可能设计出来。
6.2 简述壳Shell-bash的作用与处理流程
Shell是用户级的应用程序,代表用户控制操作系统中的任务。处理流程如下:
① 在shell命令行中输入命令:$./hello
② shell命令行解释器构造argv和envp;
③ 调用fork()函数创建子进程,其地址空间与shell父进程完全相同,包括只读代码段、读写数据段、堆及用户栈等
④ 调用execve()函数在当前进程(新创建的子进程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空间
⑤ 调用hello程序的main()函数,hello程序开始在一个进程的上下文中运行。
6.3 Hello的fork进程创建过程
终端程序通过调用fork()函数创建一个子进程,子进程得到与父进程完全相同但是独立的一个副本,包括代码段、段、数据段、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,父进程和子进程最大的不同时他们的PID是不同的。父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的 逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。
6.4 Hello的execve过程
1. 删除已存在的用户区域(自父进程独立)。
2. 映射私有区:为Hello的代码、数据、.bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的。
3. 映射共享区:比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。
4. 设置PC:exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
5. execve在调用成功的情况下不会返回,只有当出现错误时,例如找不到需要执行的程序时,execve才会返回到调用程序。
6.5 Hello的进程执行
6.5.1 上下文:
上下文就是内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。
6.5.2 进程时间片:
进程的运行本质上是CPU不断从程序计数器 PC 指示的地址处取出指令并执行,值的序列叫做逻辑控制流。操作系统会对进程的运行进行调度,执行进程A->上下文切换->执行进程B->上下文切换->执行进程A->… 如此循环往复。 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。在一个程序被调运行开始到被另一个进程打断,中间的时间就是运行的时间片。
6.5.3 调度的过程:
在对进程进行调度的过程,操作系统主要做了两件事:加载保存的寄存器,切换虚拟地址空间。
6.5.4 用户态与核心态转换:
为了能让处理器安全运行,需要限制应用程序可执行指令所能访问的地址范围。因此划分了用户态与核心态。
核心态可以说是拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.6 hello的异常与信号处理
hello程序出现的异常可能有:
中断:在hello程序执行的过程中可能会出现外部I/O设备引起的异常。
陷阱:陷阱是有意的异常,是执行一条指令的结果,hello执行sleep函数的时候会出现这个异常。
故障:在执行hello程序的时候,可能会发生缺页故障。
终止:终止时不可恢复的错误,在hello执行过程可能会出现DRAM或者SRAM位损坏的奇偶错误。
正常执行时结果如下:

图6.6.1正常运行hello时情况
ctrl-z即挂起前台的作业时,

图6.6.2 输入ctrl-z和ps后的运行结果
可以看出hello的PID时21856,使用jobs看出当前jid是1,再输入fg重新运行。

图6.6.3 输入jobs和fg后的运行结果
Ctrl+C即 SIGINT 信号时,结束 hello。在ps中查询不到其PID,可以看出hello已经被彻底结束。

输入ctrl-c后的运行结果
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
本章了解了hello进程的执行过程。在hello运行过程中,内核对其调度,异常处理程序为其将处理各种异常。每种信号都有不同的处理机制,对不同的shell命令,hello也有不同的响应结果。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址(Logical Address)是指由程序产生的与段相关的偏移地址部分。在这里指的是hello.o中的内容。
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
CPU启动保护模式后,程序hello运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。
放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个地址每位的值就在相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的。
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在 Intel 平台下,逻辑地址(logical address)是 selector:offset 这种形式,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。如果用 selector 去 GDT( 全局描述符表 ) 里拿到 segment base address(段基址) 然后加上 offset(段内偏移),这就得到了 linear address。我们把这个过程称作段式内存管理。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。
7.4 TLB与四级页表支持下的VA到PA的变换
CPU 产生虚拟地址 VA,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 中添加条目。如果查询 PTE 的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误。
7.5 三级Cache支持下的物理内存访问
CPU发送一条虚拟地址,随后MMU按照7.4所述的操作获得了物理地址PA。根据cache大小组数的要求,将PA分为CT(标记位)CI(组索引),CO(块偏移)。根据CI寻找到正确的组,依次与每一行的数据比较,有效位有效且标记位一致则命中。如果命中,直接返回想要的数据。如果不命中,就依次去L2,L3,主存判断是否命中,命中时将数据传给CPU同时更新各级cache的储存。
7.6 hello进程fork时的内存映射
当 fork 函数被 shell 进程调用时,内核为新进程创建各种数据结构,并分配给 它一个唯一的 PID,为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
1.execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。
2.删除已存在的用户区域。
3.映射私有区域
4.映射共享区域
5.设置程序计数器(PC)
7.8 缺页故障与缺页中断处理
缺页故障:当指令引用一个相应的虚拟地址,而与改地址相应的物理页面不再内存中,会触发缺页故障。通过查询页表PTE可以知道虚拟页在磁盘的位置。缺页处理程序从指定的位置加载页面 到物理内存中,并更新PTE。然后控制返回给引起缺页故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中,因此指令可以没有故障的运行完成。故障处理具体流程如图7.8所示

图7.8缺页故障处理流程
7.9动态存储分配管理
动态储存分配管理使用动态内存分配器(如malloc)来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合。每个块就是一个连续的虚拟内存页,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
动态内存管理的基本方法与策略主要有两种:
1.带边界标签的隐式空闲链表分配器管理
带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、(可能的)额外填充以及一个字的尾部组成。
隐式空闲链表:空闲块通过头部的大小字段隐含地连接着。分配器遍历堆中所有的块,间接地遍历整个空闲块的集合。
当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配和最佳适配。分配器在面对释放一个已分配块时,可以合并相邻的空闲块,其中一种简单的方式,是利用隐式空闲链表的边界标记来进行合并。
2. 显式空间链表管理
显式空闲链表是将堆的空闲块组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。进行内存管理。在显式空闲链表中。可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
本章介绍了存储器地址空间、段式管理、页式管理,VA 到 PA 的变换、物理内存访问, hello 进程fork时和execve 时的内存映射、缺页故障与缺页中断处理、包括隐式空闲链表和显式空闲链表的动态存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。Linux就是基于Unix I/O实现对设备的管理。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix I/O 接口:
(1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想 要访问一个 I/O 设备,内核返回一个小的非负整数,叫做描述符,它在 后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文 件的所有信息。
(2)Shell 创建的每个进程都有三个打开的文件:标准输入,标准输出,标 准错误。 (3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位 置 k,初始为 0,这个文件位置是从文件开头起始的字节偏移量,应用 程序能够通过执行 seek,显式地将改变当前文件位置 k。
(4)读写文件:一个读操作就是从文件复制 n>0 个字节到内存,从当前文 件位置 k 开始,然后将 k 增加到 k+n,给定一个大小为 m 字节的而文 件,当 k>=m 时,触发 EOF。类似一个写操作就是从内存中复制 n>0 个字节到一个文件,从当前文件位置 k 开始,然后更新 k。
(5)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢 复到可用的描述符池中去。
Unix中大多数文件的操作只需要用到五个函数open、read、write、lseek、和close
8.3 printf的实现分析
Printf主函数:

图8.3.1 printf主函数源码
调用的vsprintf函数:

图8.3.2 vsprintf函数源码
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。返回的是要打印出来的字符串的长度
调用的Write函数:

图8.3.3 Write函数源码
在printf中调用系统函数write(buf,i)将长度为i的buf输出,在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,
int INT_VECTOR_SYS_CALLA代表通过系统调用syscall。
Syscall函数:

图8.3.4 syscall函数源码
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
当程序调用getchar时,程序等待用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车(回车也在缓冲区中)。
当用户输入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了 Linux 的 I/O 设备的基本概念和管理方法,以及Unix I/O 接口及其函数。最后分析了printf 函数和 getchar 函数的工作过程。
(第8章1分)
结论
hello.c预处理到hello.i文本文件
hello.i编译到hello.s汇编文件
hello.s汇编到二进制可重定位目标文件hello.o
hello.o链接生成可执行文件hello
bash进程调用fork函数,生成子进程
execve函数加载运行当前进程的上下文中加载并运行新程序hello
hello最终被shell父进程回收
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
| 文件名 | 文件作用 |
| hello.i | 预处理后的文件 |
| hello.s | 编译后的汇编文件 |
| hello.o | 汇编之后的可重定位目标文件 |
| hello | 链接之后的可执行目标文件 |
| hello_o_elf.txt | hello.o的ELF格式 |
| hello_o_disassembler.txt | hello.o的反汇编代码 |
| hello_elf.txt | hello的ELF格式 |
| hello_disassembler.txt | 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.
[7] https://blog.csdn.net/weixin_45406155/article/details/103775420
(参考文献0分,缺失 -1分)

1362

被折叠的 条评论
为什么被折叠?



