摘 要
Hello.c自编译到实行会经历预处置惩罚、编译、汇编、链接,加载和消亡六个过程,在可实行文件加载进入内存后,操纵体系会对该可实行文件内存空间进行管理。本文详细介绍了上述六个部门,并讨论了Linux下的进程管理与存储管理。
关键词:计算机体系;步调人生;P2P;
目 录
第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 情况与工具 - 4 -
1.3 中间效果 - 4 -
1.4 本章小结 - 4 -
第2章 预处置惩罚 - 5 -
2.1 预处置惩罚的概念与作用 - 5 -
2.2在UBUNTU下预处置惩罚的命令 - 5 -
2.3 HELLO的预处置惩罚效果解析 - 5 -
2.4 本章小结 - 5 -
第3章 编译 - 6 -
3.1 编译的概念与作用 - 6 -
3.2 在UBUNTU下编译的命令 - 6 -
3.3 HELLO的编译效果解析 - 6 -
3.4 本章小结 - 6 -
第4章 汇编 - 7 -
4.1 汇编的概念与作用 - 7 -
4.2 在UBUNTU下汇编的命令 - 7 -
4.3 可重定位目标ELF格式 - 7 -
4.4 HELLO.O的效果解析 - 7 -
4.5 本章小结 - 7 -
第5章 链接 - 8 -
5.1 链接的概念与作用 - 8 -
5.2 在UBUNTU下链接的命令 - 8 -
5.3 可实行目标文件HELLO的格式 - 8 -
5.4 HELLO的虚拟地址空间 - 8 -
5.5 链接的重定位过程分析 - 8 -
5.6 HELLO的实行流程 - 8 -
5.7 HELLO的动态链接分析 - 8 -
5.8 本章小结 - 9 -
第6章 HELLO进程管理 - 10 -
6.1 进程的概念与作用 - 10 -
6.2 简述壳SHELL-BASH的作用与处置惩罚流程 - 10 -
6.3 HELLO的FORK进程创建过程 - 10 -
6.4 HELLO的EXECVE过程 - 10 -
6.5 HELLO的进程实行 - 10 -
6.6 HELLO的异常与信号处置惩罚 - 10 -
6.7本章小结 - 10 -
第7章 hello的存储管理 - 11 -
7.1 HELLO的存储器地址空间 - 11 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 11 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 11 -
7.5 三级CACHE支持下的物理内存访问 - 11 -
7.6 HELLO进程FORK时的内存映射 - 11 -
7.7 HELLO进程EXECVE时的内存映射 - 11 -
7.8 缺页故障与缺页中断处置惩罚 - 11 -
7.9动态存储分配管理 - 11 -
7.10本章小结 - 12 -
结论 - 13 -
附件 - 14 -
参考文献 - 15 -
第1章 概述
1.1 Hello简介
P2P:
1. .c文件通过预处置惩罚器CPP生成.i文本文件,完成宏更换、条件编译、头文件包罗、条件界说和行操纵等功能
2. .i文件通过编译器cc1生成.s汇编文件
3. .s文件通过汇编器as将汇编语言源代码翻译成机器码,生成可重定向文件.o
4. .o文件通过链接器ld完成符号解析和重定向,生成可实行文件.out
020:
1. Shell通过fork()函数生成与父进程完全相同的子进程
2. 在子进程中调用execve()函数实行./hello.out可实行文件,将步调内容载入物理内存
3. CPU分配时间片并实行步调的指令。每条指令包罗取指,译码,访问内存,实行四个阶段。
4. 实行结束后Shell接纳子进程,操纵体系关闭文件,释放内存,删除进程描述符等。
1.2 情况与工具
硬件情况:INTEL I7 10850H,4GHZ,4GRAM
软件情况:Ubuntu 22.04
开发和调试工具:gdb,edb,readelf,objdump
1.3 中间效果
hello.i: hello.c预处置惩罚后的文本文件
hello.s: hello.i编译成的汇编文件
hello.o: hello.s汇编后的可重定向文件
hello.out: 最终生成的可实行目标文件
1.4 本章小结
本章说明白Hello步调在Ubuntu 22.04上从源代码到实行过程。该过程依赖于硬件情况INTEL I7 10850H和开发工具gdb、edb等,产生了.i、.s、.o等中间效果文件。
第2章 预处置惩罚
2.1 预处置惩罚的概念与作用
概念:
• 在源代码被编译之前,对源代码进行一系列文本更换和操纵的过程。
作用:
1. 宏界说睁开:预处置惩罚器会查找全部宏界说,并将其更换为其相应的值。
2. 头文件包罗:预处置惩罚器处置惩罚#include预处置惩罚指令,将指定的头文件内容插入到#include指令的位置。
3. 条件编译:处置惩罚#if, #else, #elif 和 #endif等指令,只编译满足条件的代码。
4. 删除注释:删除源代码中的全部注释。
2.2在Ubuntu下预处置惩罚的命令
gcc -E hello.c -o hello.i
图1 预处置惩罚命令(未输出到文件)
图1展示了UBUNTU22.04中预处置惩罚的命令,利用gcc -E即可预处置惩罚.c文件。利用-o hello.i即可输出到.i文件中。
2.3 Hello的预处置惩罚效果解析
图2 预处置惩罚文件1-6行内容
这部门内容提供了有关源代码文件和行号的信息。例如# 1 "/usr/include/stdc-predef.h" 1 3 4:指示当前行是来自"/usr/include/stdc-predef.h"文件的第1行。
图2 预处置惩罚文件13-35行内容
这部门内容同样是行号指示器,例如# 27 "/usr/include/stdio.h" 3 4:指示当前行是来自"/usr/include/stdio.h"文件的第27行。同样内容不再赘述。
图3 预处置惩罚文件73-87行内容
这部门内容是类型界说,例如typedef unsigned long int __u_long;:界说了一个无符号长整数类型,定名为__u_long。
图4 预处置惩罚文件200-211行内容
这部门内容同样是类型界说,主要内容界说了一个布局体类型__mbstate_t,该布局体具有__count和__value两个成员。__count是一个整数类型的成员变量,__value是一个联合体类型的成员变量。联合体__value有两个成员:__wch是一个无符号整数类型的成员,__wchb是一个长度为4的字符数组。
图5 预处置惩罚文件331-342行内容
这部门内容为外部变量和函数的声明,这些声明表明这些变量在其他文件中界说,而在当前文件中只是声明白其存在,以便在当前文件中利用它们,便于在编译时,编译器将这些声明与现实的界说进行关联。
图6 预处置惩罚文件963-983行内容 图7 预处置惩罚文件984-1006行的内容
这部门内容是一个罗列(enum)界说,用于界说一组具有相干值的常量。该enum界说了一个匿名罗列类型,其中包罗了一系列常量。每个常量都由一个标识符和一个关联的整数值组成。如_PC_LINK_MAX:表示文件体系中的硬链接的最大数量。
图8 预处置惩罚文件963-983行内容
这部门内容是一个静态内联函数的界说,函数的主要目标是实现16位整数的字节序转换。如__bswap_16 (__uint16_t __bsx),表示函数担当一个参数__bsx,类型为__uint16_t。
图9 预处置惩罚文件3078-3091行内容
这部门内容是C语言步调的主函数界说,# 10 "hello.c":指示当前行是源代码文件"hello.c"的第10行,其余为优化后的主步调代码,同时可见注释部门被删除。
2.4 本章小结
这本章介绍了预处置惩罚阶段的概念和作⽤,并分析了预处置惩罚效果,展示了预处置惩罚文件的部门内容。
第3章 编译
3.1 编译的概念与作用
概念:将高级步调语言(如C、C++、Java等)编写的源代码转换为低级机器语言(汇编语言)的过程
作用:编译的主要作用是将高级语言编写的步调转换为机器语言,以便计算机可以大概理解和实行。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
图10 将.i文件编译为.s文件过程
3.3 Hello的编译效果解析
总体分析:
图11 汇编文件4-11行内容
.section .rodata声明白一个名为.rodata的只读数据段。.align 8指令将数据对齐到8字节边界。.LC0标号界说了一个字符串常量:"\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"。这是一个利用UTF-8编码的字符串,其内容为:用法: Hello 学号 姓名 秒数!
.LC1标号界说了另一个字符串常量:"Hello %s %s\n"。
接下来的.text指示下面的指令是代码段的一部门。.globl main声明白一个全局符号main,表示下面的代码是主函数。.type main, @function指令指定了main符号的类型为函数。
图12 汇编文件12-33行内容
自main到movq %rsi, -32(%rbp)这部门代码设置了函数的起始标号和一些函数调用约定。它保存了当前函数的栈帧,分配了32字节的栈空间,并将传递给main函数的参数保存在栈帧中的位置。
自cmpl $4, -20(%rbp)到call exit@PLT这部门代码实行了一个条件判定和相应的分支。它将-20(%rbp)中的值与4进行比力,假如相等,则跳转到标号.L2。否则,它会加载字符串常量.LC0的地址到寄存器%rax,将该地址作为参数传递给puts函数(用于输出字符串),然后调用exit函数退出步调。
图13 汇编文件34-62行内容
这部门开始一个循环标号 .L4,随后从栈帧中加载某个位置的值,进行一系列操纵,包括利用 printf 函数输出字符串、利用 atoi 函数将字符串转换为整数、利用 sleep 函数进行延时操纵等。
随后对栈帧中的另一个位置进行加法操纵。判定循环计数器是否小于等于 8,假如是则跳转到标号 .L4 继续循环,否则跳出循环。调用 getchar 函数获取用户输入的字符。返回函数。
图14 汇编文件63-82行内容
这部门内容由.size到0:从上到下依次界说了符号main的大小、编译器的版本、步调在实行时的堆栈行为、GNU属性信息、字节对齐、标号1与标号0之间的偏移量、标号4与标号1之间的偏移量,并界说了一个整数值5。后文同理。这些信息应用于调试、优化和正确链接步调。
下面详细分析每一种数据类型和操纵:
3.3.1 字符串
图15 汇编文件.rodata区字符串存储内容
由于汉字利用UTF-8编码,故无法直接解析为字符,每三个字节为一个汉字,这里\347\224\250即为一个汉字(起始数字大于ASCII码界说的200),上面所提到的数字均为八进制。于是可以将其转换为二进制字符串如下:\xe7\x94\xa8,即汉字:用,故该句为: 用法: Hello 学号 姓名 秒数!
图16 字符串转换效果
3.3.2 数组及其操纵
图17 main函数参数传递过程
如图17所示,movl %edi, -20(%rbp)和movq %rsi, -32(%rbp)把传入main函数的前两个参数(分别在%edi和%rsi寄存器中)保存到栈上,而第两个参数为字符串数组。
图18 实行到该步时栈帧数据
图19 rbp偏移后指向值
根据该部门调试信息,argv指向字符串首地址指针数组,在rbp中存放变量应为字符串数组的首地址,对字符类型来说即为指针的指针,即char**,同时可以看出Intel X86-64为小端序,每一项指向一个参数的起始地址,每个参数字符串同样在堆栈中。
图20 读取字符串数组
可见读取字符串数组时,先利用rbp找到字符串数组的起始地址,随后利用该起始地址变址寻址即可。
3.3.3 局部变量
图21 for循环处汇编代码
这里i被存储在-4(%rbp)处。每次循环时,都在栈中访问i值,每次进入循环时,都在栈中开发空间存储i值。现实上这里可能被编译器优化为register int,即存放在寄存器中。
3.3.4 常量
图21 if(argc!=4)处汇编代码
可见常量在利用时每每为立即数,若const int,在编译时会根据优化选项的差异,基于速率或内存优化会分配为立即数或存储在.rodata区中。
3.3.5 赋值语句
图22 for(i=0)处汇编代码
图23 getchar()处汇编代码
赋值语句会利用mov指令赋值,若a=b,则为内存地址或寄存器间的移动,若为a=x(数字),则为立即数。
3.3.6 类型转换语句
图24 atoi()处汇编代码
对于扩展的类型,如long与int汇编代码不关心,故差异类型间直接位扩展后作为参数输入。对于需要转化的类型,如int与string,则调用函数,如图24即为调用atoi将输入的最后一个参数从字符串类型转换为整数类型。
3.3.7 算术操纵
图25 i++处汇编代码
对于加减法,直接调用addl命令,对于乘法,则利用移位与加减或者调用imul,mul命令,对于除法则调用idiv,div命令。
3.3.8 关系操纵及控制转移
图26 if处汇编代码
关系操纵与控制转移利用jmp指令实现。这部门内容代表参数数量等于4,则跳转到.L2标签处实行,即实行print循环,否则实行printf("用法: Hello 学号 姓名 秒数!\n");并退出。
3.3.9 循环函数
图27 for循环结束处汇编代码
循环函数同样利用jmp指令,这部门内容代表假如i小于9,则继续循环,否则跳出循环。
3.3.10 函数操纵
图28 if(argc!=4)结束部门汇编代码
可见函数的调用利用call指令实现,这里PLT表示该符号在步调的过程链接表中解析,由于是体系调用。该过程可能涉及到调用者与被调用者保存等,最后这些函数后将返回值存储在%rax寄存器中,传递给主函数处置惩罚。
3.4 本章小结
本章介绍了编译的概念和作用,并进⾏了编译命令的演示,同时详细分析了汇编代码的作用,说明白编译过程中各种数据类型和操纵处置惩罚过程。
第4章 汇编
4.1 汇编的概念与作用
概念:编译器根据汇编代码生成机器语言二进制步调的过程。
作用:将汇编代码转换为机器指令,生成可重定向文件,使之在链接后可以大概被直接实行。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
图29 ubuntu下的汇编命令
4.3 可重定位目标elf格式
利用readelf -a hello.o>hello_o.elf.txt将其存储在文件hello_o.elf.txt中以便进一步分析。
图30 elf头内容
Magic: 文件的魔术字节,用于标识文件为ELF格式。以0x7f 45 4c 46的字节序列开始,对应ASCII码 "ELF"。
Class: 表明ELF文件的类型,这里ELF64表示是一个64位的ELF文件。
Data: 表示数据的编码方式。2's complement, little endian表示数据以小端序的二进制补码情势存储。
OS/ABI: 这表示目标操纵体系/ABI,UNIX - System V表示该ELF文件为System V类型的Unix体系设计的。
Type: 表示文件类型。REL (Relocatable file)表示这是一个可重定位文件。
Machine: 描述目标架构,Advanced Micro Devices X86-64表示该文件是为AMD的x86-64架构设计的。
Entry point address: 步调的入口点,对于重定向文件该值为0。
Start of program headers: 步调头部开始位置,对于重定向文件该值为0。
Start of section headers: 节区头部的开始位置,此处该值为1064字节。
Size of this header: ELF头部的大小,此处该值为64字节。
Size of section headers: 节区头部的大小,此处该值为64字节。
Number of section headers: 节区头部的数量,此处该值为14。
Section header string table index: 节区头部字符串表的索引,字符串表包罗了其他节区名字的字符串,此处该值为13。
图33 Section头内容
这是节头信息,节头信息描述了可实行文件中各个节的属性和位置。各字段含义如下:
[Nr]:节的编号。
Name:节的名称。
Type:节的类型,标识该节包罗的数据类型。
Address:节的虚拟内存地址。
Offset:节在文件中的偏移量。
Size:节的大小。
EntSize:节中每个实体的大小。
Flags:节的标志,描述该节的属性和权限。
Link:与该节相干的节的索引。
Info:附加信息,依赖于节的类型。
Align:节的对齐方式。
其中包罗了13个节,每个节含义分别如下:
.text:可实行代码节
.rela.text:重定位节,存储.text节中需要进行重定位的代码的相干信息
.data:已初始化的静态和全局C变量。
.bss:未初始化的全局和静态C变量,全部被初始化为0的全局或静态变量。
.rodata:只读数据节,存放只读数据。如一些字符串或switch跳转表。
.comment:存储注释信息,在可实行文件或目标文件中保存有关编译器和编译选项的注释文本。
.note.GNU-stack:指示操纵体系和链接器关于可实行文件的栈实行属性,告知操纵体系是否允许在实行该文件时利用栈。
.note.gnu.pr[…]:存储特定的GNU属性信息,提供了附加的与可实行文件相干的属性描述。这与.note.GNU-stack节同样以.note开头,以.note开头的节表明该节是一个包罗注释信息的节。
.eh_frame:用于异常处置惩罚,存储了与异常处置惩罚相干的调试和运行时信息以支持步调的异常处置惩罚和堆栈睁开。
.rela.eh_frame:存储.eh_frame节中需要进行重定位的信息。
.symtab:符号表,存储步调中界说和引用的符号(如函数、变量等)的信息。
.strtab:字符串表,包括.symtab和.debug节中的符号表,以及节头部的节名字,用于链接和调试过程中的符号解析、地址转换和调试信息的体现。
.shstrtab:存储节区名称。
图34 重定向节内容
重定位节包罗.text 节中需要进⾏重定位的信息,各个字段含义如下:
Offset:相对于该节的偏移量,表示需要进行重定位的指令或数据的偏移。
Info:指定符号表中相干符号的索引或其他重定位信息。
Type:重定位的类型,指示重定位的方式和规则。
Sym. Value:相干符号的值,即符号的地址或偏移量。
Sym. Name:相干符号的名称。
Addend:附加的常量偏移量。
图35 符号表内容
符号表存放在步调中界说和引用的函数和全局变量的信息。各个字段含义如下:
Num:符号表中的索引号。
Value:符号的值,即符号的地址或偏移量。对于函数符号,该值表示函数的入口地址。
Size:符号的大小,表示符号占据的字节数。
Type:符号的类型,指示符号是函数、数据还是其他类型。
Bind:符号的绑定属性,表示符号的可见性和链接性。
Vis:符号的可见性,指示符号在链接过程中的可见性级别。
Ndx:符号所在的节的索引或标识符。
Name:符号的名称。
4.4 Hello.o的效果解析
图36 objdump命令的利用
图37 objdump后生成的机器码
机器语言的构成:
由操纵码和地址码构成,通常都有一个固定的长度,比如在 x86 架构中,一条指令的长度可以从 1 个字节到 15 个字节不等。
区别:
汇编代码中,操纵数和偏移量以十进制表示,而机器码则以十六进制表示。
汇编文件中包罗了.cfi指令,用于描述堆栈帧的信息,机器码不包罗这些内容。
映射关系:
操纵数顺序:在 AT&T 语法中,源操纵数在目标操纵数之前。例如,movl %edi, -20(%rbp) 是将 EDI 寄存器的内容移动到基指针 RBP 减 20 字节的位置。在机器代码中,则为48 89 c7,可以看出48的机器码表示mov命令。
直接和间接寻址:例如,-20(%rbp) 代表的是间接寻址。这表示 RBP 寄存器值减去 20 的地址。在机器代码中用特殊编码(寻址特征+偏移)表示。
分支和跳转:例如,je .L2 和 jle .L4 分别表示"假如相等则跳转到 .L2" 和 "假如小于或等于则跳转到 .L4"。在机器代码中,这些被编码为特定的跳转指令(je 和 jle)后面跟着一个相对于当前指令的偏移量,而不是标签。
函数调用:例如,call puts@PLT 和 call atoi@PLT 表示调用函数 puts 和 atoi。这些在机器代码中被编码为 call 指令后面跟着目标函数的相对偏移。
4.5 本章小结
本章介绍了汇编的概念和作用,并分析可重定位目标elf格式中的elf头,节头表,可重定位条目,符号表,并就hello.o的反汇编文件与hello.s文件的差异,讨论了机器语言的组成,与汇编代码的映射。
第5章 链接
5.1 链接的概念与作用
概念:将差异可重定向文件中的节组合在一起,生成可实行文件或库文件的过程。
作用:通过符号解析和重定位,将多个可重定向文件进行组合,并处置惩罚模块之间的引用关系,生成可实行文件或库文件。这样使模块可以分别编译,方便项目编写与管理。
5.2 在Ubuntu下链接的命令
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o hello.o -L/usr/lib -lc -o hello.out
图38 链接命令与效果
5.3 可实行目标文件hello的格式
图39 可实行文件的ELF头
可实行文件的ELF头中与可重定向文件相同的内容见第四章,这里不再赘述。差异内容如下:
Type: EXEC ,表示 ELF 文件的类型,指示该文件是一个可实行文件。与可重定位文件的 ELF 头部差异,可重定位文件的类型为REL 。
Entry point address (入口点地址): 0x4010d0,表示步调实行的入口点地址,即步调开始实行的地址。与可重定位文件的 ELF 头部差异,可重定位文件的入口点地址为 0x0。
同时可见该文件共有 30 个节,数量大于可重定向文件的14节(见图30)。
图40 第0-11节节头内容 图41 第12-24节节头内容
关于各字段的内容不再赘述,见第四章。下面就功能对两者区别加以讨论:
Section的种类和内容:在可实行文件和目标文件中,Section Headers可能会描述差异种类和内容的sections。例如,可实行文件中通常包罗'.text'(存储步调代码)、'.data'(存储初始化的全局和静态变量)和'.bss'(存储未初始化的全局和静态变量)等section。而目标文件可能会包罗更多描述编译但尚未链接的代码和数据的sections,如'.rel.text'(存储'.text' section的重定位信息)或'.symtab'(存储符号表)等。
链接信息:在目标文件中,sections通常会包罗未解析的符号和需要在链接阶段解析的其他信息。这些信息可能会在'.symtab'或'.rel.text'等sections中找到。然而,在可实行文件中,全部的符号和地址通常都已经被链接器解析,以是不会包罗这类信息。
运行时加载:对于可实行文件,sections如'.text'、'.data'、'.rodata'、'.bss'等在步调运行时会被加载到内存中。然而,对于目标文件,其sections不会被直接加载到内存中,而是在链接阶段被其他代码引用和包罗。
而对于各字段的值和节的类型,可重定向文件与可实行文件也有所差异:
Address字段:可重定位文件的节头表Address字段通常是相对于可重定位文件加载到内存的基地址的偏移量。而可实行文件的节头表中的地址字段是现实加载到内存中的地址。
Type字段:在可重定位文件中,Type字段包括 PROGBITS (步调相干的信息)、NOBITS (应被分配空间但不占用文件空间的信息)、SYMTAB (符号表)、STRTAB (字符串表) 等。而在可实行文件中,Type字段包括更多与实行相干的类型,如 DYNAMIC (动态链接信息)、NOTE (注释信息)、HASH (符号哈希表)、GNU_HASH (GNU样式的符号哈希表) 等。
图41 步调头节内容
同样各字段内容在第四章均有介绍,下面扼要表明一下各个类型:
PHDR: 描述步调头表自身在文件和内存中的位置和大小。
INTERP: 描述了步调表明器,如动态链接器:"/lib64/ld-linux-x86-64.so.2"。
LOAD: 标识可加载的段。包罗只读代码和数据等。
DYNAMIC: 包罗了动态链接信息,如所需的共享库和符号。
NOTE: 包罗了辅助信息,比如ABI版本信息,或者是其他辅助的元数据。
GNU_PROPERTY: 这个段用于保存有关目标文件的各种属性的信息。
GNU_STACK: 标识了步调的堆栈不可实行。
GNU_RELRO: 标识部门内存地区为只读,增强步调的安全性。
图42 段列表内容
这部门内容为步调的段(section)列表,描述了步调在内存中的差异段及其含义:
.interp 是指向动态链接器(dynamic linker)的路径的段。
.note.gnu.property、.note.ABI-tag、 .hash、 .gnu.hash、 .dynsym、 .dynstr、 .gnu.version、 .gnu.version_r、 .rela.dyn、 .rela.plt 是与步调的符号表、重定位信息和版本信息相干的段。
.plt、.plt.sec、.text 是存储步调的可实行代码的段。
.rodata、.eh_frame 是存储步调的只读数据和异常处置惩罚信息的段。
.dynamic、.got、.got.plt、.data 是存储步调的动态链接信息和全局变量的段。
.dynamic 是存储动态链接信息的段。
.note.gnu.property 是与步调的GNU属性相干的段。
.note.ABI-tag 是与步调的ABI标签相干的段。
.note.gnu.property 是与步调的GNU属性相干的段。
.dynamic、.got 是存储步调的动态链接信息和全局变量的段。
图43 动态段内容
这段内容为动态段,它包罗了步调在运行时需要用到的动态链接信息,各部门含义如下:
0x0000000000000001 (NEEDED): 表示步调所需要依赖的共享库,这里指定了一个名为 "libc.so.6" 的共享库。
0x0000000000000004 (HASH): 给出符号哈希表的地址。
0x000000006ffffef5 (GNU_HASH): 给出GNU哈希表的地址。
0x0000000000000005 (STRTAB): 给出字符串表的地址,其中包罗了动态链接所需的字符串。
0x0000000000000006 (SYMTAB): 给出符号表的地址,其中包罗了动态链接所需的符号信息。
0x000000000000000a (STRSZ): 给出字符串表的大小(以字节为单元)。
0x000000000000000b (SYMENT): 给出每个符号表条目标大小(以字节为单元)。
0x0000000000000015 (DEBUG): 给出调试信息的地址。
0x0000000000000003 (PLTGOT): 给出过程链接表(Procedure Linkage Table)的全局偏移地址。
0x0000000000000002 (PLTRELSZ): 给出过程链接表重定位表的大小(以字节为单元)。
0x0000000000000014 (PLTREL): 给出过程链接表重定位表的类型,这里是RELA。
0x0000000000000017 (JMPREL): 给出过程链接表重定位表的地址。
0x0000000000000007 (RELA): 给出重定位表的地址,这里是RELA类型的重定位表。
0x0000000000000008 (RELASZ): 给出重定位表的大小(以字节为单元)。
0x0000000000000009 (RELAENT): 给出每个重定位表条目标大小(以字节为单元)。
0x000000006ffffffe (VERNEED): 给出版本需求表的地址。
0x000000006fffffff (VERNEEDNUM): 给出版本需求表的数量。
0x000000006ffffff0 (VERSYM): 给出版本符号表的地址。
0x0000000000000000 (NULL): 表示动态段的结束。
图44 重定位段内容
上图展示了两个重定位段的内容,各字段含义如下:
Offset:重定位条目标偏移地址。
Info:重定位条目标信息。
Type:重定位的类型。
Sym. Value:符号的值(地址)。
Sym. Name + Addend:符号的名称和修正值。
下面对于两个段分别进行分析:
对于 '.rela.dyn' 段:
000000403ff8:重定位偏移地址。
000100000006:重定位信息。
R_X86_64_GLOB_DAT:重定位类型,表示全局数据对象。
0000000000000000:符号的值(地址)。
__libc_start_main@GLIBC_2.34 + 0:符号的名称和修正值。
对于 '.rela.plt' 段:
000000404018、000000404020、000000404028、000000404030、000000404038、000000404040:重定位偏移地址。
000200000007、000300000007、000400000007、000500000007、000600000007、000700000007:重定位信息。
R_X86_64_JUMP_SLO:重定位类型,表示懒惰(lazy)跳转槽。
0000000000000000:符号的值(地址)。
puts@GLIBC_2.2.5、printf@GLIBC_2.2.5、getchar@GLIBC_2.2.5、atoi@GLIBC_2.2.5、exit@GLIBC_2.2.5、sleep@GLIBC_2.2.5:符号的名称和修正值。
这两个重定位段的作用均为告知链接器和加载器在步调运行时怎样将需要利用的符号引用链接到正确的地址上。(即用于动态链接)
图45 .dynsym符号表内容
这些条目描述了步调中利用的一些函数符号(如 _[...]、puts、atoi、exit、sleep)以及它们的类型、绑定属性和可见性。这些符号的详细界说来自于外部的共享库,步调在运行时会通过动态链接来解析这些符号,并将其链接到正确的地址上。各字段与各项的详细含义不再赘述。
图46 .symtab符号表内容
与 '.dynsym' 符号表相比,'.symtab' 符号表提供了更多的符号信息,包括本地符号、源文件名、弱符号以及其他一些符号的详细信息。但是这些信息都用于动态链接时的符号解析和链接。
在符号表后另有几部门,重要性相对较低,不再附图:
版本符号:对 '.gnu.version' 段的描述,列出了版本符号表中的条目,包括地址、偏移、链接到的符号表索引以及版本符号的名称。
版本需求(Version needs):是对 '.gnu.version_r' 段的描述。列出了版本需求表中的条目,包括地址、偏移、链接到的字符串表索引以及版本需求的名称和标志。
.note.ABI-tag段:表示操纵体系和 ABI 版本号等信息
.note.gnu.property段:表示与 GNU 属性相干的注释等信息
5.4 hello的虚拟地址空间
图47 edb运行时截图
图48 可实行文件elf头部门内容
图49 edb的401000-401100段内容
图50 可实行文件objdump效果
根据5.3部门图48可知,offset为401000时,为.plt 段内容,即可实行代码段,同理可以在edb的datadump区中观察到可实行代码段,其内容大抵为一些机器代码,如图50所示,401000处内容为ff 35 02 30 00 00,与edb的反汇编效果相同,故该部门代码应为push 0x3002(%rip),其余代码部门同理,不再赘述。
图51 可实行文件elf头部门内容
图52 edb的4002e0-4002f0段内容
可以看到该部门有一些动态链接库的信息,正是.interp段内容,如图50所示,
.interp段起始地址为4002e0,结束地址为4002e0+1c=4002fc,对于图51,正好为图51 4002f0后12字节内容,其最后一字节的内容为32,.note.gnu.pr[...]段与.note.ABI-tag段的内容与之同理,不再赘述。
图53 edb的402000-402100段内容
如图是402000-402100段内容,其中402010已被解析,为Hello,则显然e6 b3 95为汉字‘法’的UTF-8编码,根据UTF-8解码\xe6\xb3\x95'效果如下图所示:
图54 UTF-8解码效果
同时3a为符号冒号的ascii码,20为符号空格的ascii码。其余字符同理,该部门内容即为:用法: Hello 学号 姓名 秒数!\n。该虚拟空间地址与elf文件中声明地址完全相同,大小也与节头部表完全同等。
5.5 链接的重定位过程分析
图55 可实行文件的objdump效果.plt段
图56 可实行文件的objdump效果.plt.sec段
上两张图的内容均用于动态链接时,故在可重定向文件的objdump中不存在图55与图56所示段,并且在可重定向的对象文件中,这些地址都是相对的,它们会在链接阶段被赋予现实的值。
图55部门内容为函数间接跳转表,对于由动态链接库提供的函数,编译器生成的代码并不直接跳转到这些函数,而是跳转到 PLT 中对应的条目,然后再由 PLT 条目中的代码通过全局偏移表找到真正的函数地址进行调用。
对于图56所示的部门,其内容为动态链接的函数,如 puts@plt, printf@plt, getchar@plt, atoi@plt, exit@plt 和 sleep@plt等。
图57 可实行文件的objdump效果.text段
图58 可重定向文件的objdump效果.text段
如图57所示为该可实行文件objdump效果的.text段,与图58所示可重定位文件objdump效果中的.text段对比,可见差异。如在图57中,链接器添加了一些引导代码(如 _start),这些代码在步调启动时运行。对于可重定向文件,这些代码是不必要的。
别的,链接器解析了全部的符号引用,将它们更换为正确的地址。如图58所示,在可重定向文件中,这些符号引用只是一个占位符,如图58所示,地址为23处代码如下:23: e8 00 00 00 00 call 28 <main+0x28>,这里00 00 00 00即为一个占位符,在可实行文件中,这个符号引用被更换为79 ff ff ff。同理,在链接过程中,符号 puts@plt 被解析为地址 0x401070等。
其链接的详细过程如下:起首,链接器会读取全部的.o文件和库文件,网络其中全部的符号和对应的地址。然后,链接器会读取hello.o中的重定位条目,找到其中全部需要重定位的位置和对应的符号。最后,对于每一个需要重定位的位置,链接器会查找对应的符号在最终可实行文件中的地址,然后将这个地址写入到需要重定位的位置。
以puts函数为例,hello.o中的重定位条目可能会包罗以下信息:.text: .quad puts-0x4,这个条目即告诉链接器,需要将.text段中对应位置的值(即占位符)重定位为puts函数在可实行文件中的地址。
两者差异:
符号表:可实行文件和可重定向文件都有符号表,但它们的内容有所差异。可实行文件的符号表通常只包罗全局符号,而可重定向文件的符号表还会包罗局部符号。别的,可实行文件的符号表还会包罗动态链接的符号。
重定位信息:只有可重定向文件才会包罗重定位信息。这些信息用来指示链接器怎样将各个符号链接在一起。在可实行文件中,链接已经完成,故无需重定位信息。
地址:可实行文件的代码段起始地址为虚拟空间的真实起始地址,同时跳转指令和调用指令所利用的地址为绝对地址,并且各个符号均解析完毕;而对于可重定向文件,其起始地址为0,跳转指令和调用指令所利用的寻址方式为基于main函数偏移量,并且地址字段处可能存在占位符(全0地址)。
动态链接信息:可实行文件会包罗动态链接信息。这些信息用来指示运行时链接器怎样加载和链接动态库,而在可重定位文件中这些信息不存在。
5.6 hello的实行流程
图59 edb实行hello文件
其利用的子步调名或步调地址如下所示:
0000000000401000 <_init>
0000000000401020 <.plt>
0000000000401090 <puts@plt>
00000000004010a0 <printf@plt>
00000000004010b0 <getchar@plt>
00000000004010c0 <atoi@plt>
00000000004010d0 <exit@plt>
00000000004010e0 <sleep@plt>
00000000004010f0 <_start>
0000000000401120 <_dl_relocate_static_pie>
0000000000401125 <main>
00000000004011c0 <__libc_csu_init>
0000000000401230 <__libc_csu_fini>
0000000000401238 <_fini>
其运行过程如下所述:
步调加载:操纵体系起首将步调的二进制文件加载到内存中。加载器会读取二进制文件的头部信息,了解步调的内存布局,然后将步调的各个段(比如 .text,.data,.bss 等)加载到内存中的适当位置。
动态链接:在步调加载到内存后,动态链接器会接管控制权。动态链接器起首会解析步调的动态链接信息,找出步调需要的全部动态库,并将这些库加载到内存中。然后,动态链接器会处置惩罚这些库中的符号,将它们链接到步调中的适当位置。
步调启动:在全部的动态链接都完成后,控制权会交回给步调,步调会从 _start 符号指向的位置开始实行。_start 会完成初始化工作,然后调用 main 函数。
main 函数:main 函数中实行步调的主要逻辑。当 main 函数返回时,它的返回值被当作步调的退出码。
步调停止:当 main 函数返回后,控制权会回到 _start,_start 会调用 exit 体系调用来结束步调的运行。在 exit 调用之前,_start 还会负责调用全部注册的退出处置惩罚函数。最后,操纵体系会接纳步调利用的全部资源,包括内存,文件描述符等,步调就此结束。
5.7 Hello的动态链接分析
分析hello步调的动态链接项目,通过edb调试,分析在dl_init前后,这些项目标内容变化。要截图标识说明。
起首可以在可实行文件elf头处找到.got的地址(.got即Global Offset Table,全局偏移表),为403ff8,长度8个字节。
图60 .got地址
步调的动态链接过程,由GOT表和PLT表之间的交互实现,于是分析该地址处内容。在实行dl_init前,该部门数值为全0,即不存在任何内容(尚未动态链接),在实行结束后该部门内容变化为00 00 7f 0f 72 82 9d c0,其指向一条缓解分支预测的代码,404000处指向403e78。
图61 .got地址处内容
显然这是一个指针,观察403e78处内容,其内容为一条跳转指令,jmp 0x4004a0,而4004a0处为put@plt内容。故可见GOT[1]中存放的是重定位条目标地址。最后动态链接器根据以上压入栈中的两个参数成功的找到了puts的地址,并调用了该函数。
5.8 本章小结
本章介绍了链接的概念和作用,详细展示了hello.out的虚拟空间,并以此为例,详细分析了各个段的内容、重定位过程、虚拟地址空间以及实行流程。
第6章 hello进程管理
6.1 进程的概念与作用
概念:计算机中正在运行的步调的实例。
作用:使每个步调都有本身的地址空间、数据和代码,并且可以独立地运行、调度和管理。
6.2 简述壳Shell-bash的作用与处置惩罚流程
作用:
表明实行用户输入的命令,并将其转化为操纵体系可以大概理解和实行的指令。
处置惩罚流程:
提示符:起首shell在命令行界面中体现一个提示符,指示用户可以输入命令。
读取命令:当用户输入命令后,Shell 会读取用户输入的内容。
解析命令:Shell 对用户输入的命令进行解析,辨认命令名称和参数,并进行语法检查。其详细过程如下:起首解析器会检查命令的语法是否正确,比如检查括号和引号是否配对,管道、重定向等符号是否利用正确。假如有语法错误,Shell会提示错误并要求重新输入命令。随后Shell会按照空格把命令切割成一个个的单词。第一个单词通常是要实行的命令,后面的单词是命令的参数。
实行命令:一旦命令被解析和验证,Shell 将调用相应的步调或实行相应的操纵体系指令来实行该命令。
输出效果:实行命令后,Shell 将体现命令的输出效果(假如有的话)。
返回提示符:命令实行完毕后,Shell 会等待用户继续输入下一个命令,并体现新的提示符。
6.3 Hello的fork进程创建过程
起首,当步调运行到创建子进程的位置时,调用fork()体系调用。
随后,操纵体系内核收到fork()体系调用后,会创建一个新的进程。这个新进程是原进程(父进程)的一个完全复成品,包括代码、数据、堆栈等。
同时,在新创建的进程中,fork()体系调用返回一个值。通过fork()返回的值可以判定当前进程是父进程还是子进程。
随后,在父进程中,通过fork()返回的子进程ID来辨认子进程的标识,等待子进程完成;在子进程中,调用execve()函数来实行hello,更换当前进程的代码和数据。
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个新步调。execve函数加载并运行可实行目标文件hello,且带参数列表argv和情况变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用步调,调用成功不会返回。与fork差异,fork一次调用两次返回,execve一次调用从不返回。
execve函数在当前进程的上下文中加载并运行一个新的步调。下面给出它的函数原型:
int execve(const char *filename, char *const argv[], char *const envp[]);
可以看出该函数担当三个参数:filename,表示要加载的可实行目标文件,argv,表示参数列表,envp,表示情况变量。当调用execve函数时,当前进程的上下文将被更换为新步调的上下文。假如调用成功,execve函数不返回任何值(原步调空间已被破坏),新步调的可实行目标文件将被加载到内存中,并开始实行。
6.5 Hello的进程实行
Shell启动步调: 用户在shell中输入hello命令后,shell作为一个父进程,会调用fork()函数创建一个子进程。这个子进程是父进程的一个副本,包括进程上下文(内核上下文和用户上下文)和虚拟地址空间。同时,子进程将会修改某些值,如步调计数器(PC)。
加载步调: 子进程会调用execve()体系调用以运行新的步调,这个体系调用会启动加载器(例如在Unix体系上通常是/ lib/ld.so)。加载器会清空子进程现有的虚拟内存段,并且加载新的步调。它会创建一组新的代码、数据、堆和栈段。通过将虚拟地址空间中的页映射到hello可实行文件的页大小的片段,新的代码和数据段被初始化为hello步调的内容。
实行步调: 加载器最后会跳转到新步调的入口点,这通常是一个名为_start的函数。该函数会设置步调运行所需的情况,并最终调用main函数,随后开始实行hello步调。
上下文切换和时间片: 上下文是指进程自身的虚拟地址空间,分为用户级上下文和体系及上下文。时间片是指进程实行其逻辑控制流的一部门的每个时间段。在多任务操纵体系中,多个进程会轮流利用处置惩罚器的时间,当一个进程的时间片耗尽,操纵体系会保存该进程的上下文(即,全部必需的寄存器、堆栈状态等),然后将控制权交给下一个进程。
内核模式和用户模式: 在实行过程中,进程可能需要实行一些需要特权的体系操纵,如读写文件或发送网络数据。此时,进程会从用户模式切换到内核模式。在内核模式下,进程可以实行特权指令和访问内核代码和数据。完成这些操纵后,进程会切换回用户模式,继续实行其自身的代码。
6.6 hello的异常与信号处置惩罚
运行过程中可能四类异常:中断、陷阱、故障、停止,下面分别是其代表的含义。
中断:来自I/O设备的信号,异步发生。硬件中断的异常处置惩罚步调被称为中断处置惩罚步调。
陷阱:是实行一条指令的效果。调用后返回到下一条指令。
故障:由错误情况引起,可能能被修正。修正成功则返回到引起故障的指令,否则停止步调。
停止:不可恢复,通常是硬件错误,这个步调会被停止
在hello的实行过程中,每一种异常均可能出现。下面分别讨论每一种异常的出现和处置惩罚:
中断:
图62 hello的中断
可见当键盘输入ctrl-c时,Shell会向该进程发送 SIGINT 信号。这会导致步调立即制止实行,并进行清算操纵。
陷阱:在hello的实行过程中,调用sleep函数的时候会出现这个异常。
故障:在实行hello时,可能出现缺页故障。
停止:假如RAM破坏,在实行hello时,可能出现奇偶校验错误导致的停止。
下面的表格展示了hello实行中全部可能出现的信号:
序号
名称
产生原因
处置惩罚方法
2
SIGINT
来自键盘的中断,输入ctrl-c
调用信号处置惩罚步调,这里为停止hello
6
SIGABRT
hello进程出现异常
调用信号处置惩罚子步调,停止hello并转储内存
14
SIGALRM
来自alarm的定时器超时
调用信号处置惩罚子步调
17
SIGCHLD
子进程结束,发送给父进程
父进程收到信号,忽略或接纳子进程
18
SIGCONT
继续运行
调用信号处置惩罚子步调,继续进程
20
SIGSTP
挂起进程
调用信号处置惩罚子步调,挂起进程
下面分析一些其他的常见键盘输入导致的异常:
图63 hello的挂起
上图展示了利用ctrl-z挂起hello进程并用jobs打印作业的效果,输入fg后,步调继续运行,效果如图64所示。输入pstree后可以将全部进程以树状图体现,效果如图65。
图64 hello的继续运行
图65 pstree的打印效果
同理,可以用kill命令+pid向某一个进程发送指定信号。该指令简单清晰,故展示省略。
6.7本章小结
本章主要介绍了步调怎样从可实行文件加载并运行的过程。分析了shell的处置惩罚流程和作用。讨论了fork函数和execve函数的运行,及上下文切换机制等。
第7章 hello的存储管理
7.1 hello的存储器地址空间
(以下格式自行编排,编辑时删除)
联合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
概念:
• 线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合情势,分页机制中线性地址作为输入。
• 虚拟地址:也就是线性地址。
• 物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址
• 逻辑地址:指步调中利用的地址空间。在实行步调时,步调利用逻辑地址来引用内存中的数据和指令。逻辑地址是由步调员界说的,通常是从0开始的一连整数。差异的进程可以有差异的逻辑地址空间。在hello.c步调编译后的可实行步调反汇编效果中,可见hello.out.objdump.txt,变量的地址或函数的地址都被表示为逻辑地址。
• 线性地址:线性地址是指经过分段和分页机制转换后的地址。在利用分段和分页的内存管理方案中,线性地址是由逻辑地址通过段选择和页表转换得到的。线性地址提供了一个统一的地址空间,使得每个进程都可以访问相同的地址范围。线性地址是虚拟地址和物理地址之间的中间层。
• 虚拟地址:即线性地址,是指在虚拟内存体系中利用的地址。虚拟内存是一种机制,它允许将大于物理内存的地址空间映射到磁盘上的交换空间,从而提供更大的可用地址空间。虚拟地址是由进程利用的地址,而不思量物理内存的现实分配情况。通过虚拟内存管理,操纵体系将虚拟地址映射到物理地址。在hello.c步调中,变量和函数利用的地址被表示为虚拟地址。
• 物理地址:物理地址是指在计算机的现实物理内存中的地址。它是RAM上现实存储数据和指令的位置。物理地址是由操纵体系根据虚拟地址映射规则计算出来的真实地址。物理地址是真正用于访问内存中的数据和指令的地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位字段,其中前13位是索引号,后面3位包罗硬件细节,表示差异的段寄存器。索引号用于在段描述符表中找到对应的段描述符,每个段描述符由8个字节组成。段描述符中最重要的字段是Base字段,它指示了段的开始位置的线性地址。
段描述符可以放在全局段描述符表(GDT)或局部段描述符表(LDT)中。全局的段描述符位于GDT中,而局部的段描述符位于各自进程的LDT中。通过段选择符中的T1字段,可以确定是利用GDT还是LDT,当T1=0时利用GDT,当T1=1时利用LDT。GDT的地址和大小存储在CPU的gdtr控制寄存器中,而LDT的信息存储在ldtr寄存器中。
图66 段选择符[2]
图67 段描述符[2]
图68 段式管理逻辑图
7.3 Hello的线性地址到物理地址的变换-页式管理
内存以固定大小的页来划分和管理,被称为页式管理。每个页框可以容纳一个页面,页面是步调在内存中的最小单元,其大小通常是2的整数幂(如4KB、8KB等)。步调的逻辑地址空间也被划分成固定大小的页,称为页表。页表记录了逻辑页面和物理页面之间的映射关系。
图69 分页管理逻辑图[3]
CPU生成的步调计数器(PC)的虚拟或线性地址被MMU转换成物理地址。这个转换过程依赖于页表,一个存储在物理内存中的数据布局,用于将虚拟页映射到物理页。
图70 线性地址划分[3]
线性地址由页号和页内偏移两部门组成。每个页表条目(PTE)在页表中对应一个特定的虚拟页,并包罗一个有效位和一个地址字段。地址字段表示对应的物理页在DRAM中的起始位置。假如有效位被设置,那么地址字段就是有效的。
当步调需要引用一个逻辑地址时,操纵体系通过查找页表找到对应的物理页框,并将逻辑地址转换为物理地址。这种方式实现了物理内存的灵活利用,同时简化了地址转换过程,但是它需要额外的内存开销,由于每个页都需要记录映射关系。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB:高速缓存,存储了近来的虚拟地址到物理地址的映射,可以提高地址翻译的速率,淘汰对内存的访问次数。
四级页表:通过将虚拟地址划分为四个部门(页全局目录偏移量、页上级目录偏移量、页下级目录偏移量和页内偏移量),逐级在页全局目录、页上级目录、页下级目录和页表中查找对应的条目,从而得到物理地址。这种多级页表体系的设计,可以有效地淘汰页表的大小和访问时间,提高地址翻译的效率。
图71 k级页表管理下虚拟地址划分
若仅利用四级页表,则每次地址转换都需要访问四次内存,这会导致大量的耽误。为了解决这个问题,就需要利用TLB。当需要进行地址转换时,起首会在TLB中查找,假如TLB命中,则直接利用缓存的映射,避免了访问页表的耽误。若TLB缺失,则需要访问页表进行地址转换,并将新的映射存入TLB。
假如只利用TLB,则单级页表每个页表需要存储220B大小的空间,该空间大小难以担当。故需要在TLB与四级页表的共同支持实现虚拟地址到物理地址的转换。
7.5 三级Cache支持下的物理内存访问
图72 intel i7 10850H cpu各级缓存大小
物理地址被分为m位,其中t位是标志位(Tag),s位是组索引位(Set Index),b位是块偏移(Block Offset)。
在一个三级缓存体系中,物理内存访问涉及到三个级别的缓存:L1缓存(一级缓存)、L2缓存(二级缓存)和L3缓存(三级缓存)。在访存时,处置惩罚器起首会检查L1缓存,然后是L2缓存,最后是L3缓存和主内存。缓存的目标是尽可能地存储常用的数据,以淘汰对较慢的主内存的访问次数,从而提高数据访问速率和体系性能。下面是对各级缓存的介绍:
L1缓存:位于CPU核心内部,是最靠近处置惩罚器的缓存级别。L1缓存被组织成一个有S=2s个高速缓存组的数组,每个组包罗E个高速缓存行。每个高速缓存行由一个大小为B=2b字节的数据块组成。速率最快,但容量较小。存储着最频繁利用的数据和指令。物理内存访问起首会检查L1缓存,假如数据在其中找到,就可以快速返回效果。假如数据不在L1缓存中,将会向更高级别的缓存或者主内存进行进一步的访问。
L2缓存:位于CPU核心和主内存之间。容量比L1缓存大,速率比主内存快。通常比L1缓存慢一些,但仍然比主内存快得多。当L1缓存未命中时,会检查L2缓存以查找所需的数据。假如数据在L2缓存中找到,可以快速返回效果。假如数据不在L2缓存中,将会继续向更高级别的缓存或者主内存进行进一步的访问。
L3缓存:位于CPU核心和主内存之间,但相对于L2缓存更靠近主内存。容量最大,但速率相对较慢。用于存储更大量的数据,以满足处置惩罚器的需求。当L2缓存未命中时,会检查L3缓存以查找所需的数据。假如数据在L3缓存中找到,可以快速返回效果。假如数据不在L3缓存中,将会进一步向主内存进行访问。
7.6 hello进程fork时的内存映射
当终端调用fork函数创建子进程hello时,子进程hello会得到一个与父进程用户级虚拟地址空间相同但独立的副本。详细如下:
子进程得到独立的虚拟地址空间:子进程会得到一个与父进程相同大小的虚拟地址空间,其中包括代码段、数据段、堆和栈等。
内存映射的复制:子进程的虚拟地址空间与父进程相同,这意味着子进程会继续父进程的内存映射关系。也就是说,子进程会与父进程共享相同的页面映射关系,包括代码页和数据页等。
代码和数据的继续:子进程会继续父进程的代码段和数据段,这样子进程可以继续实行父进程的逻辑或对继续的数据进行操纵。
独立的实行情况:只管子进程继续了父进程的代码和数据,但它在独立的实行情况中运行。子进程可以在与父进程相互隔离的情况中实行,而且对父进程的运行状态没有影响。
7.7 hello进程execve时的内存映射
hello进程在实行execve时,起首清空现有映射表和物理内存,然后初始化栈和堆。接下来,建立虚拟地址空间的页与可实行文件的页之间的映射关系。最后,设置步调计数器以指向代码地区的入口点,使进程可以开始实行,详细如下:
子进程的现有映射表和物理内存被清空,即原有的映射关系和物理内存中的内容都被删除。
栈和堆被初始化为0。
虚拟地址空间的页与硬盘上的命令行请求的可实行文件的页建立映射关系。这意味着将虚拟地址空间的页与硬盘上的可实行文件的内容相干联,但这些页尚未加载到物理内存中。
execve函数设置当前进程上下文的步调计数器,将其指向代码地区的入口点。这样,进程可以被调度并开始实行。
7.8 缺页故障与缺页中断处置惩罚
图73 缺页处置惩罚流程图[4]
缺页故障是在访问虚拟地址时发生的一种故障,表示所需的页面不在物理内存中而需要从磁盘加载。当指令引用一个相应的虚拟地址,而与该地址相应的物理页面不在内存中时,就会触发缺页故障,并且进入内核模式,控制传递给缺页处置惩罚步调。
缺页异常处置惩罚的流程如下:
• 处置惩罚器生成一个虚拟地址,并将其传送给MMU(内存管理单元)。
• MMU生成页表条目(PTE)的地址,并向高速缓存或主存发起请求以获取该条目。
• 高速缓存或主存将PTE返回给MMU。
• 假如PTE中的有效位为0,表示所需的页面不在物理内存中,此时MMU会触发一次异常,将控制传递给操纵体系内核中的缺页异常处置惩罚步调。
• 缺页处置惩罚步调起首确定物理内存中的牺牲页面(假如有的话),假如该页面已被修改,则将其换出到磁盘。
• 缺页处置惩罚步调将新的页面调入内存,并更新内存中的PTE。
• 缺页处置惩罚步调返回到原来的进程,重新实行导致缺页的指令。CPU会重新发送导致缺页的虚拟地址给MMU。由于虚拟页面如今已经存在于物理内存中,以是会命中并继续实行。
通过缺页异常处置惩罚流程,体系可以将需要的页面从磁盘加载到物理内存中,以满足进程对虚拟地址的访问需求。缺页中断是一种机制,允许操纵体系对缺页故障进行处置惩罚,并提供了动态内存管理的能力,使得虚拟内存可以超出物理内存的限制,并提供了更大的可用地址空间。
7.9动态存储分配管理
动态内存管理的根本方法和策略如下:
动态内存分配方法:
• 基于堆的动态内存分配:利用动态内存分配器来管理进程的堆,通过申请和释放堆内存块来动态管理内存。
• 基于动态内存分配器的算法和数据布局:利用差异的算法和数据布局来实现动态内存分配器,例如链表、位图、伙伴体系等。
图74 内存分配流程图[5]
策略:
1. 隐式空闲链表组织堆:
◦ 每个内存块包罗边界信息和分配状态。
◦ 空闲块通过头部的大小字段隐含地链接在一起,形成隐式空闲链表的布局。
◦ 常见的搜刮策略有首次适配、下一次适配和最佳适配。
◦ 可以进行合并和分割操纵来管理内存块。
图75 隐式空闲链表组织堆
2. 显式空闲链表组织堆:
◦ 空闲块被组织成显式的数据布局,不需要将边界信息和分配状态嵌入到块中。
◦ 常见的显式空闲链表布局是双向链表,每个空闲块包罗前驱和后继指针。
◦ 通过利用双向链表,可以提高搜刮时间和分配策略的效率。
◦ 合并和分割操纵更轻易实现。
图76 显式空闲链表组织堆
可以看出隐式空闲链表策略简单易实现,但可能会产生内存碎片和搜刮开销;显式空闲链表策略提供更高效的搜刮时间和更好的分配策略控制,但可能增加内存开销。
7.10本章小结
这一章介绍了有关存储器地址空间的内容,讨论了逻辑地址、线性地址、物理地址和虚拟地址的概念,并详细分析了地址转换的过程,同时研究了Cache的访问原理,以及fork和execve函数在内存映射方面的应用,最后表明了缺页故障和缺页中断处置惩罚的实行过程。
结论
通过对HELLO步调从编译到实行的过程进行深入研究,可以得出以下结论:
HELLO步调经历了预处置惩罚、编译、汇编、链接和实行等一系列步骤。每一章的总结如下:
预处置惩罚:通过预处置惩罚器CPP将源代码中的宏界说睁开,头文件包,条件编译并删除注释,生成hello.i文本文件
编译:由编译器cc1将hello.i文件经过分词后,检查器进行词法分析,语法分析,语义分析,最终转换为低级机器语言(汇编语言)文件hello.s
汇编:由汇编器as将hello.s汇编文件中的汇编代码转换为机器指令,生成可重定向文件,使之在链接后可以大概被直接实行。
链接:由链接器ld进行链接,生成可实行文件hello.out
加载:运行hello.out,终端为其fork新建进程,拷贝父进程的空间,随后通过execve函数hello.out 所需情况加载入内存,开始实行步调;
步调实行结束:shell父进程接纳子进程,内存删除为这个进程创建的全部数据布局。
在每个阶段中,各种工具和命令的利用起到了关键作用。在UBUNTU下,预处置惩罚可以通过特定命令进行,编译可以利用相应的编译命令,汇编和链接也有对应的命令来完成。这些工具和命令被集成进gcc命令,使得整个编译和实行过程变得自动化和高效。
HELLO步调的实行过程也涉及到进程管理和存储管理。进程管理包括进程的创建、实行和异常处置惩罚等过程。HELLO步调通过FORK体系调用创建新的进程,利用EXECVE体系调用加载可实行文件并实行。异常和信号处置惩罚机制可以大概对运行过程中的异常情况进行处置惩罚,保证步调的正常实行。
存储管理包括虚拟地址到物理地址的映射和内存的分配与接纳。HELLO步调的存储管理涉及到段式管理和页式管理两种地址变换机制,通过逻辑地址和线性地址的变换,实现虚拟地址到物理地址的映射。TLB和页表的支持加速了地址映射过程。同时,HELLO步调中的动态存储分配管理确保了内存的合理分配和释放,避免了内存泄漏和溢出等问题。
综上所述,通过对HELLO步调的编译和实行过程的研究,我深刻认识到计算机体系的设计与实现需要思量到多个层面的问题,包括源代码的预处置惩罚、编译器的优化、目标文件的链接和加载、进程的管理以及存储管理等方面。在设计与实现计算机体系时,需要注重各个环节的协调和优化,以提高体系的性能和可靠性。
同时我认为我们可以探索新的设计与实现方法,以提升计算机体系的效率和可扩展性。例如,可以借鉴领域特定语言(DSL)的头脑,设计更高条理的语言和工具,简化开发过程并提高代码的可读性和维护性。同时,联合人工智能和机器学习的技术,开发智能化的编译器和调度器,优化步调的实行效率和资源利用率。别的,我也关注新型计算架构和存储技术的发展,致力于探索在新硬件平台上的计算机体系设计和优化方法,以适应未来计算需求的快速增长和多样化。
附件
名称
作用
hello.i
预处置惩罚生成的文本文件
hello.o
编译生成的可重定向文件
hello.o.objdump.txt
objdump hello.o生成的文本文件
hello.out.elf.txt
readelf hello.out生成的文本文件
hello.out.objdump.txt
objdump hello.out生成的文本文件
hello.s
汇编hello.i生成的汇编代码文件
hello.o.elf.txt
readelf hello.o生成的文本文件
hello.c
源代码文件
hello.out
链接hello.o生成的可实行文件
参考文献
[1] OpenAI. (2021). GPT-3.5-turbo. OpenAI.https://openai.com/blog/chatgpt/
[2] 刁海威(2015). linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址):https://www.cnblogs.com/diaohaiwei/p/5094959.html
[3] Felix(2013). 内存管理条记(分页,分段,逻辑地址,物理地址与地址转换方式):https://www.cnblogs.com/felixfang/p/3420462.html
[4]Yann(2020).Linux内存管理:缺页异常(一):https://zhuanlan.zhihu.com/p/195580742?utm_source=wechat_timeline
[5]BillDing(2021).深入理解计算机体系之实现动态存储分配器:https://dingfen.github.io/csapp/2021/07/05/CSAPPLab05.html
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |