小秦哥 发表于 2024-8-26 07:25:30

步调人生-Hello’s P2P




https://i-blog.csdnimg.cn/blog_migrate/093c8cd5bacf9e2db6822b5dda3e175a.png

盘算机体系

大作业


题     目  步调人生-Hello’s P2P 
专       业     盘算机科学与技术     
学     号        2022113823        
班   级         2203103          
学       生          谢单瑞    
指 导 教 师          史先俊      






盘算机科学与技术学院
2023年4月
摘  要
本论文追踪并深入分析了一个名为hello.c源文件的完备生命周期,从创建、预处置惩罚、编译、汇编、链接,不停到最终作为可执行步调在操作体系中运行的整个过程。通过对盘算机在这一过程中与操作体系、处置惩罚器、内存、以及I/O设备等层面的交互进行细致的研究,本研究旨在更为全面理解盘算机体系底层原理。
在研究方法上,我们接纳了现实的hello步调作为案例,通过在Ubuntu情况下运行相应的命令,跟踪步调从源代码到可执行步调的转化过程,对每个阶段的结果进行深入解析。
本文详细探讨了盘算机体系在编译并运行hello步调时的内部交互过程,涉及了预处置惩罚的影响、编译过程的关键步调、汇编的输出结果、链接的作用以及最终步调执行的过程,通过对进程管理、存储管理和IO管理的研究,揭示了在操作体系层面的关键机制。

关键词:盘算机体系;预处置惩罚;编译;汇编;链接;可执行步调;进程;操作体系;处置惩罚器;内存;I/O设备。                        









目  录

第1章 概述
1.1 Hello简介
1.1.1 Hello的P2P过程
1.1.2 Hello的020过程
1.2 情况与工具
1.2.1 硬件情况
1.2.2 软件情况
1.2.3 开辟工具
1.3 中心结果
1.4 本章小结
第2章 预处置惩罚
2.1 预处置惩罚的概念与作用
2.2在Ubuntu下预处置惩罚的命令
2.3 Hello的预处置惩罚结果解析
2.4 本章小结
第3章 编译
3.1 编译的概念与作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1数据
3.3.1.1 常量
3.3.1.2 变量
3.3.1.3 表达式
3.3.1.4 类型
3.3.2赋值
3.3.2.1 变量的初值赋值
3.3.2.2 不赋初值操作
3.3.2.2逗号操作符(,)的利用
3.3.3算数操作
3.3.3.1 自增操作
3.3.3.2 加法操作
3.3.4关系操作
3.3.4.1 关系操作!=
3.3.4.2 关系操作<
3.3.5数组/指针操作
3.3.6控制转移
3.3.6.1 if判断
3.3.6.2 for循环
3.3.7 函数操作
3.3.7.1参数通报
3.3.7.2函数调用
3.3.7.3局部变量
3.3.7.4函数返回
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.3.1 ELF头
4.3.2 节头部表
4.3.3重定位节
4.3.3符号表
4.4 Hello.o的结果解析
4.5 本章小结
第5章 链接
5.1 链接的概念与作用
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
5.4 hello的虚拟地址空间
5.5 链接的重定位过程分析
5.6 hello的执行流程
5.7 Hello的动态链接分析
5.8 本章小结
第6章 hello进程管理
6.1 进程的概念与作用
6.2 简述壳Shell-bash的作用与处置惩罚流程
6.3 Hello的fork进程创建过程
6.4 Hello的execve过程
6.5 Hello的进程执行
6.6 hello的非常与信号处置惩罚
6.6.1 非常
6.6.2 信号
6.6.3 非常和信号处置惩罚
6.7本章小结
第7章 hello的存储管理
7.1 hello的存储器地址空间
7.2 Intel逻辑地址到线性地址的变换-段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
7.7 hello进程execve时的内存映射
7.8 缺页故障与缺页中断处置惩罚
7.9动态存储分配管理
7.10本章小结
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
8.1.1设备的模子化:文件
8.1.2设备管理:Unix IO接口
8.2 简述Unix IO接口及其函数
8.3 printf的实现分析
8.4 getchar的实现分析
8.5本章小结
结论
附件
参考文献


第1章 概述

1.1 Hello简介


[*]
[*]
[*] Hello的P2P过程


P2P(Program to Process)指的是hello.c由源步调到进程的过程。这其中颠末了预处置惩罚、编译、汇编、链接几个过程。这几个过程如下:
预处置惩罚:通过预处置惩罚器处置惩罚hello.c,包括宏展开和头文件包含等操作,生成预编译文件hello.i。
编译:编译器将hello.i编译成汇编代码hello.o。
汇编:汇编器将汇编代码hello.o转换为机器可执行的目标代码hello.s。
链接:链接器将目标代码与体系库链接,生成可执行文件Hello。
https://i-blog.csdnimg.cn/blog_migrate/9f4b433794ff7e327cc273ae74331559.png

[*]
[*]
[*] Hello的020过程


Hello的020过程(From Zero-0 to Zero-0)形貌了可执行文件Hello生成的进程从进入内存到被回收的完备过程。这包括了Bash中的fork和execve,进程管理、内存映射、存储管理、TLB和Cache技术的应用,以及IO管理和信号处置惩罚等步调。
Bash中的fork和execve:在Bash壳中,通过fork体系调用创建新进程,然后通过execve体系调用加载Hello的可执行文件,实现步调的执行。
进程管理:操作体系通过分配时间片的方式,允许Hello在CPU上执行,实现多使命处置惩罚。
内存映射:利用mmap体系调用进行内存映射,为步调提供所需的内存空间。
存储管理与MMU:操作体系与内存管理单元(MMU)合作,将虚拟地址(VA)翻译为物理地址(PA),实现存储管理。
加快技术:利用TLB、多级页表和Cache等技术,提高内存访问效率。
IO管理与信号处置惩罚:操作体系通过IO管理和信号处置惩罚机制,实现步调与外部设备的交互,包括键盘、主板、显卡、屏幕等。
步调执行与结束:Hello在CPU上按指令集执行,完成盘算使命,然后由父进程或养父进程对其进行回收。至此,Hello的进程的所有痕迹被从体系中删除。
1.2 情况与工具

列出你为编写本论文,折腾Hello的整个过程中,利用的软硬件情况,以及开辟与调试工具。
1.2.1 硬件情况

Intel x64CPU;16G RAM
1.2.2 软件情况

Windows11 64位
1.2.3 开辟工具

Vmware 17;Ubuntu 20.04 LTS 64位;Visual Studio 2022 64位;CodeBlocks 20.03 64位;vi/vim/gedit+gcc
1.3 中心结果

hello.c
hello 的C源文件
hello.i
hello.c颠末预处置惩罚后的预编译文件
hello_p.i
hello.c颠末加上-P参数的预处置惩罚的预编译文件
hello.s
hello.i颠末编译得到的汇编语言文本文件
hello.o
hello.s颠末汇编得到的可重定位目标文件
elf.txt
通过readelf导出的hello.o的elf信息文本
asm.txt
hello.o反汇编导出得到的文本
hello
hello.o颠末链接得到可执行文件
elf1.txt
通过readelf导出的hello的elf信息文本
asm1.txt
hello反汇编导出得到的文本
1.4 本章小结

在这一章中,我们详细介绍了Hello的P2P过程,从源步调到进程的演化过程,以及020过程,即可执行文件Hello生成的进程从进入内存到被回收的全过程。同时,我们提供了编写本论文所用到 的硬件与软件情况的形貌,以及利用的开辟工具。通过中心结果文件,我们展示了整个编译体系的流程。本章为后续章节奠基了理论和实践的基础。
第2章 预处置惩罚

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

预处置惩罚是编译过程中的第一个阶段,其重要目的是对源代码进行一系列文本替换和处置惩罚,生成预处置惩罚后的代码。这个阶段并不产生可执行代码,而是生成一个供编译器进一步处置惩罚的中心文件。预处置惩罚器根据以字符#开头的命令,修改原始的C步调。预处置惩罚的作用包括:
(1)宏替换: 处置惩罚源代码中的宏界说,将宏名称替换为其界说的文本。
(2)文件包含: 处置惩罚源代码中的#include指令,将被包含的文件的内容插入到源文件中。好比hello.c中#include<stdio.h>告诉预处置惩罚器读取体系头文件stdio.h的内容,并把它直接插入到步调文本中。
(3)条件编译: 处置惩罚源代码中的条件编译指令,如#ifdef#endif等等,根据条件判断是否编译特定部门的代码。
(4)注释处置惩罚: 删除源代码中的注释。
(5)去除空格和空行: 简化源代码,去除多余的空格和空行。
结果就得到另一个C步调,通常文件拓展名为.i。
2.2在Ubuntu下预处置惩罚的命令

以对hello.c进行预处置惩罚命令为例,输入命令:
gcc -E hello.c -o -hello.i
别的,可以添加-P参数,删除无用的信息:
gcc -E -P hello.c -o -hello_p.i
https://i-blog.csdnimg.cn/blog_migrate/48c6ad1f554b41113dda2839726bb2d9.png
2.3 Hello的预处置惩罚结果解析

输入预处置惩罚命令后,文件夹中出现了hello.i和hello_p.i文件。利用文本编辑器打开生成的hello.i和hello_p.i发现它仍是一个C语言的文本文件。在这里进行预处置惩罚的结果解析如下(以hello.i为例):
文件开头注释信息: hello.i 文件开头包含了各种注释信息,这些注释信息通常是来自于 hello.c 中包含的库文件的说明。
https://i-blog.csdnimg.cn/blog_migrate/1aa90a1aeee5b33039cc5a5efd4799a9.png
标准库头文件展开: hello.i 中心部门展开了标准库头文件 stdio.h、unistd.h、stdlib.h 的代码。这意味着预处置惩罚器将 #include 指令替换为相应头文件的现实内容。此过程对内部函数进行了声明,使得这些声明在源代码中可见。
https://i-blog.csdnimg.cn/blog_migrate/376605ca007e969df9d3b12f08d4602a.png
源代码包含: 文件结尾包含了 hello.c 中主函数的源代码。值得注意的是,这个阶段已经删除了源代码中的注释,这样的操作有助于简化代码,去除编译是无用的部门。
https://i-blog.csdnimg.cn/blog_migrate/55bfa10ab7abe9cd67ba5fa0d422b258.png
hello_p.i的比较:再打开hello_p.i,发现它相较hello.i,删去了所有的注释和多余的空行、空格,大大减小了文件的的巨细(从3061行减小到1204行),使得代码更加紧凑。
https://i-blog.csdnimg.cn/blog_migrate/d597d809ee207c8ae62ed0f52060b5db.png
总结得到,预处置惩罚的重要作用是在保留步调结构的同时,对源代码进行一系列的文本处置惩罚,使得编译器能够更有效地处置惩罚源代码,并且将库函数的声明插入到源代码中,确保编译器能够正确辨认和链接这些函数,为后续的编译步调做好准备。同时,通过删除注释和不须要的空格,去除代码中无用的内容。而对于宏替换和条件编译,hello.c中提供的代码进行预处置惩罚时并不能直接体现。
2.4 本章小结

在这一章中,我们深入研究了预处置惩罚的概念、作用以及在Ubuntu下的详细操作。预处置惩罚是编译过程中的关键步调,通过它我们可以对源代码进行各种处置惩罚,为后续的编译步调做好准备。

第3章 编译

3.1 编译的概念与作用

编译是将高级编程语言代码转换为盘算机可以执行的机器代码的过程。该过程涉及多个阶段,其中包括预处置惩罚、编译、汇编和链接。在这里,我们关注的是从预处置惩罚后的文件(.i)到生成汇编语言步调(.s)的编译过程。编译的作用重要有以下几点:
翻译源代码: 编译器将高级编程语言的源代码翻译成机器语言或汇编语言,使盘算机能够理解和执行。
优化性能: 编译器在编译过程中可以进行各种优化,以提高步调的执行效率和减小生成的可执行文件的巨细。
错误检查: 编译器会检查源代码中的语法和语义错误,并在编译过程中提供相应的错误信息,资助步调员找到和修复标题。
生成目标代码: 编译器最终生成目标代码,该代码可以由盘算机硬件执行,完成步调的功能。
3.2 在Ubuntu下编译的命令

利用命令 gcc -S hello.i -o hello.s对预处置惩罚文件hello.i进行编译,根据ppt中的要求添加–m64 –no-pie –fno-PIC参数,因此利用命令
gcc -S -m64 -no-pie -fno-PIC hello.i -o hello.s
https://i-blog.csdnimg.cn/blog_migrate/043ea252d713a2313178e0ecc27e1621.png
3.3 Hello的编译结果解析

这里我按照数据、赋值、类型转换、sizeof、算术操作、逻辑/位操作、关系操作、数组/指针/结构操作、控制转移、函数操作这些数据与操作来对hello.s进行解析。在这里,我先附上hello.c和hello.s中主函数的内容,方便底下进行比较解析。
https://i-blog.csdnimg.cn/blog_migrate/381b8effb4cb964cd5b3b8cfeaf34101.png
https://i-blog.csdnimg.cn/blog_migrate/62b1cf40d7ca865a03d14d7230c4ba7a.png
3.3.1数据

3.3.1.1 常量

(1)源步调中的字符串常量有”用法:Hello 学号 姓名 秒数!\n”和”Hello %s %s\n”。在hello.s中,可以找到LC0和LC1,分别是这两个字符串常量的标识符,.string后面即为字符串的内容。这些字符串常量存储在.rodata只读数据段,只能被访问,不可被修改。
https://i-blog.csdnimg.cn/blog_migrate/593be9b265940cb8fc6a9d83c9b8c246.png
(2)源步调中还有整型常量,好比13行if(argc!=4)中的4,这些常量被直接保存在.text代码段中。其他整型常量同理。
https://i-blog.csdnimg.cn/blog_migrate/de21e10ffa5548f87dd8d967515e0a4e.png
3.3.1.2 变量

int i为局部变量,这里的汇编语言可以看出其被储存在栈中,-4(%rbp)的位置。int argc和char *argv[]也是局部变量,储存在栈中,只在main函数中起作用。它们分别在 -20(%rbp) 和 -32(%rbp) 处分配空间。毕竟上,argc和argv被一开始储存在寄存器rdi和rsi中,这两个寄存器在进入main函数前被设置为正确的值,随后它们在主函数被调用后存储在栈中。局部变量所在的函数(主函数)返回时,其所占的栈空间也会被释放。
https://i-blog.csdnimg.cn/blog_migrate/2aeaa6413eeebf5f3b5635be05e05785.png
https://i-blog.csdnimg.cn/blog_migrate/8a17688c88eb9a76358ce4b6db26deab.png
3.3.1.3 表达式

源步调中出现的表达式有算数表达式、赋值表达式、关系表达式。这里在后文中进行详细阐述。
3.3.1.4 类型

(1)基本数据类型:
int 对应于寄存器 %edi 和 %eax,这些寄存器用于存储整数值。在x86-64架构中,%edi 通常用于函数参数和返回值,而 %eax 是通用寄存器之一。整数类型在寄存器中占据4个字节。指针类型:

(2)指针类型:
char * 对应寄存器 %rdi、%rsi、%rdx 等,这些寄存器用于存储指针和地址。在x86-64架构中,这些寄存器用于通报指针参数和存储指针变量。指针类型的巨细通常是8个字节,因为64位体系的地址空间是64位的。
char *argv[] 对应汇编中的 -32(%rbp),在这里存储了指向命令行参数的数组的指针。这个指针占据8个字节,因为它是64位指针。

(3)函数类型:
int main(int argc, char *argv[]) 对应汇编语言中的 main 标签。

可以看出,在汇编语言中,类型不再是C语言中的抽象概念,而是直接映射到底层硬件架构中的寄存器和内存位置。
https://i-blog.csdnimg.cn/blog_migrate/b5ee3d1c3e4335f5ece4fd7d6ce55911.png

3.3.2赋值

编译器利用数据传送指令)MOV类指令将值赋给地址。
https://i-blog.csdnimg.cn/blog_migrate/b87047b3c256a30d094171a956334057.png
3.3.2.1 变量的初值赋值

主函数的形参被赋初值。
movl %edi, -20(%rbp):将 %edi 中的值(argc,函数参数)移动到堆栈中的 -20(%rbp),这是一个局部变量的位置。
movq %rsi, -32(%rbp):将 %rsi 中的值(argv,函数参数)移动到堆栈中的 -32(%rbp),也是一个局部变量的位置。
3.3.2.2 不赋初值操作

局部变量i在主函数开始时被分配了栈空间,但没有被设置初值,在for循环开始时,它被赋值为0,通过语句:
movl $0, -4(%rbp)
3.3.2.2逗号操作符(,)的利用

在汇编语言中,逗号操作符并不是像C语言中那样表示序列点,而是将多个操作放在一行中执行。比方,在 addq $16, %rax 和 movq (%rax), %rdx 中,逗号分隔两个操作,表示它们是一个序列。
3.3.3算数操作

hello.c中出现的算数操作只有自增操作i++,利用add指令实现。
3.3.3.1 自增操作

在源步调的循环中,i每次通过i++自增1,在编译结果中体现为:
addl $1, -4(%rbp)
通过ADD指令将-4(%rbp)中的值加1。
3.3.3.2 加法操作

固然源步调中没有直接体现,但编译结果hello.s中出现了加法操作,同样利用add指令。
addq $16, %rax:加法操作,将16添加到 %rax 寄存器的值,用于对指针数组(argv[])的偏移以访问。
addq $8, %rax:加法操作,将8添加到 %rax 寄存器的值。
3.3.3.1 减法操作
hello.s中同样出现了减法操作,利用SUB指令。
subq $32, %rsp:这是一个减法操作,将%rsp寄存器的值减去32,用于在栈上分配32字节的空间。
https://i-blog.csdnimg.cn/blog_migrate/9b325add5eab126a3e91a144704e3a80.png
3.3.4关系操作

3.3.4.1 关系操作!=

源步调中if(argc!=4)出现了关系操作!=,在hello.s中体现为
cmpl $4, -20(%rbp)
je .L2
利用CMP对立刻数4和-20(%rbp)地址储存的值进行比较。CMP指令不会改变寄存器的值,而是根据比较结果设置标记寄存器的相应位。详细来说,它会设置零标记(ZF)、符号标记(SF)和溢出标记(OF)等。在这里,je 指令是"jump if equal"的缩写,它检查零标记(ZF),假如为真(表示相称),则跳转到.L2标签处。
3.3.4.2 关系操作<

在for循环中for(i=0;i<8;i++)代码对应为
cmpl $7, -4(%rbp)
jle .L4
这是另一次利用 CMP 指令,将立刻数7和存储在 -4(%rbp) 地址的值进行比较。同样,根据比较结果设置标记寄存器的相应位。jle 是 "jump if less than or equal" 的缩写,检查零标记(ZF)和符号标记(SF),假如它们表示小于或即是关系,则跳转到.L4标签处。
3.3.5数组/指针操作

步调中对数组和指针的操作体现于对字符串指针数组argv的访问。以获得argv中的字符串首地址为例。这个过程涉及到了对数组的访问,指针的偏移和解引用,用于在汇编层面操作字符串数组和指针。
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
64位Linux中指针占8个字节,-32(%rbp)中储存的是指针数组argv的首地址,赋值给%rax。随后通过ADD指令偏移8位,使得%rax中为-32(%rbp)+8,也就是argv的地址。(%rax)为解引用操作,获取指向地址的值,即为指针argv中储存的地址,也就是对应字符串的首地址。
3.3.6控制转移

步调中出现的控制转移有if、for。编译器利用jump指令进行跳转转移,一般为判断或循环进行分支操作时,由于不同的逻辑表达式结果导致步调执行不同的代码,编译器利用CMP指令更新条件码寄存器后,利用相应的jump指令跳转到存放对应代码的地址。

https://i-blog.csdnimg.cn/blog_migrate/965699cbef2e3339b06001df59034c79.png
cmp指令
https://i-blog.csdnimg.cn/blog_migrate/d20efd38f28ac5c5c6eb43cc34f25642.png
访问条件码
https://i-blog.csdnimg.cn/blog_migrate/b1a3bd53c3ac5a17a3a639bb420d8525.png

3.3.6.1 if判断

步调中if判断为if(argc!=4),对应的汇编语言:
cmpl $4, -20(%rbp)
je .L2
利用CMP对立刻数4和-20(%rbp)地址储存的值进行比较。CMP指令不会改变寄存器的值,而是根据比较结果设置标记寄存器的相应位。详细来说,它会设置零标记(ZF)、符号标记(SF)和溢出标记(OF)等。在这里,je 指令是"jump if equal"的缩写,它检查零标记(ZF),假如为真(表示相称),则跳转到.L2标签处。
3.3.6.2 for循环

在for循环中for(i=0;i<8;i++)代码对应为
cmpl $7, -4(%rbp)
jle .L4
将立刻数7和存储在 -4(%rbp) 地址的值进行比较。同样,根据比较结果设置标记寄存器的相应位。jle 是 "jump if less than or equal" 的缩写,检查零标记(ZF)和符号标记(SF),假如它们表示小于或即是关系,则跳转到.L4标签处。
3.3.7 函数操作

在函数调用的完备过程中,包括栈帧的创建、参数通报、函数执行以及返回值通报,最后是栈帧的销毁。
当步调执行到一个函数调用指令(通常是 call 指令)时,当前函数的返回地址(即下一条指令的地址)会被推入栈中。这样,当被调用函数执行完毕后,步调可以通过弹出栈中的返回地址来回到调用函数的正确位置。同时,被调用函数开始执行时,必要为该函数创建一个栈帧。栈帧是一个逻辑单元,其中包含了函数参数、局部变量以及用于保存寄存器状态的区域。
在栈帧中,参数可能存储在寄存器中或者被推入栈中,并且被调用函数通常会保存调用前的寄存器状态,以确保函数执行完毕后能够正确恢复调用前的状态。随后,在栈帧中分配空间来存储局部变量,这一过程通过调整栈指针 %rsp 来进行,用于动态分配和释放栈空间。
一旦栈帧设置完成,步调执行被调用函数的逻辑,包括对局部变量的操作和其他盘算过程。当函数执行完毕时,步调会通过 ret 指令从栈中弹出返回地址,跳回到调用函数的执行点。同时,函数的返回值通常存储在 %rax 寄存器中,调用函数可以通过寄存器或者其他方式获取返回值。最后,在函数返回后,栈帧被销毁,包括释放局部变量占用的栈空间和还原调用前的寄存器状态。
在这里,我们通过参数通报、函数调用、局部变量和函数返回四个部门结合代码进行进一步分析。
https://i-blog.csdnimg.cn/blog_migrate/ff06bccaa063d10dae849e27dc0f3c5d.png
3.3.7.1参数通报

64位Linux体系中,在函数调用之前,编译器将参数存储在寄存器中,以方便被调用的函数利用。当参数个数不大于6个时,按照优先级顺序利用寄存器通报参数:rdi, rsi, rdx, rcx, r8, r9。当参数个数大于6个时,前六个参数利用寄存器存放,而其他参数则被压入栈中进行存储。向主函数传参argc和argv已经在上面分析过,其他的,好比调用printf前:
movq -32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq -32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
movl $.LC1, %edi
movl $0, %eax
call printf
分别把字符串常量的地址$.LC1,argv和argv存进寄存器rdi(edi),rsi和rdx中,从而向printf通报这些参数。
3.3.7.2函数调用

步调利用指令 call调用函数。call的结果是将返回地址入栈,跳转到被调用过程的起始处,这样当被调用过程返回时,执行今后继续。被调用函数开始执行时,必要为该函数创建一个栈帧。
3.3.7.3局部变量

当函数开始执行时,会在栈上分配空间来存储局部变量。这些变量通常包括函数内部声明的临时变量和对象。通过调整栈指针%rsp来分配和释放这些局部变量所需的内存空间。好比:
subq $32, %rsp
3.3.7.4函数返回

步调利用汇编指令 ret 从调用的函数中返回。这个过程会还原栈帧,也就是将栈中保存的返回地址等信息恢复到正确的位置,以便步调能够继续执行。
返回值通常被存储在 %rax 寄存器中。通过 MOV指令 %rax 中,随后利用 leave 指令,这条指令将释放当前函数的栈帧,然后通过 ret 返回,将 %rax 中的值通报给调用函数,完成函数调用的整个过程。如hello.s中:
movl $0, %eax
leave
.cfi_def_cfa 7, 8
Ret
3.4 本章小结

在本章中,我们深入探讨了编译过程的各个方面,其中编译器的重要使命是将高级语言转化为汇编代码,为后续的机器语言转换做准备。


第4章 汇编

4.1 汇编的概念与作用

汇编是指汇编器将汇编代码翻译成机器语言二进制步调的过程。 汇编器将汇编代码转换成机器语言,并生成一个二进制可重定位目标文件(通常以.o为扩展名)。这个文件包含了机器指令的二进制表示,以及与步调中利用的符号相关的信息。其详细的作用有:
符号解析与地址分配: 汇编器对步调中利用的符号(如变量、函数名等)进行解析,并为这些符号分配内存地址。这一过程称为地址分配,它将步调中的符号映射到目标文件中的详细地址。
生成重定位信息: 汇编器生成重定位信息,以便在链接阶段能够调整目标文件中的地址。这是为了适应最终可执行文件的布局,因为在链接时,多个目标文件可能会被组合成一个可执行文件。
处置惩罚伪操作和指令: 汇编器不但处置惩罚汇编代码中的机器指令,还处置惩罚伪操作(pseudo-operations)和指令,这些是用来控制步调汇编过程的指令,比方界说常量、分配内存空间等。
错误检测和报告: 汇编过程中,汇编器通常会检测并报告代码中的语法错误或者其他潜在标题,以协助步调员进行调试。
4.2 在Ubuntu下汇编的命令

利用gcc -c hello.s -o hello.o命令
https://i-blog.csdnimg.cn/blog_migrate/1e587a76c284571d1428468c1917fab7.png
4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
可重定位目标文件(Relocatable Object File),这种类型的目标文件包含了编译器生成的二进制代码,但其内部地址(如函数和变量的地址)仍然是相对的,而非绝对的。这意味着它们可以被链接到其他目标文件或者库中形成可执行文件,但单独运行它们并不能产生可执行步调。
ELF头(ELF header)以一个16字节的序列开始。这个序列形貌了字的巨细和生成该文件的字节顺序。ELF头剩下的部门包含资助链接器解析和解释目标文件的信息。包括ELF头的巨细、目标文件的类型(可重定位、可执行或者共享的)、机器类型(IA32等)、节头部表(section header table)的文件偏移,以及节头部表中的表目巨细和数量。不同节的位置是由节头部表形貌的,其中目标文件的每个节都有一个固定的表目(entry)。
夹在ELF头和节头部表之间的都是节。
https://i-blog.csdnimg.cn/blog_migrate/d3990f9b674ae9a95de40ee296ed1954.png
在这里,我们通过readelf -a hello.o > ./elf.txt命令生成包含ELF所有可用的信息的文本,包括ELF头、节头部表、步调头、符号表、重定位表等。我们将在底下进行详细分析。
https://i-blog.csdnimg.cn/blog_migrate/c986cbc976bf40a9c2d1c7b50b4d5ed8.png
4.3.1 ELF头

https://i-blog.csdnimg.cn/blog_migrate/c6746db90041783e88632faeedbfbbea.png
ELF头包含了关于可执行文件或可链接文件的重要信息。通过其我们可以看出文件按照小端序,是一个64位的可重定位文件,针对AMD x86-64架构,运行在UNIX - System V操作体系上。因为它不是可运行的步调,所以步调的虚拟地址入口点为0,也没有步调头表。节头开始处为第1192字节,文件头占64个字节。它有14个节头,字符串表索引为13。
4.3.2 节头部表

https://i-blog.csdnimg.cn/blog_migrate/2e9c1cb818b7a1a0401372832d237214.png
节头部表形貌了hello.o目标文件中各个节的类型(如.text)、偏移量、巨细、旗标、链接、对齐等信息。值得注意的是,.text 和 .data 是步调的代码段和数据段,而 .rela.text 和 .rela.data 则是与重定位相关的节,用于指导链接器在步调加载时修正代码和数据的地址,.eh_frame同理。详细来说,.data储存已初始化的全局变量,而.rela.data则是被模块界说或引用的任何全局变量的信息。用.data举例,在之后链接过程中,链接器会根据.rela.data中的信息,调整.data段中的数据的地址,以适应整个步调的内存布局。不过,hello步调中并没有这样的全局变量,因此节头部表中没有.rela.data节,但有.rela.text,意味着.text在之后是会根据.rela.data调整地址的。而没有对应.rela节的节则意味着该节在链接时不必要重定位。
4.3.3重定位节

https://i-blog.csdnimg.cn/blog_migrate/1e51cc3becb6506c491c3d907b58f9fd.png
在链接器处置惩罚目标文件 hello.o 时,它必要通过重定位来调整一些引用外部符号的位置,确保这些引用在最终的可执行文件中能够正确地映射到相应的地址。通过查看重定位节,我们可以发现hello.o中必要重定位的包括:
.rodata 中的模式串:假如 .rodata 包含了对其他模块或库中界说的常量字符串的引用,链接器必要通过重定位来调整这些引用,以便正确地定位这些字符串的地址。
puts、exit、printf、sleep、getchar 等符号:这些是外部符号,也就是在 hello.o 中引用但是在当前目标文件中未界说的符号。链接器必要通过重定位来确定这些符号在最终可执行文件或共享库中的正确地址。
4.3.3符号表

https://i-blog.csdnimg.cn/blog_migrate/48da807df905ec86918315f65ad245b1.png
符号表对应.symtab节,包含可重定位模块界说和引用的符号的信息:
第一列 (Num) 是符号表中条目的编号。第二列 (Value) 是符号的值或地址。对于未界说的符号,值通常为 0。第三列 (Size) 是符号占据的空间巨细。第四列 (Type) 形貌了符号的类型(如 FUNC、NOTYPE 等)。第五列 (Bind) 表示符号的绑定类型(好比 GLOBAL、LOCAL)。第六列 (Vis) 是符号的可见性。第七列 (Ndx) 是符号相对应的节(section)的索引。第八列 (Name) 是符号的名称。
现在来解释这些条目:
第一个条目 (Num: 0) 是一个未界说且没有类型的当地符号。
第二个条目 (Num: 1) 是一个当地文件符号,对应源文件 hello.c。
接下来的几个条目 (Num: 2 到 Num: 9) 是未界说的当地符号,与各个节(section)相关联。
其余条目 (Num: 10 到 Num: 16) 是全局的未界说符号,它们与一些函数(如 main)或外部库函数(如 puts、exit、printf、atoi、sleep、getchar)相关联。
对于这些条目,可以看到 main 函数被界说为一个全局函数,并且其他符号(如 puts、exit、printf、atoi、sleep、getchar)是未界说的全局符号,它们必要在链接时与其他目标文件或库文件中的界说进行解析和关联。
4.4 Hello.o的结果解析

利用objdump -d -r hello.o 命令分析hello.o的反汇编,在这里,我们用
objdump -d -r hello.o > asm.txt
将反汇编写进文本文件并查看。说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
https://i-blog.csdnimg.cn/blog_migrate/fb4e24284b446995603fd2536e63027f.png
https://i-blog.csdnimg.cn/blog_migrate/ef44ad76fa536ed5faa5770bcc5674dc.png
通过比较hello.o的反汇编文本与之前hello.s中的汇编语言,发现机器码对应的仍然是汇编语言的形式,这与hello.s基本有着相同的结构和内容,而也存在一些使其不同的映射关系,重要有操作数、分值转移和函数调用上的不一致,详细如下:

(1)操作数:
hello.s中的操作数为十进制,而hello.o反汇编代码的操作数都为十六进制。好比movq -32(%rbp), %rax指令在机器码中就变为了:
41: 48 8b 45 e0           mov    -0x20(%rbp),%rax
表示了相同的指令。
(2)控制转移:
在hello.s中,控制转移利用了助记符,好比下面这个例子中的.L2,
cmpl $4, -20(%rbp)
je .L2
movl $.LC0, %edi
call puts
movl $1, %edi
call exit
.L2:
在反汇编中,则利用的是目标代码的虚拟地址,像这里的0x2d,其实是相对于main的main+2d的地址,在链接之后会变为正确的地址。
17: 74 14                 je     2d <main+0x2d>
  19: bf 00 00 00 00        mov    $0x0,%edi
1a: R_X86_64_32 .rodata
  1e: e8 00 00 00 00        callq  23 <main+0x23>
1f: R_X86_64_PLT32 puts-0x4
  23: bf 01 00 00 00        mov    $0x1,%edi
  28: e8 00 00 00 00        callq  2d <main+0x2d>
29: R_X86_64_PLT32 exit-0x4
  2d: c7 45 fc 00 00 00 00 movl   $0x0,-0x4(%rbp)


[*]函数调用:
以反汇编代码中调用puts的代码为例
  1e: e8 00 00 00 00        callq  23 <main+0x23>
1f: R_X86_64_PLT32 puts-0x4
  23: bf 01 00 00 00        mov    $0x1,%edi
在汇编代码中,函数调用利用函数名进行标识。而在反汇编后的机器码中,通常利用的是与被调用函数相关的重定位类型(relocation type),好比 R_X86_64_PLT32,这个类型告诉链接器在链接时如那边理这个调用的地址修正,以便正确地调用函数,在上文提到的重定位接.rela.text也可以找到对应的信息:
00000000001f 000b00000004 R_X86_64_PLT32 0000000000000000 puts - 4
1e: e8 00 00 00 00: 这是 callq 指令,表示调用一个函数。后面的 00 00 00 00 是相对于下一条指令的偏移量,表示调用的目标地址是当前指令后面的地址。这个指令的目的是跳转到一个函数。
1f: R_X86_64_PLT32 puts-0x4: 这是重定位信息,指定了链接器在链接时要对 puts 函数地址进行修正。R_X86_64_PLT32 表示修正的方式,而 puts-0x4 表示修正的目标,即 puts 函数的地址减去 0x4。这是由于在 x86-64 架构中,函数调用通过 PLT 进行,而 PLT 中的条目通常是一个跳转指令,指向真正的函数实现。
4.5 本章小结

本章深入探讨了汇编语言及其转化为机器语言的过程。我们了解了如何通过汇编器将hello.s文件转换为hello.o可重定位目标文件。在此过程中,我们详细研究了可重定位目标文件的 ELF 格式,涉及到了readelf命令、elf头、节头部表、重定位节和符号表等重要概念。通过比较分析hello.s和hello.o,我们剖析了汇编语言转化为机器语言的变革过程。我们将在后续将hello.o其他可重定位目标文件链接,就可以得到可执行的步调了。

第5章 链接

5.1 链接的概念与作用

链接是将目标代码与其他目标代码或库文件归并,生成最终的可执行文件的过程。在这一步,编译器将步调中用到的函数和变量的引用解析为现实的地址。在这个过程中,所有的符号引用被解析为现实的内存地址,从而使得步调能够正确地执行。
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
https://i-blog.csdnimg.cn/blog_migrate/62a559ec7dfe75bcb3c39946cb2dfa63.png
5.3 可执行目标文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,巨细等信息。
hello的ELF格式如图:
https://i-blog.csdnimg.cn/blog_migrate/cab670dbc3b36b21b7fd59c6d4b4fddf.png
利用命令readelf -a hello > ./elf1.txt将hello的elf信息导出到文本。
https://i-blog.csdnimg.cn/blog_migrate/a92e5bbc457a1d7f37d7ab0f591daad4.png
https://i-blog.csdnimg.cn/blog_migrate/a8f1bf850a7f68581ec39da2f9d6bef1.png
在步调头部表中,可以看到各段的基本信息,包括各段的起始地址,巨细等信息。好比02号LOAD段,可以得知它的信息为:
偏移 (Offset): 0x0000000000001000:在文件中的偏移位置。
虚拟地址 (VirtAddr): 0x0000000000401000:段在内存中的起始地址。
物理地址 (PhysAddr): 0x0000000000401000:假如有物理地址的话,这里形貌了段在物理内存中的起始地址(在大多数情况下,虚拟地址和物理地址是相同的)。
文件巨细 (FileSiz): 0x0000000000000241:在文件中占据的巨细。
内存巨细 (MemSiz): 0x0000000000000241:在内存中占据的巨细。
标记 (Flags): R E (Read Execute):表示这个段可读和可执行。
对齐 (Align): 0x1000:对齐要求为 4096 字节。
同样的可以获得其他段的信息。
5.4 hello的虚拟地址空间

利用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
在这里,继续以在5.3中分析过得02号LOAD段举例,虚拟地址0x401000,巨细0x241,对齐0x1000,因此在0x401000到0x401241中有信息,再到0x402000都是空。查看edb中的Data Dump,发现确实云云:
https://i-blog.csdnimg.cn/blog_migrate/4225e206f387a4b1ad2cb4dcd99dd548.png
5.5 链接的重定位过程分析

利用objdump -d -r hello >asm1.txt将hello反汇编并写入文本。
https://i-blog.csdnimg.cn/blog_migrate/1499e62477e409cb2f51fe08d4720a51.png
得到如下的反汇编:
https://i-blog.csdnimg.cn/blog_migrate/8f8df9f180f172c4963d61e9f9bb3c42.png
https://i-blog.csdnimg.cn/blog_migrate/668cc61b09becd12b4b5dbaf7688ebb8.png
比较其与hello.o的反汇编,发现hello 与 hello.o 的不同重要体现在两个方面:可执行步调中包含了额外的代码和重定位信息。进而可以分析链接的过程。
额外的代码:
hello 中包含了完备的步调入口、库函数调用以及相关的启动和退出代码。这些部门并不在 hello.o 中,因为 hello.o 只是一个目标文件,它包含了编译后的代码但并未链接成一个可执行文件。
在 hello.o 的反汇编中,可以看到步调入口处(main 函数)的地址是 0x0000000000000000,而在 hello 的反汇编中,步调入口处的地址是 0x00000000004011a5。这是因为在链接的过程中,启动代码等额外的代码被添加到了可执行文件中。
重定位信息:
hello.o 中包含了一些重定位信息,如 R_X86_64_32 和 R_X86_64_PLT32。这些信息指示链接器在链接时必要进行地址的修正。比方,1a: R_X86_64_32.rodata 表示这是一个对 .rodata 段的相对地址,必要在链接时被修正。
在 hello 中,这些相对地址已经被修正为详细的绝对地址。比方,1f: R_X86_64_PLT32 puts-0x4 表示在链接时必要修正 puts 函数的地址。在 hello 的反汇编中,原本的相对地址被修正,可以看到详细的地址401090 <puts@plt>。
链接和重定位的过程:
链接器首先会扫描hello.o中的重定位项目,辨认必要解析的符号引用。这些符号引用可能是函数或变量的名称,它们在hello.o中是未解析的。
对于每个符号引用,链接器会在其他目标文件或库文件中查找相应的界说。假如在其他地方找到了相应的界说,链接器将符号引用与这些界说关联起来。这个过程确保在整个步调中,每个符号都有一个唯一的界说。
一旦所有符号引用都得到解析和关联,链接器将对hello.o中的代码段和数据段进行地址重定位。重定位涉及调整指令中的地址或数据的偏移量,以便正确地指向关联的符号的地址。链接器会根据重定位项目中的信息,盘算出相应的偏移量,并修改hello中的机器代码。
最终,链接器生成一个可执行文件(hello),其中包含了颠末重定位的代码和数据。这个可执行文件可以在盘算机上运行,因为所有的符号引用都已经被正确地解析和关联,地址也已经被正确地调整。
5.6 hello的执行流程

步调开始到结束中调用的函数可以在gdb中查看:
https://i-blog.csdnimg.cn/blog_migrate/ae9436eecab334729df0e90836f27379.png
在edb中对hello执行进行渐渐跟踪:
https://i-blog.csdnimg.cn/blog_migrate/dce127f9b6df15cc32bbbd2b155bc0b7.png
得出hello的执行流程包含三个重要阶段:载入、执行和退出。
执行过程:
一旦载入完成,步调开始执行,控制权转移到步调的入口点 _start。_start 包含了一些启动代码,可能包括栈的设置、寄存器的初始化等。_start 可能会调用一些初始化函数,如 _init,用于执行一些全局变量的初始化工作。
接着,控制流程跳转到步调的 main 函数,这是步调的重要逻辑入口。在 main 函数中,步调执行详细的业务逻辑,可能会调用其他函数,包括库函数和自界说函数。在调用其他函数时,控制权会转移到相应函数的入口点,执行该函数的代码。这个阶段涉及到函数调用、栈的利用、寄存器的保存和恢复等操作。
退出过程:
当 main 函数执行完毕或通过 exit 函数退出时,步调开始进行退出过程。在退出过程中,可能会调用一些终止函数,如 _fini,用于执行一些清理工作。最终,步调将控制权交还给操作体系,操作体系负责回收步调占用的资源,并结束步调的执行。
5.7 Hello的动态链接分析

步调的动态链接机制,特别是涉及到了 Global Offset Table(GOT)和 Procedure Linkage Table(PLT)。这是一种在步调运行时进行函数动态链接的机制,通常用于共享库的调用。GOT 存放的是全局偏移表,包含函数的地址或者指向 PLT 的入口。PLT 存放的是一系列跳转指令,每个指令对应一个函数的调用。
PLT 的初始条目通常包含两条指令:一条用于懒加载,另一条用于调用链接器。调用函数时,步调首先执行 PLT 表的第一条指令,该指令跳转到 PLT 的懒加载部门。懒加载部门负责调用链接器,链接器会更新 GOT 表的对应条目,使其指向正确的函数地址。下一次调用同一函数时,执行 PLT 表的第二条指令,该指令直接跳转到函数的地址。
GOT 表的初始值通常是 PLT 表的第二条指令,即进行懒加载的指令。链接器在懒加载时会更新 GOT 表的对应条目,使其指向正确的函数地址。
在步调运行时,动态链接器根据必要加载共享库,并更新 GOT 表中的条目。动态链接器通过重定位信息,找到函数的真正地址,并更新相应的 GOT 表项。
在这里,我们通过elf找到地址0x404000,并在edb中查看:
https://i-blog.csdnimg.cn/blog_migrate/0c0f6fb82c1a1882705a7a5e4b3b4597.png
这是步调刚开始时:
https://i-blog.csdnimg.cn/blog_migrate/59bcca9369b32144b60821d177965657.png
步调运行到main函数时,已经在dl_init后,GOT表的对应条目被改变。
https://i-blog.csdnimg.cn/blog_migrate/5333b19cc64b6b7e98dc284e0f870d4f.png
5.8 本章小结

在这一章中,我们深入研究了链接的过程,重点关注了hello步调的链接过程。我们了解了链接是将代码和数据片段组合成一个可执行文件的过程,该文件可以加载到内存并执行。通过探讨链接器对hello.o的操作、ELF可执行文件的查看以及相关信息,我们加深了对链接过程细节的理解。


第6章 hello进程管理

6.1 进程的概念与作用

进程是盘算机中运行的步调的实例。每个进程都有自己的地址空间、数据栈以及控制信息,它是体系进行资源分配和调度的基本单元。进程之间相互独立,通过进程间通讯机制来实现信息的交换与共享。进程的重要作用包括:
资源分配与管理: 操作体系通过进程来管理盘算机的资源,如内存、CPU时间片、文件等,确保它们得到合理的分配和利用。
并发执行: 进程使得多个使命可以并发执行,提高了体系的吞吐量和相应速率。
隔离性: 每个进程有独立的地址空间,不同进程之间不会相互影响,确保了体系的稳固性和安全性。
进程间通讯: 进程可以通过各种进程间通讯的方式进行信息的交换,如管道、消息队列、共享内存等。
6.2 简述壳Shell-bash的作用与处置惩罚流程

Shell是用户与操作体系之间的接口,而bash是一种常见的Unix/Linux Shell。其重要作用包括它提供了用户与操作体系交互的界面,用户通过Shell输入命令,Shell将其解释并通报给操作体系执行。并且支持脚本编程,用户可以将一系列命令组合成脚本文件,以便批量执行。它还可以用于设置用户的情况,包括情况变量、别名等。
Shell的处置惩罚流程通常包括:
用户输入命令: 用户在Shell中输入命令。
命令解析: Shell解析用户输入的命令,分析命令的结构和含义。
内置命令执行: 内置命令是Shell内部实现的一些命令,比方cd、echo等,它们直接由Shell执行,不必要调用外部步调。因此,Shell会直接解析并执行这些命令,而不是创建子进程。
外部命令执行: 当输入的命令不是内置命令时,Shell会实验在体系路径中寻找对应的可执行文件。假如找到,Shell会创建一个子进程,并在该子进程中执行这个外部步调。
键盘输入信号处置惩罚: Shell不但必要解释和执行命令,还必要处置惩罚用户输入,包括接收来自键盘的输入信号。它会监听并处置惩罚诸如Ctrl+C(中断)、Ctrl+Z(停息)、Ctrl+D(文件结束符)等特殊的键盘输入信号。这些信号可能导致Shell中断当前操作、停息步调执行或退出Shell等不同的举动。
6.3 Hello的fork进程创建过程

(1)Shell Bash 解析命令
当在 Shell 中输入 ./hello 并按下回车时,Shell 首先会解析这个命令。这涉及到查找命令的路径、确定其是否为可执行文件等。
(2)利用 fork() 创建子进程
Shell 通过调用 fork() 体系调用来创建一个新的进程。fork() 创建一个与当前进程(称为父进程)险些完全相同的新进程(称为子进程)。
父进程:这是执行 fork() 调用的原始 Shell 进程。
子进程:这是 fork() 创建的新进程。它险些复制了父进程的所有内容,包括代码、数据、堆、栈等。
(3)子进程的特点
PID(进程标识符):子进程有一个唯一的 PID,与父进程不同。
返回值:在父进程中,fork() 返回子进程的 PID;在子进程中,fork() 返回 0。
(4)父进程的举动
与此同时,父进程(Shell)可能会等待子进程完成,这通常是通过调用 wait() 或 waitpid() 实现的,取决于 Shell 的详细实现。
6.4 Hello的execve过程

execve() 是 Unix-like 体系中步调执行的核心。它不但仅是加载和运行一个新步调,而且是完全替换当前进程的内存空间,包括代码和数据,从而实现从一个步调到另一个步调的无缝切换。
(1)execve() 体系调用
在 fork() 创建了一个子进程之后,子进程通常会执行 execve() 体系调用来运行新的步调。这个新步调就是 hello。
(2)参数
execve() 接受以下参数:
路径:必要执行的步调的路径。在这个例子中,它可能是 ./hello。
参数数组:通报给新步调的参数数组。这个数组包括步调名和后续的命令行参数。
情况变量数组:新步调的情况变量。
(3)替换进程映像
execve() 会替换当前进程的映像,包括代码、数据、堆和栈等,用 hello 步调的相应部门替换。这意味着子进程的原始步调完全被新步调取代。
(4)运行新步调
一旦 execve() 成功,子进程开始运行新的步调(hello)。此时,它的进程标识符(PID)保持稳定,但进程的内部内容已经完全变成了 hello 步调。
(5)处置惩罚失败
假如 execve() 调用失败(比方,由于无法找到指定的文件),它将返回错误,并且子进程继续执行后续的代码。通常,在现实的 Shell 脚本或步调中,假如 execve() 失败,子进程会立刻退出。
6.5 Hello的进程执行

当我们讨论hello的进程执行过程时,我们必要考虑多个方面,包括进程上下文、进程调度、时间片分配,以及用户态与核心态之间的转换。
(1)进程上下文
进程上下文包括所有界说进程状态的信息,如寄存器值、步调计数器、堆栈指针、内存分配、打开的文件形貌符、情况变量、进程优先级等。当操作体系切换到一个新的进程时,它保存当前进程的上下文,并加载新进程的上下文。
https://i-blog.csdnimg.cn/blog_migrate/41b16b71acb394a8a16bfe60825dd1b6.png
(2)进程调度与时间片
操作体系的调度器负责决定哪个进程获得 CPU 时间。在多使命操作体系中,每个进程通常被分配一个固定长度的时间片,这是它可以运行的时间。
时间片分配:时间片通常非常短(如几毫秒),允许体系在多个进程之间迅速切换,给用户一种多个步调同时运行的感觉。
调度算法:操作体系利用调度算法(如轮转、最短作业优先、多级队列等)来决定哪个进程下一个获得 CPU 时间。
调度过程:当 "Hello" 步调获得调度时,体系将其上下文加载到 CPU 中。当它的时间片用完或者它等待 I/O 操作时,调度器选择另一个进程运行,并保存 "Hello" 的当前状态。
(3)用户态与核心态转换
进程在用户态和核心态(内核态)之间切换:
用户态:进程在用户态执行其正常的操作,如运行步调代码。
核心态:当进程必要执行某些只能由操作体系内核执行的操作时,好比 I/O 操作、创建或销毁进程等,它会进入核心态。
体系调用:用户态到核心态的转换通常通过体系调用实现。比方,假如 "Hello" 步调必要读取文件,它会执行一个体系调用,这会触发到核心态的切换。
核心态执行:在核心态,内核执行所需的操作,然后将控制权返回给用户态的进程。
安全性:这种机制是出于安全和稳固性的考虑。用户态进程被限制访问关键的体系资源,只有内核态才有完全的访问权限。
6.6 hello的非常与信号处置惩罚

6.6.1 非常

 hello执行过程中会出现的非常可分类为中断、陷阱、故障、终止。
https://i-blog.csdnimg.cn/blog_migrate/81ecdc39a0612b225ed10106ba8152f9.png
(1)中断(Interrupts)
中断是由硬件产生的信号,用于关照处置惩罚器发生了必要立刻处置惩罚的事件。它通常是异步的,与步调的当前执行无关。
例子:
外部中断:如键盘输入(比方用户按下Ctrl-C生成的SIGINT信号)。
处置惩罚:操作体系接收到中断信号后,会停息当前步调的执行,处置惩罚中断请求(如调用信号处置惩罚函数),然后再恢复步调的执行。
https://i-blog.csdnimg.cn/blog_migrate/f6f0f11f57d0f0203a71da1f279ecbda.png
(2)陷阱(Traps)
陷阱是一种由步调指令显式生成的同步中断,用于调试、体系调用等。
例子:
体系调用:如步调请求操作体系服务(好比读写文件操作)。
调试陷阱:步调执行到一个断点时。
处置惩罚:步调执行到陷阱指令时,控制权转移到操作体系,完成相应的处置惩罚后返回步调执行。
https://i-blog.csdnimg.cn/blog_migrate/299955c20d34dbc5d1245ad109e38847.png
(3)故障(Faults)
故障是由于步调错误或非常情况而产生的同步中断。它们通常是可恢复的,步调可以在处置惩罚故障后继续执行。
例子:
页错误:当步调访问的内存页面不在物理内存中时发生。
浮点非常:执行非法的浮点运算时发生。
处置惩罚:操作体系试图纠正故障(如加载所需的内存页)。假如可以纠正,步调继续执行;假如不可恢复,则步调终止。
https://i-blog.csdnimg.cn/blog_migrate/97bedbab8e8790d97a58981535e5f440.png
(4)终止(Aborts)
终止是一种严峻的故障,表示步调遇到了无法恢复的错误,无法继续执行。
例子:
段错误:步调试图访问无权限的内存区域。
非法指令:步调实验执行无效或未界说的机器指令。
处置惩罚:操作体系通常会终止步调,有时会产生核心转储文件以便进行调试。
https://i-blog.csdnimg.cn/blog_migrate/08642e9f90c2bdd45c7f123c303fadd1.png
6.6.2 信号

一个信号就是一条小消息,它关照进程体系中发生了一个某种类型的事件。每种信号类型都对应于某种体系事件。低层的硬件非常是由内核非常处置惩罚步调处置惩罚的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,关照用户进程发生了一些非常。
信号的种类有:
https://i-blog.csdnimg.cn/blog_migrate/4d44aeed0786752fa80616be1ddd203f.png
6.6.3 非常和信号处置惩罚

(1)Ctrl-Z
https://i-blog.csdnimg.cn/blog_migrate/d2989be5b40be8b904abcc5a27033f35.png
运行hello后按下Ctrl-Z,使得SIGTSTP信号被发送给(?),进程挂起。

[*]Ctrl-C
进程收到 SIGINT 信号,结束 hello(这里创建了三个hello,结束的是1号)。通过jobs看不到作业1,可以看出hello已经被彻底结束。
https://i-blog.csdnimg.cn/blog_migrate/3582cf24c9de10a530bc2bf16b4e2c38.png

[*]jobs
利用jobs命令查看当前终端的作业信息,显示进程状态及对应编号。
(4)ps
通过ps t命令查看终端进程状态,T表示已停止,也可显示进程的PID。
https://i-blog.csdnimg.cn/blog_migrate/f1a63e9bc9e311c5a993c0bf34bc5793.png
(5)pstree
可以查看所有进程树状图显示。
https://i-blog.csdnimg.cn/blog_migrate/5769a19117bb419fb06beef535fd8e74.png
https://i-blog.csdnimg.cn/blog_migrate/d889b418e2498b23f88b0bec7bd34aee.png
这里可以看到bash下的hello进程
(6)fg
通过fg %1(或者hello的pid)把被挂起的一号作业(hello)转为前台运行。
https://i-blog.csdnimg.cn/blog_migrate/c626725067beaa2e94802a68b60682cf.png
(7)kill
利用kill -9 %1给1号作业发送SIGKILL信号杀死步调;
利用kill -18 %1给1号作业发送SIGCONT使其继续执行。
也可以对pid进行这样的操作。
https://i-blog.csdnimg.cn/blog_migrate/30bcf50cdb3ab7baeca0014d5f20c201.png
在这里输入kill -9 -1的话虚拟机直接重启了!
(7)乱按
https://i-blog.csdnimg.cn/blog_migrate/3abd99051b17e19f5f553908cc511b6d.png
回车和空格被忽略,乱按输入的字母被认为是命令,但是没有找到相应的命令,没有执行。这种输入方式(假如不是乱按到Ctrl-C这些)不会触发体系相应。
6.7本章小结

本章详细探讨了hello进程的执行过程,从内核调度到非常处置惩罚,再到信号处置惩罚。进程作为盘算机体系中的执行单元,履历了创建、加载和执行的阶段。非常控制流涉及硬件层到操作体系级别的处置惩罚,而不同信号触发不同的处置惩罚机制。

第7章 hello的存储管理

7.1 hello的存储器地址空间

(1)逻辑地址:
逻辑地址,是由步调产生的与段相关的偏移地址。对应hello.o中的地址内容。
(2)线性地址:
假如一个地址空间中的非负整数是一连的,那我们说它是一个线性地址空间。hello中的偏移地址加上相应段的基地址就是线性地址,也就是逻辑地址颠末段式管理转换为线性地址。假如启动了分页机制,线性地址通过变换变为物理地址。若没有,则线性直接物理地址。
(3)虚拟地址:
在一个带虚拟内存的体系中,CPU从一个有N=2^n个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间。hello在运行时,操作体系为其提供了虚拟地址空间,其中包括代码、数据和堆栈等。在虚拟地址空间中,步调可以利用逻辑地址,而这些地址最终会通过操作体系和 MMU 转换成物理地址。
(4)物理地址:
物理地址指的是盘算机物理内存硬件中的现实位置。
从虚拟地址到物理地址的翻译是由 MMU 执行的。这种翻译对于内存隔离和保护至关重要。当hello运行时,操作体系和 MMU 共同工作,将步调利用的虚拟地址映射到盘算机 RAM 中的物理地址。
https://i-blog.csdnimg.cn/blog_migrate/b0d6fdc3d19188375dedd5d450962461.png
7.2 Intel逻辑地址到线性地址的变换-段式管理

在Intel x86 架构中,逻辑地址到线性地址的变换是通过段式管理实现的。以下是这个过程的重要步调:
(1)逻辑地址生成:
逻辑地址由步调生成,其中包括段选择子和段内偏移地址。
(2)段选择子解析:
段选择子包含了段的索引以及形貌段属性的信息。通过这个选择子,CPU找到全局形貌表(GDT)或局部形貌表(LDT)中相应的段形貌符。
(3)获取段基址:
从段形貌符中获取段的基址,这是线性地址的一部门。
(4)线性地址生成:
将段基址与段内偏移地址相加,得到线性地址。也就是:
虚拟地址(VA) = 段基地址(BA) + 段内偏移量(S)
7.3 Hello的线性地址到物理地址的变换-页式管理

页式管理是一种用于内存空间存储管理的技术,可以分为静态页式管理和动态页式管理两种形式。在页式管理中,各进程的虚拟空间被分别成多少个长度相称的页(page),而物理内存空间则按照相同的页巨细进行分别成片或页面(page frame)。为了创建虚拟地址与物理地址之间的对应关系,体系维护一个页表,并通过硬件地址变换机构来解决离散地址变换的标题。
在页式管理中,虚拟地址空间和物理地址空间之间的映射关系由页表进行管理,每个进程都有自己的页表。当步调试图访问虚拟地址时,操作体系通过页表将其映射到对应的物理地址,使得进程能够正常访问所需的内存。在hello步调中,线性地址到物理地址的变换是通过页式管理实现的:
(1)线性地址生成:
步调中的线性地址是由逻辑地址生成的,通过段式管理。
(2)分页:
将线性地址分别为固定巨细的页面(通常是4KB)。这个巨细由操作体系和硬件决定。
(3)页表查找:
线性地址的高位用于索引页表,找到对应的页表项。
(4)获取物理页框号:
页表项中包含物理页框号,表示页面在物理内存中的位置。
(5)偏移量添加:
将线性地址中的低位偏移量添加到物理页框号的基址,得到物理地址。
https://i-blog.csdnimg.cn/blog_migrate/a3332ecff0b25ec1e130664c75ad0ef5.png
7.4 TLB与四级页表支持下的VA到PA的变换

在支持四级页表的情况下,TLB(Translation Lookaside Buffer)是一个高速缓存,用于加快虚拟地址到物理地址的转换过程:
(1)TLB缓存检查:
CPU首先检查TLB,看是否已经缓存了虚拟地址到物理地址的映射。
(2)TLB未命中:
假如TLB未命中,CPU必要进行完备的页表查找。
(3)四级页表查找:
CPU利用虚拟地址的高位索引四级页表,逐级查找,直到找到最终的物理页框号。
(4)TLB更新:
找到物理地址后,CPU将新的虚拟地址到物理地址的映射添加到TLB中,以提高后续访问的速率。
https://i-blog.csdnimg.cn/blog_migrate/56c1e95a875da65103535f7ea3b6564e.png
7.5 三级Cache支持下的物理内存访问

在拥有三级缓存(L1、L2、L3 Cache)支持的情况下,物理内存的访问包含以下步调:
(1)CPU缓存层次:
L1 Cache是最小且最快的缓存,紧靠着CPU核心。L2 Cache通常是更大的缓存,而L3 Cache则可能是共享的,供多个核心利用。
(2)Cache行填充:
当CPU访问物理内存时,数据可能会被加载到缓存的一个或多个层次中。这是通过缓存行(通常是64字节)进行的。
(3)Cache命中:
假如请求的数据在缓存中,并且未被修改,CPU可以直接从缓存中获取数据,称为缓存命中。
(4)Cache未命中:
假如数据不在缓存中,发生缓存未命中。此时,CPU必要从主内存中加载数据到缓存中。
(5)数据更新:
假如对缓存中的数据进行了修改,可能必要将修改的数据写回到主内存,以保持一致性。
整个过程中,Cache的存在提高了内存访问速率,因为它们提供了快速的存储器访问,淘汰了对主内存的频仍访问。
7.6 hello进程fork时的内存映射

在进行 fork 过程中,新的进程(子进程)复制了父进程的内存映射,包括代码段、数据段等。详细的内存映射如下:
(1)代码段(Text Segment):
子进程复制了父进程的代码段,包括可执行步调的机器指令。这确保了子进程能够执行与父进程相同的步调逻辑。
(2)数据段(Data Segment):
子进程复制了父进程的数据段,包括全局变量等。这样,子进程可以访问与父进程相同的数据,确保了它们在初始状态下具有相同的数据副本。
(3)堆区(Heap):
子进程的堆区一般是父进程堆区的副本,但是两者是独立的。这意味着对一个进程的堆区的修改不会影响到另一个进程的堆区。堆区通常用于动态内存分配,保留了父子进程之间的独立性。
(4)栈区(Stack):
子进程获得了与父进程相同的栈区,但也是独立的。栈区是由体系主动管理的,不同进程的栈区互不干扰。在调用 fork 函数时,内核为新进程创建各种数据结构,为其分配一个唯一的PID。同时,内核为新进程创建虚拟内存,并复制当前进程的 mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当 fork 在新进程中返回时,新进程的虚拟内存现在与调用 fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新的页面,确保对一个进程的修改不会影响到另一个进程的内存空间。这种机制旨在淘汰内存的冗余拷贝,提高体系的效率。
7.7 hello进程execve时的内存映射

在 execve 过程中,进程加载新的可执行文件,替换当前的进程映像。这个过程会重新初始化进程的内存映射,详细的内存映射取决于新可执行文件的布局在 bash 中执行 execve 调用,在当前进程中加载并运行包含在可执行文件 hello 中的步调替代当前 bash 中的步调。执行 execve 函数的过程如下:
(1)删除已存在的用户区域:
在执行 execve 前,已存在的用户区域(即原先的步调、数据等)会被清理和删除,为新步调的加载腾出空间。
(2)映射私有区域:
execve 在加载 "hello" 可执行文件时会创建新的私有区域结构,包括代码区、数据区、bss 区和栈区。这些区域将被映射到进程的虚拟地址空间中。
(3)映射共享区域:
假如 "hello" 步调与标准 C 库 libc.so 等动态链接库链接,这些库中的对象可能会被动态映射到用户虚拟地址空间的共享区域。这样,多个进程可以共享这些库的代码和数据,提高了内存利用效率。
(4)设置步调计数器(PC):
execve 设置当前进程的步调计数器(PC),使其指向 "hello" 步调代码区的入口点。这是为了确保执行开始时从正确的位置开始执行步调的指令。

execve 函数负责加载一个新的可执行步调到当前进程中,进行须要的内存清理、区域映射和设置步调计数器等操作,以确保正确且安全地执行新步调的逻辑。
7.8 缺页故障与缺页中断处置惩罚

缺页故障是指当步调实验访问尚未加载到物理内存的页面时,会触发一个非常情况。操作体系通过缺页中断处置惩罚步调将这个缺页非常引导到相应的处置惩罚逻辑。

处置惩罚缺页的过程涉及以下步调:
(1)地址正当性检查:
缺页处置惩罚步调首先验证虚拟地址是否在某个区域结构界说的有效范围内。为此,它会检查虚拟地址与每个区域结构中的vm_start和vm_end进行比较。假如它不在任何正当区域内,缺页处置惩罚步调触发一个段错误,终止当前进程。
(2)权限检查:
确认试图进行的内存访问是否正当,即进程是否具有对该区域内页面的得当读、写或执行权限。比方,假如缺页是由试图对只读页面执行写操作的存储指令引起的,或者是由运行在用户模式中的进程试图从内核虚拟内存中读取字引起的,那么访问是非法的。在这种情况下,缺页处置惩罚步调触发一个保护非常,导致进程终止。
(3)处置惩罚正当且权限正确的缺页:
假如缺页是由对正当地址的正当操作引起的,那么缺页处置惩罚步调选择一个牺牲页面。假如该页面已经被修改,它会被交换到磁盘,然后新的页面被调入内存,并更新页表。当缺页处置惩罚步调返回时,CPU重新启动导致缺页的指令,这次由于已经将相应页面加载到内存,不再触发缺页中断。这样,步调可以继续执行。
https://i-blog.csdnimg.cn/blog_migrate/ee3f5f63dd1e0999fcca747ddfa238df.png
https://i-blog.csdnimg.cn/blog_migrate/e80c727c2e15c1bc21c8020733c69cdf.png
7.9动态存储分配管理

动态内存分配器是操作体系中负责管理进程虚拟内存区域中堆(heap)的重要组件。堆(Heap)是操作体系为进程动态分配内存而预留的一部门虚拟内存空间。在步调运行时,堆的巨细并不固定,而是根据步调的需求动态增长或淘汰。它通常位于未初始化数据区域(BSS)的后面,并向更高的地址方向增长。每个进程都有一个指向堆顶部的变量。
堆被分别为一组不同巨细的块(chunk),每个块要么是已分配的,要么是空闲的。已分配的块显式地保留给应用步调利用,而空闲块可用于分配。分配器负责维护这些块,并执行分配和释放操作。
(1)放置已分配的块
当应用步调请求一个巨细为k字节的块时,分配器搜索空闲链表,查找充足大以容纳请求的空闲块。放置策略由分配器的计划决定,常见的有初次适配、下一次适配和最佳适配。初次适配从链表头开始搜索,选择第一个适合巨细的空闲块。下一次适配和初次适配相似,但从上一次搜索结束的地方开始。最佳适配检查每个空闲块,选择最小的适合请求巨细的空闲块。
(2)分割空闲块
一旦找到匹配的空闲块,分配器必须决定如何分配空间。通常,分配器选择将空闲块分割为两部门,一部门变为已分配块,另一部门成为新的空闲块。
(3)获取额外的堆内存
假如分配器无法找到充足大的空闲块,一种选择是归并相邻的空闲块以创建更大的块。假如这仍然不敷以满足需求,或者空闲块已经归并到最大程度,分配器将通过调用sbrk函数向内核请求额外的内存。分配器将这块额外的内存转换为一个大的空闲块,将其插入空闲链表,然后将请求的块放置在新的空闲块中。
(4) 归并空闲块
Knuth提出了边界标记(boundary tag)技术,在每个块的结尾添加一个脚部(footer),它是头部的副本。通过检查脚部,分配器可以判断前一个块的起始位置和状态。动态内存分配管理利用动态内存分配器(如malloc)来实现。

动态内存分配重要有两种基本方法:
(1)带边界标签的隐式空闲链表分配器管理
每个块包括头部、有效载荷、可能的额外填充和尾部。通过头部的巨细字段,空闲块隐含地连接在一起,分配器遍历整个空闲块集合。放置策略和释放操作可以利用边界标记进行归并。
(2)显式空间链表管理
显式空闲链表将堆的空闲块构造成双向链表,每个空闲块包含前驱和后继指针。放置新的空闲块可以按后进先出或地址顺序维护链表。释放操作涉及线性时间搜索以定位得当的前驱。这种方式淘汰了搜索时间但可能增长了内部碎片。
(3)分离链表
分离链表包括简单分离链表、分离适配和伙伴体系。简单分离链表的等价类中块巨细相同,分配速率较快但可能导致大量内部和外部碎片。分离适配中,块巨细不同,每个等价类表示一定范围的块巨细,提高了内存利用率。伙伴体系将堆分别为2的幂次方巨细的块,可能会导致内部碎片。
7.10本章小结

本章详细介绍了操作体系中与内存管理相关的重要概念和机制,包括存储器地址空间、段式管理、页式管理、虚拟地址到物理地址的变换、物理内存访问,以及进程创建过程中的内存映射、缺页故障与缺页中断处置惩罚,还包括动态存储分配管理中的隐式空闲链表和显式空闲链表。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.1.1设备的模子化:文件

在Linux中,设备被抽象为文件,即"一切皆文件"的头脑。每个设备都被表示为文件,通过文件体系进行访问和管理。这种模子化简化了设备的访问和操作。
8.1.2设备管理:Unix IO接口

设备管理接纳Unix IO接口,通过体系调用提供对设备的访问。用户步调通过标准文件形貌符(file descriptor)来进行IO操作,比方打开、关闭、读取和写入。IO接口为设备提供了同一的访问方式,使得用户和应用步调可以通过文件操作的方式来处置惩罚设备。
8.2 简述Unix IO接口及其函数

Unix IO接口提供了一系列体系调用函数,其中一些常见的包括:
open: 打开文件或设备。
close: 关闭文件或设备。
read: 从文件或设备读取数据。
write: 向文件或设备写入数据。
ioctl: 设备控制。
select/poll/epoll: 多路复用,用于异步IO操作。
fcntl: 文件控制。
这些函数构成了Unix IO接口,允许应用步调通过文件形貌符进行对设备的读写和控制。
8.3 printf的实现分析

https://i-blog.csdnimg.cn/blog_migrate/29f537651d784ec1f36a8d6a8279c2c0.png
printf函数的实现可以分为以下几个步调:

[*]从vsprintf生成显示信息。
https://i-blog.csdnimg.cn/blog_migrate/96fc673f0d471b55f5825210ef0bc582.png

[*]通过write体系函数将信息写入缓冲区。
https://i-blog.csdnimg.cn/blog_migrate/f2fdec01966a0d9e1672392c1bdfadad.png

[*]通过陷阱-体系调用 int 0x80或syscall等,将缓冲区中的信息通报给操作体系内核。
https://i-blog.csdnimg.cn/blog_migrate/fb150808f5f687e69451aee93c1b8294.png
(4)操作体系内核将信息通报给字符显示驱动子步调。
(5)字符显示驱动子步调将ASCII码转换为字模库中对应的点阵信息。
(6)字符显示驱动子步调将点阵信息存储到显示vram中。
(7)显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析

(1)getchar函数的基本功能形貌:
步调调用getchar时,步调等待用户从键盘输入信息。在用户输入有效信息时,输入的字符被放入字符缓冲区,getchar不进行处置惩罚。当用户输入回车键时,getchar以字符为单元读取字符缓冲区,但不会读取回车键和文件结束符。
(2)异步非常-键盘中断的处置惩罚流程:
用户进行键盘输入时,CPU通过中断跳转到键盘中断处置惩罚子步调。
键盘中断处置惩罚子步调接受按键扫描码,将其转换为ASCII码,保存到体系的键盘缓冲区。控制返回给原始使命(可能是getchar调用的步调),继续等待用户输入。假如没有遇到回车键,重复上述过程。遇到回车键后,getchar按字节读取键盘缓冲区内的内容,处置惩罚完毕后getchar返回,getchar进程结束。
(3)关于getchar的详细实现:
步调调用getchar,getchar等待用户按键,用户输入的字符被存放在键盘缓冲区中,直到用户按回车(回车也在缓冲区中)。
当用户键入回车后,getchar开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1,同时将用户输入的字符回显到屏幕。假如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步非常-键盘中断的处置惩罚过程中,getchar等调用read体系函数,通过体系调用读取按键ASCII码。read体系调用会壅闭步调,直到接收到回车键才返回。
8.5本章小结

本章重要介绍了Linux的IO设备管理方法、Unix IO接口及其相关函数、以及printf和getchar函数的实现分析。Linux的IO设备管理以文件为模子,Unix IO接口提供了一套标准的设备访问方式,而printf和getchar函数通过特定的实现方式实现了格式化输出和键盘输入的功能。这些深入的分析有助于理解操作体系中的IO机制和函数实现。

结论

从源代码Hello.c到最终进程的产生,我们履历了预处置惩罚、编译、汇编和链接等多个阶段。这一系列步调展示了盘算机体系中各个处置惩罚单元的协同工作,将高级语言代码翻译成可执行的机器代码。这不但仅是技术上的过程,更是对盘算机语言、编译器和链接器等工具的理解和利用。
020过程呈现了进程的生命周期,从内存的分配到最终的回收。fork()函数的调用产生了子进程,而execve函数的执行加载并运行了新的步调。虚拟地址的概念成为了步调运行的基础,而与外界的交互和I/O操作则使进程在体系中有了更广泛的联系。最终,随着进程的结束,体系通过父进程或养父进程回收资源,消除了进程在体系中的痕迹。
编译阶段将高级语言的代码翻译成汇编语言,通过汇编阶段转化为机器语言指令,最终形成可重定位目标文件。链接阶段将各个目标文件和库文件有机地结合,产生了一个完备的可执行目标步调。
步调的运行不但仅是一系列指令的执行,更是一个进程的诞生和演化。在操作体系的调度下,进程被抽象为盘算机体系中的一个独立实体,拥有自己的内存空间、代码段、数据段、堆和栈。进程的执行是一个动态的过程,必要与操作体系、硬件以及其他进程进行紧密的协作。
在执行指令的阶段,CPU为步调分配时间片,步调依次执行指令,实现了代码的有序运行。访问内存和动态申请内存则展现了盘算机体系中内存管理的关键作用,通过虚拟内存的映射实现了地址的抽象,为步调提供了更为广阔的内存空间。
步调与外界的交互通过输入输出实现,而这也与操作体系中的I/O机制痛痒相关。步调能够与用户、其他步调以及外设进行信息的交流,实现了盘算机体系的多样性和灵活性。
最终,步调的生命周期在结束阶段得以封闭。通过信号的处置惩罚和资源的回收,步调在操作体系中谢幕,留下一段在盘算机体系中留下的足迹。
在整个过程中,我们得以深切感悟到盘算机体系计划与实现的复杂性和精密性。源步调一步步到可执行步调的过程非常精致,虚拟地址的引入为步调提供了抽象的运行情况,而进程的管理则必要对体系资源的精准掌控。对于盘算机体系的计划,我们必要在性能、安全性和可维护性之间找到平衡,以确保体系能够高效、安全地运行。

附件
hello.c
hello 的C源文件
hello.i
hello.c颠末预处置惩罚后的预编译文件
hello_p.i
hello.c颠末加上-P参数的预处置惩罚的预编译文件
hello.s
hello.i颠末编译得到的汇编语言文本文件
hello.o
hello.s颠末汇编得到的可重定位目标文件
elf.txt
通过readelf导出的hello.o的elf信息文本
asm.txt
hello.o反汇编导出得到的文本
hello
hello.o颠末链接得到可执行文件
elf1.txt
通过readelf导出的hello的elf信息文本
asm1.txt
hello反汇编导出得到的文本


参考文献

 Randal E. Bryant, David O’Hallaron. Computer Systems: A Programmer’s Perspective. Beijing: Mechanical Industry Press, 2021:10-1.
GeanQin. 认识各种内存地址. https://blog.csdn.net/angjia7206/article/details/106546203.
Pianistx. printf 函数实现的深入剖析. https://www.cnblogs.com/pianist/p/3315801.html.
laolitou_ping. C语言——预处置惩罚. https://blog.csdn.net/laolitou_ping/article/details/117108721.
风气、云落. linux——利用gcc解析步调四个阶段. https://blog.csdn.net/qq_62939852/article/details/127263338.





免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 步调人生-Hello’s P2P