步调人生-Hello’s P2P

打印 上一主题 下一主题

主题 536|帖子 536|积分 1608

摘  要

本文以一个简单的"hello.c"步调为出发点,深入探讨了步调在Linux操作系统下的完整生命周期。这个生命周期包罗预处置处罚、编译、汇编、链接等阶段,以及历程管理、存储管理和I/O管理的环节。通过形貌,读者得以清晰地观察"hello.c"从键盘输入、生存到磁盘,再到步调运行结束,最终变为僵尸历程的全过程。这不仅显现了步调的生命历程,也使我们更深入地理解了盘算机步调的运行机制。
关键词:预处置处罚,编译,汇编,链接,历程管理,存储管理,IO管理   

第1章 概述

1.1 Hello简介

Hello语言代码实现了一个简单的步调。用户通过下令行输入学号、姓名和等待时间,Hello则在循环中输出“Hello 学号 姓名 秒数”的消息,每次输出后暂停指定的时间
P2P:步调的天生并不是一挥而就的简单过程,而是经历了多个阶段的处置处罚。包罗预处置处罚、编译、汇编和链接。预处置处罚阶段清理和预备源代码,编译将源代码转换为汇编语言,汇编将汇编语言转换为呆板可辨认的指令,链接则将多个目标文件毗连为一个可执行文件hello。具体过程如流程图:
下面是相关流程图:

图1:hello步调流程图


O2O:在步调运行时,通过打开Shell等待输入指令。当我们键入 ./hello 时,Shell会创建一个新的历程来执行名为 "hello" 的步调。操作系统通过 fork 创建子历程,随后通过 execve 装载步调,并进行一系列操作,包罗内存分配、访存等,以便步调在系统中运行。当步调执行结束后,控制权返回父历程或先人历程,这些历程负责回收子历程的资源,确保系统资源得到开释。这个回收过程是操作系统对历程生命周期的管理的一部分,确保系统的稳固性和资源的有效利用。

1.2 环境与工具

硬件环境:X64 CPU;2.9GHz;16G RAM;512G SSD
软件环境:Windows10 64位;VMware17.0;Ubuntu 22.04.2 LTS 64位
开发与调试工具:Visual Studio 2022,gcc,vim,edb,objdump,readelf,Codeblocks
1.3 中间结果


表1:相关文件



1.4 本章小结

本章简述了Hello步调的一生,概括了从P2P到020的整个过程,还先容了相关软硬件环境和开发调试工具,最后列出了相关中间文件以供参考。




第2章 预处置处罚

2.1 预处置处罚的概念与作用

概念:预处置处罚是步调编译过程中的第一个阶段,它在实际编译之前对源代码进行一些文本更换和处置处罚,为编译器提供经过处置处罚的源代码。在C语言中,预处置处罚器负责执行预处置处罚操作。
作用:对于头文件:


图2:头文件

预处置处罚器会将这些头文件的内容插入到步调文本中,以便在编译时能够辨认和利用步调中的 printf、sleep 等函数。实际上,这些头文件中包含了一些宏定义和条件编译,但这些细节Hello中不明显。\

   预处置处罚指令:



表2:相关处置处罚指令



2.2在Ubuntu下预处置处罚的下令

在Ubuntu下,预处置处罚的下令为:gcc -E hello.c -o hello.i
-E参数指对文件进行预处置处罚操作
-o选项用来指定输出文件为hello.i
最后天生hello.i文件

图3:预处置处罚过程

2.3 Hello的预处置处罚结果解析

打开hello.i文件,发现其内容从原来的20多行酿成3000多行,是因为预处置处罚使得代码会包含一些在源代码中看不到的内容,包罗:
头文件内容:所有被包含的头文件<stdio.h>,<unistd.h>,<stdlib.h>的内容会被插入到预处置处罚后的输出文件

宏展开:假如在代码中利用了宏定义(本代码未涉及),预处置处罚器会将所有宏展开,将宏的定义更换为其对应的内容

条件编译:任何由条件预处置处罚指令控制代码块会根据条件的真假进行处置处罚(本代码未涉及),天生的文件会根据条件编译的结果有所差别

注释的处置处罚:预处置处罚器可能会处置处罚注释并在预处置处罚输出中进行适当的处置处罚


2.4 本章小结

本章先容了C步调中的预处置处罚阶段,强调了预处置处罚的概念和其在编译运行过程中的作用。预处置处罚作为编译的首要步骤,天生的.i文件展示了原始源文件中的代码,还包罗了通过头文件包含等操作引入的额外内容。通过查看.i文件,读者能更直观地感受到预处置处罚前后源文件的变革,理解了预处置处罚器对代码的处置处罚过程,为后续编译和运行阶段奠定了基础。




第3章 编译


3.1 编译的概念与作用

概念:编译阶段接收预处置处罚后的代码并进行语法和语义查抄,将代码转换为中间代码。天生的中间代码是一种抽象的、与硬件无关的体现。
作用:产生汇编语言文件,并交给呆板,为后续的目标代码天生和最终可执行文件的链接提供了基础。编译阶段任务是确保代码的精确性、可维护性,并通过优化提高步调的性能。

        

3.2 在Ubuntu下编译的下令

在Ubuntu下,编译的下令为:gcc -S hello.i -o hello.s
-S参数指对文件进行编译操作
-o选项用来指定输出文件为hello.
最后天生hello.s文件

图4:编译过程

3.3 Hello的编译结果解析


3.3.1 文件信息

图5:文件信息


  • .file "hello.c":标识源文件的名称
  • .text:开始文本段,包含步调的实际代码
  • .section .rodata:只读数据段,用于存储只读的常量数据
  • .LC0、LC1:用于存储字符串的标签
  • .string代表字符串
  • .align 指出对齐方式为8字节对齐
  • .globl main 和 .type main, @function:将 main 函数声明为全局函数

3.3.2 局部变量
    
图6:局部变量

这里相当于源代码语句:int i
对于32位系统,局部变量存储在栈中,当进入函数main的时候,会根据局部变量的需求,在栈上申请一段空间供局部变量利用。当局部变量的生命周期结束后,会在栈上开释。这里,将立即数0村村到%ebp-4的位置,相当于将将一部分空间用于存储随机变量。

3.3.3 字符串常量

图7:字符串常量

字符串常量在hello.s最开头.rodata(只读)的LC0与LC1处被储存,且标志为只读数据,防止字符串数据被修改。这里以.LC0举例,.LC0(%rip):是一个内存操作数,体现位于当前指令(指令指针寄存器 %rip 指向的位置)的偏移所在为 .LC0 的数据。这条指令的目标是将字符串 .LC0 的所在加载到 %rdi 寄存器中,预备将该所在通报给后续的函数调用。


3.3.4 赋值操作
             

图8:赋值

赋值操作通过数据传送指令来进行,主要是MOV类指令,包罗movb,movw,movl,movq 这里体现将立即数0x0给%eax


3.3.5 算术操作

图9:算术操作

算术操作主要涉及到寄存器之间的数据通报和基本的操作,如:
加法指令(ADD):add dest, src:将 src 寄存器或内存中的值加到 dest 寄存器中
减法指令(SUB):sub dest, src:从 dest 寄存器中减去 src 寄存器或内存中的值
乘法指令(IMUL):imul dest, src:将 dest 寄存器与 src 寄存器或内存中的值相乘
除法指令(IDIV):idiv divisor:将累加器寄存器(通常是 %rax)中的值除以 divisor 寄存器或内存中的值。商存储在累加器中,余数存储在 %rdx 中
递增指令(INC):inc operand:将操作数的值加1。通常用于递增计数器
递减指令(DEC):dec operand:将操作数的值减1。通常用于递减计数器

3.3.6 关系操作

图9:关系操作

包含两种相关指令:
1.test 指令:
test 指令执行按位逻辑与(AND)操作,但不生存结果,只更新标志寄存器的状态。通常用于查抄寄存器或内存中的位模式,而不必要生存结果,只关心设置零标志(ZF)和符号标志(SF)。
2.cmp 指令:
cmp 指令用于比较两个操作数,但不生存结果,只更新标志寄存器的状态。它是通过执行减法操作,但不生存减法结果,只更新标志寄存器。

    这里比较%rbp-4的所在和立即数7 若满足jle=1则发生跳转。

3.3.7 数组访问

图10:数组访问

    数组的访问方式涉及到偏移量,与基址寄存器相联合,基址寄存器包含数组的起始所在。通过首所在+偏移量的方式实现访问数组。以上述为例,%rbp-32存储的是数组头元素的所在,将其存入%rax中,通过add语句+16,实现访问argv[2],将其对应所在存入%rdx中。

3.3.8 条件分支

图11:条件分支

条件分支的实现离不开关系操作与跳转,通过对判断条件的比较设置标志位,在通过查抄标志位决定是否跳转到另一部分语句。这里相当于源代码中if(argc!=4)的语句,相等则跳转到一个语句继承执行。


3.3.9 循环

图12:循环

循环的调用同样必要关系操作和跳转,这里如上图所示,L3前两行执行一个判断,这里相当于源代码的i<8,若符合判断条件,则跳转到L4,这里L4相当于一个循环体,当再次运行到L3时,两者重新比较。这里和dowhile语句差别的是,for循环和while必要先一步执行判断语句。

3.4 本章小结


在汇编指令层面,代码利用了寄存器和栈操作来存储局部变量和处置处罚函数调用。条件跳转指令(如je和jle)用于控制循环的执行。函数调用通过call指令实现,而返回则通过ret指令完成。总体而言,这段代码通太过支判断、循环和字符串处置处罚,展示了对函数调用、栈操作以及条件分支的汇编实现。



第4章 汇编


4.1 汇编的概念与作用

概念:在汇编过程中,汇编器将每条汇编指令翻译成对应的呆板指令,并天生一个目标文件(.o文件),其中包含呆板指令的二进制体现。
作用:将汇编代码转换为呆板可执行的二进制代码,盘算机只有通过呆板指令才气实现各运算过程。
4.2 在Ubuntu下汇编的下令

 在Ubuntu下,编译的下令为:gcc -c hello.s -o hello.o
-c参数指对文件进行汇编操作但不链接
-o选项用来指定输出文件为hello.o
最后天生hello.o文件

图13:汇编天生.o文件


4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

图14:典范的ELF可重定位目标文件格式

ELF头以一个16个字节的序列开始,以节头部表结尾,夹在中间的都是节,一个典范的ELF可重定位目标文件包含下面几个节:

1 .text:已编译的呆板代码
2 .rodata:只读数据,比如字符串常量
3 .data:已初始化的全局和静态C变量
4 .bss:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量,5 .symtab:一个符号表,它存放在步调中定义和引用的函数和全局变量的信息6 .rel.text:.text节的位置列表
7 .rel.data:被模块引用或定义的所有全局变量的重定位信息
8 .debug:一个调试符号表,其条目是步调中定义的局部变量和类型定义,步调中定义和引用的全局变量以及原始的C源文件,在编译时以-g选项调用编译器驱动步调,才会得到这张表
9 .line:原始C源步调中的行号和.text节中呆板指令之间的映射
10 .strtab:字符串表,包含.symtab和.debug节中的符号表
利用指令:readelf -h hello.o查看ELF头

图15:hello.o ELF头


相关信息
Magic (魔数):
7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00,这是ELF文件的标识,通常以"7F

ELF"开头,用于辨认文件格式。

类别:
 ELF64体现这是一个64位的ELF文件
数据:
2 补码,小端序 (little endian),体现数据的存储方式,这里是小端序
Version:
1 (current),ELF格式的版本,当前版本是1
OS/ABI:
UNIX - System V,体现运行这个步调的操作系统和ABI(Application Binary
Interface)版本
ABI 版本:0,体现ABI的版本
类型:
REL (可重定位文件),体现这是一个可重定位文件,即一个目标文件

利用指令:readelf -S hello.o查看节头

图16:hello.o节头

利用指令:readelf -s hello.o查看符号表

图17:hello.o符号表

利用指令:readelf -r hello.o查看可重定位段信息

图18:hello.o可重定段信息

具体分析:
         1. 重定位节 '.rela.text':
PC相对32位或PLT相对32位的重定位项,对应于.rodata中的符号如exit、printf、atoi、sleep和getchar
2. 重定位节 '.rela.eh_frame':
偏移量: 000000000020信息: 000200000002
类型: R_X86_64_PC32符号值: 0000000000000000
符号名称 + 加数: .text + 0
一个PC相对32位重定位项,它指示在偏移量0x20处的位置,通过从.text的所在减去0来办理所在。
4.4 Hello.o的结果解析

以下格式自行编排,编辑时删除
利用指令:objdump -d -r hello.o查看hello.o的反汇编

图19:反汇编与hello.s对比

通过对比反汇编和原始的hello.s文件,读者可以发现,汇编指令在反汇编结果中得到了近乎一致的出现。然而,反汇编结果不仅包含了汇编指令,还额外展示了呆板代码,这些代码在左侧以16进制的形式体现。呆板指令由操作码和操作数构成,它们与汇编指令一一对应。最左侧表现的则是相对所在,为理解步调的执行流程提供了关键线索。如许的逻辑结构使得反汇编结果不仅直观易懂,还为读者深入研究步调的运行机制提供了名贵的信息。
4.5 本章小结


以下格式自行编排,编辑时删除)在本章我们理解了汇编的概念和在文件转换时的作用。通过实际操作,对hello.s文件进行了汇编,天生了ELF可重定位目标文件hello.o。借助了readelf工具,并查看其ELF头、节头表、可重定位信息和符号表等关键部分。通过详细分析这些信息,读者不仅可以理解可重定位目标文件的结构和内容,还进一步展现了呆板语言与汇编语言之间的一一对应关系。最后,将天生的hello.o与原始的hello.s文件进行了比较。通过对比两者的差别之处,读者能够更清晰地看到呆板指令与汇编指令之间的对应关系,从而深化对两者关系的理解。



第5章 链接


5.1 链接的概念与作用

概念:链接是将一个或多个目标文件组合成一个可执行文件的过程。在这个过程中,链接器负责解析符号引用、所在重定位、归并代码和数据段等任务,最终天生一个完整的可执行文件。
作用:办理处置处罚多个源文件之间的符号引用关系,调解相对所在,归并代码和数据段,最终产生能够在操作系统上独立运行的完整步调。确保步调的各个部分精确协同工作,是将散乱的代码和数据整合成一个可执行单元的关键步骤。
5.2 在Ubuntu下链接的下令

在Ubuntu下,编译的下令为:
ld -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/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello

-dynamic-linker /lib64/ld-linux-x86-64.so.2:
指定动态链接器的路径为 /lib64/ld-linux-x86-64.so.2,它是用于在运行时加载共享库的系统动态链接器
/usr/lib/x86_64-linux-gnu/crt1.o:
包含C运行时环境的启动代码
/usr/lib/x86_64-linux-gnu/crti.o:
包含C运行时环境的初始化代码
/usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o:
包含了GCC库的启动代码
-lc:

指定链接时必要利用C库
/usr/lib/gcc/x86_64-linux-gnu/9/crtend.o:
包含了GCC库的结束代码
/usr/lib/x86_64-linux-gnu/crtn.o:
包含了C运行时环境的结束代码
-z relro:
启用 RELRO保护,将某些节的重定位表标志为只读
-o hello:
指定输出文件的名称为 hello,也就是天生的可执行文件的名称

图20:链接过程


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

查看ELF头:

图21:helloELF头

查看节表头:

图22:hello节表头

查看步调表头:

图23:hello步调表头





5.4 hello的虚拟所在空间


图24:edb查看


图25:edb查看

从图中可以看出,hello虚拟空间从0x401000开始载入,查阅可知,是.init的所在,通过对比步调头部表可以发现0x401000正是步调载入所在,且查阅节头部表发现0x401000确实是.init的所在。

图26:edb对比


同理,.text节的起始所在为0x4010f0,这也是ELF头中表现的步调的入口点所在,通过与edb上hello的虚拟所在空间对照可以发现0x4010f0处有一个_start函数,这是步调的入口点。

图27:.Rodata

最后,从节头部表可以发现.rodata节的起始所在为0x402000,这不属于步调的代码部分,而是一个只读数据,按前文编译中先容的,该只读数据里存有两个printf语句中用到的格式串。用edb查看该所在发现确实存有这两个字符串。
5.5 链接的重定位过程分析

利用指令:objdump -d -r hello对hello进行反汇编

图27:反汇编结果

我们可以发现,所在已经酿成了虚拟空间的所在,而不是hello.o中的偏移量,对于跳转指令和call指令,在hello.o中为绝对所在,而在hello中是重定位之后的虚拟所在。

图28:call指令所在

这里以call指令为例,执行call指令后的下一条指令所在为0x4011fb,与puts指令所在0x401090之差转化为补码为0xfffffe95,恰恰是call指令的所在(小端体现法)。
5.6 hello的执行流程


表3:执行流程

5.7 Hello的动态链接分析

分析hello步调的动态链接项目,通过edb调试,分析在dl_init前后,这些项目标内容变革。要截图标识说明。
动态的链接器在正常工作时链接器采取耽误绑定的链接器策略,将过程所在的绑定推迟到第一次调用该过程时。
这里先了解一下got段,它用于存储全局偏移表。这个表包含了步调中所有对全局变量或函数的引用,在运行时,这些引用会被解析为实际的内存所在。
_dl_init 函数: _dl_init 是动态链接器在步调启动时执行的一个函数。负责动态链接和初始化历程中的动态共享对象。
 这里以.got为例,起首找到.got的所在为0x403ff0,接下来edb查看

图29:edb初始化

可以看出其已经被初始化


5.8 本章小结

本章起首阐述了链接的概念和作用,探讨了可执行目标文件的内部结构。在此基础上详尽地解析了重定位过程,这一关键技术对于将相对所在转换为实际内存所在至关紧张。以可执行目标文件“hello”为例,对其各个构成部分,如代码段、数据段和符号表,进行了分析。别的还探讨了虚拟所在空间的概念,以及步调的执行流程。通过这些分析,使读者能够更好地把握链接、可执行目标文件、重定位过程以及相关概念。




6hello历程管理


6.1 历程的概念与作用

概念:历程是盘算机系统中正在执行的步调的实例。每个历程有独立的内存空间、步调计数器、寄存器集合和其他系统资源。
作用:实现并发执行,提高系统效率和相应速度,防止历程间相互干扰。进行任务调理,通过操作系统的调理算法决定何时执行哪个历程。同时具备容错性,历程的独立性确保一个历程的崩溃不会影响其他历程。
6.2 简述壳Shell-bash的作用与处置处罚流程

作用:下令解释器:Bash 答应用户通过下令行输入下令,然后将下令翻译为
操作系统能够理解和执行的指令
脚本编程:Bash 是一种脚本语言的解释器,答应用户编写脚本文件,包含一系列 Shell 下令,以便主动执行一系列任务
环境配置:Bash 负责管理用户的运行环境,包罗环境变量、别名、路径等,确保用户能够方便地利用系统和应用步调
管道和重定向:Bash 支持将一个下令的输出作为另一个下令的输入,以及通过重定向符号来控制输入和输出的流向

处置处罚流程:用户输入下令:用户在下令行界面输入下令或脚本
下令解析:Bash 解析用户输入的下令,辨认下令和参数,执行相应的语法分析
环境变量和别名扩展:Bash 根据用户配置的环境变量和别名对下令进行扩展和更换
下令执行:Bash 执行用户输入的下令或脚本,调用相应的系统调用来执行指定的操作。
管道和重定向:假如下令包含了管道或重定向符号,Bash 将相应地设置输入和输出,将下令的输出通报给其他下令或将输出重定向到文件

错误处置处罚:Bash 在执行下令时会进行错误查抄,并提供错误信息


返回结果:执行完下令后,Bash 返回相应的结果,以便用户和其他步调能够根据执行结果采取相应的步调





6.3 Hello的fork历程创建过程

Hello步调开始执行时,起首创建一个初始历程。随后,步调调用fork函数(pid_t fork(void);)来创建一个新的子历程。子历程复制包罗代码、数据和文件形貌符。在 fork 函数之后,步调会有两个执行流,一个在父历程中,另一个在子历程中。这两个历程险些拥有相同的代码和变量。在 if (argc != 4) 的条件语句中,判断当前是父历程还是子历程。根据 fork 函数的返回值(在父历程中返回子历程的 ID,在子历程中返回0),可以判断执行的路径。以下是具体流程图:

图30:fork创建及后续流程


6.4 Hello的execve过程

execve函数用于在当前历程中加载并运行一个新步调。它会更换当前历程的映像为新的可执行目标文件,而不是创建一个新的历程。这意味着当execve乐成执行,它将不会返回到原始步调。但是,假如在加载和运行新步调时发生错误,例如找不到指定的文件,execve函数会失败并返回到原始步调。
与fork函数差别,execve函数只进行一次调用,然后要么继承执行新步调,要么返回到原始步调。而fork函数会创建新的子历程,并在父历程和子历程中都有返回。
 execve函数声明为:int execve(char *filename,char *argv[],char *envp[]),包含以下几个参数:
1.filename: 字符串,体现要执行的新步调的文件路径
2.argv[]: 这是一个字符串数组,体现新步调的下令行参数。数组的第一个元素 argv[0] 通常是步调的名称,其后的元素包含步调执行时的参数
3.envp[]: 这是一个字符串数组,体现新步调执行时利用的环境变量。数组的每个元素都是形如 "NAME=VALUE" 的字符串,体现一个环境变量


6.5 Hello的历程执行

历程上下文信息:
包罗历程的寄存器、内存映像、文件形貌符等信息。在Hello中,主要的上下文信息是通过函数参数 argc 和 argv 通报的。
历程时间片:
历程调理是操作系统的任务之一,它负责在多个历程之间切换执行,以便给用户提供交替执行的感觉。每个历程被分配一个时间片,体现它能够运行的时间。在时间片用完或历程主动让出CPU时,操作系统会进行历程切换。
历程调理的过程:
当步调运行时,操作系统为其分配一个历程控制块,其中包含有关历程的信息,例如寄存器状态、步调计数器等。
操作系统根据调理选择一个停当队列中的历程来运行,将CPU的控制权交给该历程。在Hello中,步调执行循环,打印问候信息并通过 sleep 函数暂停肯定的时间。在 sleep 期间,历程被置于阻塞状态,不占用CPU。当 sleep 结束后,
历程重新进入停当状态。
用户态与核心态转换:
当步调在用户态执行时,它只能执行受限的指令,不能直接访问系统资源。当必要执行特权指令时,必要进行用户态到核心态的转换。(例如本步调的sleep函数)

图31:历程上下文切换体现图


6.6 hello的非常与信号处置处罚


hello执行中可能出现的非常:
1:停止:用户在步调运行时按下Ctrl+C,产生停止信号,按下Ctrl+C会终止历程。可以通过注册信号处置处罚函数来捕捉并执行自定义操作。
2:陷阱:陷阱是故意的非常,是执行一条指令的结果,hello执行sleep函数的时候会出现这个非常。
3:故障:由错误引起,可能被故障处置处罚步调修正。在执行hello时,可能出现缺页故障。
4:终止:不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者 SRAM位被损坏时发生的奇偶错误。
接下来将展示一些常见下令:

  • 输入回车:

图32:输入回车



  • ctrl+c

图32:输入ctrl+c


  • ctrl+z

图33:输入ctrl+z


  • Ps 监视背景步调

图34:输入ps



  • 输入jobs表现当前暂停的历程

图35:输入jobs



  • 输入pstree 以树状图形式表现所有历程

图36:输入pstree



  • 输入fg 使停止的历程收到 SIGCONT信号 重新在前台运行

图37:输入fg


  • 输入kill -9体现给历程发送9号信号 即SIGKILL 杀死历程

图37:输入kill


6.7本章小结

这一章探讨了步调从可执行文件到历程的启动过程。了解了shell的工作原理及其在处置处罚下令行指令时的关键作用。接着研究了fork函数和execve函数的运作机制。其中,fork函数用于创建新的历程,而execve函数则答应一个历程更换其自身的执行映像为另一个新的步调。别的还探讨了上下文切换的机制,即在多任务处置处罚中,当一个任务暂时挂起以便另一个任务可以运行时,系统如何生存和恢复历程的状态。




7hello的存储管理


7.1 hello的存储器所在空间

逻辑所在:
逻辑所在是步调员在编程时利用的所在。它是相对于步调中某个特定段的偏移量或索引,通常由编译器天生。
线性所在:
线性所在是逻辑所在经太过段机制转换后得到的所在。在分段机制下,逻辑所在由两部分构成:段选择符和段内偏移量。线性所在是通过将段选择符乘以一个常数加上段内偏移量得到的。
虚拟所在:
虚拟所在是在步调运行时由操作系统分配的所在空间中利用的所在。它包罗逻辑所在和线性所在。
物理所在:
物理所在是在盘算机的内存硬件中实际存在的所在。操作系统通过利用页表或段表等数据结构将虚拟所在映射到物理所在。
在Hello代码中,主要关注的是逻辑所在和虚拟所在。步调员利用逻辑所在来引用下令行参数(argv[1] 和 argv[2]),而这些逻辑所在最终会被转换为虚拟所在,由操作系统进行管理。
7.2 Intel逻辑所在到线性所在的变更-段式管理

Intel x86 架构采取了分段的内存管理方式。在这种体系结构下,逻辑所在(到线性所在的变更涉及到段选择符、形貌符表以及段内偏移量。

图38:逻辑所在到线性所在的变更


以下是基本过程:
起首,由段标识符可以通过全局形貌符表(GDT)或局部形貌符表(LDT)获得段形貌符之后,从段形貌符中可以得到段基所在。
最后,段基所在与段内偏移两部分可以构成线性所在。
线性所在的形式即为[segment base address:offset]。
7.3 Hello的线性所在到物理所在的变更-页式管理

页式内存管理实现从线性所在到物理所在的映射,它把线性所在空间和物理所在空间分别划分为大小相同的块。如许的块称之为页。通过在线性所在空间的页与物理所在空间的页之间建立映射,实现线性所在到物理所在的转换。
具体过程如下图所示,起首线性所在在后可以分成虚拟页号(VPN)和虚拟页偏移量(VPO)两部分,而物理所在经太过页管理也分成物理页号(PPN)和物理页偏移量(PPO)两部分。其中MMU可以利用VPN找到对应的PPN,而VPO和PPO是相同的。故将找到的PPN与PPO联合就得到了物理所在。

图39:线性所在到物理所在的变更


7.4 TLB与四级页表支持下的VA到PA的变更

页式管理通过将线性所在映射到页目录表和页表,最终通过盘算物理所在来实现虚拟所在到物理所在的映射。这个过程中,必要访问多级页表结构,而每个页表项存储了对应的页框号。利用局部性原理,像缓存一样,将最近利用过的页表项专门缓存起来,之后找页表项的时候,先从缓存找,找不到在访问内存中的页表项。

图40:TLB与四级页表支持下的VA到PA的变更

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

在利用三级缓存支持的盘算机体系结构中,CPU核心起首查抄L1 Cache,然后L2 Cache,最后L3 Cache,以寻找所需的数据。假如在缓存中找到(命中),则加快访问;假如未命中,则从主内存加载数据到L3 Cache。这个多级缓存层次结构有效淘汰了主内存访问的耽误,提高了数据访问速度。同时,缓存更换和缓存一致性等机制保证了系统的精确性和性能。


图41: 三级Cache支持下的物理内存访问


7.6 hello历程fork时的内存映射

在fork创建子历程时,操作系统会通过内存映射机制复制父历程的所在空间到子历程中。这包罗代码段、数据段和堆,使得子历程拥有独立的内存空间。尽管父子历程共享相同的物理内存,但由于采取写时复制(Copy-On-Write)策略,只有在某个历程尝试修改内存时才会实际进行复制,从而优化内存利用效率。这种机制确保了父子历程在创建时共享相同的步调和数据,但在执行过程中各自独立修改本身的内存副本。
7.7 hello历程execve时的内存映射

在execve系统调用时,发生了历程的映像更换,新步调的二进制映像替代了原有的历程。这导致了全新的内存映射。原有历程的代码段、数据段、堆等内容都被新步调的对应部分所替代,形成了新的内存映射。与fork差别的是,这一过程保存了原有的文件形貌符表,但清空了原有历程的内存,为新步调的执行提供了独立的内存空间,实现了步调的更新和切换。
7.8 缺页故障与缺页停止处置处罚

缺页故障指的是当步调访问的页面不在当前内存中时发生的环境。当发生缺页故障时,操作系统会引发缺页停止。缺页停止的处置处罚过程包罗以下步骤:起首,操作系统查抄引发缺页停止的内存访问是否正当,假如是无效的内存访问,则终止历程。否则,操作系统从磁盘上获取所需的页面,将其载入到内存中,并更新页表以反映新的映射关系。最后,被停止的指令被重新执行,使步调能够继承执行。这一过程确保了步调在必要访问尚未加载到内存的页面时,能够通过停止处置处罚机制将所需页面加载到内存中并继承执行。

图42:缺页停止处置处罚


7.9动态存储分配管理

动态内存分配器维护着一个历程的虚拟内存区域,称为堆。系统之间细节差别,但是不失通用性,假设堆是一个哀求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长。对于每个历程,内核维护着一个变量,它指向堆的顶部。
分配器有两种基本风格:
(1)显式分配器:要求应用显式地开释任何分配的块,例如C标准库提供的malloc步调包。
(2)隐式分配器:要求分配器检测一个已分配块何时不再被步调所利用,那么就是放这个块,也被称为垃圾网络器。
7.10本章小结

在这一章中,我们探讨了与“hello”步调相关的存储管理。起首解释了如何通过段式管理和页式管理将“hello”步调中的逻辑所在转换为线性所在,并最终转化为物理所在。接下来讨论了“hello”历程在调用fork和execve函数时的内存映射环境。这有助于读者理解历程间的内存管理差异和动态存储分配的实现。最后探讨了“hello”步调如那边理缺页故障非常,以及如何实现动态存储分配管理。包罗步调如何有效地管理内存资源,以及在遇到非常环境时如何进行适当的处置处罚。



8hello的IO管理


8.1 Linux的IO设备管理方法

设备的模型化:文件
设备管理:unix io接口
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,答应Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数


Unix I/O(Input/Output)接口提供了一组函数,用于进行输入和输出操作。

1.open():打开文件并返回文件形貌符。
2.close():关闭文件形貌符。
3.read():从文件形貌符读取数据。
4.write():向文件形貌符写入数据。
5.lseek():设置文件形貌符的偏移量。
6.fcntl():对文件形貌符进行控制操作。
7.ioctl():设备控制。
8.pipe():创建管道。

8.3 printf的实现分析

printf 函数:
格式解析:printf 起首解析格式字符串,它包含平凡字符和转换说明符(例如 %d、%s)。
参数列表:printf 函数通过可变参数列表获取通报给它的参数。可变参数列表的声明利用 ... 体现。
格式化输出:根据格式字符串中的转换说明符,printf 将相应的参数格式化为字符串,并输出到标准输出流(通常是终端)。
vsprintf 函数:
格式解析:与 printf 雷同,vsprintf 也必要解析格式字符串。
参数列表:vsprintf 利用 va_list 类型的参数列表,它是一个用于处置处罚可变参数的数据结构。
格式化输出:与 printf 差别的是,vsprintf 将格式化后的字符串写入一个字符数组中,而不是输出到标准输出。因此,它必要接收一个字符数组作为参数,以及一个 va_list 参数列表。
可变参数处置处罚:通过 va_arg 宏从 va_list 中获取参数,然后按照格式字符串的说明符将其格式化为字符串。
8.4 getchar的实现分析
       getchar函数内部调用了read函数,通过系统调用read读取存储在键盘缓冲区的ASCII码,直到读到回车符才返回。不过read函数每次会把所有的内容读进缓冲区,假如缓冲区本来非空,则不会调用read函数,而是简简单单的返回缓冲区中最前面的元素
8.5本章小结

在这一章中探讨了Linux的I/O设备管理方法。起首先容Unix的IO接口及其相关函数,然后分析了printf函数和getchar函数的实现。printf函数用于格式化输出,getchar函数则用于从标准输入读取一个字符。





结论

Hello步调的生命周期可以总结如下:
1.源代码编写:起首,开发者利用C语言编写Hello步调的源代码,形成hello.c文件。这是步调生命周期的起始点。
2.预处置处罚:通过gcc -E下令,预处置处罚器对源代码进行初步处置处罚,将其转换为hello.i文件。这一阶段主要完成宏更换、条件编译等任务。
3.编译:在gcc -S下令下,编译器将预处置处罚后的代码转换为汇编语言,形成hello.s文件。
4.汇编:通过gcc -c下令,汇编器将hello.s文件转换为目标文件hello.o,这是一个二进制文件,但尚未完成所有的链接操作。
5.链接:链接器将hello.o文件与所需的库文件和其他可重定位目标文件联合,天生一个可执行文件。此时,步调已经预备幸亏盘算机上运行。
6.执行:用户在Shell中输入下令来运行步调。假如该下令不是Shell内置下令,Shell会将其解释为一个可执行文件的路径,并尝试执行它。
7.历程创建:假如必要,Shell会利用fork()系统调用创建一个新的历程来执行步调。
8.所在空间映射:新创建的历程通过execve()系统调用加载并执行Hello步调。该函数会更换当前历程的内存映像,并将Hello步调的代码和数据映射到历程的虚拟所在空间。
9.运行时环境:当Hello步调开始执行时,其所在空间由内存管理单元、TLB、多级页表机制以及三级缓存等硬件和软件组件共同管理,以确保所在精确翻译和数据访问。
10.输入与信号处置处罚:用户可以通过输入设备向运行中的Hello历程发送信号。例如,通过按下Ctrl-c,操作系统向历程发送SIGINT信号来停止其执行。
11.历程终止与回收:当Hello步调执行完毕或接收到终止信号时,其历程结束执行。假如该历程是fork()产生的子历程,父历程会负责回收已终止的子历程资源。




附件








参考文献


[1]《深入理解盘算机系统》Randal E. Bryant David R.O`Hallaron
[2]百度百科
[3]Shell简介:Bash的功能与解释过程(一) Shell简介
[4]段页式访存——逻辑所在到线性所在的转换
[5]printf函数实现的深入分析


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

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表