HIT-ICS2021大作业论文
计算机科学与技术学院
2021年5月
摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。
关键词:Hello World!;程序的生命;预处理;编译;汇编;链接;运行;
Hello是我们成为程序员后的第一个朋友,他阳光活泼,乐于助人。但他引导我们踏入这个神奇的领域并帮我们树立信心后就消失了,你是否想要了解他到底都经历了些什么吗?从他出生开始,他就开始了他传奇的一生。他经历了预处理,编译,汇编,链接,运行;他探索过外部存储设备,I/O桥,内存,各级cache。接下来让我们接近他来感受他的一生吧!
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述... - 4 -
1.1 Hello简介... - 4 -
1.2 环境与工具... - 4 -
1.3 中间结果... - 4 -
1.4 本章小结... - 5 -
第2章 预处理... - 6 -
2.1 预处理的概念与作用... - 6 -
2.2在Ubuntu下预处理的命令... - 6 -
2.3 Hello的预处理结果解析... - 6 -
2.4 本章小结... - 9 -
第3章 编译... - 10 -
3.1 编译的概念与作用... - 10 -
3.2 在Ubuntu下编译的命令... - 10 -
3.3 Hello的编译结果解析... - 10 -
3.4 本章小结... - 12 -
第4章 汇编... - 13 -
4.1 汇编的概念与作用... - 13 -
4.2 在Ubuntu下汇编的命令... - 13 -
4.3 可重定位目标elf格式... - 13 -
4.4 Hello.o的结果解析... - 16 -
4.5 本章小结... - 17 -
第5章 链接... - 18 -
5.1 链接的概念与作用... - 18 -
5.2 在Ubuntu下链接的命令... - 18 -
5.3 可执行目标文件hello的格式... - 18 -
5.4 hello的虚拟地址空间... - 21 -
5.5 链接的重定位过程分析... - 22 -
5.6 hello的执行流程... - 25 -
5.7 Hello的动态链接分析... - 25 -
5.8 本章小结... - 26 -
第6章 hello进程管理... - 27 -
6.1 进程的概念与作用... - 27 -
6.2 简述壳Shell-bash的作用与处理流程... - 27 -
6.3 Hello的fork进程创建过程... - 27 -
6.4 Hello的execve过程... - 28 -
6.5 Hello的进程执行... - 28 -
6.6 hello的异常与信号处理... - 29 -
6.7本章小结... - 34 -
第7章 hello的存储管理... - 35 -
7.1 hello的存储器地址空间... - 35 -
7.2 Intel逻辑地址到线性地址的变换-段式管理... - 35 -
7.3 Hello的线性地址到物理地址的变换-页式管理... - 36 -
7.4 TLB与四级页表支持下的VA到PA的变换... - 37 -
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的实现分析... - 42 -
8.5本章小结... - 43 -
结论... - 43 -
附件... - 45 -
参考文献... - 46 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P From Program to Process
编写源程序 hello.c(源程序)
经过cpp预处理器进行预处理 hello.i(修改了的源程序)
经过ccl编译器进行编译处理 hello.s(汇编程序)
经过as汇编器进行汇编处理 hello.o(可重定位目标程序)
经过ld连接器将标准c库中的printf.o函数进行合并 hello(可执行目标程序)
经过shell进行运行处理 进程将为其fork()一个子程序
020 From Zero to Zero
经过execve加载器载入,建立虚拟内存映射。CPU为其分配相应的时间分片
CPU上的内存管理单元MMU根据页表将CPU生成的虚拟地址翻译成物理地址,将相应的页面调度
printf调用malloc进行动态内存分配
程序终结后后,内核向父进程发送SIGCHLD信号,此时终止的hello被父进程回收
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位
开发工具:gcc gdb objdump vscode readlf HexEdit
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i 预处理之后的源程序
hello.s 编译之后的汇编程序
hello.o 汇编之后的可重定位目标程序
hello.elf hello.o的ELF格式
helloexe.elf hello的ELF格式
hello 可执行目标程序
hello2.s 反汇编后输出的程序
Helloobj.txt Hello可执行程序的反汇编代码
Temp.c 临时数据存放
1.4 本章小结
本章节我们简单的讲述了hello程序P2P与020的过程,对程序的编写到运行有了初步的认识。
我们提供了这次实验需要的软硬件环境与开发工具的信息
对程序从编写到运行所有的中间文件有了详细的了解
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理:根据以字符#开头的编译预处理指令,进行代码文本编译前的处理并且最后生成.i的文本文件。
作用:
文件包含命令,引入对应头文件
设定编译器的状态或者是指示编译器完成一些特定的动作
将宏名替换为字符串(宏替换)
2.2在Ubuntu下预处理的命令
cpp hello.c > hello.i
应截图,展示预处理过程!
2.3 Hello的预处理结果解析
通过上述的内容我们可以发现进行预处理后用绝对路径替代了.h头文件的预处理,并引入了标准c库中的一些数据类的声明和结构体的定义,同时还有对外部函数的引用,除此之外就没有其他的变化。
2.4 本章小结
通过本章我们了解到了预处理器将hello.c进行预处理形成hello.i的第一步,对预处理有了较深的了解。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译便是将程序转化为指令集
编译器(ccl)将后缀为.i的文本文件翻译成后缀为.s的文本文件
不同的高级语言经过编译器编译后都会输出统一的汇编代码
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
gcc –S hello.i –o hello.s
应截图,展示编译过程!
3.3 Hello的编译结果解析
3.3.1 伪指令
.file 声明源文件 “hello.c”
.text 代码段
.rodata 只读数据段
.align声明对齐方式 8 进行双字对齐
3.3.2 只读数据段
在此段我们可以读出函数常用字符串
其格式为Hello %s %s\n
声明为:"\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201"
3.1.3 main函数
两个参数存储在寄存器%rdi和%rsi中
将两个参数入栈,将第一个参数与立即数4进行比较
若相等则跳转到L2 否则输出参数并退出
L2 将局部变量赋值为0并跳转到L3
L3 局部变量i与立即数7进行比较
若小于等于则跳转到L4 否则调用getchar函数并返回
L4 调用printf函数打印字符串数组的第一和第二个元素 调用sleep函数并将字符串第三个元素作为参数传入 局部变量i进行加1操作
3.3.3数据
局部变量存放在栈中
输入参数存放在%rdi与%rsi当中
带打印的String存放在.string节中
argc, *argv参数局部变量存放在栈中
3.3.2 赋值
用movx src,dec,由x控制字长
如图通过movl将参数传入栈中
3.3.3运算
加法addx 操作数1,操作数2
减法subx 操作数1,操作数2
3.3.4if条件语句判断
Cmpx操作数1,操作数2 jxx条件跳转语句实现,往下跳
3.3.5循环控制语句
Cmpx操作数1,操作数2 jxx条件跳转语句实现,往上跳
3.3.6关系运算
Cmpx操作数1,操作数2
3.3.7函数返回
pushq %rbp,将上一个栈顶地址压栈,为函数返回时,出栈做准备
通过%rax 返回返回值
3.3.8参数传递,通过栈底指针间接寻址,暂时存放在寄存器%rdi,%rsi中
3.3.9函数调用
call 函数名,在调用前,准备好参数,存放在寄存器%rdi, %rsi当中
3.3.10数组
通过偏移地址+基址寻址
movq -32(%rbp), %rax
addq $16, %rax
3.4 本章小结
本章我们学会了编译器是如何处理程序的 同时我们接触了一些基本指令集的汇编代码 如数据:常量、变量的存放,通过movx的赋值 = ,算术操作:+、 - 、++,关系操作: != 、<=,数组/指针的引用:A[i]、*p 控制转移:if、for的使用,函数操作:参数传递(地址/值)、函数调用()、函数返回 return的实现。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编:通过汇编器将.s后缀文件翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在.o后缀文件中。
通过汇编翻译成机器语言指令后机器可以进行直接的读取与分析
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
elf头 表示大小端信息、字的大小,文件的类型,程序头起点,段表头起点等信息
节头部表 表示各个节名称、类型、起始地址和偏移量等信息。
重定位节 表示重定位的偏移量,类型。符号值等信息
偏移量:需要进行重定向的代码在.text或.data节中的偏移位置.
信息:包括symbol和type两部分,其中symbol占前4个字节,type占后4个字节,symbol代表重定位到的目标在.symtab中的偏移量,type代表重定位的类型:由信息决定的替换类型
符号名称:重定位目标的名字
加数:重定位过程需要加减的常量
符号表 表示程序中定义与引用的函数与全局变量的信息
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
objdump -d -r hello.o > objdump.txt
两者间的差异
hello.s文件中使用.L2, .L3等注记符 在反汇编汇编语言中,分支转移时的跳转目标地址为相对偏移量
在hello.s中操作数采用十进制表示 反汇编后使用16进制表示
Hello.s中直接使用函数名来进行函数调用 反汇编后使用相对偏移地址来调用指令
Hello.s使用标记符来访问全局变量 反汇编后使用rodata节的偏移量来表示
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
这一张我们通过编译将.s文件转化为.o文件准备下一步的链接操作。
我们进行了反汇编并与编译后的代码进行对比 我们发现进行反汇编后分支函数调用 操作数 全局变量的访问均发生了变化
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接:通过链接器,将程序调用的外部函数(.o文件)与当前.o文件进行合并,并得到可执行目标文件。
作用:链接模块是实现分离编译的基础
为模块化管理代码带来了便利
注意:这儿的链接是指从 hello.o 到hello生成过程。
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
5.3 可执行目标文件hello的格式
Readelf -a hello > hello.elf
ELF头 表示机器大小端信息,类型,系统结构,入口点地址,段头表地址,程序头等地址
节头 表示各节的信息,包括名称、类型、起始地址等。这些节已经被重定位至最终运行时的地址
程序头部表 描述了可执行文件连续的片和连续的内存段的映射关系。其中包括段:
PHDR:程序头表
INTERP:程序执行前需要调用的解释器
LOAD:程序目标代码和常量信息
DYNAMIC:动态链接器使用信息
NOTE:保存辅助信息
GNU_EH_FRAME:保存异常信息
GNU_STACK:标志栈是否可用的标志信息
GNU_RELRO:保存在重定位之后只读信息的位置
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
5.4 hello的虚拟地址空间
通过上述我们可以发现init函数的地址为:40100
我们使用edb进行hello的加载来查看其地址空间信息
便可以发现与elf文件中所示一致
我们查看elf文件可知程序头表PHDR的地址为 00400040
通过edb进行查看发现结果一致
5.5 链接的重定位过程分析
objdump -d -r hello > hellolink.txt
分析hello与hello.o的不同,说明链接的过程。
hello.o的汇编代码中只出现了main函数的名字 hello的汇编代码中出现了_init,.plt等函数名
在hello.o汇编代码中函数的地址不确定 在hello汇编代码中地址确定下来
在hello.o汇编代码中很多数据的地址不确定,在hello汇编代码中地址确定下来
结合hello.o的重定位项目,链接器将符号解析完成后,就将代码中的所有符号引用都与一个符号定义关联。这样链接器就可以按照各个内容的具体大小来进行重定位,合并输入模块,并为每个符号分配运行时的地址。
5.6 hello的执行流程
ld-2.27.so!_dl_start 0x7fce 8cc38ea0
ld-2.27.so!_dl_init 0x7fce 8cc47630
hello!_start 0x400500
libc-2.27.so!__libc_start_main 0x7fce 8c867ab0
-libc-2.27.so!__cxa_atexit 0x7fce 8c889430
-libc-2.27.so!__libc_csu_init 0x4005c0
hello!_init 0x400488
libc-2.27.so!_setjmp 0x7fce 8c884c10
-libc-2.27.so!_sigsetjmp 0x7fce 8c884b70
–libc-2.27.so!__sigjmp_save 0x7fce 8c884bd0
hello!main 0x400532
hello!puts@plt 0x4004b0
hello!exit@plt 0x4004e0
*hello!printf@plt
*hello!sleep@plt
*hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce 8cc4e680
-ld-2.27.so!_dl_fixup 0x7fce 8cc46df0
–ld-2.27.so!_dl_lookup_symbol_x 0x7fce 8cc420b0
libc-2.27.so!exit 0x7fce 8c889128
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
程序调用一个由共享库定义的函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。GNU编译系统使用延迟绑定的技术解决这个问题,将过程地址的延迟绑定推迟到第一次调用该过程时。
延迟绑定要用到全局偏移量表(GOT)和过程链接表(PLT)两个数据结构。如果一个目标模块调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT。PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,跳转到动态链接器中。每个条目都负责调用一个具体的函数。PLT[[1]]调用系统启动函数 (__libc_start_main)。从PLT[[2]]开始的条目调用用户代码调用的函数。GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[0]和GOT[[1]]包含动态链接器在解析函数地址时会使用的信息。GOT[[2]]是动态链接器在ld-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析
所以我们首先需要找到GOT的位置
在edb来查看引用前与引用后的差别
引用前:
5.8 本章小结
本章我们进行链将多个代码、数据合并为一个可执行文件的过程
我们进行了hello虚拟空间的展示
进行了反汇编的查看
对重定位进行了深入的分析。
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程: 指程序的一次运行过程。进程是具有独立功能的一个程序关于某个数据集合的一次运动活动,因为进程具有动态含义。进程具有自己的生命周期。
进程的作用:提供了一个独立的逻辑控制流和一个私有的虚拟地址空间。进程的引入简化了程序员的编程以及语言处理系统的处理。
6.2 简述壳Shell-bash的作用与处理流程
Shell 是一种命令行解释器, 其读取用户输入的字符串命令, 解释并且执行命令. 它是一种特殊的应用程序, 介于系统调用/库与应用程序之间, 其提供了运行其他程序的的接口.它可以是交互式的, 即读取用户输入的字符串;也可以是非交互式的, 即读取脚本文件并解释执行, 直至文件结束. 无论是在类 UNIX, Linux 系统, 还是 Windows, 有很多不同种类的 Shell: 如类 UNIX, Linux 系统上的 Bash, Zsh 等; Windows 系统上的 cmd, PowerShell 等.shell可以合并编程语言以控制进程和文件,以及启动和控制其他程序。shell能够减少大量的重复输入和交互操作,能够进行批量的处理和自动化完成维护,减轻管理层的负担。
Shell-bash处理流程:
(1)从终端读入输入的命令。
(2)将输入字符串切分,分析输入内容,解析命令和参数。
(3)如果命令为内置命令则立即执行,如果不是内置命令则创建新的进程调用相应的程序执行。
(4)在程序执行期间始终接受键盘输入信号,并对输入信号做相应处理。
6.3 Hello的fork进程创建过程
在终端输入./hello
由于./hello不是终端命令,而是当前目录下的可执行文件,于是终端将会调用fork函数在当前进程中创建一个新的子进程。子进程得到与父进程用户级虚拟地址空间相同的副本,包括代码、数据、堆、共享库和用户栈。
6.4 Hello的execve过程
子进程创建成功后,再通过execve系统调用启动加载器,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的堆和栈段被初始化为零。通过虚拟地址空间中的页映射到可执行文件的页大小的片。新代码被初始化为可执行文件的内容。最后加载器跳转到__start地址,它最终会调用应用程序的main函数。
6.5 Hello的进程执行
上下文是指内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象构成。
时间片是指一个进程执行它的控制流的一部分的每一个时间段。
调度是指在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。
用户态是指进程运行在用户模式中时,不允许执行特权指令,比如停止处理器、改变模式位,或者发起一个I/O操作,也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。
核心态是指进程运行在内核模式中时,可以执行指令集中的任何指令,并且可以访问内存中的任意位置。
用户态与核心态转换是指程序在涉及到一些操作时,例如调用一些系统函数,内核需要将当前状态从用户态切换到核心态,执行结束后再改回用户态。
shell进程在运行,等待命令行上的输入。
当我们运行hello程序时,shell通过系统调用来执行我们的请求。系统调用会将控制权传递给操作系统。
操作系统保存shell进程的上下文,依次通过fork,execve创建一个新的hello进程及其上下文,然后将控制权传递给新的hello进程。
此时hello在用户模式,当sleep函数调用时,触发陷阱,内核保存hello的上下文,加载其它进程上下文,控制传递给其它进程,此时hello进入内核模式。
当sleep结束,定时器发送中断信号,系统接收这个信息,hello切换回用户模式。调用getchar函数时,触发陷阱,hello进入内核模式,输入一个字符后触发键盘中断,hello回到用户模式并执行下一个程序return 0,程序结束。
hello程序结束后,操作系统恢复shell进程的上下文,并将控制权传回给shell,shell进程会继续等待下一个命令行输入。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.6 hello的异常与信号处理
异常是指为响应某个事件将控制权转移到操作系统内核中的情况
Hello执行过程中会出现如下的异常:
陷阱 故障 终止 中断
Hello执行过程中会产生如下的信号:
SIGSTP SIGCONT SIGKILL SIFGINT
陷阱 有意的,执行指令的结果 § Examples: 系统调用(System Call),用户程序和内核之间 的一个接口 § 陷阱处理程序将控制返回到下一条指令
故障 (Faults) § 不是有意的,但可能被修复 § Examples: 缺页(可恢复),保护故障(protection faults,不 可恢复), 浮点异常(floating point exceptions) § 处理程序要么重新执行引起故障的指令(已修复),要么 终止
终止 (Aborts) § 非故意,不可恢复的致命错误造成 § Examples: 非法指令,奇偶校验错误(parity error),机器检 查(machine check) § 中止当前程序
中断 处理器外部I/O设备引起 § 由处理器的中断引脚指示 § 中断处理程序返回到下 一条指令处
不停乱按
回车
Ctrl-c
Ctrl-z
Ctrl-z后运行ps
Ctrl-z后运行jobs
Ctrl-z后运行pstree
Ctrl-z后运行fg
Ctrl-z后运行kill
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
6.7本章小结
本章节我们学习了进程与shell的概念以及作用
我们进行了hello程序被父程序fork一个子程序,被execve加载到上下文进行模式切换。
我们学习了异常处理与信号处理
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:由选择符和偏移量组成,经过寻址方式的计算或变换得到内存储器中的物理地址。表示为 [段标识符:段内偏移量]。hello程序经过编译后出现在汇编代码中的地址。
线性地址:某地址空间中的地址是连续的非负整数
虚拟地址:CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前先通过MMU转换为物理地址。
物理地址:程序运行时加载到内存地址寄存器中的地址,是物理内存意义上真正的地址。表示hello程序运行时某指令或某数据在内存地址上具体的地理位置。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理是实现逻辑地址到线性地址转换机制的基础,段的特征有段基址、段限长、段属性。这三个特征存储在段描述符中,用以实现从逻辑地址到线性地址的转换。段描述符存储在段描述符表中,通常,我们使用段选择符定位段描述符在这个表中的位置。每个逻辑地址由16位的段选择符和32位的偏移量组成。
段基址规定了线性地址空间中段的开始地址。在保护模式下,段基址长32位。因为基址长度和寻址地址的长度相同,所以段基址可以是0-4GB范围内的任意地址。
和一个段有关的信息需要8个字节来描述,这就是段描述符。为了存放这些描述符,需要在内存中开辟出一段空间。在这段空间里所有的描述符都在一起集中存放,这就构成了一个描述符表,描述符表分为两种,GDT和LDT。
一些全局的段描述符,就放在"全局段描述符表(GDT)"中,一些局部的,例如每个进程自己的段描述符,就放在的"局部段描述符表(LDT)"中。
介绍一个完整的变换过程,给出一个完整的逻辑地址[段选择符:段内偏移地址]。首先看段选择符判断当前转换时GDT中的段还是LDT中的段,再根据相应寄存器得到其地址和大小。之后拿出段选择符中的前13位,在对应地址中查找到对应的段描述符,这样就知道了基址。根据基址和偏移量结合,就得到了所求的线性地址。
段地址转换过程:
1.IA-32首选确定要访问的段,然后决定使用的段寄存器。
2.根据段选择符号的TI字段决定是访问GDT还是LDT,他们的首地址则通过GTDR和LDTR来获得。
3.将段选择符的Index字段的值*8,然后加上GDT或LDT的首地址,就能得到当前段描述符的地址。(乘以8是因为段描述符为8字节)
4.得到段描述符的地址后,可以通过段描述符中BASE获得段的首地址。
5.将逻辑地址中32位的偏移地址和段首地址相加就可以得到实际要访问的物理地址
7.3 Hello的线性地址到物理地址的变换-页式管理
分页机制是实现虚拟存储的关键,位于线性地址与物理地址的变换之间设置。虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址,作为到数组的索引。磁盘上数组的内容被缓存在主存中。和存储器层次结构中其他缓存一样,磁盘上的数据被分割成块,这些块作为磁盘和主存之间的传输单元。VM系统通过将虚拟内存分割为称为虚拟页为大小固定的块来处理这个问题。每个虚拟页的大小固定。类似地,物理内存被分割为物理页,大小与虚拟页相同。
同任何缓存一样,虚拟内存系统必须用某种方法来判定一个虚拟页是否缓存在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理内存中选择一个牺牲页,并将虚拟页从磁盘复制到DRAM,替换这个牺牲页。
页表是一个存放在物理内存中的数据结构,将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时读取页表。操作系统负责维护页表中的内容,以及再磁盘与DRAM之间来回传送页。
一级页表中的每个PTE负责映射虚拟地址空间中的一个内存片,这里每个片都是由连续的页面组成,如PTE0映射第一片,PTE1映射第二片,以此类推。最后PTE即为对应的PPN,再结合原先VA中的偏移量VPO=PPO,将PPN与PPO结合,即为所求的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
CPU生成一个VA,到MMU的TLB中寻找相应的VPN,若命中,则读取相应的PPN与原VA中的VPO结合即为所求的PA;
倘若TLB不命中,则将虚拟地址划分为4个VPN和一个VPO,每个VPN i都是从一个i级页表的索引,其中,1≤j≤4.当1≤j≤3时,第j级页表的每个PTE均为执行j+1级页表的基址,第4级页表的每个PTE为物理地址的VPN,而原VA的VPO=PPO,将PTE与PPO结合之后,便是物理地址PA。
7.5 三级Cache支持下的物理内存访问
得到物理地址PA后,通过其访问物理内存,物理地址由CI(组索引)、CT(标记位)、CO(偏移量)组成。首先使用CI进行组索引,如果匹配成功且块的有效位为1则命中,根据数据偏移量CO取出数据返回。如果没有匹配成功则不命中,向下一级缓存中查询数据。查询到数据后,放置策略是如果映射到的组有空闲块则直接放置,否则产生冲突,采用最近最少使用策略驱逐块并替换新块进入。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的页面标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
hello调用execve后,execve在当前进程中加载并运行包含在可执行目标文件中的程序,用hello程序有效地代替了当前程序。当加载并运行可执行目标文件时,需要以下几个步骤:
(1)删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
(2)映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。
(3)映射共享区域。如果hello程序与共享对象(或目标)链接,然后再映射到用户虚拟地址空间中的共享区域内。
(4)设置程序计数器PC。execve做的最后一件事是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:当指令引用一个虚拟内存地址,而该地址相对应的物理页面不在内存中。
缺页中断处理:当缺页异常发生时,处理程序首先判断虚拟地址A是否合法,如果不合法则触发段错误终止进程。如果合法则判断试图进行的内存访问是否合法,如果不合法则出发保护异常终止进程。如果合法则根据页式管理的规则,选择一个牺牲页,用新页替换掉,更新页表并再次触发地址翻译硬件进行翻译。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器分为两种:显式分配器和隐式分配器。显式分配器要求应用显式地释放人设已分配地块。隐式分配器要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾回收器,而自动释放未使用的已经分配的块的过程叫做垃圾收集。
malloc使用的是显式分配器,通过free函数释放已分配的块。
下面分别介绍两种分配器:
(1)隐式空闲链表分配器。我们可以将堆组织为一个连续的已分配块和空闲块的序列,空闲块是通过头部中的大小字段隐含地连接着的,这种结构为隐式空闲表。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块地集合。一个块是由一个字的头部、有效载荷、可能的填充和一个字的脚部,其中脚部就是头部的一个副本。头部编码了这个块的大小以及这个块是已分配还是空闲的。分配器就可以通过检查它的头部和脚部,判断前后块的起始位置和状态。
(2)显示空闲链表分配器。将堆组成一个双向空闲链表,在每个空闲块中,都包含一个pred和succ指针。一种方法是用后进先出(LIFO)的顺序来维护链表,将新释放的块放置在链表的开始处。使用LIFO的顺序和首次适配的放置策略,分配器会最先检查最近使用过的块。在这种情况下,释放一个块可以在常数时间内完成。如果使用了边界标记,那么合并也可以在常数时间内完成。另一种方法是按照地址顺序来维护链表,其中链表中每个块的地址都小于它后继的地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。平衡点在于,按照地址地址排序的首次适配比LIFO排序的首次适配有更高的内存利用率,接近最佳适配的利用。一般而言,显式链表的缺点是空闲块必须足够大,以包含所有需要的指针,以及头部和可能的脚部。这就导致了更大的最小块大小,也潜在的提高了内部碎片的程度。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
本章节我们学习了计算机系统的存储系统
了解到了虚拟内存相关内容
了解到了地址之间的转化
了解到了缺页故障及其处理方法
对动态分配器有了基础的了解
(以下格式自行编排,编辑时删除)
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入输出都被当作相对应文件的读和写。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
打开文件 应用程序要求通过内核打开相应的文件,来宣告它想要访问一个I/O设备
open(const char *pathname, int flags, mode_t mode);
写文件 函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误;返回值0表示EOF;否则,返回值表示的是实际传扫的字节数量
write(int fd, const void *buf, size_t count);
读文件 从内存位置buf复制至多n个字节到描述符fd的当前文件位置
read(int fd, void *buf, size_t count);
重新定位读/写文件偏移量 通过偏移量进行seek操作
lseek(int fd, off_t offset, int whence);
关闭文件 应用程序完成了对文件的访问后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中
close(int fd);
8.3 printf的实现分析
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
以下为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函数的浏览会注意到va_list
va_list的定义如下:
typedef char *va_list
这说明它是一个字符指针。
arg变量定位到了第二个参数,也就是第一个格式串。
我们继续读取会注意到vsprintf()函数
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}
以上为其代码实现,其功能为进行格式化并返回打印出来的字符串的长度
write(buf, i)进行写操作,把buf中的i个元素的值写到终端。
Write的实现较为麻烦
我们先追踪下write:
write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
其中int INT_VECTOR_SYS_CALL表示要通过系统来调用sys_call这个函数
sys_call的实现:
sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
功能为显示格式化了的字符串
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中储存的是字节的ASCII码
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数在stdio.h中声明,代码如下:
int getchar(void)
{
static char buf[BUFSIZ];
static char *bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return (-n>=0)?(unsigned char)*bb++:EOF;
}
bb是缓冲区的开始。
int变量n初始化为0,只有在n为0的情况下从缓冲区读入BUFSIZ个字节。
返回时如果n大于0,那么返回缓冲区的第一个字符。否则返回EOF。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章我们了解了Linux的IO设备管理方法
了解了Unix I/O接口及其函数
学会了printf函数实现的分析和getchar函数的实现。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
Hello所经历的过程
(1)编写程序:源程序hello.c的编写。
(2)预处理:预处理器(cpp)进行符号的替代,生成hello.i文件。
(3)编译:编译器(ccl)将hello.i文件翻译为汇编文件hello.s。
(4)汇编:汇编器(as)将hello.s文件翻译为二进制机器语言,生成可重定位目标文件hello.o。
(5)链接:链接器(ld)将可重定位目标文件hello.o和其他目标文件链接成为可执行文件hello。
(6)运行:通过终端指令的输入进行运行
(7)创建进程:shell进程调用fork函数为hello创建新进程,并调用execve函数运行hello。
(8)访问内存:通过MMU将需要访问的虚拟地址转化为物理地址,并通过缓存系统访问内存。
(9)动态申请内存:hello运行过程中可能会通过malloc函数动态申请堆中的内存。
(10)异常:hello运行过程中可能会产生各种异常和信号,系统会针对出现的异常和收到的信号做出反应。
(11)终止:hello运行结束后内核向父进程发送SIGCHLD信号,hello进程被父进程回收,内核删除相关数据。
Hello是我们学习的第一个程序,它的编写非常简单易学。但是从其编写到终止,它涉及到了计算机系统的每一个层次。每一个代码的运行都是在计算机系统的协助下才能够顺利的运行,我们作为程序员需要了解计算机系统在代码运行过程中的协助作用,我们可以以此来简化我们的代码,避免不必要的错误。
本次实验我们通过回顾hello的一生将这学期以来的所有知识点贯穿起来,从数据的运算处理,到汇编、链接、信号异常的处理、内存管理、再到后面的I/O管理。这其中的每一步都是前辈们花费大量的资源来进行设计与优化的,每一步都体现着计算思维的优越性。我们这次的学习让我对计算机知识的向往更加强烈,计算机系统其中的有趣且神秘的设计思维深深地吸引着我的注意,我希望在以后的学习中我能更加深入了解其中的奥妙,努力早日成为一名计算机科学家!
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c 源文件
hello.i 预处理文件
hello.s 汇编语言文件
hello.o 可重定位目标文件
hello 可执行目标文件
hello.elf hello.o的elf格式文件
objdump.txt hello.o的反汇编文件
hellolink.txt 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分)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
