计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 数据科学与大数据技术
学 号 2022111334
班 级 2203501
学 生 陈童
指 导 教 师 吴锐
计算机科学与技术学院
2024年5月
本文介绍了hello.c程序的一生。详细介绍了关于hello.c程序预处理、编译、汇编、链接、进程管理、存储管理、I/O管理的相关内容。在linux系统中借助gcc、edb、以及readelf、ps等相关命令对hello.c的一生进行了部分内容的实操展示。有助于读者加深对程序运行背后的计算机系统的理解。
关键词:计算机系统;预处理编译;汇编;链接;进程管理;虚拟地址;存储管理;IO操作
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 7 -
4.2 在Ubuntu下汇编的命令........................................................................... - 11 -
5.2 在Ubuntu下链接的命令........................................................................... - 17 -
5.3 可执行目标文件hello的格式.................................................................. - 17 -
5.5 链接的重定位过程分析............................................................................... - 23 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 29 -
6.3 Hello的fork进程创建过程..................................................................... - 29 -
6.6 hello的异常与信号处理............................................................................ - 30 -
第7章 hello的存储管理............................................................................... - 37 -
7.1 hello的存储器地址空间............................................................................ - 37 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 37 -
7.3 Hello的线性地址到物理地址的变换-页式管理...................................... - 38 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 38 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 39 -
7.6 hello进程fork时的内存映射.................................................................. - 40 -
7.7 hello进程execve时的内存映射.............................................................. - 41 -
7.8 缺页故障与缺页中断处理........................................................................... - 41 -
8.1 Linux的IO设备管理方法.......................................................................... - 45 -
8.2 简述Unix IO接口及其函数....................................................................... - 45 -
第1章 概述
1.1 Hello简介
P2P:即从程序(Program)到进程(Process)。这意味着将hello.c(Program)转换为运行中的进程(Process)。为了运行hello.c这个C语言程序,必须先将其编译为可执行文件(包括四个阶段:预处理、编译、汇编和链接)。接下来可在shell中运行该程序(涉及进程管理等)。
020:即从零到零。这表示最初内存中没有hello文件的任何内容,当shell使用execve函数启动hello程序时,虚拟内存将映射到物理内存,并从程序的入口点开始加载和运行,进入main函数执行目标代码。在程序结束后,shell的父进程会回收hello进程,内核会清除与hello文件相关的数据结构。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上1.2.2 软件环境
软件环境:Windows11 64位,VMware,Ubuntu 20.04 LTS
开发与调试工具:vim objump edb gcc readelf等工具
1.3 中间结果
hello.i hello.c预处理得到的文本文件
hello.s hello.i编译后得到的由汇编代码组成的文本文件
hello.o hello.s汇编后得到的可重定位目标文件(二进制)
hello hello.o链接后得到的可执行文件(二进制)
hello.asm hello.o反汇编的文本文件
hello1.asm hello反汇编得到的文本文件
1.4 本章小结
本章介绍了P2P和020的相关内容,对./hello程序的执行有了一个整体框架上的认识 ,同时页说明了本实验进行的环境与工具,如硬件配置、软件平台等,以及在本实验中一些使用到的相关文件及其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:
预处理(Preprocessing)是编译过程的第一步,主要用于处理源代码中的宏定义、文件包含、条件编译和其他预处理指令。预处理器在编译之前扫描源代码文件,并执行特定的文本替换和指令处理,以生成扩展后的源代码文件。
预处理的作用:
预处理的主要作用包含以下几个方面:
- 宏替换:通过 #define 指令定义宏,预处理器会将所有宏定义替换为相应的值。
- 文件包含:使用 #include 指令将头文件的内容插入到源文件中。
- 条件编译:通过 #ifdef、#ifndef、#if、#else 和 #endif 指令,可以根据条件选择性地编译代码。
- 行控制:使用 #line 指令可以改变预处理器记录的行号和文件名。这在调试信息和编译错误信息中会显示。
- 注释移除:预处理器会移除所有的注释(单行和多行注释),以确保注释不会影响编译过程。
2.2在Ubuntu下预处理的命令
命令: gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
在linux下用vim对hello.i文本文件进行查看,hello.i文件已经有了3000多行,源程序在文本最后。具体预处理过程如下:
- 预处理器会找到 #include <stdio.h>、#include <unistd.h> 和 #include <stdlib.h>,并将这些标准库头文件的实际内容插入到 hello.i 文件中。这些头文件包含了C语言标准库中的函数声明、宏定义等。
- 源代码中的注释(无论是 /* ... */ 还是 // ...)都会被移除。
经过上述预处理步骤后,hello.i 文件将是一个巨大的文本文件,它包含了 hello.c 中的所有代码以及三个标准库头文件的全部内容。这个文件仍然是一个有效的C语言源文件,但已经准备好了进行后续的编译和链接过程。
2.4 本章小结
本章详细介绍了预处理的含义和主要作用,以及用gcc对hello.c文本文件进行预处理的一个过程,简要分析了hello.i中的拓展内容。经以上分析,我们知道了预处理主要用于处理源代码中的宏定义、文件包含、条件编译和其他预处理指令,执行特定的文本替换和指令处理,以生成扩展后的源代码文件。
第3章 编译
3.1 编译的概念与作用
编译的概念:
编译是指将高级程序语言(如C、C++、Java等)编写的源代码转换为目标代码或可执行代码的过程。编译器是负责执行这个过程的程序。
编译的作用:
- 转换源代码: 编译器将人类可读的源代码翻译成计算机可执行的机器代码。这个过程包括语法分析、语义分析、优化和代码生成等步骤。
- 优化性能: 编译器可以对源代码进行优化,以提高程序的性能和效率。这些优化包括消除冗余代码、减少计算时间和内存占用等。
- 检查错误: 编译器可以检测源代码中的语法错误和逻辑错误,并提供错误消息以帮助程序员修复这些问题。
- 平台无关性: 通过编译,源代码可以被转换成针对特定平台的目标代码,使得同一份源代码可以在不同的硬件平台上执行,提高了程序的可移植性。
- 加密保护: 编译器可以将源代码编译成目标代码,从而隐藏源代码的实现细节,起到保护知识产权的作用。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1 汇编代码初始部分
.flie 声明源文件
.text 代码段
.section rodata 只读数据段
.align 指令或数据在内存中存放的对齐方式
.string 字符串
.global 全局变量
.type 表示符号的类型
3.3.2 数据部分
(1)只读数据段中有两个字符串:
对应于:
(2) 参数argc
参数argc的大小为参数的个数,是main函数的第一个参数,被放在寄存器%edi中。后被放入栈中,并与5进行比较以确保参数有5个。
(3) 参数argv
参数argv为char** 类型的,为一个数组,以null结尾,存储的是参数的地址char*,被存放在%esi寄存器中,后被放入栈中,并在之后作为参数被printf函数调用。
(4)局部变量
局部变量是在栈中保存的,默认初值为0。
(5) 全局函数
Hello.c中只有main作为全局函数。
3.3.3赋值操作
hello.c 的赋值操作有局部变量的初始化等,赋值使用了movl指令。
3.3.4算术操作
for循环中的i++等,i自增使用了addl指令。
3.3.5 关系操作
对两个数的大小比较使用的是cmp指令,cmp s1, s2 通过s2-s1的结果来设置条件码,后续的操作根据操作码的状况进行相应的步骤,通常与跳转指令配合使用。在该汇编代码中体现如下:
for循环中的终止条件:
参数个数的比较:
3.3.6控制转移指令
根据3.3.5的相关阐述可知,cmp指令通过设置条件码,后续的指令根据条件码进行相关的跳转。
3.3.7 函数操作
参数传递:通常使用寄存器进行参数的传递,如%rdi为第一个参数,%rsi为第二个参数等,如果参数较多,也可使用栈进行参数的传递,将参数放在栈顶,再执行函数的调用。如:
函数调用:通过call指令实现函数的调用,此时会将%rip压入栈中(下一条指令的地址),转而执行被调用函数。
局部变量:在栈中保存,阐述与前面的相同。
函数返回:函数的返回值放在寄存器%rax中,在被调用函数中,最后的ret指令会从栈中弹出旧的%rip的值,转而执行下一条指令。如得到第5个参数的位置将其取出,再获得第五个参数作为参数传递个atoi函数,返回结果用%eax进行保存,再将其作为参数传递给sleep函数
3.4 本章小结
本章节介绍了编译阶段的相关概念及其作用,并针对hello.i的编译结果就数据、赋值、运算、关系跳转、函数调用等相关方面进行了详细的阐述。通过分析,我们了解了高级程序语言在汇编代码的层次结构,以及相关的运算操作的实现。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:
首先,编译器将汇编源文件(.s)中的汇编指令翻译成对应的机器指令,并生成目标文件(.o)。这个过程被称为汇编(assembly),其中编译器会根据指令和操作数生成相应的二进制表示。
汇编的作用:
1. 将汇编指令转换为机器指令: 汇编语言是一种与硬件架构相关的低级语言,它直接对应计算机的机器指令集。汇编过程将汇编源文件中的汇编指令翻译成对应的机器指令,这些指令是计算机能够直接执行的指令,用于完成特定的操作,如算术运算、内存访问等。
2. 生成目标文件: 汇编过程生成的目标文件(.o 文件)包含了汇编源文件中的汇编指令所对应的机器指令的二进制表示。这个目标文件是编译器生成的中间文件,它包含了程序的二进制表示,但还没有被链接成最终的可执行文件。
3. 与其他模块的链接: 汇编生成的目标文件可能需要与其他模块进行链接,以形成最终的可执行文件。这个链接过程会将目标文件中的各个模块合并成一个完整的程序,解析模块之间的引用和依赖关系,插入必要的链接代码,以及将外部函数和库文件链接到程序中。
4.2 在Ubuntu下汇编的命令
gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o
4.3 可重定位目标elf格式
1.ELF头(Executable and Linkable Format header)是可执行文件和可链接文件格式中的一个重要部分,它提供了关于目标文件结构和组织的关键信息。ELF头位于目标文件的开始处,通常占据文件的前16字节,其内容描述了生成该文件的系统的字的大小和字节顺序。ELF头的目的是帮助链接器对目标文件进行语法分析和解释,确定目标文件的结构和组织,并且提供了关键的元信息,以便于程序的加载、执行和调试。
- 节头
记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。
3.重定位节
.rel.text 节的内容通常由一系列重定位条目组成,每个条目描述了一个需要重定位的位置及其相关信息。每个重定位条目的结构如下:
- 偏移量(Offset): 描述需要重定位的位置相对于代码段的偏移量。
- 重定位类型(Relocation Type): 指示链接器如何重定位该位置的类型。不同的重定位类型对应不同的重定位操作,比如绝对重定位、相对重定位等。
- 符号索引(Symbol Index): 如果需要引用外部符号进行重定位,该字段指示了在符号表中对应的符号的索引。链接器根据该索引找到相应的符号,并根据符号的值进行重定位计算。
- 符号偏移(Symbol Offset): 如果需要引用外部符号进行重定位,该字段指示了符号在目标文件中的偏移量。链接器将重定位目标位置的值与该偏移量相加,以获得最终的重定位结果。
总的来说,.rel.text 节是可重定位文件中用于存储代码段重定位信息的节,它包含了一系列重定位条目,描述了需要进行重定位的位置、重定位类型和相关的符号信息。在链接过程中,链接器根据这些信息对代码段进行修正,以确保目标程序在执行时能够正确地访问和执行相关的符号。
- 符号表
.symtab 是 ELF 格式中的一个重要部分,它是符号表(Symbol Table)的一个节(Section)。符号表包含了目标文件或可执行文件中使用的所有符号的信息,包括函数、变量、常量等的名称、地址、类型等。
4.4 Hello.o的结果解析
hello.asm相对于hello.s而言,区别如下:
- 增加了机器语言
每一个对应的汇编代码都有与之相对应的机器语言
- 操作数进制的改变
在反汇编的代码中,操作数都是以十六进制进行表示的。
- 分支跳转
在原来的hello.s文件中,根据符号进行跳转。
在反汇编的代码中,有关跳转的指令的地址大部分是基于主函数地址+函数内偏移确定的。
- 函数调用
在原来的hello.s中,函数调用是直接使用call进行表示的。
而在hello.asm中,函数调用的地址尚未通过链接根据重定位条目进行确定。
可在重定位条目中根据寻址类型确定是绝对寻址还是相对寻址。(链接过程中实现)
4.5 本章小结
本章接晒了汇编的相关含义及其作用,并对hello.o可重定位文件的elf的相关信息进行了简要的阐述,对每个section进行了展示,以及对重定位节和符号表进行了简要的介绍。通过对hello.o的反汇编,我们与原有的hello.s文件进行了一个简要的比较。经过以上的分析,汇编阶段为链接做好相应的准备工作。
(第4章1分)
5章 链接
5.1 链接的概念与作用
链接的概念:
链接(Linking)是软件开发中的一个重要概念,它指的是将多个编译后的目标文件(Object Files)或者库文件(Library Files)组合成一个可执行文件或者共享库的过程。链接分为静态链接(Static Linking)和动态链接(Dynamic Linking)两种方式。
链接的作用:
- 组合模块: 在大型软件项目中,代码通常会被分成多个模块或文件来管理。链接将这些模块组合在一起,形成一个完整的可执行程序或库文件。
- 解决外部依赖: 软件通常会依赖于外部的库文件或模块,例如标准库、第三方库等。链接将这些依赖的外部模块或库文件与程序的主体代码进行合并,使得程序可以正常运行。
- 符号解析: 链接器会解析程序中的符号引用,将它们与对应的符号定义进行匹配。这一过程确保了程序中引用的函数或变量能够正确地链接到对应的定义,从而保证了程序的正确性和完整性。
- 地址重定位: 如果程序中的代码或数据需要被加载到内存中的不同位置,链接器会进行地址重定位,调整相关的引用地址,以确保程序能够正确地在内存中运行。
- 优化和压缩: 链接器可以对目标文件进行优化和压缩,例如删除未使用的代码、合并相同的代码等,以减小程序的体积和提高运行效率。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
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.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
1.ELF头(类型已经发生改变)
2.section头(链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址)
3.程序头
4.dynamic section
5.重定位节信息
6.符号表
5.4 hello的虚拟地址空间
使用edb对hello程序进行查看,程序从地址0x400000开始到0x401000被载入,虚拟地址从0x4000000x400f0结束,根据5.3中的节头部表,可以通过edb找到各段的信息。
如.interp节,在hello.elf文件中能看到开始的虚拟地址:
在edb中找到对应的信息:
同样的,我们可以找到如.text节的信息:
在edb中找到对应的信息:
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
Hello1.asm与hello.asm的区别:
- helloasm中的函数数量增加
这些函数是由于动态链接器将共享库中hello.c所用到的函数加载到了hello这一可执行文件中。
- 调用函数的机器代码发生了改变
在链接的过程中,链接器会解析重定位条目,如果是相对寻址,则会修改操作码后的字节,其值为被调用函数地址-下一条指令的地址;如果是绝对寻址,则修改为被调用函数的真实地址。且为小端存储。
- 机器指令的地址发生了改变
重定位目标文件的代码起始地址为0,而链接过程中,会把多个目标文件进行合并,并分配真正的虚拟地址。
重定位的过程:
重定位由两步组成:
(1)重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的聚合节。然后链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。至此程序中每条指令和全局变量都有唯一的运行内存地址。
(2)重定位节中的符号引用。这一步中链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目的数据结构。
(3)重定位过程地址计算方法如下:
对待修改的引用地址进行修改,如果是相对寻址,则需用实际地址-pc地址;如果为相对地址,只需填入跳转地址的值即可,且为小端存储。
5.6 hello的执行流程
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
(1)开始执行:_start、_libc_start_main
(2)执行main:_main、printf、_sleep、getchar
(3)退出:exit
_start | 0x4010f0 |
_lib_start_main | 0x403fd8 |
main | 0x40112d |
printf | 0x4010a0 |
exit | 0x4010d0 |
_sleep | 0x4010e0 |
getchar | 0x4010b0 |
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb/gdb调试,分析在动态链接前后,这些项目的内容变化。要截图标识说明。
动态链接指的是在程序运行时,将程序中使用的函数和变量绑定到共享库(动态链接库,如.so文件)中的实际定义。与之相对的是静态链接,静态链接在编译时将库中的代码复制到可执行文件中。
动态链接主要依赖于以下几个步骤:
- 编译和链接阶段:
在编译阶段,编译器将源代码编译成目标文件(.o文件)。
在链接阶段,链接器不会将所有库代码包含到可执行文件中,而是将库的引用信息保留在可执行文件中。
- 加载阶段:
当程序开始执行时,操作系统的程序加载器负责加载可执行文件。
程序加载器解析可执行文件的依赖项,找到所需的共享库,并将它们加载到内存中。
- 运行时链接:
动态链接器(ld.so或ld-linux.so)在程序运行时负责解析符号,并将程序中的库函数调用绑定到共享库中的实际实现。
动态链接器会调整程序的内存地址,确保程序可以正确访问共享库中的函数和数据。
因此,编译系统采用延迟绑定,将过程地址的绑定推迟到第一次调用该过程的时候。
.plt:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
.got:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
init前:
init后:
5.8 本章小结
本章介绍了链接的相关概念和作用,并对链接后的hello文件发生的变化进行了简要的说明。并使用edb工具观察了hello的相关信息,对重定位、链接过程等有了更深的理解。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:
在操作系统中,进程(Process)是一个正在执行的程序实例。它不仅包括程序的可执行代码,还包含与执行该代码相关的所有资源和状态。具体来说,一个进程包含以下信息:
- 程序代码:需要执行的可执行指令。
- 程序计数器:当前指令的地址。
- 处理器寄存器:当前正在使用的数据。
- 进程堆栈:临时数据(如函数参数、返回地址和局部变量)。
- 数据段:全局变量。
- 堆:动态分配的内存。
进程的作用
- 操作系统通过进程来分配CPU时间、内存、文件等资源。每个进程都有其独立的资源集合,确保进程之间的独立性和安全性。
- 进程是并发执行的基本单位。操作系统通过时间片轮转、优先级等调度策略,
- 进程间的隔离机制保证了一个进程的错误不会影响其他进程的正常运行,增强了系统的稳定性和安全性。
- 尽管进程之间是隔离的,但需要相互通信和协作来完成复杂的任务。操作系统提供了多种进程间通信(IPC)机制,如信号、管道、消息队列、共享内存和套接字等。
- 通过进程调度算法,操作系统能够平衡系统负载,合理利用资源,避免某些资源的过度使用或空闲。
6.2 简述壳Shell-bash的作用与处理流程
Shell-Bash的作用
- 命令执行:用户可以输入命令,Bash负责解析并执行这些命令。
- 文件操作:Bash支持多种文件操作,如创建、读取、编辑和删除文件。
- 管道与重定向:用户可以使用管道(|)将一个命令的输出作为另一个命令的输入,或者使用重定向(>、>>、<)来改变命令的输入/输出方向。
- 变量与脚本:Bash支持变量和脚本,允许用户编写复杂的自动化任务。
- 编程能力:虽然Bash主要用于命令行操作,但它也提供了一定的编程能力,如条件语句、循环和函数等。
Shell-Bash的处理流程
- 命令读取:Bash从终端读取用户输入的命令。
- 命令解析:Bash对输入的命令进行解析,识别出命令名、参数和选项等。
- 内置命令处理:如果输入的命令是Bash的内置命令(如cd、echo等),Bash会立即执行该命令。
- 外部命令处理:如果输入的命令是外部命令(即独立的可执行文件),Bash会创建一个新的子进程(通过fork系统调用),并在该子进程中执行该命令。
- 前台/后台执行:Bash会判断命令是在前台执行还是在后台执行。前台命令会阻塞终端,直到命令执行完毕;后台命令则会在子进程中异步执行,用户可以继续输入其他命令。
- 信号处理:在执行命令的过程中,Bash会监听并处理来自键盘的信号(如Ctrl+C、Ctrl+Z等),以便用户可以中断或暂停命令的执行。
- 命令退出:当命令执行完毕后,Bash会接收子进程的退出状态,并据此更新shell的退出状态
6.3 Hello的fork进程创建过程
用户在shel1界面输入指令 ./hello 2022111334 陈童 19103563486 1
Shell判断该指令不是内置命令,于是父进程调用fork函数创建一个新的子进程,获得父进程的一个副本(相同的虚拟地址空间但独立),此时子进程与父进程的PID不同。
6.4 Hello的execve过程
调用execve()函数并进一步调用loader加载器函数在当前进程(新创建的子进程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空间。其中loader删除子进程现有的虚拟内存段,创建一组新的段(栈与堆初始化为0),并将虚拟地址空间中的页映射到可执行文件的页大小的片chunk,新的代码与数据段被初始化为可执行文件的内容,然后跳到hello开头_s.
6.5 Hello的进程执行
hello程序在运行时,进程提供给应用程序的抽象有:(1)一个独立的逻辑控制流,它提供一个假象,好像我们的进程独占地使用处理器;(2)一个私有的地址空问,它提供一个假象,好像我们的程序独占地使用CPU内存。
操作系统提供的抽象有:
(1)逻辑控制流。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称为逻辑流。一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发地运行。
(2)上下文切换。操作系统内核使用一种称为上下文切换的叫高层形式的异常控制流来实现多任务。内核为每一个进程维持一个上下文。上下文就是内核重新启动一个被抢占的进程所需状态。
(3)时间片。一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。
(4)用户模式和内核模式。处理器通常使用某个控制寄存器中的一个模式位来提供这种功能。当设置了模式位时,进程就运行在内核模式里。一个运行在内核模式的进程可以执行指令集中的所有指令且可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,也不能直接引用地址空间中内核区内的代码和数据。
(5)上下文信息。上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。hello程序执行过程中,在进程调用execve函数后,进程就为hello程序分配新的虚拟地址空间,并执行程序。在程序执行过程中很大可能会发生进程调度等等。
6.6 hello的异常与信号处理
6.6.1异常的分类
6.6.2异常的处理方式
6.6.3运行结果及相关命令
(1)正常运行状态
(2)运行时按下Ctrl + C
按下Ctrl + C,Shell进程收到SIGINT信号,Shell结束并回收hello进程。
(3)运行时按下Ctrl + Z
按下Ctrl + Z,Shell进程收到SIGSTP信号,Shell显示屏幕提示信息并挂起hello进程。
(4)对hello进程的挂起可由ps和jobs命令查看,可以发现hello进程确实被挂起而非被回收,且其job代号为1。
(5)在Shell中输入pstree命令,可以将所有进程以树状图显示:
等等
(6)输入kill命令,则可以杀死指定(进程组的)进程:
(7) 输入fg 1则命令将hello进程再次调到前台执行,可以发现Shell首先打印hello的命令行命令,hello再从挂起处继续运行,打印剩下的语句。程序仍然可以正常结束,并完成进程回收。
(8)不停乱按
作为输出
6.7本章小结
本章介绍了进程的概念及其作用,并根据hello进行了有关进程创建和系统调用方面的阐述,如上下文切换等,最后通过相关linux命令对中止和暂停等信号加深了系统信号处理的理解,对各种情况进行了简要的展示。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:
在带有地址转换功能的计算机中,指令提供的地址(操作数)称为逻辑地址或相对地址。它由一个段标识符和段内偏移量组成,需要通过计算或转换才能得到物理地址。hello程序中的逻辑地址包含了段相关的偏移部分。
线性地址:
线性地址是逻辑地址转换为物理地址过程中生成的中间地址。在hello程序中,代码生成的逻辑地址在分段机制中被视为段内偏移地址,加上段基地址后得到线性地址。
虚拟地址:
虚拟地址是程序访问内存时使用的地址,经过地址翻译后转换为物理地址。虚拟地址与物理内存容量无关。在hello程序中,虚拟地址即为程序访问存储器时使用的地址。
物理地址:
物理地址是内存中每个字节单元的唯一标识。在内存中,每个字节单元都有一个唯一的存储器地址,即物理地址。在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利用页表来实现从虚拟地址到物理地址的翻译。
下面为页式管理的图示:
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。工作原理如下:
多级页表的工作原理展示如下:
7.5 三级Cache支持下的物理内存访问
如图为高速缓存存储器组织结构:
高速缓存的结构将m个地址位划分成了t个标记位,s个组索引位和b个块偏移位:
CPU发送一条虚拟地址,随后MMU按照7.4所述的操作获得了物理地址PA。根据cache的映射方式,将PA分为CT(标记位)CI(组索引),CO(块偏移)。根据CI寻找到正确的组,依次与每一行的tag比较,有效位有效且标记位一致则命中。如果命中,直接返回想要的数据。如果不命中,就依次去L2,L3,主存判断是否命中,命中时将数据传给CPU同时更新各级cache的储存。
进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,同时为这个新进程创建虚拟内存,创建当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
(1)删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
(2)映射私有区域。为新程序的代码、数据、.bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,.bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
(3)映射共享区域。hello程序与共享对象1ibc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
(4)设置程序计数器。execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。如图所示:
7.8 缺页故障与缺页中断处理
如果程序执行过程中发生了缺页故障,则内核调用缺页处理程序。处理程序执行如下步骤:
(1)检查虚拟地址是否合法,如果不合法则触发一个段错误,终止这个进程。
(2)检查进程是否有读、写或执行该区域页面的权限,如果不具有则触发保护异常,程序终止。
(3)两步检查都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。
7.9动态存储分配管理
动态内存管理的基本方法
- 堆分配器:
动态内存通常从称为堆的内存区域分配。堆分配器负责管理这一内存区域。
常见的堆分配器包括 malloc 和 free 这样的函数。
- 分配策略:
首次适应(First-Fit):从头开始搜索,找到第一个适合的空闲块进行分配。
最佳适应(Best-Fit):搜索整个空闲列表,找到最适合的块进行分配,减少内存浪费。
最差适应(Worst-Fit):分配最大的空闲块,希望剩余的空闲块足够大,以便将来使用。
- 释放内存:
当内存不再使用时,需要显式地释放它(如 free 函数)。释放的内存块会返回到空闲列表中,以便重用。
动态内存管理的策略
- 分割和合并(Splitting and Coalescing):
分割:当分配的内存块比请求的内存大时,可以将其分割成两个块,一个用于分配,另一个保留为空闲块。
合并:当释放的内存块相邻时,可以将它们合并成一个更大的块,减少内存碎片。
- 内存碎片管理:
内部碎片:分配的内存块大于请求的内存,导致一些内存未被使用。
外部碎片:空闲内存分散成许多小块,无法满足较大的内存分配请求。
策略:通过合并相邻空闲块、使用最佳适应策略等方法来减少碎片。
- 垃圾回收(Garbage Collection):
在一些编程语言中(如 Java、Python),动态内存管理由垃圾回收器自动处理,无需显式释放内存。
- 引用计数:每个对象维护一个计数器,记录有多少引用指向它。当计数为零时,释放该对象。
- 标记-清除(Mark-and-Sweep):在垃圾回收时,遍历所有引用,标记活跃对象,然后清除未标记的对象。
- 分代回收:将对象分为不同代,年轻代对象频繁回收,老年代对象较少回收,提高回收效率。
当调用 printf
函数时,可能会在内部调用 malloc
来分配缓冲区或其他内部数据结构。动态内存管理通过上述的分配和释放机制来确保内存的有效使用和管理。
7.10本章小结
本章主要介绍了hello的存储过程以及关于虚拟内存的相关知识,如VA到PA的转换,Cache的访问,内存映射,缺页故障及其处理,动态分配等。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。Linux就是基于Unix I/O实现对设备的管理。
8.2 简述Unix IO接口及其函数
Unix I/O接口基于文件描述符(file descriptor)的概念。文件描述符是一个非负整数,用于标识一个打开的文件、设备或网络连接。所有I/O操作都是通过文件描述符来执行的。
8.3 printf的实现分析
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
当程序调用getchar时,程序等待用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车(回车也在缓冲区中)。当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
8.5本章小结
本章简单介绍了关于hello的I/O管理,对Unix I/O函数有了一个简单的了解,对printf,getchar函数的实现进行了简要的分析。
(第8章1分)
结论
Hello程序的一生:
- 首先程序员进行对hello.c的编码。
- 使用gcc工具对hello.c进行编译得到hello可执行文件hello,途中hello.c文本程序经过了预处理、编译、汇编、链接操作。
- 在shell中执行hello,输入./hello 2022111334 陈童 19103563486 1
- Shell发现该命令不是内置命令,fork创建子进程获得父进程的一个副本,执行系统调用execve函数,进行上下文切换。
- 执行程序,VA通过MM U进行虚拟地址到物理地址的一个映射,去内存中访问数据(Cache, 主存)。若发生缺页,则进入缺页故障处理程序,完成调页后重新执行导致缺页故障的指令。
- 在程序运行的过程中,可接受外部的信号,如键盘的 Ctrl+c,内核发送sigint信号给前台作业,进行终止;键盘的Ctrl+z,对前台作业进行一个挂起。
- 程序运行结束后,向父进程发送sigchld信号。父进程完成对子进程的一个回收,删除这个进程的相关数据。
感悟:
通过本次大作业,我对一个程序从出生到结束的过程有了一个大致的了解,对其编译、运行、结束阶段有了一个整体的认知,加深了我对CSAPP一书的认识,将其知识进行了一个汇总。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
文件名 | 作用 |
hello.c | 源程序 |
hello.i | 预处理后得到的文本文件 |
hello.s | 编译后得到的文本文件 |
hello.o | 汇编后得到的二进制文件 |
hello | 连接后得到的可执行文件 |
hello.asm | hello.o的反汇编文件 |
hello1.asm | hello的反汇编文件 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Randal E.Bryant David R.O'Hallaron.深入理解计算机系统(第三版).机械工业出版社,2016.
[3] 深入理解计算机系统(CSAPP)含lab详解 完结-CSDN博客
[4] CS:APP3e, Bryant and O'Hallaron (cmu.edu)
(参考文献0分,缺失 -1分)