2023春盘算机系统大作业:程序人生-Hello’s P2P

打印 上一主题 下一主题

主题 1013|帖子 1013|积分 3039



盘算机系统

大作业


题     目  程序人生-Hello’s P2P 

专       业         将来技能         

学     号        20211               

班     级         21W               

学       生                     

指 导 教 师          郑贵滨        



盘算机科学与技能学院

2023年5月



摘  要

本文运用了《盘算机系统》课程中主线内容的知识,跟踪、分析了Linux系统中Hello这一程序的“程序人生”,即其从C语言源文件经预处理、编译、汇编、链接这一系列过程天生为可执行文件,并后续在操作系统中成为进程过程及被执行期间的进程管理、存储管理等内容,结合硬件信息对Hello程序的P2P(从程序到进程)在盘算机系统的层面上举行了先容。

关键词:盘算机系统;进程管理;程序运行原理;                           

(择要0分,缺失-1分,根据内容出色称都酌情加分0-1分

目  录


第1章 概述... - 5 -
1.1 Hello简介... - 5 -
1.2 环境与工具... - 5 -
1.3 中心结果... - 5 -
1.4 本章小结... - 6 -
第2章 预处理... - 7 -
2.1 预处理的概念与作用... - 7 -
2.2在Ubuntu下预处理的下令... - 7 -
2.3 Hello的预处理结果剖析... - 7 -
2.4 本章小结... - 8 -
第3章 编译... - 9 -
3.1 编译的概念与作用... - 9 -
3.2 在Ubuntu下编译的下令... - 9 -
3.3 Hello的编译结果剖析... - 9 -
3.3.1 数据... - 10 -
3.3.2 赋值... - 12 -
3.3.3 范例转换... - 12 -
3.3.4 算术操作... - 12 -
3.3.5 关系操作... - 12 -
3.3.6 数组/指针/结构操作... - 12 -
3.3.7 控制转移... - 13 -
3.3.8 函数操作... - 14 -
3.4 本章小结... - 15 -
第4章 汇编... - 16 -
4.1 汇编的概念与作用... - 16 -
4.2 在Ubuntu下汇编的下令... - 16 -
4.3 可重定位目的elf格式... - 16 -
4.3.1 ELF头... - 17 -
4.3.2 节头... - 18 -
4.3.3 .rela.text节... - 19 -
4.3.4 .rela.eh_frame节... - 20 -
4.3.5 .symtab节... - 20 -
4.4 Hello.o的结果剖析... - 21 -
4.4.1 呆板语言与汇编语言... - 21 -
4.4.2 hello.o反汇编剖析... - 21 -
4.5 本章小结... - 23 -
第5章 链接... - 24 -
5.1 链接的概念与作用... - 24 -
5.2 在Ubuntu下链接的下令... - 24 -
5.3 可执行目的文件hello的格式... - 25 -
5.3.1 ELF头... - 25 -
5.3.2 节头... - 26 -
5.3.3 程序头... - 26 -
5.3.4 Section to Segment mapping. - 27 -
5.3.5 Dynamic section. - 28 -
5.3.6 重定位节.rela.dyn. - 28 -
5.3.7 重定位节.rela.plt - 29 -
5.3.8 符号表.dynsym.. - 29 -
5.3.9 符号表. symtab. - 29 -
5.4 hello的虚拟地点空间... - 30 -
5.5 链接的重定位过程分析... - 32 -
5.5.1 分支转移... - 32 -
5.5.2 全局变量访问... - 33 -
5.6 hello的执行流程... - 33 -
5.7 Hello的动态链接分析... - 34 -
5.8 本章小结... - 34 -
第6章 hello进程管理... - 35 -
6.1 进程的概念与作用... - 35 -
6.2 简述壳Shell-bash的作用与处理流程... - 35 -
6.3 Hello的fork进程创建过程... - 35 -
6.4 Hello的execve过程... - 36 -
6.5 Hello的进程执行... - 37 -
6.6 hello的异常与信号处理... - 38 -
6.6.1 异常... - 38 -
6.6.2 信号... - 39 -
6.6.3 hello的异常... - 39 -
6.6.4 hello的信号处理... - 39 -
6.7本章小结... - 44 -
第7章 hello的存储管理... - 45 -
7.1 hello的存储器地点空间... - 45 -
7.1.1 逻辑地点... - 45 -
7.1.2 线性地点... - 45 -
7.1.3 虚拟地点... - 45 -
7.1.4 物理地点... - 45 -
7.2 Intel逻辑地点到线性地点的变动-段式管理... - 46 -
7.3 Hello的线性地点到物理地点的变动-页式管理... - 47 -
7.4 TLB与四级页表支持下的VA到PA的变动... - 48 -
7.4.1 TLB.. - 48 -
7.4.2 四级页表... - 50 -
7.5 三级Cache支持下的物理内存访问... - 51 -
7.6 hello进程fork时的内存映射... - 53 -
7.7 hello进程execve时的内存映射... - 53 -
7.8 缺页故障与缺页中断处理... - 54 -
7.9本章小结... - 55 -
结论... - 56 -
附件... - 57 -
参考文献... - 58 -

第1章 概述

(0.5分)


1.1 Hello简介

Hello是一个简单的C程序,它在系统中的生命周期可以概括为P2P、020。
P2P,即From Program to Process,从程序到进程,是指Hello从C源程序hello.c,经cpp预处理、ccl编译、as汇编、ld链接这一系列过程,天生为可执行目的程序,并在用户于shell中给出下令后,在系统中加载运行,成为进程的过程。
020,即From Zero to Zero,从0到0,是指hello这一程序在加载进入内存成为进程之前,内存中并无hello相干的内容;通过shell中调用fork()创建进程、execve()加载可执行程序文件后,hello进入内存执行;而在hello运行结束、进程被接纳、内存被释放后,又恢复到之前的“0”状态,这便是从0到0。
1.2 环境与工具

硬件环境:AMD Ryzen 7 4800H 2.90 GHz CPU;16GB RAM;512GB SSD
软件环境:Windows 11 64位;Ubuntu 22.04.2 LTS
调试工具:gcc;gdb;edb
开发工具:Visual Studio Code 1.77.1;Code::Blocks 20.03
1.3 中心结果

文件名

阐明

hello.c

hello的c语言源程序

hello.i

hello.c预处理天生的文本文件

hello.s

hello.i经编译天生的汇编语言代码文本文件

hello.o

hello.s经汇编天生的可重定位目的文件

hello

hello.o经链接天生的可执行目的文件

hello.elf

可重定位目的文件hello.o使用readelf输出的ELF格式文本文件

hello.asm

可重定位目的文件hello.o使用objdump输出的反汇编文本文件

hello1.elf

可执行目的文件hello使用readelf输出的ELF格式文本文件

hello1.asm

可执行目的文件hello使用objdump输出的反汇编文本文件

1.4 本章小结

本章概述了hello程序生命周期中P2P、020两个概念,给出了完成本次研究使用的软硬件环境及开发、调试工具,给出了天生的中心结果文件列表。

第2章 预处理

(0.5分)


2.1 预处理的概念与作用

预处理的概念:预处理是C语言的一个重要功能,它由预处理程序cpp(C Pre-Processor,C预处理器)负责完成。预处理指的是在编译过程之前,对源代码举行一系列文本替换、条件编译等操作的过程。预处理器会扫描源文件中以#开头的指令,并根据不同的指令执行相应的操作。
预处理的作用:预处理会对源代码举行一系列文本替换、条件编译等操作。如:预处理器会处理#include指令,将其他源文件或库文件中的代码包含到当前文件中,实现代码复用和模块化开发;处理#define宏定义指令,将指令中定义的宏替换为相应的标记或表达式。.c源文件颠末预处理器预处理后天生.i格式的预编译文本文件。
2.2在Ubuntu下预处理的下令

参考作业要求,对hello.c举行预处理下令为gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i,如图2-1所示。其中,参数-m64用于指定天生的目的代码为64位架构,参数-no-pie和-fno-PIC用于禁用程序的位置无关性PIE(Position Independent Executable)和PIC(Position Independent Code),参数-E用于让预处理器输出预处理结果而不举行编译。


图2-1  hello预处理操作

2.3 Hello的预处理结果剖析

起首发现,hello.c文件仅有527字节,而预处理天生的hello.i文件67.4kB,打开文件后发现hello.i已扩展为三千多行,原来的helloc程序main函数在文件的末端,如图2-2。


图2-2  预处理结果

在对应原C程序main函数的内容之前,是预处理器处理#include下令的结果。因为原程序使用#include下令包含了stdio.h、unistd.h、stdlib.h三个头文件,预处理器会删除这些指令,并去系统默认的环境变量读取指定的头文件,将其内容插入到当前源代码文件中,然后继续处理其中#define等下令。这样以来,程序员可以在本身的代码中使用系统库中定义的函数、变量和宏等元素。
2.4 本章小结

本章先容了预处理的概念与作用,展示了对hello.c举行预处理的过程,并对预处理天生的hello.i文件举行了分析。

第3章 编译

2分)


3.1 编译的概念与作用

编译的概念:C编译器ccl(C Compiler)会对预处理天生的文件举行分析和翻译,将其从高级语言如C语言翻译成汇编语言。这里的汇编语言是用文字描述的呆板语言,依然是文本文件,其会在稍后的汇编阶段天生呆板可以读取的二进制文件。
编译的作用:将颠末预处理后的.i格式的程序翻译为.s格式的汇编语言文件,便于其被呆板识别并执行。编译器在编译过程中会对代码举行严酷的语法和语义查抄,可以或许发现代码中的错误并提示开发者修改,同时会对代码举行一定的优化等操作。
3.2 在Ubuntu下编译的下令

对hello.i举行编译的下令是gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s,如图3-1。其中,-S选项告诉编译器只举行编译操作而不举行汇编和链接操作。


图3-1  hello编译操作

3.3 Hello的编译结果剖析

3.3.1 数据
hello.c的囊括数据范例有:常量(字符串常量、整型常量),变量(局部变量),表达式(算术表达式、逻辑表达式、赋值表达式)。
1.字符串常量
当编译器处理C程序时,它会将所有字符串常量存储在程序的数据区。图3-2中展示了hello中两个字符串在汇编语言源代码文件中的保存方式。


图3-2  字符串常量在hello.s中

因为举行链接前,字符串数据在程序中的位置无法确定,使用.LC0及.LC1作为两个字符串的标签,用它们引用文件中这些标签后面出现的字节,见图3-3。


图3-3  编译结果对字符串常量的调用

2.整型常量
整型常量在编译结果中以立即数的情势存在,立即数的特性是会在操作数前面加美元符号$,如图3-4。图中语句①对应C语言源程序中的exit(1),其中1为整形常量;②对应i=0;③对应i++(含整型常量1);④对应i<8的条件判定逻辑表达式,这点会在稍后的关系操作及控制转移部门中做深入分析;⑤对应return 0。


图3-4  整型常量及局部变量的编译结果

3.局部变量
编译器在举行编译时,会将局部变量存放在寄存器或栈中。hello.c中定义了局部变量i,其在汇编语言源代码文件中的存放及调用方式见图3-4中的②③④三条语句,其中%rbp保存的是栈中当前执行函数的根本地点,变量i为int范例,宽度为四个字节,因此使用-4(%rbp)作为i的地点。
对于main函数参数argc与*argv[],与其他局部变量一样,参数的值只在函数执行期间可见,而且当函数返回时,该参数所占用的内存空间也会主动释放。编译结果中,程序将传入main函数的参数从寄存器%rdi、%rsi中取出并存入栈中,此后与其他局部变量一样。对于函数参数的详细分析,会在之后的函数操作部门举行。
4.表达式
hello.c中涉及到的表达式范例有算术表达式、逻辑表达式以及赋值表达式。对于算术表达式,如i++,是通过算术运算指令,如图3-4中语句③的addl指令实现的。对于逻辑表达式,汇编中有一系列的条件判定指令,如cmp、test,图3-4中语句④对应i<8的条件判定逻辑表达式,这点会在稍后的关系操作中做深入分析。对于赋值表达式,重要是通过数据传送指令实现的,如图3-4中语句②对应i=0。
3.3.2 赋值
对于初始化全局变量的赋值,编译器会创建一个存储在数据段中的内存空间来保存该变量的值。对于函数内部声明并初始化的静态变量,编译器将会在编译时为该变量分配内存空间,并将其初始值存储在这个空间里。每次函数被调用时,该变量的值都将被保留。
hello中仅涉及到对局部变量的赋值,详细分析已在3.3.1中阐明。其中后缀l体现操作数宽度为4字节。类似地,后缀b、w、q分表体现1、2、8字节。
3.3.3 范例转换
hello.c中,调用了函数atoi()将字符串转换为整数。
3.3.4 算术操作
编译器采用算数运算指令来完成算术操作。例如图3-4中语句addl $1 -4(%rbp)盘算内存操作数-4(%rbp)加立即数1,并将结果存放在-4(%rbp),对应C源程序中的i++算数操作。
3.3.5 关系操作
在C语言中,类似argc!4、i<8的关系操作会在关系成立时表达式值为1,否则为0。在汇编中,这样的关系操作会用cmp及test类指令实现。CPU的EFLAGS寄存器保存着条件码,cmp和test类指令会对根据运算结果(cmp是Dest-Src,test是按位与操作)设置这些条件码,如图3-4中语句④的cmpl指令。对于结果的判定及分支控制跳转将会在稍后的控制转移节举行分析。
3.3.6 数组/指针/结构操作
对于数组、结构体这样的聚合数据范例,其实质是内存中连续分列的数据,对它们的访问通常采用基址+偏移量这样的情势。而对于指针的操作,在汇编中采用内存操作数,如图3-5。
-32(%rbp)存放着*argv[]的起始地点。34-36将argv[2]所指向的字符串放入寄存器%rdx作为43行调用call函数的第三个参数,详细过程为:起首程序将数组起始地点放入累加器寄存器%rax并将其值加16,因为数组元素为char*指针,宽度为8字节(相应地,汇编指令以q作为后缀),argv[2]即偏移量为16字节,这样便得到了argv[2]地点,之后,程序使用(%rax)内存操作数体现对指针的寻址,结果存入寄存器%rdx。同理,37-40行完成argv[1]的访问。


图3-5  数组元素访问

3.3.7 控制转移
在汇编中,通过条件跳转指令实现控制转移操作。如图3-6,其中(a)对应if(argc!=4)判定,汇编中使用cmpl指令运算argc-4,而je代表若相称则跳转,否则继续执行下一条指令。CPU会根据cmp指令的运算结果给条件码赋值,若结果为零,会赋值ZF=1,此时je根据ZF条件码判定是否举行跳转,若ZF=1则代表cmp指令的两个操作数相称,执行跳转操作。CPU中以寄存器%rip作为程序计数器,永久指向下一条指令的地点,而跳转类指令正式通过修改%rip的值实现跳转。(b)与(a)类似,对应的是for循环中的条件判定i<8,编译器在编译的时候将8编译为立即数$7,与跳转条件jle即小于或等于相对应,若i小于等于7则跳转至.L4处指令,继续执行循环。(c)是一条无条件跳转的指令,其前面的movl指令对应i=0的循环赋初值,之后跳转到.L3即图(b)处进入for循环过程。
  
 
 
 (a)                       (b)                    (c)

图3-6  控制转移操作

3.3.8 函数操作
1.函数调用
汇编中,用call指令与函数地点完成对函数的调用,如图3-7,这里因为没有举行链接重定位,被调用函数地点并没有确定,暂用函数名作助记符。在调用函数的时候,call指令会将返回地点即call指令的下一条语句压入栈中,以便函数调用结束后的正确返回。


图3-7  call指令函数调用

函数被调用后通常需要创建栈帧,栈帧是指在函数执行期间分配给该函数的内存空间。每当函数被调用时,编译器都会为该函数创建一个新的函数栈帧,并将其推入调用堆栈中。如图3-8是main函数的栈帧创建过程,其中保存了旧的基指针,并用subq $32, %rsp指令开发了栈空间。



图3-8  main函数栈帧创建

2.参数传递
    
 

(a)                              (b)

图3-9  函数操作中的参数传递

x86-64下,函数的前6个参数依次用寄存器%rdi、%rsi、%rdx、%rcx、%r8、%r9传递,别的参数从后往前用栈传递。此处以main函数引用参数argc与*argv[]为例,如图3-9(a),进入main函数前,两个参数分别存放在寄存器%rdi、%rsi。图(b)是main函数中调用pirintf()函数的传参过程,分别将前三个函数放入了对应的寄存器中,之后用call指令调用函数。
对exit()、sleep()函数的调用同理,详细汇编语句可见图3-3、3-4。
3.函数返回
汇编中用ret指令举行函数返回,返回值存放在%rax寄存器中,如图3-10,是main函数的返回过程,对应C语句return 0。



图3-10  main函数返回过程

3.4 本章小结

本章先容了编译的概念与作用,展示了Linux中举行编译的指令,并对hello程序中出现的C语言各种数据范例、操作举行了编译结果分析。在编译阶段,编译器将程序从高级编程语言翻译成呆板指令文本文件,便于程序在接下来的汇编过程中转换为呆板能识别并执行的二进制文件。

第4章 汇编

2分)


4.1 汇编的概念与作用

汇编的概念:编译器天生汇编代码后,由汇编器(Assembler)将汇编代码转换为二进制的呆板码的过程叫做汇编。
汇编的作用:汇编器将以.s末端的汇编程序翻译成呆板语言指令,并把这些指令打包成可重定位目的程序格式,终极结果保存在.o目的文件中,所天生的为二进制文件。
4.2 在Ubuntu下汇编的下令

对hello.s举行汇编的下令为gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o,如图4-1,其中-c选项体现只编译不链接,将汇编代码转换为二进制指令并天生目的文件。


图4-1  hello的汇编操作

4.3 可重定位目的elf格式

ELF文件(Executable and Linkable Format)指的是可执行、可链接文件,是Linux和许多其他Unix-like操作系统中最常用的目的文件格式。ELF文件的格式分有链接视图和执行视图,如图4-2。


图4-2  ELF文件格式

hello.o是汇编器天生的可重定位目的文件,可用指令readelf -a hello.o > hello.elf将目的文件hello.o的所有节信息,包括头部、符号表、重定位信息、代码段、数据段等等都转储到hello.elf中,如图4-3,其中-a体现输出所有可用的信息。改变这一参数可以输出选定节的内容,如使用-h输出ELF头,-s输出符号表,-r输出重定位信息等。


图4-3  天生hello.o的ELF格式

4.3.1 ELF头
ELF头的内容如图4-4所示,其中重要内容包括:
数据:数据的体现及存储方式。
文件范例:指定ELF文件范例的枚举值,如可执行文件、共享库等。
系统架构:指定目的呆板体系结构,如x86、ARM等。
版本:ELF文件格式的版本号。
入口地点:程序执行的入口地点。
程序头出发点:程序头表在该ELF文件中的偏移量。程序头表包含了在可执行文件中各个段的信息,包括每个段的范例、位置、大小等等。本文件中没有程序头。
节头出发点:指向节头表的偏移量。节头表包含了关于ELF文件中各个节的信息,如名称、位置、大小等等。
标记:与文件相干的标记位。
别的信息包括程序头表条目大小、程序头表中条目数量、节头表中条目数量、节头表中字符串表的索引。


图4-4  hello.o的ELF头内容

4.3.2 节头
节头内容如图4-5,其中包含了关于可重定位目的文件中各个节(section)的信息。每个节头表条目对应着一个节,条目内容重要包括:名称、范例、地点(该节的虚拟地点,可见在该文件中全为0)、偏移量(该节在可重定位目的文件中的偏移量)、大小(该节所占用的字节数)、链接(该节的附加信息。取决于该节的范例)、对齐(该节的对齐方式)。
其中,.bss节用于存储未初始化的全局变量和静态变量。.text节(代码段)用于存放程序指令,通常包含编译器天生的汇编指令和呆板指令。.data节(数据段)用于存储已初始化全局和静态变量。.rodata节(只读数据段)用于存储已初始化的常量数据,通常包含字符串、全局常量等不可修改的数据。.strtab节(字符串表)用于存储字符串,通常包含了可重定位目的文件中使用到的所有字符串。别的较为重要的节将在接下来的部门详细先容。


图4-5  hello.o的节头内容

4.3.3 .rela.text节


图4-6  hello.o的.rela.text节内容

.rela.text节的内容见图4-6。
.rela.text节是一个包含重定位表(relocation table)的节。该节用于存储与代码段(.text区段)相干联的重定位信息。重定位表列出了需要举行重定位的符号及其相应的位置、范例和修正值等信息。当操作系统将可重定位目的文件映射到内存中并运行时,它会根据重定位表重新定位代码段中的指令地点,以使得程序可以或许正确执行。
4.3.4 .rela.eh_frame节
.rela.eh_frame用于存储异常处理框架(Exception Handling Frame)信息,通常由编译器天生,以便在程序发生异常时可以或许举行堆栈回溯和调试。对于hello程序,其内容如图4-7所示。


图4-7  hello.o的.rela.eh_frame节内容

4.3.5 .symtab节
symtab节也称为符号表,由汇编器天生,用于存储程序中所有定义和引用的符号信息。符号表包含了全局/静态变量、函数名称等的信息,以及对应的值和大小,如图4-8。


图4-8  hello.o的.symtab节内容

符号表中的每个符号条目重要包含以下字段:名称、值(该符号对应的地点或偏移量)、大小(该符号所占用的字节数)、范例(指示该符号的范例,hello中包含文件、节和函数)、绑定(指示该符号的绑定范例,如局部符号、全局符号等)、可见性(默承认见性或隐蔽可见性等)。
在之后的链接阶段,链接器会将每个符号引用与一个确定的符号定义关联起来,并举行重定位:将符号从它们在.o文件中的相对位置重新定位到可执行文件中的终极绝对内存位置,并用他们的新位置,更新所有对这些符号的引用。
4.4 Hello.o的结果剖析

4.4.1 呆板语言与汇编语言
呆板语言是盘算机硬件可以或许直接执行的二进制指令代码,每个指令包含一个操作码,用于指示该指令要执行的操作范例,以及一个或多个操作数,用于体现操作所涉及的数据。
汇编语言是将呆板语言指令转化为易于阅读和编写的文本格式,与呆板语言有着逐一对应的关系。在汇编语言中,每个呆板指令在文本格式中被称为汇编指令,使用助记符来体现操作码,如ADD、MOV等。操作数使用不同的寻址方式来体现,例如寄存器直接寻址、内存间接寻址、立即数寻址等。
4.4.2 hello.o反汇编剖析
使用指令objdump -d -r hello.o > hello.asm对hello.o举行反汇编,并将结果保存在hello.asm中,如图4-9。-d参数体现反汇编所有节中的代码段,-r体现显示与重定位相干的信息。


图4-9  hello.o反汇编

1.伪指令
在汇编语言文件中,伪指令是一种特别的指令,它们不是真正的盘算机指令,而是在汇编源代码中给汇编器使用的下令。伪指令被用来定义程序的数据和结构,以及控制程序的汇编过程。而在hello.o的反汇编文件中,可以看到并没有hello.s中的伪指令部门,而是阐明文件格式后便开始代码段的反汇编,如图4-10。


图4-10  hello.o反汇编结果预阐明部门4-11

2.操作数
在对main函数的反汇编结果(如图4-11)中,可以看到操作数中的常数部门都由编译文件中的十进制数变为了十六进制数,如图中①处表明的是立即数被转换为十六进制,②处表明内存操作数中的数字偏移量被转换为了十六进制数。


图4-11  main函数反汇编剖析

3.分支转移
在hello.s中,跳转指令的目的地点使用助记符体现,如图3-6中的.L2、.L3等;而在hello.o的反汇编结果中,跳转指令的目的指令地点是用main函数基址加上偏移量体现的,如图4-11的③④⑤。
4.全局变量引用
hello程序中涉及到了字符串常量。在hello.s中,使用.LC0及.LC1作为两个字符串的标签,用它们引用文件中这些标签后面出现的字节,如图3-3。而在反汇编结果中,如图4-11的⑥和⑦处,对这些字符串的调用地点显示为$0x0,这是因为举行链接重定位前并无法确定它们的真实地点,而是用.rodata只读数据节的基地点加上字符串在节内的偏移量来举行寻址。标记R_X86_64_32是一种重定位范例,体现需要将32位的绝对地点嵌入到指令中。在链接天生可执行文件时,这些重定位项将会被剖析,根据实际的地点举行修正。
5.函数调用
在hello.s编译结果中,使用call+函数名举行函数的调用,如图3-7;而在hello,o可重定位目的文件中对函数的调用加入了重定位信息,如图4-11中⑧~⑬。对函数调用的重定位使用相对寻址,链接器会将call指令后的操作数改为目的函数起始地点相对于当前%rip值(call指令的下一条指令的地点)的修正量,这样CPU在执行call指令的时候,会将%rip值加上该修正量,就能转入目的函数运行。
对于全局库函数的引用,程序使用了R_X86_64_PLT32这种特别的重定位范例,用于将可执行代码中指向全局库函数地点的指针举行重定位。在可重定位目的文件被链接为可执行文件时,编译器不知道全局库函数简直切地点,因此天生的代码中使用了一个间接跳转指令(例如call),这个间接指令实际上是一个指向PLT(Procedure Linkage Table)表项的指针。PLT中存储了指向全局库函数的地点。
4.5 本章小结

在本章内,先容了汇编的概念与作用,展示了由汇编代码文本文件汇编为呆板指令二进制文件的指令与过程,并对所得到的可重定位目的文件hello.o举行了两方面的分析:起首,使用了readelf查看其ELF文件信息,对各节内容举行了分析和先容;其次,使用了objdump查看其反汇编结果,详细分析了其与第三章中得到的编译结果在各个方面的差异。在汇编阶段,hello从汇编语言天生为可重定位目的文件,是呆板可识别的二进制情势,颠末接下来的链接过程,即可天生可执行目的文件,可以或许进入内存运行了。

第5章 链接

1分)


5.1 链接的概念与作用

链接的概念:链接是指通过链接器(Linker)将多个目的文件(或者库文件)组合在一起成为一个可执行文件的过程。链接过程中,链接器会把编译器产生的各个目的文件及库文件合并成一个单独的可执行的二进制程序。
链接的作用:重要包括两方面:符号剖析,即在链接的时候,链接器会将每个目的文件中引用的函数和变量与其他目的文件中定义的函数和变量举行匹配,从而剖析出所有符号的终极地点;重定位,即在链接的时候,链接器会根据符号剖析的结果对目的文件中的指令和数据举行重定位,使得它们可以或许正确地访问其他目的文件中定义的变量和函数。链接这一过程使得程序员在编程时可以模块化举行,而且当一个模块有更新时,只需重新编译此模块。此外,还可以将常用的函数等代码打包成库(分为静态库和动态库),这样可以通过链接来使用这些内容。
5.2 在Ubuntu下链接的下令

使用如下下令举行链接:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/x86_64-linux-gnu/crtn.o /usr/lib/x86_64-linux-gnu/libc.so hello.o,操作过程如图5-1。


图5-1  hello链接操作

5.3 可执行目的文件hello的格式

使用readelf -a hello > hello1.elf下令将hello信息输出到hello1.elf(区别于可重定位目的文件hello.o所天生的hello.asm)文件之中,如图5-2。


图5-2  天生hello的ELF格式

5.3.1 ELF头


图5-3  hello的ELF头内容

可执行目的文件hello的ELF头内容如图5-3,与可重定位目的文件(图4-4)相比,范例从REL(可重定位文件)变为了EXEC(可执行文件),入口点地点举行了更新,程序头的出发点、大小和条目数量都有了数值(可重定位目的文件中没有程序头),节头的条目数量和节头表中字符串表的索引都有所增加。
5.3.2 节头
可执行目的文件hello的节头的部门内容如图5-4。


图5-4  hello的节头内容(部门)

与可重定位目的文件中的节头(见图4-5)相比,每个节都明确了地点而不再是0,而且多了一些节,包括动态符号表等与链接等有关的节。
5.3.3 程序头
程序头是用来描述文件中各个段的内容和属性的数据结构。hello的程序头内容如图5-5所示,其中各项内容为:
范例(Type):描述该段的范例,如代码段、数据段等。
偏移量(Offset):该段在文件中的偏移量。
虚拟地点(Virtual Address):该段在内存中的虚拟地点。
物理地点(Physical Address):该段在物理内存中的地点。
文件长度(File Size):该段在文件中的长度。
内存长度(Memory Size):该段在内存中的长度。
标记(Flags):描述该段的属性,如可读、可写、可执行等。
对齐(Alignment):该段在文件和内存中的对齐方式。


图5-5  hello的程序头内容

5.3.4 Section to Segment mapping
Section to Segment mapping的内容如图5-6。可执行目的文件中的每个节都有名称、大小、偏移量等属性,而段则对应着实际的内存区域,可能包含多个节。Section to Segment mapping便描述了哪些节被映射到了哪些段中,以便操作系统举行加载和执行。


图5-6  hello的Section to Segment mapping内容

5.3.5 Dynamic section
动态节(Dynamic section)用于描述程序在运行时需要的动态链接信息。当程序启动时,操作系统会将该节中的内容加载到内存中,并根据其中的信息举行动态链接和重定位,以便正确地执行程序。hello的Dynamic section内容如图5-7。


图5-7  hello的动态节内容

5.3.6 重定位节.rela.dyn
重定位节.rela.dyn的内容如图5-8所示,该节与动态链接有关,用于描述需要举行动态重定位的地点和偏移量信息。


图5-8  hello的重定位节.rela.dyn内容

5.3.7 重定位节.rela.plt
重定位节.rela.plt的内容如图5-9所示,用于描述程序在调用PLT时的重定位。


图5-9  hello的重定位节.rela.plt内容

5.3.8 符号表.dynsym
符号表.dynsym的内容如图5-10,用于描述程序需要访问的动态符号表信息。在使用动态链接时,程序需要访问共享库中的函数、变量等符号,需要通过动态符号表来举行访问。


图5-10  hello的符号表.dynsym内容

.dynsym节中包含了程序需要访问的所有动态符号的信息,如符号名称、范例、大小、绑定属性等。每个符号都有一个唯一的符号索引,用于在重定位和其他操作中举行引用。.dynsym节中的符号也可以被其他节(如.rela.dyn)中的条目所引用。
5.3.9 符号表. symtab
可执行目的文件hello的符号表内容见图5-11。与可重定位目的文件hello.o的符号表(图4-8)相比,经链接后,hello中增多了一些符号,所引用的库函数名称增加了库信息,而且多了一些节的起始地点信息。


图5-11  hello的符号表.symtab内容

5.4 hello的虚拟地点空间

使用edb加载hello后,起首可以用Memory Regions查看大抵的内存区域分配情况以及各区域的权限等,如图5-12。


图5-12  Memory Regions查看内存区域

从图中的结果可以看到,hello程序在运行时装入了虚拟内存中起始地点为0x400000的空间中,而在高地点区域存放着栈空间,以及一些系统调用相干的内容。
对照5.3.3中程序头的内容(图5-5)可知,edb运行hello时在虚拟内存装载方式对应着程序头中描述的内容。如0x400000起始处,装入了范例为LOAD的段等等。该段在内存中的部门内容可由edb的data dump查看,如图5-13。


图5-13  0x400000起始处内容(部门)

而对照5.3.2中节头的内容(图5-4),可知各个节在内存中的装载地点信息,可与edb所显示的结果对应。以.rodata节为例,该节存放着程序的只读信息,如字符串常量等。由图5-4,.rodata节的地点为0x402000,而在edb的data dump显示了该节在内存中的装载情况,可以看到程序中的两个字符串常量,如图5-14。


图5-14  .rodata在内存中(部门)

5.5 链接的重定位过程分析

使用下令objdump -d -r hello > hello1.asm将反汇编代码输出到hello1.asm(区别于可重定位目的文件hello.o的输出结果hello.asm)中,如图5-15。


图5-15  hello反汇编

起首发现,hello1.asm节的数量相比hello.asm有所增加(后者只有.text节,如图4-11),如图4-12(a),而且新增了许多函数(hello.asm只有main函数内容),如图4-12(b)。除此之外,函数和各条语句的地点也不再从0处开始。
   
 

(a)                                (b)

图5-16  hello1.asm中的一些新增内容

接下来,对hello1.asm中的重定位结果举行分析。
5.5.1 分支转移
在main函数中,分支转移操作的目的语句地点更改为目的语句的真实地点,如图5-17中的0x401152,而不是图4-11中从main函数首地点为0开始的偏移地点。


图5-17  分支转移的重定位结果

5.5.2 全局变量访问
在举行链接重定位之前,如图4-11,全局变量的访问地点记为0x0,并给出重定位标记;重定位后,重定位标记被删除,而全局变量的访问地点也被替换为全局变量在内存中的真实地点,如图5-18。


图5-18  全局变量访问的重定位结果

5.5.3 函数调用
hello程序中重要调用的都是以PLT方式访问的函数,如图4-11。链接重定位后,call指令的操作数被替换为被调用函数的起始地点,如图5-19。


图5-19  函数调用的重定位结果

图5-19中的puts函数是以PLT方式调用的,函数puts@plt见图5-20。


图5-20  被调用的puts@plt

5.6 hello的执行流程

用edb执行hello,使用edb的Analyzer插件即可查看hello执行过程期间所调用的子程序及其地点,如图5-21。


图5-21  hello执行过程子程序调用

5.7 Hello的动态链接分析

hello的动态链接中涉及到了PLT和GOT。PLT是一个函数指针数组,其中包含了需要被动态链接库中的函数的地点。当程序调用动态链接库中的函数时,实际上是通过PLT来间接调用这些函数的。GOT是一个全局偏移表,保存了动态链接库中全局变量的地点信息。当程序执行到对于某个全局变量的引用时,实际上是通过GOT来获取该变量的地点的。
当hello程序第一次调用动态链接库中的函数或者访问动态链接库中的全局变量时,会触发动态链接库的加载息争析过程,而且会将PLT和GOT添补好正确的地点。
通过hello1.elf的节头可知,.got的地点为0x403ff0,便可在edb运行hello时使用data dump查看此地点的内容。在调用dl_init前,.got的内容如图5-22。


图5-22  dl_init前.got的内容

调用dl_init后,.got的内容如图5-23。


图5-23  dl_init后.got的内容

5.8 本章小结

本章先容了链接的概念与作用,展示了由可重定位目的文件hello.o链接天生可执行目的文件hello的过程,并对链接结果程序的ELF格式、虚拟地点空间、重定位过程、执行流程和动态链接举行了分析。颠末链接,hello成为了可执行的程序,可以或许加载进入内存成为运行的进程了。

第6章 hello进程管理

1分)

6.1 进程的概念与作用

进程的概念:进程是一个正在运行的程序的实例,系统中的每一个程序都运行在某个进程的上下文中。
进程的作用:进程给应用程序提供两个关键抽象:一个独立的逻辑控制流,提供一个假象,似乎程序独占地使用处理器;一个私有地点空间,提供一个假象,似乎程序独占地使用内存系统。
6.2 简述壳Shell-bash的作用与处理流程

壳(Shell)是指一个用户与操作系统内核之间的接口,它为用户提供了下令行界面(CLI)或者图形界面(GUI),使得用户可以向操作系统发送下令并获取运行结果。Shell-bash的作用是将用户输入的下令翻译成操作系统可以明白的语言,调用相应的系统函数来执行这些下令,并返回下令的执行结果。Shell-bash支持多条下令的组合、管道、重定向等高级功能,使得用户可以通过简单的文本下令完成复杂的任务。
Shell-bash处理下令的根本流程如下:
1.读取用户输入的下令行。
2.对下令行举行语法分析息争释,确定下令范例及其参数。
3.判定用户输入的是否为内置下令,若是则立即执行。
4.若不是,则找到下令对应的可执行文件。调用fork创建一个子进程,调用exec函数下令对应的可执行文件。
5.如果下令中包含了输入输出重定向或者管道等特别符号,则处理这些符号,创建相干的输入输出通道。
6.等待下令执行结束,并将执行结果返回给用户。
6.3 Hello的fork进程创建过程

当Shell收到./hello下令并判定其不是内置下令后,便调用系统函数fork()创建一个子进程。fork()是Unix系统中创建新进程的系统调用,它会创建一个与原进程完全一样的子进程,此时,子进程得到与父进程用户级虚拟地点空间雷同的一份副本,包括代码和数据段、堆、共享库以及用户栈,并共享打开文件描述符。子进程与父进程的PID有所区别。
fork()函数调用一次,返回两次,在父进程中返回所创建的子进程的PID,而在子进程中返回0。当Linux系统级函数遇到错误时,通常返回-1并设置全局整数变量errno来标示出错原因。
6.4 Hello的execve过程

Shell剖析用户输入的下令,得到可执行文件的路径和参数列表,使用fork系统调用创建子进程将父进程复制一份给子进程后,子进程中使用execve系统调用执行可执行文件。
execve原型为:int execve(char *filename, char *argv[], char *envp[])。该系统调用需要三个参数:目的可执行文件filename;参数列表argv,惯例argv[0]==filename;环境变量列表envp。新程序启动后的栈结构如图6-1所示。


图6-1  新程序启动后的栈结构

execve函数在当前进程中加载并运行新程序hello的步调为:删除已有页表和结构体vm_area_struct链表、创建新的页表和结构体vm_area_struct链表、代码和初始化的数据映射到.text和.data区(目的文件提供)、.bss和栈映射到匿名文件、设置PC,指向代码区域的入口点。操作系统会根据需要换入代码和数据页面。程序载入内存后的内存结构如图6-2。


图6-2  hello载入内存中的结构表示

execve若成功调用则从不返回,子进程会替换掉本身的代码段、数据段和堆栈,成为一个全新的进程。这个新进程会继承父进程的一些属性,比如文件描述符等。如果execve执行失败,子进程会返回-1。
6.5 Hello的进程执行

当用户在shell中输入./hello时,shell剖析这条下令,得到可执行文件的路径和参数列表后,使用fork系统调用创建一个新的进程,将父进程的所有内容都复制一份给子进程。此时,子进程的PCB(Process Control Block)中保存了一些进程上下文信息,如程序计数器(PC/%rip)、寄存器、堆栈指针等。
接着,子进程调用execve系统调用来执行hello可执行文件。在执行之前,内核会先将该文件从磁盘加载到内存中,并将可执行文件映射到该进程的虚拟地点空间中。这个过程需要一定的时间,因此进程调度器会选择另一个进程举行运行,直至hello程序完全被加载到内存中准备运行。
hello程序被加载到内存中后,CPU便开始执行。执行过程中,如果hello程序需要举行IO操作或者发生错误,例如除以0等,那么它就会陷入内核态,即从用户态变化成核心态。当发生异常时,CPU会将控制权交给操作系统内核处理,内核会根据异常范例和操作系统的设置来决定怎样处理异常。比如,如果进程试图访问未分配的内存,内核会向这个进程发送一个SIGSEGV信号体现其试图访问受保护的内存区域。
在进程执行过程中,进程调度器会根据一定的策略选择一个新的进程举行运行,即对手头的进程举行时间片轮转。时间片指的是CPU给予某个进程的运行时间,其时间片用完后,CPU就会将控制权交给进程调度器,由进程调度器选择下一个进程运行。在hello的执行过程中,如果其时间片用尽,被其他的进程抢占,就会陷入内核态。
特别地,hello中有对sleep的调用。当hello程序调用sleep函数时,该进程会被阻塞,即暂时停止运行。此时,该进程的时间片消耗停息,进程陷入内核态,操作系统会将进程的上下文信息保存到PCB中,随后将控制权交给另一个进程,直到睡眠时间过去或者有信号唤醒该进程,并使该进程重新回到用户态。
当hello程序执行完毕或者被其他进程抢占时,该进程会退出并释放它所占用的所有资源。此时,进程调度器会重新调度一个新的进程举行运行。
6.6 hello的异常与信号处理

6.6.1 异常
异常是指为响应某个事件将控制权转移到操作系统内核中的情况。内核,指操作系统常驻内存的部门。每种范例的事件有一个唯一的异常号k,异常号k是到异常表的索引,任何时候,若异常k发生,则异常k的处理程序立刻被调用。异常可以分为异步异常和同步异常。
异步异常,就是通常所说的中断(Interrupts),是被处理器外部I/O装备引起的,由处理器的中断引脚指示。在当前指令执行时,若中断引脚变为高电压,则在当前指令处理完成后控制传递给处理程序,中断处理程序运行完毕后返回到下一条指令处。
同步异常可分为陷阱、故障和终止。陷阱(Traps)是故意的,是执行指令的结果(发生时间可预知),例如系统调用syscall,每个x86-64系统调用有一个唯一的ID号,应用程序执行syscall,控制会传递给处理程序,处理程序运行完毕后返回到下一条指令。故障(Faults)不是故意的,如缺页故障、保护故障等,故障可能被修复,若处理程序可以或许修复则返回到引起故障的指令重新执行,若无法修复则会终止。终止(Aborts)非故意,由不可恢复的致命错误造成,如非法指令,奇偶校验错误等,终止处理程序会返回到abort例程,进而终止进程。
6.6.2 信号
信号(signal)就是一条小消息,它通知进程系统中发生了一个某种范例的事件。信号从内核发送到(有时是在另一个进程的请求下)一个进程。信号范例是用小整数ID来标识的(1-30),信号中唯一的信息是它的ID和它的到达。
内核通过更新目的进程上下文中的某个状态项(struct sigpending pending),来实现发送(递送)一个信号给目的进程。发送信号可以是如下原因之一:内核检测到一个系统事件,如除零错误(SIGFPE)或者子进程终止(SIGCHLD);一个进程使用系统调用函数kill,显式地/直接请求内核发送一个信号到目的进程(一个进程可以发送信号给它本身)。
当目的进程被内核以某种方式对发送来的信号做出反应时,它就吸收了信号。反应的方式有:忽略这个信号(do nothing);终止进程(with optional core dump);通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号,信号处理程序的执行流程表示图见图6-3。


图6-3  信号处理程序的执行流程表示图

一个发出而没有被处理的信号叫做待处理信号,也称未决信号。一个待处理信号最多只能被吸收一次。一个进程可以选择阻塞吸收某种信号,阻塞的信号仍可以被发送,但不会被吸收,直到进程取消对该信号的阻塞。
6.6.3 hello的异常
在hello的运行过程中,会出现的异常重要有:
1.缺页故障,详细的处理方式将会在7.8节深入讨论。
2.IO装备操作,会使进程陷入内核态,内核调用相干的异常处理程序举行响应。
6.6.4 hello的信号处理
在hello执行过程中举行键盘操作,有些操作会给hello地点的进程发送某些特定信号。
1.不停乱按键盘
在hello运行过程中随意输入一些字符和回车,可以发现如果没有输入Ctrl-Z,Ctrl-C这类特定信号,hello会保持前台运行状态,键盘输入的字符和程序输出的字符一同显示在Shell中,回车会使输出换行,如图6-4。


图6-4  hello执行过程中乱按键盘的显示

在程序执行期间所输入的字符会缓存到标准输入流stdin,在hello程序结束返回后,Shell开始读取stdin中的指令,并以\n(回车)分割指令,依次对每个指令按6.2节中的流程举行执行,如图6-5。


图6-5  hello返回后Shell开始执行此前输入的下令

2.Ctrl-C
hello在前台执行过程中,若检测到用户从键盘输入Ctrl-C,会给hello发送一个SIGINT信号,这个信号的默认行为是终止(即杀死)吸收到信号的进程,如图6-6。


图6-6  Ctrl-C结果

3.Ctrl-Z及后续指令
在hello开始执行后输入Ctrl-Z,会发送一个SIGSTP信号给前台进程hello,进程会被置于停止状态,并在操作系统的作业控制中保留其状态,如图6-7。


图6-7  Ctrl-Z结果

此时,可调用ps下令查看正在运行的进程信息,可以看到进程hello以及它的PID为3482。


图6-8  ps结果

可用jobs下令列出当前正在运行或已经停止的作业,如图6-9,可以看到hello当前处于停止状态。


图6-9  jobs结果

调用pstree指令,以树状结构显示进程之间的关系,图6-10中显示了pstree输出内容中的一部门。图6-11显示了pstree中的hello进程。


图6-10  pstree结果(部门)



图6-11  hello进程在pstree中

使用fg下令将hello移回前台并恢复其执行,如图6-12。


图6-12  fg结果

再次使用Ctrl-Z停止进程,之后,输入kill 3482(hello的PID)下令向hello进程发送信号。当用户没有指定所要发送的信号时,kill指令会默认发送SIGTERM终止信号,请求该进程安全地关闭。之后使用ps下令查看,发现hello进程并没有终止接纳,如图6-13。


图6-13  hello停止时使用kill发送SIGTERM信号的结果

这是因为hello处于停止状态,无法响应信号,此信号成为一个未决信号。再次使用fg使hello继续执行,此时hello响应信号而终止退出。再次使用ps查看,hello进程已被接纳,如图6-14。


图6-14  hello执行后响应SIGTERM信号的结果

除此之外,可以使用下令kill向目的进程发送指定信号,如图6-15,使用kill -9 3616下令向hello进程发送9号SIGKILL信号,强制终止进程。当发送SIGKILL信号时,操作系统将无条件地杀死目的进程,并释放其占用的资源。SIGKILL信号不能被阻塞、忽略或处理,而是强制终止进程。由图6-15可见,hello处于停止状态时向其发送SIGKILL信号,再用ps下令查看时,hello已被杀死,进程资源被接纳释放。


图6-15  kill向hello发送SIGKILL信号的结果

6.7本章小结

本章先容了进程的概念与作用,简述了Shell-bash的作用与处理流程,对hello程序成为进程时的fork、execve调用举行了分析,跟踪了hello的进程执行过程,并对其执行过程中的异常与信号处理举行了实操分析。这一阶段,hello完成了P2P,即从程序到进程的变化,是hello的“程序人生”中最富有活力的阶段。

第7章 hello的存储管理

2分)


7.1 hello的存储器地点空间

7.1.1 逻辑地点
逻辑地点是指一个进程在其虚拟地点空间中使用的地点,由两部门构成:段地点和偏移地点。段地点用于标识进程中的一个特定段(例如代码段或数据段),而偏移地点则体现该段中的一个特定位置。
从可重定位目的文件hello.o的反汇编结果hello.asm可见hello的逻辑地点空间,如图4-11所示main函数中各条指令以及对.rodata中内容的访问等。
7.1.2 线性地点
线性地点是指一个进程在其虚拟地点空间中颠末了分段和分页机制处理后所得到的地点。线性地点可以被操作系统直接映射为物理地点,以使进程可以访问实际的内存位置。对hello进程而言,线性地点即为虚拟内存映像中的虚拟地点。
7.1.3 虚拟地点
如果没有开启分页机制,那么线性地点就和物理地点是逐一对应的,可以明白为两者相称。如果开启了分页机制,那么线性地点将被视为虚拟地点,这个虚拟地点将会通过分页机制的转换,终极转换成物理地点。
7.1.4 物理地点
物理地点是指内存中实际的地点,是虚拟地点颠末页表映射终极映射到的地点,直接对应于盘算机主板上的内存芯片,用来指示数据在物理内存中的位置。操作系统通过使用虚拟内存技能将进程的虚拟地点映射到物理地点上,并保证进程访问的所有数据都存在于物理内存中。当程序需要访问某个虚拟地点时,处理器会将该虚拟地点传递给操作系统内核,内核则会将其转换为相应的物理地点。
对hello而言,物理地点就是hello在物理内存中的地点。
7.2 Intel逻辑地点到线性地点的变动-段式管理

在Intel处理器架构中,逻辑地点的转换是通过段式管理机制实现的。段式管理可以将逻辑地点划分为两部门:段选择符和偏移量。
在实地点模式下,处理器使用20位的地点总线,可以访问1MB(0~FFFFF)内存。对于8086的模式,只有16位的地点线,不能直接体现20位的地点,因而采用内存分段的办理方法,将内存空间划分为64KB的段(Segment),段地点存放于16位的段寄存器中(CS、DS、ES或SS)。CS、DS、ES和SS分别用于存放16位的代码、数据、堆栈、附加段的段地点/基地点。在保护模式下,寄存器、地点总线都是32位每个程序可寻址4GB内存(0~FFFFFFFF),使用段寄存器通过设置段描述符实现保护。段描述符占8字节,包括段的参数、(保护)属性(访问权限:可写、可写程序的优先级、可执行)等。段描述符会合存放于段描述符表(Segment Descriptor Table)中,段寄存器保存段描述符在段描述符表中的索引值,称为段选择器(Segment Selector)。
对于IA32架构,系统寄存器中设立全局描述符表寄存器GDTR和局部描述符表寄存器LDTR。48位的GDTR保存全局描述符表的地点,全局段描述符表包含了任务状态段和局部描述符表的指针。16位的LDTR保存当前正在运行的程序的代码段、数据段和堆栈段的指针,指向LDT段在GDT中的位置(索引)。
16位段选择器的编码方式为:高13位体现索引值;0、1位体现程序的当前优先级RPL;第2位是TI位,体现段描述符的位置,TI=0,段描述符在GDT中,TI=1,段描述符在LDT中。如图7-1。


图7-1  段选择器的编码

在保护模式下,段寻址方式如图7-2,其中,若段寄存器为全局描述符表项,则寻址流程为1-2-3;若段寄存器为局部描述符表项,则寻址流程为1’-2’- 3’-4’-5’。

图7-2  段寻址表示图

7.3 Hello的线性地点到物理地点的变动-页式管理

线性地点到物理地点的变动是通过页式管理实现的。操作系统将物理内存划分为大小相称的页面(通常为4KB或4MB),而且将每个页面映射到一组页表项中,将线性地点划分为页号和页内偏移量两部门。页号用来选择一个页表项,页内偏移量体现相对于所选页面基址的偏移量,用于盘算物理地点。页表是一个页表条目(Page Table Entry,PTE)的数组,将虚拟页地点映射到物理页地点,DRAM中的每个进程都有本身的页表。

图7-3  基于页表的地点翻译过程

基于页表的地点翻译过程如图7-3,通过段式管理得到线性地点后,操作系统会将线性地点划分为虚拟页号(Virtual Page Number,VPN)和虚拟页偏移量(Virtual Page Offset,VPO)两部门。寻址时,系统起首根据虚拟页号到页表中查找对应的页表条目,页表的基址存于寄存器PTBR中。若对应页表条目的有效位为1,则阐明该PTE有效,可直接获取物理页号(Physical Page Number,PPN),而由于虚拟页面和物理页面的大小划一,因此VPO即为物理页偏移量(Physical Page Offset,PPO),PPN和VPO组合起来便得到虚拟地点对应的物理地点。若对应PTE的有效位为0,则阐明页不命中,会触发缺页故障。
7.4 TLB与四级页表支持下的VA到PA的变动

7.4.1 TLB
在页式管理机制中,页表条目与其他内存数据字一样缓存在L1中,这样一来,PTE可能被其他数据引用所替换驱逐,导致不命中;即便Cache命中,也仍然需要与L1相当的延迟(1-2周期)。对于以上标题,采用了翻译后备缓冲器(Translation Lookaside Buffer,TLB)作为办理办法。
TLB俗称快表,是MMU(Memory Management Unit内存管理单元)中一个小的具有高相联度的聚集,页数很少的页表可以完全放在TLB中,通过TLB实现虚拟页号向物理页号的映射这一过程的加快。如图7-4,MMU使用虚拟地点的VPN部门来访问TLB。

图7-4  虚拟地点访问TLB表示图

TLB命中的地点翻译流程如图7-5。处理器给出虚拟地点VA,根据VPN查TLB得到PTE,PTE中的PPN和PPO即VPO举行组合,就可以得到PA。

图7-5  TLB命中的地点翻译流程

TLB命中时,减少内存访问,地点翻译都在芯片上的MMU中完成,极大提高了地点翻译的速率。
TLB不命中的地点翻译流程如图7-6。由VA得到VPN查TLB,若标记位不匹配或有效位为0,则发生TLB不命中,此时会用VA得到的PTEA去内存中查页表,返回PTE并用得到的PTE更新TLB,之后便可由PTE得出PPN进而得到PA。

图7-6  TLB不命中的地点翻译流程

TLB不命中会引发额外的内存访问,但由于程序的局部性原理,加之页面的容量较大,TLB不命中很少发生。
7.4.2 四级页表
多级页表机制的产生是为相识决由于页面数很多导致单级页表占用空间过大的标题。以二级页表为例:一级页表常驻内存,每个PTE指向1个2级页表的页面;二级页表有多页,每个PTE指向1个物理页。像平常数据一样,可以调入或调出页表,若一级页表项为空,对应的二级页表就不存在(不消保存),只有一级页表需要常驻在主存中,而二级页表根据需要,创建、页面调入或调出,最经常使用的二级页表才会在主存中,大大减少了页表对于内存空间的占用。使用K级页表的地点翻译过程如图7-7。

图7-7  使用K级页表的地点翻译

Intel Core i7 CPU采用4级页表,其地点翻译过程如图7-8。

图7-8  Core i7四级页表的地点翻译

7.5 三级Cache支持下的物理内存访问

高速缓存存储器(Cache)是小型的、快速的基于SRAM的存储器,是在硬件中主动管理的。Cache保存着经常访问主存的块,CPU访问内存时会起首查找缓存中的数据。通用的高速缓存存储器构造结构如图7-9所示。

图7-9  通用的高速缓存存储器构造结构

通过地点翻译得到物理地点后,会将物理地点划分为标记(t位)、组索引(s位)、块偏移(b位)三部门。读数据流程为:定位组、定位行(查抄聚集中的任何行是否有匹配的标记,是而且行有效即为命中)、定位数据(从块偏移开始的数据),如图7-10。

图7-10  物理内存访问Cache

Intel Core i7内存系统如图7-11,其中设立三级Cache,L1和L2 Cache是每个核独立的,且L1 Cache分为指令(i)和数据(d)缓存,而L3Cache是四核公用的,L1至L3级Cache的速率依次减慢,容量依次增大。

图7-11  Intel Core i7内存系统

综合页式管理和Cache机制,Core i7由虚拟地点的地点翻译访存过程如图7-12。

图7-12  Core i7地点翻译访存流程

三级Cache支持下,物理内存访问的流程为:CPU起首访问L1 Cache(位于CPU内部)。如果命中,则可以直接从中获取数据。如果L1 Cache不命中,CPU将会访问L2 Cache(CPU芯片附近),如果命中,则从中读出数据并将数据地点的块调入到L1 Cache中。如果L2 Cache再次不命中,CPU会访问L3 Cache(位于主板),如果L3 Cache命中,则读出数据,而且将地点块调入到L2和L1 Cache中。如果需要的数据不在任何一级缓存中,则CPU将会访问主存。
7.6 hello进程fork时的内存映射

内存映射,即将虚拟内存区域与磁盘上的对象关联起来,用于初始化这个虚拟内存区域的内容。虚拟内存和内存映射可以用来解fork函数怎样为每个新进程提供私有的虚拟地点空间。
当调用fork为hello创建新进程时,操作系统会为新进程创建虚拟内存,详细过程为:创建当前进程的mm_struct、vm_area_struct链表和页表的原样副本;两个进程中的每个页面都标记为只读;两个进程中的每个区域结构vm_area_struct都标记为私有的写时复制(Copy-On-Write,COW),在父进程或新进程修改这些区域前,它们都指向雷同的物理页。如果某个进程修改了此区域,那么操作系统会复制此区域到新的物理页上。
7.7 hello进程execve时的内存映射


图7-13  execve时的内存映射

当调用execve函数在当前进程中加载并运行新程序hello时,会发生如下内存映射:删除已有页表和结构体vm_area_struct链表、创建新的页表和结构体vm_area_struct链表、代码和初始化的数据映射到.text和.data区(目的文件提供)、.bss和栈映射到匿名文件(全为二进制0),如图7-13。
7.8 缺页故障与缺页中断处理

在页面命中的情况下,地点翻译、访存的流程为:(1)处理器天生一个虚拟地点并将其传送给MMU;(2)MMU天生PTE地点(PTEA)并从高速缓存/主存请求得到PTE;(3)高速缓存/主存向MMU返回PTE;(4)MMU将物理地点传送给高速缓存/主存;(5)高速缓存/主存返回所请求的数据字给处理器,如图7-14。

图7-14  页面命中的地点翻译流程

如果出现页面不命中,涉及到缺页中断处理的地点翻译流程为:(1)处理器天生一个虚拟地点 并将其传送给MMU;(2)MMU天生PTE地点,并从高速缓存/主存请求得到PTE;(3)高速缓存/主存向MMU返回PTE;(4)PTE的有效位为零,因此MMU触发缺页异常;(5)缺页处理程序确定物理内存中的牺牲页若页面被修改则换出到磁盘(写回策略);(6)缺页处理程序调入新的页面 并更新内存中的PTE;(7)缺页处理程序返回到原来进程再次执行导致缺页的指令。如图7-15。

图7-15  缺页异常时的地点翻译流程

7.9本章小结

本章中,先容了hello的各个地点空间,以及从逻辑地点到线性地点变动过程中的段式管理机制和从线性地点到物理地点变动过程中的页式管理机制,随后结合了Intel CPU架构,对TLB、四级页表支持下的地点翻译过程和三级Cache下的内存访问过程举行了分析。再之后,结合了虚拟内存概念更为细致地剖析了fork和execve的内存映射,末了对缺页故障与缺页中断处理举行了先容。到这里,已对hello在内存中的运行方式先容完毕,当hello退出后,操作系统会清理其内存,hello完成了它的“谢幕”。

结论

0分,必要项,如缺失扣1分,根据内容酌情加分)


Hello的一生经历了程序和进程两个阶段。
最初,程序员在C文件中完成对hello的C语言编程,此时,hello以高级语言源程序的情势诞生。
在预处理阶段,预处理器将hello所引用的头文件信息插入hello中,并处理宏定义等操作。
在编译阶段,编译器将hello从C语言翻译为汇编语言,方便后续汇编为呆板可以识别的二进制情势,此阶段中,编译器会对不同范例的数据、操作等举行不同方式的处理,这些巧妙的处理让我感叹于编译器的明智和盘算机系统的精妙。
在汇编阶段,汇编器将hello从汇编语言代码文本文件转换为可重定位目的文件,是ELF文件的一种,我们查看了它的ELF格式和反汇编结果,又一次展现了盘算机系统在天生可执行程序过程中的种种巧妙构思。
在链接阶段,链接器将hello.o与别的库举行链接,完成了符号剖析、重定位的操作,天生了可执行目的文件hello。我们分析了hello的ELF格式,对hello举行了反汇编,分析了重定位作用的结果,跟踪了hello的执行流程,并分析了其动态链接过程。
在Shell中用户输入下令./hello,Shell会fork一个新进程,并execve调用hello进入内存,执行hello程序。我们分析了执行hello时的fork、execve过程,并分析了hello的进程执行以及其过程中的异常、信号处理机制。
Hello的执行过程中涉及到存储管理,我们对hello的虚拟地点空间举行了分析,结合了段式管理、页式管理、TLB、多级页表、Cache、多种机制详细先容了地点翻译和访存的原理和流程,并对fork和execve的内存映射以及缺页故障处理做了分析。
本文通过hello这个程序回顾了盘算机系统的知识体系。盘算机科学由hello这个简单的程序开始,也被这个程序所代表和体现。hello的一生很短暂,但其中奥秘颇深,结合着盘算机系统各个层面的运作,蕴含着无数人的心血成果。

附件

(附件0分,缺失 -1分)


文件名

阐明

hello.c

hello的c语言源程序

hello.i

hello.c预处理天生的文本文件

hello.s

hello.i经编译天生的汇编语言代码文本文件

hello.o

hello.s经汇编天生的可重定位目的文件

hello

hello.o经链接天生的可执行目的文件

hello.elf

可重定位目的文件hello.o使用readelf输出的ELF格式文本文件

hello.asm

可重定位目的文件hello.o使用objdump输出的反汇编文本文件

hello1.elf

可执行目的文件hello使用readelf输出的ELF格式文本文件

hello1.asm

可执行目的文件hello使用objdump输出的反汇编文本文件


参考文献

为完成本次大作业你翻阅的册本与网站等

.
(参考文献0分,缺失 -1分)

[1]  KUNKLIU. ELF 格式详解[EB/OL]. (2023-03-19)https://blog.csdn.net/kunkliu/article/details/129648744.
[2]  DEVILISDEVI. GOT表 & PLT表 | Global Offset Table & Procedure Linkage Table[EB/OL]. (2020-08-24)https://www.jianshu.com/p/8904be06210c.
[3]  热爱编程的大忽悠. Linux 0.11-进程的阻塞与唤醒-44[EB/OL]. https://blog.csdn.net/m0_53157173/article/details/127824162.
[4]  码上读书. Linux源码学习笔记day11 逻辑地点、线性地点、物理地点、虚拟地点都是个什么鬼?[EB/OL]. https://zhuanlan.zhihu.com/p/609654024.
[5]  都是一家人. PIC和PIE[EB/OL]. https://www.cnblogs.com/Spider-spiders/p/8868535.html.
[6]  ORACLE. Procedure Linkage Table (Processor-Specific)[EB/OL]. https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-1235.html.
[7]  FOUNDATION L. Sections[EB/OL]. https://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-S390/LSB-Core-S390/sections.html.
[8]  嵌入式与LINUX那些事. 扒一扒ELF文件[EB/OL]. https://ac.nowcoder.com/discuss/678790.


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宝塔山

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表