哈工大2023春盘算机体系大作业——程序人生-Hello’s P2P ...

打印 上一主题 下一主题

主题 1116|帖子 1116|积分 3348

盘算机体系

大作业

题目:程序人生-Hello’s P2P
专业:未来技术学院
学号:2021110788
班级:21WL026
学生:左镕畅
指导教师:吴锐

摘 要
本文对hello.c源程序在Linux下运行的生命历程举行了描述。在这个过程中,分别运行C预处理器、C编译器、汇编器,举行预处理、编译、汇编,得到了hello.i、hello.s、hello.o文件,最后利用链接器得到了可执行文件hello。随后对hello的进程运行、内存管理等过程举行探索,更深入理解Linux体系下的存储层次结构、异常控制流、假造内存等相干内容。随着对hello生命历程的探索,hello的“生命”也走向了止境。
关键词:盘算机体系;汇编;假造内存;进程管理


  
第1章 概述


1.1 Hello简介

P2P:全称为From Program to Progress,从程序到进程。这个看似简单的过程必要经过预处理、编译、汇编、链接等一系列的复杂动作才可以生成一个可执行文件。在运行时,我们打开Shell,通过输入./hello,使Shell创建新的进程用来执行hello。操纵体系会使用fork()产生子进程,然后通过execve()将其加载,不断举行访存、内存申请等操纵执行程序。最后,在程序竣事返回后,由父进程或祖先进程举行接纳,程序竣事。
020:全称为From 0 to 0,从无到终。Hello的出生是由操纵体系举行存储管理、地址翻译、内存访问,通过按需页面调理来开始这段生命。父进程或祖先进程的接纳也标志着它生命的竣事。

1.2 环境与工具


1.2.1 硬件环境

处理器:Intel Core i7 10th GEN
体系范例:X64 CPU; 2GHz; 16G RAM; 256G HD Disk

1.2.2 软件环境

Windows10家庭版
VMware Workstation pro2022
Ubuntu22.04

1.2.3 开发与调试工具

gedit+gcc
VSCode
Edb

1.3 中间结果

文件名功能hello.c源程序hello.i预处理后的文件hello.s汇编文件hello.o可重定位目的执行文件hello可执行文件hello.elfhello.o的ELF格式hello1.txthello.o的反汇编语言hello2.txthello的反汇编语言hello1.elfhello的ELF格式
1.4 本章小结

本章简述了Hello程序的一生,可以发现盘算机体系课程的学习和hello的生命轨迹根本重合一致。本章还简要说明了实验的软、硬件环境以及编译处理工具,是团体文章的布局脉络。

第2章 预处理


2.1 预处理的概念与作用


2.1.1 预处理的概念

预处理是指在编译阶段之前对.c源文件举行预处理的过程。预处理器会根据预处理指令对源代码举行一系列转换和更换,生成新的源代码文件(ASCII码的中间文件.i文件),这个过程叫做预处理。

2.1.2 预处理的作用


  • 宏界说:通过宏界说来界说常量、函数等,使程序更加易读、易维护。
  • 条件编译:通过条件编译来控制程序的行为,使程序具有更强的灵活性和可移植性。
  • 文件包含:通过文件包含来引入其他程序文件中界说的变量、函数等,克制重复界说和进步代码的复用性。
  • 注释处理:去除程序中的注释,减小程序的体积,进步程序的执行效率。
  • 预编译指令:可以通过预编译指令来指定编译器的行为,如优化品级、编译选项等,以达到更好的程序性能。
2.2 在Ubuntu下预处理的命令

在终端中输入cpp hello.c > hello.i,即可生成.i文件

图2.2.1 预处理命令

图2.2.2 预处理生成文件

2.3 Hello的预处理结果解析

hello.c的预处理结果为hello.i,直接打开hello.i文件观察发现它共有3121行,最后的14行为hello.c中的main函数。相较于hello.c,hello.i中已经没有了注释,同时.c文件中的头文件(stdio.h、unistd.h、stdlib.h等)也被加载到了.i文件中。

图2.3.1 hello.i文件部分内容

2.4 本章小结

本章首先介绍了预处理的界说与作用,再以hello.c文件为例,展示了.c文件的预处理的过程,最后结合预处理生成的.i程序对预处理结果举行了分析。


第3章 编译


3.1 编译的概念与作用


3.1.1 编译的概念

编译过程是将预处理后的代码转换为汇编语言代码的过程。预处理后的代码中包含了宏界说、条件编译、文件包含等预处理指令,这些指令必要被编译器进一步处理,生成可执行的汇编语言代码。

3.1.2 编译的作用


  • 语法检查:编译器会对源代码举行语法检查,确保代码符合语法规范,如括号匹配、语句竣事符等。
  • 语义检查:编译器会对源代码举行语义检查,确保代码的正确性和可靠性,如范例检查、变量声明检查、函数调用检查等。
  • 代码优化:编译器会对源代码举行优化,使程序更加高效、快速和可靠,如常量折叠、循环睁开、内联函数等。
  • 中间代码生成:编译器会将源代码转换为中间代码,以便后续的优化和目的代码生成。
  • 目的代码生成:编译器会将中间代码转换为目的代码,即汇编代码,以便后续的汇编和链接。
  • 调试支持:编译器会生成调试信息,以便程序出错时能够举行调试和排错。
3.2 在Ubuntu下编译的命令

打开终端输入命令gcc -S hello.c -o hello.s实现编译。

图3.2.1 编译命令

图3.2.2 编译结果

3.3 Hello的编译结果解析


图3.3.1 hello.s文件内容

3.3.1 字符串常量

hello.c中必要打印的字符串的编译结果如下图所示

图3.3.2 字符串常量编译结果

3.3.2 局部变量

main函数中的局部变量int被存在栈中,它在编译文件中的表现如下图所示

图3.3.3 局部变量编译结果

3.3.3 算数操纵

算数操纵++的编译结果如下图所示,通过汇编指令addl来实现

图3.3.4 算数操纵++编译结果

3.3.4 关系操纵

关系操纵<的编译解析如下图所示,在.s文件中i<7被解析为i<=7

图3.3.5 关系操纵<编译结果
关系操纵!=的编译解析如下图所示

图3.3.6 关系操纵!=编译结果

3.3.5 数组操纵

数组的下标[]访问的编译解析如下图所示

图3.3.7 数组操纵编译结果

3.3.6 控制转移

main函数中的控制转移for的汇编解释如下图所示,它是通过比较指令和跳转指令实现的

图3.3.8 控制转移编译结果

3.3.7 函数操纵

如下图所示,图片展现了main函数的参数转达。由于还会使用到%rbp,所以先将%rbp压栈生存起来。并将栈指针减少32位,然后分别将%rdi和%rsi的值存入栈中。由此可知,%rbp-20和%rbp-32的位置分别存了argv数组和argc的值。

图3.3.9 参数转达
函数调用的编译解析如下图所示,分别为printf和sleep函数的调用,均使用call指令举行调用
其中printf调用前先取得argv数组的第二个和第三个元素,并放入寄存器%rsi和%rdx,然后取得了字符串的地址,并存入了%rdi中作为第一个参数
atoi和sleep函数调用前先取得argv存入%rdi作为第一个参数,然后调用atoi函数,并将返回值存到%rax中,在将%rax的值存到%rdi作为sleep的第一个参数

图3.3.10 函数调用
函数的返回值存在%rax寄存器中

3.3.9 赋值操纵

通过movq指令是吸纳赋值操纵,如下图所示为部分赋值操纵

图3.3.11 赋值操纵

3.4 本章小结

本章先介绍编译的界说和作用,并通过hello.s汇编语言文件和hello.c源程序的比较,对编译举行了解析,进一步指出编译器是如何处理各种数据范例和各类操纵的。


第4章 汇编


4.1 汇编的概念与作用


4.1.1 汇编的概念

汇编将程序员编写的易于理解和编写的汇编语言代码转换为盘算性能够理解和执行的机器语言代码。

4.1.2 汇编的作用


  • 将汇编语言源代码转换为机器语言指令,使盘算性能够执行程序。
  • 检查源代码中的语法错误和逻辑错误,并提示程序员举行修正。
  • 生成可执行文件或目的文件,包括可执行文件、库文件、目的文件等,以便于程序员举行后续的操纵。
  • 实现宏汇编等高级汇编技术,进步程序员的编程效率和代码可读性。
4.2 在Ubuntu下汇编的命令

在终端中输入命令gcc -c hello.s -o hello.o对.s文件举行汇编操纵,得到.o文件

图4.2.1 汇编命令

图4.2.2 汇编结果

4.3 可重定位目的elf格式

终端输入命令readelf -a hello.o > hello.elf获得ELF格式文件如下图所示


图4.3.1 elf文件生成命令
终端输入readelf -S hello.o查看hello.o的节头表,它描述了每个节的节名文件偏移、大小、访问属性、对齐方式等

图4.3.2 节头表
终端输入readelf -h hello.o查看hello.o的ELF头


图4.3.3 ELF头
readelf -s hello.o查看符号表,Name为符号名称,Value是符号相对于目的节的起始位置偏移,Size为目的大小,Type是范例,数据或函数,Bind表现是当地还是全局

图4.3.4 符号表
readelf -r hello.o查看重定位节,其中重定位节.rela.text包含.text节中必要举行重定位的信息,当链接器把这个目的文件和其他文件组适时,必要修改这些位置。以下8条重定位信息对.L0、puts函数、exit函数、.L1、printf、sleepsec、sleep、getchar函数举行了重定位声明

图4.3.5 重定位节

4.4 Hello.o的结果解析

在终端输入objdump -d -r hello.o可以查看hello.o文件的反汇编结果,如下图所示

图4.4.1 hello.o反汇编结果
通过与第三章的hello.s举行对比,可以发现有以下区别:
1. 分支跳转:.s文件中的分支跳是使用段名作为标识,而.o文件中使用地址的偏移量来标识。现实上段名称只是在汇编语言中的助记符,这个助记符就像是注释一样,在汇编成机器语言之后不存在,而换为了确定的地址
2. 函数调用:与分支跳转类似,hello.s中使用函数名来举行标识的,而hello.o使用当前位置的下一条指令,但因为被调用的函数仅有界说没有实现(在别的库中实现),现在不能确定地址,必要链接接后才能确定其地址

图4.4.2 hello.s文件
汇编语言与机器语言的映射关系是逐一对应的,因此,汇编语言的指令可以直接转换成机器语言的指令,机器语言的指令也可以用汇编语言的助记符表现,两者之间是相互转化的关系。在编程过程中,汇编语言可以进步程序员的编程效率和代码可读性,同时也可以更好地理解盘算机内部的运行机制

4.5 本章小结

本章首先介绍了汇编的概念和作用,再以hello.s到hello.o的汇编过程为例,说明.o文件中包含的信息,并通过对比说明从汇编语言映射到机器语言必要实现的转换

第5章 链接


5.1 链接的概念与作用


5.1.1 链接的概念

链接是将目的文件和库文件组合成可执行文件的过程。在链接过程中,链接器会办理符号引用,将目的文件中引用的符号与库文件中界说的符号举行匹配。如果找不到匹配的符号,则会产生链接错误。

5.1.2 链接的作用

链接使得分离编译成为可能,使我们可以独立的修改和编译我们必要修改的小的模块

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 hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

图5.2.1 链接命令

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

命令readelf -a hello
1. elf头:

图5.3.1 elf头
2. 节头

图5.3.2 节头
3. 程序头

图5.3.3 程序头
4. 重定位节

图5.3.4 重定位节
5. 符号表

图5.3.5 符号表

5.4 hello的假造地址空间

假造地址空间的起始地址为0x400000,竣事地址为0x400ff0

图5.4.1 edb界面
与此同时,根据5.3里的Section头部表,我们可以在edb中找到对应节的假造空间地址


图5.4.2 节头表与edb假造地址空间对比

5.5 链接的重定位过程分析

终端输入命令objdump -d -r hello,并将结果与hello.o对比发现:
1. 相比hello.o,hello中增长了hello.c用到的库函数,例如printf
2. hello中也比hello.o多了很多节,如_init、puts@plt等
3. hello.o的地址从0开始,是相对地址,而hello的地址从0x400000开始,是重定位之后的假造地址
4. hello的跳转指令和call指令使用的均为绝对地址,而hello.o中使用的是相对地址
链接的过程如下:
1. 将所有目的文件中的符号名和地址导入全局符号表中
2. 对全局符号表中的符号名举行符号解析,确保每个符号名只有一个界说
3. 对每个目的文件举行重定位,将相对地址转换为绝对地址
4. 将所有目的文件中的代码和数据段归并成一个可执行文件或共享库
5. 对归并后的可执行文件或共享库举行地址重定位,将全局符号表中的符号地址更新为正确的地址
6. 最后生成可执行文件或共享库,供操纵体系加载和执行
hello.o的重定位过程:
1. 编译器在编译源代码时,将代码中的绝对地址转换为相对地址。
2. 链接器将hello.o与其他目的文件或库文件举行链接,生成可执行文件或共享库。
3. 链接器对hello.o举行重定位,将代码和数据段中的相对地址转换为绝对地址。重定位过程中,链接器会将hello.o中的每个符号引用与全局符号表中的相应符号界说举行匹配,并盘算出符号引用的绝对地址。
4. 如果hello.o中存在未办理的符号引用,则链接器会陈诉链接错误。
5. 链接器对归并后的可执行文件或共享库举行地址重定位,将全局符号表中的符号地址更新为正确的地址。
6. 最后生成可执行文件或共享库,供操纵体系加载和执行。

图5.5.1 hello反汇编文件

5.6 hello的执行流程

使用edb徐徐执行并记载

图5.6.1 edb界面
得到从加载hello到_start,到call main,以及程序终止的所有过程如下:
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
-libc-2.27.so!__cxa_atexit
-libc-2.27.so!__libc_csu_init
hello!_init
libc-2.27.so!_setjmp
-libc-2.27.so!_sigsetjmp
-libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
hello!printf@plt
hello!atoi@plt
hello!sleep@plt
hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave -ld-2.27.so!_dl_fixup
-ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit
得到子程序名的及地址如下表所示
子程序名子程序地址hello!_start0x00000000004010f0hello!__libc_csu_init0x0000000000401270hello!_init0x0000000000401000hello!frame_dummy0x00000000004011d0hello!register_tm_clones0x0000000000401160972994hello!main0x00000000004011d6hello!printf@plt0x0000000000401040hello!atoi@plt0x0000000000401060hello!sleep@plt0x0000000000401080hello!getchar@plt0x0000000000401050hello!exit@plt0x0000000000401070hello!__do_global_dtors_aux0x00000000004011a0hello!deregister_tm_clones0x0000000000401130hello!_fini0x00000000004012e8
5.7 Hello的动态链接分析

动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目的地址,PLT使用GOT中地址跳转到目的函数
首先通过查看elf可以找到.got的地址为0x601000
Got表在调用dl_init前如下图所示

图5.7.1 dl_init调用前的假造地址空间
Got表在调用dl_init后如下图所示

图5.7.2 dl_init调用后的假造地址空间

5.8 本章小结

本章重要介绍了链接的概念与作用,通过对比hello反汇编文件与hello.o可重定位文件,详细分析了重定位过程,最后使用edb调试hello程序,分析了hello的执行流程以及动态链接过程。


第6章 hello进程管理


6.1 进程的概念与作用


6.1.1 进程的概念

进程是盘算机体系中正在运行的程序的实例。它是操纵体系对正在运行的程序举行管理和调理的根本单元。每个进程都有自己的地址空间、堆栈、程序计数器、寄存器等资源,同时还有自己的状态,如运行、就绪、壅闭等。

6.1.2 进程的作用


  • 实现并发:多个进程可以同时运行,实现并发执行,进步体系的效率。
    2. 资源管理:操纵体系通过进程管理来控制体系资源的分配和使用,如CPU时间、内存、磁盘、网络等。
    3. 进步体系的可靠性:每个进程都是独立的,一个进程的崩溃不会影响其他进程的正常运行。
    4. 实现进程间通讯:不同进程之间可以通过进程间通讯(IPC)机制举行数据互换和协作。
    5. 实现多任务处理:操纵体系可以通过进程管理来实现多任务处理,使得多个任务同时执行,进步体系的效率。
6.2 简述壳Shell-bash的作用与处理流程


6.2.1 shell-bash的作用

壳(Shell)是盘算机操纵体系中的一个命令行解释器,用于解释用户输入的命令并将其转化为操纵体系可以执行的指令。其功能包括文件管理、进程管理、环境变量管理、输入输出重定向等。
Bash是一种壳(Shell)程序,是Unix和Linux操纵体系中最常用的壳程序之一。

6.2.1 shell-bash的处理流程


  • 读取用户输入的命令。
    2. 对命令举行解析,包括辨认命令名和参数。
    3. 执行命令,包括调用体系程序或执行脚本。
    4. 输出命令结果。
    在执行命令时,Bash会首先查找当前工作目录下是否存在命令,如果不存在则查找体系路径下是否存在相应的程序。命令执行完毕后,Bash会将结果输出到标准输出或重定向到其他文件。
6.3 Hello的fork进程创建过程


  • 在终端中输入./hello 2021110788 左镕畅 100,运行的终端程序会对输入的命令行举行解析
    2. ./hello 不是一个内置的shell命令,所以解析之后终端程序判定./hello的语义为执行当前目录下的可执行目的文件hello,hello主函数main的参数即为终端中输入的四个字符串(以空格作为分隔)
    3. 之后终端程序首先会调用fork函数创建一个新的子进程,新创建的子进程几乎与父进程类似。fork函数会返回两次,其中0代表子进程,非0(返回父进程的pid)代表父进程
    4. 父进程与子进程之间最大的区别在于它们拥有不同的PID。子进程得到与父进程用户级假造地址空间类似的一份副本,当父进程调用fork时,子进程可以读写父进程中打开的任何文件
    5. 内核能够以任意方式瓜代执行父子进程的逻辑控制流的指令,父进程与子进程是并发而独立运行的。在子进程执行期间,父进程默认选项是表现等待子进程的完成
    6. 父进程和子进程独立运行,二者竣事顺序不可知,父进程负责接纳子进程

    图6.3.1 进程的地址空间
6.4 Hello的execve过程

创建子进程完成后,子进程调用execve函数在当前子进程的上下文加载并运行hello程序,详细步调:
1. 删除之前进程在用户中已存在的结构
2. 创建新的私有的复制的代码、数据和堆栈
3. 通过标准C语言库的动态链接,映射到用户假造地址空间中的共享地区
4. 设置程序计数器,使之指向代码的入口

6.5 Hello的进程执行

1、上下文信息:操纵体系使用一种称为上下文切换的较高层次的异常控制流来实现多任务。上下文就是进程自身的假造地址空间,分为用户级上下文和体系及上下文,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。每个进程的假造地址空间和进程自己逐一对应(因此和PID逐一对应)。由于每个CPU只能同时处理一个进程,而很多时候体系中有很多进程都要去运行,因此处理器只能一段时间就要切换新的进程去运行,而实现不同进程中指令瓜代执行的机制称为进程的上下文切换。
2、进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

图6.5.1 进程切换过程
上图为进程A与进程B之间的相互切换。在执行过程中,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这个过程称为调理。在此基础上,hello程序与操纵体系其他进程通过操纵体系的调理,切换上下文,拥有各自的时间片从而实现并发运行。程序在涉及到一些操纵时,例如调用一些体系函数,内核必要将当前状态从用户态切换到核心态,执行竣事后再及时改用户态,从而保证体系的安全与稳定
处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,为用户模式;设置模式为为内核模式。用户模式就是运行相应进程的代码段的内容,此时进程不答应运行特权指令,也不答应直接引用地址空间中内核区内的代码和数据;而内核模式中,进程可以运行任何指令

6.6 hello的异常与信号处理


6.6.1 异常的种类

如下表所示,异常可以分为四类:中断、陷阱、故障和终止
种别原因异步/同步返回行为中断来自I/O设备的信号异步总是返回到下一条指令陷阱有意的异常同步总是返回到下一条指令故障潜在可恢复的错误同步可能返回到当前指令终止不可恢复的错误同步不会返回
6.6.2 信号的种类

信号可以被理解为通知,他通知进程体系中发生了一个某种范例的事件。每种信号范例都对应于某种体系事件,它提供了一种机制,通知用户进程发生了这些异常,并让进程执行相应的信号处理程序。Linux中信号有如下种类:

图6.6.1 常见信号

6.6.3 hello的异常与信号处理


  • 乱按并不会影响进程执行

    图6.6.2 乱按
    2. 键入ctrl+z向进程发送信号,使前台进程挂起

    图6.6.3 ctrl+z
    3. 再键入ps发现ctrl+z只是挂起前台作业,并没有接纳hello

    图6.6.4 ps
    4. 键入jobs发现hello的后台号分别为1和2(我执行并挂起了两次hello)

    图6.6.5 jobs
    5. 使用fg 1命令可以将后台任务调到前台,hello继续运行

    图6.6.6 fg
    6. 输入kill -9 11504可以发送SIGKILL信号杀死进程

    图6.6.7 kill
    7. 输入ctrl+c

    图6.68 ctrl+c
6.7本章小结

本章重要介绍了hello进程的执行过程,介绍了shell的处理流程和作用。通过在进程执行时在终端键入不同命令,研究了hello执行过程中各种操纵可能引发的异常和信号处理,发现针对不同的shell命令,hello有不同的相应

第7章 hello的存储管理


7.1 hello的存储器地址空间


  • 逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操纵数或者是一条指令的地址,其格式为“段标识符:偏移地址”,例如“6123:3028000”
    2. 线性地址:逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址
    3. 假造地址:同线性地址
    4. 物理地址:在存储器里以字节为单元存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址,又叫现实地址或绝对地址
7.2 Intel逻辑地址到线性地址的变换-段式管理

Intel平台下,逻辑地址的格式为“段标识符:段内偏移量”。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。背面3位包含一些硬件细节。分段机制将逻辑地址转化为线性地址的步调如下:
1. 使用段选择符中的偏移值在GDT或LDT表中定位相应的段描述符。
2. 利用段选择符查验段的访问权限和范围,以确保该段可访问。
3. 把段描述符中取到的段基地址加到偏移量上,最后形成一个线性地址

7.3 Hello的线性地址到物理地址的变换-页式管理

线性地址即假造地址,假造内存与物理内存都被分别为页,并与页号相对应。假造地址由假造页号(VPN)+假造页偏移量(VPO)组成。页表是创建假造页号与物理页号(PPN)映射关系的表结构,页表项(PTE)包含有有效位、物理页号、磁盘地址等信息。
通过假造页号和页表初始地址能找到相对应的页表项,页表初始地址存储在页表基址寄存器中。页表项存储的页表状态有三种,分别为:未分配,已缓冲,未缓冲。当对应状态为已缓冲时,说明假造页所对应的物理页已经存储在内存中,此时页表项存储的物理页号加上物理页偏移量即为物理地址,而物理页偏移量与假造页偏移量类似,可以从假造地址中直接得出。页表项状态为已缓冲时,页式管理过程如下图所示

图7.3.1 页式管理

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

查找页表的过程也是访问内存的过程,利用局部性原理,把最近使用过的页表项缓存起来,并通过TLB(叫快表)实现。
多级页表即上一级的页表映射到下一级页表,最后一级页表映射到假造内存,如果下一级内容都未分配,那么页表项则为空,不映射到下一级,也不存在下一级页表,当分配时再创建相应页表,从而达到节约内存空间的效果。多级页表的实现如下图所示:

图7.4.1 TLB和四级页表

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

我们得到了物理地址之后,先在L1 Cache中寻址。物理地址被分为CT(标记)+CI(索引)+CO(偏移量)三部分,在Cache中寻址,如果L1无缓存则向L2查找,同理L2没有向L3中查找

图7.5.1 三级Cache下的物理内存访问

7.6 hello进程fork时的内存映射

当shell调用fork 函数时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID。为了给hello进程创建假造内存,它创建了hello进程的mm_struct、地区结构和页表的原样副本,但是它将两个进程中的每个页面都标记为只读,并将两个进程中的每个地区结构都标记为私有的写时复制。通过 fork 创建的子进程拥有父进程类似的地区结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。
当 fork 在新进程中返回时,新进程现在的假造内存刚好和调用 fork 时存在的假造内存类似,当这两个进程中的任一个厥后举行写操纵时,就会触发一个掩护故障。当故障处理程序留意到掩护异常是由于进程试图写私有的写时复制地区中的一个页面而引起的,它就会在物理内存中创建这个页面的一个新副本,更新页表条目指向这个新的副本,然后恢复这个页面的可写权限。当故障处理程序返回时,CPU重新执行这个写操纵,现在在新创建的页面上这个写操纵就可以正常执行了。

7.7 hello进程execve时的内存映射

execve函数调用驻留在内核地区的启动加载器代码,在当前进程中加载并运行包含在可执行目的文件hello中的程序,用hello程序有效地替换了当出息序。加载并运行 hello 必要以下几个步调:
1. 删除当前进程假造地址中已存在的用户地区
2. 映射私有地区,为新程序的代码、数据、bss和栈创建新的地区结构,所有这些新的地区都是私有的、写时复制的。
3. 映射共享地区,将hello与libc.so动态链接,然后再映射到假造地址空间中的共享地区。
4. 设置当前进程上下文程序计数器(PC),使之指向代码地区的入口点。

图7.7.1 假造地址空间

7.8 缺页故障与缺页中断处理

缺页实在就是DRAM缓存未掷中。当我们的指令中对取出一个假造地址时,若我们发现对该页的内存访问是合法的,而找对应的页表项式发现有效位为0,则说明该页并没有生存在主存中,出现了缺页故障。
此时进程暂停执行,内核会选择一个主存中的一个断送页面,如果该页面是其他进程或者这个进程自己页表项,则将这个页表对应的有效位改为0,同时把必要的页存入主存中的一个位置,并在该页表项储存相应的信息,将有效位置为1。然后进程重新执行这条语句,此时MMU就可以正常翻译这个假造地址了。

7.9动态存储分配管理

所有动态申请的内存都存在堆上面,用户通过生存在栈上面的一个指针来使用该内存空间。动态内存分配器维护着堆,堆顶指针是brk。有两种方式,其中一种叫显式分配器,使用两个函数,malloc() 和free(),分别用于执行动态内存分配和释放。
malloc()的作用是向体系申请分配堆中指定size个字节的内存空间。函数返回的指针为指向堆里的一块内存。而且,操纵体系中有一个记载空闲内存地址的链表。当操纵体系收到程序申请时,就会遍历该链表,然后探求第一个空间大于所申请空间的堆结点,将该结点从空闲结点链表中删除后,将该结点的空间分配给到程序。在使用malloc()分配内存空间后,需释放内存空间,否则就会出现内存走漏。
free()释放的是指针指向的内存,而不是指针。指针并没有被释放,它仍然指向原来的存储空间。因此指针必要手动释放,指针是一个变量,只有当程序竣事时才被销毁。释放了内存空间后,原本指向这块空间的指针仍然存在。但此时指针指向的内容为垃圾,是未界说的。因此,释放内存后要把指针指向NULL,防止该指针后续被解引用。

7.10本章小结

本章重要介绍了hello的存储器地址空间,重点分析了四级页表下的线性地址到物理地址的变换,分析了hello的内存映射、缺页故障与缺页中断处理和动态存储分配管理。

第8章 hello的IO管理


8.1 Linux的IO设备管理方法


  • 文件体系:Linux将所有的IO设备都视为文件,包括硬盘、光驱、串口、网卡等等。每个设备都会被分配一个文件名,例如/dev/sda表现第一个SATA硬盘,/dev/ttyS0表现第一个串口。
    2. 设备驱动程序:Linux体系中有很多设备驱动程序,它们负责控制各种IO设备的工作。设备驱动程序可以通过内核模块的形式加载到内核中,也可以编译进内核中。当体系启动时,内核会自动加载所有必要的设备驱动程序。
    3. 设备文件权限:由于Linux将所有IO设备都视为文件,因此每个设备文件都有自己的权限。管理员可以使用chmod命令来修改设备文件的权限,以控制用户对设备的访问权限。
    4. 设备文件的访问方式:Linux支持多种访问设备文件的方式,包括壅闭式IO、非壅闭式IO、多路复用IO和异步IO。管理员可以根据应用程序的必要来选择不同的访问方式。
    5. 中断处理:当IO设备完成一个操纵时,它会向CPU发送一个中断信号。内核会相应这个中断信号,并调用相应的设备驱动程序来处理中断。设备驱动程序可以使用DMA(直接内存访问)来加快数据传输,减少CPU的负担。
    总而言之,所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备映射为文件的方式,答应Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行:设备的模型化:文件,设备管理:unix io接口
8.2 简述Unix IO接口及其函数

Unix IO接口是一组用于处理文件和数据流的函数集合。这些函数包括文件读写、文件描述符管理、进程通讯等操纵。
常用的Unix IO函数有:
1. open():打开文件并返回文件描述符。
2. close():关闭文件描述符。
3. read():从文件描述符中读取数据。
4. write():向文件描述符中写入数据
5. lseek():移动文件描述符的读写位置。
6. dup():复制文件描述符。
7. pipe():创建一个管道。
8. select():等待多个文件描述符的IO事件。
9. poll():等待多个文件描述符的IO事件。
10. socket():创建一个套接字。
11. accept():接受一个连接请求。
12. connect():创建一个连接。

8.3 printf的实现分析


图8.3.1 printf函数
printf是一个C语言的标准库函数,用于将格式化的数据输出到标准输出流stdout(通常是屏幕)或者其他输出流中。实在现过程可以大抵分为以下几个步调:
1. 格式化字符串生成表现信息:printf函数的第一个参数是格式化字符串,其中包含了必要输出的数据范例和格式,例如%d表现输出整数,%f表现输出浮点数等等。在调用printf函数时,格式化字符串会被转达给vsprintf函数举行处理,生成现实必要表现的信息。
2. 将表现信息写入输出流:生成的表现信息必要通过输出流输出到屏幕或者其他设备上。在Unix/Linux体系中,可以使用write体系函数将信息写入文件描述符(通常是标准输出流stdout)。在Windows体系中,可以使用WriteFile函数将信息写入控制台或者文件。
3. 调用体系调用将信息传输到表现芯片:在操纵体系中,体系调用是一种从用户态切换到内核态的机制,通过体系调用可以访问操纵体系内核提供的功能和资源,例如文件、网络、进程等等。在将信息输出到屏幕时,必要通过体系调用将信息传输到表现芯片中,以便在屏幕上表现。在Linux体系中,可以使用int 0x80或syscall指令举行体系调用。
4. 表现芯片将信息表现在屏幕上:最后,表现芯片会按照刷新频率逐行读取vram中存储的每一个点的RGB颜色信息,并通过信号线向液晶表现器传输每一个点的RGB分量,从而在屏幕上表现出完整的信息。

8.4 getchar的实现分析


图8.4.1 getchar函数
在Linux体系中,getchar函数的实现依赖于read体系函数。当调用getchar函数时,它会调用read函数从标准输入流中读取一个字符,直到读取到回车符为止。read函数会将字符存储到缓冲区中,并返回读取的字节数。
在键盘输入方面,当用户按下键盘上的某个键时,键盘会向盘算机发送一个中断信号,通知操纵体系有一个键盘事件发生。操纵体系会调用键盘中断处理程序来处理这个事件。在处理程序中,操纵体系会读取键盘扫描码,并将其转换为相应的ASCII码。
转换完成后,操纵体系会将ASCII码存储到体系的键盘缓冲区中。当用户调用getchar函数时,它会读取键盘缓冲区中的字符,直到读取到回车符为止。这个过程是异步的,因为键盘中断处理程序和getchar函数是在不同的进程中执行的。

8.5本章小结

本章介绍了Linux的IO设备的管理方法,以及Unix IO函数的功能,并分析了printf函数和getchar函数的实现

结论


  • 我们程序员就像是造人的女娲,敲击在键盘上的字符就是泥土,我们使用双手在电脑里敲击一个个字符,用字符“粘合”成了hello的身体(main函数)、四肢(外部函数),给hello赋予了智慧(for循环)……创造出了一个个小泥人(hello.c源文件),从被我们创造开始,小泥人hello.c便开始了它的发展之路。
  • 一开始,小泥人hello.c只是安静的在磁盘中睡觉,直到有一天,它被创造者——程序员唤醒了,但是hello.c和程序员身处两个世界,hello.c活在盘算机世界中,盘算机世界里程序员为它装配的身体、四肢、大脑好像并不实用,于是hello.c积极适应盘算机世界。它经历了预处理,头文件被引入、宏被睁开,酿成了ASCII码中间文件hello.i,之后它又经历了编译器的处理,变为了汇编语言文件hello.s,随后有经历了汇编器的转换,酿成了可重定位目的文件hello.o,最后经过链接,它终于酿成了可以在盘算机世界自由遨游的可执行文件hello
  • 这个时候,程序员在shell中输入了命令“./hello 2021110788 左镕畅 1”让小生命hello开始了工作,shell通过调用fork()函数创建了子进程,又通过execve()函数映射到假造内存当中,并通过缺页异常将hello放入主存,又通过四级页表、多级缓存,最后hello加载到处理器内部,开展执行它的工作
  • hello执行了自己的任务后,通过Unix IO函数把任务完成结果输出到屏幕,告诉它的创造者程序员,它完成了任务
  • 当hello任务完成,进程执行竣事后,由父进程对子进程举行接纳,hello重新回到了硬盘中,等待程序员的下一次唤醒,至此,hello的一生竣事,固然它的一生如此短暂,但是却崎岖而精彩!

附件

文件名功能hello.c源程序hello.i预处理后的文件hello.s汇编文件hello.o可重定位目的执行文件hello可执行文件hello.elfhello.o的ELF格式hello1.txthello.o的反汇编语言hello2.txthello的反汇编语言hello1.elfhello的ELF格式
参考文献

[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
[7] 一个简单程序从编译、链接、装载(执行)的过程-静态链接 - 知乎 (zhihu.com)
[8]假造地址空间. 小林coding. 2021-06-27. https://www.zhihu.com/ question/290504400
[9] gcc–编译的四大过程及作用:https://blog.csdn.net/shiyongraow/article/details/81454995
[10] CSDN. 编译器工作流程详解. 2014:04-27.https://blog.csdn.net/u012491514/article/details/24590467
[11] 网络用户. 阿里云. ELF格式文件符号表全解析及readelf命令使用方法. 2018:07-19.https://www.aliyun.com/zixun/wenji/1246586.html
[12] gcc–编译的四大过程及作用:https://blog.csdn.net/shiyongraow/article/details/8145499


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

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

笑看天下无敌手

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表