【无标题】

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业    计算机科学与技术    

学     号      ****       

班   级        ****        

学       生         ***        

指 导 教 师        郑贵滨           

计算机科学与技术学院

2021年5月

摘  要

HelloWorld是每一个程序员梦开始的地方,而这篇文章就跟踪采访了Hello的程序人生。Hello从最开始的C语言源代码,会先经过他人生的第一步——预处理;接着会继续变化,从一个青涩的.i文件变化成更能让机器理解的.s汇编文件;随着Hello的一步一步成长,他会经过汇编、链接等一系列的动作处理,变成一个可执行文件。这也标志着它即将迈入机生的一个新阶段。

在下一个阶段中,它会和操作系统进行交谈,操作系统像它的伯乐一样,给他开辟进程,提供虚拟内存和独立的地址空间;给它划分时间片、逻辑控制流来让它操作系统上畅游,最后随着进程的结束,停止这短暂而辉煌的机生。

本文从一个hello.c源代码开始,跟随hello的脚步,详细说明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简介

P2P是指from program to process,对原程序进行包括预处理(.c文件 -> .i文件),汇编(.i文件 -> .s文件),链接(.s文件 -> .o文件)的一系列操作后,形成可执行文件。执行该文件时,OS为该文件fork产生进程(process)。

020是指from zero to zero。程序开始执行后,OS为其映射到虚拟内存,执行目标代码,然后mmap分配时间片,最终在硬件上实现。实现后由内核使内存等恢复到程序执行前的状态,即020。

1.2 环境与工具

硬件:Intel®Core™ i5-7200U CPU @ 2.50GHz (4 CPUs), ~2.7GHz
软件:Ubuntu20.04.1 LTS
开发和调试工具:gcc, gdb, as, ld, readelf等

1.3 中间结果

Hello.c: 源文件
Hello.i: 预处理输出文件
Hello.s: 编译输出文件
Hello.o:汇编输出文件
Hello:链接输出文件

1.4 本章小结

本章简要介绍了文章的创作环境,以及过程文件。


第2章 预处理

2.1 预处理的概念与作用

概念:在编译之前进行的处理
作用:1.宏替换,将宏名替换为文本。

2.加载头文件
3·处理条件编译

2.2在Ubuntu下预处理的命令

gcc是是GCC中的GUN C Compiler ,即C语言编译器,而后面加一个-E表示只进行预处理而不进行其他步骤。

如图,生成了预处理文件hello.i

2.3 Hello的预处理结果解析

这是部分预处理文件的内容

通过gcc的预处理阶段,原本没有多少的源程序代码扩展到了3065行,这是因为在预处理阶段中,会进行头文件的展开、宏替换、去掉注释和多余的空白字符。

当然,代码扩充这么多的最主要原因是对头文件进行了展开,利用ubuntu的文本编辑器打开hello.i后使用查找工具分别查询stdio、unistd、stdlib三个字样来寻找stdio.h头文件、unistd.h头文件、stdlib.h头文件展开的位置。查询后发现13行到729行是对stdio.h头文件的展开,730行到1968行是对unistd.h头文件的展开、1969行到3045行是对stdlib.h头文件的展开。

hello.i前面的部分绝大部分是对头文件的展开,而最后的部分便是C语言源程序的复制

最后顺着usr/include的路径可以在文件夹中找到stdio.h、unistd.h、stdlib.h三个文件,可以发现在头文件中存在许多的宏定义和if/endif/else/elif指令,所以预处理阶段还要根据if/endif/else/elif指令对头文件中进行条件编译。

2.4 本章小结

本章主要介绍了预处理的概念和作用,并使用gcc -E hello.c -o hello.i命令生成了hello.i文件,最后对hello.i文件进行分析,已到达更加理解预处理的过程的目的。从分析过程中,我们可以学习到在预处理阶段主要有四个要做的事:头文件展开、宏替换(又称宏代换)、条件编译、去掉注释和多余的空白字符。


第3章 编译

3.1 编译的概念与作用

编译的概念:

编译是从.i文件到.s文件的过程,此次大作业中是hello.i到hello.s的过程。编译过程将预处理得到的文件转换为汇编文件。

编译的作用:

经过预处理得到的输出文件中,将只有常量,如数字、字符串、变量的定义,以及C语言的关键字,如main, if, else, for, while, {, }, +, -, *, , 等等。编译所要做的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将预处理程序翻译成等价的中间代码表示或汇编代码。(此次大作业中是hello.i到hello.s的过程)

3.2 在Ubuntu下编译的命令

如图,对预处理文件进行处理,生成汇编代码

3.3 hello的编译结果解析

Hello.s的代码如下

3.3.1生成的hello.s文件的分析:

.file :C语言源文件名

.text:程序代码段

.globl :声明一个全局变量

.data :包含静态初始化的数据,所以有初值的全局变量和static变量在data区

.align :声明对指令或者数据的存放地址进行对齐的 方式

.type 声明是函数类型还是对象类型

.size :声明大小

.string:声明一个字符串类型

.long:声明一个long类型

3.3.2数据:

1)在hello.c文件中定义了局部变量i,使用了整形、字符串、数组三种类型的数据

  1. 对于局部变量int类型的i,它是存储在栈当中,我们可以知道i变量保存着栈空间的地址为%rbp-4的位置处

  1. 对于整形变量argc,argc是main函数的第一个形式参数,它存储了参数的个数,观察hello.s文件中的代码可以知道,在经过开辟栈空间的操作以后,传入的第一个参数应该就是argc,所以argc存储在地址为%rbp-20的位置上

  1. 对于立即数,在代码中,如果数字前面有$符号,那么这个数字就是立即数,汇编代码中是允许立即数存在的

 

  1. 对于argv[]数组,它是main函数中传入的第二个参数,用于存放各个输入的参数,第一个参数是文件名,后面的参数就是键入的数据。argv数组一个元素是8个字节大小,即32位

6)对于字符串类型的数据,在hello.c有两个字符串数据:“Usage: Hello 学号 姓名!\n”、“Hello %s %s\n”。在hello.s文件代码中,第一个printf输出的是“Usage: Hello 学号 姓名!\n”,我们可以观察到\345\255\246\345\217\267 \345\247\223\345\220\215\357\274\201,这些是“学号”和“姓名”这四个汉字的UTF-8格式,中间用\隔开。第二个printf输出的是“Hello %s %s\n”,输出的内容是argv[]数组的第二、三个参数,即键入的第一、二个参数(因为argv[]数组第一个参数为文件名)

3.3.3赋值操作

1)在hello.c文件中赋值操作有三个,即:i=0、i++

2)在hello.s文件中通过汇编语句movl $0, -4(%rbp)将立即数0赋给局部变量int i。

3.在hello.s文件中通过汇编语句addl $1,-4(%rbp)实现i++。

3.3.4算术操作和逻辑/位操作

  1. 在hello.s文件中通过汇编语句addl $1,-4(%rbp)实现i++,这条算术指令是将立即数1和地址为%rbp-4的空间存储的值进行相加,并存储到这个空间当中,而后面的l是因为变量i是int类型,四字节。

2)subq $32,%rsp的作用是将%rsp这个栈指针减去32,达到指向栈顶,开辟栈空间的目的

  1. leaq .LC1(%rip),%rdi指令计算LC0的段地址,即%rip+LC1,然后存储的%rdi寄存器当中

3.3.6关系操作

1)在hello.c文件中,第一个关系操作是“argc!=4”,在hello.s文件中,它被编译成了cmpl $4,-20(%rbp),并且通过条件码判断是否跳转到分支当中。

2)第二个关系操作是“i<8”, 在hello.s文件中,通过条件码判断是否跳转。

3.4 本章小结

本章主要了解了编译器将.i文件转换为.s汇编文件的过程,同时以C语言为例对于文件内部的各种数据以及操作进行了详细的叙述,加深了对于高级语言和底层执行之间关系的理解。
第4章 汇编

4.1 汇编的概念与作用

概念:

汇编器将hello.s文件翻译成二进制机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存到目标文件hello.o中。hello.o是一个二进制文件,包含着程序的指令编码,如果用文本编辑器查看,将看到一堆乱码。

作用:

把汇编文件转换为机器容易理解的机器代码(二进制文件),该文件的内容是程序在该机器的机器语言的表示。

4.2 在Ubuntu下汇编的命令

如图所示,生成hello.o文件

4.3 可重定位目标elf格式

使用readelf -a hello.o > hello1elf.txt命令

如图所示,生成hello1elf.txt文件

4.3.1ELF头

查看ELF头,其中含有的信息有系统的字的大小和字节顺序,ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

例如我的ELF开头为:7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00图中还能看出,小端序排列,大小为64字节,文件是重定位的类型,与我们的命令一致,机器为X86-64,节头部表的文件偏移为0,节头数量为14,字符串表索引节头为13。

4.3.2节头

可以看出,节头描述了所有节的基本信息,而他们本身的意义如下:

.text节:已编译程序的机器代码

.rela.text节:一个.text节中的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。

.data节:已初始化的静态和全局C变量。类型为PROGBITS,意为程序数据,旗标为WA,即权限为可分配可写。

.bss节:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。类型为NOBITS,意为暂时没有存储空间,旗标为WA,即权限为可分配可写。

.rodata节:存放只读数据,例如printf中的格式串和开关语句中的跳转表。类型为PROGBITS,意为程序数据,旗标为A,即权限为可分配。

.comment节:包含版本控制信息。

.note.GNU_stack节:用来标记executable stack(可执行堆栈)。

.eh_frame节:处理异常。

.rela.eh_frame节:.eh_frame的重定位信息。

.shstrtab节:该区域包含节区名称。

.symtab节:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。

.strtab节:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。

4.3.3符号表

可以看到一共提供了18个符号,其中:

main是一个位于.text节(Ndx=1)偏移量(value)为0,大小为146个字节的全局符号,类型为变量。而下方的puts、exit、printf、sleep、getchar等函数为NOTYPE未知类型,未定义(UND)符号。hello.c为文件,ABS表示不该被重定位的符号。

4.3.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.4 Hello.o的结果解析

1.与.s文件相比,最明显的不同便是.o文件有代码前方有机器码,而.s没有。

2.跳转上,.o文件是依照地址跳转,而.s文件是依照标识符跳转。

3.申请栈时,.o文件是16进制写法,.s文件为十进制。

4.重定位时,.o文件会留下地址,而.s文件则直接声明。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.5 本章小结

本章主要了解了.s文件到.o文件的转换,查看了程序的ELF条目。还了解到了反汇编这种重要工具,以及汇编与反汇编直接的一些异同。

5链接

5.1 链接的概念与作用

概念:

链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接执行符号解析、重定位过程。

作用:

  把可重定位目标文件和命令行参数作为输入,产生一个完全链接的,可以加载运行的可执行目标文件。使得分离编译成为可能。

5.2 在Ubuntu下链接的命令

如图所示,生成hello文件

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

5.3.1 ELF头

键入readelf -h hello命令查看hello文件的ELF头。从ELF头中我们可以看到文件类型为可执行文件,并且有27个节

5.3.2节头表

键入readelf -s hello命令查看hello文件的节头表。在节头表中,包含了hello文件中所以节的详细信息,包括名称,大小,类型,地址,旗标,偏移量,对齐等信息。

5.3.3程序头

5.4 hello的虚拟地址空间

(1)在命令行终端中键入./edb开始运行edb,并在edb中打开hello文件

  1. 在edb中查看hello文件,可以观察到hello的虚拟空间在0x401000开始,于0x401ff0结束。

   

5.5 链接的重定位过程分析

如图,生成hello_dis.txt

最明显的不同在于,hello的反汇编代码地址是0x40xxxx的类型,是经过重定位的,而.o文件则是虚拟地址0开始。这一点同样体现在两种反汇编文件的函数调用的地址显示上。

除此之外,.o文件中先是.text段,然后是main函数。而hello的反汇编中则存在各种其他的复杂函数与数据。

5.6 hello的执行流程

函数名称

初始地址位置

 <_init>

0000000000401000

 <.plt>

0000000000401020

<puts@plt>

0000000000401090

<printf@plt>

00000000004010a0

<getchar@plt>

00000000004010b0

<atoi@plt>

00000000004010c0

<exit@plt>

00000000004010d0

<sleep@plt>

00000000004010e0

<_start>

00000000004010f0

<_dl_relocate_static_pie>

0000000000401120

<main>

0000000000401125

<__libc_csu_init>

00000000004011c0

<__libc_csu_fini>

0000000000401230

<_fini>

0000000000401238

5.7 Hello的动态链接分析

   动态共享库是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,兵和一个程序链接起来,这个过程就是动态链接。

把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。

.plt:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。

.got:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。

hello在动态连接器加载前后的重定位是不一样的,在加载之后才进行重定位

5.8 本章小结

本章介绍了连接的过程。解释了程序是如何进行重定位的操作,把相同类型的数据放在同一个节的过程,同时也说明了链接的工作原理。
6hello进程管理

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.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行一个新程序。

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

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

6.5 Hello的进程执行

多个流并发地执行的一般现象被称为并发。一个进程和其他进轮流运行的概念称为多任务。而上下文是内核重新启动一个被抢占的进程所需的状态,它右通用寄存器、浮点你寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。

操作系统内核使用一种称为上下文切换的较高层次形式的异常控制流来实现多任务。在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决策叫做调度,是由内核中被称为调度器的代码处理的。

而一个进程执行它的控制流的一部分的每一时间段叫做时间片。

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

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

由图可以看到,用户态和内核态直接是不断切换的,例如执行hello程序的时候,若在调用sleep函数之前,hello程序被抢占,就进行上下文切换,调用sleep时,进入内核态,处理器处理sleep并请求主动释放当前进程。之后计时器开始计时,内核进行上下文切换,执行其他的进程。当计时结束时,计时器发送一个中断信号,处理器处理中断信号,并进行上下文切换,重新回来执行hello进程。

6.6 hello的异常与信号处理

会出现四类异常:中断,陷阱,故障,终止。

如果乱按且结尾没有回车,这些东西会被当做字符串缓存,而如果结尾是回车,它之前的信息会被当作输入的命令。

发送一个SIGTSTP信号给前台进程组的每个进程,停止前台作业,即停止hello程序。control+z

让内核发送一个SIGINT信号给到前台进程组中的每个进程,终止前台进程,即终止hello程序。control+c

查看进程信息,发现hello还在后台control+z后ps

输入jobs后,看到进程停止。control+z后jobs

 

打印所有进程的关系。

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

杀死进程(control+z后kill)

6.7本章小结

本章首先介绍了进程的概念和作用,然后阐述了hello程序调用fork函数创建进程的过程、execve过程、进程执行、异常与信号处理,更加熟悉了hello程序载入到内存后的运行流程。
第7章 hello的存储管理

7.1 hello的存储器地址空间

(1)物理地址:物理地址(Physical Address) 是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么hello的线性地址会使用页目录和页表中的项变换成hello的物理地址。如果没有启用分页机制,那么hello的线性地址就直接成为hello的物理地址了。

(2)逻辑地址:逻辑地址(Logical Address) 是指由hello产生的和段相关的偏移地址部分。只有在Intel实模式下,hello的逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,cpu不进行自动地址转换);逻辑地址也就是在Intel保护模式下程式执行代码段限长内的偏移地址(假定代码段、数据段如果完全相同)。应用程式员仅需和逻辑地址打交道,而分段和分页机制对你来说是完全透明的,仅由系统编程人员涉及。应用程式员虽然自己能直接操作内存,那也只能在操作系统给你分配的内存段操作。

(3)线性地址:是逻辑地址到物理地址变换之间的中间层。hello程序代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么hello的线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么hello的线性地址直接就是物理地址。

(4)虚拟地址:有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的,是hello中的虚拟地址。

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

(1)段式管理基本思想: 在段式存储管理中,将程序的地址空间划分为若干个段(segment),这样每个进程有一个二维的地址空间。在前面所介绍的动态分区分配方式中,系统为整个进程分配一个连续的内存空间。而在段式存储管理系统中,则为每个段分配一个连续的分区,而进程中的各个段可以不连续地存放在内存的不同分区中。程序加载时,操作系统为所有段分配其所需内存,这些段不必连续,物理内存的管理采用动态分区的管理方法。

(2)段式管理的数据结构

为了实现段式管理,操作系统需要如下的数据结构来实现进程的地址空间到物理内存空间的映射,并跟踪物理内存的使用情况,以便在装入新的段的时候,合理地分配内存空间。

·进程段表:描述组成进程地址空间的各段,可以是指向系统段表中表项的索引。每段有段基址(baseaddress),即段内地址。

·系统段表:系统所有占用段(已经分配的段)。

·空闲段表:内存中所有空闲段,可以结合到系统段表中。

(3)段式管理的地址变换

在段式 管理系统中,整个进程的地址空间是二维的,即其逻辑地址由段号和段内地址两部分组成。为了完成进程逻辑地址到物理地址的映射,处理器会查找内存中的段表,由段号得到段的首地址,加上段内地址,得到实际的物理地址(图五十四)。这个过程也是由处理器的硬件直接完成的,操作系统只需在进程切换时,将进程段表的首地址装入处理器的特定寄存器当中。这个寄存器一般被称作段表地址寄存器。

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

(1)基本原理:将程序的逻辑地址空间划分为固定大小的页(page),而物理内存划分为同样大小的页框(page frame)。程序加载时,可将任意一页放人内存中任意一个页框,这些页框不必连续,从而实现了离散分配。该方法需要CPU的硬件支持,来实现逻辑地址和物理地址之间的映射。在页式存储管理方式中地址结构由两部构成,前一部分是页号,后一部分为页内地址w(位移量)

(2)页式管理数据结构

1)进程页表:完成逻辑页号(本进程的地址空间)到物理页面号(实际内存空间,也叫块号)的映射。

2)物理页面表:整个系统有一个物理页面表,描述物理内存空间的分配使用状况,其数据结构可采用位示图和空闲页链表。

3)请求表:整个系统有一个请求表,描述系统内各个进程页表的位置和大小,用于地址转换也可以结合到各进程的PCB(进程控制块)里。

(3)页式管理地址变换:

在页式系统中,指令所给出的地址分为两部分:逻辑页号和页内地址。

CPU中的内存管理单元(MMU)按逻辑页号通过查进程页表得到物理页框号,将物理页框号与页内地址相加形成物理地址。

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

TLB:翻译后备缓冲器,其中每一行都保存着一个由单个PTE组成的块。通过这种方式我们可以再把VPN分成TLBT(TLB标记)和TLB索引(TLBI),根据索引和标记在TLB中寻找对应的PPN,TLB命中可以减少内存访问,就和之前的cache命中类似,这里少了行,也可以理解成一组只有一行,类似直接映射。

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

多级页表中,VPN被分为k个部分,第一级VPN结合基址寄存器得到一个页表条目,其中存放下一级页表的基址,再结合VPN2,得到第三级页表基址,继续寻找,以此类推,直到最后确定对应的物理页号,与VPO结合,得到由PPN与PPO结合成的物理地址,用于物理地址寻址。

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

收到虚拟地址之后,先在TLB中寻找,若不在TLB中,结合多级页表得到它的物理地址,然后到Cache里面寻找。若在TLB中,则直接被MMU得到。

在Cache中,命中就返回,不命中就依次在L1、L2、L3中不断寻找。

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 缺页故障与缺页中断处理

缺页:虚拟内存中的字不在物理内存中(BRAM缓存不命中),通俗地讲就是内存中没有这个页,所以导致MMU找不到对应的物理地址。缺页故障是一种故障,当指令引用一个虚拟地址,MMU会查找页表,当找不到对应的物理地址时,就会触发缺页中断。

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

7.9动态存储分配管理

动态内存:

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

基本方法与策略:

(1)带边界标签的隐式空闲链表分配器管理

带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的。

在隐式空闲链表中,因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。其中,一个设置了已分配的位而大小为零的终止头部将作为特殊标记的结束块。

(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的实现分析

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

用户输入命令后,getchar从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章简要介绍了hello文件的I/O管理,包括设备管理方法,接口函数,以及读写函数的实现分析。

结论

开始的代码,经过预处理,将各种需要的头文件展开、引入。处理完这些的.i文件有经过编译器的洗礼,化身成为汇编代码。在之后,.s中的汇编代码被转换成只有机器认识的机器代码,生成重定位信息,.o文件也在这时诞生。一个文件到这一步已经不容易了,可是这对于hello来说还没结束,它还要经历动态库链接,才能成为可执行文件。

下面就到了运行的阶段,它借用shell小跑起来,用fork来创建子进程,用execve帮它加载,在上下文间不断切换。与此同时,它随时有可能被中断,甚至承担着被杀死的风险。

在度过上述难关之后,它的运行又涉及到虚拟地址、内存引用、cache等等知识,越来越复杂,也越来越高效。同时异常处理机制保证了程序对异常信号的处理,使得程序之间都能在系统之下和平共处

感悟:计算机系统的内容十分多,而且复杂,是大学学习中目前遇到的最难学习的课程。但是,虽然难学,这些内容中蕴含了许多计算机的奥妙,在学习过程中也充满着乐趣。由于实在是水平有限,对于一下很深入的知识仍然不是很理解,需要在后面的学习过程中花费更多的功夫。

附件

hello.c:源程序文件

hello.i:预处理后的文本文件

hello.s:编译后汇编程序文本文件

hello.o:汇编后的可重定位目标程序(二进制文件)

hello:链接后的可执行目标文件

hello_o.txt:hello.o的反汇编文件

hello1elf.txt:ELF格式下的hello.o

参考文献

为完成本次大作业你翻阅的书籍与网站等

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值