摘 要
该论文从一个最基础的 hello.c 程序开始,遍历其从天生到运行的各个阶段,涵盖了预处理、编译、汇编、链接、历程管理、存储管理和IO管理等差别的主题。每个章节都包括了对该主题的概念与作用的论述,以及在Ubuntu下相关下令的示例和实践结果的解析。通过以上内容,对课本教授内容进行细致的回顾,了解了一个程序的始末,对整个计算机体系有了更深入的了解。
关键词:预处理;编译;汇编;链接;历程管理;存储管理;IO管理。
目 录
第1章 概述
1.1 Hello简介
1.2 情况与工具
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.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的下令
4.3 可重定位目的elf格式
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.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.2 简述Unix IO接口及其函数
8.3 printf的实现分析
8.4 getchar的实现分析
8.5本章小结
结论
附件
参考文献
第1章 概述
1.1 Hello简介
根据Hello的自白,使用计算机体系的术语,简述Hello的P2P,020的整个过程。
Program to Progress: 原文件 hello.c 经过预处理器(cpp)预处理后成为 hello.i 文件;接着经过编译阶段将预处理后的文件翻译为汇编语言,由编译器完成,天生一个汇编代码文件 hello.s ;然后,再通过汇编器将汇编代码转换为机器可以实行的二进制文件 hello.o ;末了,链接器将目的文件 hello.o 与库中的所需文件组合形成可实行文件 hello 。然后即可在 bash 中运行该程序,创建了一个对应的历程,即为 progress 。
0 to 0:在 bash 中运行 hello 程序,bash 为其创建一个新的子历程,子历程中调用execve()函数,加载 hello 程序,CPU 为运行的 hello 分配时间片实行逻辑控制流。程序运行竣事后采取历程,开释内存并删除程序相关的数据结构。
1.2 情况与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件情况,以及开发与调试工具。
硬件:x64 AMD Ryzen 7 5800H with Radeon Graphics 3.20 GHz 16G RAM
软件:Windows10 64 位,Ubuntu 22.04
工具:gcc,vim,gdb
1.3 中间结果
列出你为编写本论文,天生的中间结果文件的名字,文件的作用等。
文件名称
| 文件作用
| hello.i
| 预处理后产生文件
| hello.s
| 编译后产生文件
| hello.o
| 汇编后产生文件
| hello
| 链接产生的可实行文件
| helloasm.txt
| hello.o反汇编产生的文本文件
|
1.4 本章小结
本章类似一份总结,先容了 hello 程序实现运行的团体流程,并先容了本次实验使用到的软硬件和工具,末了列出了本次大作业所产生的各种中间文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理是编译过程中的第一个阶段,其主要目的是对源代码进行处理,天生供编译器进一步处理的中间代码。预处理器负责处理源代码中的预处理指令,如 #include、#define、#ifdef、#ifndef 等,并对其进行睁开或处理。预处理器并不关心语法、变量、函数等具体语言特性,它只是简朴地实行指令,并将结果传递给编译器。
作用:
包含文件:使用#include 指令将其他文件的内容包含到当前文件中,便于模块化开发和代码重用。
宏替换:使用 #define 指令定义宏,可以用来表示常量、函数等,并在代码中进行替换,进步代码的可读性和机动性。
条件编译:使用 #ifdef、#ifndef、#if 等指令对代码进行条件编译,根据差别的条件编译差别的代码,实现跨平台兼容性或调试等功能。
2.2在Ubuntu下预处理的下令
应截图,展示预处理过程!
图 1 预处理下令
2.3 Hello的预处理结果解析
预处理器根据头部指令 #include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>将对应文件内容都插入到了 hello.i 文件中,同时将源代码中的注释全部去除。末了得到的 hello.i 文件达到了三千多行。
图 2 hello.c 的预处理结果
2.4 本章小结
预处理后的 hello.i 文件与原始的 hello.c 文件相比,会更大且包含更多的内容,因为它包含了所有预处理操纵的结果。这些改变使得编译器能够更好地明白源代码并天生对应的目的文件。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到天生汇编语言程序
概念:编译是将高级语言源代码转换为目的代码(机器语言)的过程。在编译过程中,编译器将源代码翻译成与特定硬件平台兼容的目的代码,这个过程包括词法分析、语法分析、语义分析、优化和代码天生等步骤。
作用:编译的主要作用是将人类可读的高级语言代码转换为计算机可实行的机器代码,使得计算机能够明白和实行这些指令。
3.2 在Ubuntu下编译的下令
应截图,展示编译过程!
图 3 编译的下令
3.3 Hello的编译结果解析
此部门是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操纵的。应分3.3.1~ 3.3.x等按照类型和操纵进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操纵,都应解析。
hello.c 文件中出现的数据类型有常量,局部变量,全局变量,涉及的操纵有数据、赋值,算术操纵,关系操纵,数组/指针/结构操纵,控制转移以及函数操纵。
3.3.1 数据
常量:
.LC0 定义了一个字符串常量,同时 hello.s 中还有以立刻数表示的各种常量用来进行比较或赋值,记载方式为 $x。
图 4 常量示例
全局变量:
.globl main:这是一个全局声明指令,它声明白一个全局的符号 main。main是一个函数类型的全局变量。
图 5 全局变量
局部变量:
在 main 函数中,用为局部变量分配栈空间,分析代码可知 i 为一个局部变量。
同时有subq $32, %rsp:为局部变量分配 32 字节的栈空间。
接着movl %edi, -20(%rbp) 和 movq %rsi, -32(%rbp):将 main 函数参数 argc 和 argv 存储到栈中。
图 6 局部变量
3.3.2 赋值
通过 mov 指令进行赋值操纵,例如将参数值存储到局部变量中。
图 7 赋值示例
3.3.3 算数操纵
分析代码内容可知,存在一个 for 循环中,对应变量 i 每次循环进行一次加一操纵。
图 8 加法操纵
3.3.4 关系操纵
关系操纵有两种,一种是不等关系“!=”。判断参数个数是否等于 5。通过 cmp 指令进行比较操纵。如果参数为 5,则跳转到 .L2 。
图 9 判断是否为5
另一种是 for 循环体中,小于关系“<”,小于 10 用小于等于 9 表示。判断 i 是否小于等于 9。如果循环变量小于等于 9,则跳转到循环体。
图 10 判断 i 是否小于等于 9
3.3.5 数组/指针/结构操纵
为局部变量分配栈空间时使用到了栈指针。同时通过 add 指令对地址进行偏移,实现了数组/指针的访问即访问argv[1]到argv[3]。包括参数传递和函数调用时也涉及到指针的操纵。
字符串数组首地址加载到寄存器 rax ,接着进行加 8 操纵实现地址偏移,指向第二个元素,再将该指针值放入 rsi ,printf 函数调用打印对应数组元素。
图 11 指针访问数组
3.3.6 控制转移
通过条件跳转指令(如 je 和 jle)实现条件控制流程。程序中实现的有 if 判断和 for 循环。
if 判断:判断参数个数是否等于 5。通过 cmp 指令进行比较操纵。如果参数为 5,则跳转到 .L2 。
图 12 if 判断
for 循环:循环体中直到满足循环变量大于9,就跳出循环,否则使用 jle 语句重新开始新一轮迭代。
图 13 for 循环控制转移
3.3.7 函数操纵
函数调用:通过 call 指令调用外部函数(如 puts, exit, printf, atoi, sleep, getchar)。
图 14 调用外部函数示例
参数传递:main 函数接收到传递的两个参数,一个是 int argc,一个是 char* argv[]。
图 15 接收传递的参数
3.4 本章小结
通过对产生的汇编语言文件的分析,详细了解到编译器是如何对程序中的各种数据结构,各种操纵进行明白和处理的。对其将高级语言转化为低级汇编语言的过程更加清楚。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到天生机器语言二进制程序的过程。
概念:汇编是使用汇编器(as)将汇编指令逐条映射到机器指令,使其成为机器可识别的形式。
作用:可将 hello.s 翻译为机器语言指令,并把这些指令打包成一种叫做可重定位目的程序的格式储存在 hello.o 文件中,得到一个二进制文件。
4.2 在Ubuntu下汇编的下令
应截图,展示汇编过程!
图 16 汇编下令
4.3 可重定位目的elf格式
分析hello.o的ELF格式,用readelf等列出其各节的根本信息,特别是重定位项目分析。hello.o 的DLF格式,包括ELF头、节头、程序头、段节、重定位节等内容。
4.3.1 ELF头
描述内容在下令面板中均有体现,不再赘述。
图 17 ELF头
4.3.2 节头
图 18 节头
4.3.3 程序头
图 19 程序头
4.3.4 重定位节
重定位节包括以下内容:
偏移量:指示需要进行重定位操纵的代码在.text大概.data中的偏移量。
重定位类型:描述需要进行的重定位操纵的类型,例如绝对地址、相对地址、符号引用等。
加数:计算重定位位置的辅助信息
信息:包括与重定位操纵相关的其他数据,比如偏移量、符号信息等。
图 20 重定位节
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操纵数与汇编语言差别等,特别是分支转移函数调用等。
机器语言的构成:机器语言是计算机可以直接实行的指令序列,由二进制数字组成。每条指令都被编码为一个特定的二进制模式,这些模式对应于差别的操纵和操纵数。
机器语言与汇编语言的映射关系:汇编语言是机器语言的助记符表示法,它使用易于明白和记忆的助记符来代替机器语言中的二进制指令。汇编语言程序由一系列指令和操纵数组成,每条指令对应一个机器语言指令。
对照分析得到的差别点:
分支转移:反汇编代码跳转指令的操纵数使用的不是段名称如.L3,段名称只是在汇编语言中便于编写的助记符,在汇编成机器语言之后就不存在,以是反汇编跳转指令的操纵数是具体的地址。
函数调用:在 hello.s 文件中,进行函数调用只需直接 call 对应的函数名称,但反汇编得到的机器码中 call 的对象为下一条指令,该指令在链接器处理后通过重定位才气正确到达函数的实行地址。在汇编成为机器语言的时间,对于不确定地址的函数调用,将其 call 指令后的相对地址设置为全 0(目的地址正是下一条指令),然接着在.rela.text 节中为其添加重定位条目,等待静态链接的进一步确定。
图 21 反汇编结果
4.5 本章小结
本章通过指令实现hello.s 进行汇编得到hello.o,并分析了 hello.o 的ELF格式。接着又对 hello.o 进行反汇编操纵得到 helloasm.txt ,通过将其与 hello.s 对比分析了机器指令与汇编指令的差别。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
注意:这儿的链接是指从 hello.o 到hello天生过程。
概念:将多个目的文件或库文件归并成一个可实行文件或共享库的过程。链接器负责解析目的文件之间的符号引用关系,解决符号的重定位,以及天生最终的可实行文件或共享库。
作用:可以使分离编译成为现实,将一个巨大的源文件分成更小的模块进行编写,当我们改变这些模块中的一个时,只需简朴地重新编译它,并重新链接应用,而不必重新编译其他文件。
5.2 在Ubuntu下链接的下令
使用ld的链接下令,应截图,展示汇编过程! 注意不只连接hello.o文件。
图 22 链接下令
5.3 可实行目的文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的根本信息,包括各段的起始地址,大小等信息。
ELF头部开始是16个字节构成的魔数,接着是ELF头的大小、目的文件类型、机器类型、节头部表的文件偏移及此中条目的信息。
图 23 hello 的 ELF 头信息
节头中包括了每一节的名称、类型、地址和偏移量。
图 24 节头信息
readelf -l hello 查看 hello 的程序头部门。程序头中体现段的起始地址、大小、类型等内容。
图 25 程序头信息
5.4 hello的虚拟地址空间
使用edb加载hello,查看本历程的虚拟地址空间各段信息,并与5.3对照分析说明。
加载 hello 到 edb ,data dump中体现虚拟地址空间的相关内容。在节头信息中选择一个类型为 .text 的段:
图 26 段信息
其在 edb 中的对应部门如下:
对比 readelf 输出和 EDB 中的虚拟地址空间信息,发现它们是同等的。从0x4010f0开始到0x4011c8。反面内容也照应readelf 提供了一个下令行的方式来查看 ELF 文件的各个段的根本信息,而 EDB 提供了一个图形化界面来查看历程的虚拟地址空间信息。两者都能够体现代码段、数据段、堆、栈等信息,并且它们在虚拟地址空间中的起始地址和大小应该是符合的。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的差别,说明链接的过程。
联合hello.o的重定位项目,分析hello中对其怎么重定位的。
hello 的反汇编文件中指令对应的有唯一的虚拟地址,而hello.o的反汇编文件中对应的是对 .text 段的偏移地址。
链接过程中,链接器在天生可实行文件时会将程序的各个部门(如代码段和数据段)分配到虚拟内存地址空间中的差别区域。hello经过链接,每条指令分配了唯一的虚拟地址。操纵体系会根据链接器指定的虚拟地址空间分配情况将程序加载到适当的位置。
图 27 两种反汇编文件
5.6 hello的实行流程
使用gdb/edb实行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
edb 查看结果如下,名称与地址如图所示:
图 28 edb 查看实行流程
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb/gdb调试,分析在动态链接前后,这些项目的内容变革。要截图标识说明。
动态链接项目通常包括共享库的路径、符号表、重定位表等。这些信息存储在程序的 ELF 头部及相关的段中。动态链接之后,程序会动态加载并链接共享库,这会导致程序的内存布局发生变革。使用调试器 gdb 来分析程序在动态链接前后的内存布局变革,以及动态链接项目的加载情况。下面是通过gdb查看,动态链接过程中,共享库的变革和内存信息变革:
图 29 共享库的变革
图 30 内存布局变革
5.8 本章小结
本章先容了链接的概念与作用,并对其ELF格式进行了分析和比较,接着通过 edb 调试分析了链接的过程和重定位的方法。反面又对重定位和动态链接进行的更深入的探究,加深了相关明白。
(第5章1分)
第6章 hello历程管理
6.1 历程的概念与作用
概念:历程是计算机中正在运行的程序的实例。每个历程都有自己的地址空间、内存、数据栈、文件描述符等资源。历程之间是相互独立的,彼此不受影响。
作用:能够并发实行进步了体系资源的使用率,操纵体系通过历程来管理体系资源,如内存、CPU 时间等,以确保各个历程能够公道地共享和使用资源。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是用户与操纵体系内核之间的接口,用户通过壳与操纵体系进行交互。bash 作为常见的 Linux 情况下的壳,可以接收用户下令并传递给操纵体系实行。
处理流程:
等待用户输入: bash 不停等待用户输入下令。
解析下令: 当用户输入下令后,bash 会对下令进行解析,识别下令名称、参数等。
实行下令: bash 根据解析结果调用相应的程序或体系调用实行用户的下令。
输出结果: 实行完下令后,bash 将结果输出到屏幕上,并继续等待用户的输入。
6.3 Hello的fork历程创建过程
程序开始实行时,存在一个主历程(父历程)。在 Hello 程序中,调用 fork() 函数会创建一个新的历程。该函数会返回两次,一次在父历程中返回新创建的子历程的历程 ID,另一次在子历程中返回 0。在调用 fork() 后,父历程和子历程将实行类似的代码,但它们有各自独立的内存空间。由于 argc 不等于 5,以是会实行条件判断语句 if(argc!=5),这会导致程序输出 "用法: Hello 学号 姓名 手机号 秒数!" 并退出。在子历程中,由于 fork() 返回值为 0,子历程会实行 for 循环中的代码块。子历程会输出带有学号、姓名和手机号的 "Hello" 信息。接着子历程会调用 sleep(atoi(argv[4])),停息指定的秒数。子历程会重复实行 for 循环,直到循环竣事。父历程在实行条件判断后输出提示信息并退出;子历程在循环竣事后实行 getchar(),等待用户输入字符,然后返回 0,竣事实行。
6.4 Hello的execve过程
调用 execve 时,操纵体系会加载并实行 hello ,构建新程序的参数列表,即 argv (字符串数组,包含了新程序的下令行参数)和 envp(字符串数组,包含了新程序的情况变量)接着开始实行新程序的代码。如果 execve 调用乐成,它将不会返回到原来的程序,只有当找不到可实行目的文件时才会但回到调用程序,否则execve函数会将控制转移到新的可实行文件去实行。
调用 execve 通常是在创建子历程后,子历程调用 execve 来实行另一个程序。如许做可以实现程序的替换效果
6.5 Hello的历程实行
联合历程上下文信息、历程时间片,论述历程调度的过程,用户态与核心态转换等等。
在终端中输入可实行目的文件hello及其参数,操纵体系会为调用fork()函数创建一个子历程。在这个新历程的上下文中调用execve函数加载可实行目的文件hello。此时,父历程fork() 返回子历程的 PID。子历程fork()返回 0。
用户模式和内核模式,设置模式位时,历程运行在内核模式,内核模式下的历程能够实行指令会合的任何指令,并且可以访问体系中任何内存位置。没有设置模式位时,历程就运行在用户模式中,用户模式下的历程不允许实行特权指令,也不允许用户模式中的历程直接引用地址空间中内核区内的代码和数据。
当 hello 程序实行体系调用(如 sleep)时,会触发从用户态到核心态的转换。在核心态下,操纵体系会处理体系调用,例如设置定时器以实现就寝功能。完成后,操纵体系将控制权返回到用户态,hello 程序继续实行。
6.6 hello的异常与信号处理
hello实行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等下令,请分别给出各下令及运行结截屏,说明异常与信号的处理。
回车,Ctrl-Z以及后续下令对应结果如下:
图 31 异常处理
回车只是是结果换了一行,Ctrl-Z 传递 SIGTSTP 信号停息当前历程实行,将其置于后台。ps 下令可查看当前历程状态,jobs体现出当前历程状态,fg 可将历程调回前台继续实行,大概使用 bg 下令将其转移到后台实行。Pstree 下令展示所有历程的树状图。
Ctrl-C结果:
图 32 Ctrl-c 结果
Ctrl-C会终止当前历程的实行。Kill可以杀死指定历程。
6.7本章小结
本章从历程角度先容了 hello 程序的整个历程运行过程,并了解了 shell 的相关内容,对历程的知识有了更深的了解。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
联合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:逻辑地址是指程序中使用的地址,hello 程序中,逻辑地址用于访问变量、函数等程序中的各个部门。但逻辑地址根据地址中包含的段标识符和段内偏移量大概别的内容映射到对应的有效地址。
线性地址:是由逻辑地址通过段地址转换后的地址,操纵体系使用页表或段表等数据结构来管理线性地址到物理地址的映射关系。
虚拟地址:在 hello 程序实行过程中,所有的逻辑地址都被转换为虚拟地址,然后由操纵体系将虚拟地址映射到物理地址上。
物理地址:实际存在于计算机内存中的地址,用于访问存储在内存中的数据和指令。虚拟地址通过页表映射到物理地址,这个映射由操纵体系管理。
hello 程序实行过程中,操纵体系会将程序的逻辑地址转换为虚拟地址,并将虚拟地址映射到物理地址上。如许,程序就可以访问内存中的数据和指令了。这个过程是由操纵体系的内存管理单元来完成的,它负责管理虚拟地址到物理地址的映射关系,以及内存的分配和采取等工作。
7.2 Intel逻辑地址到线性地址的变更-段式管理
Intel 的段式管理是一种内存管理技术,用于将逻辑地址转换为线性地址。在段式管理中,内存被划分为多个段,每个段都有一个段基址和段限长。逻辑地址由两部门组成:段选择器和偏移量。段选择器用于选择段,偏移量用于指定段内的具体地址。
段式管理通过将内存划分为多个段,并使用段描述符来管理每个段的基址和限长,实现了逻辑地址到线性地址的转换。
7.3 Hello的线性地址到物理地址的变更-页式管理
页式管理中,内存被划分为固定大小的页,而逻辑地址和物理地址都被分割成类似大小的页。当CPU访问内存时,逻辑地址首先被分成页号和页内偏移。然后,页表被用来将页号映射到物理地址的页框号。页框号和页内偏移被组合成物理地址。
7.4 TLB与四级页表支持下的VA到PA的变更
当程序访问一个虚拟地址时,硬件首先尝试在TLB中查找对应的条目。
TLB是一个快速的缓存,存储了最近访问过的VA到PA的映射。
如果TLB掷中(即找到了对应的条目),则可以直接从TLB获取物理地址,并完成地址转换。
如果TLB未掷中,硬件需要在页表中查找VA对应的条目。
页表查找过程如下:
使用VA中的最高位索引PML4,找到PDPT的条目。
使用VA中的下一组高位索引PDPT,找到PDT的条目。
使用VA中的下一组高位索引PDT,找到PT的条目。
使用VA中的下一组高位索引PT,找到最终的页帧(Page Frame)条目。
一旦找到第3级页表中的条目,就可以从该条目中获取页帧号。页帧号与VA中的页内偏移量(offset)组合,形成完整的物理地址。物理地址 = 页帧号 * 页大小 + VA中的偏移量在x86-64架构中,页大小通常是4KB,但也可以是2MB或1GB的大页。
7.5 三级Cache支持下的物理内存访问
三级Cache下的内存访问过程:
CPU 缓存访问:首先会检查 L1 缓存。L1 缓存是距离 CPU 最近的高速缓存,通常包含 CPU 最常用的数据和指令;在 L1 缓存中未找到所需的数据或指令(未掷中),则会继续在 L2 缓存中查找。
L2缓存访问:L2 缓存通常比 L1 缓存更大,但速率稍慢一些。如果在 L2 缓存中找到了数据或指令,则可以直接从 L2 缓存中读取,从而实现较快的访问速率。如果在 L2 缓存中仍未找到所需的数据或指令,则会继续在 L3 缓存中查找。
L3缓存访问:L3 缓存是一个更大、但速率相对较慢的缓存,通常位于 CPU 核心之间或与多个核心共享。L3 缓存中找到了所需的数据或指令,则可以直接从 L3 缓存中读取,固然速率不及 L1 和 L2 缓存,但仍然比访问主存要快得多。如果在 L3 缓存中未找到所需的数据或指令,则会继续访问主存。
主存访问:主存通常是存储在计算机体系中的物理内存,速率比 CPU 缓存慢得多,但容量更大,CPU 将会向主存发出请求,将所需的数据或指令加载到缓存中,以便后续的快速访问。
7.6 hello历程fork时的内存映射
Hello函数调用fork()创建一个新的历程时,新历程会复制父历程的内存映像,包括代码段、数据段、堆和栈等。这种内存映像的复制是通过写时复制技术来实现的,这意味着父历程和子历程在初始阶段共享类似的物理内存页。
7.7 hello历程execve时的内存映射
hello 历程调用execve()函数时,它会加载一个新的程序映像到其地址空间,替换当前的历程映像。xecve()实行后,原始的"hello"历程将会完全被替换为新的程序,且新程序将会在历程的地址空间中占据主导职位。
7.8 缺页故障与缺页中断处理
缺页故障:指的是当程序试图访问一个虚拟内存页,而这个页当前不在物理内存中时发生的情况。
缺页中断处理:CPU会停息当前程序的实行,并跳转到操纵体系内核中预先定义好的缺页中断处理程序。中断处理程序会分析引起缺页中断的缘故原由。可能的缘故原由包括页面不在内存中、页面被换出到磁盘、页面权限错误等。中断处理程序会根据缺页缘故原由采取适当的措施来解决标题。可能的操纵包括将页面从磁盘加载到内存、将被替换的页面写回到磁盘、更新页表等。页面被乐成加载到内存中,中断处理程序将更新页表,以便将虚拟地址映射到新加载的物理页面。一旦所需的页面已经在内存中并且页表已更新,中断处理程序将恢复程序的实行。程序将重新实行引发缺页中断的指令,这次可以乐成访问所需的页面。
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的根本方法与策略。
动态内存分配器在程序启动时通常会初始化一个内存池,这是一个预先分配的内存区域。内存池的大小可以根据需要动态调整,大概在编译时指定一个固定大小。程序需要分配内存时,它会调用动态内存分配器提供的接口函数(如malloc()或new),并传递所需内存的大小。动态内存分配器会搜刮内存池,以找到足够大且未被使用的内存块来满足分配请求。它可能使用差别的算法来决定选择哪个空闲内存块,如首次适应、最佳适应或最差适应等。找到合适的空闲内存后,动态内存分配器会将其标志为已分配,并返回一个指向该内存块的指针给程序。程序不再需要某个已分配的内存块时,它会调用动态内存分配器提供的开释接口函数(如free()或delete),并传递指向该内存块的指针。动态内存分配器会将已开释的内存块标志为可用,并将其添加到空闲内存列表中,以便在后续的内存分配请求中再次使用。
7.10本章小结
本章多角度分析了 hello 程序运行时的内存分配管理,先容了差别地址的转换方式还有几种映射方法,同时也对hello 历程的内存映射做了解释,末了先容了缺氧中断与缺页中断处理的方式。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
Linux 中,一切都是文件的概念被广泛应用,包括硬件设备。这意味着每个设备都被抽象为一个文件,可以通过尺度的文件 I/O 操纵来访问和控制。设备文件通常位于 /dev 目次下,每个设备都有相应的文件代表。对这些设备文件进行读写操纵,应用程序可以与硬件设备进行通讯。例如,可以通过读写 /dev/sda 文件来对硬盘进行读写操纵。
设备管理:unix io接口
Unix/Linux 操纵体系提供了一组尺度的 I/O 接口,允许应用程序通过文件描述符进行设备管理和 I/O 操纵。通过这些尺度的 Unix I/O 接口,应用程序可以方便地与设备进行通讯,而无需了解底层硬件细节或特定设备的驱动程序实现。
8.2 简述Unix IO接口及其函数
Unix I/O 接口提供了一系列函数和体系调用,用于在 Unix/Linux 体系上进行输入输出操纵。下面是一些基础的函数:
open():打开一个文件,返回文件描述符。
close():关闭一个文件描述符。
read():从文件中读取数据。
write():写入数据到文件。
lseek():在文件中移动文件指针。
fcntl():对文件描述符进行各种控制操纵。
8.3 printf的实现分析
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf天生体现信息,到write体系函数,到陷阱-体系调用 int 0x80或syscall等.
字符体现驱动子程序:从ASCII到字模库到体现vram(存储每一个点的RGB颜色信息)。
体现芯片按照革新频率逐行读取vram,并通过信号线向液晶体现器传输每一个点(RGB分量)。
对与print函数:
printf() 函数继续一个格式化字符串 fmt 和可变数量的参数,函数内部,首先定义了一个缓冲区 buf,用于存储格式化后的字符串。使用 va_list 类型的变量 arg 来处理可变数量的参数。va_list 是一个指向参数列表的指针。调用 vsprintf() 函数,将格式化后的字符串存储到缓冲区 buf 中。vsprintf() 是一个类似于 sprintf() 函数的变种,可以继续一个 va_list 类型的参数列表。调用 write() 体系函数,将缓冲区中的内容写入到尺度输出流中。这里假设 write() 函数继续一个缓冲区和其大小作为参数,将缓冲区中的内容写入到尺度输出流中。末了返回写入到尺度输出流中的字符数目 i。
根据网页中的vsprintf程序,可知vsprintf程序按照格式fmt联合参数args天生格式化之后的字符串,并返回字串的长度。vsprintf的作用为继续确定输特别式的格式字符串fmt。用格式字符串对个数变革的参数进行格式化,进而产生格式化输出。
查看write内容:
给几个寄存器传递了参数,然后以一个int INT_VECTOR_SYS_CALL竣事。INT_VECTOR_SYS_CALL代表通过体系调用syscall,查看syscall的实现:
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码,符体现驱动子程序:从ASCII到字模库到体现vram(存储每一个点的RGB颜色信息)。体现芯片按照革新频率逐行读取vram,并通过信号线向液晶体现器传输每一个点。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。继续按键扫描码转成ascii码,保存到体系的键盘缓冲区。
getchar等调用read体系函数,通过体系调用读取按键ascii码,直到继续到回车键才返回。
getchar() 函数的实现通常是基于尺度输入流的,Unix/Linux体系中,键盘输入会被当作尺度输入流的数据。当你在终端中键入字符时,这些字符会被传递给操纵体系内核,然后内核会触发相应的中断。操纵体系的键盘中断处理程序会负责将键盘输入转换成相应的 ASCII 码,并将其存储在体系的键盘缓冲区中。
当你调用 getchar() 函数时,实际上是在请求从尺度输入流中读取一个字符。getchar() 函数内部通常会调用 read() 体系调用来实现这个功能。read() 体系调用会壅闭程序的实行,直到从文件描述符中读取到指定的字节数大概发生错误。在这种情况下,文件描述符是尺度输入流对应的文件描述符 0。
因此,当你调用 getchar() 函数时,它会等待键盘输入,直到你按下回车键为止。一旦你按下回车键,getchar() 函数会返回缓冲区中的下一个字符,并将其从缓冲区中移除,以便下一次调用时能够读取新的字符。
8.5本章小结
本章先容Linux的IO设备管理,强调以文件模型化设备。Unix提供尺度IO接口,包括open()、close()、read()、write()、lseek()和fcntl()等函数,可方便进行设备管理。深入探讨printf和getchar实现,对明白Unix IO接口原理有资助。
(第8章1分)
结论
用计算机体系的语言,逐条总结hello所经历的过程。
你对计算机体系的计划与实现的深切感悟,你的创新理念,如新的计划与实现方法。
- 预处理: hello.c 经过预处理器对源文件hello.c进行宏睁开、头文件包含、条件编译等处理天生预处理后的文件 hello.i 。
- 编译: 将预处理后的hello.i翻译成汇编代码,天生汇编代码文件 hello.s 。
- 汇编: 汇编阶段将汇编代码翻译成机器可读的目的文件,天生可重定位目的文件hello.o
- 链接: 将各个目的文件及所需的库文件归并成一个可实行文件,天生可实行文件hello。
- 历程管理: shell 运行hello程序,调用 fork() 函数创建历程,hello 在创建的子历程中运行。
- 内存管理: hello 被实行时程序的代码、数据段被加载到对应内存地址。程序加载到内存后操纵体系对其进行分配,还有符号解析和重定位确保程序中的地址引用都指向正确的内存位置。
- I/O管理: 所有的I/O设备(如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对文件的读写操纵。通过尺度的 Unix I/O 接口,应用程序可以方便地与设备进行通讯,而无需了解底层硬件细节或特定设备的驱动程序实现。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
文件名称
| 文件作用
| hello.i
| 预处理后产生文件
| hello.s
| 编译后产生文件
| hello.o
| 汇编后产生文件
| hello
| 链接产生的可实行文件
| helloasm.txt
| hello.o反汇编产生的文本文件
|
(附件0分,缺失 -1分)
参考文献
为完本钱次大作业你翻阅的册本与网站等
[1] 王爽. (2018). 汇编语言(第3版). 人民邮电出书社
[2] Hennessy, J. L., & Patterson, D. A. (2017). Computer Architecture: A Quantitative Approach (6th ed.). Morgan Kaufmann.
(参考文献0分,缺失 -1分)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |