HIT-CSAPP大作业

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 英才学院
学   号 7203610417
班   级 2036012
学 生 张子庆  
指 导 教 师 刘宏伟

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

关键词:预处理、编译、汇编、链接、进程、存储、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简介
1.最初通过编辑器编写hello的程序建立.c文件,得到hello.c的源程序。
2.运行C预处理器(cpp)将其进行预处理生成hello.i文件。
3.运行C编译器(ccl)将其进行翻译生成汇编语言文件hello.s。
4.运行汇编器(as)将其翻译成一个可重定位目标文件hello.o。
5.运行链接器程序ld将hello.o和系统目标文件组合起来,创建了一个可执行目标文件hello。
1.2 环境与工具
1.硬件环境:X64 CPU;2GHz;4GRAM;256Disk
2.软件环境:Windows10 64位;Oracle VM VirtualBox;Ubuntu 20.04.4 64位
3.工具:codeblocks;gdb;Objdump;visualstudio
1.3 中间结果
hello.c 源程序
hello.i 预处理后文件
hello.s 编译后的汇编文件
hello.o 汇编后的可重定位目标执行文件
hello 链接后的可执行文件
hello.elf hello.o的ELF格式
hellor.txt hello.o的反汇编
hellor2.txt hello的反汇编代码
hello1.elf hello的ELF格式
1.4 本章小结
本章介绍了hello在计算机整个处理过程。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
(1)概念
预处理是在编译之前进行的处理,一般指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。预处理器并不对程序的源代码进行解析,但它把源代码分割或处理为特定的段。预处理中会展开以#起始的行,修改原始的C程序,它检查包含预处理指令的语句和宏定义,并对源代码进行转换,还会删除注释和多余的空白字符。
(2)作用
预处理器会读取头文件中用到的库的代码,将这段代码直接插入到程序文件中。预处理指令是一种命令语句(如#define),它指示预处理程序如何修改源代码。在对程序进行通常的编译处理之前,编译程序会自动运行预处理程序,对程序进行编译预处理。
2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结
本章介绍了预处理的定义和作用。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
(1)概念
把代码转化为汇编指令的过程,汇编指令只是CPU相关的。
(2)作用
将高级语言源程序翻译成目标程序
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

3.3.1 数据
1.整数
1.1 int argc
%edi保存传入函数的第一个参数,即argc。

1.2 int i
编译器将局部变量存储在寄存器或者栈空间中,在hello.s中编译器将i存 储在%rbp-4中,图3-5中可以看出i占据了栈中的4位,符合int类型。

2.数组
2.1char argv[]

3.字符串
Hello.c的两个字符串存储位置如图所示

最后作为printf函数的参数调用

3.3.2 赋值

  1. int i
    i是保存在栈中的局部变量,使用mov语句并以l为后缀构成movl对i进 行赋值。

3.3.3 算数操作
1.for循环中i++
采用add指令,用addl对i进行增加。

2.leaq计算LC1段地址
for循环内部需要对LC1处字符串进行打印,使用了加载有效地址指令leaq 计算LC1的段地址%rip+.LC1并传递给%rdi。

3.3.4数组操作
C源程序中的数组操作出现在循环体for循环中,每次循环中都要访问 argv[1]、argv[2]这两个内存。
argv[1]:数组首地址存放于-32(%rbp),先将其存储到%rax中,再加上 偏移量$8,再将该位置内容放在%rsi中,成为下一个函数的第一个参数。
argv[2]:数组首地址存放于-32(%rbp),先将其存储到%rax中,再加上 偏移量$24,再将该位置内容放在%rdi中,成为下一个函数的第二个参数。

3.3.5 控制转移
1.if(argc!=4)

2.for(i=0;i<8;i++),for循环中每次循环判断i是否小于8结束循环

3.3.6 函数操作
函数是一种过程,过程提供了一种封装代码的方式,用一组指定的参数和 可选的返回值实现某种功能。
hello.c中涉及的函数操作有:
1.main函数:
(1)传递控制:
系统启动函数调用,call指令将下一条指令的地址压栈,然后跳转到 main函数。
(2)传递数据:
外部调用过程向main函数传递参数argc和argv,分别使用%edi和%rsi 存储,函数正常出口为return 0,将%eax设置0返回。
(3)分配和释放内存:
使用%rbp记录栈帧的底,函数分配栈帧空间在%rbp之上,程序结束时, 调用leave指令,leave相当于
mov %rbp,%rsp
pop %rbp
恢复栈空间为调用之前的状态,然后ret返回,ret相当pop IP。

2.printf函数:
(1)传递数据:
第一次printf将%rdi设置为“Usage: Hello 学号 姓名!\n”字符串的首地址。 第二次printf设置%rdi为“Hello %s %s\n”的首地址,设置%rdx为argv[1],%rsi 为argv[2]。
(2)控制传递:
第一次printf因为只有一个字符串参数,所以call puts@PLT;第二次printf 使用call printf@PLT。

3.exit函数:
(1)传递数据
将%edi设置为1。
(2)控制传递
call exit@PLT。

4.sleep函数:
(1)参数传递:
将atoi的返回值%eax通过%rdi传递给sleep函数
(2)控制传递:
调用了sleep函数,将控制传送。
(3)函数返回:
从sleep中返回。

5.getchar函数:
(1)控制传递
call gethcar@PLT

6.atoi函数
(1)参数传递:将argv[3](字符串)通过%rdi传递给atoi函数。
(2)控制传递:通过call atoi@PLT函数,进行函数调用。
(3)函数返回:从atoi中返回。

3.4 本章小结
本章主要介绍了有关编译的概念作用,然后使用gcc -S hello.c -o hello.s生成了编译后的文件。对于生成的.s文件,分析了C语言的数据与操作在机器之中如何被处理翻译的,为下一步汇编打下了基础。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用

(1)概念
任何一种用于 电子计算机 、 微处理器 、 微控制器 或其他可编程器件的低级语言,亦称为符号语言。 在汇编语言中,用 助记符 代替 机器指令 的 操作码 ,用地址符号或标号代替指令或 操作数 的地址。
(2)作用
可以更好地了解计算机的工作原理,主要用于一些对速度要求高,需更少存储容量的地方。
4.2 在Ubuntu下汇编的命令
gcc -c -o hello.o hello.s

4.3 可重定位目标elf格式

(1)ELF头
ELF头以一个16字节的序列开始,描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。

(2)节头部表

(3)重定位信息
重定位是将EFL文件中的未定义符号关联到有效值的处理过程。在hello.o中,对printf,exit等函数的未定义的引用和全局变量(sleepsecs)替换为该进程的虚拟地址空间中机器代码所在的地址。

(4)符号表
符号表(.symtab)是用来存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。

4.4 Hello.o的结果解析

(1)操作数
Hellor.txt

hello.s

在反汇编中,立即数变为16进制。

(2)跳转指令
反汇编代码跳转指令的操作数使用的不是段名称如.L2,段名称只是在汇编语言中便于编写的助记符,在汇编成机器语言之后使用地是确定的地址。
Hellor.txt

hello.s

(3)函数调用
在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前下一条指令。这是因为hello.c中调用的函数最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0,然后在.rela.text节中为其添加重定位条目,在链接后再进一步确定。
4.5 本章小结
本章介绍了hello从hello.s到hello.o的汇编过程,通过查看hello.o的ELF格式和使用objdump得到反汇编代码与hello.s进行比较的方式,间接了解到从汇编语言映射到机器语言汇编器需要实现的转换。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
(1)概念
链接是通过链接器将文件中调用的各种函数跟静态库及动态库链接,并将 它们打包合并形成目标文件,即可执行文件。
(2)作用
通过链接可以实现将头文件中引用的函数并入到程序中,解析未定义的符 号引用,将目标文件中的占位符替换为符号的地址。
5.2 在Ubuntu下链接的命令

反汇编

5.3 可执行目标文件hello的格式

5.4 hello的虚拟地址空间
用edb加载hello程序,我们可以在DataDump里看到hello的虚拟地址空间。
5.5 链接的重定位过程分析
在命令行输入:
objdump -d -r hello > hellor1.txt
(1)对比hellor.txt与hellor2.txt,hellor2中除了text部分,还多出了.init,.plt,.plt.sec三个部分。并且在text节中除了main多出了<_start>,<_dl_relocate_static_pie>,<__libc_csu_init>,<__libc_csu_fini>,<_fini>几个部分。
(2)分析重定位过程
A.合并相同的节(数据节和代码节)
B.对定义的符号进行重定位
确定定义的符号在虚拟空间的绝对地址,完成这一步以后,每个全局或局部变量都可确定地址
C.对引用符号进行重定位

5.6 hello的执行流程
(1)_start:0x4010f0
(2)_libc_start_main 0x2f12271d
(3)main 0x401125
(4)_printf 0x401040
(5)_exit 0x401070
(6)_sleep 0x401080
(7)_getchar 0x401050
5.7 Hello的动态链接分析
动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。如果一个目标模块调用定义在共享库中的任何函数,那么就有自己的GOT和PLT。
·PLT是一个数组,其中每个条目是16字节代码。每个库函数都有自己的PLT条目,PLT[0]是一个特殊的条目,跳转到动态链接器中。从PLT[2]开始的条目调用用户代码调用的函数。
·GOT也是一个数组,每个条目是8字节的地址,和PLT联合使用时,GOT[2]是动态链接在ld-linux.so模块的入口点,其余条目对应于被调用的函数,在运行时被解析。每个条目都有匹配的PLT条目。
·在函数调用时,首先跳转到PLT执行。plt中操作,第一次访问跳转时GOT地址为下一条指令,将函数序号入栈,然后跳转到PLT[0],之后将重定位表地址入栈,访问动态链接器,在动态链接器中使用在栈里保存的函数序号和重定位表计算函数运行时的地址,重写GOT,返回调用函数。之后如果还有对该函数的访问,就不用执行第二次跳转,直接参看GOT信息。
5.8 本章小结
本章分析了链接过程中对程序的处理。主要介绍了链接的概念与作用、hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
(1)概念
进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间。用户通过向shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程。
(2)作用
显示当前内存中运行的程序,服务等。通过进程你可以判断某个程序占用多少CPU和内存使用量,还可以通结束进程来结束无法关闭的程序。
6.2 简述壳Shell-bash的作用与处理流程
(1)作用
shell是一个应用程序,他在操作系统中提供了一个用户与系统内核进行交互的界面。它解释由用户输入的命令并且把它们送到内核。
(2)处理流程
a.从终端读入命令
b.将输入解读并获得所有的参数
c.如果是内置命令则立即执行
d.否则调用相应的程序为其分配子进程并运行
6.3 Hello的fork进程创建过程
Shell(父进程)通过fork函数创建一个新的运行的子进程。新的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork 时,子进程可以读写父进程中打开的任何文件。
6.4 Hello的execve过程
execve函数在新创建的子进程的上下文中加载并运行hello程序。execve函数的功能是加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。只有发生错误时execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次且从不返回。
execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行程序
具体步骤:
1)删除已存在的用户区域
2)映射私有区域。
3)映射共享区域。
4)设置程序计数器。

6.5 Hello的进程执行
1)上下文信息
进程上下文一般在进程切换中提到,进程控制块PCB,保存着进程的诸多详细信息,当进程要切换时当前进程的寄存器内容以及内存页表的详细信息等等内容,也就是关于描述进程的信息。
(3)进程时间片
时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片,即该进程允许运行的时间,使各个程序从表面上看是同时进行的。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。而不会造成CPU资源浪费。在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。
(4)进程调度的过程
无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。
这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。

(5)用户态与核心态转换
a. 系统调用 这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。系统调用实质上是一个中断,而汇编指令int 就可以实现用户态向内核态切换,iret实现内核态向用户态切换 b. 异常 当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
c. 外围设备的中断 当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。 这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
 2)具体的切换操作 从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,关于它们的具体区别这里不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析,涉及到由用户态切换到内核态的步骤主要包括:
a.从当前进程的描述符中提取其内核栈的ss0及esp0信息。
b.使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个 过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
c.将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始 执行中断处理程序,这时就转到了内核态的程序执行了。
6.6 hello的异常与信号处理
(1)异常情况
1.中断
2.陷阱
3.故障
4.终止
(2)处理方式
1.中断
中断是异步发生的,是来自处理器外部的 I/O 设备的信号的结果。硬件 中断不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。硬件 中断的异常处理程序常常称为中断处理程序。
2.陷阱
陷阱是有意的异常,是执行一条指令后的结果。就像中断处理程序一样, 陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内 核之间提供一个像过程一样的接口,叫做系统调用。
3.故障
故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时, 处理器将控制转移给故障处理程序。如果故障处理程序能够修正这个错误,它 就将控制返回给引起故障的指令,从而重新执行它,否则,处理程序返回到内 核中的 abort例程,abort例程会终止引起故障的应用程序。
4.终止
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如 DRAM 或者 SRAM 位被损坏时发生的奇偶错误。终止处理程序不会将控制 返回给应用程序。
(以下格式自行编排,编辑时删除)
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
本章介绍了壳的概念及运行过程,并且加深了对异常处理的认识。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
(1)逻辑地址
逻辑地址是指由程序产生的与段相关的偏移地址部分。逻辑地址由一个段(segment)和偏移量(offset)组成,偏移量指明了从段开始的地方到实际地址之间的距离,表示为 [段标识符:段内偏移量]。
(2)线性地址
线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
(3)虚拟地址
虚拟地址是程序保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为[段:偏移量]的形式,这里的段是指段选择器。
(4)物理地址
CPU通过地址总线的寻址,找到真实的物理内存对应地址。计算机主存被组织成由M个连续字节大小的内存组成的数组,每个字节都有一个唯一的地址,该地址被称为物理地址。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部分组成:段选择符和段内偏移量。
段选择符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。TI:0为GDT,1为LDT。Index指出选择描述符表中的哪个条目,RPL请求特权级。
最初8086处理器的寄存器是16位的,为了能够访问更多的地址空间但不改变寄存器和指令的位宽,所以引入段寄存器,8086共设计了20位宽的地址总线,通过将段寄存器左移4位加上偏移地址得到20位地址,这个地址就是逻辑地址。将内存分为不同的段,段有段寄存器对应,段寄存器有一个栈、一个代码、两个数据寄存器。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址 ------> 物理地址的整体过程如下:

(1)由逻辑地址得到的线性地址一共 32 位。前 10 位是页目录索引,中间 10 位是页表索引,最后 12 位是业内偏移量
(2)由 CR3 寄存器得到页目录基地址,再得到页目录项
(3)由页目录项得到页表基地址
(4)由页表基地址得到页表项,最后得到物理地址
7.4 TLB与四级页表支持下的VA到PA的变换
前提如下:
虚拟地址空间48位,物理地址空间52位,页表大小4KB,4级页表。TLB 4路16组相联。CR3指向第一级页表的起始位置(上下文一部分)。
为了消除每次 CPU 产生一个虚拟地址,MMU 就查阅一个PTE带来的时间开销,许多系统都在MMU中包括了一个关于 PTE 的小的缓存,称为翻译后被缓冲器(TLB),TLB的速度快于L1 cache。
TLB通过虚拟地址VPN部分进行索引,分为索引(TLBI)与标记(TLBT)两个部分。这样,MMU在读取PTE时会直接通过TLB,如果不命中再从内存中将PTE复制到TLB。同时,为了减少页表太大而造成的空间损失,可以使用层次结构的页表页压缩 页表大小。core i7使用的是四级页表。
在四级页表层次结构的地址翻译中,虚拟地址被划分为4个VPN和1个VPO。每个VPNi都是一个到第i级页表的索引,第j级页表中的每个PTE都指向第j+1级某个页表的基址,第四级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问四个PTE。
7.5 三级Cache支持下的物理内存访问
L1、L2、L3各级Cache的原理相同,只做L1 Cache的分析。L1 Cache是8路64组相连高速缓存。块大小64B。因为有64组,所以需要6 bit CI进行组寻址,共有8路,块大小为64B,所以需要6 bit CO表示数据偏移位置,因为VA共52 bit,所以CT共40 bit。
在上一步中已经获得了物理地址VA,使用CI进行组索引,每组8路,对8路的块分别匹配CT(前40位)。如果匹配成功且块的valid标志位为1,则命中(hit),根据数据偏移量CO(后六位)取出数据返回。
因为L1高速缓存是直接映射高速缓存,所以每一组只有一行。在寻找对应字节时,首先根据组索引确定要访问的组,然后看有效位是否为真,为0则缓存不命中。如果有效位为1,再看缓存中的标记位是否与物理地址给出的标记位匹配。
如果没有匹配成功或者匹配成功但是标志位是0,则不命中(miss),向下一级缓存中查询数据(L2 Cache->L3 Cache->主存)。查询到数据之后,一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生冲突(evict),则采用最近最少使用策略LFU进行替换。

7.6 hello进程fork时的内存映射
当fork函数被shell调用时,并分配给hello一个唯一的PID。为了给hello创建虚拟内存,fork创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在hello中返回时,hello现在的虚拟内存刚好和调用shell的虚拟内存相同。当这两个进程中的任何一个进行写操作时,写时复制机制会创建新页面。因此也就为每个进程保持了私有地址空间的概念。
7.7 hello进程execve时的内存映射
execve 函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运 行包含在可执行目标文件 hello 中的程序,用 hello 程序有效地替代了当前程序。 它可能会自动覆盖当前进程中的所有虚拟地址和空间,删除当前进程虚拟地址的所有用户虚拟和部分空间中的已存在的代码共享区域和结构,但它不会自动创建一个新的代码共享进程。
加载并运行 hello 需要以下几个步骤:
(1)删除当前进程虚拟地址中已存在的用户区域
(2)映射私有区域,为新程序的代码、数据、bss和栈创建新的区域结构。
(3)映射共享区域,将hello与libc.so动态链接,然后再映射到虚拟地址空间中的共享区域。
(4)设置当前进程上下文程序计数器(PC),使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的 物理地址不在内存中,因此必须从磁盘中取出时就会发生故障。
选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去, 换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页 的指令,这条指令再次发送VA到MMU,这次MMU就能正常翻译VA了。
7.9动态存储分配管理
动态存储分配管理由动态内存分配器完成。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。堆是一个请求二进制零的区域,它紧接在未初始化的数据区后开始,并向更高的地址。分配器将堆视为一组不同大小的块的集合来维护。
(1)隐式空闲链表
头部一共四个字节,前三个字节存储的是块的大小,最后一个字节存储的是当前这个块是空闲块还是已分配的块,0代表空闲块,1代表已分配的块。中间的有效载荷就是用于存放已分配的块中的信息用的。最后的填充部分是为了地址对齐等一些要求用的。
隐式链表的结构就是根据地址从小到大进行连接的,如图7-11所示。其中的每一个元素表示的是一个空闲块或者一个分配块,由于空闲块会合并的特性,链表中的元素的连接一定是空闲块的分配块交替连接的。
(2)显式空闲链表
显示结构在空闲块中增加了8个字节,分别保存当前空闲块的前驱空闲块的地址和后继空闲块的地址。显式的结构比隐式结构多维护了一个链表,就是空闲块的链表。这样做的好处就是在malloc的时候,隐式的方法是要遍历所有的块,包括空闲块了分配块。但是显式的结构只需要在空闲块中维护的链表检索就可以了,这样降低了在malloc时候的复杂度。
关于空闲块的维护方式一共有两种,一种是后进先出的方式,另一种是按照地址的方式。按照地址维护很好理解,与隐式的结构大致相同。后进先出的方式的思想是,当一个分配块被free之后,将这个块放到链表的最开头,这样在malloc的时候会首先看一下最后被free的块是否符合要求。这样的好处是释放一个块的时候比较高效,直接放在头部就可以。
7.10本章小结
本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以intel Core7在指定环境下介绍了虚拟地址到物理地址的变换、物理内存访问,还介绍了hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:特殊文件一般分为两种:
块特殊文件是一个能存储固定大小块信息的设备,它支持以固定大小的块,扇区或群集读取和(可选)写入数据。每个块都有自己的物理地址。通常块的大小在 512 - 65536 之间。所有传输的信息都会以连续的块为单位。块设备的基本特征是每个块都较为对立,能够独立的进行读写。常见的块设备有 硬盘、蓝光光盘、USB 盘与字符设备相比,块设备通常需要较少的引脚。
块特殊文件的缺点基于给定固态存储器的块设备比基于相同类型的存储器的字节寻址要慢一些,因为必须在块的开头开始读取或写入。所以,要读取该块的任何部分,必须寻找到该块的开始,读取整个块,如果不使用该块,则将其丢弃。要写入块的一部分,必须寻找到块的开始,将整个块读入内存,修改数据,再次寻找到块的开头处,然后将整个块写回设备。
另一类 I/O 设备是字符特殊文件。字符设备以字符为单位发送或接收一个字符流,而不考虑任何块结构。字符设备是不可寻址的,也没有任何寻道操作。常见的字符设备有打印机、网络设备、鼠标、以及大多数与磁盘不同的设备。
每个设备特殊文件都会和设备驱动相关联。每个驱动程序都通过一个主设备号来标识。如果一个驱动支持多个设备的话,此时会在主设备的后面新加一个次设备号来标识。主设备号和次设备号共同确定了唯一的驱动设备。
设备管理:Linux内核有一个简单、低级的接口,成为Unix I/O,是的所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
接口:不带缓存的I/O对文件描述符操作,带缓存的标准I/O是针对流的。
标准I/O对每个I/O流自动进行缓存管理(标准I/O函数通常调用malloc来分配缓存)。它提供了三种类型的缓存:
1) 全缓存。当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的。
2) 行缓存。当输入输出遇到新行符或缓存满时,才由标准I/O库执行实际I/O操作。stdin、stdout通常是行缓存的。
3) 无缓存。相当于read、write了。stderr通常是无缓存的,因为它必须尽快输出。
一般而言,由系统选择缓存的长度,并自动分配。标准I/O库在关闭流的时候自动释放缓存。另外,也可以使用函数fflush()将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync()将所有内核缓冲区的数据写到文件(磁盘)。
函数:
(1)int open(const char* path, int oflag, …/mode_t mode/);
int openat(int fd, const char* path, int oflag, …/mode_t mode/)
若文件打开失败返回-1,打开失败原因可以通过errno或者strerror(errno)查看;
若成功将返回最小的未用的文件描述符的值。
(2) int create(const char *path, mode_t mode);
若文件创建失败返回-1;
若创建成功返回当前创建文件的文件描述符。
(3)int lseek(int fd, off_t offset, int whence);
成功则返回新的文件的偏移量;
失败则返回-1.
(4)#include <unistd.h>

ssize_t read(int fd, void *buf, size_t nbytes);
若读取成功,读到文件末尾返回0,未读到文件末尾返回当前读的字节数。
若读取失败,返回-1。
(5)#include <unistd.h>

ssize_t write(int fd, const void* buf, size_t ntyes);
若写入成功则返回写入的字节数;失败返回-1.
8.3 printf的实现分析
vsprintf(buf, fmt, arg)函数。 
    
   
int vsprintf(char *buf, const char fmt, va_list args)
{
char
p;
char tmp[256];
va_list p_next_arg = args;

for (p=buf;*fmt;fmt++) { 
if (*fmt != '%') { 
*p++ = *fmt; 
continue; 
} 

fmt++; 

switch (*fmt) { 
case 'x': 
itoa(tmp, *((int*)p_next_arg)); 
strcpy(p, tmp); 
p_next_arg += 4; 
p += strlen(tmp); 
break; 
case 's': 
break; 
default: 
break; 
} 
} 

return (p - buf); 

}

8.4 getchar的实现分析
getchar函数调用read函数,将整个缓冲区都读到buf里,并将缓冲区的长度赋值给n。返回时返回buf的第一个元素,除非n<0。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章简述了Linux的I/O设备管理机制,Unix I/O接口及函数,并简要分析了printf函数和getchar函数的实现。
(第8章1分)

结论
总结hello所经历的过程:
1.编写hello.c代码。

2.预处理:预处理器扩展源代码插入所有用#include命令指定的文件,并扩展所有用#define声明指定的宏,合并为一个hello.i文件。

3.编译:编译器将hello.i编译成hello.s文件,包含汇编代码。

4.汇编:汇编器将hello.s可重定位为hello.o文件。

5.链接:链接器将hello.o与实现库函数的代码合并,产生最终的可执行代码文件。

6.带参数运行:在命令行输入:./hello 7203610417 张子庆 1

7.创建子进程:调用fork创建一个子进程。

8.运行:调用execve,execve调用启动加载器,加映射虚拟内存,进入hello程序入口后将程序载入物理内存,进入main函数执行hello。

9.执行指令:CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行相应的控制逻辑流。

10.访问内存:CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行相应的控制逻辑流。

11.申请内存:将虚拟内存地址通过页表映射成物理地址。

12.发出/接收信号:如果运行中键入Ctrl + C或Ctrl + Z,则调用shell的信号处理函数分别停止、挂起。

13.结束进程:exit,hello的父进程回收hello,内核也删除它的所有数据。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
hello.c 源程序
hello.i 预处理后文件
hello.s 编译后的汇编文件
hello.o 汇编后的可重定位目标执行文件
hello 链接后的可执行文件
hello.elf hello.o的ELF格式
hellor.txt hello.o的反汇编
hellor2.txt hello的反汇编代码
hello1.elf hello的ELF格式

(附件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.
(参考文献0分,缺失 -1分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值