2022春哈工大计算机系统大作业——hello的程序人生
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号
班 级
学 生
指 导 教 师
计算机科学与技术学院
2021年5月
本文阐述了hello程序的一生,详细描述了Linux系统下hello.c从预处理、编译、汇编到可重定位的目标文件,再链接经过符号解析和重定位之后到可执行文件的过程。同时详细介绍了hello的进程、存储和IO管理,从hello程序的一生中,梳理阐述了计算机系统的相关知识。
关键词:hello程序;预处理;编译;汇编;链接;进程;存储;IO管理
目 录
第1章 概述
1.1 Hello简介
1.2 环境与工具
1.3 中间结果
1.4 本章小结
第2章 预处理
2.1 预处理的概念与作用
2.1.1 预处理的概念:
2.1.2预处理的作用:
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
2.4 本章小结
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念
3.1.2编译的作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1数据
3.3.2赋值
3.3.3算术操作
3.3.4 关系操作
3.3.5 控制转移
3.3.6数组/指针/结构操作
3.3.7函数操作
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.3.1readelf操作
4.3.2ELF头
4.3.3节头目表
4.3.4重定位节
4.3.5符号表
4.4 Hello.o的结果解析
4.4.1 objdump操作
4.4.2汇编代码比较
4.5 本章小结
第5章 链接
5.1 链接的概念与作用
5.1.1链接器的概念
5.1.2链接器的作用
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
5.4 hello的虚拟地址空间
5.5 链接的重定位过程分析
5.5.1hello反汇编指令与操作
5.5.2hello与hello.o的反汇编文件的不同
5.5.3hello重定位地址计算
5.6 hello的执行流程
5.7 Hello的动态链接分析
5.8 本章小结
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1进程的概念
6.1.2进程的作用
6.2 简述壳Shell-bash的作用与处理流程
6.3 Hello的fork进程创建过程
6.4 Hello的execve过程
6.5 Hello的进程执行
6.5.1逻辑控制流
6.5.3上下文
6.5.4调度的过程
6.5.5用户态与核心态转换
6.6 hello的异常与信号处理
6.6.1异常的类型和处理流程
6.6.2正常运行状态
6.6.3按下Ctrl+Z:
6.6.4按下Ctrl+C
6.6.5中途乱按:
6.7本章小结
第7章 hello的存储管理
7.1 hello的存储器地址空间
7.1.1逻辑地址
7.1.2线性地址
7.1.3虚拟地址
7.1.4物理地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
7.7 hello进程execve时的内存映射
7.8 缺页故障与缺页中断处理
7.9动态存储分配管理
7.10本章小结
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
8.1.1设备的模型化
8.1.2设备管理
8.2 简述Unix IO接口及其函数
8.2.1Unix IO接口
8.2.2Unix IO函数
8.3 printf的实现分析
8.4 getchar的实现分析
8.5本章小结
结论
附件
参考文献
第1章 概述
1.1 Hello简介
P2P,即Program to Process;Hello的P2P即Program hello.c to Process
具体过程为:
- linux下cpp预处理器将hello.c文件进行预处理,插入对应系统头文件中的内容,得到hello.i;
- 编译器ccl将其译为汇编语言文件hello.s;
- 汇编器as负责将汇编语言翻译为机器指令代码,得到hello.o文件;
- 链接器ld将hello.o和其它用到的预编译好的目标文件合并到一起并且完成引用的重定位工作,得到可执行文件hello。
(5)shell中输入./hello执行hello程序,调用fork函数创建子进程,并且在子进程中加载该程序,由此,hello.c文件就成为了一个进程。
Hello的020过程:
子进程调用execve加载hello执行,操作系统OS映射虚拟内存至物理内存,CPU为运行此进程分配时间,通过cache,TLB等机制加速访问时间,程序结束后,shell父进程通过相应的信号机制收到子进程结束信息并且回收子进程,内核将控制权转移回shell,子进程相关数据被清除。
1.2 环境与工具
硬件环境:CPU个数:1 物理核数:8 逻辑处理器个数:16
具体信息如下图所示:
软件环境:VirtualBox 6.1.18 Ubuntu20.04.2LTS Windows10 64位
开发工具:VIM8.1.2269 Visual Studio Code1.54.3
1.3 中间结果
文件名 |
文件作用 |
hello.i |
预处理后的ASCII码文件 |
hello.s |
编译之后得到的文件 |
hello.o |
汇编之后得到的目标文件 |
hello |
链接之后得到的可执行文件 |
Hello.out |
反汇编之后得到的可重定位文件 |
helloELF.txt |
目标文件hello.o的ELF格式 |
helloELF1.txt |
可执行文件hello的ELF格式 |
1.4 本章小结
本章介绍hello.c的P2P和O2O过程,及其中的具体转换细节。并介绍了本次大作业的硬件、软件环境和使用到的开发工具,最后列举了完成大作业过程中生成的中间文件。
第2章 预处理
2.1 预处理的概念与作用
2.1.1 预处理的概念:
2.1.2预处理的作用:
(1)处理头文件:cpp预处理器将头文件插入对应指令的位置,从而修改源文件。
(2)处理宏定义:用实际值或者是代码去替换#define定义的宏。
(3)处理条件编译指令:#ifef等相关语句代表需要判断条件编译等情况。
2.2在Ubuntu下预处理的命令
指令:gcc hello.c -E -o hello.i
生成文件如下:
2.3 Hello的预处理结果解析
预处理后hello.c文件转化为hello.i文件,原本28行的代码被扩展为3060行,文件内容添加,对原文件中的宏进行了宏展开,并添加了头文件中的内容。原本的注释被删除,原本的代码被放置在3043-3060行,没有太大变化。
hello.i中包含调用头文件的地址,其在原有代码的基础上,将头文件stdio.h的内容引入,例如声明函数、定义结构体、定义变量、定义宏等内容:
2.4 本章小结
本章主要介绍了预处理的概念及作用,展示了Linux系统下的预处理指令的具体操作,通过分析得到的hello.i文本,了解的cpp预处理器在执行预处理指令时候进行的具体工作,包括插入头文件,扩展宏定义,删除注释等内容。
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念
编译就是把高级语言变成汇编语言或机器语言的过程,编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。
本次大作业中,我们需要让编译器将预处理之后得到的hello.i文本文件翻译成hello.s文本文件。
3.1.2编译的作用
编译主要是为了将高级语言翻译为机器语言一一对应的汇编语言,部分步骤如下:
代码优化:将中间表示形式进行分析并转换为功能等价但是运行时间更短或占用资源更少的等价中间代码。
3.2 在Ubuntu下编译的命令
指令:gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1数据
我们发现,hello.c文件中有两处printf中的字符串,具体信息如下:
编译后得到的hello.s文件中我们可以找到常量字符串对应的存储位置,并用.LCO和.LC1代表字符串的首地址:
可以看到汉字变为以utf-8的格式进行编码,每个汉字3字节,包括中文感叹号也使用了utf-8编码,占3字节。
(2)整形常量
整形常量在hello.c文件中是以实数的形式存在,例如:
if(argc!=4){
for(i=0;i<8;i++){
这些常量在汇编代码中变成了立即数,与寄存器中的值相比较或存储在相应的寄存器中:
(3)局部变量
hello.c中的局部变量有int i,参数argc,argv
(1)在汇编代码中可以发现i被分配在运行时栈上,初始值为0
其值小于等于7时执行循环。
- 参数argc,argv
可以看到argc存储在寄存器edi中,argv存储在寄存器rsi中,使用时又存在了栈中,栈指针分别为%rbp-20和%rbp-32。
3.3.2赋值
3.3.3算术操作
3.3.4 关系操作
(1)对于表达式i<8的值的判断,通过cmp指令和jle指令组合完成对于for循环体是否循环的判断,从而完成循环体的跳转。
(2)对于表达式if(argc!=4)的判断,通过cmpl和je组合判断完成if语句的跳转。
3.3.5 控制转移
Hello.c中的控制转移包括if分支和for循环,分别对应两次关系操作:
如下图所示,将argc的值与4比较,如果不相等,跳转到L4,相等则顺序执行,控制转移通过jle实现:
for循环在.L2处对循环变量i进行初始化,然后通过jmp跳转到.L3,在.L3中进行了循环终止条件的判断,如果i<=7,则跳转到循环体部分.L4,否则顺序执行。
3.3.6数组/指针/结构操作
在main函数中传递的参数出现了指针数组
char *argv[])每一个数组的元素都是一个char*类型的指针,可以指向一个字符串,而具体来说argv[0]指向文件名,argv[1] argv[2]分别指向命令行输入的第一个和第二个参数,在汇编代码中这个指针数组的首地址被存放在rsi中,也就是存放main函数的第二个参数的寄存器。
对于指针数组argv,其每一个元素的大小都是8字节,而且数组中的元素应是连续存放的,因此址偏移量也应该是8的倍数
从此处的代码我们可以验证这一点。
3.3.7函数操作
main函数汇编代码中有两个参数argc和argv分别被存放在寄存器rdi和rsi中:
对于argv的操作,其指针数组的基地址通过寄存器存放,将基地址加上8的倍数的偏移量就可以访问指针数组的其它成员指针,用时为了访问每一个指针成员指向的字符串,我们需要访问每一个指针指向的内存空间,即每个指针成员指向的字符串是存放在内存中的。
Main函数是由系统函数调用的,同时main函数也可以调用别的函数,但是需要遵从寄存器保护的规则和参数传递的规则。Main函数返回值是int类型的,存储在寄存器rax中,在需要返回时,先将rax的值设置为0,然后返回即可,对应于hello.c文件中的return 0.
结合之前的代码我们可以发现,rax虽然被用作传递返回值,但是在此之前都可以被main函数自由使用。
Printf函数
Hello.c中,调用了2次printf函数:
printf("用法: Hello 学号 姓名 秒数!\n");
printf("Hello %s %s\n",argv[1],argv[2]);第一次调用的汇编代码部分如下:
我们看到,第一次实际上是调用了puts函数,这是因为此次调用printf函数不需要传递额外参数,只需将内存中存储的字符串复制到屏幕上,因此编译器做了等价的替换。第二次调用的汇编代码如下:
总共传递了三个参数,所以无法使用puts函数替换,第一个参数是主体字符串的首地址,存放在rdi中,第二、第三个参数是替换的字符串的首地址,分别通过寄存器rsi和rdx传递。
Exit函数
Exit函数的源代码如下:
if(argc!=4){
printf("用法: Hello 学号 姓名 秒数!\n");
exit(1);
}
对应的汇编代码如下:
我们可以看到函数传递的参数存放在edi中,对应了源代码中的exit(1),说明传递的整数值直接作为函数退出的状态值。
Sleep函数
Sleep函数的源代码如下:
for(i=0;i<8;i++){
printf("Hello %s %s\n",argv[1],argv[2]);
sleep(atoi(argv[3]));
}
对应的汇编代码如下:
可以看到sleep函数通过edi传递了一个参数作为休眠时间的控制。
Getchar函数
Getchar函数的汇编代码如下:
此函数无参数,不需要通过寄存器进行参数传递,。
3.4 本章小结
本章介绍编译的概念、作用及具体实现过程。对hello.i文件进行编译得到hello.s文本文件,并逐行分析汇编代码,使我们更加深刻的了解了常量和变量是如何进行存储运算的,非线性执行的跳转语句是如何进行的,以及在实现跳转的基础上如何进行逻辑控制实现循环。在跳转的基础上,我们也了解了函数如何进行调用,调用函数时如何进行参数传递,包括这个过程中寄存器的使用方法。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:运行汇编器as,将汇编语言翻译成机器语言的过程称为汇编,得到可重定位目标文件。在本次大作业中,此过程就是将hello.s文件翻译成hello.o文件。
汇编的作用:汇编就是将高级语言转化为机器可直接识别执行的代码文件的过程,汇编器将汇编程序(hello.s)翻译成机器语言指令目标文件(hello.o),把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中,目标文件是一个二进制文件,它包含程序的指令编码。
4.2 在Ubuntu下汇编的命令
汇编指令:gcc -c hello.s -o hello.o
输入指令过程及生成文件如下:
4.3 可重定位目标elf格式
4.3.1readelf操作
用readelf指令:readelf -a hello.o > helloELF.txt
4.3.2ELF头
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Data: 2's complement, little endian
Machine: Advanced Micro Devices X86-64
Start of program headers: 0 (bytes into file)
Start of section headers: 1240 (bytes into file)
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Size of section headers: 64 (bytes)
Section header string table index: 13
可以看到,其中包含了包含了版本和系统信息、编码方式、节的大小和数量、ELF头大小和头部索引等等一系列信息。
4.3.3节头目表
节头目表包含了各个节的起始地址和偏移量等信息,具体内容如下:
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000092 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000388
00000000000000c0 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 000000d2
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 000000d2
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 000000d8
0000000000000033 0000000000000000 A 0 0 8
[ 6] .comment PROGBITS 0000000000000000 0000010b
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000137
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 00000138
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 00000158
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000448
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000190
00000000000001b0 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000340
0000000000000048 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 00000460
0000000000000074 0000000000000000 0 0 1
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
.rela.text节:一个.text节中的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
.data节:已初始化的静态和全局C变量。类型为PROGBITS,意为程序数据,旗标为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.4重定位节
汇编器遇到对最终位置未知的目标引用,就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
Relocation section '.rela.text' at offset 0x388 contains 8 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000001c 000500000002 R_X86_64_PC32 0000000000000000 .rodata - 4
000000000021 000c00000004 R_X86_64_PLT32 0000000000000000 puts - 4
00000000002b 000d00000004 R_X86_64_PLT32 0000000000000000 exit - 4
000000000054 000500000002 R_X86_64_PC32 0000000000000000 .rodata + 22
00000000005e 000e00000004 R_X86_64_PLT32 0000000000000000 printf - 4
000000000071 000f00000004 R_X86_64_PLT32 0000000000000000 atoi - 4
000000000078 001000000004 R_X86_64_PLT32 0000000000000000 sleep - 4
000000000087 001100000004 R_X86_64_PLT32 0000000000000000 getchar - 4
Relocation section '.rela.eh_frame' at offset 0x448 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
可以看到,重定位节中有各种引用的外部符号,给出了他们的偏移量。我们还可以看到重定位节中的外部符号包括了全局变量,以及调用的函数。
有了这些信息之后,在下一步进行链接,就可以通过重定位节对这些位置的地址进行重定位,使其映射到虚拟内存上。
*refptr = (unsigned) (ADDR(r.symbol) + r.addend – refaddr);
*refptr = (unsigned) (ADDR(r.symbol) + r.addend);
4.3.5符号表
Symbol table '.symtab' contains 18 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 9
9: 0000000000000000 0 SECTION LOCAL DEFAULT 6
10: 0000000000000000 146 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND atoi
16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND sleep
17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND getchar
No version information found in this file.
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.4.1 objdump操作
指令:objdump -d -r hello.o > hello1.txt
4.4.2汇编代码比较
Hello.s中的操作数采用10进制编码;
反汇编文件中的操作数用16进制编码。
(2)跳转语句不同
Hello.s中的跳转采用跳转到.L2等方式进行跳转;
反汇编文件中采用直接跳转计算出地址,跳转位置为主函数起始地址加上偏移量。
(3)函数调用机制不同
Hello.s中的函数调用采用跳转到函数名的方式进行跳转;
反汇编文件中函数调用采用直接跳转,跳转位置为主函数起始地址加上偏移量。
(4)有无对应机器码
hello.s:只有汇编代码
反汇编代码:有对应的机器码
4.5 本章小结
本章内容主要介绍了汇编的概念、作用和基本流程,了解到汇编语言文件是如何通过汇编器翻译成机器可识别的机器语言的,分析了直接编译hello.c文件得到hello.s中的汇编代码和反汇编hello.o文件得到的汇编代码的区别。使我们更加深刻的理解了汇编的基本机制。
第5章 链接
5.1 链接的概念与作用
5.1.1链接器的概念
链接就是将前一步得到的各种不同文件的代码段和数据段重新整合成两个片段,并且完成相应的符号解析和重定位的,将其组织成一个可执行文件的过程。,这个文件可以被加载到内存并执行。
5.1.2链接器的作用
5.2 在Ubuntu下链接的命令
命令如下:
生成文件如下:
5.3 可执行目标文件hello的格式
采用指令如下:
生成文件如下:
ELF的文件头如下所示:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4010f0
Start of program headers: 64 (bytes into file)
Start of section headers: 14208 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 12
Size of section headers: 64 (bytes)
Number of section headers: 27
Section header string table index: 26
可以看到里面的信息只要有系统版本,框架,大小,起止位置,此节大小等信息。
节头信息如下:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000004002e0 000002e0
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000400300 00000300
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.ABI-tag NOTE 0000000000400320 00000320
0000000000000020 0000000000000000 A 0 0 4
[ 4] .hash HASH 0000000000400340 00000340
0000000000000038 0000000000000004 A 6 0 8
[ 5] .gnu.hash GNU_HASH 0000000000400378 00000378
000000000000001c 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 0000000000400398 00000398
00000000000000d8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000400470 00000470
000000000000005c 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 00000000004004cc 000004cc
0000000000000012 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 00000000004004e0 000004e0
0000000000000020 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000400500 00000500
0000000000000030 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000400530 00000530
0000000000000090 0000000000000018 AI 6 21 8
[12] .init PROGBITS 0000000000401000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000401020 00001020
0000000000000070 0000000000000010 AX 0 0 16
[14] .plt.sec PROGBITS 0000000000401090 00001090
0000000000000060 0000000000000010 AX 0 0 16
[15] .text PROGBITS 00000000004010f0 000010f0
0000000000000145 0000000000000000 AX 0 0 16
[16] .fini PROGBITS 0000000000401238 00001238
000000000000000d 0000000000000000 AX 0 0 4
[17] .rodata PROGBITS 0000000000402000 00002000
000000000000003b 0000000000000000 A 0 0 8
[18] .eh_frame PROGBITS 0000000000402040 00002040
00000000000000fc 0000000000000000 A 0 0 8
[19] .dynamic DYNAMIC 0000000000403e50 00002e50
00000000000001a0 0000000000000010 WA 7 0 8
[20] .got PROGBITS 0000000000403ff0 00002ff0
0000000000000010 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000404000 00003000
0000000000000048 0000000000000008 WA 0 0 8
[22] .data PROGBITS 0000000000404048 00003048
0000000000000004 0000000000000000 WA 0 0 1
[23] .comment PROGBITS 0000000000000000 0000304c
000000000000002b 0000000000000001 MS 0 0 1
[24] .symtab SYMTAB 0000000000000000 00003078
00000000000004c8 0000000000000018 25 30 8
[25] .strtab STRTAB 0000000000000000 00003540
0000000000000158 0000000000000000 0 0 1
[26] .shstrtab STRTAB 0000000000000000 00003698
00000000000000e1 0000000000000000 0 0 1
可以看到,节头描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
程序头表信息如下:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000002a0 0x00000000000002a0 R 0x8
INTERP 0x00000000000002e0 0x00000000004002e0 0x00000000004002e0
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000005c0 0x00000000000005c0 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000245 0x0000000000000245 R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x000000000000013c 0x000000000000013c R 0x1000
LOAD 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001fc 0x00000000000001fc RW 0x1000
DYNAMIC 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001a0 0x00000000000001a0 RW 0x8
NOTE 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000320 0x0000000000400320 0x0000000000400320
0x0000000000000020 0x0000000000000020 R 0x4
GNU_PROPERTY 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001b0 0x00000000000001b0 R 0x1
其中,每种结构都描述了系统准备程序执行所需的段或其他信息。
PHDR部分负责保存程序头表;
INTERP指定了在程序映射到内存之后必须调用的解释器;
LOAD表示一个从二进制文件映射到虚拟地址空间的段,其中保存了常量数据、程序的目标代码等等数据;
DYNAMIC保存了由动态链接器使用的信息;
NOTE保存辅助信息;
GNU_STACK是其中的权限标志,用于标识栈是否是可执行的;
GNU_RELRO则是在指定在重定位结束之后哪些内存区域需要设置为只读区域。
重定位节:
重定位条目告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
重定位节的内容如下:
Relocation section '.rela.dyn' at offset 0x500 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000403ff0 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000403ff8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x530 contains 6 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000404018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000404020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000404028 000400000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
000000404030 000600000007 R_X86_64_JUMP_SLO 0000000000000000 atoi@GLIBC_2.2.5 + 0
000000404038 000700000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
000000404040 000800000007 R_X86_64_JUMP_SLO 0000000000000000 sleep@GLIBC_2.2.5 + 0
符号表:
Symbol table '.symtab' contains 51 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000004002e0 0 SECTION LOCAL DEFAULT 1
2: 0000000000400300 0 SECTION LOCAL DEFAULT 2
3: 0000000000400320 0 SECTION LOCAL DEFAULT 3
4: 0000000000400340 0 SECTION LOCAL DEFAULT 4
5: 0000000000400378 0 SECTION LOCAL DEFAULT 5
6: 0000000000400398 0 SECTION LOCAL DEFAULT 6
7: 0000000000400470 0 SECTION LOCAL DEFAULT 7
8: 00000000004004cc 0 SECTION LOCAL DEFAULT 8
9: 00000000004004e0 0 SECTION LOCAL DEFAULT 9
10: 0000000000400500 0 SECTION LOCAL DEFAULT 10
11: 0000000000400530 0 SECTION LOCAL DEFAULT 11
12: 0000000000401000 0 SECTION LOCAL DEFAULT 12
13: 0000000000401020 0 SECTION LOCAL DEFAULT 13
14: 0000000000401090 0 SECTION LOCAL DEFAULT 14
15: 00000000004010f0 0 SECTION LOCAL DEFAULT 15
16: 0000000000401238 0 SECTION LOCAL DEFAULT 16
17: 0000000000402000 0 SECTION LOCAL DEFAULT 17
18: 0000000000402040 0 SECTION LOCAL DEFAULT 18
19: 0000000000403e50 0 SECTION LOCAL DEFAULT 19
20: 0000000000403ff0 0 SECTION LOCAL DEFAULT 20
21: 0000000000404000 0 SECTION LOCAL DEFAULT 21
22: 0000000000404048 0 SECTION LOCAL DEFAULT 22
23: 0000000000000000 0 SECTION LOCAL DEFAULT 23
24: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
25: 0000000000000000 0 FILE LOCAL DEFAULT ABS
26: 0000000000403e50 0 NOTYPE LOCAL DEFAULT 19 __init_array_end
27: 0000000000403e50 0 OBJECT LOCAL DEFAULT 19 _DYNAMIC
28: 0000000000403e50 0 NOTYPE LOCAL DEFAULT 19 __init_array_start
29: 0000000000404000 0 OBJECT LOCAL DEFAULT 21 _GLOBAL_OFFSET_TABLE_
30: 0000000000401230 5 FUNC GLOBAL DEFAULT 15 __libc_csu_fini
31: 0000000000404048 0 NOTYPE WEAK DEFAULT 22 data_start
32: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
33: 000000000040404c 0 NOTYPE GLOBAL DEFAULT 22 _edata
34: 0000000000401238 0 FUNC GLOBAL HIDDEN 16 _fini
35: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2.5
36: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
37: 0000000000404048 0 NOTYPE GLOBAL DEFAULT 22 __data_start
38: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@@GLIBC_2.2.5
39: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
40: 0000000000402000 4 OBJECT GLOBAL DEFAULT 17 _IO_stdin_used
41: 00000000004011c0 101 FUNC GLOBAL DEFAULT 15 __libc_csu_init
42: 0000000000404050 0 NOTYPE GLOBAL DEFAULT 22 _end
43: 0000000000401120 5 FUNC GLOBAL HIDDEN 15 _dl_relocate_static_pie
44: 00000000004010f0 47 FUNC GLOBAL DEFAULT 15 _start
45: 000000000040404c 0 NOTYPE GLOBAL DEFAULT 22 __bss_start
46: 0000000000401125 146 FUNC GLOBAL DEFAULT 15 main
47: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@@GLIBC_2.2.5
48: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@@GLIBC_2.2.5
49: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
50: 0000000000401000 0 FUNC GLOBAL HIDDEN 12 _init
可以看到,可执行目标文件的符号表表项数目明显多于可重定位目标文件的表项数目。可执行目标文件中加入了与调试、加载、动态链接相关的节,使得表示节的符号数增多。
动态符号表
动态符号表保存与动态链接相关的导入导出符号不包括模块内部的符号。
Symbol table '.dynsym' contains 9 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getchar@GLIBC_2.2.5 (2)
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND atoi@GLIBC_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
可以看到程序头的部分信息如下:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000002a0 0x00000000000002a0 R 0x8
INTERP 0x00000000000002e0 0x00000000004002e0 0x00000000004002e0
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000005c0 0x00000000000005c0 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000000245 0x0000000000000245 R E 0x1000
LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000
0x000000000000013c 0x000000000000013c R 0x1000
LOAD 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001fc 0x00000000000001fc RW 0x1000
DYNAMIC 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001a0 0x00000000000001a0 RW 0x8
NOTE 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000320 0x0000000000400320 0x0000000000400320
0x0000000000000020 0x0000000000000020 R 0x4
GNU_PROPERTY 0x0000000000000300 0x0000000000400300 0x0000000000400300
0x0000000000000020 0x0000000000000020 R 0x8
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002e50 0x0000000000403e50 0x0000000000403e50
0x00000000000001b0 0x00000000000001b0 R 0x1
从中可知虚拟空间从0x400000开始。
在edb中打开hello程序,可以看到hello加载到虚拟地址的情况和各段的信息。在0x401000~0x402000段中,程序被载入,虚拟地址0x401000开始,到0x401ff0结束
根据节头目表,可以在edb中找到各个节的信息
例如可以看到.text节起始地址为0x4010d0,即可从edb中找到.text节的信息:
[15] .text PROGBITS 00000000004010f0 000010f0
0000000000000145 0000000000000000 AX 0 0 16
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.5.1hello反汇编指令与操作
指令如下:
objdump -d -r hello > hello2.txt
生成文件如下:
5.5.2hello与hello.o的反汇编文件的不同
在hello的反汇编文件中新增了.init和.plt节,和节中定义的函数。
(2)文件中包含的函数不同
在hello.c中调用的一些库函数被链接器链接到了hello文件中,通过反汇编代码就可以查看到这些新增函数,如exit、printf、sleep、getchar等函数。
(3)重定位后的函数调用地址不同:
Hello的跳转和函数地址都变成了虚拟内存地址,这就是链接器的重定位的功能。而hello.o的反汇编代码中,没有相应的虚拟地址,因此在.rela.text节中为其添加了重定位条目。
(4)虚拟地址的不同
hello.o中的代码段的起始地址为0,需要将其映射到对应的可执行文件的虚拟地址中,因此需要重定位,并且添加重定位条目。
5.5.3hello重定位地址计算
重定位PC相对引用重定位算法如下:
refaddr = ADDR(s) + r.offset;
*refptr = (unsigned) (ADDR(r.symbol) + r.addend – refaddr);
重定位绝对引用重定位算法如下:
*refptr = (unsigned) (ADDR(r.symbol) + r.addend
其中,假设算法运行时,链接器为每个节(用ADDR(s)表示)和每个符号都选择了运行时地址(用ADDR(r.symbol))表示。
5.6 hello的执行流程
①开始执行:_start、_libc_start_main
②执行main:_main、_printf、_exit、_sleep、_getchar
③退出:exit
可以使用gdb进行逐步调试,得到每一步的地址值:
401000 <_init>
401020 <.plt>
401080 <puts@plt>
401090 <printf@plt>
4010a0 <getchar@plt>
4010b0 <exit@plt>
4010c0 <sleep@plt>
4010d0 <_start>
401105 <main>
401190 <__libc_csu_init>
401200 <__libc_csu_fini>
401208 <_fini>
5.7 Hello的动态链接分析
动态链接指在可执行文件装载时或运行时,由操作系统的装载程序加载库。大多数操作系统将解析外部引用(比如库)作为加载过程的一部分。其基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
延迟绑定是通过GOT和PLT实现的,根据hello ELF文件可知,GOT起始表位置为0x404000如下:
[20] .got PROGBITS 0000000000403ff0 00002ff0
0000000000000010 0000000000000008 WA 0 0 8
[21] .got.plt PROGBITS 0000000000404000 00003000
0000000000000048 0000000000000008 WA 0 0 8
在edb中找到0x404000位置,发现GOT表位置在调用dl_init之前0x404008后的16个字节均为0:
在dl_init后条目改变了:
5.8 本章小结
本章主要介绍在Linux系统中链接的运行机制。介绍了将目标文件hello.o链接为hello可执行文件的过程,这也使我们更加充分的了解了链接器的工作细节。同时我们通过Ereadelf指令得到了ELF文件,以及objdump指令得到了反汇编文件,并前几章中的反汇编文件进行比较,使我们也更加清楚了关于链接和重定位的具体细节,以及动态链接的具体工作内容。
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1进程的概念
6.1.2进程的作用
进程给应用程序提供一种假象:每个进程在运行时,就像是一个独立的逻辑控制流一样,独占整个CPU和内存以及其它资源。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash的作用:Linux系统中,Shell是一个交互型应用级程序,为使用者提供操作界面,它接收用户命令,然后调用相应的应用程序。
Shell-bash的处理流程:
(1)首先,Shell程序根据空格符解析输入的命令行,将其拆分为单个单词组成的字符串数组。
(2)shell程序解析得到结果,分析是否是合法命令。
如果不是合法命令则清空输入缓冲区,然后向屏幕输出错误信息或者帮助信息。如果是一个合法命令那么程序会接着判断这是一个内嵌命令或者是一个打开可执行程序的命令,如果是一个内嵌命令那么程序会立即执行这个命令,然后清空输入缓冲区,来到下一个循环,等待新的命令输入。
- 调用fork函数创建个子进程;
- 在子进程中调用execve函数加载输入的程序,如果程序能够正常打开就不会返回,知道程序运行结束,如果程序打开失败,execve函数返回。
- 回收子进程
在加载了程序之后,父进程shell会根据输入的命令行判断该程序是在前台执行还是后台执行,如果是在前台执行的话shell就会等待子进程的结束,然后将其回收,接着等待下一次命令行的输入。如果是在后台运行,那么shell程序就会立即开始等待下一次命令行的输入,不再管当前子进程,知道子进程暂定或是终结,父进程shell程序收到信号,才会对子进程进行回收。
6.3 Hello的fork进程创建过程
(1)输入命令./hello后,父进程shell会先解析命令,判断这是一个非内嵌的前台执行的命令。
(2)调用fork函数,创建的子进程,且子进程虚拟空间的内容与父进程完全相同,还包括相同的打开文件:这意味着父进程打开的文件,子进程拥有完全一样的读写权限。二者的不同之处在于其虚拟内存空间指向的物理内存空间不同,而且其进程ID,也就是PID也不同。此外,fork函数被调用一次,但是会分别在父子进程中各返回一次,共计两次,父进程返回子进程的PID,子进程返回0。
6.4 Hello的execve过程
shell父进程创建的子进程,子进程调用execve函数,其参数主要是两个二级指针char **argv , char **envp,具体步骤如下:
(1)删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。
(2)execve加载Hello,会为新的程序映射虚拟内存空间,并且将控制流转移到新程序的主函数中,主函数main有三个参数int argc , char **argv , char **envp。
(3)子进程还会为新的程序映射新的数据段和代码段区域,以及共享区。
(4)程序计数器(PC)的值修改为新程序的代码区域入口处的地址。
6.5 Hello的进程执行
6.5.1逻辑控制流
逻辑控制流即控制程序的逻辑行为。控制流一般分成正常控制流和异常控制流,正常就是一切按预期的方向发展,异常就是控制流的突变。
6.5.3上下文
上下文是内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等构成。此外, 还包括进程打开的文件描述符等。
6.5.4调度的过程
系统中用户进程数一般多于处理机数,这导致它们互相争夺处理机。这就要求进程调度程序按一定的策略,动态分配处理进程,使之合理执行。
6.5.5用户态与核心态转换
为了能让处理器安全运行,我们需要对于程序对于不同区域的读写权限进行划分。
6.6 hello的异常与信号处理
6.6.1异常的类型和处理流程
深入理解计算机系统书中主要总结了4种类型的异常和相应的处理流程:
中断类型的异常来源于I/O设备的信号,是一种异步的异常,具体的处理流程如下:
可以看到其包括四个步骤并总是返回下一条指令。
陷阱
陷阱是一种进程故意达成的状态,目的是为了进行系统调动,进入内核模式,其异常是同步的,具体处理流程如下:
故障
产生故障的原因是因为当前进程发生了一些错误,而这种错误有可能可以被修复(例如缺页错误),也有可能不能被修复。这种异常是同步的,具体处理流程如下:
处理程序要么处理故障成功,返回当前导致故障的指令使其重新执行;要么处理故障失败,程序报错并且终止。
终止
程序终止的原因是因为发生了一个不可以被修复的错误,这种异常是同步的,且不会返回,具体流程如下:
6.6.2正常运行状态
6.6.3按下Ctrl+Z:
- 按下Ctrl+Z之后,进程会收到SIGSTP 信号,使得当前的hello进程被挂起。
- 用ps指令查看其进程PID,可以发现hello的PID是9294;
- 用jobs查看此时hello的后台 job号是1;
- 调用指令fg 1将其调回前台。
6.6.4按下Ctrl+C
6.6.5中途乱按:
- kill指令向所在的挂起的进程发出终止指令;
- ps指令无法找到对应的进程;
- jobs指令也无法找到作业,进程已经被终止。
6.7本章小结
本章进一步介绍了hello作为进程运行时候的相关知识。包括shell程序中hello进程是如何被创建、加载和结束的,通过键盘输入的指令是如何在输入缓冲区等待和清除的,以及在hello进程执行的过程中,是如何捕获信号并且对信号作出相应的反应和行为的。对于不同的信号,有着不同的处理方式和结果。除此之外还介绍了如何在shell进程中查看进程和作业,以及如何设置前台或者是后台作业。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
7.1.1逻辑地址
7.1.2线性地址
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
7.1.3虚拟地址
虚拟地址是程序运行在保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址。
7.1.4物理地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址的构成有两个部分:分别是段标识符和段内偏移量。其中,段标识符是一个由16位长数据组成的字段,将其称为段选择符。其中的前13位是一个索引号。而后面三位中则包含了一些硬件的细节。
段式内存管理方式就是直接将逻辑地址转换成物理地址,也就是CPU不支持分页机制。其地址的基本组成方式是段号+段内偏移地址。
在x86保护模式下,段的信息即段描述符占8个字节,段信息无法直接存放在段寄存器中。Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值。
- 首先给定一个完整的逻辑地址;
- 其次,看段选择描述符中的T1字段是0还是1,可以知道当前要转换的是GDT中的段,还是LDT中的段;
- 再根据指定的相应的寄存器,得到其地址和大小,我们就有了一个数组了。
- 接着,拿出段选择符中的前13位,可以在这个数组中查找到对应的段描述符,这样就有了Base,即基地址就知道了。
- 最后,把基地址Base+Offset,就是要转换的下一个阶段的地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
(1)首先,将线性地址划分为VPN+VPO的格式;
(2)然后将VPN拆分为TLBT+TLBI的格式,然后在TLB中寻找对应的PPN,如果有缺页的情况发生,那么就去下一级页表中寻找对应的PPN;
(3)以此类推,找到PPN之后,将其与之前的VPO进行组合就得到了对应的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
多级页表将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。
在k级页表的情况下,会将VPN的部分分成等长的k个段,将每一个段作为某一级页表的索引,直到找到正确的PPN。
多级页表示意图如下:
7.5 三级Cache支持下的物理内存访问
(1)首先使用物理地址的CI进行组索引,对8个块分别对CT进行标志位的匹配。如果匹配成功且块的有效位为1,则成功命中。然后根据数据偏移量 CO取出相应的数据并返回。这里的数据保存在一级Cache。
(2)如果没有命中,或者没找到相匹配的标志位,那么就会在下一级Cache中寻找,只要本级Cache中没找到就要去下一级的Cache中寻找数据,然后逐级写入Cache。
(3)在更新Cache的时候,首先需要判断是否有有效位为0的块。若有,则直接写入;若不存在,则需要驱逐一个块(LRU策略),再进行写入。
Cache示意图如下:
7.6 hello进程fork时的内存映射
(1)fork函数被当前进程调用时:
首先,内核会为新进程创建各种与父进程相同的数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存。
(2)fork在新进程中返回时:
新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任意一个在之后进行写操作时,写时复制机制就会为其创建新页面。因此,也就为每个进程保持了私有空间地址的抽象概念。
7.7 hello进程execve时的内存映射
在exceve函数加载和执行hello程序的过程中,需要执行几个步骤:
- 首先删除已存在的用户区域。
- 然后,会映射私有区域,该函数会为Hello的代码、数据、bss和栈区域,并创建新的区域结构,这些区域都是私有、写时复制的。
- 接着,.映射共享区域,比如,在Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的。最后再设置程序计数器,完成相应的跳转。
7.8 缺页故障与缺页中断处理
如果在页表中发生了一次不命中,我们就认为发生了一次缺页故障,具体处理流程如下:
- 首先,由于PTE中对应页的有效位是0,所以MMU出发了一次缺页异常信号,此时操作系统就开始调用相应的异常处理程序。
- 被调用的缺页处理程序会根据一定的替换策略确认出物理内存中的牺牲页,如果这个页已经被修改了,程序就会把它换到磁盘。
- 缺页处理程序在物理内存中写入新的页面,并更新内存中的PTE。
- 完成了上述操作之后,缺页处理程序就会返回到原来的进程,再次执行刚才导致缺页的命令。此时,因为虚拟页面已经换存在物理内存中,所以就会命中。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存空间,其要么是已分配的,要么是空闲的。动态内存分配主要有两种基本方法与策略:
(1)隐式空闲链表
隐式空闲链表通常带有一个头部和脚部标签,内容相同,记录块的大小和是否分配等信息。一般来说每个块都是由头部和脚部、有效载荷、可能的额外填充组成,对于某些优化的链表,已分配的块可以没有脚部。
在隐式空闲链表中,所有的块都是通过头部和脚部中的大小字段连接着的。因此分配器可以依次遍历整个堆。其中,一个设置了已分配的位而大小为零的终止头部将作为特殊标记的结束块。
当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大的可以放置所请求块的空闲块。分配器有三种放置策略:首次适配、下一次适配合最佳适配。分配完后可以分割空闲块减少内部碎片。同时分配器在面对释放一个已分配块时,可以合并空闲块,其中便利用隐式空闲链表的边界标记来进行合并。
(2)显示空间链表管理
显式空闲链表是将空闲块组织为某种形式的显式数据结构。堆被组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。
在显式空闲链表中,可以采用后进先出的顺序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址顺序来维护链表。其中链表中每个块的地址都小于它的后继地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前驱。
7.10本章小结
本章主要介绍了虚拟内存的基本思想和基本机制,以及如何通过虚拟地址得到一个物理地址,如何通过TLB机制来加速寻找的过程。我们介绍了关于堆的动态分配的不同机制,关于空闲块管理和分配的机制的不同我们可以得到不同的效果的堆分配器。还介绍了一个新的进程在被创建或者是一个程序被加载时,系统如何为其分配和创建存储空间,以及私有文件和共享文件在存储和读写时候的不同机制。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
8.1.1设备的模型化
我们可以将所有的I/O设备都被模型化为文件,方便统一进行操作。
8.1.2设备管理
8.2 简述Unix IO接口及其函数
8.2.1Unix IO接口
创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0) 、标准输出(描述符为1) 和标准错误(描述符为2) 。头文件中的常量可以代替显式的描述符值。
对于每个打开的文件,内核保持着一个文件位置 k,初始为 0,这个文件位置是从文件开头起始的字节偏移量,应用 程序能够通过执行 seek,显式地将改变当前文件位置 k。
8.2.2Unix IO函数
此函数用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等各种参数。如果打开成功就返回其文件描述符,如果失败则返回-1
close函数
该函数用于关闭一个被打开的的文件,该文件通过输入参数文件描述符fd确定,如果成功关闭则返回0,如果关闭失败则返回-1。
read函数
该函数带有缓冲区,用于从文件读取数据。返回读取到的文件字节数,如果直接读取到EOF就返回0,读取失败返回-1。
write函数
该函数用于向文件写入数据,如果写入成功就返回向其写入的字节数,写入失败就返回-1。
lseek函数
该函数用于将文件描述符指定的文件的指针位置移到别处中,如果成功则返回当前位置,失败就返回-1。
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函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。printf中调用了两个函数,分别为vsprintf和write:
其中引用的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);
}
vsprintf函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。
write函数是将buf中的i个元素写到终端的函数。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar源码如下:
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;
}
当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换为ASCII码,保存到系统的键盘缓冲区中。
getchar函数调用了read函数,read函数也通过sys_call调用内核中的系统函数,将读取存储在键盘缓冲区中的ASCII码,直到回车符,然后返回整个字符串,getchar函数只从中读取第一个字符,其他的字符被缓存在输入缓冲区。
当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
8.5本章小结
本章介绍了Linux的I/O设备的抽象处理和读取写入等操作,深入地剖析了关于文件描述符以及文件打开,文件位置等机制,了解了Unix I/O接口和函数,并且分析了printf和getchar函数是如何通过Unix I/O函数来实现的。
结论
hello经历的过程:
- 源程序编写:通过编写工具(文本编辑器、IDE等)编写出hello.c;
- 预处理:预处理器cpp读取需要的系统头文件内容,生成ASCII码的中间文件hello.i。
- 编译:编译器ccl将C语言代码翻译成汇编指令,生成hello.s。
- 汇编:汇编器as将hello.s翻译成机器语言指令,并生成重定位信息,将结果保存在可重定位目标文件hello.o中。
- 链接:链接器进行符号解析、重定位、动态链接等创建一个可执行目标文件hello,此时hello可以被执行。
- 运行阶段:当我们在shell键入./hello启动程序的时候,shell调用fork函数为其产生子进程,子进程中调用execve函数,加载hello程序,进入hello的程序入口点。
- 进程运行:内核负责调度进程,并对可能产生的异常及信号进行处理。内存的管理由MMU、TLB、多级页表、cache、DRAM内存、动态内存分配器共同完成,而Unix I/O的作用则是让程序与文件进行交互。
- 终止:hello最终被shell父进程回收,内核删除为hello进程创建的所有数据结构。
感悟:
这次大作业带给我在实践中理解计算机系统的一次机会,以往在课堂中学习总是忙着对知识的记忆,而知识背后的应用却被我忽视了。而完成这次大作业的过程中,随着对hello程序一生的探索,也打开了我对计算机系统深入理解的大门,感受到了计算机系统之美。机器是没有思维的,但是当我们探索机器背后的机理时,就仿佛在和一代代为计算机科学做出贡献的人进行思维上的交流,感受计算思维的美妙。
附件
文件名 |
文件作用 |
hello.i |
预处理后的ASCII码文件 |
hello.s |
编译之后得到的文件 |
hello.o |
汇编之后得到的目标文件 |
hello |
链接之后得到的可执行文件 |
Hello.out |
反汇编之后得到的可重定位文件 |
helloELF.txt |
目标文件hello.o的ELF格式 |
helloELF1.txt |
可执行文件hello的ELF格式 |
参考文献
- Bryant R E. O'Hallaron D R. Computer systems: A programmer's perspective[M]. Upper Saddle River: Prentice Hall, 2003.
- 桂盛霖, 徐洁. “计算机系统结构”实验教学探索[J]. 实验科学与技术, 2013,11(6): 306-308.
- 李山山, 郑宁汉, 高玉超. 基于开放式CPU设计系统的双核CPU设计[J]. 实验室科学,2015,18(5): 27-32
- 刘磊,熊小鹏.最小驻留价值缓存替换算法[J].计算机应用,2013,33(04):1018-1022.
[5]]黄毅伟. TLB的设计与验证[D].国防科学技术大学,2006.
2022春哈工大计算机系统大作业——hello的程序人生相关推荐
- 哈工大计算机系统大作业-Hello的程序人生
目录快速链接 摘要 第一章 概述 1.1Hello简介 1.2环境与工具 1.3中间结果 1.4本章小结 第二章 预处理 2.1预处理的概念和作用 Ubuntu下的预处理命令 2.3Hello的预处理 ...
- 2022春哈工大ICS大作业——程序人生-hello‘sP2P
计算机系统(初发布于2022/5/15,修改于2022/5/20) 大作业 题 目 程序人生-Hello's P2P 专 业 学 号 班 级 学 生 沈业力 ...
- 2023春 哈工大计算机系统大作业
计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 人工智能 学 号 2021112002 班 级 2103602 学 生 唐家誉 指 导 教 ...
- 【2022】哈工大计算机系统大作业——程序人生Hello’s P2P
2022哈工大计算机系统大作业--程序人生Hello's P2P 摘要 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概 ...
- 2021春哈工大计算机系统大作业
摘 要 本文主要阐述hello程序在Linux系统的生命周期,借助edb.gcc等工具探讨hello程序从hello.c经过预处理.编译.汇编.链接生成可执行文件的全过程.同时比较全方面的涉及了Hel ...
- 哈尔滨工业大学计算机系统大作业-hello的程序人生
摘 要 本文通过对hello程序在Ubuntu系统下从程序变成进程再被回收的过程的分析,深入剖析了程序在计算机系统中的运行流程.我们能在这个过程中,看到hello程序一步一步从普通的c语言代码,成长为 ...
- 程序人生-哈工大计算机系统大作业2022春
计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 7203610401 班 级 2036012 学 生 王元辰 指 导 ...
- 哈工大计算机系统大作业2022春
计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算机科学与技术学院 学 号 7203610110 班 级 2036011 学 生 王 ...
- 2022哈工大计算机系统大作业——程序人生
计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 人工智能(未来技术) 学 号 120L020301 班 级 2036011 学 生 张思远 ...
最新文章
- bpnn matlab工具箱,bpnn——matlab工具箱-归一化函数 premnmx、tramnmx、postmnmx、mapminmax...
- 在文本每一行的行首和行尾都插入特定的相同字符或文本
- linux c主要应用在哪个领域
- Vector和LinkedList源代码阅读笔记
- Android空间WebView和ViedeoView的使用
- 中年人在“洗脑课”上迷了路
- 将文件转为stream流_NIO之文件IO
- Solution 19: Fibonacci数列
- C++boost Class named_condition翻译
- 非阻塞I/O多路复用机制
- Linux下那些查找命令
- 无聊了就上来转转,看看大家发的东东也挺好玩的啊
- 做web前端的小伙伴注意了,未来这些发展方向可以试试!
- T-SQL 函数概述
- listctrl 的使用技巧
- HDU2222【AC自动机(基础·模板)】
- 《Android Jetpack》— Android 书籍
- 配置xml文件来实现FlightGear通信,接收与发送数据
- C语言实现电脑关机程序
- 什么是VHDL?一文带你了解VHDL语言
热门文章
- 罗马音平假名中文可复制_【日语歌曲】恋爱循环罗马音+日语+中文
- 计数器的设计--电子技术课程设计说明书--模99
- 编译原理课后题答案 龙书 机械工业出版社
- 安卓Accessibility(Talkback无障碍读屏器)适配笔记
- 网页标识计算机题目,职称计算机模拟试题:Dreamweaver网页设计模拟试题及答案(6)...
- 基于SSM+Vue的SSM学业预警平台信息管理系统
- 飞鸽传书绿色版XEIM指出
- Go后端博客项目实战_持续更新ing
- cocos2dX打包图文详解
- 代码创建mdb数据库创建表