2022年 计算机系统大作业 Hello‘s P2P

计算机系统大作业

题 目 程序人生-Hello’s P2P
专 业 计算机
学  号 120L020119
班  级 2003003
学 生 徐涛   
指 导 教 师 史先俊

摘 要

hello程序并不让人陌生,而在hello的生命周期中包含着完整的P2P过程。我们将了解预处理、编译、汇编、链接的作用机理,还将通过反汇编的方式得到汇编文件,对比产生文件前后变化,以了解机器如何对程序加工。此外,还介绍了shell的工作方式、内存管理的方法策略及IO管理,特别是一些读写函数。

关键词:hello;P2P;计算机系统;

目 录

第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:就是Program to Process 。首先,hello.c经过cpp的预处理、ccl的编译、as的汇编、ld的链接成为了可执行目标程序hello,然后在shell中键入启动命令,shell调用fork命令为其产生一个子进程,这样hello便从程序变为了进程。

020: 一个进程的调用前与回收后不会在系统留下痕迹。shell为hello子进程execve,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入 main函数执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序运行结束后,shell父进程负责回收hello进程,内核删除相关数据结构。
1.2 环境与工具
硬件环境:x86 64CPU Intel core i7-10875 2.30Gz ;16GB RAM;512GB Disk
软件环境:操作系统 Windows 11 64位;VMware 15.6;Ubuntu 20.04
调试开发工具:Visual Studio 2019;gcc/g++;objdump;EDB;vim;gdb
1.3 中间结果
预处理:hello.i , 对hello.c预处理得到的文本文件,将include调用的库内容 直接插入文本。
编译:hello.s , 对hello.i编译的文本文件得到,包含一个汇编语言程序。
汇编:hello.o , 可重定位的二进制目标程序
链接:hello , 将可重定位二进制程序链接,如printf函数,生成可执行程序。
1.4 本章小结
本章对hello进行了简单的介绍,分析了其P2P和020的过程,列出了本次任务的环境和工具,并且阐明了任务过程中出现的中间产物及其作用。

第2章 预处理
2.1 预处理的概念与作用
概念:预处理器(CPP)根据以字符#开头的命令(宏定义、条件编译等),修改原始的C程序,并将其引用的所有库进行展开合并,生成以.i结尾的文本文件。
作用:
删除注释。
用实际值代替用#define定义的字符串。
将源文件中用#include形式声明的文件复制到新的程序中。
条件编译,根据#if后面的条件决定要进行编译的代码。

2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i

在这里插入图片描述

图2.1 预处理命令及生成文件

2.3 Hello的预处理结果解析
预处理生成的hello.i文件仍为可以阅读的C语言文本文件,根据条件编译决定被编译代码,系统头文件中的内容被直接插入到了源程序文本中,宏定义被展开,注释被删除了。
2.4 本章小结
通过本章了解了C语言在编译前的预处理过程,明白了预处理的概念及作用,学会通过gcc下达预处理指令,同时解析了预处理文本文件内容,对预处理的结果分析其作用的实例化。

第3章 编译
3.1 编译的概念与作用
概念:将由高级程序语言编写的程序转为汇编语言或机器语言的过程,称作编译。
作用:将hello.i转为hello.s这种机器能够读懂指令的程序。

3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

在这里插入图片描述

图3.1 编译命令及生成文件
3.3 Hello的编译结果解析
3.3.1 汇编指令介绍
.file:声明源文件
.text:代码节
.section:
.rodata:只读代码段
.align:数据或者指令的地址对其方式
.string:声明一个字符串(.LC0,.LC1)
.globl:声明全局变量(main)
.type:声明一个符号是数据类型还是函数类型

3.3.2 数据

  1. 常量:printf函数中的字符串为常量
    在这里插入图片描述

图3.2 字符串常量
2) 变量:.globl声明全局变量,i,argc,argv为局部变量

在这里插入图片描述

图3.3 全局变量
在这里插入图片描述

图3.4 变量i
在这里插入图片描述

图3.5 变量argv
在这里插入图片描述

图3.6 赋值操作

3.3.3 操作

  1. 赋值:调用movq,movl指令实现,见图。
  2. 算数操作:调用addl,subq指令实现。
    在这里插入图片描述

图3.7 算数操作

  1. 关系操作:调用cmp系列指令实现
  2. 控制转移操作:通过je,jne,jmp等指令实现,常搭配cmp类指令。
    在这里插入图片描述

图3.8 控制转移操作
5. 调用函数:设置传递参数的寄存器的值并调用call指令。
3.4 本章小结
通过本章了解了C语言的编译过程,明白了编译的概念及作用,学会了gcc下编译操作对应的汇编指令,同时解析了hello.i的编译结果,对编译的过程处理有了一定的了解,同时对程序的机器级表示方法更加熟悉。

第4章 汇编
4.1 汇编的概念与作用
概念:汇编器将hello.s文件翻译成二进制机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存到目标文件hello.o中。hello.o是一个二进制文件,包含着程序的指令编码,如果用文本编辑器查看,将看到一堆乱码。
作用:把汇编文件转换为机器容易理解的机器代码(二进制文件),该文件的内容是程序在该机器的机器语言的表示。
4.2 在Ubuntu下汇编的命令
命令:gcc -c hello.s -o hello.o
在这里插入图片描述

图4.1 汇编命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
在这里插入图片描述

图4.2 汇编elf文本
指令:readelf -a hello.o > hello_elf.txt

  1. elf头
    在这里插入图片描述

图4.3 elf头
ELF Headers:以 16B 的序列 Magic 开始,Magic 描述了生成该文件的系统 的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括 ELF 头的大小、目标文件的类型、机器类型、字节头部表(section header table)的文件偏移,以及节头部表中条目的大 小和数量等信息。
2. 节头
在这里插入图片描述

图4.4 节头
Section Headers:通过此可知道此elf文件中共有13个节,包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,各节的读写权限等。
3. 符号表
在这里插入图片描述

图4.5 符号表
4. .rela.text节和.rela.eh_frame节
图中信息介绍了两条重定位节的一些信息,这里同时涉及到了重定位的绝对引用与相对引用,还需要了解绝对引用、相对引用的重定位算法。
绝对引用重定位算法:
refaddr = ADDR(s) + r.offset;
*refptr = (unsigned) (ADDR(r.symbol) + r.addend – refaddr);
相对引用重定位算法:
*refptr = (unsigned) (ADDR(r.symbol) + r.addend);
在这里插入图片描述

图4.6 偏移量
4.4 Hello.o的结果解析
objdump -d -r hello.o > asm.txt 分析hello.o的反汇编,并与第3章的 hello.s进行对照分析。
在这里插入图片描述

图4.7 反汇编与编译文件内容对比图
可以看到asm.txt的都有长串的机器码供机器识别,而hello.s中并没有。hello.o文件依照地址跳转,hello.s文件依照表示符跳转。申请栈时hello.o是用16进制,而hello.s用十进制。重定位时hello.o会留下地址,hello.s文件直接声明。
4.5 本章小结
第四章汇编解析了从hello.s到hello.o的过程中产生变化,查看了程序ELF条目,了解其内容如何概括程序整体信息。使用了反汇编工具objdump得到还原出来的汇编程序,通过分析编译生成文件与反汇编的差异,对机器认识汇编程序的方法有所了解。

第5章 链接
5.1 链接的概念与作用
概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接执行符号解析、重定位过程。
作用:把可重定位目标文件和命令行参数作为输入,产生一个完全链接的,可以加载运行的可执行目标文件。使得分离编译成为可能。
5.2 在Ubuntu下链接的命令
命令:ld -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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello.out hello.out
在这里插入图片描述

图5.1 链接命令
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

  1. ELF头
    在这里插入图片描述

图5.2 elf头
显示文件类型为EXEC(可执行文件),共有30个节头。
2. 节头
在这里插入图片描述

图5.3.1 节头
在这里插入图片描述

图5.3.2 节头
在节头中我们可以找到各个段的偏移量,从而得到载入虚拟内存后的地址。
3. 程序头
在这里插入图片描述

图5.4 程序头
4. 符号表

在这里插入图片描述

图5.5 符号表
5. Section to Segment mapping部分
在这里插入图片描述

图5.6 Section to Segment mapping
6. Dynamic section
在这里插入图片描述

图5.7 动态内存分配表
7. 重定位节
在这里插入图片描述
图5.8 重定位节

  1. 其他信息
    在这里插入图片描述

图5.9 其他信息
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照。
在这里插入图片描述

图5.10 edb加载hello
程序的起始虚拟地址位于0x401000。而.interp段地址从0x4002e0,偏移量为0x1e0,大小为0x1c,对齐要求为1。
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
在这里插入图片描述

图5.12 反汇编文件
hello.o没有加载虚拟内存,指令地址只含有偏移量,虚拟地址从0开始。hello已经完成了虚拟内存加载,地址从401000开始。hello中还包含了hello.o没有的.init节和.plt节。
结合分析可得hello重定位的过程:
1,重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
2,重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
3.重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。
链接的重定位过程说明:要合并相同的节,确定新节中所有定义符号在虚拟地址空间中的地址,还要对引用符号进行重定位(确定地址),修改.text节和.data节中对每个符号的引用(地址),而这些需要用到在.rel_data和.rel_text节中保存的重定位信息。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

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

-libc-2.27.so!__libc_csu_init

hello!_init

libc-2.27.so!_setjmp

libc-2.27.so!exit
5.7 Hello的动态链接分析
对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要为其添加重定位记录,并等待动态链接器处理。为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT和全局偏移量表GOT实现函数的动态链接。其中GOT 中存放函数目标地址,PLT使用 GOT中地址跳转到目标函数。
在这里插入图片描述

图5.13 动态链接偏移地址
调用_init函数后,从地址0x4008处,由原来的00 00 00 00 00 00 变为90 91 fb 2f 5d 7f;由原来的00 00 00 00 00 00变为20 2a fa 2f 5d 7f由于小端的缘故,则这两处的地址应该是0x7f 5d 2f fb 91 90 ,0x7f 5d 2f fa 2a 20。

在之后的函数调用时,首先跳转到PLT执行.plt中逻辑,第一次访问跳转时,GOT 地址为下一条指令,将函数序号压栈,然后跳转到PLT[0],在 PLT[0]中将重 定位表地址压栈,然后访问动态链接器,在动态链接器中使用函数序号和重定位 表确定函数运行时地址,重写 GOT,再将控制传递给目标函数。之后如果对同样函数调用,第一次访问跳转直接跳转到目标函数。
5.8 本章小结
本章介绍了链接的相关内容,熟悉了链接的指令,将.o文件链接为可执行文件。复习了edb的使用方法,分析了重定位的相关知识。

第6章 hello进程管理
6.1 进程的概念与作用
概念:进程的经典定义是一个执行中程序的实例,系统的每个程序都运行在某个进程的上下文。上下文是由程序正确运行所需的状态组成的,这个状态包括存放在内存里的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。
作用:通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:作为命令处理器,接受用户输入的命令,然后根据命令进行相关操作,比如调用相关的程序。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令。作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
处理流程:

  1. 从终端读入用户输入的命令
  2. 对命令解析,并判断其是否为内置命令
  3. 若是内置命令,则直接执行
  4. 若不是,则调用execve函数创建子进程运行
  5. 再判断是否为前台运行程序,若是,则调用等待函数,等待前台程序结束。否则程序转入后台,接受用户下一步输入的命令
  6. Shell接受键盘输入的信号,并且应该对信号产生相应的反应
  7. 回收僵死进程
    6.3 Hello的fork进程创建过程
    当输入一个非内置shell命令时,比如./hello,shell会将hello识别为可执行程序,因此会调用某个驻留在存储器中被称为加载器的操作系统代码来运行它。具体是父进程fork一个子进程,这个子进程就是目标程序,而子进程与父进程非常相似,但PID不同。
    在这里插入图片描述

图6.1 fork函数过程
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个新程序。
在这里插入图片描述

图6.2 execve
execve函数加载并运行可执行文件filename(hello),且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。

当加载器运行时,它创建一个内存映像。在程序头部表的引导下,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的堆和栈会被初始化为0,通过将虚拟地址空间的页映射到可执行文件的页大小的片,加载器从可执行目标文件中读入.init /.text/.rodata/.data/.bss。然后开始执行。
在这里插入图片描述

图6.3 进程内存空间

6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
多个流并发地执行的一般现象被称为并发。一个进程和其他进轮流运行的概念称为多任务。而上下文是内核重新启动一个被抢占的进程所需的状态,它右通用寄存器、浮点你寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
操作系统内核使用一种称为上下文切换的较高层次形式的异常控制流来实现多任务。在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决策叫做调度,是由内核中被称为调度器的代码处理的。
而一个进程执行它的控制流的一部分的每一时间段叫做时间片。
在这里插入图片描述

图6.4 上下文切换
处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核态(又是也叫超级用户态)。一个运行在内核态的进程可以执行指令集中的任何指令,并且可以访问系统的任何内存位置。

由图6.4可以看到,用户态和内核态直接是不断切换的,例如执行hello程序的时候,若在调用sleep函数之前,hello程序被抢占,就进行上下文切换,调用sleep时,进入内核态,处理器处理sleep并请求主动释放当前进程。之后计时器开始计时,内核进行上下文切换,执行其他的进程。当计时结束时,计时器发送一个中断信号,处理器处理中断信号,并进行上下文切换,重新回来执行hello进程。
6.6 hello的异常与信号处理
四类异常:中断,陷阱,故障,终止。
如果乱按且结尾没有回车,这些东西会被当做字符串缓存,而如果结尾是回车,它之前的信息会被当作输入的命令。
在这里插入图片描述

图6.5 回车
让内核发送一个SIGINT信号给到前台进程组中的每个进程,终止前台进程,即终止hello程序。
在这里插入图片描述

图6.7Ctrl+C
查看进程信息,发现hello还在后台。输入jobs后,看到进程停止。
在这里插入图片描述

图6.8 Ctrl+Z后jobs

pstree打印所有进程的关系。
在这里插入图片描述

图6.6 Ctrl+Z后pstree

使第一个后台作业变为前台,而第一个后台作业是hello,所以输入fg 后hello程序又在前台开始运行,并且是继续刚才的进程,输出剩下的信息。
在这里插入图片描述

图6.7 Ctrl+Z后fg
杀死进程。
在这里插入图片描述

图6.8 kill的用法

6.7本章小结
本章主要介绍了进程的相关概念以及操作,理解了shell的概念与作用,同时fork,execve函数以及上下文切换也都非常重要。在程序运行过程中,hello程序的终止与恢复受到命令控制,直到进程回收。

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:在有地址变换功能的计算机中,访问指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的物理地址。
线性地址:线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。
虚拟地址:CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。
物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址

7.2 Intel逻辑地址到线性地址的变换-段式管理
(逻辑地址由两部分组成:段标识符,段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号,剩下3位包含硬件信息。其中索引号可以直接理解成数组下标,它对应的“数组”就是段描述符表,段描述符具体描述了一个段地址,这样,很多段描述符就组成段描述符表。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。

给定一个完整的逻辑地址[段选择符:段内偏移地址],

看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。

拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,基地址就知道了。

把基地址 + 偏移量,就是要转换的线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
计算机会利用页表,通过MMU来完成从虚拟地址到物理地址的转换。而页表是一个页表条目(PTE)的数组,将虚拟页地址映射到物理页地址。

线性地址即虚拟地址,用VA来表示。VA被分为虚拟页号(VPN)与虚拟页偏移量(VPO),CPU取出虚拟页号,通过页表基址寄存器(PTBR)来定位页表条目,在有效位为1时,从页表条目中取出信息物理页号(PPN),通过将物理页号与虚拟页偏移量(VPO)结合,得到由物理地址(PPN)和物理页偏移量(PPO)组合的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:快表,其中每一行都保存着一个由单个PTE组成的块。通过这种方式我们可以再把VPN分成TLBT(TLB标记)和TLB索引(TLBI),根据索引和标记在TLB中寻找对应的PPN,TLB命中可以减少内存访问,就和之前的cache命中类似,这里少了行,也可以理解成一组只有一行,类似直接映射。
在这里插入图片描述

图7.1 TLB

多级页表:将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。
在这里插入图片描述

图7.2 多级页表

多级页表中,VPN被分为k个部分,第一级VPN结合基址寄存器得到一个页表条目,其中存放下一级页表的基址,再结合VPN2,得到第三级页表基址,继续寻找,以此类推,直到最后确定对应的物理页号,与VPO结合,得到由PPN与PPO结合成的物理地址,用于物理地址寻址。
7.5 三级Cache支持下的物理内存访问
收到虚拟地址之后,先在TLB中寻找,若不在TLB中,结合多级页表得到它的物理地址,然后到Cache里面寻找。若在TLB中,则直接被MMU得到。

在Cache中,命中就返回,不命中就依次在L1、L2、L3中不断寻找。
在这里插入图片描述

图7.3 Cache中访问流程
7.6 hello进程fork时的内存映射
调用fork函数时,会创建一个基本与父进程相同的子进程,并为子进程分配一个独立的PID。在这个过程中,内核创建了hello进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork 在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。同时,写时复制机制也会在其中一个进程写操作时创建新的页面,由此也就有了私有地址的概念。
7.7 hello进程execve时的内存映射
execve 函数在shell中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。而加载程序的步骤如下:

(1)删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构

(2)映射私有区域,为新程序的代码、数据、bss、和栈区域创建新的区域,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data、.bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。

(3)映射共享区域,hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。

(4)设置程序计数器(PC),execve做的最后一件事就是设置当前进程上下文的PC,使其指向代码区的入口。
7.8 缺页故障与缺页中断处理
缺页:虚拟内存中的字不在物理内存中(DRAM缓存不命中),通俗地讲就是内存中没有这个页,所以导致MMU找不到对应的物理地址。缺页故障是一种故障,当指令引用一个虚拟地址,MMU会查找页表,当找不到对应的物理地址时,就会触发缺页中断。
在这里插入图片描述

图7.4.1 缺页中断处理
当MMU触发缺页中断,缺页异常处理程序在内存中选择一个牺牲页,从其他存储器(磁盘)中读入这个页替换牺牲页,接着导致缺页的指令重新启动,页面命中。
在这里插入图片描述

图7.4.2 缺页中断处理
7.9动态存储分配管理
动态内存分配器维护者进程的虚拟内存区域,堆。分配器将堆视为一组大小不同的块的集合。空闲的块被分配后显式的保留为已分配状态,直到被显式释放或程序结束才被释放空间。
(1) 隐式空闲链表
带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的。
在这里插入图片描述

图7.5 隐式空闲链表
在隐式空闲链表中,因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。其中,一个设置了已分配的位而大小为零的终止头部将作为特殊标记的结束块。
(2) 显式空闲链表
显式空闲链表是将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。如,堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。
7.10本章小结
本章主要介绍了内存管理的的相关知识,包括了虚拟地址空间,动态内存映射,地址翻译等。同时在Intel环境下,段式管理和页式管理、fork和exceve的内存映射也同样重要,还有缺页故障和缺页中断管理机制,以及如何根据缓存或页表寻找物理內存。

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而文件就是一个字节序列。所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
接口作用:Unix IO接口能够将设备映射成文件,从而统一输入输出。
函数:
打开文件:应用程序通过open函数要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。

关闭文件:当应用完成了对文件的访问之后,它就使用close函数通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的而文件,当k>=m时执行读操作会触发一个成为end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF符号”。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。分别通过read、write操作执行。
改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行lseek,显式地将改变当前文件位置k。
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字符串,传入vsprintf。vsprintf接受确定的输出格式字符串fmt,对参数格式化,产生格式化输出,调用write系统函数输出到屏幕。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
用户输入命令后,getchar从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章了解了Linux下IO设备的管理方法,IO接口及其函数,对于printf和getchar函数有了更深刻的理解。

结论

hello.c文件经过预处理、编译、汇编、链接后生成可执行文件hello,由shell执行hello的过程也就是From Program to Process,P2P。代码逐渐转换成机器语言,不同格式的转换,执行文件内容的补充,包括elf文件的内容产生都在整个实验中得到分析和体现。
我通过对比不同文件之间的差异分析出每项工作的作用,即使是一个汇编程序的反汇编文件也存在差异包括最明显的机器码和偏移量的地址存储方式。而在进程的运行过程中,进程不总是能够顺利的运行,我根据各种程序运行过程中可能出现的情况下达指令,能够发现进程的状态并改变他的状态。有利于今后在程序运行时排除纰漏,保证程序顺利运行。
在内存管理中,虚拟地址帮助我们更好的管理内存空间,MMU进行VA与PA的翻译。SRAM的读写速度约是DRAM的10倍,而DRAM约是磁盘的100000倍,所以DRAM的未命中代价很大,需要磁盘补偿。通过虚拟空间映射,不同的进程之间可以共用物理地址。同时,动态内存分配为用户的内存空间利用提高效率。
最后在第八章中,讲到文件读写,进程是如何读写文件,包括数据格式、文件EOF等等的问题下,程序是如何应对异常的。还剖析了printf函数的函数体,里面由vsprintf进行格式化,还有write系统函数为显示器呈现信息提供指令。
计算机的硬件与操作系统之间的联系密不可分,我们先了解操作系统的命令目的以认识硬件的工作方式,然后再通过解析硬件工作的方法与过程反推出进程可能面临的问题、隐患和提升空间。

附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i 预处理文件
hello.s 编译文件
hello.o 汇编文件
hello.out 链接文件
hello 可执行文件
asm.txt hello.o的反汇编文件
hello_elf.txt hello.o的elf文件
hello2elf.txt hello.out的elf文件
hello_dis hello的反汇编文件

参考文献

[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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值