csapp大作业

计算机系统
大作业

题 目 程序人生-Hello’s P2P
专 业 计算学部
学   号 12******6
班   级 *****
学 生 ***    
指 导 教 师 史先俊

计算机科学与技术学院
2022年5月
摘 要
本文主要对Hello的一生进行分析,即从Hello.c源程序的预处理-编译-汇编-链接到Hello的进程管理、存储管理、IO管理进行分析描述。在这一过程中,加深自己对一个程序运行原理的理解

关键词:Hello; 预处理;编译;汇编;链接;进程管理;存储管理;IO管理

目 录

第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简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P过程:
P2P过程是指从程序到进程的过程,这一过程分为以下阶段。
(1)cpp预处理hello.c文件将其转化为hello.i文件;
(2)ccl将hello.i转化为hello.s汇编文件;
(3)as将hello.s处理后,得到hello.o文件;
(4)链接器ld生成hello.o程序,最后得到hello文件。
(5)之后通过shell加载运行hello程序,并为其fork创建一个进程
020过程:
通过shell用execve加载可执行文件hello,映射虚拟内存,之后将程序载入物理内存,进入 main函数执行代码。程序结束后,hello进程被shell父进程回收,内核删除相关数据结构,再次回到初态。
1.2 环境与工具
1.2.1 硬件环境
X64 Intel® Core™ i5-10210U CPU;2.10GHz;8G RAM;256GHD Disk
1.2.2 软件环境
Windows10 64位;Vmware Workstation pro 15.5;Ubuntu-20.04.4;
1.2.3 开发工具
Objdump , Codeblocks , gdb,gcc,edb,Hexedit
1.3 中间结果
Hello.c程序源代码
Hello.i程序预处理后文本文件
Hello.s程序编译后的汇编文件
Hello.o程序汇编后的可重定位目标文件
Hello_elf.txt:ELF格式下的hello.o
Hello.asm hello的反汇编文件
Hello.o.asm hello.o的反汇编文件
Hello 链接后的可执行文件
1.4 本章小结
本章着重用计算机术语分析了hello程序的P2P和020过程,同时列出了开发环境,工具,为后续作业内容进行铺垫。

第2章 预处理
2.1 预处理的概念与作用
概念:
预处理是计算机对程序处理的第一步,通过预处理器对.c文件进行初步处理,根据以字符#开头的命令(宏定义、条件编译等),修改原始的C程序,并将其引用的所有库进行展开合并,生成以.i结尾的文本文件。后计算机会对.i文件进行下一步的处理。
作用:
1: 宏定义:在预处理阶段用定义的实际数值将宏替换
2:文件包含 :文件包含处理是指在一个源文件中,通过文件包含命令将另一个源文件的内容全部包含在此文件中。在源文件编译时,连同被包含进来的文件一同编译,生成目标目标文件。
3:条件编译:,根据#if后面的条件决定需要编译的代码

2.2在Ubuntu下预处理的命令
预处理命令:gcc hello.c -E -o hello.i
在这里插入图片描述
图2.1 预处理命令

2.3 Hello的预处理结果解析
打开hello.i文件可以看到hello.i文件内容行数为3000多行,与hello.c文件内容相比大大增加,这是因为.c头文件中的内容被复制到了.i文件中,同时注释也被删去,#define也被替换成了字符串与参数
在这里插入图片描述
图2.2 hello.c文件
在这里插入图片描述
图2.3 hello.i文件

2.4 本章小结
本章着重对预处理的概念与作用进行描述,并用预处理指令gcc hello.c -E -o hello.i 对hello.c文件进行预处理,并对预处理结果也就是hello.i文件进行分析。
第3章 编译
3.1 编译的概念与作用
概念:
编译器将hello.i翻译为为hello.s,它包含一个汇编语言程序。其以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。这个过程称为编译
作用:
1.扫描(词法分析),2.语法分析,3.语义分析,4.源代码优化(中间语言生成),5.代码生成,目标代码优化。

3.2 在Ubuntu下编译的命令
编译指令:gcc -S -o hello.s hello.i
在这里插入图片描述
图3.1 编译命令

3.3 Hello的编译结果解析
3.3.1 汇编指令介绍
.file 声明源文件
.text 以下是代码段
.data 以下是数据段
.globl 声明一个全局变量
.align 数据或者指令的地址对齐方式
.type 声明一个符号是数据类型还是函数类型
.long 声明一个long类型
.string 声明一个字符串
.section.rodata 以下是rodata节
3.3.2 数据
(1)字符串
可以看出有两个字符串,且都存在于只读数据段
在这里插入图片描述
图3.2 字符串

(2)局部变量
main函数声明了一个局部变量i,编译器进行编译的时候将局部变量i放在堆栈中。如图,局部变量i被放在-4(%rbp)的位置上。
在这里插入图片描述
图3.3 局部变量

(3)参数argc
参数argc作为用户传给main的参数也被放到了堆栈中。
在这里插入图片描述
图3.4 参数argc

(4)数组
数组作为传入的参数,储存在栈上,数组的起始地址存放在栈中-32(%rbp)的位置,之后就能通过偏移argv+8和argv+16找到argv[1]和argv[2]的地址,argv[1]和argv[2]都被调用给了printf函数。
在这里插入图片描述
图3.5 数组

(5)立即数
立即数可以直接在汇编代码中看出
在这里插入图片描述
图3.6 立即数

3.3.3 赋值
赋值操作利用mov语句。
全局或静态变量变量不赋初值,会存放在.bss段;已初始化的全局变量,会存放在.data段。局部变量如果不赋初值,在汇编代码里没有体现,只在用到并赋初值时才用寄存器等来存储;本文件中只有一个赋值操作 i=0
在这里插入图片描述
图3.7 赋值
3.3.4 类型转换
源文件中存在一个类型转换,通过atoi函数将字符串转为整型
在这里插入图片描述
图3.8 类型转换
3.3.5算术操作
源文件中的算术操作有i++还有对argv[]参数地址的累加,这两种操作通过指令add实现,对于局部变量i:通过addl$1, -4(%rbp)来对i进行累加对于main函数参数argv[]:通过addq$8, %rax,addq$16, %rax和addq$24, %rax来获得argv[1],argv[2]和argv[3]的地址。
在这里插入图片描述
图3.9 算术操作
3.3.6 关系操作

  1. argc!=4;是在一条件语句中的条件判断:argc!=4,进行编译时,这条指令被编译为:cmpl $4,-20(%rbp),同时这条cmpl的指令还有设置条件码的作用,当根据条件码来判断是否需要跳转到分支中。
  2. i<8,在hello.c作为判断循环条件,在汇编代码被编译为:cmpl $7,-4(%rbp),计算 i-7然后设置 条件码,为下一步 jle 利用条件码进行跳转做准备。
    在这里插入图片描述
    图3.10 关系操作
    3.3.7 控制转移
    在汇编语言中控制转移通过cmp再加上jXX来实现,cmp指令来改变标志位,然后再用JXX根据标志位来决定是否跳转
    3.3.8 函数操作
    利用call指令来调用函数,将函数需要的参数存放在寄存器中或栈中进行参数传递,函数调用结束之后,返回值保存在寄存器%rax中。
    Main函数:参数传递,%edi存储着argc的值(参数变量的个数),为第一个参数%rsi存储argv的值,作为地址,指向参数字符串数组的首地址为第二个参数;函数调用,主函数,第一个执行的函数;函数返回,返回0
    Printf函数:参数传递,%rdi中存储传递的第一个参数,如果要输出字符串,字符串在开头.LC0和.LC1处,将字符串的地址赋值给%rdi作为第一个参数,在第二个printf中有三个参数,另外两个参数通过%rax赋值给%rsi和%rdx;函数调用使用call指令调用;函数返回,将返回值存储在%rax中,使用指令ret返回
    Exit函数:参数传递,将1放在%edi中作为参数传递;函数调用,使用call指令进行调用;函数返回,无返回
    Sleep函数:参数传递,将sleepsecs的值作为参数,将%eax赋值给%edi,%edi中的值作为第一个参数;函数调用,使用call指令进行调用;函数返回将返回值存储在%eax,使用ret指令返回。

3.4 本章小结
本章介绍了编译的过程。并对编译结果中的数据类型和各类操作的处理进行阐述说明,帮助我们更好的理解编译过程,为后续作业进行铺垫。
第4章 汇编
4.1 汇编的概念与作用
概念:通过汇编器ad对.s文件进行处理,把汇编语言转化为机器语言并生成.o的可重定位目标文件的过程。
作用:通过汇编过程把汇编代码转换成计算机能直接识别的机器代码

4.2 在Ubuntu下汇编的命令
汇编命令:gcc -c -o hello.o hello.s
在这里插入图片描述
图4.1 汇编命令
4.3 可重定位目标elf格式
4.3.1利用readelf命令来查看ELF,并将其重定位为文本文件
在这里插入图片描述
图4.2 查看elf

ELF格式:以一个16字节序列开始,该序列描述了生成该文件的系统的字的大小和字节顺序,其余信息帮助链接器语法分析和解释目标文件信息
在这里插入图片描述

图4.3 ELF表

节头部表:记录了各节名称、类型、地址、偏移量、大小、全体大小、旗标、连接、信息等
在这里插入图片描述
图4.4 节头部表
重定位节,描述了.text节中需要重定位的信息,即当链接器链接该目标文件时需要修改的信息
在这里插入图片描述
图 4.5 重定位节
符号表:程序中定义和引用函数和全局变量的信息,声明重定位需要引用的符号
在这里插入图片描述
图4.6 符号表

4.4 Hello.o的结果解析

objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
在这里插入图片描述
图4.7 反汇编
在这里插入图片描述
图4.8 hello.s

相同点:汇编语言指令基本相同
不同点:
1.操作数:Hello.s用十进制,而反汇编用十六进制
2.分支转移:跳转时,hello.s是直接利用段的名字进行跳转,而在反汇编中则使用相对寻址来进行跳转
3.函数调用:Hello.o通过函数名调用函数,反汇编则通过相对寻址调用函数,而且call指令后的相对地址设置为0
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章主要介绍了程序的汇编过程,分析了一个可重定位目标文件中的内容,包括ELF头,节头部表,符号表等,并对比了汇编与反汇编代码的异同。通过这一章的内容的总结学习能够帮助我们理解链接的过程。
第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行与编译时,也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。
作用:使得分离编译成为可能。
时间上,可分开编译:只需要重新编译修改的源程序文件,然后重新链接;
空间上,无需包含共享库所有代码:源文件中无需包含共享库函数的源码,只要直接调用即可(如,只要直接调用printf() 函数,无需包含其源码),另外,可执行文件和运行时的内存中只需包含所调用函数的代码,而不需要包含整个共享库。
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.1 链接命令

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

  1. ELF Header 指令:readelf -h hello:ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息
    在这里插入图片描述
    图5.2 ELF header

2.Section Headers 指令:readelf -S hello:
在这里插入图片描述
图5.3 Section header

3.symtab 指令:readelf -s hello:
在这里插入图片描述
图5.4 Symbol table

4.rela.text 指令:readelf -r hello:
在这里插入图片描述
图5.5 relocation section

5.4 hello的虚拟地址空间
1.用edb打开hello,可以看出hello的虚拟地址空间开始于0x400000
在这里插入图片描述
图5.6 edb打开hello

  1. 由elf文件可看出.inerp偏移量为0x2e0,在edb对应位置找到:
    在这里插入图片描述
    图5.7 查看.inerp偏移量

3.Rodata段:虚拟起始地址为40200,与printf函数中的只读内容相对应。
在这里插入图片描述
图5.8 查看rodata段
5.5 链接的重定位过程分析
首先生成两个反汇编文件hello.asm,hello.o.asm
在这里插入图片描述
图5.9,5.10 hello和hello.o的反汇编文件
两个文件进行对比,可以分析出二者的不同之处
1.可以看出hello反汇编的代码中多了很多的节以及很多函数的汇编代码,这是因为链接的时候将头文件链接到了可执行文件中
在这里插入图片描述
图5.11 hello反汇编文件

3.hello.o反汇编代码中代码的虚拟地址均为0,未完成可重定位的过程,hello反汇编的代码有确定的虚拟地址,已经完成了重定位。
在这里插入图片描述
图5.12 hello反汇编文件

5.6 hello的执行流程
程序名
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
libc-2.27.so!__cxa_atexit
hello!__libc_csu_init
hello!printf@plt
hello!sleep@plt
hello!getchar@plt
libc-2.27.so!exit

5.7 Hello的动态链接分析
如图,init运行前puts 的GOT地址为空,运行后global_offset表产生了变化, 说明dl_init函数能根据当前执行的内存地址偏移量给程序赋值在这里插入图片描述图5.13 调用init函数前
在这里插入图片描述图5.14 调用init函数后

5.8 本章小结
本章主要介绍了链接的概念与作用,分析了链接命令,对链接的细节有了更进一步的了解,同时通过hello的反汇编文件与hello.o反汇编文件的对比,对重定位过程有了更深刻的理解;并在最后得到一个可执行目标文件hello。

第6章 hello进程管理
6.1 进程的概念与作用
概念:
进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
作用:
进程通过切换上下文实现多组程序能够独占cpu,使我们得到一种我们的程序是当前唯一运行的程序的假象,同时也使得计算机效率更高。
6.2 简述壳Shell-bash的作用与处理流程
作用:
Shell相当于命令解释器,用户向shell提出请求,shell解释并将请求传给内核。实现用户和系统交互
处理流程:
1.打印提示信息
2.等待用户输入
3.读入输入的命令
4.解释命令
5. 若为内置命令则执行, 否则调用相应的程序为其分配子进程执行
6. 执行完成,返回第一步
6.3 Hello的fork进程创建过程
首先判断hello不是一个内置的shell指令,然后调用应用程序,找到可执行文件hello,准备执行。然后shell通过调用fork函数创建子进程,子进程得到一个完全独立的副本,父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的 逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成
6.4 Hello的execve过程
得到子进程后,子进程调用execve函数在当前进程的上下文加载运行hello程序.加载器执行以下操作,删除子进程现有的虚拟内存段,创建新的代码、数据、堆和栈段。新的代码和数据段被初始化为可执行文件中的内容。堆和栈被置空。然后加载器将PC指向hello程序的起始位置,这样就完成了execve的过程
6.5 Hello的进程执行
进程上下文信息,就是内核重新启动一个被抢占的程序所需的状态,它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈、和各种内核数据结构。

进程时间片,是指一个进程和执行它的控制流的一部分的每一时间段。用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这个决策就叫做调度。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用上文所述的上下文切换的机制将控制转移到新的进程。内核代表的用户执行系统调用时,可能会发生上下文切换;中断也有可能引发上下文切换。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
异常种类:
类别 原因 异步 / 同步 返回行为
中断 来自I/O设备信号 异步 总是返回到下一条指令
陷进 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复错误 同步 可能返回到当前指令
终止 不可恢复错误 同步 不返回
(1)正常结果 循环打印输出学号姓名八次,之后按回车,进程结束
在这里插入图片描述
图6.1 正常执行结果

(2)不停乱按
不停的乱按不会影响程序的输出,这是因为程序在执行时会阻塞外来操作产生的信号,执行完之后读到回车时,程序结束。
在这里插入图片描述
图6.2 乱按输出结果

(3)Ctrl-Z
程序中断,因为输入ctrl-z会发送SIGSTP信号到父进程,之后会运行信号处理程序,结果是挂起前台的作业
在这里插入图片描述
图6.3 Ctrl-Z输出结果

(4)Ctrl-C
程序中断,因为输入Ctrl+C会使得内核发送一个SIGINT信号到前台进程组的每个进程,终止前台作业
在这里插入图片描述
图6.4 Ctrl-C输出结果

(5)CTRL+z+ps
打印各进程的PID,可以看到挂起的进程
在这里插入图片描述
图6.5 CTRL+z+ps输出结果

(6)CTRL+z+jobs
打印被挂起的进程组的JID
在这里插入图片描述
图6.6 CTRL+z+jobs输出结果

(7)CTRL+z+pstree
打印所有进程的关系
在这里插入图片描述
图6.7 CTRL+z+pstree 输出结果

(8)CTRL+z+fg
把挂在后台的程序调到前台继续执行
在这里插入图片描述
图6.8 CTRL+z+fg输出结果

(9)CTRL+z+kill
杀死进程
在这里插入图片描述
图6.9 CTRL+z+kill输出结果

6.7本章小结
本章主要介绍了进程的定义和作用,Shell的处理流程,如何调用fork创建子进程,
如何调用execve函数执行可执行程序以及hello进程执行时的异常处理情况,并对这些异常情况进行实际操作,加深了对异常和信号处理的理解

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:是指由程序产生的和段相关的偏移地址(出现再机器语言中).由一个标识符加上一个指定段内的相对地址的偏移量,是cpu执行程序中的一种中间地址.hello.o反汇编的输出文件中的地址就是逻辑地址.

线性地址:逻辑地址到物理地址变换之间的中间层.逻辑地址加上相应段的基地址就生成了线性地址

虚拟地址:虚拟地址是一个抽象的概念空间,每一个虚拟地址对应与一个虚拟页,每一个虚拟页会映射一个磁盘空间的一页,如果要使用该数据,则会将该页载入内存,这样每个虚拟地址就对应与唯一的一个物理地址.

物理地址:指出目前CPU外部地址总线上的寻址物理内存的地址信号,用于内存级芯片的单元寻址.可以将内存看成一个从0字节开始的大数组,数组中每个字节拥有独有的物理地址.
7.2 Intel逻辑地址到线性地址的变换-段式管理
TI=0则转换的是GDT中的段,TI=1则转换的是LDT中的段。再根据相应寄存器,得到其地址和大小。之后选取段选择符的前13位乘8再加上根据TI所选的首地址,之后根据得到的结果就能在描述符表中找到段描述符。再将其中的32位基地址和32位段内偏移量相加,就能得到32位线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。VM系统将虚拟内存分割,称为虚拟页,类似地,物理内存也被分割成物理页。利用页表来管理虚拟页,页表就是一个页表条目(PTE)的数组,每个PTE由一个有效位和一个n位地址字段组成,有效位表明了该虚拟页当前是否被缓存在DRAM中,如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始位置。如果发生缺页,则从磁盘读取。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB是MMU中对与PTE的缓存,采用组相联的方式;
TLB由TLB标记,PTE虚拟地址被划分位:a1位 a2位 a3位 a4位
MMU先在TLB中寻找PTE;根据虚拟地址的后p+t-1位值作为组号,找到其对应的TLB的组,VPN剩下的位作为标记,与该组中的标记进行对比,如果存在标记相同切有效为为1的pte那么获得pte中的ppn如果不存在,那么将vpn发送给内存和cache。通过一个固定寄存器中的值获得以及页表的基地址,由a1位值作为索引,知道对应表中的值,将其作为二级页表的机制,由a2位值作为索引,直到有a4作为索引获得四级页表的pte的值,这样就可以得到vpn所对应的ppn的值,再由ppn+vpo就可以得到va对应的pa
7.5 三级Cache支持下的物理内存访问
通过内存地址的组索引获得值,如果对应的值是data则像L1 d-cache对应组中查找,如果是指令,则向L1 i-cache对应组中查找。将L1对应组中的每一行的标记位进行对比,如果相同并且有效位为1则命中,获得偏移量,取出相应字节,否则不命中,向下一级cache寻找,直到向内存中寻找
7.6 hello进程fork时的内存映射
调用fork函数时,会为hello创建一个子进程,同时会创建各种数据结构并分配给hello唯一的PID。为了给hello创建虚拟内存,内核创建了当前进程的mm_struct、vm_area_struct和页表的原样副本并将两个进程中的每个页面都标记为只读,将两个进程中的每个区域结构都标记为写时复制。在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存随后的写操作通过写时复制机制创建新页面
7.7 hello进程execve时的内存映射
execve函数在当前代码共享进程的上下文中加载并自动运行一个新的代码共享程序,它可能会自动覆盖当前进程的所有虚拟地址和空间,删除当前进程虚拟地址的所有用户虚拟和部分空间中的已存在的代码共享区域和结构,但是它并没有自动创建一个新的代码共享进程。新的运行程序仍然在堆栈中拥有相同的区域pid。之后为新运行程序的用户共享代码、数据、bss和所有堆栈的区域结构创建新的共享区域和结构,这一步叫通过链接映射到新的私有代码共享区域,所有这些新的代码共享区域都可能是在运行时私有的、写时复制的。
它首先映射到一个共享的区域,hello这个程序与当前共享的对象libc.so链接,它可能是首先动态通过链接映射到这个代码共享程序上下文中的,然后再通过映射链接到用户虚拟地址和部分空间区域中的另一个共享代码区域内。为了设置一个新的程序计数器,execve函数要做的最后一件要做的事情就是自动设置当前代码共享进程上下文的一个程序计数器,使之成为指向所有代码共享区域的一个入口点。
7.8 缺页故障与缺页中断处理
DRAM 缓存不命中称为缺页,即虚拟内存中的字不在物理内存中. 缺页导致页面出错,产生缺页异常. 缺页异常处理程序选择一个牺牲页,从其他存储器中读入这个页替换牺牲页,然后将目标页加载到物理内存中. 最后让导致缺页的指令重新启动,页面命中
7.9动态存储分配管理
程序运行时,程序员使用动态内存分配器获取虚拟内存. 动态内存分配器维护进程的虚拟内存区域,称为堆. 分配器将堆维护为不同大小的块的集合,并且每个块都被分配或空闲. 分配器的类型包括显式和隐式分配器. 前者要求应用程序显式释放所有分配的块,而后者则在检测到程序不再使用分配的块时释放该块.
动态内存管理的策略包括首次适配、下次适配和最佳适配. 首次适配将从头开始搜索空闲列表,然后选择第一个空闲块. 搜索时间与块总数线性相关. 小空闲块的“碎片”保留在链接列表的开头附近. 下次适配与首次适配类似,不同之处在于它从链接列表中上一个查询的结束点开始. 比首次适配更快,避免了重复扫描那些无用块. 最佳适配将查询链接列表,选择最佳空闲块,满足拟合要求,并保留最少的可用空间.提高内存利用率
7.10本章小结
本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,虚拟地址到物理地址的变换、物理内存访问,以及hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。通过分析这些内容可以写出对cache友好的代码同时使我对一些cache优化解决手段有了一定认识,今后在编写程序时可以运用这些知识使程序更快运行
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
文件就是一个字节序列,所有的I/O设备都被模型化为文件,所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix I/O接口:
打开文件。
一个应用程序通过要求内核打开相应的文件,来宣告它想要访间一个I/O 设备.内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件.内核记录有关这个打开文件的所有信息.应用程序只需记住这个描述符.
描述符。
Linux shell 创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0) 、标准输出(描述符为1) 和标准错误(描述符为2) .头文件< unistd.h> 定义了常量STDIN_FILENO 、STOOUT_FILENO 和STDERR_FILENO, 它们可用来代替显式的描述符值.
改变当前的文件位置。
对于每个打开的文件,内核保持着一个文件位置k, 初始为0.这个文件位置是从文件开头起始的字节偏移量.应用程序能够通过执行seek 操作,显式地设置文件的当前位置为K .
读写文件。
一个读操作就是从文件复制n>0 个字节到内存,从当前文件位置k 开始,然后将k增加到k+n .给定一个大小为m 字节的文件,当k~m 时执行读操作会触发一个称为end-of-file(EOF) 的条件,应用程序能检测到这个条件.在文件结尾处并没有明确的“EOF 符号” .类似地,写操作就是从内存复制n>0 个字节到一个文件,从当前文件位置k开始,然后更新k .
关闭文件。
当应用完成了对文件的访问之后,它就通知内核关闭这个文件.作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中.无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源.
函数:
1,open()函数
功能描述:用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。
函数原型:int open(const char *pathname,int flags,int perms)
参数:pathname:被打开的文件名(可包括路径名如"dev/ttyS0")flags:文件打开方式,
返回值:成功:返回文件描述符;失败:返回-1

2,close()函数
功能描述:用于关闭一个被打开的的文件
所需头文件: #include
函数原型:int close(int fd)
参数:fd文件描述符
函数返回值:0成功,-1出错

3,read()函数
功能描述: 从文件读取数据。
所需头文件: #include
函数原型:ssize_t read(int fd, void *buf, size_t count);
参数:fd:将要读取数据的文件描述词。buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。count: 表示调用一次read操作,应该读多少数量的字符。
返回值:返回所读取的字节数;0(读到EOF);-1(出错)。

4,write()函数
功能描述: 向文件写入数据。
所需头文件: #include
函数原型:ssize_t write(int fd, void *buf, size_t count);
返回值:写入文件的字节数(成功);-1(出错)

5,lseek()函数
功能描述: 用于在指定的文件描述符中将将文件指针定位到相应位置。
所需头文件:#include ,#include
函数原型:off_t lseek(int fd, off_t offset,int whence);
参数:fd;文件描述符。offset:偏移量,每一个读写操作所需要移动的距离,单位是字节,可正可负(向前移,向后移)
返回值:成功:返回当前位移;失败:返回-1。
8.3 printf的实现分析
vsprintf函数:
在这里插入图片描述图8.1 vsprintf函数

vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
Write函数
在这里插入图片描述
图8.2 write函数

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

Syscall实现
在这里插入图片描述
图8.3 Syscall实现

syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
Getchar函数代码:
在这里插入图片描述
图8.4 getchar代码

getchar 由宏实现:#define getchar() getc(stdin)。getchar有一个int型的返回值.当程序调用getchar时.程序就等着用户按键.用户输入的字符被存放在键盘缓冲区中.直到用户按回车为止(回车字符也放在缓冲区中).当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符.getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾则返回-1(EOF),且将用户输入的字符回显到屏幕。如果用户在按下回车键之前,输入了不止一个字符,其他字符会保存在键盘缓冲区中,等待后续getchar()调用读取。

也就是说后续的getchar()函数调用不会在等待用户按键了,而是直接读取缓冲区中的字符,知道缓冲区中的字符完全读完后,getchar()函数的调用才又会等待用户按键。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux下IO设备的管理方法, Unix IO接口及其函数, 分析了printf函
数和getchar两个函数.
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1、编写代码:用c语言写.c文件

2、预处理:从.c生成.i文件

3、编译:由.i生成.s汇编文件

4、汇编:将.s文件翻译为机器语言指令,并打包成可重定位目标程序hello.o

5、链接:将.o可重定位目标文件和动态链接库链接成可执行目标程序hello

6、创建子进程:shell调用fork为程序创建子进程

7、加载:shell调用execve函数,将hello程序加载到该子进程,映射虚拟内存

8、执行指令:CPU为进程分配时间片,加载器将计数器预置在程序入口点,则hello可以顺序执行自己的逻辑控制流

9、信号:shell的信号处理函数可以接受程序的异常和用户的请求

10、终止:执行完成后父进程回收子进程,内核删除为该进程创建的数据结构

感悟:
一个看似简单的hello.c的源程序实现的背后居然需要这么多复杂的步骤,蕴含着如此多的计算机原理和知识,真可谓一花一世界,一叶一菩提。想必在今后的计算机学习道路上,还有许多东西等待我去探索学习,尽管这过程很是艰辛,但自己既然选择了这条路,就要无畏的走下去,希望自己在将来能在计算机这条路上有所得,有所成。最后感谢老师和助教在这一阶段的课程学习中为我们传道授业解惑!
附件
Hello.c程序源代码
Hello.i程序预处理后文本文件
Hello.s程序编译后的汇编文件
Hello.o程序汇编后的可重定位目标文件
hello_elf.txt:ELF格式下的hello.o
hello.asm hello的反汇编文件
hello.o.asm hello.o的反汇编文件
Hello 程序链接后的可执行文件

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E. Bryant, David R. O’Hallaon. 深入理解计算机系统. 第三版. 北京市:机械工业出版社[M]. 2018: 1-737
[2]https://www.csdn.net/
[3]https://www.baidu.com/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值