计算机系统大作业 Hello的一生:

计算机系统

大作业

题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学   号 1170300606
班   级 1703006
学 生 郭开宇    
指 导 教 师 吴锐

计算机科学与技术学院
2018年12月
摘 要
在学习了计算机系统这本书后,我们又回到了程序猿的老朋友身边,hello.c程序,我们结合知识并借助相关工具研究hello文件的一生(即P2P与020的过程),并了解系统在处理hello时发生的编译、链接、加载、进程管理、存储管理等过程,以及它们的运行机制。

关键词:预处理,编译,汇编,链接,进程,存储,虚拟地址,IO,文件。。。

(摘要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的预处理结果解析 - 6 -
2.4 本章小结 - 6 -
第3章 编译 - 7 -
3.1 编译的概念与作用 - 7 -
3.2 在UBUNTU下编译的命令 - 7 -
3.3 HELLO的编译结果解析 - 7 -
3.4 本章小结 - 11 -
第4章 汇编 - 12 -
4.1 汇编的概念与作用 - 12 -
4.2 在UBUNTU下汇编的命令 - 12 -
4.3 可重定位目标ELF格式 - 12 -
4.4 HELLO.O的结果解析 - 13 -
4.5 本章小结 - 14 -
第5章 链接 - 17 -
5.1 链接的概念与作用 - 17 -
5.2 在UBUNTU下链接的命令 - 17 -
5.3 可执行目标文件HELLO的格式 - 17 -
5.4 HELLO的虚拟地址空间 - 18 -
5.5 链接的重定位过程分析 - 19 -
5.6 HELLO的执行流程 - 20 -
5.7 HELLO的动态链接分析 - 21 -
5.8 本章小结 - 22 -
第6章 HELLO进程管理 - 23 -
6.1 进程的概念与作用 - 23 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 23 -
6.3 HELLO的FORK进程创建过程 - 23 -
6.4 HELLO的EXECVE过程 - 24 -
6.5 HELLO的进程执行 - 24 -
6.6 HELLO的异常与信号处理 - 25 -
6.7本章小结 - 27 -
第7章 HELLO的存储管理 - 28 -
7.1 HELLO的存储器地址空间 - 28 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 28 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 29 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 29 -
7.5 三级CACHE支持下的物理内存访问 - 29 -
7.6 HELLO进程FORK时的内存映射 - 29 -
7.7 HELLO进程EXECVE时的内存映射 - 30 -
7.8 缺页故障与缺页中断处理 - 30 -
7.9动态存储分配管理 - 30 -
7.10本章小结 - 31 -
第8章 HELLO的IO管理 - 33 -
8.1 LINUX的IO设备管理方法 - 33 -
8.2 简述UNIX IO接口及其函数 - 33 -
8.3 PRINTF的实现分析 - 34 -
8.4 GETCHAR的实现分析 - 34 -
8.5本章小结 - 36 -
结论 - 37 -
附件 - 38 -
参考文献 - 39 -

第1章 概述
1.1 Hello简介
P2P:Hello.c在Linux中经过预处理,编译,以及会变之后,成为可执行文件,在命令行启动之后,shell对其fork,于是,Hello从一个P(program)变为了另一个P(Process)。
020:程序启动后,shell为其execve,映射虚拟内存,进入程序入口后程序开始载入物理内存,程序开始执行,CPU为运行的hello分配时间片执行逻辑控制流,结束后,进程被回收,并删除相关数据
1.2 环境与工具
Windos 10家庭中文版
Intel® Core™i5-7200U @2.50GHz 2.70GHz
64位操作系统
Vmware
Ubuntu 16.04
vim,gcc,as,ld,edb,readelf,HexEdit,EDB,GDB
1.3 中间结果
hello.i 预处理产生的文本文件
hello.s 编译产生的汇编文件
hello.o 汇编产生的可重定位目标执行(二进制)
Hello 链接之后的可执行目标文件
helloo.objdmp Hello.o的反汇编代码
helloo.elf Hello.o的ELF格式
hello.objdmp Hello的反汇编代码
hello.elf Hello的ELF格式
1.4 本章小结
:简单介绍了P2P与020的基本意义,并要求给出实验的环境与工具,并要求写出中间文件名,方便梳理脉络。

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用

1,所谓预处理是指进行编译的第一遍扫描之前所做的工作,是C语言中的一个重要功能,由预处理程序负责完成。
2,C语言提供了多种预处理功能,如宏定义,文件包含,条件编译等
3,在C语言源程序中允许一个标识符来表示一个字符串,称为“宏”;被定义为“宏”的标识符称为”宏名”。在编译程序中,对程序所有出现的“宏名”,都用宏定义中的字符取代换,又被称为“宏替代”或“宏代换”。
4,宏定义是有源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。
5,在C语言中,宏分为有参数和无参数两种。

功能:
(1):将文件中#include<>的文件复制粘到文件的前面。
(2):替换#define
(3):根据#if后的条件确定待编译的代码
(4):删除注释信息
(5):添加文件名标识和行号信息
(6):保留所有的#pragma编译指令
2.2在Ubuntu下预处理的命令
命令:cpp hello.c > hello.i或gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

在默认的环境下找到stdio.h unistd.h stdlib.h,将其代码复制到截图所示的主函数之前,替换#define并且检查新添加代码中有无新的#define,递归的完成所有的声明,观察打开后的代码发现其中使用了大量的#ifdef #ifndef的语句,cpp会对条件值进行判断来决定是否执行包含其中的逻辑。
2.4 本章小结
解释了预处理的定义与作用,并探讨了预处理的过程。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用

将预处理之后得到的hello.i文件进行词法分析,语法分析,语义分析并优化生成汇编代码的过程(hello.s)
用来变异处理的程序叫做编译程序(编译器)
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
gcc -S hello.c -o hello.s .c文件

3.3 Hello的编译结果解析
3.3.1
汇编命令简介:
.file 声明源文件
.text 以下是代码段
.section .rodata 以下是rodata节
.globl 声明一个全局变量
.type 用来指定是函数类型或是对象类型
.size 声明大小
.long、.string 声明一个long、string类型
.align 声明对指令或者数据的存放地址进行对齐的方式
3.3.2数据:
在程序中使用了包括字符串,整型,还有数组的数据类型。
(一):字符串

  1. printf(“Usage: Hello 学号 姓名!\n”);中文字被编码为UTF-8的形式
  2. printf(“Hello %s %s\n”,argv[1],argv[2]);
    在这两条条语句中使用字符串
    声明:

(二):整型
int sleepsecs=2.5;作为已被赋值的全局变量,sleepsecs被存放在.data(.data节存放已经初始化的全局和静态C变量)节中,而不是.bss。其具体声明代码如下:

首先声明变量为全局变量,再声明其type为对象,size为4并赋值为三,
(Linux编译器下long与int长度相同都为4),并将其赋值为3. (我是不知道为什么在程序中要把一个int型赋值2.5。)
int i;
局部变量

int argc
作为参数传入。
(三)数组:
char *argv[]
作为参数传入,其中的每一个元素的大小为双字,数组的首地址为argv,函数访问了[1]和[2]相应的值,即采用地址操作访问。

3.3.3:赋值

  1. int sleepsecs=2.5:因为sleepsecs是全局变量,所以直接在.data节中将sleepsecs声明为值3的long类型数据。

  2. int i;大小为单字所以使用movl的指令为其赋值。

3.3.4 类型转换:?
(我的天,不是说好了向零舍入的吗?怎末还四舍五入了?)
(怎么回事?我的电脑又不走寻常路了?)
涉及int sleepsecs=2.5,将2.5转换为整型数据。
2.5默认为是double类型,
根据优化的选择,在进行数据类型转换时采用四舍五入的办法进行转换。
3.3.5 算术操作(加减乘除):

  1. i++,对计数器i自增,使用程序指令addl,后缀l代表操作数是一个4B大小的数据。

  2. 采用地址加减的操作访问数组中的数据,因为地址的数据大小为32,所以每次改变地址四个字节来寻找相应值

3.3.6 关系操作(比较):
argc!=3:判断argc不等于3。hello.s中使用cmpl $3,-20(%rbp),计算argc-3然后设置条件码,为下一步je利用条件码进行跳转作准备。

i<10:判断i小于10。hello.s中使用cmpl $9,-4(%rbp),计算i-9然后设置条件码,为下一步jle利用条件码进行跳转做准备。

3.3.7 跳转:
判断argc是否等于3, 首先cmpl比较argv和3,设置条件码,使用je判断ZF标志位,如果为0,说明argv-3=0 argv==3,则不执行if中的代码直接跳转到.L2,否则继续执行下一条指令。

判断i是否小于十;首先无条件跳转至循环判断语句,判断i是否小于10,如果小于10,则跳转至循环的内容,结束后又进行上述命令,知道不满足条件,直接执行下一条语句。

3.3.8函数操作:
main函数:
传递控制,main函数因为被调用call才能执行,call指令将下一条指令的地址dest压栈,然后跳转到main函数。
传递数据,外部调用过程向main函数传递参数argc和argv,分别使用%rdi和%rsi存储,函数正常出口为return 0,将%eax设置0返回。
分配和释放内存,使用%rbp记录栈帧,函数分配栈帧空间在%rbp之上,程序结束时,调用leave指令,leave相当于mov %rbp,%rsp,pop %rbp,恢复栈空间为调用之前的状态,然后ret返回,ret相当pop IP,将下一条要执行指令的地址设置为dest。
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。
exit函数:
传递数据:将$1传递给%edi。
控制传递:call exit@PLT。
sleep函数:
传递数据:将sleepsecs传入%edi。
控制传递:call sleep@PLT。
getchar函数:
控制传递:call gethcar@PLT
3.4 本章小结

简述了编译器的相关原理,并结合hello.c的汇编代码进行了相应的分析。
(第3章2分)

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

将.s文件即汇编代码翻译为二进制代码,可重定位目标程序的格式,即.o文件的过程。
4.2 在Ubuntu下汇编的命令

as hello.s -o hello.o

4.3 可重定位目标elf格式
readelf -a hello.o > helloo.elf

重定位节:rela.text 位于偏移量 0x318 含有八个条目:
下图所包含的是重定位信息,在链接时,需要重新修改下面的地址信息,下面分别是对L0,puts(),exit(),L1,printf(),int sleepsecs,sleep(),getchar()的重定位说明。

偏移量:需要进行重定向的代码在.text或.data节中的偏移位置,8个字节。
信息:包括symbol和type两部分,其中symbol占前4个字节,type占后4个字节,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型
类型:重定位到的目标的类型
名称:重定向到的目标的名称
加数:计算重定位位置的辅助信息,共占8个字节
例:以L1的重定位来分析过程:
由图可知:偏移量是:16,symblo时)0x05,类型是:R_X86_64_32,加数是0;
的计算公式如下:(设需要重定位的.text节中的位置为src,设重定位的目的位置dst):
refptr = s +r.offset (1)指向src的指针
refaddr = ADDR(s) + r.offset (2)计算src的运行时地址,
*refptr = (unsigned) (ADDR(r.symbol) + r.addend-refaddr)(3)ADDR(r.symbol)计算dst的运行时地址

从这里可以看出:偏移量是16,type是0x0A,symblo是0x05;加数是0;

4.4 Hello.o结果解析
objdump -d -r hello.o > helloo.objdump

区别
(一):在地址跳转时,汇编代码(.s文件)习惯使用符号如.L2 .L4,在call的时候习惯使用函数名如 call getchar call sleep;而在反汇编代码(.objdump文件),在地址跳转时习惯使用明确的地址如 jle 32.<main+0x32>,而在使用函数时候也习惯使用地址如 callq 76<main+0x76>.因为在.s文件中函数的地址都是需要重定位的,最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text节中为其添加重定位条目,等待静态链接的进一步确定。
(二)而且在访问全局变量时(如.LC0),.s文件也是使用段名称+%edi,而在反汇编代码中使用的是0+%edi, 因为rodata中数据地址也是在运行时确定,故访问也需要重定位。所以在汇编成为机器语言时,将操作数设置为全0并添加重定位条目。
4.5 本章小结
介绍了.o文件到.s文件的编译过程,介绍了.s文件的内容与介绍了.elf文件的相关结构,比较了汇编代码与反汇编代码的异同。

(第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.ohello.o/usr/lib/x86_64-linux-gnu/libc.so/usr/lib/x86_64-linux-gnu/crtn.o。

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

在ELF格式文件中,Section Headers对hello中所有的节信息进行了声明,包括大小,类型,偏移量,以及虚拟的首地址。
5.4 hello的虚拟地址空间

PHDR保存程序头表。
INTERP指定在程序已经从可执行文件映射到内存之后,必须调用的解释器(如动态链接器)。
LOAD表示一个需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串)、程序的目标代码等。
DYNAMIC保存了由动态链接器使用的信息。
NOTE保存辅助信息。
GNU_STACK:权限标志,标志栈是否是可执行的。
GNU_RELRO:指定在重定位结束之后那些内存区域是需要设置只读。

通过Data Dump查看虚拟地址段0x6000000x602000,在0fff空间中,与0x4000000x401000段的存放的程序相同,在fff之后存放的是.dynamic.shstrtab节。
5.5 链接的重定位过程分析
.interp 保存ld.so的路径
.note.ABI-tag Linux下特有的section
.hash 符号的哈希表
.gnu.hash GNU拓展的符号的哈希表
.dynsym 运行时/动态符号表
.dynstr 存放.dynsym节中的符号名称
.gnu.version 符号版本
.gnu.version_r 符号引用版本
.rela.dyn 运行时/动态重定位表
.rela.plt .plt 节的重定位条目
.init 程序初始化需要执行的代码
.plt 动态链接-过程链接表
.fini 当程序正常终止时需要执行的代码
.eh_frame contains exception unwinding and source language information.
.dynamic 存放被ld.so使用的动态链接信息
.got 动态链接-全局偏移量表-存放变量
.got.plt 动态链接-全局偏移量表-存放函数
.data 初始化了的数据
.comment 一串包含编译器的NULL-terminated字符串
1)函数个数:在使用ld命令链接的时候,指定了动态链接器为64的/lib64/ld-linux-x86-64.so.2,crt1.o、crti.o、crtn.o中主要定义了程序入口_start、初始化函数_init,_start程序调用hello.c中的main函数,libc.so是动态链接共享库,其中定义了hello.c中用到的printf、sleep、getchar、exit函数和_start中调用的__libc_csu_init,__libc_csu_fini,__libc_start_main。链接器将上述函数加入。

   2)函数调用:链接器解析重定条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入到了PLT中,.text与.plt节相对距离已经确定,链接器计算相对距离,将对动态链接库中函数的调用值改为PLT中相应函数与下条指令的相对地址,指向对应函数。对于此类重定位链接器为其构造.plt与.got.plt。

   3).rodata引用:链接器解析重定条目时发现两个类型为R_X86_64_PC32的对.rodata的重定位(printf中的两个字符串),.rodata与.text节之间的相对距离确定,因此链接器直接修改call之后的值为目标地址与下一条指令的地址之差,指向相应的字符串。这里以计算第一条字符串相对地址为例说明计算相对地址的算法

5.6 hello的执行流程
程序的名称与地址:
ld-2.27.so!_dl_start
0x7fce 8cc38ea0
ld-2.27.so!_dl_init
0x7fce 8cc47630
hello!_start
0x400500
libc-2.27.so!__libc_start_main
0x7fce 8c867ab0
-libc-2.27.so!__cxa_atexit
0x7fce 8c889430
-libc-2.27.so!__libc_csu_init
0x4005c0
hello!_init
0x400488
libc-2.27.so!_setjmp
0x7fce 8c884c10
-libc-2.27.so!_sigsetjmp
0x7fce 8c884b70
–libc-2.27.so!__sigjmp_save
0x7fce 8c884bd0
hello!main
0x400532
hello!puts@plt
0x4004b0
hello!exit@plt
0x4004e0
ld-2.27.so!_dl_runtime_resolve_xsave
0x7fce 8cc4e680
-ld-2.27.so!_dl_fixup
0x7fce 8cc46df0
–ld-2.27.so!_dl_lookup_symbol_x
0x7fce 8cc420b0
libc-2.27.so!exit
0x7fce 8c889128
5.7 Hello的动态链接分析
在edb调试之后我们发现原先0x00600a10开始的global_offset表是全0的状态,在执行过_dl_init之后被赋上了相应的偏移量的值。这说明dl_init操作是给程序赋上当前执行的内存地址偏移量,这是初始化hello程序的一步。
5.8 本章小结
本章介绍了链接,分析了hello的ELF格式,了解了虚拟地址空间的分配,重定位和执行过程还有动态链接的过程的相关知识。
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动过程调用的指令和本地变量。

进程为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
shell是一个应用程序,他在操作系统中提供了一个用户与系统内核进行交互的界面。他的处理过程一般是这样的:
1)从终端读入输入的命令。
2)将输入字符串切分获得所有的参数
3)如果是内置命令则立即执行
4)否则调用相应的程序为其分配子进程并运行
5)shell应该接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程

6.4 Hello的execve过程
每一个进程都有一段唯一属于自己的内存地址段,在execve运行时,开始先是从0x00400000(对于32位系统来说是0x8048000)开始程序的执行。先是从可执行文件中加载的内容,然后是运行时的堆栈和共享库的存储器映射区域。

6.5 Hello的进程执行

循环如上图所示的
在内核与前端做上下文切换。
用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
调用getchar()时先是运行在前端hello进程中,然后调用时切换到内核进程的标准读取程序中,从键盘输入读取到一个字符之后再回到hello进程
6.6 hello的异常与信号处理

CtrlZ命令:这个操作向进程发送了一个sigtstp信号,让进程暂时挂起,输入ps命令符可以发现hello进程还没有被关闭。

CtrlC命令:当按下ctrl-c之后,shell父进程收到SIGINT信号,信号处理函数的逻辑是结束hello,并回收hello进程,输入ps命令符可以发现hello进程已经被关闭。

fg:使被挂起的程序继续进行,例如在输出三次后按下Ctrl+Z,再输入fg,可以使其把剩下的七次打印完。

jobs: jobs命令可以查看当前的关键命令内容.

kill: 用法: kill [-s 信号声明 | -n 信号编号 | -信号声明] 进程号 | 任务声明 … 或 kill -l [信号声明]
(如果是信号30的话就是显示电源故障……不同的信号会返回不同的信息来描述这个信号)
6.7本章小结
简述了shell在用户与系统内核之间的作为桥梁,并分析了linux下的异常处理机制,介绍了应用程序的信号处理。shell执行程序是通过fork函数以及execve创建新的进程并执行程序的。
程序运行中可能会遇到异常,异常分为中断、陷阱、故障、终止四类,由异常处理子程序来处理,信号作为一种特殊的异常,实现了对程序运行终止等操作的控制。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址(Logical Address) 是指由程式产生的和段相关的偏移地址部分。例如,你在进行C语言指针编程中,能读取指针变量本身值(&操作),实际上这个值就是逻辑地址,他是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,Cpu不进行自动地址转换);逻辑也就是在Intel保护模式下程式执行代码段限长内的偏移地址(假定代码段、数据段如果完全相同)。应用程式员仅需和逻辑地址打交道,而分段和分页机制对你来说是完全透明的,仅由系统编程人员涉及。应用程式员虽然自己能直接操作内存,那也只能在操作系统给你分配的内存段操作。
线性地址(Linear Address) 是逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。
物理地址(Physical Address) 是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
7.2 Intel逻辑地址到线性地址的变换-段式管理
先将逻辑地址分成段选择符+段描述符的判别符(TI)+地址偏移量的形式,然后先判断TI字段,看看这个段描述符究竟是局部段描述符(ldt)还是全局段描述符(gdt),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了。

7.3 Hello的线性地址到物理地址的变换-页式管理
在这个转换中要用到翻译后备缓冲器(TLB),首先我们先将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式,然后再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)然后去TLB缓存里找所对应的PPN(物理页号)如果发生缺页情况则直接查找对应的PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。
7.4 TLB与四级页表支持下的VA到PA的变换
虚拟地址VA被分成VPN和VPO两部分,VPN被分为TLBT和TLBI用于在TLB中查询。根据TLBI确定TLB中的组索引,并根据TLBT判断PPN是否已被缓存到TLB中,如果TLB命中,则直接返回PPN,否则会到页表中查询PPN。在页表中查询PPN时,VPN会被分为四个部分,分别用作一二三四级页表的索引,而前三级页表的查询结果为下一级页表的基地址,第四级页表的查询结果为PPN。将查询到的PPN与VPO组合,得到物理地址PA。
7.5 三级Cache支持下的物理内存访问
MMU发送物理地址PA给L1缓存,L1缓存从物理地址中抽取出缓存偏移CO、缓存组索引CI以及缓存标记CT。高速缓存根据CI找到缓存中的一组,并通过CT判断是否已经缓存地址对应的数据,若缓存命中,则根据偏移量直接从缓存中读取数据并返回;若缓存不命中,则继续从L2、L3缓存中查询,若仍未命中,则从主存中读取数据。
7.6 hello进程fork时的内存映射
在用fork创建虚拟内存的时候,要经历以下步骤:

  1. 创建当前进程的mm_struct,vm_area_struct和页表的原样副本
  2. 两个进程的每个页面都标记为只读页面
  3. 两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制。
    7.7 hello进程execve时的内存映射
  4. 删除已存在的用户区域
  5. 创建新的私有区域(.malloc,.data,.bss,.text)
  6. 创建新的共享区域(libc.so.data,libc.so.text)
  7. 设置PC,指向代码的入口点

7.8 缺页故障与缺页中断处理
缺页故障是一种常见的故障,当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障。其处理流程遵循图 所示的故障处理流程。
缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU就能正常翻译VA了。

7.9动态存储分配管理
printf会调用malloc,接下来提一下动态内存分配的基本原理。

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。分配器将堆视为一组不同大小的块的集合来维护,每个块就是一个连续的需内存片,要么是已分配的,要么是空闲的。已分配的块显示地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显示地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显示执行的,要么是内存分配器自身隐式执行的。
两种堆的数据结构组织形式
带标签的隐式空闲链表:所谓隐式空闲链表,对比于显式空闲链表,代表并不直接对空闲块进行链接,而是将对内存空间中的所有块组织成一个大链表,其中Header和Footer中的block大小间接起到了前驱、后继指针的作用。

显示空闲链表:将空闲块组织成链表形式的数据结构。堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个pred(前驱)和succ(后继)指针

7.10本章小结
本章介绍了储存器的地址空间,讲述了虚拟地址、物理地址、线性地址、逻辑地址(Intel逻辑地址到线性地址的变换)的概念,还有进程fork和execve时的内存映射的内容,了解了缺页故障与其相关处理,最后学习了动态存储分配管理的相关知识。
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件

  文件的类型:

普通文件(regular file):包含任意数据的文件。
目录(directory):包含一组链接的文件,每个链接都将一个文件名映射到一个文件(他还有另一个名字叫做“文件夹”)。
套接字(socket):用来与另一个进程进行跨网络通信的文件
命名通道
符号链接
字符和块设备
设备管理:unix io接口

打开和关闭文件
读取和写入文件
改变当前文件的位置
8.2 简述Unix IO接口及其函数
Unix I/O接口统一操作:

打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。
Shell创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。
改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。
读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
Unix I/O函数:

int open(char* filename,int flags,mode_t mode) ,进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。
int close(fd),fd是需要关闭的文件的描述符,close返回操作结果。
ssize_t read(int fd,void *buf,size_t n),read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。
ssize_t wirte(int fd,const void *buf,size_t n),write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
8.3 printf的实现分析
首先printf()的代码如下:
int printf(const char fmt, …)
{
int i;
char buf[256];
va_list arg = (va_list)((char
)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
printf需要做的事情是:接受一个fmt的格式,然后将匹配到的参数按照fmt格式输出,其调用了连个外部函数一个是vsprintf,还有一个是write。
首先我们来查看vsprintf()代码:
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':     //只处理%x一种情况
            itoa(tmp, *((int*)p_next_arg)); //将输入参数值转化为字符串保存在tmp
            strcpy(p, tmp);  //将tmp字符串复制到p处
            p_next_arg += 4; //下一个参数值地址
            p += strlen(tmp); //放下一个参数值的地址
            break;
        case 's':
            break;
        default:
            break;
    }
}
return (p - buf);   //返回最后生成的字符串的长度

}
从上面vsprintf函数可以看出,这个函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。
再来看write()函数:write函数是将buf中的i个元素写到终端的函数。
从vsprintf生成显示信息,显示信息传送到write系统函数,write函数再陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序。从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)

8.4 getchar的实现分析
getchar函数的大致实现如下:
代码:
int getchar(void)
{
char c;
return (read(0,&c,1)==1)?(unsigned char)c:EOF
}
getchar函数通过调用read函数来读取字符。read函数由三个参数,第一个参数为文件描述符fd,fd为0表示标准输入;第二个参数为输入内容的指针;第三个参数为读入字符的个数。read函数的返回值是读入字符的个数,若出错则返回-1。
当用户按键时,键盘接口会产生一个键盘扫描码和一个中断请求,中断处理程序会从键盘接口取得按键扫描码并把它转换成ASCII码,保存到系统的键盘缓冲区。
read执行一个系统调用,按照系统调用从键盘缓冲区读取按键ASCII码,直到接受到回车键才返回。
8.5本章小结
Linux把所有的I/O设备模型化为文件,并提供统一的Unix I/O接口,这使得所有的输入输出都能以一种统一且一致的方式来执行,在此之下我们了解了开、关、读、写、转移文件的接口及相关函数,并简单分析了printf和getchar函数的实现方法以及操作过程。

(第8章1分)
结论

  1. 我们从hello.c文件出发,经过预处理(.i)与编译(.s)过程得到汇编代码,此时的代买仍然可读,即不被计算机理解,再经过编译过程,得到二进制的文件.o(可重定位的),再在连接器的作用下,将多个可重定位的文件链接到一起形成可执行文件,在运行hello的时候,首先bash会新建一个进程,清空当前进程的数据并加载hello,从函数的入口进入,开始执行,由于各种各样的原因,我们的hello,可能会暂时的休息(系统调用或者计时器中断),这时我们保留当前进度,并切换上下文,内核去处理别的进程,我们还可以输入信号来终止或挂起hello进程,hello输出信息时需要调用printf和getchar,而printf和getchar的实现需要调用Unix I/O中的write和read函数,而它们的实现需要借助系统调用,在最后结束之后bash等到exit,作为hello的父进程回收hello.

(结论0分,缺少 -1分,根据内容酌情加分)

附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i 预处理产生的文本文件
hello.s 编译产生的汇编文件
hello.o 汇编产生的可重定位目标执行(二进制)
Hello 链接之后的可执行目标文件
helloo.objdmp Hello.o的反汇编代码
helloo.elf Hello.o的ELF格式
hello.objdmp Hello的反汇编代码
hello.elf Hello的ELF格式

参考文献
为完成本次大作业你翻阅的书籍与网站等
[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/bat67/article/details/52071829
【8】《深入理解计算机系统》Randal E.Bryant,David R.O Hallron
【9】https://baike.sogou.com/v53369.htm?fromTitle=ascii码表

(参考文献0分,确实 -1分)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值