计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号
班 级
学 生
指 导 教 师
计算机科学与技术学院
2021年5月
本文通过hello程序实例对《深入了解计算机系统》知识主线进行总结梳理,阐释分析了Linux系统下hello.c程序从 Program到Process,从无到有再到无的过程。运用了多种调试手段和工具,从计算机系统的角度解释了Linux系统对典型C语言程序的处理机制,有助于加深初学者对于计算机软硬件系统的初步认识。
关键词:计算机系统;Linux;程序生命周期;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 6 -
3.2 在Ubuntu下编译的命令............................................................................. - 9 -
4.2 在Ubuntu下汇编的命令........................................................................... - 14 -
5.2 在Ubuntu下链接的命令........................................................................... - 18 -
5.3 可执行目标文件hello的格式.................................................................. - 18 -
5.5 链接的重定位过程分析............................................................................... - 20 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 24 -
6.3 Hello的fork进程创建过程..................................................................... - 24 -
6.6 hello的异常与信号处理............................................................................ - 26 -
第7章 hello的存储管理............................................................................... - 30 -
7.1 hello的存储器地址空间............................................................................ - 30 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 30 -
7.3 Hello的线性地址到物理地址的变换-页式管理...................................... - 30 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 31 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 31 -
7.6 hello进程fork时的内存映射.................................................................. - 32 -
7.7 hello进程execve时的内存映射.............................................................. - 32 -
7.8 缺页故障与缺页中断处理........................................................................... - 33 -
8.1 Linux的IO设备管理方法.......................................................................... - 35 -
8.2 简述Unix IO接口及其函数....................................................................... - 35 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P,全称Program to Process,Program为hello.c源文件,Process为源文件经过编译等处理成为可执行文件的过程。
P2P过程:
- 预处理器cpp将hello.c进行预处理,处理头文件,得到hello.i文件
- 编译器ccl将其翻译为汇编语言文件hello.s
- 汇编器as将汇编语言翻译为二进制机器指令形式的代码,得到hello.o文件;
- 链接器ld将hello.o和其它用到的预编译好的目标文件合并到一起并且完成引用的重定位工作,就得到了一个可执行文件hello.out。
020:: From Zero-0 to Zero-0
020过程:shell为其execve映射虚拟内存。进入程序入口后,程序开始加载物理内存,然后进入main函数执行目标代码。 CPU为正在运行的hello分配时间片以执行逻辑控制流,通过cache,TLB等机制加速访问时间。程序完成后,shell父进程负责恢复hello进程,子进程的相关数据被清除。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1.2.1 硬件环境
X86-64 CPU;2.20GHz;16.0G RAM;512G Disk
1.2.2 软件环境
Windows10 64位;VirtualBox;Ubuntu 20.04.4 64位
1.2.3 开发工具
Visual Studio 2022 64位;CodeBlocks 64位;vim + gcc
1.2.4 调试工具
Visual Studio自带调试工具;CodeBlocks自带调试工具;edb ; gdb ;
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c hello程序源代码
hello.i 经预处理处理得到的文本文件
hello.s 经编译后得到的汇编代码
hello.o 汇编后得到的可重定位目标执行文件
hello_objdumo.s hello.out的反汇编文件
hello.oasm hello.o的反汇编文件
hello链接后得到的二进制文件
1.4 本章小结
本章简单介绍了hello.c成为hello.out的P2P过程和020机制。写明了本次大作业使用的软硬件环境及开发调试工具。最后列出了中间结果及其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
(以下格式自行编排,编辑时删除)
概念:一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程,即在编译之前进行的处理。此过程会调用cpp将所有#起始的行解释为预处理指令,并且对源代码进行相应的转换,包括加入包含的源文件,处理宏定义或者处理条件语句等等,预处理阶段也会删除注释等等。
作用: 1.将预处理指令(可以简单理解为#开头的正确指令)转换为实际代码中的内容(替换)
2.如果头文件中包含了其他头文件,也需要将头文件展开包含
3.#if等先关语句代表需要判断条件编译等情况。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
(以下格式自行编排,编辑时删除)
应截图,展示预处理过程!
2.3 Hello的预处理结果解析
经过预处理后可以看到原先几十行的代码扩展为了3060行。注释全被删除,原先的代码放在了最后。前面为头文件代码的展开。
可以看到被调用的头文件的地址
找到stdlib.h头文件并打开,发现他也调用了其它头文件,头文件调用存在递归调用情况。
2.4 本章小结
(以下格式自行编排,编辑时删除)
本章展示了hello.c预处理成为hello.i的过程。并且通过解析hello.i文件验证了预处理的作用。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译是将高级语言转换为汇编语言的过程。程序编译之前,C语言编译器会进行词法分析、语法分析(-fsyntax-only),接着会把源代码翻译成中间语言,即汇编语言。
作用:编译主要是为了将高级语言(C,C++等)翻译为机器语言一一对应的汇编语言。
(以下格式自行编排,编辑时删除)
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
(以下格式自行编排,编辑时删除)
应截图,展示编译过程!
3.3 Hello的编译结果解析
3.3.1常量
常量分为字符串常量和整型常量
字符串常量被给予了专门的储存位置。
而整型常量被嵌入到了汇编代码中。
3.3.2变量
局部变量
hello.c文件中出现了局部变量i
从hello.s文件中可以看到i被分配在栈中。
3.3.3表达式
i=0为一个表达式
在hello.s中用movl操作实现了赋值表达式的功能。
3.3.4类型
比如变量i为int类型,需要4个字节,所以栈中为其分配了4个字节的空间。
3.3.5算数操作
(
i++为一个算数操作
在汇编指令中是使用aadl指令完成的。
3.3.6关系操作
Hello.c中存在关系操作i<8
汇编中用cmpl指令实现
3.3.7指针数组
hello.c中出现了argv[]的指针数组,其中arg[0]指向文件名,其余指向参数。
指针数组首地址存在了栈中-32(%rbp)上(之前在寄存器%rsi上)
对于指针数组argv,其每一个元素的大小都是8字节,而且数组中的元素应是连续存放的,因此地址偏移量也应该是8的倍数
由此处可以验证。
3.3.8控制转移
使用cmpl比较指令和jle跳转指令的组合实现。
3.3.9函数操作
hello.c中exit函数
汇编中先将参数1移入寄存器%edi中,然后利用call指令调用exit函数。
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.4 本章小结
本章从常量、变量、表达式、类型、算术操作、关系操作、数组指针、控制转移、函数操作九个方面追踪了其从hello.c到hello.s的变化,表明了编译器是如何利用汇编指令处理各类数据和操作。
(以下格式自行编排,编辑时删除)
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:把汇编语言翻译成机器语言的过程称为汇编
作用:将汇编语言翻译为加器语言。
(以下格式自行编排,编辑时删除)
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s
(以下格式自行编排,编辑时删除)
应截图,展示汇编过程!
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
输入指令readelf,得到各节信息的文件
ELF头:
包含了版本和系统信息、编码方式、节的大小和数量、ELF头大小和头部索引等等一系列信息。
节头目录
表明了各节的起始地址和偏移量等信息。
重定位节:
由图可知,重定位节中有各种引用的外部符号,给出了他们的偏移量。
有了这些信息之后,在下一步进行链接2,就可以通过重定位节对这些位置的地址进行重定位,使其映射到虚拟内存上。
符号表:
可以看到,其中包括了引用的函数等符号。
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
输入指令objdump -d -r hello.o,得到反汇编代码如下
解析:
机器语言实际上就是对汇编语言的编码,每一条汇编代码都有唯一的机器级编码。
操作数不同:hello.s中采用十进制操作数,而反汇编文件中采用16进制操作数。
分支转移:反汇编文件中,没有像hello.s中.L1, .L2, .L3等那样的跳转目标名称,而是以指令的地址为跳转。
函数调用:hello.s中的函数调用采用直接跳转到函数名的方式进行跳转,而反汇编文件中则是采用间接跳转,跳转位置为主函数起始地址加上偏移量。
4.5 本章小结
本章对elf文件格式进行了研究,介绍了汇编代码的概念、结构、作用,对汇编与反汇编代码进行了比较。
(以下格式自行编排,编辑时删除)
(第4章1分)
第5章 链接
5.1 链接的概念与作用
(以下格式自行编排,编辑时删除)
注意:这儿的链接是指从 hello.o 到hello生成过程。
链接的概念:链接时将各种代码和数据片段收集并组合成为一个单一的文件的过程,这个文件可被加载(复制)
链接的作用:将目标文件集合整合成一个文件,并将其映射到对应的虚拟内存,得到可执行文件,并且在链接的过程中,会合并一些提前预制编译的模块,比如printf.o,这也使得程序的运行效率提升(省去了编译printf模块的环节)
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等列出其各段的基本信息,包括各段的起始地址,大小等信息。
输入命令,生成helloELF2文件,查看hello的ELF格式
ELF 头:
可以看到里面的信息有系统版本,框架,大小,起止位置,此节大小等信息。
节头:
可以看到,节头描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
用edb查看hello的虚拟地址,以.hash节为例,可以看到它与5.3的地址保持一致。所有节都与5.3的信息保持一致。
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
使用命令objdump -d -r hello,得到hello的反汇编代码。
经过对hello和hello.o的比较分析,可以看出hello反汇编的代码有明确的虚拟地址,即完成了重定位,而hello.o中虚拟地址为0,未完成重定位,如下图所示。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
401000 <_init>
401020 <.plt>
401030 <puts@plt>
401040 <printf@plt>
401050 <getchar@plt>
401070 <exit@plt>
401080 <sleep@plt>
4010f0 <_start>
401120 <_dl_relocate_static_pie>
401125 <main>
4011c0 <__libc_csu_init>
401230 <__libc_csu_fini>
401238 <_fini>
5.7 Hello的动态链接分析
(以下格式自行编排,编辑时删除)
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
对于动态共享链接库中PIC函数,编译器添加重定位记录,等待动态链接器处理,同时,链接器采用延迟绑定的方法。
可以看出,在dl_init前, PIC函数调用的目标地址都实际指向PLT中的代码逻辑,初始时每个GOT条目都指向对应的PLT条目的第二条指令。
在dl_init后,部分数据信息发生变动。
5.8 本章小结
(以下格式自行编排,编辑时删除)
本章介绍了链接的概念和作用,阐述hello.o如何链接成为可执行文件,介绍了ELF格式和各个节的含义。还使用了edb进行了hello各节的深入分析。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
(以下格式自行编排,编辑时删除)
进程的概念:进程的经典定义就是一个执行中程序的实例。进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。系统中的每个程序都运行在某个进程的上下文中,由程序正确运行的状态组成的,包括存放在内存中的程序的代码和数据,它的栈,通用目的寄存器的内存,程序计数器,环境变量以及打开文件描述符的集合。
进程的作用:进程提供给应用程序两个关键的抽象。一个独立的逻辑控制流,他提供一个假象,好像我们的程序独占的使用处理器。一个私有的地址空间,它提供一个假象,好像我们的程序独占的使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
(以下格式自行编排,编辑时删除)
Shell是用户和操作系统之间完成交互式操作的一个接口程。bash是Linux操作系统的默认shell程序。
Shell是“提供使用者使用界面”的软件,是一个命令行解释器,作用是:保护内核同时帮助使用者向计算机传递交互信息。
处理流程:
1.用户输入命令或程序代码。
2.Shell解析输入信息。
3.若为内置命令,调用内置命令处理函数,否则调用fork( )创建子进程。
4.若为前台运行程序,调用wait()直至前台作业结束,如果为后台命令则不等待。
6.3 Hello的fork进程创建过程
(以下格式自行编排,编辑时删除)
当使用者输入./hello命令时,shell解析该命令,由于这不是内置命令。于是 shell 调用 fork()函数,创建一个子进程来执行hello。
6.4 Hello的execve过程
(以下格式自行编排,编辑时删除)
在创建了一个子进程后,子程序调用execve函数在上下文加载hello程序。
此时子进程还会为新的程序映射新的数据段和代码段区域,以及共享区,然后将程序计数器(PC)的值修改为新程序的代码区域入口处的地址。
6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
调度过程:在系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。
在进程执行的某些时刻,内核可以决定先停止当前进程的运行,然后重新启动一个先前以及暂停了的进程,这种决策就叫做进程的调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
用户态和核心态:
上图为用户态和核心态之间的转换示意图。
在调用进程发送sleep之前,hello在当前的用户内核模式下运行,在内核中进程再次调用当前的sleep之后进程转入用户内核等待休眠模式,内核中所有正在处理等待休眠请求的应用程序主动请求释放当前正在发送处理sleep休眠请求的进程,自动将当前调用hello的进程加入正在执行等待的队列,移除或退出正在内核中执行的进程等待队列。
设置定时器,休眠的时间为自己设置的时间,当计时器时间到,发送一个中断信号。内核收到中断信号进行中断处理,hello被重新加入运行队列,等待执行,这时候hello就可以运行在自己的逻辑控制流里面了。
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
异常分为4类,如上图所示。
产生信号如上。
正常运行状态:
按照提示输入指令及参数,可以看到程序按照设定的时间间隔休眠,然后输出特定信息。
按Ctrl+Z:
按下Ctrl+Z之后,进程会收到一个SIGSTP 信号,使得当前的hello进程被挂起。
用ps指令查看其进程PID,可以发现hello的PID是26695;再用jobs查看此时hello的后台 job号是1,调用指令fg 1将其调回前台。首先打印命令行命令,然后把剩余Hello信息输出。
按Ctrl+C:
此时进程收到一个SIGINT 信号,一次结束 hello,使用命令ps,发现查询不到hello进程,输入命令jobs,也没有发现对应作业,所以hello进程被彻底终止。
不停乱按:
程序运行时乱按键盘,会发现屏幕的输入缓存到stdin。getchar的时候读出一个’\n’结尾的字符串,其他当作命令行输入。
kill命令:
使用kill命令杀死hello进程后,再用ps指令可以看到hello进程号消失,并且提示表明已被杀死。
6.7本章小结
(以下格式自行编排,编辑时删除)
本章阐述了进程的概念和作用,介绍了shell的处理流程,分析了fork创建进程和调用execve函数的流程,hello的进程执行和异常处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
(以下格式自行编排,编辑时删除)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:访问指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址,由选择符和偏移量组成。要经过寻址方式的计算或变换才得到内存储器中的物理地址。
线性地址:线性地址或也叫虚拟地址,逻辑地址经过段机制后转化为线性地址,是一个不真实的地址,线性地址对应硬件页式内存的转换前地址。
虚拟地址:虚拟地址即线性地址,虚拟地址是对物理地址的映射。
物理地址:真实的物理内存对应地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
(以下格式自行编排,编辑时删除)
首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
1.看段选择符的T1=0还是1,判断当前要转换是GDT中的段还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组。
2.拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样基地址就知道了。
3.把Base + offset,就是要转换的线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
(以下格式自行编排,编辑时删除)
首先,将线性地址划分为VPN+VPO的格式,然后将VPN拆分为TLBT+TLBI的格式,然后在TLB中寻找对应的PPN,如果有缺页的情况发生,那么就去下一级页表中寻找对应的PPN,以此类推。找到PPN,将其与之前的VPO进行组合就得到了对应的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
(以下格式自行编排,编辑时删除)
多级页表将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。其结构类似于多级缓存的结构。
在四级页表的情况下,会将VPN的部分分成等长的4个段,将每一个段作为某一级页表的索引,直到找到正确的PPN。
多级页表示意图如下:
7.5 三级Cache支持下的物理内存访问
(以下格式自行编排,编辑时删除)
L1Cashe的物理访存大致过程如下:
(1) 组选择:取出虚拟地址的组索引位,把二进制组索引转化为一个无符号整数,找到相应的组
(2) 行匹配:把虚拟地址的标记为拿去和相应的组中所有行的标记位进行比较,当虚拟地址的标记位和高速缓存行的标记位匹配时,而且高速缓存行的有效位是1,则高速缓存命中。
(3) 字选择:一旦高速缓存命中,我们就知道我们要找的字节在这个块的某个地方。因此块偏移位提供了第一个字节的偏移。把这个字节的内容取出返回给CPU。
(4)不命中:如果高速缓存不命中,那么需要从存储层次结构中的下一层取出被请求的块,然后将新的块存储在组索引位所指示的组中的一个高速缓存行中。一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,产生冲突,则采用最近最少使用策略 LFU进行替换。具体过程如下图所示:
7.6 hello进程fork时的内存映射
(以下格式自行编排,编辑时删除)
当fork 函数被shell调用时,内核为进程创建各种数据结构,并分配给它一个唯一的PID。为了给进程创建虚拟内存,它创建了hello进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork 在进程中返回时,进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
(以下格式自行编排,编辑时删除)
在创建了一个子进程后,子程序调用execve函数在上下文加载hello程序。
(1)删除当前虚拟地址中已存在的用户区域。
(2)为新程序建立新的区域结构,这些区域结构是私有的,虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区,bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
(3)如果hello与共享对象链接,那么这些对象都被动态链接到这个程序,然后映射到用户虚拟地址空间中的共享区域。
(4)设置程序计数器,使之指向代码区域的入口点,下次调用这个进程时,从这个入口点开始执行。
7.8 缺页故障与缺页中断处理
(以下格式自行编排,编辑时删除)
如果在页表中发生了一次不命中,我们就认为发生了一次缺页故障,具体处理流程如下:
- 由于PTE中对应页的有效位是0,所以MMU出发了一次缺页异常信号,此时操作系统就开始调用相应的异常处理程序。
- 被调用的缺页处理程序会根据一定的替换策略确认出物理内存中的牺牲页,如果这个页已经被修改了,程序就会把它换到磁盘。
- 缺页处理程序在物理内存中写入新的页面,并更新内存中的PTE。
- 完成了上述操作之后,缺页处理程序就会返回到原来的进程,再次执行刚才导致缺页的命令。此时,因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
动态存储分配管理由动态内存分配器完成。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。堆是一个请求二进制零的区域,它紧接在未初始化的数据区后开始,并向上生长(向更高的地址)。分配器将堆视为一组不同大小的块的集合来维护。
每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显示地被应用程序所分配。
一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
动态内存分配器从堆中获得空间,将对应的块标记为已分配,回收时将堆标记为未分配。而分配和回收的过程中,往往涉及到分割、合并等操作。
动态内存分配器的目标是在对齐块的基础上,尽可能地提高吞吐率及空间占用率,即减少因为内存分配造成的碎片。其实现常见的数据结构有隐式空闲链表、显式空闲链表、分离空闲链表,常见的放置策略有首次适配、下一次适配和最佳适配。
7.10本章小结
(以下格式自行编排,编辑时删除)
本章分析了hello的存储管理,主要涉及内存。介绍了Intel逻辑地址到线性地址的变换-段式管理,以及TLB与多级页表支持下的VA到PA的转换,同时对三级Cache支持下的物理内存访问做了说明。简述了hello的fork和execve内存映射,了解了缺页故障与缺页中断处理程序,介绍了动态存储分配管理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
文件包括:普通文件(包含任意数据的文件)、目录(文件夹,包含一组链接的文件,每个链接都将一个文件名映射到一个文件)、套接字(用来与另一个进程进行跨网络通信的文件)、命名通道、符号链接以及字符和块设备。
设备管理:unix io接口
操作包括:打开和关闭文件、读取和写入文件以及改变当前文件的位置。
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
接口:
(1)打开文件。一个应用程序通过要求内核打开相应的文件,来访问一个 I/O 设备,内核返回一个小的非负整数作为描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
(2)Shell 创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。 (3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置 k,初始为 0,这个文件位置是从文件开头起始的字节偏移量。
(4)读写文件:一个读操作就是从文件复制 n>0 个字节到内存,从当前文件位置 k 开始,然后将 k 增加到 k+n,给定一个大小为 m 字节的而文件,当 k>=m 时,触发 EOF。类似一个写操作就是从内存中复制 n>0 个字节到一个文件,从当前文件位置k开始,然后更新k。
(5)关闭文件,内核释放打开文件时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
函数:
(1)int open(char* filename,int flags,mode_t mode) ,进程通过调用 open 函数来打开一个存在的文件或是创建一个新文件。
(2)int close(fd),fd 是需要关闭的文件的描述符,close 返回操作结果。
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
实现过程调用了vsprintf和write函数,接受一个格式串之后将匹配到的参数按照格式串的形式输出。
printf函数的实现过程调用了Vsprintf函数生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
getchar有一个int型的返回值。当程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。
当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
8.5本章小结
(以下格式自行编排,编辑时删除)
本章主要介绍了 Linux 的 IO 设备管理方法、Unix IO 接口及其函数,分析了 printf 函数和 getchar 函数的实现。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
Hello经历的过程:
(1)我们写出hello.c文件,也就是.c源文件。
(2)进行预处理,把各种库加入进来,把一些宏定义进行处理,产生hello.i。
(3)接下来通过编译,hello.i又变为了hello.s文件,内容为汇编指令等等。
(4)把hello.s变为我们的计算机真正看得懂的可重定位文件hello.o。
(5)通过动态链接最终得到可执行文件hello,由此完成了P2P。
(6)进行人机交互,在shell上输入命令行,shell读取我们的命令,调用fork和execve为我们的hello加载虚拟内存,分配时间片,在此期间程序通过我们的缓存结构,从内存或cache、磁盘等位置取得数据。而某些输入如Ctrl+z等等会导致内核产生并发送信号,造成诸如程序被挂起等等后果。
(7)shell将其回收,删除其创建的栈堆等等数据结果,hello的一生至此完毕。
感悟:计算系统的设计与实现非常复杂但又充满魅力,只有充分理解它,才能更好地理解程序,理解编程。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)
中间产物文件名 | 作用 |
hello.i | 预处理后的ASCII码文件 |
hello.s | 编译之后得到的文件 |
hello.o | 汇编之后得到的目标文件 |
hello | 链接之后得到的可执行文件 |
helloELF.txt | 目标文件hello.o的ELF格式 |
helloFLF2.txt | 可执行文件hello的ELF格式 |
hello_objdump | 目标文件hello.o文件反汇编得到的结果 |
hello_objdump2 | 可执行文件hello反汇编之后的结果 |
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Eandal E. Bryant David R. O Hallaron. 深入理解计算机系统[M] 北京 :机械工业出版社,2016.10
(参考文献0分,缺失 -1分)