作业"/>
HIT CSAPP大作业
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 7203610722
班 级 20361014
学 生 王文瑞
指 导 教 师 刘宏伟
计算机科学与技术学院
2021年5月
摘 要
本文简述了hello.c程序从编写完成到真正运行的全过程,对其中涉及到的计算机系统原理做了深入剖析。我们使用gcc,objdump,edb等一系统工具,对这段简短的代码进行了预处理、编译、汇编、链接与反汇编的完整操作,进行了严格的分析和比较,并且借助shell和其他linux软件对程序的运行过程进行解读,最后还从剖析了计算机储存原理以及程序的读写机制
关键词:预处理,编译,汇编,链接,进程,储存,I/O
(摘要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简介
P2P:from program to process
hello.c文件经过预处理器cpp生成.i文件,经过编译器编译,将高级语言转化为指令,生成.s文件,再经过汇编(as)生成二进制.o文件,再经过链接器与c标准库动态链接,生成可执行的二进制目标文件hello。最后由 shell调用fork函数,execve函数创建新的进程。
020:from zero to zero
shell 调用fork创建新的子程序,在子程序中调用execve,加载并运行helllo, 映射虚拟内存,进入 main 函数执行目标代码。当程序运行结束后,shell 父进程回收 hello 进程,内核删除相关数据结构。
1.2 环境与工具
1.2 环境与工具
硬件环境:Intel Core i7 2.8GHZ 16GB内存
软件环境:Unbuntu18.04 Windows10 VMware 16PRO
开发工具:VS 2019 codeblocks gcc edb gdb readelf
1.3 中间结果
hello.i:hello.c经预处理得到的ASCII码的中间文件。
hello.s:hello.i编译之后得到的一个ASCII汇编语言文件。
hello.o:hello.s汇编之后得到的一个可重定位目标文件。
hello:hello.s和标准的C库进行链接得到的可执行目标文件。
hello.objdump:hello.o的反汇编代码。
Hello1.objdump:hello的反汇编代码。
hello.elf:hello.o的elf格式文件。
hello1.elf:hello的elf格式文件。
1.4 本章小结
本章介绍了hello的p2p , 020过程,以及开发环境和生成的中间文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——(用C/C++的术语来说是)预处理记号(preprocessing token)用来支持语言特性(如C/C++的宏调用)。
作用:根据以字符#开头的命令,修改原始的c程序
C/C++要求支持的包括#if/#ifdef/#ifndef/#else/#elif/#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令)
2.2在Ubuntu下预处理的命令
应截图,展示预处理过程!
2.3 Hello的预处理结果解析
我们发现生成的hello.i文件中本来的#include内容被替换成了其他内容(引用头文件),而其他部分未发生改变。
2.4 本章小结
本章介绍了文件预处理的原理和过程,并进行了实验观察生成的文件hello.i。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:利用编译程序从源语言编写的源程序产生目标程序的过程。
作用: 将高级语言翻译成汇编语言,汇编语言为不同机器提供程序的执行方案。
3.2 在Ubuntu下编译的命令
-
- Hello的编译结果解析
1.文件声明
.file 源文件名
.text代码段(codesegment/textsegment)
通常是指用来存放程序执行代码的一块内存区域
.section 指示把代码划分成若干个段
.rodata 用于维护只读数据
.align 8 地址对齐的伪指令,用来指定符号的对齐方式
.string 字符串的存储位置
.global用来让一个符号对链接器可见,可以供其他链接对象模块使用;告诉编译器后续跟的是一个全局可见的名字(可能是变量,也可以是函数名)
.type 指定是对象类型或是函数类型
2.数据
2.1整型: 如
main函数中的参数 argc
局部变量 int i
-
- 字符串
两个字符串存在rodata段中
LC0中表示的是默认输出,LC1中表示的是argc==3时的输出
2.3指针数组
argv[]数组作为参数,其首地址被存放在%rsi
3.运算符及其他操作
赋值:mov 指令
算术:
比较:
数组,指针,结构体操作
通过add指令来将指针指向目标数据,如argv[1],被编译为addq
\$16,%rax。
4. 控制转移
If跳转
For循环语句跳转
5.函数调用
1.main函数:
参数传递:传入参数argc和argv,分别用寄存器%rdi和%rsi存储。
函数调用:被系统启动函数调用。
函数返回:设置%eax为0并且返回,对应return 0 。
2.printf函数:
参数传递:第一次 printf 将%rdi 设置为“Usage: Hello 学号 姓名! \n”字符串的首地址。第二次 printf 设置%rdi 为“Hello %s %s\n” 的首地址,设置%rsi 为 argv[1],%rdx 为 argv[2]。
函数调用:第一次 printf 因为只有一个字符串参数,所以 call puts@PLT;第二次 printf 使用 call printf@PLT。
3.exit函数:
参数传递:传入的参数为1,并将%edi 设置为 1,再执行退出命令
函数调用:if判断条件满足后并在if内容基本执行完成后被调用,对应于汇编 文件中语句call exit@PLT。
4.sleep函数:
参数传递:传入参数argv[3]
函数调用:for循环下被调用,对应于汇编文件中的call sleep@PLT。
5.getchar
函数调用:在main中被调用,对应于汇编文件中的call gethcar@PLT。
3.4 本章小结
本章介绍了编译的概念和作用,演绎了编译的过程,并且对hello.s的汇编代码进行了解释
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:把汇编语言书写的程序翻译成与之等价的机器语言程序,把这些机器语言指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
(以下格式自行编排,编辑时删除)
4.3 可重定位目标elf格式
先通过指令readelf -a hello.o > hello.elf,获得hello.elf
分析:
ELF头
ELF头部以一个16字节的序列开始,描述生成该文件的系统的字的大小和字节顺序。剩下的部分包含帮助链接器分析语法和解释目标文件的信息,其中包含ELF头大小、目标文件的类型、及其类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。
节头部表
节头部表描述了不同节的位置和大小,其中目标文件中每个节都有一个固定大小的条目。具体的描述包括节的名称、类型、地址和偏移量等
重定位条目
重定位是一种用符号定义连接符号引用的过程。可重定位文件必须拥有描述如何去修改它们section
contents的信息,好让可执行或共享目标文件保存程序镜像的正确信息。可重定位条目就是这些数据。
符号表
存放在程序中定义和引用的函数和全局变量的信息
4.4 Hello.o的结果解析
用指令objdump -d -r hello.o > hello.objdump生成反汇编的文本文件hello.objdump。
将其与hello.s文件进行比较
下面是hello.s文件
对比发现:
1..s文件中还有许多的伪代码,而反汇编代码中没有
2.反汇编代码中采用16进制表示常数,而.s中采用10进制
3.关于跳转,反汇编代码中用相对偏移位置表示,而.s文件中是用标记的位置名称表示
4.5 本章小结
分析了hello.o的elf文件的信息,之后通过查看反汇编代码,比较与.o文件的差别,经过了汇编之后,我们的程序更容易被机器读懂。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。
作用:
链接器使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,二是可以把他分解为更小的更好管理的模块,可以独自修改和编译这些模块。当我们改变其中的一个块时,只需要简单的重新编译他,并重新链接应用,而不必重新编译其它文件。
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.3 可执行目标文件hello的格式
readelf -a hello > hello1.elf,获得hello.elf
ELF头:描述了可执行文件的总体格式,其中有关于执行的入口等信息
节头部表:对可执行文件的所有节信息进行了声明,包括大小,偏移量等信息
程序头:
INTERP:指定在程序已经从可执行映射到内存之后,必须调用解释器
PHDR:保存程序头表
LOAD:表示一个从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串),程序的目标代码等等
DYNAMIC:保存了其他动态链接器(即,INTERP中指定的解释器)使用的信息
NOTE:保存了专有信息
GNU_STACK:堆栈段
GNU_RELRO:在重定位之后哪些内存区域需要设置只读
5.4 hello的虚拟地址空间
用edb打开hello的可执行文件,在Data
Dump中找到hello的虚拟地址空间
ELF被映射到了0x401000,其余段按表中所示,依次映射到相应位置。
5.5 链接的重定位过程分析
objdump -d -r hello > hello1.objdump 生成hello的反汇编代码文本文件
将其与hello.o的反汇编代码比较:
可执行文件中多出了很多个函数,为程序执行所必要的函数,如sleep、getchar等
可执行文件增加了.init和.fini等节
地址编码方式不同,可执行文件采用虚拟地址,hello.o采用相对地址。
重定位过程主要有以下两步:
重定位节和符号定义:链接器将所有的同类型的节合并成一个新的节。然后链接器将内存地址赋给新的节以及每个符号。
重定位节中的符号引用:
链接器依赖于hello.o中的重定位条目,修改代码节和数据节中对每个符号的引用,使得它们指向正确的内存地址。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
start函数,地址 0xe9f00df4
libc_start_main函数,地址0xe9f00df4
libc-2.27.so!__cxa_atexit
libc-2.27.so!__new_exitfn
_libc_csu_init
_libc_csu_init 0xe9f00501
_init函数
libc-2.27.so!_setjmp函数
-libc-2.27.so!_sigsetjmp函数
–libc-2.27.so!__sigjmp_save函数
main
puts
exit
ld-2.27.so!_dl_runtime_resolve_xsave
ld-2.27.so!_dl_fixup
5.7 Hello的动态链接分析
在执行_dl_int函数前先找到got表
从而找到该处地址,在执行_dl_init函数后:
在_dl_init函数执行之后global_offset表由全0地状态被赋值上相应地值.
5.8 本章小结
本章主要介绍了链接的概念与作用、hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用作用:
进程为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象
6.2 简述壳Shell-bash的作用与处理流程
Shell的作用:shell是一种交互型的应用级程序。它能够接收用户命令,然后调用相应的应用程序,即代表用户运行其他程序。
处理流程:
1)从终端读入输入的命令。
2)将命令切分获得参数
3)如果是内置命令则立即执行
4)否则调用相应的程序为其分配子进程并运行
5)接受各种信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
用户输入命令行,shell解析输入的命令行,因为hello不是一个内置的shell命令,首先会调用fork函数创建一个新的运行的子进程,新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,这就意味着,当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程与子进程之间最大的区别在于它们拥有不同的PID[2]。
父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。6.4 Hello的execve过程
当fork之后,子进程调用execve函数在当前进程的上下文中加载并运行一个新程序即hello程序,execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。
新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存[2]。
6.5 Hello的进程执行
多个流并发地执行的一般现象被称为并发。一个进程和其他进程轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。
内核为每个进程维持一个上下文,上下文就是内核重新启动一个先前被抢占的进程所需的状态。
在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个过程称为调度。
在此基础上,hello程序与操作系统其他进程通过操作系统的调度,切换上下文,拥有各自的时间片从而实现并发运行。
程序在涉及到一些操作时,例如调用一些系统函数,内核需要将当前状态从用户态切换到核心态,执行结束后再及时改用户态,从而保证系统的安全与稳定。
上下文切换的具体过程:
保存当前进程上下文,恢复某个先前被抢占的进程被保存的上下文,最后将控制传递给这个新恢复的进程
6.6 hello的异常与信号处理
(以下格式自行编排,编辑时删除)
hello执行过程中可能出现三类异常:中断、故障和终止
中断是来自I/O设备的信号,异步发生,中断处理程序对其进行处理,返回后继续执行调用前待执行的下一条代码,就像没有发生过中断。
故障是由错误情况引起的,它可能能够被故障处理程序修正。如果修正成功,则将控制返回到引起故障的指令,否则将终止程序。
终止是不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程序会将控制返回给一个abort例程,该例程会终止这个应用程序。
hello执行过程中,可能会遇到各种异常,信号则是一种通知用户异常发送的机制。例如较为底层的硬件异常以及较高层的软件事件,收到信号后进程会调用相应的信号处理程序对其进行处理。
6.7本章小结
本章介绍了进程的概念与作用,介绍了Shell的一般处理流程,调用fork创建新进程,调用execve执行hello,hello的进程执行,hello的异常与信号处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:Intel为了兼容,保留了IA-32的段式管理方式。逻辑地址是由一个段标识符加上一个指定段内相对地址的偏移量组成的,表示为段标识符:段内偏移量.
线性地址:线性地址指的是连续的地址空间组成的地址
虚拟地址:虚拟地址只的是虚拟内存中的地址,从程序员的视角看到的都是虚拟地址
物理地址:物理地址指的是在主存中的真实地址,一个页只有被加载到主存中,才会有物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成,段标识符和段内偏移量
虚拟内存可以分成很多的段,每个段都有段描述符,这个描述符是记录在段描述符表里边的,段描述符记录了段的起始地址。
Intel处理器在通过段式管理寻址时,首先通过段描述符得到段基址,然后与偏移量结合得到线性地址,从而得到了虚拟地址
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存相对与物理内存很大,虚拟内存分页,动态的载入到物理内存中。页表记录了虚拟页在不在物理内存中的信息。虚拟内存用4KB划分,每4KB为一页,物理内存也有同样的划分方式,即二者的页大小一定是一致的
寄存器CR3专门记录了页表的基址,每个进程有独立的页表,基址也就不一样,所以上下文切换的过程中CR3要动态更新。
得到虚拟地址后,根据虚拟地址的高位VPN到页表中寻找对应的项,如果页表项已经载入,那么直接得到对应的PPN与VPO拼接则得到物理地址;如果页表项没有载入,那么发生缺页故障,由缺页异常处理程序进行处理。
7.4 TLB与四级页表支持下的VA到PA的变换
VPN:虚拟页号
VPO:虚拟页面偏移
PPN:物理页号
PPO:物理页面偏移
地址翻译过程中:首先将VA分成VPN和VPO,用VPN到TLB中找对应的表项,过程类似于高速缓存的过程。如果TLB命中,可以直接得到PPN,如果TLB不命中,则以VPN作为索引到页表中取相应的项,将VPN分成四个部分,分别对应四级页表的索引,前边几级得到的是下一级页表的索引,最后一级得到的是相应的PPN。如果任意一级页表中有效位为0,那么这个页都不在物理内存中,此时,要交由缺页异常处理程序处理,修改页表对应的值,重新执行导致缺页异常的指令
7.5 三级Cache支持下的物理内存访问
首先,通过页表的查询,由VA可以得到PA,然后就可以通过PA访问cache,先访问L1cache,首先将PA分成CT CI CO 三个部分,通过CI找到对应的组,然后通过CT和cache中的tag匹配寻找相应的项,如果命中,则可以以CO作为偏移得到数据,如果不命中,则继续向L2L3cache发出请求,如果cache都不命中就到主存中请求相应数据
7.6 hello进程fork时的内存映射
当调用fork()时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前程序的mm_struct,区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中每个区域结构都标记为只读、私有的写时复制。当发生写的时候,会引起相应的保护故障,有操作系统函数来进行复制,并修改权限,此时再写就可以不会引发故障,这样的机制节省了宝贵的物理内存资源
7.7 hello进程execve时的内存映射
- 删除已存在的用户区域。即当前虚拟地址用户部分已存在的结构
- 映射私有区域。为新程序代码,数据,和堆栈区域创建新的区域结构。
- 映射共享区域。如果与共享对象链接比如libc.so,那么这些对象都是动态链接到这个程序的,然后在映射到用户虚拟地址的共享区域。
- 设置程序计数器。设置当前进程的PC,使之指向代码区域的入口
7.8 缺页故障与缺页中断处理
DRAM缓存不命中称为缺页,即虚拟内存中的字不在物理内存中。CPU引用了虚拟页的一个字,地址翻译硬件从内存中读取了该虚拟页对应的页表条目,从有效位推断出该页未被缓存,这样就触发了一个缺页异常,缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,把要缓存的页缓存到牺牲页的位置。如果这个牺牲页被修改过,就把它交换出去。当缺页处理程序返回时,CPU 重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU 就能正常翻译VA了。
7.9动态存储分配管理
动态内存分配是由内存分配器实现的。内存分配器维护着堆区域。内核维护着一个变量brk指向堆顶。分配器把堆视为一组大小不相等的块的集合。每个块都是一个连续的虚拟内存空间,是已分配的或空闲的。已分配的块显式的保留为供应程序使用。空闲块可以用来分配。一个已分配的块保持已分配状态直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
带边界标签的隐式空闲链表分配器:
每个块增加四字节的头部和四字节的脚部保存块大小和是否分配信息,可以在常数时间访问到每个块的下一个和前一个块,使空闲块的合并也变为常数时间,而且可以遍历整个链表。隐式空闲链表即为,利用边界标签区分已分配块和未分配块,根据不同的分配策略(首次适配、下一次适配、最佳适配),遍历整个链表,一旦找到符合要求的空闲块,就把它的已分配位设置为1,返回这个块的指针。隐式空闲链表并不是真正的链表,而是"隐式"地把空闲块连接了起来(中间夹杂着已分配块)。
显式空闲链表的基本原理:
因为隐式空闲链表每次查找空闲快都需要线性地遍历整个链表,而其中的已分配块显然是不需要遍历的,所以浪费了大量时间,一种更好的方式是把空闲块组织
成一个双向链表,每个空闲块中包含一个pred和succ指针,指向它的前驱和后继,在申请空闲块时,就不需要遍历整个堆,只需要利用指针,在空闲链表中遍历空闲块即可。一旦空闲块被分配,它的前驱和后继指针就不再有效,变成了有效载荷的一部分。显式空闲链表的已分配块与隐式空闲链表的堆块的格式相同。
7.10本章小结
本章介绍了hello的内存管理,包括地址空间、段页式存储、cache访问、内存映射、缺页故障处理、动态内存分配等内容
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
一个Liunx文件就是一个m个字节的序列:B0,B1,…,Bm-1。所有的I/O设备都被模型化为文件。
文件的类型有:
1. 普通文件:包含任何数据,分两类
文本文件:只含有ASCII码或Unicode字符的文件
二进制文件:所有其他文件
2. 目录:包含一组链接的文件。每个链接都将一个文件名映射到一个文件
3. 套接字:用于与另一个进程进行跨网络通信的文件
设备管理:unix io接口
所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅的映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
Unix I/O接口的几种操作:
1. 打开文件:程序要求内核打开文件,内核返回一个小的非负整数(描述符),用于标识这个文件。程序在只要记录这个描述符便能记录打开文件的所有信息。
2. shell在进程的开始为其打开三个文件:标准输入、标准输出和标准错误。
3. 改变当前文件的位置:对于每个打开的文件,内核保存着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作显式地设置文件的当前位置为k。
4. 读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k>=m时执行读操作会出发一个称为EOF的条件,应用程序能检测到这个条件,在文件结尾处并没有明确的EOF符号。
5. 关闭文件:内核释放打开文件时创建的数据结构以及占用的内存资源,并将描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
Unix I/O 函数:
1. int open(char *filename, int flags, mode_t mode);
open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
2. int close(int fd);
关闭一个打开的文件。
3. ssize_t read(int fd, void *buf, size_t n);
read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
4. ssize_t write(int fd, const void *buf,size_t);
write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。8.3 printf的实现分析
8.3 printf的实现分析
va_list arg = (va_list)((char*)(&fmt) + 4);
由于printf函数的参数位置不确定,我们要根据格式串确定参数数量,这句语句实际上是在确定参数的地址
vsprintf(buf, fmt, arg);
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
va_list arg = (va_list)((char*)(&fmt) + 4);
由于printf函数的参数位置不确定,我们要根据格式串确定参数数量,这句语句实际上是在确定参数的地址
vsprintf(buf, fmt, arg);
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
write(buf, i);
i是返回字符串的长度,这里是要将buf中的字符串写向终端。write是系统函数,这里使用int 0x80或syscall陷入内核
打印的过程是由字符串显示子程序运行的。字符显示驱动子程序从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
i是返回字符串的长度,这里是要将buf中的字符串写向终端。write是系统函数,这里使用int 0x80或syscall陷入内核
打印的过程是由字符串显示子程序运行的。字符显示驱动子程序从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返用户从键盘输入后,通过CPU的引脚信号,发现键盘处的异常(异步异常)。当发现用户输入的符号是Enter的时候,getchar函数将调用系统函数read将用户输入的内容全部读到缓冲区中,并只从缓冲区中读一个元素,如果发现缓冲区中为空,则返回EOF。回。
8.5本章小结
本章介绍了hello的IO管理,包括对Unix IO的简介以及对printf函数和getchar函数的详细分析,对Linux的IO设备管理有了进一步认识。
(第8章1分)
结论
Hello的一生:
用c语言编写成代码hello.c , 先经过预处理,生成hello.i , 再经过编译,生成hello.s文件,经过汇编,生成可重定位的hello.o二进制文件。接着,链接器将hello.o与动态库函数链接到一起,生成最后的可执行文件hello 。在shell中执行./hello , 开始为hello创建进程并将hello程序加载到内存中运行,如遇到异常则要进行异常,最后程序执行结束,进程被回收,hello结束了它的一生。
感悟:
计算机系统是一门深奥且知识繁多的课程。仅仅一个简单的.c程序的运行就有如此多的学问,学好这一门课对提高程序的性能有很大帮助。
结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.i:hello.c经预处理得到的ASCII码的中间文件。
hello.s:hello.i编译之后得到的一个ASCII汇编语言文件。
hello.o:hello.s汇编之后得到的一个可重定位目标文件。
hello:hello.s和标准的C库进行链接得到的可执行目标文件。
hello.objdump:hello.o的反汇编代码。
Hello1.objdump:hello的反汇编代码。
hello.elf:hello.o的elf格式文件。
hello1.elf:hello的elf格式文件。
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 百度百科 /
[2] Randal E.Bryant: David R.O’Hallaron 深入理解计算机系统. 机械工业出版社
[3] shell命令行处理流程 =dra
[4]
[5]
[6]
(参考文献0分,缺失 -1分)
更多推荐
HIT CSAPP大作业
发布评论