大作业
题 目 程序人生-Hello’s P2P
专 业数学与应用数学+盘算机科学与技术双学位
学 号 2023110264
班 级 23SXS11
学 生 谢辰阳
指 导 教 师 史先俊
盘算机科学与技术学院
2024年5月
摘 要
本论文主要是解释一个具体的c语言程序(hello.c)是如何转化为可执行文件以及是如何被终端程序执行的。这个步骤大致分为预处理、编译、汇编、链接、进程管理等。与此同时本文还结合Linux体系(以ubuntu20.04演示)的一些根本知识如虚拟内存,地址空间,时间片,逻辑控制流和UNIX IO等,具体描述了这个过程,体现了hello程序短暂而又出色的一生。
关键词:盘算机体系;汇编语言;链接;Linux;c语言
目 录
第1章 概述................................................................................................................ - 4 -
1.1 Hello简介......................................................................................................... - 4 -
1.2 情况与工具........................................................................................................ - 4 -
1.3 中心结果............................................................................................................ - 4 -
1.4 本章小结............................................................................................................ - 5 -
第2章 预处理............................................................................................................ - 6 -
2.1 预处理的概念与作用........................................................................................ - 6 -
2.2在Ubuntu下预处理的下令............................................................................. - 6 -
2.3 Hello的预处理结果解析................................................................................. - 6 -
2.4 本章小结............................................................................................................ - 7 -
第3章 编译................................................................................................................ - 8 -
3.1 编译的概念与作用............................................................................................ - 8 -
3.2 在Ubuntu下编译的下令................................................................................ - 8 -
3.3 Hello的编译结果解析..................................................................................... - 8 -
3.4 本章小结.......................................................................................................... - 10 -
第4章 汇编.............................................................................................................. - 11 -
4.1 汇编的概念与作用.......................................................................................... - 11 -
4.2 在Ubuntu下汇编的下令.............................................................................. - 11 -
4.3 可重定位目的elf格式.................................................................................. - 11 -
4.4 Hello.o的结果解析....................................................................................... - 13 -
4.5 本章小结.......................................................................................................... - 14 -
第5章 链接.............................................................................................................. - 15 -
5.1 链接的概念与作用.......................................................................................... - 15 -
5.2 在Ubuntu下链接的下令.............................................................................. - 15 -
5.3 可执行目的文件hello的格式..................................................................... - 16 -
5.4 hello的虚拟地址空间................................................................................... - 17 -
5.5 链接的重定位过程分析.................................................................................. - 18 -
5.6 hello的执行流程........................................................................................... - 20 -
5.7 Hello的动态链接分析................................................................................... - 22 -
5.8 本章小结.......................................................................................................... - 23 -
第6章 hello进程管理...................................................................................... - 24 -
6.1 进程的概念与作用.......................................................................................... - 24 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 24 -
6.3 Hello的fork进程创建过程........................................................................ - 24 -
6.4 Hello的execve过程.................................................................................... - 25 -
6.5 Hello的进程执行........................................................................................... - 25 -
6.6 hello的异常与信号处理............................................................................... - 26 -
6.7本章小结.......................................................................................................... - 28 -
第7章 hello的存储管理.................................................................................. - 29 -
7.1 hello的存储器地址空间............................................................................... - 29 -
7.2 Intel逻辑地址到线性地址的变更-段式管理............................................... - 29 -
7.3 Hello的线性地址到物理地址的变更-页式管理......................................... - 30 -
7.4 TLB与四级页表支持下的VA到PA的变更................................................ - 31 -
7.5 三级Cache支持下的物理内存访问............................................................. - 31 -
7.6 hello进程fork时的内存映射..................................................................... - 32 -
7.7 hello进程execve时的内存映射................................................................. - 32 -
7.8 缺页故障与缺页停止处理.............................................................................. - 32 -
7.9动态存储分配管理.......................................................................................... - 33 -
7.10本章小结........................................................................................................ - 34 -
第8章 hello的IO管理.................................................................................... - 35 -
8.1 Linux的IO装备管理方法............................................................................. - 35 -
8.2 简述Unix IO接口及其函数.......................................................................... - 35 -
8.3 printf的实现分析........................................................................................... - 36 -
8.4 getchar的实现分析....................................................................................... - 37 -
8.5本章小结.......................................................................................................... - 38 -
结论............................................................................................................................ - 38 -
附件............................................................................................................................ - 39 -
参考文献.................................................................................................................... - 40 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用盘算机体系的术语,简述Hello的P2P,020的整个过程。
1.1.1 2P的过程
P2P指的是From Program to Process,指的是hell如何从c语言源文件经过一系列处理转化为可执行文件,这一过程主要可以分为四个阶段
- 预处理:复制include的内容,展开宏变量,转为hello.i
- 编译:翻译成汇编语言,转为hello.s
- 汇编:将汇编语言翻译成机器语言,天生可重定位目的程序hello.o
- 链接:由链接器ld来把c标准库内里的函数合并到我们的文件中,天生可执行文件hello
1.2 情况与工具
1.2.1硬件情况:X64 CPU;2.20GHz;16G RAM;1024GHD Disk;
1.2.2软件情况:Windows11 64位,VMware Workstation Pro15.5.1,Ubuntu 20.04.4
1.2.3开发及调试工具:gdb;edb;objdump;readelf;Code::Blocks20.03;vscode
1.3 中心结果
- hello.i: hello.c预处理后的文件。 用于处理#对应的内容
2)hello.s: hello.i编译后的文件。由编译器天生的汇编代码
3)hello.o: hello.s汇编后的文件
4)helloelf.txt:readelf产生的hello.o的elf分析文件
5) hello_asm1.s:使用objdump天生的hello.o的反汇编文件
6)helloelf2.txt:hello的elf文件
7)hello_asm2.s:使用objdump天生的hello的反汇编文件
8)hello:最终编译天生的可执行文件
9)hello.c:程序源文件
1.4 本章小结
本章主要是讲述了一个背景,先容本作业的目的和总结hello的一生。同时列出来实验的情况,开发工具,和中心内容说明以便后续阅读。
第2章 预处理
2.1 预处理的概念与作用
2.1.1预处理的概念:预处理是指预处理器在源代码编译前所进行的一系列文本利用,其主要包括删除解释、处理#开头的预处理指令(如复制头文件,展开宏)等
2.1.2预处理的作用1)去除解释:方便后续汇编器天生汇编代码2)进行宏替换:把宏定义的变量直接换成常值。3)展开头文件:将#include所指定的头文件写入并展开。4) 做条件编译:主要与#if系列语句有关,可以在差别条件下执行差别的程序,便于程序跨平台
2.2在Ubuntu下预处理的下令
cpp hello,c > hello.i 大概 gcc hello.c -E -o hello.i
2.3 Hello的预处理结果解析
检察hello.i发现前面是三个引用的头文件的信息,3480行以后是和原文件一样的c语言源码。
2.4 本章小结
本章是hello“程序生”的出发点,从最起始的预处理开始,解说了预处理的概念和作用,展示了ubuntu下预处理利用及利用结果,并对预处理文件hello.i进行相识析,直观反应预处理的作用。
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念:利用编译软件,从c语言源码天生汇编代码的过程。
3.1.2编译的作用:1)产生汇编代码2)优化源代码的结构
3.2 在Ubuntu下编译的下令
/usr/lib/gcc/x86_64-linux-gnu/9/cc1 hello.i > hello.s大概gcc hello.i -S -o hello.s
3.3 Hello的编译结果解析
3.3.1分析各个段的意义:1).file:声明这个文件的名字是hello.i
2).rodata:只读数据 LC0是谁人输入时候参数数量不对的提示词(用法: Hello 学号 姓名 手机号 秒数!) .LC1常量是一个格式串,是printf要使用的
3).globl 表示声明的符号是全局的
4).text表示代码段
5).type 表示这个符号的范例,后面的@function表示他是一个函数
6).LFB6等是函数的标签 .cfi_*是栈的利用大概是调试用的
3.3.2 留局部变量:1)在21行把%edi(argc)放到%rbp-20的位置留出了20Byte 其实argc是int只有4Byte
2)在22行把%rsi(argv) 放到了%rbp-32的位置。注意这里其实只是放了数组的首地址,即只使用了8Byte
3.3.3跳转:通过比较argc与5的巨细,相同的话执行.L2函数
3.3.4循环:1)声明%rbp-4是存一个局部变量,在源码中他是i。
2)这里.L3明显是一个for循环的实现,假如i小于等于8继承执行.L4即循环体。i大于8则调用getchar再结束函数
3)当然这里的je就是一个关系利用
3.3.5对于.L4的实现主要分1)使用栈通报参数如使用-32(%rbp)
2)立即数算数利用:add $24, %rax把24这个应该是10进制加到rax中
3)数组利用:上面有说到%rbp-32存的是一个字符串数组的首地址故对这个地址加8的n倍即是取数组中下标为n的数
4)寄存器通报参数:在调用printf和sleep函数的时候,汇编代码使用了寄存器通报参数,具体寄存器的值我使用#给出了
5)赋值:movq -32(%rbp), %rax就是把数组首地址赋值给了rax
6)指针利用: addq $16, %rax就是一个对rax保存的指针进行利用
movq (%rax), %rax则是取出地址对应的值
7)函数调用:call sleep@PLT #调用sleep就是调用sleep
3.4 本章小结
本章首先分析了编译的概念和作用,再在ubuntu下实际进行了编译利用并展示结果,通过按范例、利用分析编译结果文件,生动体现了编译的利用方法和重要作用。
第4章 汇编
4.1 汇编的概念与作用
4.1.1汇编的概念:汇编是指把汇编语言编写的程序转换为相匹配的机器语言程序的过程。汇编程序所输入的是用汇编语言编写的源程序,而输出的是用机器语言编写的目的程序。
4.1.2汇编的作用 1)将程序转写为机器语言
2)汇编过程中可从汇编程序得到一个可重定位目的文件,便于后续的链接利用
4.2 在Ubuntu下汇编的下令
As hello.s -o hello.o 大概gcc –c hello.s –o hello.o
4.3 可重定位目的elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.3.1使用下令readelf -a hello.o > helloelf.txt把hello.o的ELF信息重定向到一个txt文件中下面是结果,及各段的解析
可重定位目的文件的ELF主要包括三个部分:ELF头,节,和节头部表
- ELF头:包含页面巨细,虚拟地址内存段(节),段巨细
- .text:机器代码部分
- .rela.text:代码的重定位部分
- .data:已经初始化的全局变量和静态c变量,
- .bss:未初始化的全局变量和静态c变量以及所有被初始化为0的全局变量和静态c变量
- .rodata:只读数据,比如上面提到的,提示语句和printf的格式串
- .symtab:符号表:内含函数和全局/静态变量名,节名称和位置
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的利用数与汇编语言不一致,特别是分支转移函数调用等。
4.4.1:使用objdump天生hello_asm1.s
4.4.2对比 从hell.o反汇编来的 之前天生的
- 发现多了机器码,就是多了在汇编代码前面以16进制表示的那些代码
同时栈分配的空间也有统一使用16进制表示
- 函数调用方式差别:汇编代码中,函数的调用直接通过函数名进行;而在机器代码中,call的目的地址是当前指令的下一条地址。机器代码中,对于这种不确定地址的调用,需要在链接时才能确定其地址
- 分支跳转方式差别。汇编代码中使用标识符来确定跳转目的;机器代码中经过转写直接使用地址实现跳转。
4.5 本章小结
这一章首先简要说明了汇编在“程序生”中的概念与作用,随后在ubuntu中实际利用了汇编并天生重定向文件(hello.o),对elf文件的内容进行了具体解析。接着还进行了反汇编并对反汇编结果进行阐释,由此衍生,解释了机器代码与汇编代码的关系与差别。
第5章 链接
5.1 链接的概念与作用
5.1.1链接的概念:链接是指将各种代码和数据片段(好多的.o可重定位目的文件)整合为一个单一文件的过程,这一文件可以被加载到内存执行,由此使得程序可被执行(天生可执行目的文件)。
5.1.2链接的作用:1)分离编译:使得差别的c语言代码可以先天生自己的.o文件再在这一步被链接起来,这样假如改动了某一处的代码就不需要对整个项目重新编译
2)确定地址:使得机器代码中所调用的不确定地址可以被确定
5.2 在Ubuntu下链接的下令
下令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o
5.3 可执行目的文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,巨细等信息。
5.3.1使用 readelf -a hello > helloelf2.txt把hello的elf格式信息输出到helloelf2.txt中
1)ELF头:
其中文件范例发生了变化,变为了EXEC(可执行文件)。节头部数量(Number of section headers)也发生了变化,变为了27个
2)节头:
3)符号表
多了很多其他链接文件内里的符号
5.4 hello的虚拟地址空间
使用edb加载hello,检察本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
- edb看到的虚拟地址空间:这路0x401000是init的开始地址
- .interp段检察上面readelf的结果可以知道是在0x400270
3).text段:
4).rodata段
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的差别,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
5.5.2使用objdump -d -r hello >hello_asm2.s
5.5.3差别点:1)在hello.o的反汇编文件中的地址都是相对于main函数基址的偏移。而在hello的反汇编文件中是虚拟地址。
2)在hello.o的反汇编文件中call和jump类的指令都是指向下一个地址。而而在hello的反汇编文件中,已经被调解为指向目的地点的虚拟地址。
5.5.4重定位:。
图1重定位表
图2在重定位前的反汇编代码
图3在重定位后的
- R_X86_64_PC32 相对地址引用:观察图2,我们发现第15行lea的调用就是(这里lea的作用是把提示信息的字符串赋值给rdi以打印提示信息)
下面是说明具体的工作:
在重定位的时候,链接器读出重定位表中的数据知道了①偏移量是0x18 ②要从.rodata(0x402000)开始 ③加数为-0x4就是他有四个字节的0去占位,因为pc指向下一条指令的位置,所以要把占位的加上 ④main函数从0x4010c5开始
Step1:算出lea之后的开始地址:(待填充的地址)
即main函数+offset = 0x4010c5 + 0x18=0x4010dd
Step2:算出目的字符串的位置:0x402000+0x4=0x402004
Step3:算出相对值:0x402004-0x4010dd=0xf27
- R_X86_64_PLT32 地址引用:观察图2,发现17行的callq调用的就是(这里callq是调用函数输出我们的字符串)
下面是说明具体的工作:
在重定位的时候,链接器读出重定位表和函数表中中①偏移量是0x1d②puts在0x 401030③main函数从0x4010c5开始④加数为-0x4
所以是0x401030-(0x4010c5+0x1d)-0x4 = 0xffff4a这是一个补码表示的负数
5.6 hello的执行流程
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序停止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
5.6.1首先使用-g选项天生符号表进入gdb,再在_init _start main exit四个函数处设置断点
1)_init:调用__init_misc 和 0x7ffff7df9b50 <__setfpucw>
2)_start:调用了 0x403ff0处的一个程序
- main :调用了 0x401030 <puts@plt> 和401070 <exit@plt>
和401040 <printf@plt>和401060 <atoi@plt>和401080 <sleep@plt> 和401050 <getchar@plt>
- exit 调用callq 0x7ffff7dfd7b0 <__run_exit_handlers>
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb/gdb调试,分析在动态链接前后,这些项目的内容变化。要截图标识说明。
5.7.1使用ldd先找出那些库被动态链接了
5.7.2动态链接的概念:动态链接的概念是基于共享库建立的。在程序运行大概加载时,共享库可以加载到程序的任意内存地址并和一个程序链接起来。相较于把所有模块链接起来成为一个单独的可执行文件的静态链接,它在运行时才成为一个完整的程序,因此称为动态链接。
5.7.3使用gdb观察 info sharedlibrary
可见,在程序运行之后加载了两个共享库
5.8 本章小结
这一章解说了链接相关的知识,首先阐述了链接的概念与作用,随后在ubuntu下实际利用并展示了链接结果,之后重定位分析链接过程、解读hello虚拟内存空间,并具体展示了动态链接的概念、作用和带来的影响。
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1进程的概念:进程是指一个执行中的程序的实例,体系中每个程序都运行在某个进程的上下文中,其内含多种程序正常执行所需要的状态。
6.1.2进程的作用:在运行一个进程时,我们的这个程序认为自己是体系当中唯一一个运行的程序,进程的作用就是提供给程序两个关键的抽象:1.独立的逻辑控制流,即程序独占使用处理器的假象。2.私有的地址空间,即程序独占使用内存体系的假象。
6.2 简述壳Shell-bash的作用与处理流程
Step1:对下令行参数开端处理,分析出参数中的下令部分和参数部分。同时判断下令行是否为空
Step2:假如不为空则判断第一个下令行参数是不是一个内置的下令,假如是一个内置下令则直接执行
Step3:否则检查是否是一个应用程序。之后在搜索路径里探求这些应用程序,假如键入的下令不是一个内部下令并且路径里没有找到这个可执行文件,则报错
Step3:假如找到可执行文件,那么创建新的进程去执行这个文件
6.3 Hello的fork进程创建过程
Shell调用fork创建子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,因此fork后子进程可以读写父进程中打开的任意文件。父进程和创建的子进程最大的区别在于其PID差别。 fork会被父进程调用一次,返回两次,父进程与创建的子进程并发执行。执行hello时,fork后的进程在前台执行,因此创建它的父进程shell暂时挂起等待hello进程执行完毕。
实际上是创建当前进程的mm_struct,vm_area_struct和页表的副本。两个进程的每个页表都标记为写时复制
6.4 Hello的execve过程
shell通过fork创建一个子进程后,execve函数在当前进程的上下文中加载并运行一个新程序即hello。 Execve需要三个参数:可执行目的文件名filename、参数列表argv、情况变量列表envp。这些都由shell构造并通报。
除非找不到filename,否则execve不会返回。(调用一次,(正常情况下)从不返回) 调用execve会将这个进程执行的本来的程序完全替换,它会删除已存在的用户区域,包括数据和代码;然后,映射私有区:为Hello的代码、数据、.bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的;之后映射共享区;最后把控制通报给当前的进程的程序入口。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.5.1逻辑控制流
逻辑控制流是一个PC值的序列,PC值就是程序计数器的值,这些值与可执行目的文件的指令大概包含在运行时动态链接到程序的共享对象中的指令一一对应。
6.5.2时间分片
在当代盘算机体系中,进程是轮流使用处理器的,每个进程都执行它的流的一部分,然后被抢占(暂时挂起),再轮到其它进程。一个逻辑流的执行在时间上与另一个流重叠被称为并发流,这两个流并发运行。 多个流并发执行的概念被称为并发。一个进程与其他进程轮流运行的概念称为多任务。一个进程执行其控制流一部分的每一个时间段叫做时间片,多任务也就被称作是时间分片。
6.5.3用户模式与内核模式
为了掩护利用体系内核,处理器在某一个控制寄存器中的一个模式位,设置模式位时,进程就运行在内核模式中,否则运行在用户模式。内核模式的代码可以无限定地访问所有处理器指令集以及全部内存和 I/O 空间。假如用户模式的进程要享有此特权,它必须通过体系调用向装备驱动程序或其他内核模式的代码发出哀求。
运行程序代码初始时都是在用户模式中的,当发生停止故障或体系调用的异常时,进程从用户模式变化为内核模式。当异常发生时,控制通报到异常处理程序,处理器将模式变化为内核模式。内核处理程序运行在内核模式中,当它返回到应用程序代码时,处理器把模式从内核模式改回到用户模式。
6.5.4进程上下文切换
上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。进程执行的某些时候,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这样的决策叫做调度,由内核中的调度器的代码处理。在这个抢占过程中需要用到上下文切换,上下文切换保存当前进程的上下文,规复先前某个被抢占的上下文,并将控制通报给新规复的进程。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等下令,请分别给出各下令及运行结截屏,说明异常与信号的处理。
6.6.1异常种类
1停止:来自I/O装备的异常
2陷阱:有意的异常
3故障:存在可以规复的错误
4停止:不可规复的错误
6.6.2信号范例
1sigint:ctrl+c用于停止程序 2sigtstp:ctrl+z用于停息程序
3sigchld:子程序停止时向父进程发送的指令
6.6.3具体的
1乱按键盘
2ctrlc和ctrlz
3ps和jobs
4pstree
5 fg和kill
6.7 本章小结
本章概述了hello进程大致的执行过程,阐述了进程、shell、fork、execve等相关概念,之后从逻辑控制流、时间分片、用户模式/内核模式、上下文切换等角度具体分析了进程的执行过程。并在运行时尝试了差别情势的下令和异常,每种信号都有差别处理机制,针对差别的shell下令,hello会产生差别响应
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.1.1逻辑地址
程序代码经过编译后出现在汇编程序中地址。逻辑地址由选择符(在实模式下是描述符,在掩护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成,是一个相对值。
7.1.2线性地址
线性地址空间是一个非负整数的聚集。逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合情势。
7.1.3虚拟地址
虚拟地址空间是0到N的所有整数的聚集(N是正整数),是线性地址空间的有限子集。分页机制以虚拟地址为桥梁,将硬盘和物理内存联系起来。是程序自己分配的。
7.1.4物理地址
CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过毗连着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
7.2 Intel逻辑地址到线性地址的变更-段式管理
在 Intel 平台下,逻辑地址是 selector ffset 这种情势,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。
CS寄存器(代码段寄存器): CS寄存器存储了当前执行的指令地点的代码段的起始地址。它是一个16位寄存器,指示了代码在内存中的位置。CS寄存器的值与代码段的段基址相关,形成了代码段的起始物理地址。
EIP寄存器:用来存储CPU要读取指令的地址,CPU通过EIP寄存器读取即将要执行的指令。每次CPU执行完相应的汇编指令之后,EIP寄存器的值就会增长。
假如用 selector 去 GDT( Global Descriptor Table,全局描述符表 ) 里拿到 segment base address(段基址) 然后加上 offset(段内偏移),这就得到了 linear address(线性地址)。这个过程就称作段式内存管理。
逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段(段选择符),可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
全局的段描述符,放在GDT中,一些局部的段描述符,放在“LDT(Local Descriptor Table,局部段描述符表)”中。
给定一个完整的逻辑地址段选择符+段内偏移地址,看段选择符的T1=0照旧1,知道当前要转换是GDT中的段,照旧LDT中的段,再根据相应寄存器,得到其地址和巨细。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。再由基地址加上偏移量的值,便得到了线性地址。
7.3 Hello的线性地址到物理地址的变更-页式管理
线性地址(VA)到物理地址(PA)之间的转换通过分页机制完成。分页机制雷同主存和Cache之间的分块机制,分页机制对虚拟地址和物理内存进行分页,页的巨细通常是4KB到2M。在x86-64机器上,虚拟地址空间的N是2的48次方,有256TB,比正常的硬盘大得多
分页机制中使用一个叫做页表的数据结构来记录这些关系,页表也是存储在内存中的,是由利用体系维护的。每个进程都有一个页表,页表中的每一项,即PTE(页表条目),记录着该对应的虚拟地址空间的那一页是否有用(即是否有对应的物理内存上的页),物理页的起始位置或磁盘地址,访问权限等信息。PTE根据差别的映射状态也被划分为三种状态:未分配、未缓存、已缓存
7.4 TLB与四级页表支持下的VA到PA的变更
7.4.1页表
页表是 PTE(页表条目)的数组,它将虚拟页映射到物理页,每个 PTE 都有一个有用位和一个 n 位地址字段,有用位表明该虚拟页是否被缓存在 DRAM 中。虚拟地址分为两个部分,虚拟页号(VPN,Virtual Page Number)和虚拟页面偏移量(VPO,Virtual Page Offset)。其中VPN需要在PTE中查询对应,而VPO则直接对应物理地址偏移(PPO)。
7.4.2TLB
TLB(translation lookaside buffer,地址转换后备缓冲器,习惯称之为“快表”)是一个位于MMU(Memory Management Unit,内存管理单元)中,关于PTE的一个缓存。TLB是一个小的、虚拟寻址的缓存,其中每一行均保存了一个由单个PTE组成的块。TLB有高度的相联性,能够加速地址翻译,而多级页表能够对页表进行压缩,便于大量存储。
7.4.3变更机制
在从VA翻译得到PA的过程中,MMU首先用VPN向TLB申请哀求对应的PTE,假如命中,那么直接跳过后面的步骤;之后MMU天生PTE地址,从高速主存哀求得到PTE,高速缓存或主存会向MMU返回PTE。若PTE有用位为0,说明缺页,MMU触发缺页异常,缺页处理程序确定物理内存中的牺牲页(若页面修改,则换出到磁盘)。之后缺页处理程序调入新的页面,并更新PTE。之后却也处理程序返回原进程,并重新执行导致缺页的指令。
7.5 三级Cache支持下的物理内存访问
通过内存地址的组索引获得值,假如对应的值是data则像L1 d-cache对应组中查找,假如是指令,则向L1 i-cache对应组中查找。将L1对应组中的每一行的标记位进行对比,假如相同并且有用位为1则命中,获得偏移量,取出相应字节,否则不命中,向下一级cache探求,直到向内存中探求。
7.6 hello进程fork时的内存映射
当fork函数被调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本,并将两个进程中的每个界面都标记为只读,将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存搞好和调用fork时存在的虚拟内存相同。这两个进程中的任意一个后来进行写利用时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve 函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运 行包含在可执行目的文件 hello 中的程序,用 hello 程序有用地替换了当前程序。 加载并运行 hello 需要以下几个步骤:
step1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
step2.映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结 构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域是哀求二进制零的,映射到匿名 文件,其巨细包含在 hello 中,栈和堆地址也是哀求二进制零的,初始长 度为零。
step 3.映射共享区域, hello 程序与共享对象 libc.so 链接,libc.so 是动态链 接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
step 4.设置程序计数器(PC)。execve 做的最后一件事情就是设置当前进程 上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页停止处理
7.8.1缺页故障
一个虚拟页没被缓存在DRAM中,即DRAM缓存不命中被称为缺页。当CPU引用了一个页表条目中的一个字,而该页表条目并未被缓存在DRAM中,地址翻译硬件从内存中读取该页表条目,从有用位为0可以判断尚未被缓存,进而触发缺页异常。
7.8.2缺页停止处理
缺页异常调用缺页异常处理程序,该程序会选择一个牺牲页,假如这个牺牲页在DRAM中已被修改,那么就将他写回磁盘,之后将引用的虚拟页复制到内存中的原来牺牲页地点位置,并对页表条目进行更新,随后返回。当异常处理程序返回时,它会重新启动缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。此时需要调用的虚拟页已经缓存到主存中了,则页命中可以由地址翻译硬件正常处理。
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与计谋。
7.9.1动态内存分配的概念
在程序运行时程序员使用动态内存分配器,比方调用malloc函数从而获得虚拟内存。分配器将堆(heap)视为一组差别巨细的块(blocks)的聚集来维护。每个块要么是已分配的,要么是空闲的。
7.9.2堆
由动态存储分配管理器维护着的进程虚拟内存区域称为堆。当内存中的碎片和垃圾被接纳之后,内存中就会产生多余的空闲空间。为了避免内存空间的浪费,需要记录这些空闲块,而接纳隐式空闲链表和显式空闲链表的方法则可以实现这一利用。
7.9.3隐式空闲链表
当隐式空闲链表工作时,若分配块比空闲块小,则还可以把空闲块分为两部分,一部分用来承装分配块从而避免大概导致的浪费。
隐式链表接纳界限标记的方法进行双向合并。即脚部与头部是均为 4 个字节,这一部分用来存储块的巨细并表明这个块的空闲与分配状态。同时定位头部和尾部,可以以常数时间来进行块的合并。由此,无论是与下一块照旧与上一块合并,都可以通过他们的头部或尾部得知块巨细,从而定位整个块,避免了从头遍历链表导致的时间花销极高。
7.9.4 显示空闲链表
与隐式空闲链表差别,显式空闲链表只记录空闲块而不记录所有块。
显示空闲链表的思路是维护多个空闲链表,每个链表中的块有大致相称的巨细,分配器维护着一个空闲链表数组,每个巨细类一个空闲链表。当需要分配块时,只需要在对应的空闲链表中搜索即可。
7.10本章小结
这一章聚焦于hello程序在盘算机体系当中的存储与管理问题,并引出盘算机体系当中的一个重要概念“虚拟内存”。在本章当中,我们具体讲述了hello的四种地址及其探求、变更、翻译的过程,阐释了TLB相关概念和流程,三级缓存、动态内存管理的要点重点。
第8章 hello的IO管理
8.1 Linux的IO装备管理方法
8.1.1装备的模子化:文件
一个Linux文件就是一个m个字节的序列,所有的I/O装备都被模子化为文件,所有的输入输出都被当作是文件的读和写来执行。。
8.1.2装备管理:unix io接口
这种将装备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,被称为是Unix I/O,这使得所有的输入和输出都能够以一种统一且一致的方式来执行
8.2 简述Unix IO接口及其函数
8.2.1 UNIX接口
(1)打开文件:一个应用程序通过内核打开文件,来宣告它想访问一个I/O装备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有利用中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记着这个描述符。
(2)I/O装备:内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有利用中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记着这个描述符。
(3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。
(4)读写文件:一个读利用就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增长到k+n,给定一个巨细为m字节的而文件,当k>=m时,触发EOF。雷同一个写利用就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
(5)关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件,作为响应,内核开释文件打开时创建的数据结构,并将这个描述符规复到可用的描述符池中。无论一个进程因为何种原因停止时,内核都会关闭所有打开的文件并开释他们的内存资源。
8.2.2 unix接口相关的函数
(1)int open(char *filename, int flags, mode_t mode);
进程通过调用open函数打开一个已存在的文件大概创建一个新文件。open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags参数指明了进程打算如何访问这个文件:O_RDONLY:只读、O_WRONLY:只写和O_RDWR可读可写。mode参数指定了新文件的访问权限位。
(2)int close(fd):
进程调用close函数关闭一个打开的文件,fd是需要关闭的文件的描述符
(3)ssize_t read(int fd, void *buf, size_t n);
read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF(End Of File),否则返回值表示的是实际传送的字节数量。
(4)ssize_t write(int fd, const void *buf, size_t n);
write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
8.3 printf的实现分析
由上图可知printf用了两个外部函数,一个是vsprintf,还有一个是write。
图:vsprintf 的实现
vsprintf函数作用是担当确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。write函数将buf中的i个元素写到终端。在unistd.h头文件中,我们可以找到write函数的声明:
write()函数有三个参数:
fd: 文件描述符,标识待写入的文件大概套接字。
buf: 指向要写入的数据的缓冲区。
count: 要写入的字节数。
返回值为实际写入的字节数,错误时返回-1,并设置errno。
从vsprintf天生显示信息,到write体系函数,到陷阱-体系调用 int 0x80或syscall等。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数调用了read函数,通过体系调用read读取存储在键盘缓冲区的ASCII码,直到读到回车符才返回。不过read函数每次会把所有内容读进缓冲区,假如缓冲区原来非空,则不会调用read函数,而是简单的返回缓冲区最前面的元素。
异步异常-键盘停止的处理:键盘停止处理子程序。担当按键扫描码转成ascii码,保存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到担当到回车键才返回。
8.5本章小结
本章主要先容了hello的I/O管理机制,先简述了I/O装备被抽象为文件的现象,随后先容了I/O的装备管理方法——unix IO接口,随后对unixIO接口做了先容之后,给出了Unix IO接口的相关函数,并在此根本上,对printf和getchar的实现原理进行了先容。
结论
hello从出生(编写完成)到死亡(进程被接纳)总共履历了如下几个步骤:
1、预处理(cpp)。将hello.c进行预处理(处理所有的#后面的代码),将文件调用的所有外部库文件合并展开,天生一个经过修改的hello.i文件。
2、编译(ccl)。将hello.i文件翻译成为一个包含汇编语言的文件hello.s。
3、汇编(as)。将hello.s翻译成为一个可重定位目的文件hello.o。这时候不再是文本文件了。
4、链接(ld)。将hello.o文件和可重定位目的文件和动态链接库链接起来,天生一个可执行目的文件hello。
5、运行。在shel1中输入./hello 2023110264 谢辰阳 13868460946 并回车。
6、创建进程。首先终端判断出输入的指令不是shell内置指令,于是调用fork函数创建一个新的子进程。
7、加载程序。shell调用execve函数,启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。
8、执行指令:CPU为进程分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流。
9、访问内存:MU将程序中使用的虚拟内存地址通过页表映射成物理地址。
10、信号管理:当程序在运行的时候我们输入Ctrl+c,内核会发送SIGINT信号给进程并停止前台作业。当输入Ctrl+z时,内核会发送SIGTSTP信号给进程,并将前台作业停止挂起。
11、停止:当子进程执行完成时,内核安排父进程接纳子进程,将子进程的退出状态通报给父进程。内核删除为这个进程创建的所有数据结构。
附件
列出所有的中心产物的文件名,并予以说明起作用。
- hello.i: hello.c预处理后的文件。 用于处理#对应的内容
2)hello.s: hello.i编译后的文件。由编译器天生的汇编代码
3)hello.o: hello.s汇编后的文件
4)helloelf.txt:readelf产生的hello.o的elf分析文件
5) hello_asm1.s:使用objdump天生的hello.o的反汇编文件
6)helloelf2.txt:hello的elf文件
7)hello_asm2.s:使用objdump天生的hello的反汇编文件
8)hello:最终编译天生的可执行文件
9)hello.c:程序源文件
参考文献
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] R. E. Bryant, D. R. O'Hallaron, and T. Krentel, Computer systems: A programmer's perspective, 3rd ed. Pearson, 2015. 深入相识盘算机体系
[3] [转]printf 函数实现的深入分析 - Pianistx - 博客园 printf函数实现的深入相识与分析
[4] 【Linux】GDB保姆级调试指南(什么是GDB?GDB如何使用?)_gdb教程-CSDN博客 GDB保姆式指南
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |