大作业
题目 | 程序人生-Hello’s P2P |
---|---|
专业 | 计算学部 |
学号 | 120L021403 |
班级 | 2003011 |
学生 | 冯新航 |
指导教师 | 郑贵滨 |
计算机科学与技术学院
2022年5月
计算机系统是人类创造出的工业奇迹之一,其构造之精妙,功能之强大,令人赞叹。要全面描述计算机系统的方方面面实际上是非常困难的,因此本文从简单的 hello
程序入手,讲述计算机系统的一些基本概念、功能与原理。我们将讲述 hello
是如何从源码编译为可执行文件,并经由 shell
执行的过程,剖析一些概念的细节和实现原理。
关键词:Linux C语言 计算机系统 编译 内存管理 进程
根据Hello的自白,利用计算机系统的术语,简述Hello
的P2P,020的整个过程。
hello
程序在 Linux 下将经历以下步骤:
hello.c
源文件hello.i
文件hello.s
hello.o
ld
将 hello.o
与系统的其他目标文件链接起来,形成最终的 hello
可执行文件shell
中输入相应命令后,shell
为其 fork
一个子进程,经由 execve
函数,使得子进程代表用户执行 hello
程序hello
可执行文件映射到虚拟内存,而后载入物理内存,进入 main
函数执行main
函数返回shell
回收该进程,内核删除相关数据结构列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境
软件环境
开发工具
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c
源代码文本hello.i
编译预处理文件hello.s
汇编文本文件hello.o
可重定位目标文件hello
可执行文件本章讲述了 hello
程序如何从源代码文本转换为可执行文件的过程,同时列出了实验环境等相关信息。
概念:
编译预处理器是针对源代码中的编译预处理指令进行的一类操作,如引入头文件、条件编译等
作用:
处理 #include
指令,将 #include
后的文件原封不动地拷贝到源码对应位置
处理条件编译指令 #ifdef
等,保留满足条件的源码代码
替换由 #define
定义的常量等
应截图,展示预处理过程!
命令:
gcc -E hello.c -o hello.i
截图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tIpRjdSD-1653009846945)(https://s1.ax1x.com/2022/05/20/OqqpZt.png)]
编译预处理器按照C语言标准规则修改和替换源码中某些位置的代码,我们可以在得到的 hello.i
底部找到原来的部分,而在顶端是由 #include
引入的头文件,它被原封不动地拷贝到对应位置。
本章讲述了编译预处理器和编译预处理指令的相关概念及其作用,本质上是修改源文件的文本内容,为编译器将其编译成汇编语言做准备。
编译器将编译预处理文件 hello.i
编译成汇编文件 hello.s
,这个文件仍然是一个文本文件,内容是 hello
源文件的等价汇编结果。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
编译命令:
gcc -S -g hello.c -o hello.s
应截图,展示编译过程!
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要 hello.s
中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
.file "hello.c"
.text
.Ltext0:
.section .rodata
.align 8
.LC0:
.string "\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"
.LC1:
.string "Hello %s %s\n"
.text
.globl main
.type main, @function
各部分内容说明:
.file
声明源文件.text
代码段.section.rodata
数据段.align
地址对齐方式.string
声明字符串globl
声明全局符号.type
声明符号类型字符串
main
函数中存在对 printf
的调用,格式控制字符串的地址作为 printf
的参数被传入,如对于:
printf("用法: Hello 学号 姓名 秒数!\n");
的汇编为:
leaq .LC0(%rip), %rdi
call puts@PLT
局部变量
main
函数中有局部变量 i
作为循环变量,由语句:
cmpl $7, -4(%rbp)
可知该变量存储在栈上,位置为 %rpb-4
。
argc
与 argv
参数
argc
与 argv
都是 main
的参数,在 main
函数开头有
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
可知这些变量都存储在栈上。
立即数
立即数是直接硬编码在汇编代码中的。
函数调用、参数传递与返回
以 sleep(atoi(argv[3]))
调用为例,调用 sleep
函数需传入调用 atoi
的返回值,在调用 atoi
时,先访问 argv[3]
将其值保存到 %rdi
:
movq %rax, %rdi
call atoi@PLT
在 atoi
中以 ret
返回,返回值保存在 %rax
中,再将返回值作为 sleep
函数参数传入:
movl %eax, %edi
call sleep@PLT
赋值语句对应 x86
汇编中的 mov
指令,后缀表示操作的字节数,如:
movq (%rax), %rax
将 %rax
作为内存地址,取地址的值(四字)传入 %rax
其他后缀,有:
movb
单字movw
双字movl
四字movq
八字四则运算在 x86
有指令直接实现,如:
addl $1, -4(%rbp)
对 %rbp-4
的内存引用值加一,即 i++
其他指令的使用方法如图所示:
以 hello.c
的第一个判断语句为例:
if (argc != 4)
{
printf("用法: Hello 学号 姓名 秒数!\n");
exit(1);
}
汇编如下:
cmpl $4, -20(%rbp)
je .L2
.loc 1 16 3
leaq .LC0(%rip), %rdi
call puts@PLT
.loc 1 17 3
movl $1, %edi
call exit@PLT
因为 %rbp-20
就是 argc
的地址,将其与 4
比较,cmpl
会修改条件码寄存器,je
指令根据条件码寄存器的值决定是否转跳,此处可知,只有相等时转跳,不相等时进入 if
代码块内。
对 i < 8
,同理:
cmpl $7, -4(%rbp)
jle .L4
机器级的类型转换,如基本类型 int
float
double
short
等是通过二进制表示的截断与扩展实现的。
字符串与数字间的类型转换则利用 atoi
这样的函数实现。
本章对 hello.c
与 hello.i
,即源码到汇编的对应关系做出解释,讲述了 x86
的基本汇编命令的作用。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
汇编器将汇编语言文本翻译成机器语言,产生二进制文件,并将这些文件打包成可重定向目标文件,将各类信息存储在文件头中。
命令:
gcc -c -g hello.s -o hello.o
elf
格式分析 hello.o
的ELF格式,用 readelf
等列出其各节的基本信息,特别是重定位项目分析。
编译命令:
gcc -c hello.s -o hello.o
ELF
头
命令:
readelf -h hello.o
结果:
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: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 1240 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
ELF
头描述了该可重定向目标文件的一些信息,可以帮助链接器链接文件,其中包括了 ELF
头的大小、目标文件类型、各节的大小和偏移等信息。
在本例中,可以看见这个可重定向目标文件有 14
个节。
节分布
命令:
readelf -S hello.o
结果:
There are 14 section headers, starting at offset 0x4d8:
Section Headers:
[Nr] Name Type Address Offset
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
000000000000002a 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 00000135
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
Key to Flags:
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)
此处可以看到各节的类型、大小、位置等信息,还可以看出各节的可执行或可读情况。
符号表
命令:
readelf -s hello.o
结果
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
符号表包括了程序中各类符号的信息,指明其是函数还是全局变量等,和可重定向信息。
重定位节
命令:
readelf -r hello.o
结果:
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
rela.txt
保存了 .text
节中需要被修改的信息,因为链接时所有函数,全局变量等的地址都需要修正。在这个程序中,需要重定位的信息有:printf
puts
exit
getchar
sleep
等。
objdump -d -r hello.o
分析hello.o
的反汇编,并请与第3章的 hello.s
进行对照分析。
反汇编命令:
objdump -d -r hello.o
结果:
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 :
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 20 sub $0x20,%rsp
c: 89 7d ec mov %edi,-0x14(%rbp)
f: 48 89 75 e0 mov %rsi,-0x20(%rbp)
13: 83 7d ec 04 cmpl $0x4,-0x14(%rbp)
17: 74 16 je 2f
19: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 20
1c: R_X86_64_PC32 .rodata-0x4
20: e8 00 00 00 00 callq 25
21: R_X86_64_PLT32 puts-0x4
25: bf 01 00 00 00 mov $0x1,%edi
2a: e8 00 00 00 00 callq 2f
2b: R_X86_64_PLT32 exit-0x4
2f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
36: eb 48 jmp 80
38: 48 8b 45 e0 mov -0x20(%rbp),%rax
3c: 48 83 c0 10 add $0x10,%rax
40: 48 8b 10 mov (%rax),%rdx
43: 48 8b 45 e0 mov -0x20(%rbp),%rax
47: 48 83 c0 08 add $0x8,%rax
4b: 48 8b 00 mov (%rax),%rax
4e: 48 89 c6 mov %rax,%rsi
51: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 58
54: R_X86_64_PC32 .rodata+0x22
58: b8 00 00 00 00 mov $0x0,%eax
5d: e8 00 00 00 00 callq 62
5e: R_X86_64_PLT32 printf-0x4
62: 48 8b 45 e0 mov -0x20(%rbp),%rax
66: 48 83 c0 18 add $0x18,%rax
6a: 48 8b 00 mov (%rax),%rax
6d: 48 89 c7 mov %rax,%rdi
70: e8 00 00 00 00 callq 75
71: R_X86_64_PLT32 atoi-0x4
75: 89 c7 mov %eax,%edi
77: e8 00 00 00 00 callq 7c
78: R_X86_64_PLT32 sleep-0x4
7c: 83 45 fc 01 addl $0x1,-0x4(%rbp)
80: 83 7d fc 07 cmpl $0x7,-0x4(%rbp)
84: 7e b2 jle 38
86: e8 00 00 00 00 callq 8b
87: R_X86_64_PLT32 getchar-0x4
8b: b8 00 00 00 00 mov $0x0,%eax
90: c9 leaveq
91: c3 retq
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
反汇编的结果与 hello.s
存在差异:
分支与转跳:
分支转跳的地址被重定位,像是 L2
被重定位为机器语言的地址:
# before
cmpl $4, -20(%rbp)
je .L2
# after
cmpl $0x4,-0x14(%rbp)
je 2f
函数调用
当编译时使用了 -g
选项,函数调用将保留函数名,若没有该选项,实际应为函数地址。
立即数的变化
可重定位目标文件中立即数均以十六进制表示:
#before
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
#after
mov %edi,-0x14(%rbp)
mov %rsi,-0x20(%rbp)
本章讲述了由 hello.s
生成可重定位目标文件 hello.o
的ELF
头、节、符号表和可重定位节,解释了 hello.s
和 hello.o
反汇编结果的差距。
注意:这儿的链接是指从 hello.o
到``hello`生成过程。
链接是链接器将各类可重定位目标文件合并成可执行文件的过程,可执行文件可以加载到内存进行执行。
使用ld
的链接命令,应截图,展示汇编过程! 注意不只连接hello.o
文件
链接命令:
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
的ELF
格式,用readelf
等列出其各段的基本信息,包括各段的起始地址,大小等信息。
ELF
头
❯ readelf -h hello
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
节分布
> readelf -S hello
There are 27 section headers, starting at offset 0x3780:
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
0000000000000029 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
Key to Flags:
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)
符号表
> readelf -s hello
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)
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
重定位节:
> readelf -r hello
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
使用edb
加载hello
,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
EDB
显示 hello
的虚拟内存地址范围是 0x401000-0x402000
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biFAIZ9X-1653009846949)(D:\Users\VonBrank\OneDrive - stu.hit.edu.cn\Project\Daily\20220317 - HIT-CSAPP\Final Assignment\大作业\report.assets\image-20220518091052579.png)]
举例来说,由前面的信息可知 .text
节的地址为 0x4010f0
:
objdump -d -r hello
分析hello
与hello.o
的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
hello
的 objdump
结果为:
hello: file format elf64-x86-64
Disassembly of section .init:
0000000000401000 <_init>:
401000: f3 0f 1e fa endbr64
401004: 48 83 ec 08 sub $0x8,%rsp
401008: 48 8b 05 e9 2f 00 00 mov 0x2fe9(%rip),%rax # 403ff8 <__gmon_start__>
40100f: 48 85 c0 test %rax,%rax
401012: 74 02 je 401016 <_init+0x16>
401014: ff d0 callq *%rax
401016: 48 83 c4 08 add $0x8,%rsp
40101a: c3 retq
Disassembly of section .plt:
0000000000401020 <.plt>:
401020: ff 35 e2 2f 00 00 pushq 0x2fe2(%rip) # 404008 <_GLOBAL_OFFSET_TABLE_+0x8>
401026: f2 ff 25 e3 2f 00 00 bnd jmpq *0x2fe3(%rip) # 404010 <_GLOBAL_OFFSET_TABLE_+0x10>
40102d: 0f 1f 00 nopl (%rax)
401030: f3 0f 1e fa endbr64
401034: 68 00 00 00 00 pushq $0x0
401039: f2 e9 e1 ff ff ff bnd jmpq 401020 <.plt>
40103f: 90 nop
401040: f3 0f 1e fa endbr64
401044: 68 01 00 00 00 pushq $0x1
401049: f2 e9 d1 ff ff ff bnd jmpq 401020 <.plt>
40104f: 90 nop
401050: f3 0f 1e fa endbr64
401054: 68 02 00 00 00 pushq $0x2
401059: f2 e9 c1 ff ff ff bnd jmpq 401020 <.plt>
40105f: 90 nop
401060: f3 0f 1e fa endbr64
401064: 68 03 00 00 00 pushq $0x3
401069: f2 e9 b1 ff ff ff bnd jmpq 401020 <.plt>
40106f: 90 nop
401070: f3 0f 1e fa endbr64
401074: 68 04 00 00 00 pushq $0x4
401079: f2 e9 a1 ff ff ff bnd jmpq 401020 <.plt>
40107f: 90 nop
401080: f3 0f 1e fa endbr64
401084: 68 05 00 00 00 pushq $0x5
401089: f2 e9 91 ff ff ff bnd jmpq 401020 <.plt>
40108f: 90 nop
Disassembly of section .plt.sec:
0000000000401090 :
401090: f3 0f 1e fa endbr64
401094: f2 ff 25 7d 2f 00 00 bnd jmpq *0x2f7d(%rip) # 404018
40109b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000004010a0 :
4010a0: f3 0f 1e fa endbr64
4010a4: f2 ff 25 75 2f 00 00 bnd jmpq *0x2f75(%rip) # 404020
4010ab: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000004010b0 :
4010b0: f3 0f 1e fa endbr64
4010b4: f2 ff 25 6d 2f 00 00 bnd jmpq *0x2f6d(%rip) # 404028
4010bb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000004010c0 :
4010c0: f3 0f 1e fa endbr64
4010c4: f2 ff 25 65 2f 00 00 bnd jmpq *0x2f65(%rip) # 404030
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000004010d0 :
4010d0: f3 0f 1e fa endbr64
4010d4: f2 ff 25 5d 2f 00 00 bnd jmpq *0x2f5d(%rip) # 404038
4010db: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
00000000004010e0 :
4010e0: f3 0f 1e fa endbr64
4010e4: f2 ff 25 55 2f 00 00 bnd jmpq *0x2f55(%rip) # 404040
4010eb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .text:
00000000004010f0 <_start>:
4010f0: f3 0f 1e fa endbr64
4010f4: 31 ed xor %ebp,%ebp
4010f6: 49 89 d1 mov %rdx,%r9
4010f9: 5e pop %rsi
4010fa: 48 89 e2 mov %rsp,%rdx
4010fd: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
401101: 50 push %rax
401102: 54 push %rsp
401103: 49 c7 c0 30 12 40 00 mov $0x401230,%r8
40110a: 48 c7 c1 c0 11 40 00 mov $0x4011c0,%rcx
401111: 48 c7 c7 25 11 40 00 mov $0x401125,%rdi
401118: ff 15 d2 2e 00 00 callq *0x2ed2(%rip) # 403ff0 <__libc_start_main@GLIBC_2.2.5>
40111e: f4 hlt
40111f: 90 nop
0000000000401120 <_dl_relocate_static_pie>:
401120: f3 0f 1e fa endbr64
401124: c3 retq
0000000000401125 :
401125: f3 0f 1e fa endbr64
401129: 55 push %rbp
40112a: 48 89 e5 mov %rsp,%rbp
40112d: 48 83 ec 20 sub $0x20,%rsp
401131: 89 7d ec mov %edi,-0x14(%rbp)
401134: 48 89 75 e0 mov %rsi,-0x20(%rbp)
401138: 83 7d ec 04 cmpl $0x4,-0x14(%rbp)
40113c: 74 16 je 401154
40113e: 48 8d 3d c3 0e 00 00 lea 0xec3(%rip),%rdi # 402008 <_IO_stdin_used+0x8>
401145: e8 46 ff ff ff callq 401090
40114a: bf 01 00 00 00 mov $0x1,%edi
40114f: e8 7c ff ff ff callq 4010d0
401154: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
40115b: eb 48 jmp 4011a5
40115d: 48 8b 45 e0 mov -0x20(%rbp),%rax
401161: 48 83 c0 10 add $0x10,%rax
401165: 48 8b 10 mov (%rax),%rdx
401168: 48 8b 45 e0 mov -0x20(%rbp),%rax
40116c: 48 83 c0 08 add $0x8,%rax
401170: 48 8b 00 mov (%rax),%rax
401173: 48 89 c6 mov %rax,%rsi
401176: 48 8d 3d b1 0e 00 00 lea 0xeb1(%rip),%rdi # 40202e <_IO_stdin_used+0x2e>
40117d: b8 00 00 00 00 mov $0x0,%eax
401182: e8 19 ff ff ff callq 4010a0
401187: 48 8b 45 e0 mov -0x20(%rbp),%rax
40118b: 48 83 c0 18 add $0x18,%rax
40118f: 48 8b 00 mov (%rax),%rax
401192: 48 89 c7 mov %rax,%rdi
401195: e8 26 ff ff ff callq 4010c0
40119a: 89 c7 mov %eax,%edi
40119c: e8 3f ff ff ff callq 4010e0
4011a1: 83 45 fc 01 addl $0x1,-0x4(%rbp)
4011a5: 83 7d fc 07 cmpl $0x7,-0x4(%rbp)
4011a9: 7e b2 jle 40115d
4011ab: e8 00 ff ff ff callq 4010b0
4011b0: b8 00 00 00 00 mov $0x0,%eax
4011b5: c9 leaveq
4011b6: c3 retq
4011b7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
4011be: 00 00
00000000004011c0 <__libc_csu_init>:
4011c0: f3 0f 1e fa endbr64
4011c4: 41 57 push %r15
4011c6: 4c 8d 3d 83 2c 00 00 lea 0x2c83(%rip),%r15 # 403e50 <_DYNAMIC>
4011cd: 41 56 push %r14
4011cf: 49 89 d6 mov %rdx,%r14
4011d2: 41 55 push %r13
4011d4: 49 89 f5 mov %rsi,%r13
4011d7: 41 54 push %r12
4011d9: 41 89 fc mov %edi,%r12d
4011dc: 55 push %rbp
4011dd: 48 8d 2d 6c 2c 00 00 lea 0x2c6c(%rip),%rbp # 403e50 <_DYNAMIC>
4011e4: 53 push %rbx
4011e5: 4c 29 fd sub %r15,%rbp
4011e8: 48 83 ec 08 sub $0x8,%rsp
4011ec: e8 0f fe ff ff callq 401000 <_init>
4011f1: 48 c1 fd 03 sar $0x3,%rbp
4011f5: 74 1f je 401216 <__libc_csu_init+0x56>
4011f7: 31 db xor %ebx,%ebx
4011f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
401200: 4c 89 f2 mov %r14,%rdx
401203: 4c 89 ee mov %r13,%rsi
401206: 44 89 e7 mov %r12d,%edi
401209: 41 ff 14 df callq *(%r15,%rbx,8)
40120d: 48 83 c3 01 add $0x1,%rbx
401211: 48 39 dd cmp %rbx,%rbp
401214: 75 ea jne 401200 <__libc_csu_init+0x40>
401216: 48 83 c4 08 add $0x8,%rsp
40121a: 5b pop %rbx
40121b: 5d pop %rbp
40121c: 41 5c pop %r12
40121e: 41 5d pop %r13
401220: 41 5e pop %r14
401222: 41 5f pop %r15
401224: c3 retq
401225: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
40122c: 00 00 00 00
0000000000401230 <__libc_csu_fini>:
401230: f3 0f 1e fa endbr64
401234: c3 retq
Disassembly of section .fini:
0000000000401238 <_fini>:
401238: f3 0f 1e fa endbr64
40123c: 48 83 ec 08 sub $0x8,%rsp
401240: 48 83 c4 08 add $0x8,%rsp
401244: c3 retq
而 hello.o
的 objdump
的结果为:
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 :
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 20 sub $0x20,%rsp
c: 89 7d ec mov %edi,-0x14(%rbp)
f: 48 89 75 e0 mov %rsi,-0x20(%rbp)
13: 83 7d ec 04 cmpl $0x4,-0x14(%rbp)
17: 74 16 je 2f
19: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 20
1c: R_X86_64_PC32 .rodata-0x4
20: e8 00 00 00 00 callq 25
21: R_X86_64_PLT32 puts-0x4
25: bf 01 00 00 00 mov $0x1,%edi
2a: e8 00 00 00 00 callq 2f
2b: R_X86_64_PLT32 exit-0x4
2f: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
36: eb 48 jmp 80
38: 48 8b 45 e0 mov -0x20(%rbp),%rax
3c: 48 83 c0 10 add $0x10,%rax
40: 48 8b 10 mov (%rax),%rdx
43: 48 8b 45 e0 mov -0x20(%rbp),%rax
47: 48 83 c0 08 add $0x8,%rax
4b: 48 8b 00 mov (%rax),%rax
4e: 48 89 c6 mov %rax,%rsi
51: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 58
54: R_X86_64_PC32 .rodata+0x22
58: b8 00 00 00 00 mov $0x0,%eax
5d: e8 00 00 00 00 callq 62
5e: R_X86_64_PLT32 printf-0x4
62: 48 8b 45 e0 mov -0x20(%rbp),%rax
66: 48 83 c0 18 add $0x18,%rax
6a: 48 8b 00 mov (%rax),%rax
6d: 48 89 c7 mov %rax,%rdi
70: e8 00 00 00 00 callq 75
71: R_X86_64_PLT32 atoi-0x4
75: 89 c7 mov %eax,%edi
77: e8 00 00 00 00 callq 7c
78: R_X86_64_PLT32 sleep-0x4
7c: 83 45 fc 01 addl $0x1,-0x4(%rbp)
80: 83 7d fc 07 cmpl $0x7,-0x4(%rbp)
84: 7e b2 jle 38
86: e8 00 00 00 00 callq 8b
87: R_X86_64_PLT32 getchar-0x4
8b: b8 00 00 00 00 mov $0x0,%eax
90: c9 leaveq
91: c3 retq
可以发现重定位后,hello.o
中所有的可重定位符号都替换成了确切的地址,同时包含许多其他的汇编代码。
使用edb
执行hello
,说明从加载hello
到_start
,到call main
,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
hello
中各函数执行顺序如下:
_start
_libc_start_main
_main
_printf
_exit
_sleep
_getchar
exit
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
节分布表可知, GOT
地址为 0x403ff0
,在 dl_init
执行前,其内容为:
dl_init
执行完成后,其内容发生变化:
表明 printf
作为库函数,当第一次调用符号时会动态解析其绝对地址并写到 GOT 中,下次调用的时候就不用再次解析了。
本章讲述了链接的相关内容,介绍了可重定位目标文件链接为可执行文件的过程,解释了 ELF
文件格式的意义,同时说明了 hello
运行时虚拟地址空间的布局及其重定位、执行和动态链接过程。
概念:
进程是操作系统中执行程序的一个实例,它是操作系统执行程序的基本单元。
作用:
操作系统中每个程序都运行在进程的一个上下文中,这个上下文维护了程序的执行状态,如栈、寄存器、环境变量、打开的文件描述符等。
shell
是一个用于人与操作系统交互的程序,它提供用户界面,接受命令,并替用户执行程序。
其处理流程大致如下:
fork
一个进程,并调用 execve
函数代表用户执行相应程序waitpid
回收子进程当在终端输入执行 hello
的命令时,shell
检测到它不是内部命令,就 fork
一个进程,这相当于在内存的另一个区域开辟一个和原来的 shell
相同的副本,然后继续执行。这个子进程的 PID
与父进程不同,是与父进程并发独立执行。
fork
完成后,shell
调用 execve
函数执行 hello
程序,并传入相关参数。
execve
函数将删除当前虚拟地址中的用户区域,为新程序建立区域结构,将代码段和数据段分别映射至 hello
的 .text
和 .data
节。程序计数器将指向代码段的入口,下次调度该进程时从此开始执行。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
操作系统为 hello
进程提供一个独立的逻辑控制流和私有地址空间,创造了一种进程独占处理器和内存的假象。
上下文信息:
进程的上下文信息包括栈、寄存器、PC
,系统 IO
等状态信息,每次被抢占时操作系统会保存这些信息,每次进程被调度时操作系统将以这些信息为基准继续执行控制流。
进程时间片:
一个进程执行其控制流的每一个之时间段称为时间片。
进程调度:
操作系统在多个进程间并发执行,一个进程执行一段时间后,操作系统可以让其休眠,然后切换到另一个进程。
用户态与内核态:
shell
使得用户程序有机会修改内核,因此需要手段保护内核。用户态与内核态的划分可以限制当前的进程行为、限制指令的作用范围和数据的可访问、修改范围。
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
常见异常:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6lxroF34-1653009846951)(https://s1.ax1x.com/2022/05/18/O7PfmQ.png)]
常见信号:
命令与键盘触发异常:
正常执行:
挂起进程
程序执行过程中, 按下 Ctrl+Z
挂起进程,然后用 ps
命令查看
用 jobs
可以看见挂起的进程,输入 fg %1
再调回前台执行:
终止进程
运行时按下 Ctrl+C
可以终止进程,由于进程终止,所以 ps
命令看不见进程
乱按键盘
运行时乱按键盘会将按下的内容输出到终端
本章介绍了进程的概念,以及 shell
处理流程,同时展示了 fork
和 execve
函数的原理及其在 hello
程序运行中的作用
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:
源码编译后出现在汇编代码的地址,用来标识一条指令或操作数的地址
线性地址:
如果地址空间中的整数是连续的, 那么我们说它是一个线性地址空间。 为了简化讨论,我们总是假设使用的是线性地址空间。
虚拟地址:
在一个带虚拟内存的系统中,CPU从一个有 N=2^n
个地址的地址空间中生成虚拟地址, 这个地址空间称为虚拟地址空间
物理地址:
一个系统还有一个物理地址空间,对应于系统中物理内存的 M
个字节
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是个索引号,后面3位包含一些硬件细节 。
CPU将一个逻辑地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址,CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。
从线性地址到物理地址的变换由分页机制完成,分页即对虚拟地址的分页,使用页表维护。操作系统的数据传输单元为页,每个大小为 4KB
,其变换流程如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tk6SnRnN-1653009846959)(https://s1.ax1x.com/2022/05/19/O7LNGQ.png)]
对于 n
位虚拟地址,将其分为 VPO
和 VPN
两个部分。MMU
会根据 VPN
选择 PTE
,虚拟页缓存是否命中与通常的缓存命中处理步骤完全相同,即若为缓存,则引发缺页异常,调用缺页处理程序处理,从而获得 PPN
。PPO
和 VPO
是相同的,PPN
和 PPO
拼接形成最终的物理地址。
Intel Core i7 处理器的地址翻译使用四级页表,如图所示:
CPU 产生 VA
后,将其传给 MMU
,先使用 VPN
查询物理页地址是否缓存在 TLB
中,如果在,则得到物理页地址 PPN
;否则不命中,将VPN
分为四段,VPN1
指向一级页表对应页的地址,得到下一级页表的地址,VPN2
在下一级页表中查询,以此类推,VPN4
在最后一级页表中查询到 PPN
,将 PPN
与 VPO
拼接形成最终的物理地址。
获得物理地址后,用此地址分成标记:组索引:字节偏移
三个部分,在 L1
中依次进行如下步骤:
L2
L3
直至主存中对应地址的数据,每一层不匹配都将请求下一层的数据。请求完成后,若组内有空闲块,则直接替换;否则采用一定的策略驱逐某些块,进行替换。shell
调用 fork
创建新的进程时,内核为新进程创建数据结构或 PID
。为了创建虚拟内存,创建新的 mm_struct
,新的区域结构和页表,并保持这些结构与父进程一直,同时使用写时复制策略。
fork
创建一个子进程后,子程序调用 execve
函数加载 hello
程序,依次执行以下步骤:
删除当前进程虚拟地址的用户区域
建立新的区域结构,布局如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KdTiIGyC-1653009846960)(https://s1.ax1x.com/2022/05/19/OHPegK.png)]
若用到共享对象,则将共享对象映射到共享库的内存映射区域
设置程序计数器,使得它指向代码段的入口,下次调度此进程时从此开始执行
当进程执行时遇到一个内存引用,但是这个地址指向的物理页面不在物理内存中,将引发一个缺页,引发缺页处理程序。通过 PTE
能查询该页面在磁盘中的位置,将页面调入物理内存,更新 PTE
,将控制权返回引发故障的指令,内存引用的指令再次访存,能正常执行。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
动态内存管理需要动态内存分配器完成,它维护着进程的虚拟内存的堆区,它 .bss
节之后,向上生长。对每个进程,维护一个变量 brk
,指向堆顶。分配器将堆视作一组不同大小的块,分为已分配
和未分配
两种。
有两种类型的分配器用于维护堆:
显式分配器:
显式地释放任何已分配的块,如 C 语言中任何 malloc
的块,都应该 free
隐式分配器:
分配其检测哪些块是不可达的,从而释放这些块,即垃圾回收器。
为了维护和分配内存,需要使用一些方法来维护堆中哪些块已分配,哪些空闲,有以下做法:
隐式空闲链表,在空闲块的头部标记这个块的大小,以及它是否分配;更优化的做法是在空闲块的脚部维护相同的信息:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hkjnNPXQ-1653009846966)(https://s1.ax1x.com/2022/05/19/OHkva4.png)]
显式空闲链表:
将堆的空闲状态显式维护在真正的链表数据结构中,使用这种方法使得分配块的时间从块总数的线性实现降低到空闲链表长度相关的线性时间。
本章讲述了 hello
程序执行时的内存相关问题。简述了 hello
进程的 fork
和 execve
过程中的内存相关细节,以及从逻辑地址到物理地址的翻译过程、内存缺页处理等问题。
设备的模型化:Linux 世界中的所有设备都被视作文件,设备的输入输出被视作文件读写。
设备管理:Unix I/O 是 Linux 的一种接口,它实现了所有输入输出执行的统一。
Unix I/O 接口:
打开文件:
一个程序需要通过内核来打开一个文件,内核返回文件描述符,之后程序可以通过此描述符来访问文件。
Linux Sehll
创建进程都打开了三个文件:
stdin
,stdout
,stderr
。文件描述符分别为 0
,1
,2
。
改变文件位置:
对打开的文件,内涵维护其文件位置 k
,默认为 0
。应用可以通过执行 seek
来改变其文件位置。
读写文件:
一个读操作是从文件复制 n
个字符 n>0
到内存。若当前文件位置是 k
,读取完成后为 k+n
。对大小为 m
的文件,k >= m
时将触发 EOF。写操作与之类似。
关闭文件:
程序完成对文件的访问后,通知内核关闭文件。进程终止时,内核都会关闭它们打开的文件并释放这些资源。
Unix I/O 函数:
int open(char *filename, int flags, mode_t mode)
int close(int fd)
ssize_t read(int fd,void *buf,size_t n)
ssize_t write(int fd, const void *buf, size_t n)
从vsprintf
生成显示信息,到write
系统函数,到陷阱-系统调用 int 0x80
或 syscall
等.
字符显示驱动子程序:从ASCII到字模库到显示 vram
(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取 vram
,并通过信号线向液晶显示器传输每一个点(RGB分量)。
具体来说,来看看 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;
}
...
表示一种可变参数,((char*)(&fmt) + 4)
表示可变参数的第一个参数的地址,通过 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);
}
可知 vsprintf
可以返回格式化好的字符串。
最后调用 write(buf, i)
,这是一个系统调用,将触发一次异常,系统将调用异常处理程序为我们打印字符。
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;
}
可看到 getchar
是通过 read
来实现文件读的,每次读入不超过 BUFSIZ
字节的内容,失败时返回 EOF
。有如下特性:
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成 ascii
码,保存到系统的键盘缓冲区。
getchar
等调用 read
系统函数,通过系统调用读取按键 ascii
码,直到接受到回车键才返回。
本章讲述了 Linux I/O
的概念与原理,介绍了几种 I/O
函数的使用,特别是 printf
和 getchar
函数的实现。
用计算机系统的语言,逐条总结hello所经历的过程。
终端运行 hello
程序,它将经历以下过程:
hello.c
完成源码hello.c
转化为 hello.i
hello.i
翻译成汇编文件 hello.s
hello.s
编译成可重定位目标文件 hello.o
hello.o
与动态链接库链接为可执行目标文件hello
及其参数,运行程序shell
为 hello
调用 fork
创建子进程。shell
调用 execve
函数加载 hello
,此时重新构建虚拟地址区域,映射虚拟内存,将程序计数器指向程序入口,待下次调度时载入物理内存。hello
执行时需要访问内存,此过程依赖 MMU
将虚拟地址转换为物理地址。printf
等函数将使用内存分配器 malloc
为其向堆中申请内存。Ctrl+C
Ctrl+Z
,将向 hello
发送信号,并转到相应的异常处理程序。hello
进程终止时,shell
回收子进程,内核将删除进程在内存中的数据结构。你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法:
现代计算机系统设计之精妙,令人赞叹。它通过从底层至顶层的一系列概念的层层抽象,不仅使之易于实现,还为底层硬件工作者提供易于维护的平台、为上层开发者提供通用的抽象。这套体系形式之通用,功能之强大,有力推动了人类文明的发展。
列出所有的中间产物的文件名,并予以说明起作用。
hello.c
源码
hello.i
编译预处理文件
hello.s
汇编文件
hello.o
可重定位目标文件
hello
可执行目标文件