HIT hello的一生---2021大作业

程序人生-Hello’s P2P

摘要

   本文以hello.c程序为源程序,首先对该程序进行预处理,得到hello.i,之后对得到的文件进行编译,得到汇编文件hello.s,再之后对得到的文件进行汇编,得到可重定位目标文件hello.o,最终通过与其他文件链接,得到一个可执行文件hello。在得到这些文件之后,分别对其内容进行了分析。
   得到可执行文件之后,在linux系统下进行进程管理分析,包括进程的执行过程与信号的异常处理。另外,通过了解虚拟内存的概念,也在本文中描述了程序是如何在内存中存储的,比如通过线性地址到物理地址的变换,比如通过cache高速缓存机制。存储时也会发生缺页故障,本文也描述了缺页中断处理操作与动态存储分配。
   IO管理也是必不可少的。本文在最后一章中描述了Linux的IO设备管理方法,包括文件的不同类型以及unix io接口的操作,还描述了io接口的不同函数、printf函数的实现以及getchar函数的实现。
关键词:链接;操作系统; IO管理;汇编语言;shell;程序的生命周期;进程;

第1章:概述

1.1 HELLO简介

1.P2P (From Program to Process)
  首先创建一个hello.c文件(Program),依次对该文件进行预处理(hello.i)、编译(hello.s)、汇编(hello.o)、链接(hello),形成一个可执行文件(二进制文件)。得到可执行文件之后,OS(进程管理)通过shell新建一个进程,还可以通过fork函数创建一个子进程,分配相应的内存资源,使用execve函数加载进程,完成P2P过程。
2.020 (From Zero-0 to Zero-0)
  计算机存储结构层层递进,下一级作为上一级的缓存,并通过cache加快传输数据的速度。对hello的数据进行处理时,将数据从磁盘加载到CPU寄存器。处理hello时,通过TLB、分级页表等机制,将程序映射到虚拟内存,在开始运行时载入物理内存,多样的信号处理机制能应对程序产生异常的情况。操作系统将IO设备都抽象成了文件,实现程序能够间接调用硬件完成输入输出。hello执行完成后shell回收hello进程,并且内核会从系统中删除hello的所有痕迹,实现020过程。

1.2 环境与工具

一.硬件环境:
   X64 CPU; 2.60GHz; 8.00GB RAM; 476.81GB Disk
二.软件环境:
   Windows 10 64位操作系统; Vmware 15.0.4; Ubuntu 18.01
三.开发工具:
   Codeblocks 17.12; vim/gcc/g++;objdump;gdb;edb;hexedit

1.3 中间结果

1.hello.i:hello.c预处理之后的文本文件
2.hello.s:hello.i编译之后得到的汇编文件(文本文件)
3.hello.o:hello.s汇编之后得到的可重定位目标文件(二进制文件)
4.hello1:用于测试sleepsecs的可执行目标文件
5.helloo.elf:可重定位目标文件的elf文件
6.hello:hello.c的可执行目标文件
7.hello.elf:可执行目标文件的elf文件
8.helloosection.txt:可重定位目标文件的重定位信息(链接的重定位过程分析)
9.hellosection.txt:可执行目标文件的重定位信息(链接的重定位过程分析)

1.4 本章小结

   本章是对这个大作业的一个概述,首先描述了对这个大作业的理解,即hello简介,然后描述了完成作业需要的环境要求,在完成大作业后,将所输出的中间结果的文件完整列出。

第2章 预处理

2.1 预处理的概念与作用

1.概念:在编译之前的命令,在C/C++中处理源文件中以”#”开头的预编译命令。
2.作用:
(1)删除”#define”并展开所定义的宏
(2)处理所有条件预编译指令,如”#if”, “#ifdef”, “#endif”等
(3)插入头文件到”#include”处,可以递归方式进行处理
(4)删除所有的注释”//”和”/* */”
(5)添加行号和文件名标识,以便编译时编译器产生调试用的行号信息
(6)保留所有”pragma”编译指令
经过预编译处理后,得到的是预处理文件(如hello.i),文件还是一个可读的文本文件,但不包含任何宏定义

2.2在UBUNTU下预处理的命令

1.利用gcc -m64 -no-pie -fno-PIC -E hello.c > hello.i生成预处理文件hello.i

在这里插入图片描述

2.生成hello.i文件
在这里插入图片描述

部分文件内容:

在这里插入图片描述

2.3 HELLO的预处理结果解析

hello.c头文件如下:
在这里插入图片描述

hello.i文件较长,共3105行,存储了头文件中的所有内容
比如下面截取了部分库在计算机中的存储位置
在这里插入图片描述

还有对一些数据结构和函数的声明:
在这里插入图片描述

2.4 本章小结

   本章了解了预处理的概念及作用,能够运用gcc -E的指令生成xxx.o预处理文件,并能直观了解预处理文件的内容,为下一步编译做好准备

第3章 编译

3.1 编译的概念与作用

1.概念:编译是利用编译程序从源语言编写的源程序产生目标程序的过程,一般通过编译程序来实现。将某一种程序设计语言写的程序翻译成等价的另一种语言的程序的程序,称之为编译程序。
2.作用:编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
(1) 词法分析
   词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。执行词法分析的程序称为词法分析程序或扫描器。
(2) 语法分析
   编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,如表达式、赋值、循环等,最后看是否构成一个符合要求的程序,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构,程序是最终的一个语法单位。编译程序的语法规则可用上下文无关文法来刻画。
(3) 中间代码
   中间代码是源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码,即为中间语言程序。
(4) 代码优化
   代码优化是指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。所谓等价,是指不改变程序的运行结果。所谓有效,主要指目标代码运行时间较短,以及占用的存储空间较小。这种变换称为优化。
(5) 目标代码
   目标代码生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。

3.2 在UBUNTU下编译的命令

1.用gcc -m64 -no-pie -fno-PIC -S hello.i > hello.s
在这里插入图片描述

2.生成hello.s文件
在这里插入图片描述

3.文件部分内容:
在这里插入图片描述

3.3 HELLO的编译结果解析

3.3.1 数据

1.全局变量:int sleepsecs = 2.5
在这里插入图片描述

   已初始化的全局变量sleepsecs,在.text节中声明为全局变量,在.data节,设置对齐方式是4个字节,类型是“object”,大小占4个字节。
输出sleepsecs发现值为2,在文件中存在隐式的类型转换
在这里插入图片描述

2.局部变量:int i;
局部变量分配在栈中,本题位于-4(%rbp),先将i赋值为零,之后每次循环与9作比较
在这里插入图片描述

3.main函数参数:int main(int argc,char *argv[])
在这里插入图片描述

   argc占4个字节,所以是movl,argv[]是指针,占8个字节,所以是movq,并且下一步是将argc与3进行比较
4.数组:argv[]
   argv[]作为main函数的第二个参数传进去,被分配在-32(%rbp)位置处,argv指针指向一段连续的、已经被分配好的内存空间
在这里插入图片描述

对printf语句的调用如下,连续两次调用argv数组,并用%rax保存两次调用的值
在这里插入图片描述

5.字符串:
第一个字符串输出,汇编代码如下:
在这里插入图片描述

可以看出,汉字在Linux下使用UTF-8编码格式,每个汉字占3个字节
在这里插入图片描述

第二个字符串输出,声明在main函数中
在这里插入图片描述

3.3.2 赋值

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

   已初始化的全局变量sleepsecs位于.data节,未初始化的全局变量位于.bss节,且不占任何实际磁盘空间
2.局部变量:for(i=0;i<10;i++)
在这里插入图片描述

通过mov指令进行赋值操作,根据不同数据类型增添不同后缀

3.3.3 关系操作

1.不等于:if(argc!=3)
在这里插入图片描述

2.小于:for(i=0;i<10;i++)
在这里插入图片描述

关系操作通过cmp指令实现,得到的结果一般与跳转指令相结合,跳转到相应的位置

3.3.4 算术操作

自增操作:i++
通过add指令可以实现加1操作

3.3.5 函数操作

(1)函数传递:
int main(int argc, char *argv[]),argc存储在%edi,argv[]存储在%rsi,局部变量存放在栈中,所以刚开始将%rsp-32,分配32个字节
在这里插入图片描述

(2)函数调用:
通过call指令调用函数,puts()、exit()、printf()、sleep()、main()、getchar(),将PC设置为要跳转的地址,将当前地址的下一行地址作为返回地址压入栈中
在这里插入图片描述

(3)函数返回
通过leave和ret指令结束函数,实现return 0:释放分配的栈空间,并弹出调用函数%rbp值作为PC地址,进行跳转,结束调用函数
在这里插入图片描述

3.4 本章小结

   本章了解了编译的概念和作用,在Ubuntu下实现编译操作,并得到编译文件hello.s,分析hello.s文件了解C语言数据与操作是如何在汇编语言中实现的,比如不同类型的数据、赋值、类型转换、算术操作、关系操作、函数操作、数组、控制转移是如何在汇编语言中实现的。

第4章 汇编

4.1 汇编的概念与作用

1.概念:
   汇编语言, 即第二代计算机语言,用一些容易理解和记忆的字母,单词来代替一个特定的指令,比如:用“ADD”代表数字逻辑上的加减,“MOV”代表数据传递等等。
汇编代码文件(由汇编指令构成)称为汇编语言源程序。汇编程序用来将汇编语言源程序转换为机器指令序列。汇编指令和机器指令一一对应,前者是后者的符号表示。
2.作用:
   比起机器语言,汇编语言具有更高的机器相关性,更加便于记忆和书写,但又同时保留了机器语言高速度和高效率的特点。汇编语言仍是面向机器的语言,很难从其代码上理解程序设计意图,设计出来的程序不易被移植,故不像其他大多数的高级计算机语言一样被广泛应用。所以在高级语言高度发展的今天,它通常被用在底层,通常是程序优化或硬件操作的场合。

4.2 在UBUNTU下汇编的命令

通过gcc -m64 -no-pie -fno-PIC -c hello.s > hello.o
在这里插入图片描述

生成hello.o文件

4.3 可重定位目标ELF格式

通过readelf -a hello.o > helloo.elf生成包含ELF头文件和节头表的文件helloo.elf

  1. ELF头信息格式
    在这里插入图片描述

(1)Magic:ELF文件的魔数,加载或读取文件时,可用魔数确认文件类型是否正确
(2)Class:ELF版本
(3)Data:2进制补码,小端法
(4)Type:REL可重定位目标文件
(5)没有程序头表,且虚拟内存从0开始存储
(6)节头表大小为64*13

2.节头表
在这里插入图片描述
(1)Address:可重定位目标文件中,每个可装入节的起始地址总是0
(2)Offset:相对于起始位置的偏移,对于.bss节无意义
(3)Align:节的对齐要求
(4)Flags:节标志,该节在虚拟空间中的访问属性
3…rela.text节重定位节
该节中存储了重定位的相关信息
在这里插入图片描述

4.4 HELLO.O的结果解析

利用objdump -d -r hello.o分析反汇编代码
得到反汇编
在这里插入图片描述

分析:
1.两种基本的重定位类型
R_X86_64_32 绝对地址
R_X86_64_PC32 PC相对地址
2.分支转移:
   不再使用段名称如.L2,而是具体地址,重定位前的序号代表从第几个字节开始被重定位,比如下图是从第16个字节开始重定位
在这里插入图片描述

   下图是hello.s的文件信息,能够看出左边没有数字序列,hello.o冒号左边数字代表运行时机器指令的位置,冒号右边的数字序列代表每一行汇编语句代表的机器指令
在这里插入图片描述

3.函数调用call指令
   在hello.s文件中call指令后直接接函数名称,因为编译成汇编语言时,是不确定函数的调用位置的;而在hello.o文件中,call后面接的是下一条指令的地址,因为在hello.c中调用的函数都是共享库的函数,需要通过链接才能确定最终地址。

4.5 本章小结

   本章中了解了汇编的概念与作用,在Ubuntu中通过gcc指令生成汇编文件,另外生成了可重定位elf文件,了解elf头文件信息、节头表、重定位表各个表项代表的含义,分析了hello.o与hello.s文件的不同,找出汇编语言与机器语言的映射关系,因为是一一对应的,但还是在某些地方有所不同,这些都进行了一定的了解。

第5章 链接

5.1 链接的概念与作用

1.概念:在构建大型程序的时候,为了方便代码管理,会根据不同的功能把代码分为多个片段(或模块)并存储在不同的文件中,在代码执行时需要把这些代码模块合并成一个单一的可执行文件,这个合并过程叫链接
2.作用:将多个可重定位目标文件合并生成可执行目标文件

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
在这里插入图片描述得到hello文件

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

生成hello.elf文件:通过readelf -a hello > hello.elf指令,生成hello.elf文件
分析文件内容
1.ELF头信息表
在这里插入图片描述

信息与可重定位目标文件的ELF头信息表基本相同,有几处不同
(1)Type:EXEC代表可执行文件
(2)头文件入口地址不为零
(3)有程序头表

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
2.节头表:
在这里插入图片描述

基本信息与可重定位目标文件中的节头表基本相同

5.4 HELLO的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,能够从Data Dump中看出hello的起始地址为0x00400000,结束地址为0x00400ff0
在这里插入图片描述

分析程序头表:
在这里插入图片描述

(1)代码段
第一个LOAD是只读代码段
第二个LOAD是读写代码段
(2)PHDR程序头表

5.5 链接的重定位过程分析

利用objdump -d -r hello命令生成两个文件,便于比较
在这里插入图片描述

分析hello与hello.o的不同
(1)hello以init节开始
在这里插入图片描述

(2)hello中多了不同函数的汇编代码
在这里插入图片描述

(3)hello地址为真实储存地址
hello.o中main初始地址显示为0,因为是虚拟内存首地址
(4)函数跳转指令
hello中是call+首地址调用该函数
在这里插入图片描述

hello.o是call+main偏移量
在这里插入图片描述

(5)重定位信息
hello.o文件15、1a、24所在行后面的零都是需要重定位的信息,在没有重定位之前,后面都初始化为0
在这里插入图片描述

重定位之后,hello文件中能够看出bf后面四个字节、e8后面四个字节都是重定位后的信息,通过公式转移目标地址=PC+偏移地址进行计算
在这里插入图片描述

5.6 HELLO的执行流程

程序名称
载入:
_dl_start
_dl_init
开始执行:
_start
_libc_start_main
_init
执行main:
_main
_printf
_exit
_sleep
_getchar
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
退出:
exit

5.7 HELLO的动态链接分析

   在edb调试之后我们发现原先0x00600a10开始的global_offset表是全0的状态,在执行过_dl_init之后被赋上了相应的偏移量的值。这说明dl_init操作是给程序赋上当前执行的内存地址偏移量,这是初始化hello程序的一步。
   分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

5.8 本章小结

   本章介绍了链接的概念和作用,在Ubuntu下生成链接文件,通过gcc命令生成ELF可执行目标文件,了解了ELF头信息、节头表、程序头表等各项代表的含义,了解虚拟空间的大小,分析了重定位前后文件的变化,函数的执行过程与链接过程

第6章 HELLO进程管理

6.1 进程的概念与作用

1.概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
2.作用:在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

6.2 简述壳SHELL-BASH的作用与处理流程

1.Shell:一般我们是用图形界面和命令去控制计算机,真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),由于安全、复杂、繁琐等原因,用户不能直接接触内核,需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核,内核和用户之间就多了一层“中间代理”,Shell 其实就是一种脚本语言,也是一个可以用来连接内核和用户的软件,我们编写完源码后不用编译,直接运行源码即可。
2.常用的Shell:bash由 GNU 组织开发,sh 是 UNIX 上的标准 shell,是第一个流行的 Shell,bash保持了对 sh shell 的兼容性,是各种 Linux 发行版默认配置的 shell。现在sh 已经基本被 bash 代替,bash是sh的扩展补充,但是也有些是不兼容的,大多数情况下区别不大,特殊场景可以使用 bash 代替 sh。
3.处理流程:
(1)读取用户的输入
(2)分析输入内容,获得输入参数
(3)如果是内核命令则直接执行,否则调用相应的程序执行命令
(4)在程序运行期间,shell需要监视键盘的输入内容,并且做出相应的反应

6.3 HELLO的FORK进程创建过程

   当在shell中输入一条命令时,shell会判断是否是内核命令,如果是内核命令则直接执行,否则shell进程会以自己为模板(即父进程),创建(fork)一个新的进程。这个新建的进程对shell中输入的命令进行处理,即调用exec()这一系统调用来执行shell中输入的命令,处理完之后新进程结束自己的生命,等待shell进程进行回收。
   新创建的父进程和子进程内容相同,虚拟地址相同,但是是完全独立的,并且拥有不同的PID。

6.4 HELLO的EXECVE过程

(1)通过pid=fork()函数创建一个新进程后,判断pid的值,若pid=0,则该进程为新创建的子进程
(2)子进程通过系统调用execve()函数将新程序加载到子进程的内存空间中,execve()函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp
(3)只有当出现错误时,例如找不到filename,execve才会返回到调用程序;一般情况下,execve调用一次并从不返回,加载filename后,调用启动代码,启动代码设置栈,并将控制传递给新程序的主函数,主函数有如下原型:
int main(int argc, char **argv, char **envp);
(4)当main开始执行时,用户栈的组织结构如下:
在这里插入图片描述

举例来说:
   在shell(shell也是一个进程)中执行最简单的HelloWorld程序。它也是首先调用execve()这个系统调用的,下面用strace跟踪下执行HelloWorld的过程,我们可以看到第一步执行的是execve()。

6.5 HELLO的进程执行

1.基本概念:
(1)并发:多个流并发地执行的一般现象
(2)多任务(时间分片):一个进程和其它进程轮流运行的概念
(3)上下文:内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态
(4)调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程
2.如下程序进行上下文切换,其中sleepsecs=2.5

   sleep函数的作用是将一个进程挂起一段指定的时间,如果请求的时间量已经到了,sleep返回零。
在这里插入图片描述

6.6 HELLO的异常与信号处理

1.ctrl+Z:
   输入ctrl+Z后,会发送一个SIGTSTP信号到前台进程组中的每个进程,默认情况下,是停止(挂起)前台作业。在图中能看到还有hello进程未结束。
在这里插入图片描述

2.fg命令:
   fg命令是让挂起的程序继续运行,下图能看出在输出6次字符串后输入ctrl+z让程序挂起,输入fg后继续运行输出十次结束
在这里插入图片描述

3.ctrl+C
   输入ctrl+C会导致内核发送一个SIGINT信号到前台进程组中的每个进程,默认情况下,是终止前台作业。下图通过ps能看到hello进程已经结束。
在这里插入图片描述

4.jobs
   jobs 命令可以用来查看当前终端放入后台的工作,工作管理的名字也来源于 jobs 命令。下图通过jobs能看到被挂起的程序。
在这里插入图片描述

5.pstree:进程树,把各个进程通过树型连接起来
在这里插入图片描述

6.kill命令:
   通过ps查看进程号为2778的进程未终止,kill -s向固定进程发送信号,比如下图24信号SIGXCPU,代表CPU时间限制超出,fg继续运行输出错误信息。
在这里插入图片描述

6.7本章小结

   在本节中,了解了进程的概念和作用,能运用fork创建进程,execve执行进程,了解了不同信号的作用,并能在linux下使用,能通过运用不同的命令了解前台作业和后台作业的基本信息。

第7章 HELLO的存储管理

7.1 HELLO的存储器地址空间

1.物理地址:用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚按发送到内存总线上的电信号相对应。物理地址由32位或36位无符号整数表示,也可以简单的理解为实际上内存上的地址。
2.虚拟地址/线性地址:虚拟地址:虚拟地址并不真实存在于计算机中。每个进程都分配有自己的虚拟空间,而且只能访问自己被分配使用的空间。理论上,虚拟空间受物理内存大小的限制,如给有4GB内存,那么虚拟地址空间的地址范围就应该是0x00000000~0xFFFFFFFF。每个进程都有自己独立的虚拟地址空间。这样每个进程都能访问自己的地址空间,这样做到了有效的隔离。
3.逻辑地址:包含在机器语言指令中用来指定一个操作数或一条指令的地址,这种寻址方式在80x86著名的分段结构中表现得尤为具体,它促使windows程序员把程序分成若干段。每个逻辑地址都由一个段和偏移量组成,偏移量指明了从段开始的地方到实际地址之间的距离。例如我们常说的结构体中某个参数的地址其实就相当于:结构体首地址 + 偏移量。逻辑地址是相对于应用程序而言的。

7.2 INTEL逻辑地址到线性地址的变换-段式管理

   逻辑地址是程序源码编译后所形成的跟实际内存没有直接联系的地址,即在不同的机器上,使用相同的编译器来编译同一个源程序,则其逻辑地址是相同的,但是相同的逻辑地址,在不同的机器上运行,其生成的线性地址又不相同,因为把逻辑地址转换成线性地址的公式是:线性地址=段基址*16+偏移的逻辑地址,而段基址由于不同的机器其任务不同,其所分配的段基址(线性地址)也会不相同,因此,其线性地址会不同。
   即使,对于转换后线性地址相同的逻辑地址,也因为在不同的任务中,而不同的任务有不同的页目录表和页表把线性地址转换成物理地址,因此,也不会有相同的物理地址冲突。
   注意的是,源码编译后生成的地址,只是偏移的地址,而形成逻辑地址的[段基址:偏移地址]中的段基址,是在生成任务时才定下来的,也就是说,[段基址:偏移地址]只有在进程中才会用到,在程序中只有偏移地址的概念。

7.3 HELLO的线性地址到物理地址的变换-页式管理

1.基本概念
(1)线性地址:是进程使用的地址,虚拟的地址。人为抽象出一大片地址空间给进程使用,为了方便32位地址总线存取,linux内核定义为了4G。
(2)物理地址:是采用32位总线存取物理内存某个字节时,地址总线上电位的高低。
分页单元将线性地址转换成物理地址。
2.两种变换:
CPU通过地址来访问内存中的单元,地址有虚拟地址和物理地址之分,如果CPU没有MMU,或者有MMU但没有启用,CPU核在取指令或访问内存时发出的地址将直接传到CPU芯片的外部地址引脚上,直接被内存芯片接收,这称为物理地址.

如果CPU启用了MMU,CPU核发出的地址将被MMU截获,从CPU到MMU的地址称为虚拟地址,而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址,如下图所示。,利用TLB加速地址翻译,TLB是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。

3.具体过程:
将虚拟地址分为VPN(虚拟页号)和VPO(虚拟页偏移),TLB是利用VPN的位进行虚拟寻址的,VPN分为TLBI(组索引)和TLBT(标记)。将物理地址分为PPN(物理页面)和PPO(物理页偏移),然后再进行划分,分为CO(块偏移)、CI(组索引)、CT(标记)。
VPO与PPO相对应,VPN对应页表中的某一项,通过PTE查找到对应的PPN,将PPN和PPO连在一起即为物理地址。

7.4 TLB与四级页表支持下的VA到PA的变换

   一般来讲,能通过VPN中TLB找到PPN是最好的,但也可能出现缺页的情况,在TLB中无法找到对应的PPN,则要访问内存,在页表中寻找PPN。
   将虚拟地址划分为4个VPN和1个VPO,每个VPN i都是到第i级页表的索引,为了构造物理地址,在能够确定PPN之前,MMU必须访问4个PTE,和只有一级到页表结构一样,PPO和VPO是相同的。
在这里插入图片描述

7.5 三级CACHE支持下的物理内存访问

   将物理地址分为三部分,分为CO(块偏移)、CI(组索引)、CT(标记),根据CI找到组索引,找到标记相同的位置,最后通过块偏移找到对应位置。若未找到,则到二级或三级cache下寻找。

7.6 HELLO进程FORK时的内存映射

1.基本概念:
(1)mm_struct(内存描述符):描述了一个进程的整个虚拟内存空间
(2)vm_area_struct(区域结构描述符):描述了进程的虚拟内存空间的一个区间
2.映射过程:
(1)当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID
(2)创建当前进程的mm_struct,vm_area_struct和页表的原样副本
(3)将两个进程的每个页面都标记为只读页面
(4)两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制。

7.7 HELLO进程EXECVE时的内存映射

1.加载并运行程序步骤:
(1)删除已存在的用户区域
(2)映射私有区域:为新程序的代码、数据、bss和 栈区域创建新的区域结构。
(3)映射共享区域
(4)设置程序计数器PC
2.加载器映射用户地址空间区域:
在这里插入图片描述

7.8 缺页故障与缺页中断处理

1.缺页故障:
(1)段错误:首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)
(2)非法访问:接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。
(3)如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。

2.缺页处理:

  1. 处理器将虚拟地址发送给 MMU
    2-3) MMU 使用内存中的页表生成PTE地址
  2. 有效位为零, 因此 MMU 触发缺页异常
  3. 缺页处理程序确定物理内存中牺牲页 (若页面被修改,则换出到磁盘)
  4. 缺页处理程序调入新的页面,并更新内存中的PTE
  5. 缺页处理程序返回到原来进程,再次执行缺页的指令

7.9动态存储分配管理

1.程序员使用动态内存分配器(比如malloc)获得虚拟内存,动态内存分配器维护着一个进程的虚拟内存区域,称为堆

2.分配器
(1)概念:分配器将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的。
(2)分配器的类型
a.显式分配器: 要求应用显式地释放任何已分配的快
例如,C语言中的 malloc 和 free
b.隐式分配器: 应用检测到已分配块不再被程序所使用,就释放这个块
比如Java,ML和Lisp等高级语言中的垃圾收集 (garbage collection)
3.malloc程序包
#include <stdlib.h>
(1)void *malloc(size_t size)
1)成功:返回已分配块的指针,块大小至少 size 字节,对齐方式依赖编译模式:8字节(32位模式),16字节(64位模式)
If size == 0, returns NULL
2)出错: 返回 NULL (0) ,同时设置 errno
(2)void free(void *p)
将p指向的块返回到可用内存池,p 必须 malloc、 realloc或calloc已分配块的起始地址
(3)Other functions
calloc: malloc的另一版本,将已分配块初始化为0 .
realloc: 改变之前分配块的大小.
sbrk: 分配器隐含地扩展或收缩堆
4.记录空闲块方法:
(1)隐式空闲链表:通过头部中的大小字段—隐含地连接所有块

(2)显式空闲链表:在空闲块中使用指针

(3)分离空闲链表:按照大小分类,构成不同大小的空闲链表
(4)块按大小排序:在每个空闲块中使用一个带指针的平衡树,并使用长度作为权值

7.10本章小结

   通过本章的学习,了解了虚拟地址、物理地址、线性地址、逻辑地址的概念,也了解了地址之间的转化关系,比如从虚拟地址转换到物理地址是需要通过地址翻译机制实现的。学习内存映射,掌握fork函数、execve函数的内存映射。还学习了缺页的种类与处理步骤,以及处理空闲块的方法。

第8章 HELLO的IO管理

8.1 LINUX的IO设备管理方法

1.设备的模型化:文件
文件类型:
(1)普通文件:包含任意数据,应用程序通常包含文本文件和二进制文件。
(2)目录:包含一组链接的文件,其中每个链接都将一个文件名映射到一个文件,这个文件可能是另一个目录
(3)套接字:用来与另一个进程进行跨网络通信的文件
(4)命名通道
(5)符号链接
(6)字符和块设备
2.设备管理:unix io接口
(1)打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个IO设备。Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)、标准错误(描述符为2)
(2)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量。
(3)读写文件:读操作是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。写操作是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
(4)关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件,作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。

8.2 简述UNIX IO接口及其函数

一.打开和关闭文件
1.open()函数:打开一个已存在的文件或者创建一个新文件
   将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符。若出错,则返回-1。flags参数指明了进程打算如何访问这个文件。mode参数制定了新文件的访问权限位。
在这里插入图片描述

2.close()函数:关闭一个已打开的文件,若关闭一个已关闭的描述符会出错,返回-1.
在这里插入图片描述

二.读和写文件
1.read()函数:若成功则为读的字节数,若EOF则为0,若出错为-1
read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。
在这里插入图片描述

2.write()函数:若成功则为写的字节数,若出错则为-1
write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
在这里插入图片描述

三.改变当前文件位置
调用lseek函数改变当前文件位置

8.3 PRINTF的实现分析

一.printf函数体内容:
在这里插入图片描述

二.分析函数体:
1.形参列表中的“…”:可变参数的写法,当传递参数的个数不确定时,就用这种方式表示。
2.va_list arg = (va_list)((char*)(&fmt) + 4);
(1)va_list:是一个字符指针
(2)((char*)(&fmt) + 4):表示的是…中的第一个参数
fmt是一个指针,指向第一个const参数(const char fmt)的第一个元素,它是在栈上进行分配的,有地址。
char
类型的变量,入栈的是指针而不是该变量,所以得到的也是一个固定的值
所以将得到的值转换成va_list类型可以得到第一个参数的地址。
3. i = vsprintf(buf, fmt, arg);
(1)vsprintf函数原型:

(2)函数功能:接受确定输出格式的格式字符串fmt,用格式字符串对个数变化的参数进行格式化,产生格式化输出。
4. write(buf, i);
函数作用:将buf中的i个元素的值写到终端
三.printf的运行过程:
   从vsprintf生成显示信息,显示信息传送到write系统函数,write函数陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序,从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 GETCHAR的实现分析

1.getchar()函数原型:
在这里插入图片描述

2.函数分析:
n = read(0, buf, BUFSIZ);
getchar调用read函数,将整个缓冲区都读到了buf里面,返回值是缓冲区的长度。函数实现时,只有当buf长度为零,getchar才会调用read函数,否则是直接将保存的buf中的最前面的元素返回。
3.异步异常-键盘中断的处理:
   键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

   本章描述了Linux的IO设备管理方法,包括文件的不同类型以及unix io接口的作用,还描述了unix io接口及不同函数的作用。另外,分析了printf和getchar函数的具体实现过程。

结论

   终于深刻地了解hello程序了!
   回顾hello的一生,从hello.c开始出发,进行预处理,处理了文件的“头”,进行编译,将文件用生动形象的汇编语言进行翻译,进行汇编,将文件翻译成枯燥无味的机器代码,进行链接,将不同文件“紧紧”链接在一起,得到一个可执行文件。但是一生才刚刚开始,它还没有发挥它的作用。我们运行它,为它创建“儿子”(子进程),对它轮番轰炸,进行各种花式操作,比如发送kill或pause信号,比如用字符串充满缓冲区,比如按ctrl+c或ctrl+z。最终它的进程被清除,它也完成了它的使命。我们为了充分了解它,还要分析它的内存空间和IO文件管理。多亏了它,我们才能有这样一份独一无二的大作业,复习课内的知识,以及掌握很多额外的知识。
   回顾这一门课程,是真的感叹csapp这本大黑书写的很好,并且通过完成hello的一生大作业,我能再次回顾课程的知识点,进行查缺补漏。另外,也掌握了一些调试器的使用方法,比如gdb、edb等,这些都是分析程序必不可少的工具。

附件

1.hello.i:hello.c预处理之后的文本文件
2.hello.s:hello.i编译之后得到的汇编文件(文本文件)
3.hello.o:hello.s汇编之后得到的可重定位目标文件(二进制文件)
4.hello1:用于测试sleepsecs的可执行目标文件
5.helloo.elf:可重定位目标文件的elf文件
6.hello:hello.c的可执行目标文件
7.hello.elf:可执行目标文件的elf文件
8.helloosection.txt:可重定位目标文件的重定位信息(链接的重定位过程分析)
9.hellosection.txt:可执行目标文件的重定位信息(链接的重定位过程分析)

参考文献

[1] https://www.cnblogs.com/pianist/p/3315801.html
[2] http://c.biancheng.net/view/3210.html
[3] https://blog.csdn.net/liugaigai427/article/details/86742062

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值