哈工大盘算机系统大作业程序人生-Hello‘s P2P
盘算机系统大作业
题 目 程序人生-Hello’s P2P
专 业 医学与康健
学 号 2022111142
班 级 2252003
学 生 竺光辉
指 导 教 师 史先俊
盘算机科学与技能学院
2024年5月
摘 要
本文旨在阐述C语言程序从源代码到可执行文件的转换过程。通过以hello.c程序为例,详细表明了预处理惩罚、编译、汇编、链接等步调,以及这些步调在盘算机系统中的作用。不但仅是理论探究,文章还通过实际演示展示了这些工具的操作及其生成的结果。通过深入讨论盘算机系统的工作原理和体系结构,旨在资助读者更好地理解和掌握C语言程序的编译和执行过程。
关键词:盘算机系统;编译;执行
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述................................................................................... - 4 -
1.1 Hello简介............................................................................ - 4 -
1.2 环境与工具........................................................................... - 4 -
1.3 中间结果............................................................................... - 4 -
1.4 本章小结............................................................................... - 4 -
第2章 预处理惩罚............................................................................... - 5 -
2.1 预处理惩罚的概念与作用........................................................... - 5 -
2.2在Ubuntu下预处理惩罚的命令................................................ - 5 -
2.3 Hello的预处理惩罚结果解析.................................................... - 5 -
2.4 本章小结............................................................................... - 5 -
第3章 编译................................................................................... - 6 -
3.1 编译的概念与作用............................................................... - 6 -
3.2 在Ubuntu下编译的命令.................................................... - 6 -
3.3 Hello的编译结果解析........................................................ - 6 -
3.4 本章小结............................................................................... - 6 -
第4章 汇编................................................................................... - 7 -
4.1 汇编的概念与作用............................................................... - 7 -
4.2 在Ubuntu下汇编的命令.................................................... - 7 -
4.3 可重定位目的elf格式........................................................ - 7 -
4.4 Hello.o的结果解析............................................................. - 7 -
4.5 本章小结............................................................................... - 7 -
第5章 链接................................................................................... - 8 -
5.1 链接的概念与作用............................................................... - 8 -
5.2 在Ubuntu下链接的命令.................................................... - 8 -
5.3 可执行目的文件hello的格式........................................... - 8 -
5.4 hello的虚拟地址空间......................................................... - 8 -
5.5 链接的重定位过程分析....................................................... - 8 -
5.6 hello的执行流程................................................................. - 8 -
5.7 Hello的动态链接分析........................................................ - 8 -
5.8 本章小结............................................................................... - 9 -
第6章 hello历程管理.......................................................... - 10 -
6.1 历程的概念与作用............................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理惩罚流程........................... - 10 -
6.3 Hello的fork历程创建过程............................................ - 10 -
6.4 Hello的execve过程........................................................ - 10 -
6.5 Hello的历程执行.............................................................. - 10 -
6.6 hello的异常与信号处理惩罚................................................... - 10 -
6.7本章小结.............................................................................. - 10 -
第7章 hello的存储管理...................................................... - 11 -
7.1 hello的存储器地址空间................................................... - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理................... - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理............. - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换.................... - 11 -
7.5 三级Cache支持下的物理内存访问................................ - 11 -
7.6 hello历程fork时的内存映射......................................... - 11 -
7.7 hello历程execve时的内存映射..................................... - 11 -
7.8 缺页故障与缺页中断处理惩罚................................................. - 11 -
7.9动态存储分配管理.............................................................. - 11 -
7.10本章小结............................................................................ - 12 -
第8章 hello的IO管理....................................................... - 13 -
8.1 Linux的IO装备管理方法................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................. - 13 -
8.3 printf的实现分析.............................................................. - 13 -
8.4 getchar的实现分析.......................................................... - 13 -
8.5本章小结.............................................................................. - 13 -
结论............................................................................................... - 14 -
附件............................................................................................... - 15 -
参考文献....................................................................................... - 16 -
第1章 概述
1.1 Hello简介
P2P:即From Program to Process。涵盖了从源代码(Program)到运行时历程(Process)的变化过程。对于一个C语言程序hello.c,要使其运行,起首必要经历预处理惩罚、编译、汇编和链接四个阶段,将其转换为可执行文件。完成后,可执行文件可以在shell中执行,shell会为其分配历程空间,从而将其变化为运行时历程。
020:即From Zero-0 to Zero-0。形貌了从内存中最初没有hello文件相干内容到运行结束的整个过程。当shell使用execve函数启动hello程序时,它将程序加载到虚拟内存中,与物理内存相对应,然后从程序入口开始加载和执行。一旦程序执行完毕,shell父历程将回收hello历程,内核也会删除与hello文件相干的数据结构。
1.2 环境与工具
硬件环境:
处理惩罚器:12th Gen Intel(R) Core(TM)i5-12500H 2.50 GHz
机带RAM:16.0GB
系统类型:64位操作系统,基于x64的处理惩罚器
软件环境:Windows11 64位,VMware,Ubuntu 20.04 LTS
开辟与调试工具:Visual Studio 2021 64位;vim objump edb gcc readelf等工具
1.3 中间结果
hello.i 预处理惩罚后得到的文本文件
hello.s 编译后得到的汇编语言文件
hello.o 汇编后得到的可重定位目的文件
hello.asm 反汇编hello.o得到的反汇编文件
hello1.asm 反汇编hello可执行文件得到的反汇编文件
1.4 本章小结
本章起首介绍了hello的P2P,020流程,包括流程的计划思路和实现方法;然后,详细说明了本实行所需的硬件配置、软件平台、开辟工具以及本实行生成的各个中间结果文件的名称和功能。
第2章 预处理惩罚
2.1 预处理惩罚的概念与作用
概念:预处理惩罚是在程序正式编译之前对源代码举行的一系列处理惩罚步调。在这个阶段,预处理惩罚器会执行一些指令,这些指令以#开头,用于对源文件举行修改、替换和删除操作,以便为后续编译提供更符合的源代码。
预处理惩罚的重要作用包括:
1.头文件包含: 将指定的头文件内容插入到当前文件中,以便在编译时使用相干的函数、变量或宏定义。
2.宏替换: 将程序中定义的宏标识符替换为对应的文本内容,这样可以简化代码编写,提高代码的可读性和可维护性。
3.条件编译: 根据预定义的条件判断,决定是否编译某段代码,这对于实现跨平台的代码编写和调试很有效。
4.注释删除: 删除源文件中的注释内容,以淘汰编译时的代码量,提高编译效率。
5.其他预处理惩罚指令的处理惩罚: 包括文件包含、条件编译、行连接等。
总的来说,预处理惩罚阶段通过执行预处理惩罚指令,对源文件举行文本替换、删除和插入,以生成经过初步加工的源代码,为后续编译阶段做好准备。
2.2在Ubuntu下预处理惩罚的命令
预处理惩罚的命令:gcc -E hello.c -o hello.i
https://img-blog.csdnimg.cn/direct/bf6b21c9eaf948f195c96723f6329ee9.png
2.3 Hello的预处理惩罚结果解析
在Linux下打开hello.i文件,我们对比了源程序和预处理惩罚后的程序。结果表现,除了预处理惩罚指令被扩展成了几千行之外,源程序的其他部分都保持不变,说明.c文件的确是被修改过了。
https://img-blog.csdnimg.cn/direct/0d9022e3e5b8441586315e1d44d5acfb.png
在main函数代码出现之前的大段代码源自于的头文件<stdio.h> <unistd.h> <stdlib.h> 的依次睁开。
以 stdio.h 的睁开为例:预处理惩罚过程中,#include指令的作用是把指定的头文件的内容包含到源文件中。stdio.h是标准输入输出库的头文件,它包含了用于读写文件、标准输入输出的函数原型和宏定义等内容。
当预处理惩罚器碰到#include<stdio.h>时,它会在系统的头文件路径下查找stdio.h文件,一般在/usr/include目录下,然后把stdio.h文件中的内容复制到源文件中。stdio.h文件中大概还有其他的#include指令,比如#include<stddef.h>或#include<features.h>等,这些头文件也会被递归地睁开到源文件中。
预处理惩罚器不会对头文件中的内容做任何盘算或转换,只是简单地复制和替换。
2.4 本章小结
本章报告了在linux环境中,怎样用命令对C语言程序举行预处理惩罚,以及预处理惩罚的含义和作用。然后用一个简单的hello程序演示了从hello.c到hello.i的过程,并用具体的代码分析了预处理惩罚后的结果。通过分析,我们可以发现预处理惩罚后的文件hello.i包含了标准输入输出库stdio.h的内容,以及一些宏和常量的定义,还有一些行号信息和条件编译指令。
第3章 编译
3.1 编译的概念与作用
概念:将使用高级编程语言编写的源代码转换为底层呆板能够理解的等效形式的过程。这个过程的重要目的是提高代码的执行效率、可移植性和可维护性。
作用:盘算机程序编译的作用是使高级语言源程序变为汇编语言,提高编程效率和可移植性。盘算机程序编译的根本流程包括词法分析、语法分析、语义分析、中间代码生成、代码优化和目的代码生成等阶段。
3.2 在Ubuntu下编译的命令
编译的命令:gcc -S hello.i -o hello.s
https://img-blog.csdnimg.cn/direct/673c059359c642aab99a268c8f6bc2b6.png
3.3 Hello的编译结果解析
1. 汇编初始部分
在main函数前有一部分字段展示了节名称:
https://img-blog.csdnimg.cn/direct/fe7e801e2cda449591270631db665f35.png
.file 声明出源文件
.text 表现代码节
.section .rodata 表现只读数据段
.align 声明对指令大概数据的存放地址举行对齐的方式
.string 声明一个字符串
.globl 声明全局变量
.type 声明一个符号的类型
2. 数据部分
https://img-blog.csdnimg.cn/direct/744711c1b292425b8e3b5aa01773168d.png
hello.c中唯一的数组是main函数中的第二个参数(即char**argv),数组的每个元素都是一个指向字符类型的指针。由知数组起始地址存放在栈中-32(%rbp)的位置,被两次调用作为参数传到printf中。
如图,分别将rdi设置为两个字符串的起始地址:
https://img-blog.csdnimg.cn/direct/537ecb0e050845bd8d256054a3a72446.png
(2)参数argc
参数argc是main函数的第一个参数,被存放在寄存器%edi中,由语句
https://img-blog.csdnimg.cn/direct/0a5ce6875cad401ab1014800d8e4a420.png
可见寄存器%edi地址被压入栈中,而语句
https://img-blog.csdnimg.cn/direct/27f6c7af6c6b421db6bbb96fd16844ff.png
可知该地址上的数值与立即数5判断大小,从而得知argc被存放在寄存器并被压入栈中。
(3)局部变量
程序中的局部变量只有i,我们根据
https://img-blog.csdnimg.cn/direct/4c390ee74f654a3294bac5fd718d61ac.png
可知局部变量i是被存放在栈上-4(%rbp)的位置。
3.全局函数
hello.c中只声明了一个全局函数int main(int arge,.char*argv[]),我们通过汇编代码
https://img-blog.csdnimg.cn/direct/516588b1a8b74d2ca2f6347ceea5faae.png
可知。
4,。赋值操作
hel1o.c中的赋值操作贝有for循环开头的i-0,该赋值操作体现在汇编代码上,则是用mov指令实现,如图:
https://img-blog.csdnimg.cn/direct/c4aa37c1670042b9b3ef0c02b800e2b7.png
由于int型变量i是一个32位变量,使用movl传递双字实现。
5.算术操作
hello.c中的算术操作为for循环的每次循环结束后i++,该操作体现在汇编代码则使用指令add实现,问样,由丁变量i为32位,使用指令addl。指令如下:
https://img-blog.csdnimg.cn/direct/8428a86288f246a9a0b521a34d7e3857.png
6.关系操作
hello.c中存在两个关系操作,分别为:
(1) 条件判断语句if(argc!=4):汇编代码将这条代码翻译为:
https://img-blog.csdnimg.cn/direct/dc0157ab7c7646bba822d066c227c23f.png
使用了cmp指令比力立即数5和参数argc大小,并且设置了条件码。根据条件码,假如不相等则执行该指令背面的语句,否则跳转到.L2。
(2) 在for循环每次循环结束要判断一次i<10,判断循环条件被翻译为:
https://img-blog.csdnimg.cn/direct/72b93ddd9415471ebe908958f12d4550.png
同(1),设置条件码,并通过条件码判断跳转到什么位置。
7.控制转移指令
设置过条件码后,通过条件码来举行控制转移,在本程序中存在两个控制转移:
(1) https://img-blog.csdnimg.cn/direct/3b7ee5744b7f43d6b58a19c9f08e92c0.png
判断argc是否为5,假如不为5,则执行if语句,否则执行其他语句,在汇编代码中则表现为假如条件码为1,则跳到.L2,否则执行cmpl指令后的指令。
(2) https://img-blog.csdnimg.cn/direct/0afd5e99f6904ca0bcf465a4d8d33efa.png
在for循环每次结束判断一次i<10,翻译为汇编语言后,通过条件码判断每次循环是否跳转到.L4。而在for循环初始要对i设置为0,如下:
https://img-blog.csdnimg.cn/direct/46eda5d0b39d44ab97e310e869e4546a.png
然后直接无条件跳转到.L3循环体。
8.函数操作
(1)main函数
参数传递:该函数的参数为int argc,,char*argv[]。具体参数传递地址和值都在前面阐述过。
函数调用:通过使用call内部指令调用语句举行函数调用,并且将要调用的函数地址数据写入栈中,然后自动跳转到这个调用函数内部。main函数里调用了printf、exit、sleep函数。
局部变量:使用了局部变量i用于for循环。具体局部变量的地址和值都在前面阐述过。
(2)printf函数
参数传递:printf函数调用参数argv,argv。
函数调用:该函数调用了两次。第一次将寄存器%rdi设置为待传递字符串"用法:Hello学号姓名 秒数!\n"的起始地址;第二次将其设置为“Hello %s %s\n”的起始地址。具体已在前面讲过。使用寄存器%rsi完成对argv的传递,用%rdx完成对argv的传递。
(3)exit函数
参数传递与函数调用:
https://img-blog.csdnimg.cn/direct/80599791836a4280a6ad4c38e5963b08.png
将rdi设置为1,再使用call指令调用函数。
(4)atoi、sleep函数
参数传递与函数调用:
https://img-blog.csdnimg.cn/direct/7da8d361a43a412ca29668e387c47f23.png
可见,atoi函数将参数argv放入寄存器%rdi中用作参数传递,简单使用call指令调用。
https://img-blog.csdnimg.cn/direct/18d04700fed74a3fb55e6077b62a2172.png
然后,将转换完成的秒数从%eax传递到%edi中,edi存放sleep的参数,再使用call调用。
(5)getchar函数
无参数传递,直接使用call调用即可。
9.类型转换
atoi函数将宁字符中转换为sleep函数必要的整型参数.
3.4 本章小结
这一章介绍了C编译器怎样把hello.i文件转换成hello.s文件的过程,简要说明了编译的含义和功能,演示了编译的指令,并通过分析生成的hello.s文件中的汇编代码,探究了数据处理惩罚,函数调用,赋值、算术、关系等运算以及控制跳转和类型转换等方面,比力了源代码和汇编代码分别是怎样实现这些操作的。
第4章 汇编
4.1 汇编的概念与作用
概念:通过汇编器将汇编语言文件(.s文件)翻译为呆板语言指令,然后将这些指令打包成一个可重定位目的文件(.o文件)的过程。在这个过程中,汇编器将汇编语言代码转换为盘算机能够直接执行的呆板码,生成的目的文件包含了程序的指令编码,可以被后续的链接器使用。
作用:将高级语言转换为呆板可以直接执行的代码文件,使得程序能够在盘算机上运行。生成的目的文件(.o文件)是一个二进制文件,其中包含了程序的指令编码,为最终生成可执行文件奠定了根本。
4.2 在Ubuntu下汇编的命令
在Ubuntu系统下,对hello.s举行汇编的命令为:
gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
https://img-blog.csdnimg.cn/direct/ea67f234060942e1b404e48756650834.png
4.3 可重定位目的elf格式
在shell中输入readelf -a hello.o > hello.elf 指令得到 hello.o 文件的 ELF 格式:
https://img-blog.csdnimg.cn/direct/221ec3a2fffd46d188b46e3862b52d51.png
典型的ELF格式的可重定位目的文件的结构如下:
(1)ELF头
ELF头(ELF header)以一个l6字节的序列开始,这个序列形貌了生成该文件的系统的字的大小和字节次序。ELF头剩下的部分包含了资助链接器语法分析和表明目的文件的信息,其中包括ELF头的大小、目的文件的类型(如可重定位、可执行大概共享的)、呆板类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表形貌的,其中目的文件中每个节都有一个固定大小的条目(entry)。ELF头展示如下:
https://img-blog.csdnimg.cn/direct/31fab2cb84d0442297e8e5c8ea30690c.png
(2)节头(section header)
记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。
https://img-blog.csdnimg.cn/direct/17a37fdfd8bc409f88615cd58e5d4a26.png
(3)重定位节
.rel.text节是一个.text节中位置的列表,当链接器把这个目的文件和其他文件组合时,必要修改这些位置。一般而言,任何调用外部函数大概引用全局变量的指令都必要修改,而调用本地函数的指令不需修改。可执行目的文件中不包含重定位信息。如图,必要重定位的内容如下:
https://img-blog.csdnimg.cn/direct/970054901e20466bb869493523e4d0eb.png
(4)符号表
.symtab节中包含ELF符号表,这张符号表包含一个条目的数组,存放一个程序定义和引用的全局变量和函数的信息。该符号表不包含局部变量的信息。符号表如下:
https://img-blog.csdnimg.cn/direct/3a625c69bfea496ab33023552e10aa4c.png
4.4 Hello.o的结果解析
1. 命令
在shell中输入 objdump -d -r hello.o > hello.asm 指令输出hello.o的反汇编文件,并与第3章的hello.s文件举行对照分析。
https://img-blog.csdnimg.cn/direct/7fe9b4d142324cab8d94937db916438b.png
(1)增加呆板语言
每一条指令增加了一个十六进制的表现,即该指令的呆板语言。例如,在hello.s中的一个cmpl指令表现为
https://img-blog.csdnimg.cn/direct/a8da906bd456495db1109ce4fc2f7da0.png
而在反汇编文件中表现为
https://img-blog.csdnimg.cn/direct/cd562d901631467f9df1bf8aae08ed03.png
(2)操作数进制
反汇编文件中的所有操作数都改为十六进制。如(1)中的例子,立即数由hello.s中的$5变为了$0x5,地址表现也由-20(%rbp)变为-0x14(%rbp)。可见只是进制表现改变,数值未发生改变。
(3)分支转移
反汇编的跳转指令中,所有跳转的位置被表现为主函数+段内偏移量这样确定的地址,而不再是段名称(例如.L3)。例如下面的jmp指令,反汇编文件中为
https://img-blog.csdnimg.cn/direct/6da850a5b53342268c2b010676efd771.png
而hello.s文件中为
https://img-blog.csdnimg.cn/direct/efd6c432a8174ee8940ef42fa4e7c4d1.png
(4)函数调用
反汇编文件中对函数的调用与重定位条目相对应。观察下面两个call指令调用函数,在hello.s中为
https://img-blog.csdnimg.cn/direct/25ec9bb07d254c64ba7f77f1921ad257.png
而在反汇编文件中调用函数为https://img-blog.csdnimg.cn/direct/6b775bd47b4244f9848d3311bebf0267.png
在可重定位文件中call背面不再是函数名称,而是一条重定位条目指引的信息。
4.5 本章小结
本章详细介绍了在Ubuntu系统下使用汇编语言文件hello.s的示例来说明汇编过程。起首,我们展示了怎样将hello.s文件通过汇编器转换为可重定位目的文件hello.o,然后进一步将其转换为ELF(Executable and Linkable Format)格式的可执行文件hello.elf。在这个过程中,我们深入解析了生成的可重定位目的文件的结构,包括每个节(section)的内容和作用。
通过对hello.o文件举行反汇编,我们得到了hello.asm文件,它展示了汇编语言和呆板语言之间的转换过程。通过比力hello.s和hello.asm,我们可以清晰地理解汇编语言代码怎样被转换为呆板语言指令,并了解呆板语言指令在目的文件中的分列和构造结构。这种比力和分析使我们能够深入了解汇编过程中的细节,以及目的文件中各个部分的作用,为后续的链接过程做好准备。
第5章 链接
5.1 链接的概念与作用
概念:链接是将多个代码和数据片段整合为一个可执行文件的过程,这个文件可被加载到内存并执行。链接可以执行与编译时,也就是在源代码被翻译为呆板代码时;也可以执行与加载,也就是程序被加载器加载到内存并执行时:乃至执行于运行时。
作用:链接是由叫做链接器(1iker)的程序自动执行的。链接器是现代系统中执行的程序,它们的使命是将多个模块组合成一个完整的可执行文件。这种模块化的计划答应我们将大型应用程序分解为更小、更易于管理的部分。每个模块可以独立地举行修改和编译,而不必重新编译整个应用程序。当我们修改了一个模块时,只需重新编译该模块并重新链接应用程序,而不必重新编译整个项目。这种方式简化了软件开辟的过程,提高了开辟效率,并且使得项目的维护和更新更加轻易。
5.2 在Ubuntu下链接的命令
在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
运行截图如下:
https://img-blog.csdnimg.cn/direct/b94257d710dc418294e07c307c5b8a4f.png
5.3 可执行目的文件hello的格式
使用readelf解析hello的ELF格式,得到hello的节信息和段信息:
(1)ELF头(ELF Header)
hello1.elf中的ELF头与hello.elf中的ELF头包含的信息种类根本相同,以形貌了生成该文件的系统的字的大小和字节次序的16字节序列Magic开始,剩下的部分包含资助链接器语法分析和表明目的文件的信息。与hello.elf相比力,hello1.elf中的根本信息未发生改变(如Magic,类别等),而类型发生改变,程序头大小和节头数量增加,并且得到了入口地址。
https://img-blog.csdnimg.cn/direct/939adf21594840f39eae85742dffa267.png
(2)节头
形貌了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段归并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
https://img-blog.csdnimg.cn/direct/65186393240f49b2980403c819b9d2e7.png
(3)程序头
程序头部分是一个结构数组,形貌了系统准备程序执行所需的段或其他信息。
https://img-blog.csdnimg.cn/direct/3ab4d54919c1424bbe509cc755b6694c.png
(4)Dynamic section
https://img-blog.csdnimg.cn/direct/32001e2e492a498284569c052c1848f4.png
(5)Symbol table
符号表中生存着定位、重定位程序中符号定义和引用的信息,所有重定位必要引用的符号都在其中声明。
https://img-blog.csdnimg.cn/direct/22ef119c269d46d19be889003dbaebbe.png
5.4 hello的虚拟地址空间
观察程序头的LOAD可加载的程序段的地址为0x400000。如图:
https://img-blog.csdnimg.cn/direct/42f64f2601814cb4ae11199f429dd957.png
使用edb打开hello从Data Dump窗口观察hello加载到虚拟地址的环境,查
看各段信息。如图:
https://img-blog.csdnimg.cn/direct/7159b85fa2684d5d92b3fe8c8d036d50.png
https://img-blog.csdnimg.cn/direct/68d525fc99424bb1b871a62c5de31671.png
与5.3对照分析说明如下:在edb中的虚拟地址和在elf文件中看到的虚拟地址是对应相同的,具体如下:在elf文件中我们可以看到节头信息如图所示,在edb中我们可以选择打开symbol viewer举行loaded symbls,也会加载出节头的虚拟地址信息,如图所示,可以看出,两侧的虚拟地址是对应相同的。
https://img-blog.csdnimg.cn/direct/1fe5d73c4aa4413995f19f6a8d84fdca.png
https://img-blog.csdnimg.cn/direct/f531987077394203acb1fc33f39db3cf.png
如.interp节,在hello.elf文件中能看到开始的虚拟地址:
https://img-blog.csdnimg.cn/direct/24a3b1f84dae466fbef4603bdde9ecd1.png
在edb中能找到对应的信息:
https://img-blog.csdnimg.cn/direct/820a9fedec9c4dc682987797865262f9.png
同样的,我们可以找到如.text节的信息:
https://img-blog.csdnimg.cn/direct/47967aa7b78745f49a2ec1aed7fc0793.png
https://img-blog.csdnimg.cn/direct/9e340b46aa284fc09a772aa12fe042b1.png
5.5 链接的重定位过程分析
在Shell中使用命令objdump -d -r hello > hello1.asm生成反汇编文件hello1.asm
https://img-blog.csdnimg.cn/direct/81b308638b724f22afc72a1b75871e1a.png
与第四章中生成的hello.asm文件举行比力,其不同之处如下:
(1)链接后函数数量增加
链接后的反汇编文件hello2.asm中,多出了.plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt等函数的代码。这是因为动态链接器将共享库中hello.c用到的函数加入可执行文件中。
https://img-blog.csdnimg.cn/direct/f9d66f21973b4b2c9b0577012be1c48a.png
(2)函数调用指令call的参数发生厘革
在链接过程中,链接器解析了重定位条目,call之后的字节代码被链接器直接修改为目的地址与下一条指令的地址之差,指向相应的代码段,从而得到完整的反汇编代码。
https://img-blog.csdnimg.cn/direct/dfe4d1d4b85341d288d0bb24f61d0ea7.png
(3)跳转指令参数发生厘革
在链接过程中,链接器解析了重定位条目,并盘算相对间隔,修改了对应位置的字节代码为PLT 中相应函数与下条指令的相对地址,从而得到完整的反汇编代码。
https://img-blog.csdnimg.cn/direct/c2929eeee85d4182a36777c7db19ad9c.png
https://img-blog.csdnimg.cn/direct/5507c554935d4ac996cb6a1980b4e164.png
重定位由两步组成:
(1)重定位节和符号定义。在这一步中,链接器将所有相同类型的节归并为同一类型的聚合节。然后链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。至此程序中每条指令和全局变量都有唯一的运行内存地址。
(2)重定位节中的符号引用。这一步中链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目的模块中称为重定位条目的数据结构。
(3)重定位过程地址盘算方法如下:
https://img-blog.csdnimg.cn/direct/fd7138552a5e406e9cae80afd04487d7.png
5.6 hello的执行流程
通过edb的调试,一步一步地记录下call命令进入的函数
https://img-blog.csdnimg.cn/direct/f860cedbe276469490987c39ebc8f7c7.png
子程序名 程序地址
_init 0x 401000
_start 0x4010f0
__libc_csu_init 0x4011d0
_init 0x 401000
main 0x401125
puts@plt 0x401090
exit@plt 0x4010d0
_fini 0x401248
5.7 Hello的动态链接分析
动态链接的核心理念是将程序分解成多个相对独立的模块,在运行时动态地将它们连接起来形成完整的程序。当程序调用共享库函数时,编译器无法确定函数的确切运行时地址,因为定义该函数的共享模块可以加载到内存的任何位置。因此,编译器会生成一个重定位表来记录这种引用,并在程序加载时,由动态链接器举行解析和处理惩罚。延迟绑定是通过GOT和PLT实现的,根据hello.elf文件可知,GOT起始表位置为:0x404000:
https://img-blog.csdnimg.cn/direct/6f89e87800de4a8d9cb910c701ce51c1.png
GOT表位置在调用dl_init之前0x404008后的16个字节均为0:
https://img-blog.csdnimg.cn/direct/3a51ba687ba34eef8a50148aa04e45aa.png
调用了dl_init之后字节改变了:
https://img-blog.csdnimg.cn/direct/d0605943bb5d4b16bd1fbce0eb107043.png
对于变量而言,利用代码段和数据段的相对位置不变的原则去盘算正确地址。
对于库函数而言,必要plt、got合作。plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。接下来执行程序的过程中,就可以使用过程链接表plt和全局偏移量表got举行动态链接。
5.8 本章小结
本章起首介绍了链接的根本原理和其在程序构建过程中的作用。我们展示了怎样使用命令行举行链接操作,将多个目的文件归并生成可执行文件,以及通过观察可执行文件的 ELF 格式内容来深入理解链接的实现机制。
随后,我们利用 EDB 工具来观察了可执行文件在虚拟地址空间中的分布环境,包括代码段、数据段等,以及它们在内存中的布局和使用环境。
最后,以 hello 程序为例,我们对链接过程、执行过程以及动态链接举行了详细分析。从程序源代码到生成可执行文件的整个过程中,我们深入分析了重定位的概念以及动态链接的实现原理,资助读者更好地理解程序的运行机制和链接过程中的关键步调。
第6章 hello历程管理
6.1 历程的概念与作用
概念:历程的经典定义就是一个执行中程序的实例。历程是盘算机中的程序关于某数据集合上的一次运行活动,是系统举行资源分配和调度的根本单位,是操作系统结构的根本。在传统的操作系统中,历程既是根本的分配单位,也是根本的执行单位。
作用:历程为程序提供了一种假象,程序好像是独占的使用处理惩罚器和内存,处理惩罚器好像是无中断地一条接一条地执行我们程序中的指令。历程作为一个执行中程序的实例,系统中每个程序都运行在某个历程的上下文中。
6.2 简述壳Shell-bash的作用与处理惩罚流程
作用:Shell是一个交互型应用级程序,也被称为命令解析器,它为用户提供一个操作界面,继承用户输入的命令,并调度相应的应用程序。
处理惩罚流程:
[*]从终端读入输入的命令,对输入的命令举行解析
[*]假如该命令为内置命令,则立即执行命令,否则调用fork创建一个新的子历程,在该子历程的上下文中执行指定的程序
[*]判断该程序为前台程序还是后台程序,假如为前台程序则等候程序执行结束,若为后台程序则将其放回后台并返回。在过程中shell可以继承从键盘输入的信号并对其举行处理惩罚。
6.3 Hello的fork历程创建过程
在终端输入./hello的输入命令后,shell举行命令行表明,由于不是内部指令,通过fork函数创建子历程,新创建的子历程险些但不完全与父历程相同,子历程得到与父历程用户及虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈,子历程还得到与父历程任何打开文件,形貌符相同的副本,这就意味着,当父历程调用fork函数时,子历程可以读写父历程中打开的任何文件。
6.4 Hello的execve过程
execve函数在当前历程的上下文中加载并运行一个程序。函数声明如下:
int execve(const char *filename, const char *argv[], const char *envp[]);
execve函数加载并运行可执行目的文件filename,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并不返回。main函数运行时,用户栈的结构如图所示:
https://img-blog.csdnimg.cn/direct/76402341ce1c438285e025ccb71741a1.png
6.5 Hello的历程执行
hello程序在运行时,历程提供给应用程序的抽象有:(1)一个独立的逻辑控制流,它提供一个假象,好像我们的历程独占地使用处理惩罚器;(2)一个私有的地址空问,它提供一个假象,好像我们的程序独占地使用CPU内存。
操作系统提供的抽象有:
(1)逻辑控制流。假如想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目的文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,大概简称为逻辑流。一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发地运行。
(2)上下文切换。操作系统内核使用一种称为上下文切换的叫高层形式的异常控制流来实现多使命。内核为每一个历程维持一个上下文。上下文就是内核重新启动一个被抢占的历程所需状态。
(3)时间片。一个历程执行它的控制流的一部分的每一时间段叫做时间片。因此,多使命也叫做时间分片。
(4)用户模式和内核模式。处理惩罚器通常使用某个控制寄存器中的一个模式位来提供这种功能。当设置了模式位时,历程就运行在内核模式里。一个运行在内核模式的历程可以执行指令会合的所有指令且可以访问系统中的任何内存位置。没有设置模式位时,历程就运行在用户模式中。用户模式中的历程不答应执行特权指令,也不能直接引用地址空间中内核区内的代码和数据。
(5)上下文信息。上下文就是内核重新启动一个被抢占的历程所必要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。hello程序执行过程中,在历程调用execve函数后,历程就为hello程序分配新的虚拟地址空间,开始时程序运行在用户模式中,调用printf函数输出“Hello 2021113211 郑文翔”,之后调用sleep函数,历程进入内核模式,运行信号处理惩罚程序,再返回用户模式,运行过程中,cpu不停切换上下文,使运行过程被切分成时间片,与其他历程瓜代占用cpu,实现历程的调度。
6.6 hello的异常与信号处理惩罚
https://img-blog.csdnimg.cn/direct/c2867034439c4824ae7bcb9b2a04578a.png
https://img-blog.csdnimg.cn/direct/fba278ad433d44cd8fe9c7da4b190d60.png
https://img-blog.csdnimg.cn/direct/e504b180584942f28dcbce22756f2d69.png
https://img-blog.csdnimg.cn/direct/383b9eb6144e4492b4eaa2de708a9b08.png
https://img-blog.csdnimg.cn/direct/6dd7444666874946b8a98a6ea43415fd.png
(1)正常运行状态
在程序正常运行时,打印提示信息,以输入回车为标志结束程序,并回收历程。
https://img-blog.csdnimg.cn/direct/ed6f1c88bd5641eea04edcb985066c59.png
(2)运行时按下Ctrl + C
按下Ctrl + C,Shell历程收到SIGINT信号,Shell结束并回收hello历程。
https://img-blog.csdnimg.cn/direct/9dc653530c714057be726ebe906136bc.png
(3)运行时按下Ctrl + Z
按下Ctrl + Z,Shell历程收到SIGSTP信号,Shell表现屏幕提示信息并挂起hello历程。
https://img-blog.csdnimg.cn/direct/58f1eb10522c400cad51cc23613fc709.png
(4)对hello历程的挂起可由ps和jobs命令检察,可以发现hello历程确实被挂起而非被回收,且其job代号为1。
https://img-blog.csdnimg.cn/direct/24594a4ca35f44a7a6f89adbd5b03dd5.png
(5)在Shell中输入pstree命令,可以将所有历程以树状图表现:
https://img-blog.csdnimg.cn/direct/fb7410e9ad774796a9babdb1ef4778d4.png
(6)输入fg 1则命令将hello历程再次调到前台执行,可以发现Shell起首打印hello的命令行命令,hello再从挂起处继承运行,打印剩下的语句。程序仍旧可以正常结束,并完成历程回收。
https://img-blog.csdnimg.cn/direct/5150fb8db36d41fd90457f9e110657c0.png
(7)不绝乱按
在程序执行过程中乱按所造成的输入均缓存到stdin,当getchar的时间读出一个’\n’末了的字串(作为一次输入),hello结束后,stdin中的其他字串会当做Shell的命令行输入。
https://img-blog.csdnimg.cn/direct/02e04464580a4e898eaef0373102b185.png
6.7本章小结
本章的重要内容是探究盘算机系统中的历程和shell,起首通过一个简单的hello程序,简要介绍了历程的概念和作用、shell的作用和处理惩罚流程,还详细分析了hello程序的历程创建、启动和执行过程,最后,本章对hello程序大概出现的异常环境,以及运行结果中的各种输入举行了表明和说明
第7章 hello的存储管理
7.1 hello的存储器地址空间
1. 逻辑地址
在有地址变换功能的盘算机中,访问指令给出的地址(操作数)叫逻辑地址,也叫相对地址。要经过寻址方式的盘算或变换才得到内存储器中的物理地址。逻辑地址是由一个段标识符加上一个指定段内相对地址的偏移量,由程序hello产生的与段相干的偏移地址部分
2.线性地址
线性地址是逻辑地址到物理地址变换之间的一步,程序hello的代码会产生逻辑地址,在分段部件中逻辑地址是段中的偏移地址,加上基地址就是线性地址。
3.虚拟地址
程序访问存储器所使用的逻辑地址称为虚拟地址。虚拟地址经过地址翻译得到物理地址。与实际物理内存容量无关,是hello中的虚拟地址
4.物理地址
在存储器里以字节为单位存储信息,每一个字节单位给一个唯一的存储器地址,这个地址称为物理地址,是hello的实际地址或绝对地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理是指把一个程序分成多少个段举行存储,每个段都是一个逻辑实体。段式管理是通过段表举行的,包括段号(段名)、段出发点、装入位、段的长度等。程序通过分段划分为多个块,如代码段、数据段、共享段等。
一个逻辑地址是两部分组成的,包括段标识符和段内偏移量。段标识符是由一个16位长的字段组成的,称为段选择符。其中前13位是一个索引号,后3位为一些硬件细节。索引号即是“段形貌符”的索引,段形貌符具体地址形貌了一个段,很多个段形貌符就组成了段形貌符表。通过段标识符的前13位直接在段形貌符表中找到一个具体的段形貌符。
全局形貌符表(GDT)整个系统只有一个,它包含:(1)操作系统使用的代码段、数据段、堆栈段的形貌符(2)各使命、程序的LDT(局部形貌符表)段。
每个使命程序有一个独立的LDT,包含:(1)对应使命/程序私有的代码段、数据段、堆栈段的形貌符(2)对应使命/程序使用的门形貌符:使命门、调用门等。
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存被构造为一个由存放在磁盘上的N个连续的字节大小的单位组成的数组。VM系统将虚拟内存分割,称为虚拟页,雷同地,物理内存也被分割成物理页。利用页表来管理虚拟页,页表就是一个页表条目(PTE)的数组,每个PTE由一个有效位和一个位地址字段组成,有效位表明了该虚拟页当前是否被缓存在DRAM中,假如设置了有效位,那么地址字段就表现DRAM中相应的物理页的起始位置,假如发生缺页,则从磁盘读取。
MMU利用页表来实现从虚拟地址到物理地址的翻译。
下面为页式管理的图示:
https://img-blog.csdnimg.cn/direct/0417ffa101194c6da19fb328b13f70dc.png
7.4 TLB与四级页表支持下的VA到PA的变换
Core i7采用四级页表的层次结构。CPU产生虚拟地址VA,虚拟地址VA传送给MU,MMU使用VPN高位作为TLBT和TLBI,向TLB中寻找匹配。假如命中,则得到物理地址PA。假如TLB中没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。工作原理如下:
https://img-blog.csdnimg.cn/direct/b023265be5864fe7b4fcbdc9b733984a.png
多级页表的工作原理展示如下:
https://img-blog.csdnimg.cn/direct/82f9a19cfc644879927ace6e4b84126f.png
7.5 三级Cache支持下的物理内存访问
如图为高速缓存存储器构造结构:
https://img-blog.csdnimg.cn/direct/2a619787376c4039a0b27fe468c8c6ea.png
高速缓存的结构将m个地址位划分成了t个标志位,s个组索引位和b个块偏移位:
https://img-blog.csdnimg.cn/direct/e03b363f963d4301a7092fab5a6d8830.png
假如选中的组存在一行有效位为1,且标志位与地址中的标志位相匹配,我们就得到了一个缓存命中,否则就称为缓存不命中。假如缓存不命中,那么它必要从存储器层次结构的下一层中取出被哀求的块,然后将新的块存储在组索引位指示组中的一个高速缓存行中,具体替换哪一行取决于替换策略,例如LRU策略会替换最后一次访问时间最久远的那一行。
7.6 hello历程fork时的内存映射
当fork函数被当前历程调用时,内核为新历程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新历程创建虚拟内存,它创建了当前历程的mm_ struct、.区域结构和页表的原样副本。当fork在新历程中返回时,新历程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个历程中的任何一个。厥后举行写操作时,写时复制机制就会创建新页面,因此,也就为每个历程保持了私有地址空间的抽象概念。
https://img-blog.csdnimg.cn/direct/38680d5075204859acf031aadd9eb1f2.png
7.7 hello历程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前历程中加载并运行包含在可执行目的文件hello中的程序,用hello程序有效地替换了当出息序。加载并运行hello必要以下几个步调:
(1)删除已存在的用户区域。删除当前历程虚拟地址的用户部分中的已存在的区域结构。
(2)映射私有区域。为新程序的代码、数据、.bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,.bss区域是哀求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是哀求二进制零的,初始长度为零。
(3)映射共享区域。hello程序与共享对象1ibc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
(4)设置程序计数器。execve做的最后一件变乱就是设置当前历程上下文的程序计数器,使之指向代码区域的入口点。如图所示:
https://img-blog.csdnimg.cn/direct/28889783158647d683b2cb2c58a5f13a.png
7.8 缺页故障与缺页中断处理惩罚
假如程序执行过程中发生了缺页故障,则内核调用缺页处理惩罚程序。处理惩罚程序执行如下步调:
(1)检查虚拟地址是否正当,假如不正当则触发一个段错误,停止这个历程。
(2)检查历程是否有读、写或执行该区域页面的权限,假如不具有则触发掩护异常,程序停止。
(3)两步检查都无误后,内核选择一个牺牲页面,假如该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello历程,再次执行触发缺页故障的指令。
7.9动态存储分配管理
动态储存分配管理使用动态内存分配器(如malloc)来举行。动态内存分配器维护着一个历程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合。每个块就是一个连续的虚拟内存页,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配的状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
动态内存管理的根本方法与策略重要有两种:
1.带边界标签的隐式空闲链表分配器管理
带边界标志的隐式空闲链表的每个块是由一个字的头部、有效载荷、(大概的)额外填充以及一个字的尾部组成。
隐式空闲链表:空闲块通过头部的大小字段隐含地连接着。分配器遍历堆中所有的块,间接地遍历整个空闲块的集合。
当一个应用哀求一个k字节的块时,分配器搜刮空闲链表,查找一个充足大的可以放置所哀求块的空闲块。分配器有三种放置策略:首次适配、下一次适配和最佳适配。分配器在面对释放一个已分配块时,可以归并相邻的空闲块,其中一种简单的方式,是利用隐式空闲链表的边界标志来举行归并。
https://img-blog.csdnimg.cn/direct/3e48caa82668403bacbf494425e8d107.png
2. 显式空间链表管理
显式空闲链表是将堆的空闲块构造成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。举行内存管理。在显式空闲链表中。可以采用后进先出的次序维护链表,将最新释放的块放置在链表的开始处,也可以采用按照地址次序来维护链表,其中链表中每个块的地址都小于它的后继地址,在这种环境下,释放一个块必要线性时间的搜刮来定位符合的前驱。
https://img-blog.csdnimg.cn/direct/850862cec4174385b604ddb97886ce3f.png
Printf会调用malloc,请简述动态内存管理的根本方法与策略。
7.10本章小结
本节内容重要涉及以下方面:
1.存储器地址空间:介绍了程序在内存中的地址空间布局,包括代码段、数据段、堆、栈等部分。
2.Intel的段式管理:讲解了Intel处理惩罚器使用的段式管理机制,如代码段、数据段、堆栈段等,以及段选择符和段形貌符的概念。
3.页式管理:说明了页式管理的原理,包括页面的大小、页表、页目录等,以及虚拟地址到物理地址的转换过程。
4.虚拟地址到物理地址的转换:详细介绍了在Intel Core i7处理惩罚器上,虚拟地址怎样转换为物理地址的过程,包括分页机制、页表的使用等。
5.物理内存访问:阐述了程序怎样通过虚拟地址访问物理内存中的数据,以及在访问过程中涉及的缓存、TLB等机制。
6.内存映射:分析了在历程创建(fork)和程序替换(execve)过程中,内核怎样将虚拟内存映射到物理内存,以及相干的内存管理操作。
7.缺页故障与缺页中断处理惩罚:探究了当程序访问的页面不在物理内存中时,系统怎样处理惩罚缺页故障,包括页面调度、页面置换等。
这些内容共同构成了对内存管理和地址转换相干机制的全面介绍,有助于理解程序在盘算机系统中的内存管理过程。
第8章 hello的IO管理
8.1 Linux的IO装备管理方法
装备的模子化:文件
装备管理:unix io接口
在Linux系统中,所有的输入输出(IO)装备都被抽象为文件,这意味着它们可以像操作平凡文件一样被访问和操作。这种文件抽象的方法使得Linux内核可以提供一个简单而统一的应用接口,称为Unix I/O。通过这个接口,应用程序可以使用相同的方式举行文件操作,包括打开文件、移动文件指针、读写文件以及关闭文件。
8.2 简述Unix IO接口及其函数
Unix I/O 接口:
1.打开文件:应用程序通过哀求内核打开特定文件,得到一个称为文件形貌符的小型非负整数,用于标识该文件。这个文件形貌符在后续的文件操作中唯一标识该文件,并且内核会记录与该打开文件相干的所有信息。对于每个历程,内核会默认打开三个文件:标准输入、标准输出和标准错误。
2.改变当前文件位置:对于每个打开的文件,内核维护一个文件位置指针(通常称为文件偏移量),初始位置为0,表现文件的开头。应用程序可以通过执行 seek 操作来显式地改变当前文件位置。这个操作答应程序以字节为单位在文件中举行随机访问。
3.读写文件:读取操作从文件中复制肯定数量(大于0)的字节到内存中,从当前文件位置开始,然后将文件位置指针移动到读取的最后一个字节的下一个位置。假如文件位置指针高出了文件大小,则会触发EOF(End of File)标志。雷同地,写操作将肯定数量(大于0)的字节从内存复制到文件中,同样从当前文件位置开始,然后更新文件位置指针以反映写入的数据。
4.关闭文件:当不再必要访问某个文件时,应用程序可以关照内核关闭该文件。内核会释放与打开文件相干的数据结构,并将文件形貌符返回到可用的形貌符池中,以便其他文件可以使用。
函数:open():打开文件大概装备,并返回一个文件形貌符,用于后续对该文件的读写操作。
close():关闭一个已经打开的文件形貌符。
read():从文件中读取数据到缓冲区中。
write():将数据从缓冲区写入到文件中。
lseek():在文件中移动文件指针的位置,用于定位读写位置。
fcntl():对文件形貌符举行各种控制操作,如设置文件状态标志、获取文件状态等。
8.3 printf的实现分析
起首看printf函数体:
https://img-blog.csdnimg.cn/direct/4f7f268b3c7e459a95c800258444ce67.png
形参列表中的…是可变形参的一种写法,当传递参数的个数不确定时,用这
种方式来表现。
va_list 的定义:typedef char *va_list,说明它是一个字符指针,其中 (char*)(&fmt) + 4) 即 arg 表现的是...中的第一个参数。
再看vsprintf函数:
https://img-blog.csdnimg.cn/direct/2c98781a972245fa8ab35bceb4bb585c.png
https://img-blog.csdnimg.cn/direct/a74d4ef566e0415dae194d3f4da2c9b3.png
https://img-blog.csdnimg.cn/direct/16d62d4a1b974460a51bce9194134361.png
vsprintf函数的作用是将格式化的字符串生成到一个指定的缓冲区中,并返回生成的字符串的长度。它继承一个格式字符串fmt和一个参数列表args,将这些参数按照fmt的格式化要求,生成相应的字符串,然后存储到指定的缓冲区中。而printf函数则将这个生成的字符串输出到终端上。因此,vsprintf函数在字符串的生成和格式化方面起到了关键作用,为printf函数提供了所需的字符串内容。
最后wirte函数将buf中i个元素写到终端
https://img-blog.csdnimg.cn/direct/e57b9d42df7b4bb9add9bd1c28505e24.png
在 write 函数中,将栈中参数放入寄存器,ecx 是要打印的元素个数,ebx 存放buf中的第一个元素地址,int INT_VECTOR_SYS_CALLA 代表通过系统调用 syscall,检察 syscall 的实现
https://img-blog.csdnimg.cn/direct/891285fa723a4add9363777f5a574bf2.png
syscall答应将文本数据从处理惩罚器的寄存器经过总线传输到图形处理惩罚器的显存中。这些数据通常是以ASCII码的形式存储的字符。表现驱动程序负责将ASCII码转换为字模,并将这些字模映射到表现装备的视频RAM(VRAM)中。VRAM中存储着每个像素的RGB颜色信息,以便在屏幕上表现出相应的图像。表现芯片以固定的革新频率逐行扫描VRAM,并通过信号线将每个像素的RGB分量传输到液晶表现器,从而将图像呈现在屏幕上。
8.4 getchar的实现分析
异步异常-键盘中断的处理惩罚:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生中断哀求,调用键盘中断处理惩罚子程序,程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成ASCII码,生存到系统的键盘缓冲区之中。
getchar调用了系统函数read,通过系统调用read读取存储在键盘缓冲区中的ASCII码直到读到回车符然后返回整个字串。
8.5本章小结
本章重要介绍了linux的IO装备管理方法和及其接口和函数,对printf函数和getchar 函数的底层实现有了根本了解
结论
"hello"程序的执行过程可以概括如下:
1.源代码编写:程序员通过键盘输入编写了"hello"程序的源代码。
2.预处理惩罚:源代码经过预处理惩罚器处理惩罚,包含在代码中的所有宏、文件包含等都会被睁开和处理惩罚,生成一个经过修改的文件。
3.编译:经过预处理惩罚后的文件被编译器编译成汇编代码文件。
4.汇编:汇编器将汇编代码转换成呆板可以执行的指令,生成目的文件。
5.链接:链接器将目的文件与所需的库文件链接在一起,解析符号引用,生成可执行文件。
6.运行:在shell中输入"./hello 2021113211 郑文翔"命令,启动"hello"可执行文件。
7.创建历程:假如命令不是shell内置命令,则shell会调用fork函数创建一个新的子历程来执行该命令。
8.加载程序:通过execve函数,操作系统加载可执行文件到内存中,并执行程序入口,即main函数。
9.执行指令:CPU按照程序指令次序执行程序的逻辑流程。
10.访问内存:程序中的虚拟内存地址通过页表映射成物理地址,实现对内存的访问。
11.信号管理:用户可以通过输入Ctrl+c或Ctrl+z发送信号给历程,内核会相应地处理惩罚这些信号,例如停止历程或挂起历程。
12.停止:当程序执行完成时,操作系统会回收历程所占用的资源,并将退出状态传递给父历程。此时,为该历程创建的所有数据结构都会被删除。
附件
文件名 功能
hello.c :源程序
hello.i :预处理惩罚后得到的文本文件
hello.s :编译后得到的汇编语言文件
hello.o :汇编后得到的可重定位目的文件
hello.elf :用readelf读取hello.o得到的ELF格式信息
hello.asm :反汇编hello.o得到的反汇编文件
hello1.asm :反汇编hello可执行文件得到的反汇编文件
hello :可执行文件
参考文献
Randal E.Bryant David R.O'Hallaron.深入理解盘算机系统(第三版).机械工业出版社,2016.
https://www.cnblogs.com/buddy916/p/10291845.html
https://www.cnblogs.com/pianist/p/3315801.html
https://www.cnblogs.com/fanzhidongyzby/p/3519838.html.
https://www.cnblogs.com/diaohaiwei/p/5094959.html
https://blog.csdn.net/spfLinux/article/details/54427494
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]