摘 要
通过运用在计算机系统课程上学习的知识,分析研究hello程序在Linux下的P2P和020过程,使用各种工具,演示Linux框架下程序的声明周期。
关键词:
程序的生命周期
进程
P2P;020;
目 录
第1章 概述................................................................................................................ - 4 -
1.1 Hello简介......................................................................................................... - 4 -
1.2 环境与工具........................................................................................................ - 4 -
1.3 中间结果............................................................................................................ - 4 -
1.4 本章小结............................................................................................................ - 4 -
第2章 预处理............................................................................................................ - 6 -
2.1 预处理的概念与作用........................................................................................ - 6 -
2.2在Ubuntu下预处理的命令............................................................................. - 6 -
2.3 Hello的预处理结果解析................................................................................. - 7 -
2.4 本章小结............................................................................................................ - 9 -
第3章 编译.............................................................................................................. - 10 -
3.1 编译的概念与作用.......................................................................................... - 10 -
3.2 在Ubuntu下编译的命令.............................................................................. - 10 -
3.3 Hello的编译结果解析................................................................................... - 10 -
3.4 本章小结.......................................................................................................... - 14 -
第4章 汇编.............................................................................................................. - 15 -
4.1 汇编的概念与作用.......................................................................................... - 15 -
4.2 在Ubuntu下汇编的命令.............................................................................. - 15 -
4.3 可重定位目标elf格式.................................................................................. - 15 -
4.4 Hello.o的结果解析....................................................................................... - 17 -
4.5 本章小结.......................................................................................................... - 22 -
第5章 链接.............................................................................................................. - 23 -
5.1 链接的概念与作用.......................................................................................... - 23 -
5.2 在Ubuntu下链接的命令.............................................................................. - 23 -
5.3 可执行目标文件hello的格式..................................................................... - 23 -
5.4 hello的虚拟地址空间................................................................................... - 24 -
5.5 链接的重定位过程分析.................................................................................. - 25 -
5.6 hello的执行流程........................................................................................... - 27 -
5.7 Hello的动态链接分析................................................................................... - 28 -
5.8 本章小结.......................................................................................................... - 28 -
第6章 hello进程管理...................................................................................... - 30 -
6.1 进程的概念与作用.......................................................................................... - 30 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 30 -
6.3 Hello的fork进程创建过程........................................................................ - 31 -
6.4 Hello的execve过程.................................................................................... - 31 -
6.5 Hello的进程执行........................................................................................... - 31 -
6.6 hello的异常与信号处理............................................................................... - 31 -
6.7本章小结.......................................................................................................... - 31 -
第7章 hello的存储管理.................................................................................. - 32 -
7.1 hello的存储器地址空间............................................................................... - 32 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................... - 32 -
7.3 Hello的线性地址到物理地址的变换-页式管理......................................... - 32 -
7.4 TLB与四级页表支持下的VA到PA的变换................................................ - 33 -
7.5 三级Cache支持下的物理内存访问............................................................. - 34 -
7.6 hello进程fork时的内存映射..................................................................... - 34 -
7.7 hello进程execve时的内存映射................................................................. - 34 -
7.8 缺页故障与缺页中断处理.............................................................................. - 34 -
7.9动态存储分配管理.......................................................................................... - 34 -
7.10本章小结........................................................................................................ - 35 -
第8章 hello的IO管理.................................................................................... - 36 -
8.1 Linux的IO设备管理方法............................................................................. - 36 -
8.2 简述Unix IO接口及其函数.......................................................................... - 36 -
8.3 printf的实现分析........................................................................................... - 36 -
8.4 getchar的实现分析....................................................................................... - 36 -
8.5本章小结.......................................................................................................... - 36 -
结论............................................................................................................................ - 37 -
附件............................................................................................................................ - 38 -
参考文献.................................................................................................................... - 39 -
在linux中,hello.c经过cpp预处理,ccl编译,as汇编,ld链接之后,最终可以作为目标程序执行。在shell中键入启动命令后,shell会使用fork为其创建并生成子进程,这样hello从Program转换为Process,这是P2P的过程。
然后shell为其execve映射虚拟内存。进入程序入口后,程序开始加载物理内存,然后进入main函数执行目标代码。 CPU为正在运行的hello分配时间片以执行逻辑控制流。程序完成后,shell父进程负责恢复hello进程,内核删除相关的数据结构。以上就是020的过程。
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
Ubuntu64位
codeblocks,objdump,gdb,edb,hexedit
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
总的列了一下这整个实验中的中间结果和所在的软硬件环境还有使用的工具。
做了一个提纲挈领的总结
预处理又称预编译,(对于c/c++来说)预处理指的是在程序编译之前,根据以字符#开头的命令(即头文件/define等),修改原始的c程序,例如大作业所提供的hello.c文件,就有三个头文件:stdio.h,unistd.h,stdlib.h
预处理器所做的就是从系统头文件包中找到这三个头文件,并且把他们的内容插入hello.c的文本中,得到一个叫做hello.i的另类的c程序。
这样做的目的是使得编译器在对程序进行翻译的时候更加方便。
gcc -E hello.c -o hello.i
图 2.1
图2.2
经过预处理后,前面3个#include消失了,取而代之的是一长串以#开头的字符串,这个应该是原来的三个系统头文件
图2.3
预处理命令,可以在编译器编译之前,提前进行一些操作,比如定义常量,还可以进行条件编译以方便调试,可以进行文件引入来导入一些预先写好的模块,便于程序的组织和调试和一些特殊的编程技巧的实现,是一项非常有用的功能。
(第2章0.5分)
编译即利用编译程序从源语言编写的源程序产生目标程序的过程。
作用:
把源程序,翻译成汇编语言
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
gcc -S hello.i -o hello.s
图3.1
3.3.1 数据
1整数
Int sleepsecs=2.5
图3.2 sleepsecs在.i文件中
经过编译器处理,hello.s 文件先声明了 sleepsecs 这个全局变量,然
后将 sleepsecs 存放在.data 节
图3.3
由图 3.3 可以看出.data 节是 4 字节对齐的,其中 sleepsecs 类型为对象,占 4 字节,而接着 sleepsecs 被赋值为了 long型的 2
除了 sleepsecs,hello.c 中还有 argc 和 i 两个整型变量。由图3.4可知hello.s将i存储在-4(%rbp)中,初始值为0,每次循环加一,循环结束条件是i>9
图3.4
argc作为第一个参数,由寄存器%edi保存,然后又被存入-20(%rbp)
图 3.5 局部变量argc
对于 hello.c 中直接出现的整数比如 10、3 编译器则处理成立即数出现在汇编代码中。
2 字符串
字符串被存放在.rodata 节。hello.c 中共有两个字符串,分别是两个 printf格式化输出的字符串。“Usage: Hello 学号 姓名!\n”和“Hello %s %s\n”
图3.6 字符串的定义
3 数组
程序中涉及的数组是:char *argv[], main函数执行时输入的命令行,argv同事还是第二个传入的参数,如图3.7所示
图3.7
3.3.2 赋值
1 int sleepsecs = 2.5
因为sleepsecs是全局变量,所以直接在.data节中将sleepsecs声明为值2的long类型数据。
2 i = 0
因为i是4B的int类型,所以使用movl进行赋值
图 3.8
3.3.3 类型转换
程序中出现的类型转换是 int sleepsecs = 2.5,即将浮点数2.5转换为int类型。浮点数默认类型为double,所以上述强制转化是double强制转化为int类型。遵从向零舍入的原则,将2.5舍入为2。
3.3.4 算数操作
1 自增 i++
对计数器i进行自增操作,使用程序指令addl,后缀代表操作数是一个大小为4B的数据
2 leaq .LC1(%rip),%rdi
使用了加载有效地址指令leaq计算LC1的段地址%rip+.LC1并传递给%rdi
3.3.5 关系操作
进行关系操作的汇编指令有
指令 |
效果 |
描述 |
CMP S1, S2 |
S2 - S1 |
比较-设置条件码 |
TEST S1,S2 |
S1&S2 |
测试-设置条件码 |
SET** D |
D=** |
按照**将条件码设置D |
J** |
-- |
根据**与条件码进行跳转 |
程序中涉及的关系运算为:
1 argc!=3:判断argc不等于3。hello.s中使用cmpl $3,-20(%rbp),计算argc-3然后设置条件码,为下一步je利用条件码进行跳转作准备。
2 i<10:判断i小于10。hello.s中使用cmpl $9,-4(%rbp),计算i-9然后设置条件码,为下一步jle利用条件码进行跳转做准备
3.3.6 控制转移
控制转移往往与关系操作配合进行,如果满足某个条件,则跳转至某个位置。
hello.c 中的控制转移操作见图 3.8。
汇编语言中的控制转移主要有jmp的一系列指令完成
图3.8
图3.9 图3.10
图3.9和图3.10分别是if和for的控制转移汇编代码实现
3.3.7 函数操作
如果我们进入了if语句,我们这时需要调用两个函数,函数调用的第一步就是把函数需要的参数放入相应的寄存器中,由前面解析我们已经知道,函数的第一个参数存放在%rdi中,所以这里首先把.LC0中数据放入edi,由图 知里面的数据就是我们的输出然后再call puts函数。call Q 指令会把地址A压入栈中,并将PC设置位Q的起始地址,压入栈中的地址称位返回地址,是call指令后面的那条指令的地址。同理可得exit(0)的调用过程。
汇编代码与我们的高级语言已有了很大的不同,里面涉及到了很多如寄存器等真正在计算机上如何实现的过程,基本上是计算机真正如何执行我们的程序,可能一个简单的for循环,if语句,函数调用,在汇编语言中会花费比高级语言多的多的语句来实现,就是一个简单的hello程序,汇编代码就花费了几百行来实现。懂得汇编代码如何运行对于我们对程序的理解和以后面对一些在高级语言中难以发现的错误都大有脾益
(第3章2分)
概念:通过汇编器,把汇编语言翻译成机器语言
作用:通过汇编这个过程,把汇编代码转化成了计算机完全能够理解的机器代码,这个代码也是我们程序在计算机中表示。
as hello.s -o hello.o
图4.1
4.3 可重定位目标elf格式
典型的 ELF 可重定位目标文件如图4.2所示:
图4.2
1 ELF头
ELF头首先以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。剩下部分就图4.3所示,列出了包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小(64字节),目标文件的类型(REL可重定位文件),机器类型(AMD X86-64),节头部表的文件偏移,以及节头部表中条目的大小和数量
图4.3
在ELF后的都是节,下图也列出了节和它的一些基本信息,这里在写出每个节里应该存放的东西。
.text: 已编译的机器代码,类型为PROBITS,意思是程序数据,旗标为AX,下面也给出了解释,意思是分配内存且可执行
.rela.text 一个.text节中位置的列表(下一节会重点解释)
.data: 这个里面是已初始化的全局变量和静态c变量,类型也为PROBITS,旗标WA意思是分配内存且可修改
.bss: 这里面放的是未初始化的全局变量和静态c变量,类型NOBITS,意思是暂时没有存储空间,说明这个节在开始是不占据实际的空间
。
.rodata: 只读数据,如printf中的格式串和switch中的跳转表,我们hello程序中的printf中的格式串就存放在这里。
.comment: 这个节中包含了版本控制信息
.note.GNU_stack: 用来标记executable stack(可执行堆栈)
.eh_frame: This section contains information necessary for frame unwinding during exception handling.(这个节的意思我没有找到中文的解释)主要就是用来处理异常
.rela.eh_frame:.eh_frame的重定位信息
.symtab:,装载符号信息
.strtab: 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。
.shstrtab:该区域包含节区名称
图4.4
3 符号表
每个可重定位目标模块都有一个符号表,它包含m的定义和引用的符号的信息。符号表是由汇编器构造,使用编译器输出到汇编语言.s文件中的符号。每个符号表是一个条目的数组
然后我们可以看到我们hello程序的符号表举个例子,我们通过图4.5可以看到对全局符号sleepsecs的定义,它是一个位于.data段偏移量位0(value值)处一个大小为4个字节的变量,全局符号main是一个位于.text段,段偏移为0,大小129字节的函数、
图4.5
4 重定位节
在rela.text里面有我们的重定位条目,这个条目能告诉链接器目标文件合并成可执行文件时如何修改引用。
图4.6
图4.7
机器语言就是一系列的二进制代码,一般包括操作码字段和地址码字段。每一种cpu都有自己的机器指令集\汇编指令集,在这个指令集中,汇编代码中操在反汇编生成的汇编代码方面,hello 和 hello.o 完全相同,hello 与 hello.o 两者
的反汇编文件的唯一不同之处在于:地址由相对偏移变为了可以由 CPU 直接访问
的虚拟地址,链接器把 hello.o 中的偏移量加上程序在虚拟内存中的起始地址
0x0040000 和.text 节的偏移量就得到了 hello1.d 中的一个个地址。函数内的控制转
移即 jmp 指令后的地址由偏移量变为了偏移量+函数的起始地址(虚拟地址); call 后
的地址由链接器执行重定位后计算出实际地址。
第四章已经介绍过重定位的概念以及 linux 的重定位算法,下面结合 hello 与
hello.o 的实例进一步介绍。
首先回顾之前的内容:汇编器识别出了 main 函数引用了一个全局符号,因此
产生了一个重定位条目,来让链接器能够将该全局符号正确地重定位。 hello.o 中我
们看到的 16、18、21、32 行就是四个重定位条目,它包含了对应符号的重定位类
型以及 addend 的值。文件交给链接器后,链接器先进行符号解析,就是把代码中
的每个符号引用和正好一个符号定义关联起来,然后就可以开始重定位了。重定位
由两步组成:
1. 重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同
一类型的新的聚合节。例如,来自所有输入模块的.data 节被全部合并成一
个节,这个节成为输出的而可执行目标文件的.data 节。然后链接器将运行
时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入
模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都
由唯一的运行时内存地址了。
2. 重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每
个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器作数,寄存器等都会对应一个机器码
观察上图和我们第三章给出的汇编代码可以发现,机器代码反汇编后得到的汇编代码与我们原来的有些地方不同,这里来展示哪些地方不同及其原因。
经过观察,发现机器代码里没有再分.L1这些段了,而是在一串连续地址里,这点对我们下面跳转和函数调用很重要
1)
图4.8
图4.9
第一个不同是堆栈大小,我们能够发现机器代码中申请的堆栈大小为0x20,而原来汇编代码里为0x32
2)
图4.10
图4.11
第二个不同就是跳转,汇编代码中有不同的段,跳转就是跳转到下一个段,而机器代码你没有这个了,我们需要根据机器代码地址来跳转
3)
图4.12
图4.13
第三个不同则是函数调用,函数参数传递时,根据我们上一节知道的,printf的格式串存放在.rodata,根据后面的.rodata_0x0查询重定位节
当我们从汇编代码变为了机器代码,程序就真正变成了计算机可以理解的程序,我们也知道了我们的程序真正在计算机中是以什么存储的。机器代码与汇编代码会根据cpu的指令集,产生一个对应,我们也能通过objdump这样的反汇编工具查看机器码对应的汇编码,不过这里对代码已经与我们.s里的汇编代码有了些不同,已经在汇编过程中我们的代码变成了ELF格式,代码被放在代码段,全局变量放在.data段,通过重定位条目得到每个符号不同偏移量,去不同的段找到我们想要的信息.
(第4章1分)
概念:
作用:把可重定位目标文件和命令行参数作为输入,产生一个完全链接的,可以加载运行的可执行目标文件。
注意:这儿的链接是指从 hello.o 到hello生成过程
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/5/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello
图5.1
图5.2
第四章中已经介绍过了 elf 文件的基本格式,这里 hello 的 elf 格式也符合该格
式。我们已经知道节头部表中保存了各节的信息,因此找到节头部表部分,在这里
可以看到 hello 各节的起始地址、大小等信息。
用 hexedit 加载 hello,可以看到开头是 ELF 头(但不是一个节)。
图5.3
图5.4
先用 objdump -d -r hello > hello.d 生成 hello 的反汇编文件。
图5.5
图5.6
在反汇编生成的汇编代码方面,hello 和 hello.o 完全相同,hello 与 hello.o 两者的反汇编文件的唯一不同之处在于:地址由相对偏移变为了可以由 CPU 直接访问的虚拟地址,链接器把 hello.o 中的偏移量加上程序在虚拟内存中的起始地址0x0040000 和.text 节的偏移量就得到了 hello1.d 中的一个个地址。函数内的控制转移即 jmp 指令后的地址由偏移量变为了偏移量+函数的起始地址(虚拟地址); call 后的地址由链接器执行重定位后计算出实际地址。
第四章已经介绍过重定位的概念以及 linux 的重定位算法,下面结合 hello 与hello.o 的实例进一步介绍。
首先回顾之前的内容:汇编器识别出了 main 函数引用了一个全局符号,因此产生了一个重定位条目,来让链接器能够将该全局符号正确地重定位。 hello.o 中我们看到的 16、18、21、32 行就是四个重定位条目,它包含了对应符号的重定位类型以及 addend 的值。文件交给链接器后,链接器先进行符号解析,就是把代码中的每个符号引用和正好一个符号定义关联起来,然后就可以开始重定位了。重定位由两步组成:
1. 重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data 节被全部合并成一个节,这个节成为输出的而可执行目标文件的.data 节。然后链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都由唯一的运行时内存地址了。
2. 重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于汇编器产生的可重定位条目。
hello 和 hello.o 除了在反汇编生成的汇编代码有所不同,hello 的反汇编文件还在开头比 hello.o 多了.init、.fini、.plt 和.plt.got 节,其中.init 节是程序初始化需要执行的代码,.fini 是程序正常终止时需要执行的代码,.plt 和.plt.got 节分别是动态链接中的过程链接表和全局偏移量表
图5.7
程序名 |
程序地址 |
ld-2.23.so!_dl_start |
0x00007f11037199b0 |
ld-2.23.so!_dl_init |
0x00007f1103728740 |
hello!_start |
0x400510 |
libc-2.23.so!__libc_start_main |
0x00007fa17c942740 |
ld-2.23.so!_dl_fixup |
0x00007fb4dc2d39f0 |
libc-2.23.so!__cxa_atexit |
0x00007fb4dbf34280 |
libc-2.23.so!__libc_csu_init |
0x400690 |
libc-2.23.so!_setjmp |
0x00007fb4dbf2f250 |
hello!main |
0x400606 |
hello!puts@plt |
0x4004a0 |
hello!exit@plt |
0x4004e0 |
ld-2.23.so!_dl_fixup |
0x00007fb4dc2d39f0 |
libc-2.23.so!exit |
0x00007f2137ae8030 |
libc-2.23.so!__run_exit_handlers |
0x00007f2137ae7f10 |
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
链接(linking) 是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(compile time), 也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time), 也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(run time), 也就是由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,
链接是由叫做链接器(linker) 的程序自动执行的。
链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译(separate compilation)成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,不必重新编译其他文件
(以下格式自行编排,编辑时删除)
(第5章1分)
进程的经典定义是一个执行中程序的实例,系统的每个程序都运行在某个进程的上下文。上下文是由程序正确运行所需的状态组成的,这个状态包括存放在内存里的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。
通过进程,我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象。
shell俗称壳,它是指UNIX系统下的一个命令解析器;主要用于用户和系统的交互。UNIX系统上有很多种Shell。首个shell,即Bourne Shell,于1978年在V7(AT&T的第7版)UNIX上推出。后来,又演变出C shell、bash等不同版本的shell。
bash,全称为Bourne-Again Shell。它是一个为GNU项目编写的Unix shell。bash脚本功能非常强大,尤其是在处理自动循环或大的任务方面可节省大量的时间。bash是许多Linux平台的内定Shell。
处理流程:
1 新建文件test.sh
$ touch test.sh
2 添加可执行权限
$ chmod +x test.sh
3 编辑test.sh,test.sh内容如下:
#!/bin/bash
echo “hello bash”
exit 0
说明:
#!/bin/bash : 它是bash文件声明语句,表示是以/bin/bash程序执行该文件。它必须写在文件的第一行!
echo “hello bash” : 表示在终端输出“hello bash”
exit 0 : 表示返回0。在bash中,0表示执行成功,其他表示失败。
4 执行bash脚本
$ ./bash
在终端输出“bash hello”
shell调用fork函数,形成自身的一个拷贝(子进程),为运行hello做准备
在shell的子进程中执行execve函数,将参数传给Hello程序,并执行Hello
一开始,Hello运行在用户模式,当程序收到一个信号时,进入内核模式,运行信号处理程序,之后再返回用户模式。在Hello运行的过程中,cpu不断切换上下文,使Hello程序运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度
hello 执行过程中可能出现四类异常:中断、陷阱、故障和终止。
1. 中断是来自 I/O 设备的信号,异步发生,中断处理程序对其进行处理,返
回后继续执行调用前待执行的下一条代码,就像没有发生过中断。
2. 陷阱是有意的异常,是执行一条指令的结果,调用后也会返回到下一条指
令,用来调用内核的服务进行操作。帮助程序从用户模式切换到内核模式。
3. 故障是由错误情况引起的,它可能能够被故障处理程序修正。如果修正成
功,则将控制返回到引起故障的指令,否则将终止程序。
4. 终止是不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程
序会将控制返回给一个 abort 例程,该例程会终止这个应用程序。
接下来分析 hello 对各种信号的处理:
linux命令行shell是一个非常强大的工具,用它可以更方便的执行Hello和发送各种命令请求。通过信号等方式可以实现异常处理,让Hello在顺序执行者也能处理一些突发状况和实现一些功能。进程调度实现了各个进程计算资源合理分配,互不干扰,提高了系统稳定性和效率
(第6章1分)
物理地址(physical address)
用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
逻辑地址(logical address)
逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。如Hello中sleepsecs这个操作数的地址。
线性地址(linear address)或也叫虚拟地址(virtual address)
跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。
段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本组成方式是段号+段内偏移地址。
在x86保护模式下,段的信息(段基线性地址、长度、权限等)即段描述符占8个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节)。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index)。
首先给定一个完整的逻辑地址[段选择符:段内偏移地址],
1.看段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT中的段,还是LDT中的段,再根据指定的相应的寄存器,得到其地址和大小,我们就有了一个数组了。
2.拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,这样就有了Base,即基地址就知道了。
3.把基地址Base+Offset,就是要转换的下一个阶段的地址。
分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。
为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。32位的线性地址被分成3个部分:最高10位 Directory 页目录表偏移量,中间10位 Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位),项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。
每个活动的任务,必须要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器。页表可以提前分配好,也可以在用到的时候再分配。
在Intel Core i7环境下研究VA到PA的地址翻译问题。前提如下:
虚拟地址空间48位,物理地址空间52位,页表大小4KB,4级页表。TLB 4路16组相联。CR3指向第一级页表的起始位置(上下文一部分)。
解析前提条件:由一个页表大小4KB,一个PTE条目8B,共512个条目,使用9位二进制索引,一共4个页表共使用36位二进制索引,所以VPN共36位,因为VA 48位,所以VPO 12位;因为TLB共16组,所以TLBI需4位,因为VPN 36位,所以TLBT 32位。
如图 ,CPU产生虚拟地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT(前32位)+TLBI(后4位)向TLB中匹配,如果命中,则得到PPN(40bit)与VPO(12bit)组合成PA(52bit)。
如果TLB中没有命中,MMU向页表中查询,CR3确定第一级页表的起始地址,VPN1(9bit)确定在第一级页表中的偏移量,查询出PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA,并且向TLB中添加条目。
如果查询PTE的时候发现不在物理内存中,则引发缺页故障。如果发现权限不够,则引发段错误。
先访问一级缓存,不命中时访问二级缓存,再不命中访问三级缓存,再不命中访问主存,如果主存缺页则访问硬盘
执行新进程(hello)时,为这个新进程创建虚拟内存
创建当前进程的的mm_struct, vm_area_struct和页表的原样副本
两个进程中的每个页面都标记为只读
两个进程中的每个区域结构(vm_area_struct) 都标记为私有的写时复制
在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存, 随后的写操作通过写时复制机制创建新页面
1 删除已存在的用户区域
2 创建新的区域结构: 代码和初始化数据映射到.text和.data区(目标文件提供), .bss和栈映射到匿名文件
3 设置PC,指向代码区域的入口点
缺页故障:需要访问的页不在主存,需要操作系统将其调入后才能访问。
有三种情况:
只有正常缺页时,系统才会调入需要访问的页,并再次执行访问该页的命令。
基本方法:维护一个虚拟内存区域“堆”,将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的,需要时选择一个合适的内存块进行分配。
1 记录空闲块,可以选择隐式空闲链表,显示空闲链表,分离的空闲链表和按块大小排序建立平衡树
2 放置策略,可以选择首次适配,下一次适配,最佳适配
3 合并策略,可以选择立即合并,延迟合并
4 需要考虑分割空闲块的时机,对内部碎片的忍耐阈值.
通过高速缓存、虚拟内存、动态内存分配,可以实现快速、高校、利用率高的储存空间管理。可以通过内存映射等方式实现文件共享。储存管理是一个相当重要、值得研究的机制。
(第7章 2分)
设备的模型化:将设备抽象成文件
设备管理:通过unix io接口管理
打开和关闭文件:open()and close()
读写文件:read() and write()
改变当前的文件位置 lseek()
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成 ASCII 码,保存到系统的键盘缓冲区之中
getchar 调用了 read 函数,read 函数也通过 sys_call 调用内核中的系统函数,将读取存储在键盘缓冲区中的 ASCII 码,直到读到回车符,然后返回整个字符串, getchar 函数只从中读取第一个字符,其他的字符被缓存在输入缓冲区。
输入输出看似简单,实际是一个非常精巧的过程,从程序发出请求到系统函数调用到设备相应,需要执行许多步骤,往往也是拖慢程序的主要因素和一些崩溃异常的高发地,需要谨慎选用函数、命令实现目的
(第8章1分)
hello的源码hello.c文件,要生成可执行文件,首先要进行预处理,其次要进行编译生成汇编代码,接着进行汇编处理生成目标文件,目标文件通过链接器形成一个可执行文件,可执行文件需要一个执行环境,它可以在linux下通过shell进行运行,与计算机其他经常文件同步运行,并通过异常处理机制相应信号。在运行的过程中,程序通过Intel内存管理机制一步步访问逻辑地址、虚拟地址、物理地址,从而进行数据交换,还可以通过IO机制进行输入输出交互。
合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。
一个计算机系统的实现是从最简单的几条指令或者程序的几个动作开始的。通过这些微小的部分的分工合作却能够完成那么多very cool的事
(结论0分,缺少 -1分,根据内容酌情加分)
列出所有的中间产物的文件名,并予以说明起作用。
(附件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分)