计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机大类
学 号 藏
班 级 藏
学 生 藏
指 导 教 师 藏
计算机科学与技术学院
2021年5月
本文以hello程序为例,介绍了C语言经过预处理,编译,汇编,链接逐步生成可执行文件的过程,hello可执行文件如何创建进程,访问内存,通过I/O系统在计算机上执行。通过剖析hello程序P2P,020的全过程,更加深入且生动地了解计算机系统。
关键词:预处理;编译;汇编;重定位;链接;进程;存储;数据访问;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的预处理结果解析.................................................... - 6 -
2.4 本章小结............................................................................... - 6 -
第3章 编译................................................................................... - 7 -
3.1 编译的概念与作用............................................................... - 7 -
3.2 在Ubuntu下编译的命令.................................................... - 7 -
3.3 Hello的编译结果解析........................................................ - 7 -
3.4 本章小结............................................................................. - 12 -
第4章 汇编................................................................................. - 13 -
4.1 汇编的概念与作用............................................................. - 13 -
4.2 在Ubuntu下汇编的命令.................................................. - 13 -
4.3 可重定位目标elf格式...................................................... - 13 -
4.4 Hello.o的结果解析........................................................... - 16 -
4.5 本章小结............................................................................. - 18 -
第5章 链接................................................................................. - 19 -
5.1 链接的概念与作用............................................................. - 19 -
5.2 在Ubuntu下链接的命令.................................................. - 19 -
5.3 可执行目标文件hello的格式......................................... - 19 -
5.4 hello的虚拟地址空间....................................................... - 24 -
5.5 链接的重定位过程分析..................................................... - 25 -
5.6 hello的执行流程............................................................... - 26 -
5.7 Hello的动态链接分析...................................................... - 27 -
5.8 本章小结............................................................................. - 27 -
第6章 hello进程管理.......................................................... - 28 -
6.1 进程的概念与作用............................................................. - 28 -
6.2 简述壳Shell-bash的作用与处理流程........................... - 28 -
6.3 Hello的fork进程创建过程............................................ - 28 -
6.4 Hello的execve过程........................................................ - 29 -
6.5 Hello的进程执行.............................................................. - 29 -
6.6 hello的异常与信号处理................................................... - 30 -
6.7本章小结.............................................................................. - 34 -
第7章 hello的存储管理...................................................... - 35 -
7.1 hello的存储器地址空间................................................... - 35 -
7.2 Intel逻辑地址到线性地址的变换-段式管理.................. - 35 -
7.3 Hello的线性地址到物理地址的变换-页式管理............. - 36 -
7.4 TLB与四级页表支持下的VA到PA的变换................... - 36 -
7.5 三级Cache支持下的物理内存访问................................ - 37 -
7.6 hello进程fork时的内存映射......................................... - 37 -
7.7 hello进程execve时的内存映射..................................... - 37 -
7.8 缺页故障与缺页中断处理................................................. - 38 -
7.9动态存储分配管理.............................................................. - 38 -
7.10本章小结............................................................................ - 39 -
第8章 hello的IO管理....................................................... - 40 -
8.1 Linux的IO设备管理方法................................................. - 40 -
8.2 简述Unix IO接口及其函数.............................................. - 40 -
8.3 printf的实现分析.............................................................. - 40 -
8.4 getchar的实现分析.......................................................... - 40 -
8.5本章小结.............................................................................. - 41 -
参考文献....................................................................................... - 43 -
第1章 概述
1.1 Hello简介
p2p:
hello程序的生命周期是从一个C语言程序开始的,通过gcc编译器读取源文件,经过预处理cpp,编译cll,汇编as,连接ld最终生成二进制的可执行文件。在shell中输入可执行文件名字的时候,shell会调用fork函数为hello程序创建一个新进程,访问io设备将hello word显示在屏幕上。
020:
程序原本不存在,由程序元编写出现,运行hello程序后进程终止并由shell回收。
1.2 环境与工具
硬件环境:x64 CPU; 1.5GHz; 8G RAM; 512GHD Disk
软件环境:Windows10; Vmware Workstation pro; Ubuntu 20.04
开发工具:Visual Studio 2016; CodeBlocks; vim; gcc
1.3 中间结果
hello.c C语言源程序
hello.i 预处理生成的文本
hello.s 编译后生成的汇编文件
hello.o 汇编后生成的可重定位目标程序(二进制)
hello 连接后生成的可执行目标程序(二进制)
1.4 本章小结
本章以hello程序为例,介绍了p2p,020过程。列出了本次实验的硬件环境
软件环境和开发工具,简单介绍了hello从C语言到可执行目标文件的大致过程。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理器根据以字符#开头的命令,修改原始的C程序,将用到的库插入到程序文本中得到另一个C程序,通常以.i作为文件拓展名
作用:
1.处理条件编译指令
条件编译指令如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。
2.处理头文件
包含指令如#include "FileName"或者#include 等。 该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
3.处理特殊符号
预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。
2.2在Ubuntu下预处理的命令
图1 预处理指令截图
2.3 Hello的预处理结果解析
图2 hello.i内容查看
打开生成的.i文件发现有3061行。预处理器对原文件的宏定义展开并插入到.i文件中。
2.4 本章小结
本章以gcc预处理hello.c为例,介绍了预处理的概念和作用,并展示了预处理生成的.i文件。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译器ccl将文本文件hello.i翻译成文本文件hello.s,该文件包含一个汇编语言程序。
作用:编译程序把源程序翻译成目标程序,同时进行语法检查,调试措施,优化程序,分配寄存器的使用。
3.2 在Ubuntu下编译的命令
图3 编译指令截图
3.3 Hello的编译结果解析
图4 编译结果截图1
打开生成的文件可以看到指令已经变成汇编语言
图5 编译结果截图2
.file:声明源文件
.text:代码节
.section:
.rodata:只读代码段
.align:数据或者指令的地址对其方式
.string:声明一个字符串(.LC0,.LC1)
.global:声明全局变量(main)
.type:声明一个符号是数据类型还是函数类型
3.3.1 数据类型
(1)常量
对于有符号整型数据,会转化成对应的十六进制补码
图6 有符号整数在汇编语言中的形式
对于无符号整数,会转化为对应的十六进制源码754标准转化为十六进制数
对于字符型会根据Unicode编码生成十六进制数
图7 字符串在汇编语言中的形式
(2)变量
全局变量:存储在.global后
图8 全局变量在汇编语言中的存储
局部变量:将变量的值存放在寄存器中,或者当寄存器不够存放所有本地数据式分配新的栈帧将局部变量存放在内存中
图9 局部变量在寄存器中1
图10 局部变量在寄存器中2
静态变量:存储在栈中
3.3.2 操作
(1)赋值 对编译器来说就是将数据从一个位置复制到另一个位置,进行这种操作的指令叫做数据传送指令,如mov系列。
图11 mov指令
(2)类型转换
显示类型转换由程序员通过强制类型转换语句实现,隐式类型转换由编译器自动完成,比如高位字节截断只传送低位字节可以实现整型到整型或者整型到字符型转化,或者低位字节向高位字节拓展,包括零拓展和符号位拓展。
(3)算数/逻辑/位操作操作
图12 算数、逻辑、位运算指令
如对于i++操作
图13 i++在汇编语言中的表示
(4)关系操作/控制转移if/else switch for while continue break
通过test、cmp设置条件码CF ZF SF OF
图14 条件码的含义
比较i是否小于等于7,满足时跳转
比较argv[]的大小是否等于4,等于时跳转
(5)数组/指针/结构操作
对数组、指针、结构的操作都是对指针的操作,指针以对象的大小为单位伸缩。内存访问形式有
图15 寻址的形式
hello.s文件中如
(6)函数操作 传递参数(地址/值)函数调用 局部变量 函数返回
函数调用用call,返回用ret
如调用printf函数
调用getchar函数
传递参数如果小于6个依次使用寄存器%rdi, %rsi, %rdx, %rcx, %r8, %r9,如果大于6个则用栈传递
3.4 本章小结
本章讲述了编译阶段编译器如何处理各种数据和操作以及C语言对应的汇编代码。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:将汇编语言源程序翻译成目标程序的过程。
作用:汇编器as将.s文件翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在目标文件中。
4.2 在Ubuntu下汇编的命令
图16 汇编指令截图
4.3 可重定位目标elf格式
EFL文件包含
EFL头
段头部表
.text
.rodata
.data
.bss
.symtab
.rel.text
.rel.data
.debug
.line
.strtab
节头部表
4.3.1 ELF头
7f 、45、4c、46分别对应ascii码的Del(删除)、字母E、字母L、字母F。这四个字节被称为ELF文件的魔数,操作系统在加载可执行文件时会确认魔数是否正确,如果不正确则拒绝加载。
第五个字节标识ELF文件是32位(01)还是64位(02)的。
第六个字节标识该ELF文件字节序是小端(01)还是大端(02)的。
第七个字节指示ELF文件的版本号,一般是01。
后九个字节ELF标准未做定义。一般为00.
剩下的部分包含帮助链接器语法分析和解释目标文件的信息,如目标文件的类型(Type):可重定向文件REL,机器类型Machine:x86-64等等。
图17 ELF头截图
4.3.2 节头部表
描述不同节的位置和大小,目标文件中每个节都有一个固定大小的条目,展示各节的名称name、类型type、地址address、偏移量offset、对齐信息align等
图19 头节部表截图
4.3.3 重定位节
当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中。已初始化数据的重定位条目放在.rel.data中。这些重定位条目都放在重定位节。
Offset偏移量是需要被修改的引用的节偏移;
Type告知链接器如何修改新的引用;
Sym.name表示被修改引用应该指向的符号;
add是一个有符号常数,调整被修改的值的偏移。
R_X86_64 PC32。重定位一个使用32位PC相对地址的引用
R _X86_ 64_ 32。 重定位一个使用32位绝对地址的引用
R_X86_64_PLT32类型,PLT 是一个新的函数入口表的格式
图20 重定位节截图
4.3.4 符号表
symbol table存放程序中定义和引用的函数、全局变量的信息。通过符号表可以得出符号的名字,定义符号的节,在该节中距离起始位置的偏移,大小,类型,源文件路径等信息
图21 符号表截图
4.4 Hello.o的结果解析
图22 打开objdunp后的hello.o
图22 objdump hello的内容
对比汇编代码和反汇编代码发现两者的指令相同,汇编语言和机器语言一一对应。对于分支转移,反汇编的跳转指令是相对下一条指令的相对地址偏移量,汇编跳转指令是段落名称;对于函数调用,反汇编函数调用指令的操作数是还是相对偏移量,汇编指令的操作数是函数名称。
4.5 本章小结
本章对hello.s进行了汇编,生成了hello.o可重定位目标文件,并且分析了可重定位文件的ELF头、节头部表、符号表和可重定位节,比较了hello.s和hello.o反汇编代码的不同之处,分析了从汇编语言到机器语言的一一映射关系
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(compile time),也就是在源化码被翻译成机器代码时;也可以执行于加载时(load time), 也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(run time),也就是由应用程序来执行。
5.2 在Ubuntu下链接的命令
图23 链接指令截图
5.3 可执行目标文件hello的格式
EFL头
段头部表
.text
.rodata
.data
.bss
.symtab
.rel.text
.rel.data
.debug
.line
.strtab
节头部表
5.3.1 ELF头
与4.3.1节类似,可执行目标文件的ELF头表述文件的类别ELF64,数据(补码 小端序)类型EXEC可执行文件,机器x86-64,入口点地址程序头起点等信息。
图24可执行文件 ELF头
5.3.2 节头部表
与4.3.2相同,不再赘述。
图25 可执行文件节头部表
5.3.3 程序头部表
ProgramHeader存储so文件运行时所需要的信息,这部分信息会直接被linker使用,用于加载so文件。可执行文件的连续的片(chunk)被映射到连续的内存段。
Type段类型
PHDR 此类型的数组元素如果存在,则给出程序头部表自身的大小和位置,既包括在文件中也包括在内存中的信息。此类型的段在文件中不能出现一次以上,并且只有程序头部表是程序的内存映像的一部分时才起作用。
INTERP 数组元素给出一个NULL结尾的字符串的位置和长度,该字符串将被当作解释器调用。
LOAD 此数组元素给出一个可加载的段,段的大小由p_filesz和p_memsz描述。文件中的字节被映射到内存段开始处。
DYNAMIC 数组元素给出的动态连接信息
Offset段位置,相对于文件的索引地址
VirtAddr段第一个字节被放入内存中的虚拟地址
PhysAddr仅用于与物理地址相关的系统
图24 可执行文件程序头部表
5.3.4 Section to Segment mapping
5.3.5 Dynamic section
5.3.6 Relocation section
5.3.7 Symbol table
5.4 hello的虚拟地址空间
通过查看edb,看出hello的虚拟地址空间开始于0x400000,不同的节有不同的空间地址,例如:
.text节:
.roldata节:
.data节:
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
(1)hello反汇编的代码有确定的虚拟地址,也就是说已经完成了重定位,而hello.o反汇编代码中代码的虚拟地址均为0,未完成可重定位的过程。
如图
(2)hello反汇编的代码中多了很多的节,如程序初始化需要执行的代码.init节,动态链接.plt节,存放被ld.so使用的动态链接信息的.dynamic节。此外还有链接加入的函数exit、printf、sleep、getchar的汇编代码
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
(1)重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
(2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
(3)重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程:
(1)加载: _init、_start
(2)执行main:_main、_printf、_exit、_sleep、_getchar、_dl_runtime_resolve_xsave、_dl_fixup、_dl_lookup_symbol_x
(3)退出:exit
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
对于动态共享连接库中的代码,编译器无法预测函数运行时的地址,因为定义函数的模块在运行的时候可能加载到任何位置。由于数据段和代码段的距离在运行时候不变,可以在数据段开始的地方创建全局变量表GOT,对每个被应用的全局数据生成条目,动态链接器根据GOT重定位到绝对地址。
dl_init前:
dl_init后:
5.8 本章小结
本章介绍了链接的概念和作用。用gcc进行链接操作,查看了可执行目标文件的ELF,分析了hello的虚拟地址空间,重定位过程和执行流程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
进程提供给应用程序两个关键抽象:一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器;一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。其基本功能是解释并运行用户的命令行。
流程:
1. 读取用户由键盘输入的命令行。
2. 分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量。
3. 检查第一个命令行参数是否是一个内置的shell命令。
4. 如果是内置的shell命令,则立即执行。
5. 如果不是内置的shell命令,调用fork( )创建子进程
6. 在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。
7. 如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait等待作业终止后返回。
8. 如果用户要求后台运行(如果命令末尾有&号),则shell返回;
6.3 Hello的fork进程创建过程
在终端输入./hello 120L022202 lijieyu 1后,由于hello不是内置命令,shell会创建一个子进程,并分配PID。子进程和父进程几乎相同,子进程得到父进程用户级虚拟地址空间的独立副本,包括代码和数据段、堆、共享库、用户栈,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。fork调用一次,却会返回两次:一次是在调用进程中(返回子进程的PID);一次是在新创建的子进程中(返回0)。
父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。
6.4 Hello的execve过程
1. 陷入内核
2. 加载新的可执行文件并进行可执行性检查
3. 将新的可执行文件映射到当前运行进程的进程空间中,并覆盖原来的进程数据
4. 将EIP的值设置为新的可执行程序的入口地址。如果可执行程序是静态链接的程序,或不需要其他的动态链接库,则新的入口地址就是新的可执行文件的main函数地址;如果可执行程序还需要其他的动态链接库,则入口地址是加载器ld的入口地址
5. 返回用户态,程序从新的EIP出开始继续往下执行。至此,老进程的上下文已经被新的进程完全替代了,但是进程的PID还是原来的。
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 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
Hello执行过程中会出现4种异常:
中断:外部的I/O设备造成的。
陷阱:系统调用有意的错误。
故障:故障由错误情况引起,它可能被故障处理程序修正。
终止:不可恢复的错误。
正常执行:程序正常运行,结束后按回车返回
不停乱按:可以看到乱按输入的字符会堆积到缓冲区并且在程序结束之后当作指令再次输出。
ctrl+c:程序终止
ctrl+z:程序停止
ps:可以看到当前的进程仍有hello
jobs:可以看到hello进程已经停止
pstree(截图为一部分)
fg:程序回复运行
6.7本章小结
本章阐述了进程的概念和作用,介绍了shell的处理流程,分析了fork创建进程和调用execve函数的流程,hello的进程执行和异常处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序编译后出现在汇编代码中的地址,用来指定一个操作数或一条指令的地址,由段标识符加上偏移量表示。
线性地址:一个逻辑地址经过段地址机制转化后变成一个线性分页地址,线性地址可以再经过物理地址变换以产生一个新的物理分页地址。线性地址是逻辑地址和物理地址变换的中间层。
虚拟地址:即线性地址
物理地址:用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。可以直接把物理地址理解成插在机器上的内存本身,把内存看成一个从0字节一直到最大空量逐字节编号的大数组,把这个数组叫做物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号,是 “段描述符(segment descriptor)”的索引,段描述符具体地址描述了一个段,很多个段描述符,就组了一个数组,叫“段描述符表”。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,每一个段描述符由8个字节组成,其中Base字段,描述了一个段的开始位置的线性地址。
Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。
GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。
从逻辑地址转变为线性地址:
首先,给定一个完整的逻辑地址[段选择符:段内偏移地址],
1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。
2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符Base,即基地址就知道了。
3、把Base + offset,得到转换的线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,整个线性地址被划分为一个total_page [2^20]的大数组,称之为页目录。目录中的每一个目录项,就是一个对应的页的地址。
而物理页,是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。
1、分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。
2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),也对应了一个独立的页目录地址。运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。
3、每一个32位的线性地址被划分为三部份,面目录索引(10位)、页表索引(10位)、偏移(12位)
依据以下步骤进行转换:
1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
2、根据线性地址前十位,在数组中,找到对应的索引项。页目录中的项,是一个页表的地址。
3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
4、将页的起始地址与线性地址中最后12位相加,得到最终物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE,以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会要求从内存多取一次数据, 代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就下降到1个或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一-个关于PTE的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer, TLB)。
TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。TLB通常有高度的相联度。
用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号取出来的。如果TLB由T=2t 个组,那么TLB索引(TLBI)就是由PTN的t个最低位组成的,而TLB标记(TLBT)是由VPN中剩余的位组成的。
7.5 三级Cache支持下的物理内存访问
首先取组索引对应位,查L1 cache,L1 cache不命中查L2 cache,L2 cache不命中查L3 cache,L3 cache不能命中查内存,内存是磁盘的cache,内存中的数据可能被虚拟内存系统放到磁盘中,如果内存也不能命中就要查磁盘。查找的过程:
1. 向cache中寻找对应组,看是否存在;
2. 如果存在,检查对应行的有效位是否为1,
3. 比较标志位是否相同。
如果上述条件均满足则命中,如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块按照一定的策略驱逐,将目标块放到被驱逐块的原位置。如果不存在则可能时地址错误,发生终止异常。
7.6 hello进程fork时的内存映射
当fork函数被shell调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、 区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork 在新进程中返回时,新进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。
7.7 hello进程execve时的内存映射
在创建了一个子进程后,子程序调用execve函数在上下文加载hello程序。
(1)删除当前虚拟地址中已存在的用户区域。
(2)为新程序建立新的区域结构,这些区域结构是私有的,虚拟地址空间的代码和数据区域被映射为hello文件的.txt和.data区,bss区域是请求二进制零的,映射匿名文件,其大小包含在hello文件中。栈和堆区域也是请求二进制零的,初始长度为零。
(3)如果hello与共享对象链接,那么这些对象都被动态链接到这个程序,然后映射到用户虚拟地址空间中的共享区域。
(4)设置程序计数器,使之指向代码区域的入口点,下次调用这个进程时,从这个入口点开始执行。
7.8 缺页故障与缺页中断处理
缺页故障:当指令引用一个相应的虚拟地址,而与该地址相应的物理页面不再内存中,会触发缺页故障。通过查询页表PTE可以知道虚拟页在磁盘的位置。缺页处理程序从指定的位置加载页面到物理内存中,并更新PTE。然后控制返回给引起缺页故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中,因此指令可以没有故障的运行完成。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址) 。对于每个进程,内核维护着一个变量brk, 它指向堆的顶部.
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
1.简单分离存储
使用简单分离存储,每个大小类的空闲链表包含大小相等的块,每个块的大小就是这个大小类中最大元素的大小。
2.分离适配
使用这种方法,分配器维护着一个空闲链表的数组。每个空闲链表是和一个大小类相关联的,并且被组织成某种类型的显式或隐式链表。每个链表包含潜在的大小不同的块,这些块的大小是大小类的成员。有许多种不同的分离适配分配器。
3.伙伴系统
伙伴系统是分离话配的一种特例。 其中每个大小类那是2的幂。基本的思路是假设一个堆的大小为2m个字, 我们为每个块大小2k维护一个分离空闲链表。其中0<=k<=m。请求块大小向上舍入到最接近的2的幂。最开始时,只有一个大小为2m个字的空闲块
7.10本章小结
本章对hello程序运行时虚拟地址的变化进行分析,解析了hello应用程序的虚拟存储地址空间,分析了虚拟地址,线性地址和虚拟物理线性地址之间的互相转换,页表的命中与不页表的命中,使用动态快表缓存作为页表的高速缓存以及如何加速页表,动态内存管理的操作,fork时的动态内存中断与映射、execve时的动态内存中断与映射、缺页的中断与缺页映射和中断的处理。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
输入操作即把数据从I/O设备复制到主存,输出操作是从主存复制数据到I/O设备,所有的操作被当作对应文件的读写来执行。
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
ssize_t read(intfd, void *buf, size_t count);
ssize_t write(intfd, void *buf, size_t count);
以上两个是linux下的两个系统调用,用于对文件行基本的I/O操作。fd是非负文件描述符,是标识一个文件的唯一编号。默认标号0是标准输入(终端输入),1是标准输出(终端输出),2是标准错误。所以用户通过 open 能够打开的文件得到的文件描述符的最小编号是3。
在Linux中,read 和 write 是基本的系统级I/O函数。当用户进程使用read 和 write 读写linux的文件时,进程会从用户态进入内核态,通过I/O操作读取文件中的数据。
8.3 printf的实现分析
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出,并返回字符串的长度i。系统函数write(buf,i)将长度为 i 的 buf 输出。write函数的第一个参数为描述符,1代表标准输出。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成ASCII码,保存到系统的键盘缓冲区之中。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO管理方法;简述了Unix IO接口机器函数;分析了printf函数和getchar的实现。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
程序员编写hello.c文件,预处理器根据开头的宏定义等重写文件生成hello.i,编译器将文本文件翻译成汇编语言,生成hello.s,汇编器将其转化为机器语言并生成二进制的可重定位目标程序hello.o,最后链接器将其与内置代码数据链接并生成可执行的文件。用户输入./hello 120L022202 lijieyu 2,shell会fork子进程并调用execve执行程序。加载器为hello分配内存并把数据段代码段等内容缓存,同时监测来自内核的信号。当程序执行完成,子进程会终止由shell回收。
计算机是一个复杂的系统,仅仅是最简单的hello程序,执行的全过程都由很多流程和软硬件的配合。通过这样一个简单的例子,对计算机系统的了解更加深入更加细致。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.c C语言源文件
hello.i hello.c预处理之后的文本文件
hello.s hello.i编译后的文本文件
hello.o hello.s汇编之后的可重定向文件
hello_o_readelf.txt 查看hello.oELF的结果
hello_o_objump.txt 用objdump查看hello.o的结果
hello 链接后的可执行文件
hello_readelf.txt 查看helloELF的结果
hello_objdump.txt 用objdump查看hello的结果
(附件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分)