马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
摘 要
本文重要对hello程序从代码部分到程序天生、执行、竣事接纳整个过程举行了具体的先容,可执行文件hello可以或许读取下令行参数,每隔断一段时间打印下令行输入的数据。
从hello的文本开始叙述,经过预处理处罚、编译、汇编和链接天生一个可执行文件,同时对进程调用、内存开销、子进程和父进程、进程接纳和信号处理处罚举行了形貌,并先容文件存储以及CPU调用内存数据的原理(包括高速缓存、RAM、页表、快页表的知识点)。在最后先容了系统级IO,简述了文件的输入和输出。
Hello的一生经历了很多过程,本文结合所学知识逐步解析了各个过程在Linux系统中的实现及背后机制,完整探讨了hello.c从编写完成到终极执行的整个生命周期。
关键词:盘算机系统;hello程序;编译;汇编;链接;进程
(择要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第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简介
P2P(From Program to Process)过程:
对于一个C源文件到目标文件的转化分为四个步调:预处理处罚、编译、汇编和链接:起首经过预处理处罚器cpp举行预处理处罚,天生文本文件hello.i,然后经过编译器ccl天生hello.s汇编程序,接着经过汇编器as天生hello.o文件,最后经过链接器ld将其与引用到的库函数链接,天生可执行文件hello。之后shell系统会利用fork、execve等函数创建新进程并且把程序内容加载,实现程序到进程的转化。
020(From Zero-0 to Zero-0)过程:
程序运行前,shell调用execve函数加载hello程序后,将程序内容载入物理内存。在程序运行竣事后,shell父进程接纳进程,同时内核将控制权转移回shell,之后开释虚拟内存空间并清除相关数据。
图一、编译系统
1.2 环境与工具
1.2.1 硬件环境:
x64 CPU;3.20GHz;16GRAM;
1.2.2 软件环境:
windows10 64位;VMware Workstation Pro16.2.2;Ubuntu 23.04
1.2.3 开发工具:
gcc;vim;edb;objdump;CodeBlocks
1.3 中间结果
列出你为编写本论文,天生的中间结果文件的名字,文件的作用等。
文件名
| 文件含义
| hello.c
| 源文件、文本文件
| hello.i
| 预处理处罚文件
| hello.s
| 汇编文件
| hello.o
| 可重定位目标文件
| hello
| 目标文件
| hello.elf
| 可重定位的elf格式文件
| hello2.elf
| hello的elf格式文件
| hello.asm
| hello的反汇编文件
| hello2.asm
| hello.o的反汇编文件
|
1.4 本章小结
本章重要先容了hello.c P2P和020的过程。写出了本次实行所需的环境和工具以及过程中所天生的中间结果文件。
(第1章0.5分)
第2章 预处理处罚
2.1 预处理处罚的概念与作用
在编译过程中,预处理处罚是编译器的第一个阶段之一,其重要目的是对源代码举行一些预处理处罚操作,以便为后续的编译阶段做预备。预处理处罚器通常执行以下几项使命:
- 移除解释: 将源代码中的解释(如单行解释 // 或多行解释 /* */)去除,以便编译器不会考虑这些解释内容。
- 展开宏定义: 处理处罚源代码中的宏定义,将宏替换为其定义的内容。例如,将#define PI 3.14159替换为代码中的3.14159。
- 包罗头文件: 处理处罚#include指令,将头文件的内容插入到源文件中。这样,可以在源文件中利用其他文件中定义的函数、变量等。
- 条件编译: 处理处罚条件编译指令(如#if、#ifdef、#ifndef、#else、#elif和#endif),根据条件判定编译哪些代码块,排除不需要的部分。
- 删除空行和空格: 去除源代码中的多余空格、空行等,以便提高编译效率和减小天生的目标文件巨细。
2.2在Ubuntu下预处理处罚的下令
应截图,展示预处理处罚过程!
2.3 Hello的预处理处罚结果解析
可以发现代码中对源文件中定义的宏举行了展开,将头文件中的内容包罗到这个文件中。
2.4 本章小结
本章重要先容了hello.c程序的预处理处罚,包括预处理处罚的概念和作用,举行的hello.c文件的预处理处罚展示。预处理处罚作为编译运行的第一步,.i文件可以更加直观的感受到预处理处罚前后源文件的厘革。
第3章 编译
3.1 编译的概念与作用
注意:这儿的编译是指从 .i 到 .s 即预处理处罚后的文件到天生汇编语言程序
概念:编译(Compiling)是指将高级程序代码转换为低级机器代码或其他可执行形式的过程。编译的过程包括了从预处理处罚后的文件(通常以 .i 结尾,表示经过预处理处罚的源文件)到汇编语言程序(通常以 .s 结尾)的转换。编译的重要目标是将高级语言的代码转换为汇编语言代码,以便后续可以通过汇编器和链接器天生可执行程序。
作用:
语法查抄和错误提示:编译器可以或许对源代码举行语法分析,帮助开发者在编码阶段发现并修正题目。
代码优化:编译器可以对源代码举行各种优化,以提高程序的性能、减少资源消耗或改善代码布局。常见的优化包括常量折叠、循环展开、函数内联等。
目标代码天生:编译器将经过语法分析和优化后的源代码转换为目标平台上的机器代码或其他可执行形式,以便在盘算机上运行。
内存管理:编译器大概会负责分配和管理程序运行时所需的内存空间,包括静态内存分配和动态内存分配。
调试支持:一些编译器提供与调试器的集成,可以或许天生调试信息或支持调试信息的检察,帮助开发者在程序出现题目时举行调试。
3.2 在Ubuntu下编译的下令
应截图,展示编译过程!
3.3 Hello的编译结果解析
3.3.1 数据
本程序中只有常量和局部变量。
常量(字符串常量)
c程序里有两处:"用法: Hello 学号 姓名 秒数!\n"和"Hello %s %s\n"
数字常量
局部常量:在汇编语言中被放在寄存器或栈上
3.3.2 赋值
movq %rax, %rax将%rax中的值赋给%rax
3.3.3 算数操作
subq $32, %rsp减去栈指针的值
3.3.4 关系操作
cmpl $5, -20(%rbp)比力-20(%rbp)中的值和5的巨细
3.3.5 控制转移
je用于判定cmpl比力的结果,若两个操作数的值不相等则跳转到指定地点。
3.3.6 逻辑/位操作:
根据比力结果跳转到.L2
3.3.7 函数操作
参数通报:传入参数argc和*rgv[],分别用寄存器%rdi和%rsi存储。
函数调用:被系统启动函数调用。
函数返回:设置%eax为0并且返回,对应return 0
3.4 本章小结
本章先容了编译的概念和作用,实行对预处理处罚后的代码举行了编译得到hello.s文件,分析了编译的结果,对编译过程和汇编语言有了更加深入的认识。
第4章 汇编
4.1 汇编的概念与作用
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到天生机器语言二进制程序的过程。
汇编是指汇编器将以.s结尾的汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式,终极结果保存在.o目标文件中的过程,这些文件可以进一步链接成终极的可执行程序。
汇编语言的重要作用包括:
直接控制硬件:汇编语言答应程序员直接控制盘算机的硬件资源,这使得编写底层系统程序和设备驱动程序成为大概。
优化性能:由于汇编语言可以更精确地控制盘算机的底层操作,因此程序员可以编写更高效的代码,以优化程序的性能和资源利用率。
明白底层原理:通过学习汇编语言,程序员可以更深入地明白盘算机的底层工作原理,包括指令执行、内存管理和寄存器操作等方面。
嵌入式系统开发:在嵌入式系统开发中,通常需要对硬件举行直接控制,而汇编语言是实现这一目标的紧张工具之一。
4.2 在Ubuntu下汇编的下令
(以下格式自行编排,编辑时删除)
应截图,展示汇编过程!
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的根本信息,特别是重定位项目分析。
ELF文件的标识符:7f 45 4c 46。02 01表示ELF文件的种类,01表示32位,02表示x86-64系统,01表示字节序,背面为保留字段,无含义。
节头信息:
变量类型:
.rodata--只读数据段,对应C语言类型中的const类型
printf中的字符串--常量类型
.string--字符串类型
.glabal以及.main--局部变量
循环操作:
jmp指令--根据c语言源代码我们可以推得这是循环开始的一个操作,在L3中会与循环条件比对并判定是否继承执行。不难发现这里的jmp(jle)后跳转的地点都是一个参数,而不是具体的地点,其根本原因是还需要后续编译器提供现实的地点。
数据操作:
cmp--将两个数据举行比力,并根据比力结果改变条件码的数据。
mov--将一个寄存器的数据移动到另一个寄存器
add--加法
函数调用:
call printf@PLT,call getchar@PLT--因为printf和getchar都还未重定位,所以这里用一个指代完成对这两个函数的调用。
返回值:
ret--退出当前指令并返回返回值,一般返回值默认保存在rax寄存器中
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,与第3章的 hello.s举行对照分析。
左侧为机器语言反汇编得到的结果,右为汇编程序。
可以看到机器语言程序是由机器指令组成的,反汇编中每一行的1到5个字节不等的16进制数表示就是一条机器指令,对应汇编语言中的一行。机器指令可以是变长的,常用的,操作数少的指令字节数少;不常用的,操作数多的指令字节数多。
机器语言中全部的操作数都是16进制形式的,而汇编语言里操作数可以有10进制形式的。机器语言中call背面跟的是该函数的地点,而汇编语言中call背面跟的是函数名。机器语言中跳转的目标是地点,而汇编语言中跳转的目标是标号。
4.5 本章小结
本章中我们列出的汇编的概念作用,利用汇编和反汇编指令得到了hello.o和hello.elf文件,并对结果举行了说明,与第3章的 hello.s举行对照分析。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接(Linking)是将多个目标文件(object files)或库文件(library files)归并成一个可执行程序或共享库的过程。在编程中,通常会将一个项目分成多个文件来组织代码,每个文件编译后天生一个目标文件。链接器(Linker)负责将这些目标文件中的符号(symbols)解析并连接起来,以创建一个完整的可执行程序或库文件。
链接的重要作用包括:
符号解析:链接器负责解析每个目标文件中引用的符号,包括函数、变量和其他命名对象。它将这些符号解析为内存地点,以便程序可以正确地访问和调用它们。
符号重定位:当程序中的某些符号引用的地点在编译时是未知的(例如,外部函数或变量),链接器负责将这些符号的地点映射到正确的位置,以确保程序在运行时可以或许正确地访问它们。
库链接:链接器还可以将目标文件与库文件(如静态库或动态库)举行链接,以便在程序中利用库中提供的函数和资源。
天生可执行文件或共享库:链接器终极将全部的目标文件和库文件组合在一起,天生一个完整的可执行程序或共享库,供用户执行或其他程序利用。
5.2 在Ubuntu下链接的下令
5.3 可执行目标文件hello的格式
头信息:
节头部表信息(包罗了每一个节的名称、巨细、类型、地点、偏移量等信息)
程序头
段节相关信息
重定位信息
地点信息
5.4 hello的虚拟地点空间
利用edb加载hello,检察本进程的虚拟地点空间各段信息
Data Dump检察虚拟地点:
5.5 链接的重定位过程分析
可以发现,通过反汇编代码中多了很多指令,原因是在编译的时候动态链接将系统中其他的尺度库函数引入。同时hello中无hello.o中的重定位条目,并且跳转和函数调用的地点在hello中都变成了虚拟内存地点。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地点,因此在.rela.text节中为其添加了重定位条目。
在链接过程中,链接器会根据hello.o中的重定位项目,将其中引用的符号与其他目标文件或库文件中的定义举行匹配,并举行相应的地点替换和调解。这样,终极天生的可执行文件hello中的代码和数据就可以正确地与其他模块举行连接和执行。
5.6 hello的执行流程
利用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的全部过程(重要函数),调用与跳转的各个子程序名或程序地点如下:
<_init>:401000
<.plt>:401020
<puts@plt> :401090
<printf@plt>:4010a0
<getchar@plt>:4010b0
<atoi@plt>:4010c0
<exit@plt>:4010d0
<sleep@plt>:4010e0
<_start>:4010f0
<_dl_relocate_static_pie>:401120
<main> :401125
<_libc_scu_init>:4011c0
<_libc_csu_fini>:401230
<_fini>: 401238
5.7 Hello的动态链接分析
可以发现,动态链接之前0x401000地点存的是INIT的虚拟地点,而动态链接后变成了现实地点。
5.8 本章小结
本章先容了链接的概念与作用,也通过在Ubuntu下的下令获得了可执行目标文件hello的格式。在edb中检察了hello的虚拟地点空间,分析反汇编与hello.o的不同后,也对hello的动态链接举行了分析,为下章的操作做了预备。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
在盘算机系统中,进程是指正在运行的程序的实例。每个进程都有自己的内存空间、代码、数据和运行时的状态。进程是操作系统举行资源分配和管理的根本单元,它可以独立运行并与其他进程隔离。
进程的作用:
现代盘算机中给每个进程一个独立的PID,这使得程序员可以更好的调治某个正在运行程序的资源和数据。同时,每个程序独占一个进程可以更好的保护程序内部资源。CPU的一个核只能处理处罚一个进程,这使得盘算机的硬件资源可以或许得到更好的利用。进程为程序提供了两个抽象:逻辑控制流和私有地点空间。逻辑控制流使得每个进程都好像独立占用cpu,而私有地点使得每个程序好像独立占据系统内存。
6.2 简述壳Shell-bash的作用与处理处罚流程
Shell 是一种下令行解释器,用于与操作系统举行交互,它吸收用户输入的下令并将其转换成操作系统可以或许明白的指令。而 bash(Bourne Again Shell)则是其中最常见和盛行的一种 Unix Shell。
Shell 的作用:
提供了用户与操作系统交互的界面,用户可以通过 Shell 输入下令执行各种操作,如创建文件、管理进程、设置环境变量等。
解释并执行用户输入的下令,将其转换成系统调用或其他符合的指令,从而实现用户的需求。
支持脚本编程,用户可以编写 Shell 脚本来主动化执行一系列操作,提高工作效率。
处理处罚流程:
用户在下令行界面输入下令,按下回车键。
操作系统将输入的下令通报给当前正在运行的 Shell 进程。
Shell 解析下令,查抄下令是否存在、语法是否正确,并根据需要执行相应的操作。
如果下令是内置下令(如 cd、echo 等),则 Shell 直接执行对应的功能。
如果下令是外部下令(如 ls、grep 等),则 Shell 会在系统路径中查找可执行文件,并调用相应的程序执行下令。
执行完下令后,Shell 将结果输出到尺度输出(通常是显示在下令行界面上)。
Shell 进入等候用户下一次输入的状态,重复以上流程。
总的来说,Shell 是用户与操作系统之间的桥梁,通过解释和执行用户输入的下令,实现了用户对盘算机系统的控制和操作。
6.3 Hello的fork进程创建过程
起首,系统级父进程init调用fork()函数可以或许创建子进程,子进程可以或许共享父进程的文件资源,在init中由于init是整个系统进程,所以可以看成创建的子进程为单独一个进程,且占用整个内存。Fork()会返回两个值,在父进程中返回子进程pid,在子进程中返回0。
6.4 Hello的execve过程
execve()系统调用用于在当前进程中执行一个新的程序。它现实上是替换了当前进程的映像,而不是像fork()那样创建一个新的进程。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调治的过程,用户态与核心态转换等等。
起首,操作系统会根据指定的路径加载 "Hello" 程序的可执行文件。这个过程涉及文件系统的操作,操作系统会读取程序文件并将其加载到内存中。
在加载程序后,操作系统会为 "Hello" 程序创建一个新的进程。这个新进程将执行 "Hello" 程序的代码,并拥有自己的进程上下文,包括进程 ID、内存空间等。
一旦进程创建完成,操作系统会将程序的执行权限交给这个新的进程。 "Hello" 程序的代码会被加载到进程的内存空间中,并且程序的执行会从程序的入口点开始。
"Hello" 程序开始在新的进程中运行。它大概会执行一系列操作,这取决于程序的实现。在本例中,程序会打印 "Hello"。
一旦程序完成了它的使命,它会通知操作系统,并退出执行。这个时候,操作系统会清理相关资源,包括开释进程所占用的内存空间等。
6.6 hello的非常与信号处理处罚
hello执行过程中会出现哪几类非常,会产生哪些信号,又怎么处理处罚的:
正常情况
不停乱按(不包括回车)
(回车)
Ctrl-Z 利用SIGTSTP信号停止前台作业
Ctrl-C 利用SIGINT信号终止前台进程
Ctrl-Z 后运行ps jobs pstree fg kill
6.7本章小结
在本章中相识的hello进程的执行流程,也展示了 hello执行过程中会出现哪几类非常,会产生哪些信号,以及他们的处理处罚方式。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地点空间
结合hello说明逻辑地点、线性地点、虚拟地点、物理地点的概念。
在盘算机系统中,程序在执行时需要访问内存中的数据和指令,因此需要用到地点的概念,不同的地点代表了不同的抽象层次。
逻辑地点是指进程代码访问内存的地点空间,是由程序天生的地点,由段基址和偏移量组成,与现实物理内存无关。
线性地点是指逻辑地点经过分段、分页机制转换后得到的地点,由形貌符和偏移量组成,地点空间中整数连续,它是虚拟地点空间的地点。
虚拟地点是指应用程序对内存的地点请求,即程序所看到的地点,也就是逻辑地点和线性地点的总和。
物理地点是指现实存在于内存中的地点,通过页表机制由线性地点转换而来。用于内存芯片级的单元寻址,与处理处罚器和CPU连接的地点总线相对应。
执行hello程序时,程序的指令和数据起首被加载到逻辑地点空间,经过分段、分页转换后得到线性地点,终极通过页表机制转换为物理地点,才能被现实执行。
7.2 Intel逻辑地点到线性地点的变换-段式管理
Intel处理处罚器利用分段来管理内存。在这种模式下,逻辑地点由段选择器和偏移量组成。段选择器用于选择一个段,偏移量表示在选定的段中的偏移。
段选择器和偏移量的组合:起首,CPU将逻辑地点中的段选择器和偏移量组合成一个48位的线性地点。这个过程是通过利用段选择器中的索引乘以一个固定值(段形貌符的巨细)来盘算段形貌符的起始地点,然后加上偏移量得到的。
段形貌符的获取:接下来,CPU利用盘算得到的线性地点来访问形貌符表,通常是全局形貌符表或局部形貌符表。每个形貌符都包罗了段的基地点和巨细等信息。
段的基地点和巨细:CPU从形貌符中提取出段的基地点和巨细信息。
线性地点的盘算:CPU将逻辑地点中的偏移量与段的基地点相加,得到线性地点。
访问内存:最后,CPU将线性地点发送到内存管理单元,MMU根据页表或段表将线性地点转换为物理地点,然后用于访问内存。
7.3 Hello的线性地点到物理地点的变换-页式管理
将程序的逻辑地点空间和物理内存划分为等长的页面,这种分配方式便于维护,且不容易产生碎块。
虚拟页面的集合分为三个不相交的子集:
未分配的:VM系统还未分配(大概创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。
缓存的:当前已缓存在物理内存中的已分配页。
未缓存的:未缓存在物理内存中的已分配页。
7.4 TLB与四级页表支持下的VA到PA的变换
(
在盘算机系统中,虚拟地点到物理地点的转换是通过页表来实现的。TLB是一个缓存,用于存储迩来的一些虚拟地点到物理地点的映射,以提高地点转换的速度。四级页表是一种页表布局,将虚拟地点空间分割成多个层级,以便有用地管理大量的内存页。
利用TLB和四级页表举行VA到PA转换的步调:
当CPU执行一个指令时,它会天生一个虚拟地点。
虚拟地点被送入TLB举行查找。如果TLB中有这个虚拟地点的映射,则TLB会返回相应的物理地点。
如果TLB中没有找到对应的映射,CPU会利用虚拟地点的高位来索引四级页表的根节点。
四级页表的根节点会指向下一级页表(大概页表页),依此类推,直到找到最底层的页表项。
最底层的页表项包罗了物理页的地点,通过将虚拟地点的偏移部分与页表项中的偏移部分相加,得到物理地点。
整个过程中,TLB的作用是加快常用的地点映射,减少了对页表的访问次数,从而提高了地点转换的速度。如果TLB中没有找到对应的映射,则需要通过访问页表来完成地点转换,这会增加一定的耽误。
7.5 三级Cache支持下的物理内存访问
在拥有三级缓存的体系布局中,物理内存的访问通常会涉及到多个层级的缓存和内存控制器。
一个根本的物理内存访问过程:
- CPU访问缓存:当CPU需要访问内存中的数据时,起首会查抄最高级别的缓存,即L1缓存。如果所需的数据已经在L1缓存中,则CPU可以直接从缓存中读取数据,这样可以极大地提高访问速度。如果数据不在L1缓存中,CPU会继承查抄更低级别的缓存,如L2缓存和L3缓存,直到找到所需的数据大概在全部缓存中都没有找到。
- 缓存未命中:如果数据在缓存中未命中(不在缓存中),CPU就需要从更慢的主存中读取数据。在这种情况下,CPU会发送一个请求给内存控制器,请求所需的数据。
- 内存控制器访问内存:内存控制器负责管理系统中的内存模块,并相应来自CPU的读写请求。一旦内存控制器收到请求,它会在物理内存中定位所需的数据,并将其传输到CPU的缓存层级中。如果所需的数据已经存在于主存的缓存行中,则内存控制器可以直接将数据传输到CPU的缓存中。
数据返回到缓存:一旦数据从主存传输到CPU的缓存中,CPU就可以在缓
- 存中访问所需的数据。同时,内存控制器也会更新相关的缓存行,以确保缓存中的数据与主存中的数据保持一致。
7.6 hello进程fork时的内存映射
当一个进程调用fork()系统调用来创建一个新的进程时,新进程将会复制父进程的地点空间。这意味着,新进程将会有与父进程雷同的内存映射,包括代码段、数据段、堆、栈等。
具体来说,在fork()调用后,操作系统会执行以下操作来完成内存映射:
- 复制页表:操作系统会复制父进程的页表到新进程的页表中。页表是一种数据布局,用于将虚拟地点映射到物理地点。
- 写时复制:在大多数现代操作系统中,fork()系统调用接纳了写时复制(copy-on-write)的技术。这意味着在新进程被创建时,并不会立刻复制父进程的内存页,而是将这些内存页标记为只读,并且它们与父进程共享雷同的物理内存页。只有在新进程或父进程实行修改其中一个内存页时,操作系统才会将该内存页复制一份,以确保新进程和父进程的内存空间是相互独立的。
- 更新页表:如果发生写时复制,操作系统会更新新进程的页表,使其指向新分配的物理内存页,而不是父进程的共享内存页。
- 返回新进程:最后,操作系统将控制权交给新进程,并开始执行新进程的代码。
7.7 hello进程execve时的内存映射
在执行execve系统调用时,当前进程的内存映射会被清除,并被新的可执行文件所指定的内存映射所替代。这是因为execve系统调用用于加载并执行一个新的程序,因此旧程序的内存映射不再有用。
当调用execve时,操作系统会执行以下步调来创建新的内存映射:
- 清除旧的内存映射:当前进程的全部内存映射将被清除。这包括程序代码、数据、堆栈等区域的映射。
- 加载新的可执行文件:操作系统会将新的可执行文件加载到内存中,创建新的程序代码、数据和其他段的内存映射。这通常包括程序的代码段、数据段、堆、栈等。
- 设置程序入口点:操作系统会设置新程序的入口点,即程序开始执行的位置。
- 初始化堆栈和参数:操作系统会初始化一个新的堆栈,用于新程序的函数调用和局部变量等。将通报给execve的参数通报给新程序。这些参数通常包括下令行参数、环境变量等。
- 执行新程序:一旦全部预备工作完成,操作系统会开始执行新程序,从其入口点开始执行。
7.8 缺页故障与缺页中断处理处罚
当程序试图访问虚拟内存中的某个页面,而该页面并未装入物理内存时,就会发生缺页故障。这时,操作系统需要举行相应的缺页中断处理处罚,以下是缺页中断处理处罚的根本流程:
- 触发缺页中断:当程序访问的页面不在物理内存中时,CPU会产生缺页中断,将控制权交给操作系统内核。
- 操作系统的相应:操作系统内核捕获缺页中断,并开始处理处罚该非常情况。
- 确定缺页位置:操作系统内核起首需要确定引起缺页中断的虚拟页面的位置,包括页面号、进程标识符等信息。
- 查找页面:操作系统内核会查找页面是否在辅存(通常是硬盘大概固态硬盘)中,如果页面已经在辅存中,则需要将其调入到物理内存中。
- 更新页表:一旦页面被调入物理内存,内核需要更新页表,建立虚拟地点到物理地点的映射关系。
- 恢复执行:最后内核会返回到引起缺页中断的指令,并重新执行该指令,这时由于页面已经在物理内存中,程序可以正常访问所需的数据。
缺页中断处理处罚是虚拟内存管理的核心部分,它答应操作系统将程序的部分数据存储在辅存中,从而有用地扩展了可用的物理内存空间。通过将不常用的页面置换到辅存中,并在需要时再次调入到物理内存中,操作系统可以或许更加高效地利用有限的内存资源。
7.9动态存储分配管理
Printf会调用malloc
动态存储分配管理是指操作系统或运行时环境动态地分配和开释内存空间,以满意程序在运行过程中对内存的需求。涉及以下几个方面:
- 内存分配算法:动态存储分配管理需要利用一定的算法来决定怎样分配可用的内存空间。常见的内存分配算法包括首次顺应、最佳顺应和最坏顺应等。这些算法根据空闲内存块的巨细、位置等条件来选择符合的内存块举行分配。
- 内存分配数据布局:为了有用地管理内存分配,需要利用适当的数据布局来跟踪已分配和未分配的内存块。常见的数据布局包括空闲块列表、内存映射表、位图等。
- 内存碎片整理:动态存储分配容易导致内存碎片的产生,造成内存资源的浪费。因此,管理器通常需要实现内存碎片整理机制,通过归并相邻的空闲块来减少内存碎片,以提高内存利用率。
- 内存接纳:动态存储分配管理也需要负责接纳不再利用的内存空间,以便将其重新分配给其他程序利用。内存接纳通常通过开释操作大概垃圾接纳机制来实现。
- 内存保护:在多使命操作系统中,动态存储分配管理还需要考虑内存保护的题目,确保每个程序只能访问其分配的内存空间,防止程序之间相互干扰。
7.10本章小结
本章重要先容了存储器地点空间中各种类型地点的含义,分析了通过段式管理实现逻辑地点到线性地点的变换、页式管理实现线性地点到物理地点变换的过程。还先容了TLB与四级页表及三级Cache下的地点翻译及访问过程,进程调用fork、execve时的内存映射、缺页故障和处理处罚等相关内容。进一步加深了我对程序的存储管理的明白。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
全部的I/O设备都被模型化为文件,而全部的输入和输出都被当做对相应文件的读和写来执行,答应Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
UNIX I/O接口是UNIX操作系统提供的一套用于举行输入和输出操作的接口,它提供了一系列函数来举行文件操作、设备访问等。这些函数通常包罗在不同的头文件中,例如 <stdio.h>、<fcntl.h> 和 <unistd.h> 等。
常用的UNIX I/O函数及其功能:
open():用于打开文件,并返回文件形貌符,如果文件打开失败,则返回-1。函数原型为:int open(const char *path, int flags);
close():关闭一个打开的文件形貌符。函数原型为:int close(int fd);
read():从文件形貌符中读取数据,并将其存储到缓冲区中。函数原型为:ssize_t read(int fd, void *buf, size_t count);
write():将数据从缓冲区写入到文件形貌符中。函数原型为:ssize_t write(int fd, const void *buf, size_t count);
lseek():设置文件形貌符的读/写位置。函数原型为:off_t lseek(int fd, off_t offset, int whence);
fcntl():用于对文件形貌符举行各种控制操作,如设置文件形貌符的属性。函数原型为:int fcntl(int fd, int cmd, ... /* arg */);
ioctl():对设备举行控制操作,如设置设备参数、发送控制下令等。函数原型为:int ioctl(int fd, unsigned long request, ...);
dup() / dup2():复制文件形貌符,使多个文件形貌符指向同一个文件表项。函数原型为:int dup(int oldfd); int dup2(int oldfd, int newfd);
pipe():创建管道,用于在两个进程之间举行通信。函数原型为:int pipe(int pipefd[2]);
select() / poll() / epoll():用于多路复用I/O操作,以便同时监视多个文件形貌符的I/O状态。select() 和 poll() 是传统的方法,而 epoll() 是Linux特有的高性能机制。
8.3 printf的实现分析
从vsprintf天生显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}
这段代码是一个简化版的 vsprintf() 函数实现,它吸收一个格式化字符串 fmt 和一个 va_list 类型的参数列表 args,将格式化后的字符串存储到缓冲区 buf 中。这个函数实现了对 %x 类型的参数的处理处罚,即将整数参数以十六进制格式输出到缓冲区。
工作原理:
char* p;:定义一个指针 p,用于指向缓冲区 buf。
char tmp[256];:定义一个临时数组 tmp,用于临时存储格式化后的字符串。
va_list p_next_arg = args;:将传入的参数列表 args 复制一份给 p_next_arg。
for (p=buf; *fmt; fmt++) { ... }:遍历格式化字符串 fmt,直到遇到字符串竣事符 \0。
if (*fmt != '%') { ... }:如果当前字符不是格式化字符 %,直接将其复制到缓冲区 buf 中。
fmt++;:移动指针,跳过 % 符号。
switch (*fmt) { ... }:根据格式化字符的类型举行处理处罚。
case 'x'::处理处罚十六进制整数的情况。
itoa(tmp, *((int*)p_next_arg));:将整数参数转换为字符串并存储到临时数组 tmp 中。
strcpy(p, tmp);:将临时数组 tmp 中的字符串复制到缓冲区 buf 中。
p_next_arg += 4;:移动参数列表指针到下一个参数。
p += strlen(tmp);:移动缓冲区指针到新添加的字符串的末端。
case 's'::处理处罚字符串的情况,这部分代码没有实现。
default::处理处罚其他格式化字符的情况,这部分代码也没有实现。
return (p - buf);:盘算格式化后的字符串的长度,并返回。
8.4 getchar的实现分析
getchar() 函数通常用于从尺度输入流中读取一个字符。它的实现方式可以根据操作系统和编程语言的不同而异,但一般而言,它会调用底层的系统函数来实现字符的获取。在UNIX或类UNIX系统中,getchar() 大概会利用 read() 系统调用来从尺度输入中读取字符。这个过程中大概会涉及到键盘中断的处理处罚。
异步非常 - 键盘中断的处理处罚:
当用户按下键盘时,键盘会发送一个中断信号给盘算机,通知它有按键事故发生了。
操作系统会相应这个中断,并执行相应的中断处理处罚程序。
中断处理处罚程序会读取键盘控制器中的数据,将按键的扫描码转换成相应的 ASCII 码,然后将这个 ASCII 码放入系统的键盘缓冲区中。
getchar() 的实现:
当程序调用 getchar() 时,它会调用底层的系统函数来获取字符。
在UNIX系统中,通常会调用 read() 系统调用来从尺度输入流中读取字符。
read() 函数会查抄尺度输入流中是否有字符可读。如果没有,它大概会阻塞程序的执行,直到有字符可读大概发生了错误。
当键盘中有字符可读时,read() 函数会从键盘缓冲区中读取一个字符,然后将这个字符返回给调用 getchar() 的程序。
等候回车键的输入:
在大多数情况下,getchar() 函数会一直读取字符,直到吸收到回车键才返回。
这意味着用户在输入字符时,可以在按下回车键之前输入任意数目的字符。
一旦用户按下了回车键,getchar() 就会返回刚刚输入的第一个字符,而别的的字符则会留在输入缓冲区中等候后续的读取。
8.5本章小结
本章重要先容了Linux的IO设备管理方法、Unix IO接口及其函数,分析了printf和getchar函数的实现。
(第8章1分)
结论
hello 程序“艰辛”的一生包罗以下阶段:
- 预处理处罚:预处理处罚器cpp将头文件内容插入程序文本中,完成字符串替换和删除多余空缺字符,天生包罗完整程序代码的预处理处罚文件hello.i。
- 编译:通过词法分析和语法分析等,编译器ccl将hello.i翻译成具备在指令级别上控制硬件资源能力的汇编语言文件hello.s。
- 汇编:汇编器as将汇编程序翻译成机器语言指令,而后打包成可重定位目标程序hello.o。
- 链接:链接器ld将hello.o与动态链接库链接整合为单一文件,天生完全链接的可执行目标文件hello。
- 进程载入:通过Bash键入下令,操作系统为程序fork新进程并通过execve加载代码和数据到为其提供的私有虚拟内存空间,程序开始执行。
- 进程控制:由进程调治器对进程举行时间片调治,并通过上下文切换实现hello的执行,程序计数器(PC)更新,CPU按顺序取指,执行程序控制逻辑。
- 内存访问:内存管理单元MMU将逻辑地点逐步转换成物理地点,通过三级Cache访问物理内存/磁盘中的数据。
- 信号处理处罚:进程吸收信号,调用相应的信号处理处罚函数对信号举行终止、停止、前/后台运行等处理处罚。
- 进程接纳:Shell等候并接纳子进程,内核删除为进程创建的全部资源。
盘算机系统的设计与实现涉及多个领域,包括硬件、操作系统、编译器、网络等。即使是一个简单的 hello.c也需要操作系统综合其他部分举行很多复杂的操作,并且每一步都经过了设计者的深图远虑,需要在有限的硬件资源下尽大概地提高了程序的时间和空间性能。
在盘算机系统这门课上的学习和实操让我深刻认识到盘算机系统的复杂性和紧张性,它们是现代科技和生存的基石。深入明白盘算机系统的设计原理和实现细节有助于我们提高代码编写和性能优化的能力,更好的提拔作为新医工专业学生的专业素养,服务康健中国战略。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
共九个
hello.c:hello程序的源代码,文本文件
hello.i:经过预处理处罚修改了的源程序,文本文件
hello.s:编译器翻译成的汇编程序,文本文件
hello.o:汇编器翻译汇编程序为二进制机器语言指令,可重定位的目标程序
hello:调用printf.o函数,经过链接器后得到的可执行文件
hello.elf:hello.o的elf格式文件
hello2.elf:hello的elf格式文件
hello.asm:hello的反汇编文件
hello2.asm:hello.o的反汇编文件
(附件0分,缺失 -1分)
参考文献
- 《深入明白盘算机系统》Randal E.Bryant David R.O’Hallaron 机械工业出版社
- 段页式访存-逻辑地点到线性地点转换https://www.jianshu.com/p/fd2611cc808e
(参考文献0分,缺失 -1分)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |