计算机系统大作业-程序人生

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

摘 要

本文在做有关hello的实验基础上,对实验的相关知识以及相应的结果进行分析介绍,并且根据hello的生成以及运行描述了hello的从无到有再到无的整个过程。

关键词:hello、预处理、程序、编译、汇编、链接、进程管理、存储管理、IO管理


第1章 概述

1.1 Hello简介

P2P:
Hello从P(program)经过预处理器(cpp)变为修改了的源程序(文本)hello.i,后通过编译器(cc1)变为汇编程序(文本)hello.s,再通过编译器(as)生成可重定位目标程序(二进制)hello.o,最后和共享链接库通过链接器链接生成可执行目标程序hello。当用shell输入命令行执行这个可执行程序时,系统会对命令行解析并查找可执行文件,接着shell通过系统调用(如fork)来创建一个新的进程成为shell的子进程,至此一个新的进程P(process)创建完成了。
020:
020即From Zero-0 to Zero-0,在新进程中,shell调用另一个系统调用(如exec())来加载并执行可执行文件。exec()系统调用会替换当前进程的内存映像(即代码、数据、堆和栈)为新程序的内存映像。从此刻开始,新进程不再执行shell的代码,而是开始执行新程序的代码。如果新程序成功执行并退出,shell会接收到一个退出状态码。shell可以使用这个退出状态码来确定程序是否成功执行,并据此进行后续操作(如打印消息、设置条件语句的结果等)。当新程序执行完毕并退出时,系统会释放与该进程相关的所有资源(如内存、打开的文件描述符等)。这实现从无到有又到无的过程。

1.2 环境与工具

硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk
软件环境
Windows11,VMware 16;Ubuntu 20.04
开发与调试工具
Visual Studio2022,gcc,edb

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c 源程序文件
hello.i 预处理文件
hello.s 汇编文件
hello.o 可重定位目标文件
hello 可执行文件
hello.elf 可重定位目标文件的elf文件
hello.elf1 可执行文件的elf文件

1.4 本章小结

本章主要介绍了hello程序的p2p和020的过程;介绍了硬件环境、软件环境和开发与调试工具;描述了生成的中间文件的名字和作用。

第2章 预处理

2.1 预处理的概念与作用

概念
在C语言程序中,预处理是一个在编译之前对源代码进行处理的阶段。预处理阶段主要由预处理器完成,它的主要作用是对源代码进行宏替换、条件编译、头文件包含等处理,然后生成一个预处理后的文件,该文件通常与原始源文件具有相同的扩展名。
作用
宏替换:通过#define定义的宏在预处理阶段被替换为其对应的值或代码,从而简化代码并提高效率。
条件编译:通过#if等指令实现条件编译,可以根据需要编译不同的代码段,实现跨平台编程或调试代码。
头文件包含:通过#include指令包含头文件,实现代码的模块化,提高代码的可读性和可维护性。
生成预处理后的文件:预处理阶段结束后,预处理器会生成一个预处理后的文件,该文件是编译器接下来要处理的输入。

2.2在Ubuntu下预处理的命令

可以用gcc -E hello.c > hello.i命令来对hello.c文件进行预处理并保存到hello.i中(如图2.2.1所示)。
在这里插入图片描述

                            图2.2.1预处理命令

2.3 Hello的预处理结果解析

hello.c预处理后得到的hello.i文件的部分内容如图2.3.1所示。其中在文件的最后几行为hello.c中缺少注释和头文件的代码内容。并且我们可以发现hello.i文件有3061行内容,这远远大于c文件。
在这里插入图片描述

                       图2.3.1预处理后文件的部分内容 

2.4 本章小结

本章主要介绍了预处理的概念及作用;介绍了在Ubuntu下预处理的命令;解析了hello.c的预处理结果。

第3章 编译

3.1 编译的概念与作用

概念
编译是指将C语言编写的源代码转换为汇编语言代码的过程。这个过程由C语言编译器完成,它读取源程序,进行词法分析、语法分析、语义分析等一系列处理。
作用
将C语言编写的源代码(也称为源程序或源码)转换为汇编语言。汇编语言是一种低级编程语言,它使用简短的助记符(Mnemonics)来表示机器指令的操作码和操作数,从而允许程序员直接操作硬件资源,如寄存器、内存地址等。

3.2 在Ubuntu下编译的命令

在Ubuntu下可以用gcc -S hello.c -o hello.s来生成汇编文件(如图3.2.1)。
在这里插入图片描述

                          图3.2.1编译命令

3.3 Hello的编译结果解析

3.3.1数据
常量
整型常量:
在C语言值整型常量以数字表示,如下图3.3.1中的数字5。
在这里插入图片描述

                       图3.3.1 c文件中的整型常量

在hello的汇编语言中,存在整型常量,如图3.3.2所示的$5即为整型常量。
在.s文件中还有还多整型常量,这里便不一一列出了。
在这里插入图片描述

                     图3.3.2汇编文件中的整型常量

字符串:
C语言中字符串如图3.3.3所示,为“Hello 学号 姓名 手机号 秒数”。
在这里插入图片描述

                       图3.3.3 c文件中的字符串

汇编语言中字符串放在了LC0段,如图3.3.4所示
在这里插入图片描述

                       图3.3.4汇编文件中的字符串

变量
C语言中的变量以英文字符的方式表示,如下图3.3.5中的主函数参数argc。
在这里插入图片描述

                        图3.3.5 c文件中的变量

在hello的汇编语言中参数argc如图3.3.6所示,存在了-20(%rbp)中,并与5比较,如果不等于5,退出程序。
在这里插入图片描述

                         图3.3.6汇编文件中的变量

3.3.2赋值
在hello.c中赋值操作为把0赋值给变量i,如下图3.3.7所示。
在这里插入图片描述

                       图3.3.7 c文件中的赋值操作

而在汇编语言中赋值操作通过mov实现,如下图3.3.8,将i存入了-4(%rbp)中,并赋值为0。
在这里插入图片描述

                      图3.3.8汇编文件中的赋值操作

3.3.3类型转换
C语言中通过函数atoi来将字符串转化为整型常量,如图3.3.9。
在这里插入图片描述

                       图3.3.9 c文件中的类型转换操作

在汇编语言中也通过call的方式调用c语言标准库函数atoi来实现类型的转换,如下图3.3.10。
在这里插入图片描述

                      图3.3.10汇编文件中的类型转换操作

3.3.4算数操作
C语言代码中存在加法操作i++,其等价于i=i+1。如下图3.3.11。
在这里插入图片描述

                      图3.3.11 c文件中的算数操作

在汇编语言中加法用add实现,减法用sub实现。而在上文我们知道i值存入了-4(%rbp),所以汇编语言应为addl $1, -4(%rbp)。在hello.s中我们也会发现此汇编代码,如下图3.3.12。
在这里插入图片描述

                      图3.3.12汇编文件中的算数操作

3.3.5关系操作
不等于
C语言中存在关系操作“不等于”,如下图3.3.13所示,argc!=5。
在这里插入图片描述

                      图3.3.13 c文件中的关系操作

汇编中,我们已知了argc的值存入了-20(%rbp)(见上文)。如图3.3.14所示,其汇编语言为cmpl $5, -20(%rbp) je .L2。其含义为比较5和argc,如果相等跳转到.L2,如果不等,执行退出操作。
在这里插入图片描述

                      图3.3.14汇编文件中的关系操作

小于
C语言中存在关系操作“小于”,如下图3.3.15所示,i<10。
在这里插入图片描述

                     图3.3.15 c文件中的小于表示

汇编中,采用“小于等于”来代表“小于”,即i<=9来代表i<10,如下图3.3.16。
汇编指令为cmpl $9, -4(%rbp) jle .L4。其含义为比较9和i,如果i<=9则跳转到.L4,否则执行退出操作。
在这里插入图片描述

                      图3.3.16汇编文件中的小于表示

3.3.6数组操作
C语言中的数组操作为输出argv[1]、argv[2]、argv[3]的内容,如下图3.3.17。
在这里插入图片描述

                     图3.3.17 c语言文件中的数组表示

汇编语言中用栈指针的形式存储数组元素,如下图3.3.18。其中3个数组元素分别存在了$24, %rax $16, %rax $8, %rax中。
在这里插入图片描述

                       图3.3.18汇编文件中的数组表示

3.3.7控制转移
C语言代码存在控制转移for循环,如下图3.3.19。
在这里插入图片描述

                       图3.3.19 c语言中的控制转移

汇编中通过跳转指令来实现,如下图3.3.20,指令为jle .L4。对于如何跳转在上文3.3.5关系操作中已给出。
在这里插入图片描述

                      图3.3.20汇编文件中的控制转移

3.3.8函数操作
C语言中存在多个调用函数操作以及函数返回操作。
在汇编语言中,存在对应的汇编代码。举例如下图3.3.21。
在这里插入图片描述

                     图3.3.21汇编语言中的函数操作

3.4 本章小结

本章介绍了编译的概念与作用;介绍了在Ubuntu下编译的命令;解析Hello的编译结果。

第4章 汇编

4.1 汇编的概念与作用

概念
汇编的概念主要涉及到一个将汇编语言源代码转换为机器语言指令的过程,这个过程是通过汇编器来完成的。汇编语言是一种低级编程语言,它使用简短的助记符来表示机器指令的操作码和操作数,从而允许程序员直接操作硬件资源,如寄存器、内存地址等。
作用
在汇编过程中,汇编器会进行语法分析、符号表生成、目标代码生成等步骤。语法分析用于检查源代码是否符合汇编语言的语法规则;符号表生成则记录源代码中定义的所有符号及其地址;目标代码生成则是将汇编指令转换为对应的二进制代码。如果在汇编过程中发现错误,汇编器会输出相应的错误信息。
编译器在生成目标代码时,会尝试对源代码进行优化,以提高程序的执行效率。优化可以包括删除无用的代码、简化复杂的表达式、调整代码的执行顺序、进行循环展开等。这些优化操作可以使生成的程序运行得更快,占用更少的内存空间。

4.2 在Ubuntu下汇编的命令

可以用gcc -c hello.s -o hello.o来将汇编文件转化为机器语言二进制文件(如图4.2.1所示)。
在这里插入图片描述

                          图4.2.1汇编的命令

4.3 可重定位目标elf格式

可重定位目标elf格式如下图4.3.1。
在这里插入图片描述

                            图4.3.1elf格式

通过命令readelf -a hello.o > hello.elf生成可重定位目标文件的elf文件并保存到hello.elf中,如图4.3.2所示。
在这里插入图片描述

                 图4.3.2生成可重定位目标文件的elf文件的命令

hello.elf的内容:
ELF头(如图4.3.3)
在这里插入图片描述

                      图4.3.3 ELF头(可重定位文件)

节头部表(如图4.3.4)
在这里插入图片描述

                    图4.3.4 节头部表(可重定位文件)

重定位节(如图4.3.5)
在这里插入图片描述

                    图4.3.5 重定位节(可重定位文件)

重定位是链接过程中的一个阶段,其中链接器需要修改目标文件中的某些指令或数据,以便在最终的可执行文件中正确地引用符号(例如函数或变量)。这是因为链接器在链接时才知道各个目标文件中符号的最终地址。重定位节通常包含需要被修改的信息,以便在链接过程中正确地更新这些引用。这些节包含了足够的信息来告诉链接器在何处找到需要被修改的指令或数据,以及如何进行修改。

4.4 Hello.o的结果解析

通过objdump -d -r hello.o反汇编指令得出的结果如下图4.4.1所示。与hello.s对比可以得出在反汇编文件中,跳转目标使用的是PC相对的地址,比如callq 87 <main+0x87>。而在hello.s中跳转目标是段名(具体看第三章)。并且在反汇编中立即数都为十六进制,这与hello.s中十进制也不同。
在这里插入图片描述

                      图4.4.1可重定位文件的反汇编

因为十六进制与二进制数对应(一位对应四位),所以这也能说明hello.o是以一个二进制文件。汇编语言的每一条指令都对应着一条机器语言指令。汇编语言的助记符只是机器语言指令的一种符号化表示,它们之间存在着一一对应的关系。在汇编语言中,程序员可以使用标签(label)来标识代码中的特定位置。这些标签在汇编过程中会被转换成机器语言中的内存地址,以便在执行时能够准确地跳转到相应的代码段。汇编语言也支持变量和数据的定义和操作。在汇编过程中,这些变量和数据会被分配到相应的内存地址中,并使用机器语言的指令来访问和操作它们。

4.5 本章小结

本章介绍了汇编的概念与作用;介绍了介绍了在Ubuntu下汇编的命令;介绍了可重定位目标elf格式;解析了hello.o的结果。

第5章 链接

5.1 链接的概念与作用

概念
C语言中的链接是程序编译过程中的一个重要步骤,它涉及将程序中引用的外部变量或函数(如标准库或其他库定义的)进行解析,并将它们的定义包含到C语言程序中,从而形成一个完整的可执行程序。链接是将多个源代码文件以及相应的库函数合并成一个可执行文件的过程。这个过程主要解决程序中引用的外部变量或函数的定义问题,确保程序能够正确地执行。
作用
符号解析:链接器的主要任务之一是进行符号解析。在编译过程中,编译器会为程序中的每个全局变量、函数等生成一个符号,并在链接阶段查找这些符号的定义。如果链接器能够找到符号的定义,则将其与符号引用关联起来;否则,会报错并终止链接过程。
重定位:编译器和汇编器生成的代码和数据通常是从地址零开始的。链接器需要将这些代码和数据放置到它们在内存中的实际位置,并修改所有对这些符号的引用,使它们指向正确的内存地址。这个过程称为重定位。
生成可执行文件:链接器将多个目标文件(由编译器生成)和库文件组合成一个可执行文件。这个可执行文件包含了程序运行所需的所有代码和数据,并且已经解决了所有符号引用和重定位问题。

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。生成的链接结果存在hello文件中,如下图5.2.1所示。
在这里插入图片描述

                          图5.2.1链接的命令

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

用指令readelf -a hello > hello.elf1 将hello的可执行目标文件格式存到了hello.elf1中。如下图5.3.1所示。
在这里插入图片描述

                  图5.3.1生成可执行文件的elf文件的命令

ELF头(如图5.3.2)
在这里插入图片描述

                       图5.3.2 ELF头(可执行文件)

ELF头首先标识了该文件是一个ELF格式的文件,并区分了它是可重定位文件、可执行文件还是共享对象文件。ELF头包含了关于文件的基本信息,如机器架构(如32位或64位)、数据编码方式(如大端或小端)以及操作系统版本等。ELF头定义了文件的组织方式,包括节和段的数量和位置。节是文件在磁盘上的表示,而段是文件在内存中的表示。对于动态链接的可执行文件,ELF头还包含了与动态链接相关的信息,如动态节和动态段的位置和大小。
节头部表(如图5.3.3)
在这里插入图片描述

                       图5.3.3节头部表(可执行文件)

节头部表包含了关于文件中各个节的描述信息。每个节在表中都有一个对应的条目,该条目详细描述了该节的属性,如节的名称、大小、在文件中的偏移量等。链接器在构建最终的可执行文件或共享库时,需要知道各个节的信息,以便进行符号解析、重定位等操作。节头部表为链接器提供了这些信息。
程序头表(如图5.3.4)
在这里插入图片描述

                      图5.3.4程序头表(可执行文件)

程序头表包含了描述文件中各个段的条目。这些段定义了当可执行文件被加载到内存中时,哪些数据应该被加载到哪些地址。每个段在内存中都有一个对应的映像,并且包含了必要的权限信息(如读、写、执行权限)。
符号表(如图5.3.5)
在这里插入图片描述

                      图5.3.5符号表(可执行文件)

符号表用于存储程序中各个标识符(如变量、函数、类等)的声明和定义信息。这些信息包括符号的名称、数据类型、作用域、存储位置等。对于每个标识符,符号表会记录其相关的属性信息,确保在后续的语义分析、中间代码生成和代码优化等过程中能够正确地进行查找和处理。
重定位节(如图5.3.6)
在这里插入图片描述

                      图5.3.6重定位节(可执行文件)

在链接过程中,链接器需要将各个目标文件中的符号引用与相应的符号定义进行连接。重定位节就负责保存了这些连接所需的信息。

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,如图5.4.1所示。并与5.3对照分析说明。
在这里插入图片描述

                          图5.4.1 edb调试结果

由图中Data Dump显示的内容可知,程序的开始地址为00401000,这正对应里节头部表中的init的地址。在Data Dump中的地址对应了节头部表的各个地址,再比如.text的在节头部表中的首地址004010f0,对应于Data Dump的地址004010f0。

5.5 链接的重定位过程分析

通过objdump -d -r hello指令得到hello的反汇编语言,如图5.5.1所示。
在这里插入图片描述

                       图5.5.1可执行文件的反汇编

通过对比hello.o的反汇编文件我们可以发现其中多了一些段如_init .plt等等。并且增加一些函数printf 、atoi、getchar等等。主函数的地址也发生改变,比原来的增加了一个偏移量。
链接器将所有目标文件中的相同类型的节(如代码节、数据节等)合并成一个大的节。这个过程中可能会进行一些优化,例如去除重复的代码和数据。
链接器进行重定位,由于目标文件中的代码和数据在内存中的最终位置在编译时是无法确定的,所以编译器在生成目标文件时使用了相对地址。在重定位阶段,链接器会确定每个目标文件在内存中的最终位置,并修改其中的代码和数据地址,使其指向正确的内存位置。

5.6 hello的执行流程

从加载hello到_start,到call main,以及程序终止的所有过程记录如下,包括跳转的各个子程序名和程序地址。
ld-2.32.so!_dl_sta 0x7f96ed2e1ea0
ld-2.32.so!_dl_init 0x7f96ed2f0630
hello! _start 0x400500
libc-2.32.so! __libc_start_main 0x7fbdf0cccab0
hello!puts@plt 0x401030
hello!exit@plt 0x401060
hello!printf@plt 0x401050
hello!sleep@plt 0x401070
hello!getc@plt 0x401080
libc-2.32.so!exit 0x7f66b7b9e120

5.7 Hello的动态链接分析

从节头部表中找到.GOT.PLT的地址为0x404000 (见5.3)。
链接前GOT.PLT表如图5.7.1所示。
在这里插入图片描述

                        图5.7.1链接前GOT.PLT表

链接后GOT.PLT表如图5.7.2所示。
在这里插入图片描述

                      图5.7.2链接后GOT.PLT表

动态链接后我们可以发现部分0值被赋予了新的值。

5.8 本章小结

本章介绍了链接的概念与作用;介绍在Ubuntu下链接的命令;介绍了可执行目标文件hello的格式;介绍了hello的虚拟地址空间;分析了链接的重定位过程;介绍了hello的执行流程;分析了Hello的动态链接。

第6章 hello进程管理

6.1 进程的概念与作用

概念
进程是操作系统进行资源分配和调度的基本单位,是程序执行时的一个实例。当一个程序被加载到内存中并准备执行时,它就成为了一个进程。一个程序可以有多个进程,例如,一个Web服务器程序可以同时处理多个客户端的请求,每个请求都可能对应一个独立的进程。
作用
资源隔离:进程是资源分配的基本单位。操作系统为每个进程分配独立的内存空间和其他资源,确保进程之间的资源互不干扰。这样,一个进程的崩溃不会影响到其他进程的执行。
操作系统可以同时运行多个进程,实现并发执行。这大大提高了系统的吞吐量和响应速度。例如,在一个多用户操作系统中,多个用户可以同时运行各自的程序,而不会相互干扰。
通过进程管理,操作系统可以对进程进行严格的控制和管理,确保系统的安全性和稳定性。例如,操作系统可以设置进程的优先级、限制进程的资源使用、终止异常进程等。

6.2 简述壳Shell-bash的作用与处理流程

作用
bash是Linux系统下的命令行解释器,它允许用户通过输入命令与系统进行交互。这些命令可以是简单的文件操作命令,也可以是复杂的脚本或程序。bash负责解析用户输入的命令,并将其传递给系统内核进行处理。
bash作为用户和Linux内核之间的桥梁,使得用户可以通过简单的命令来管理复杂的系统资源。用户不需要直接操作内核或硬件设备,只需要通过bash输入相应的命令即可实现。
bash支持通过上下键调用执行过的命令,以及通过TAB键补全命令和参数,这大大提高了用户的工作效率。同时,bash还记录了用户输入的命令历史,方便用户查看和重复使用。
处理流程
当用户输入一个命令时,bash会创建一个子进程来执行该命令。如果命令已经处于子shell内,则不会再次创建子进程。在命令执行之前,bash会处理命令中的重定向操作,如输入重定向和输出重定向。bash通过调用execve()系统调用来将控制权移交给操作系统。操作系统会判断该文件是否是可执行的,如果是,则调用相应的函数或解释器来执行该文件。如果文件不是操作系统能够直接处理的可执行格式(如文本文件但没有顶格写的#!),bash会判断该文件是否有可执行权限并且不是一个目录。如果满足条件,bash会认为该文件是一个脚本,于是调用默认解释器(如bash自身)来解释执行该文件的内容。命令执行完毕后,bash会收集命令的返回值,并根据返回值判断命令是否执行成功。用户可以通过查看命令的返回值来了解命令的执行情况。

6.3 Hello的fork进程创建过程

输入./hello后,父进程会用fork函数创建子进程hello。新创建的子进程几乎和父进程相同。子进程得到和父进程相同但独立的一份副本,包括代码和数据段,堆,共享库和用户栈。
由于子进程有和父进程任何打开文件描述符相同的副本,所以父进程打开的文件,子进程可以读写。父进程和子进程最大不同是它们有不同pid。

6.4 Hello的execve过程

execve函数在当前进程的上下文中加载并运行一个新程序。execve函数加载并运行可执行目标文件Hello,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序,调用成功不会返回。与fork不同,fork一次调用两次返回,execve一次调用从不返回。

6.5 Hello的进程执行

进程上下文信息是指当一个进程从CPU上被移除,另一个进程开始执行时,操作系统需要保存和恢复的所有信息。这些信息包括:
程序计数器:指向当前进程正在执行的指令的地址。
寄存器:包括CPU中存储的各种信息,如通用寄存器、浮点寄存器、状态寄存器等。
内存管理信息:如进程的页表、内存映射等。
文件描述符和I/O状态:进程打开的文件及其状态信息。
审计和安全性信息:用于安全检查和审计的信息。
进程时间片是指操作系统分配给每个进程执行的时间段。在这个时间段内,进程可以运行其代码并访问系统资源。当时间片用完时,操作系统会停止该进程的执行,并将其状态保存为上下文信息,然后调度下一个进程执行。
进程调度的过程
就绪队列:操作系统维护一个或多个就绪队列,其中包含了等待CPU资源的进程。
调度算法:根据一定的调度算法(如先进先出、优先级调度、时间片轮转等),从就绪队列中选择一个进程。
上下文切换:
保存当前运行进程的上下文信息(如程序计数器、寄存器值等)。
加载被选中进程的上下文信息到CPU中。
将控制权交给被选中的进程,使其开始执行。
时间片管理:
当被选中进程的时间片用完或遇到需要阻塞的操作(如等待I/O)时,操作系统会停止该进程的执行。
保存该进程的上下文信息。
将该进程移到相应的等待队列(如I/O等待队列)中。
选择下一个进程进行调度。
循环执行:操作系统不断重复上述过程,以确保系统中的进程能够有序、公平地获取CPU资源。
Hello程序接收到sleep信号后会进行用户模式和内核模式的切换,如下图6.5.1所示。
在这里插入图片描述

                      图6.5.1hello接收信号后的运行

6.6 hello的异常与信号处理

乱按(如图6.6.1)
在这里插入图片描述

                         图6.6.1乱按后的输出

程序出现我们乱打入的字符。
回车(如图6.6.2)
在这里插入图片描述

                        图6.6.2按回车后的输出

程序输出中也有换行,并且延时作用在shell行。
Ctrl-Z(如图6.6.3)
在这里插入图片描述

                       图6.6.3按ctrl+z后的输出

程序暂停。
Ctrl-C(如图6.6.4)
在这里插入图片描述

                       图6.6.4按ctrl+c后的输出

程序终止。
ps(如图6.6.5)
在这里插入图片描述

                     图6.6.5暂停后按输入ps的输出

暂停前输出了四个hello…,通过ps可以发现有三个hello的进程。
jobs(如图6.6.6)
在这里插入图片描述

                      图6.6.6暂停后按输入jobs的输出

暂停的操作通过jobs会被列出来。
pstree(如图6.6.7)
在这里插入图片描述

                     图6.6.7暂停后按输入pstree的输出

通过pstree我们可以看到所有进程之间的父子关系,可以看到我们的hello进程是shell(bash)创建的进程。
fg(如图6.6.8)
在这里插入图片描述

                      图6.6.8暂停后按输入fg的输出

fg指令可以让暂停的作业重新回到前台运行,打印出hello程序未打完的字符。
kill(如图6.6.9)
在这里插入图片描述

                      图6.6.9暂停后按输入kill的输出

kill命令结束了暂停的进程。

6.7本章小结

本章介绍了进程的概念和作用;简述了壳Shell-bash的作用和处理流程,介绍了Hello的fork进程创建过程;介绍了Hello的execve过程,简述了Hello的进程执行过程,并分析了在程序运行过程中键入不同的按键会产生的结果。

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:
也称虚拟地址,是程序所使用的地址空间,是程序代码中使用的地址。
它是由程序员或操作系统生成的,指向内存中的一段地址空间,是相对于程序的起始地址(即基址)的偏移量。
在程序执行时,逻辑地址(或虚拟地址)会被翻译成物理地址。
线性地址:
是逻辑地址到物理地址变换之间的中间层。
在分段部件中,逻辑地址是段中的偏移地址,加上基地址就形成线性地址。
线性地址是一个32位无符号整数,可以用来表示高达4GB的地址,即高达4294967296个内存单元。
如果启用了分页机制,线性地址可以再经过变换以产生一个物理地址。如果没有启用分页机制,线性地址直接就是物理地址。
虚拟地址:
在Windows程序中,当程序运行在386保护模式下时,程序访问存储器所使用的逻辑地址称为虚拟地址。
虚拟地址可以写为“段:偏移量”的形式,这里的段是指段选择器。
在分页机制下,虚拟地址空间被划分为固定大小的页,每一页或者被映射到物理内存,或者被映射到硬盘上的交换文件中,或者没有映射任何东西。
CPU通过内存管理单元(MMU)自动将虚拟地址转换成物理地址。
物理地址:
是内存中真实的地址,由CPU生成,用来访问实际的内存单元。
物理地址是放在寻址总线上的地址,用于直接访问物理内存中的数据。
物理内存是以字节(8位)为单位编址的,物理地址就是内存中各存储单元的编号。

7.2 Intel逻辑地址到线性地址的变换-段式管理

全局描述符表和局部描述符表存储了段描述符。这些描述符包含了段的基地址、长度、权限等信息。GDT对所有进程都是可见的,而LDT是特定于进程的。在大多数操作系统中,特别是Linux,通常只使用GDT。每个进程都有一个或多个段寄存器,如CS(代码段寄存器)、DS(数据段寄存器)等。这些寄存器中存储的是段描述符在GDT或LDT中的索引值。首先,根据段选择器从GDT或LDT中找到相应的段描述符。从段描述符中提取出段的基地址(BASE字段)。将偏移量与基地址相加,得到线性地址。

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

页式管理是一种内存管理技术,它将进程的虚拟空间(即线性地址空间)划分为若干个长度相等的页,并将物理内存也划分为同样大小的页面。
将线性地址划分为页号和页内偏移量。页号用于在页表中查找对应的页表项,页内偏移量用于在物理页面内定位具体的字节。
使用页号作为索引,在页表中查找对应的页表项。页表通常存放在主存中,因此每次查找页表都需要访问一次内存。
从找到的页表项中提取物理页面的基地址。
将物理页面的基地址与页内偏移量相加,得到最终的物理地址。

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

虚拟地址检查TLB:
当CPU需要访问某个内存地址时,首先会检查TLB中是否存储了该虚拟地址的页表项。TLB是一种特殊的高速缓存,它存储了最近使用的页表项,用于加速虚拟地址到物理地址的转换。
如果TLB命中,即TLB中存在该VA对应的页表项,则可以直接从TLB中获取物理页号和页内偏移量,从而计算出物理地址。此时,无需进一步访问页表。
TLB未命中时访问页表:
如果TLB未命中,即TLB中没有该VA对应的页表项,CPU需要访问页表来获取VA到PA的映射关系。
在四级页表结构中,CPU首先会访问页全局目录,通过VA的最高位索引到PGD页表中的一个条目。该条目指向下一级页表的基地址。
然后,CPU使用VA中的下一段位索引PUD页表,找到指向下一级页表的条目。
接着,CPU继续使用VA中的下一段位索引PMD页表,找到指向页表条目的条目。
最后,CPU使用VA中的最低位索引PTE页表,找到包含物理页号和页属性信息的页表项。
计算物理地址:
一旦从PTE页表中获取了物理页号和页内偏移量,CPU就可以计算出物理地址。物理地址由物理页号左移页大小位(例如,对于4KB页面如图7.4.1所示,左移12位)并加上页内偏移量得到。
更新TLB:
为了加速未来的地址转换,CPU会将从页表中获取的页表项信息(包括物理页号和页属性)缓存到TLB中。这样,在下次访问相同的虚拟地址时,就可以直接从TLB中获取物理地址,而无需再次访问页表。
在这里插入图片描述

                          图7.4.1 4K页表的4级转换

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

L1 Cache 访问:
当CPU需要读取或写入某个物理地址的数据时,它首先会检查L1缓存(通常是分为L1指令缓存和L1数据缓存)。L1缓存是CPU访问速度最快的缓存,因为它直接集成在CPU内部。
如果L1缓存中存在所需的数据(缓存命中),则CPU直接从L1缓存中读取或写入数据,无需访问更慢的内存层次结构。
L2 Cache 访问:
如果L1缓存中不存在所需的数据(缓存未命中),CPU会检查下一级缓存,即L2缓存。L2缓存通常比L1缓存大,但访问速度稍慢。
如果L2缓存中存在所需的数据,CPU会从L2缓存中读取或写入数据。
L3 Cache 访问:
如果L2缓存中也没有所需的数据,CPU会检查L3缓存(如果存在的话)。L3缓存是三级缓存中最大的一级,但访问速度比L1和L2慢。
如果L3缓存中找到了所需的数据,CPU会从L3缓存中读取或写入数据。
物理内存(RAM)访问:
如果三级缓存中都没有所需的数据(缓存未命中),CPU会发送请求到物理内存(RAM)以获取数据。RAM是存储数据和程序的主要位置,但访问速度比缓存慢得多。
一旦数据从RAM中读取到,它通常会被存储到L3、L2和L1缓存中(取决于缓存替换策略和缓存大小),以便将来的快速访问。
三级Cache下的物理内存访问的过程如下图7.5.1所示。
在这里插入图片描述

                        图7.5.1 3级Cache

7.6 hello进程fork时的内存映射

内存映射:
当父进程创建子进程时,子进程会继承父进程的虚拟地址空间的一个“副本”。但是,这个“副本”是虚拟的,意味着它们共享相同的物理页面(如果它们引用相同的内存区域),但每个进程都有自己的虚拟地址到物理地址的映射表。
子进程的内存布局与父进程在创建时的布局相同,但它们是独立的副本。对子进程的内存修改不会直接影响父进程(除非它们共享某些内存区域,如通过共享内存、文件映射或内存映射文件)。
代码段、数据段和堆栈:
代码段(也称为文本段或程序段)包含程序的指令代码。当子进程被创建时,它继承父进程的代码段的一个副本(或共享相同的物理页面,但每个进程有自己的虚拟地址映射)。
数据段包含全局变量和静态变量。同样,子进程继承父进程数据段的一个副本。
堆栈用于局部变量、函数调用和返回地址。每个进程都有自己的堆栈,子进程的堆栈是独立的。
共享内存:
虽然父进程和子进程通常具有独立的内存空间,但它们可以通过某些机制(如共享内存、内存映射文件、消息队列等)共享数据。
当使用共享内存时,两个进程都可以访问同一块物理内存区域,但每个进程仍然通过其自己的虚拟地址空间进行访问。

7.7 hello进程execve时的内存映射

查找和加载可执行文件:
操作系统首先根据execve函数的第一个参数(通常是可执行文件的路径名)在文件系统中查找该文件。
一旦找到文件,操作系统会将其加载到内存中,通常是在一个新的地址空间中。
释放旧内存映射:
在加载新程序之前,操作系统会释放当前进程(调用execve的进程)的所有内存映射,包括代码段、数据段、堆和栈。
这意味着原来的代码、数据和所有变量(包括局部变量、全局变量、静态变量等)都会被丢弃。
创建新内存映射:
操作系统为新程序创建一个新的内存映射,包括代码段、数据段、堆和栈。
这个新的内存映射是根据新加载的可执行文件的布局和需求来创建的。
设置参数和环境变量:
execve函数的第二和第三个参数(分别是argv和envp)用于设置新进程的参数和环境变量。
这些参数和环境变量会被复制到新进程的内存空间中,并在程序执行时使用。

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

当发生缺页故障时,操作系统会介入并处理此中断,以确保程序能够继续执行。处理过程通常包括以下步骤:

硬件陷入内核:硬件会触发一个异常,将控制权交给操作系统内核。在内核堆栈中保存程序计数器(PC)和其他相关状态信息。
保存现场信息:启动一个汇编代码例程来保存通用寄存器和其他易失的信息,以避免被操作系统破坏。这个例程实际上是将操作系统作为一个函数来调用。
发现需要的虚拟页面:操作系统会尝试确定需要哪个虚拟页面。这通常通过一个硬件寄存器或检索程序计数器并分析当前指令来完成。
页面有效性检查:操作系统会检查此次访问是否有效。如果无效(例如,访问了受保护的内存区域),则操作系统会结束相关的进程。
页面调入:如果访问是有效的,操作系统会尝试从硬盘上的虚拟内存文件(如交换空间或页面文件)中调入所需的页面到物理内存中。
更新页表:一旦页面被加载到物理内存中,操作系统会更新页表以反映这一变化,确保虚拟地址能够正确地映射到新的物理地址。
恢复执行:最后,操作系统会恢复被中断的程序的执行,从之前保存的程序计数器开始继续执行。

7.9动态存储分配管理

动态内存分配:在运行时根据需要动态地分配和释放内存空间。这种方式允许程序在运行时根据需要调整内存使用量。
堆内存分配:通过调用系统提供的动态内存分配函数(如malloc、calloc、realloc等)来分配和释放内存。这些函数允许程序在运行时请求任意大小的内存块。
栈内存分配:将变量存储在栈上,由系统自动管理其生命周期。栈内存分配通常用于函数内的局部变量。
内存回收:
内存回收用于释放不再使用的内存空间,以避免内存泄漏和浪费。常见的内存回收方法有:
标记清除法:标记出活跃对象,清除未被标记的对象。
复制法:将活跃对象复制到另一块内存区域,清除原始内存区域。
分代收集法:根据对象的生命周期将内存划分为不同的代,不同代采用不同的回收策略。
内存整理:
内存整理用于整理和重新排列内存中的对象,以提高内存的利用率和访问效率。常见的内存整理方法有:
垃圾回收:自动识别并清除无用的对象,释放其占用的内存空间。
内存紧缩:将分散在内存中的对象集中到一起,释放间隙空间。
内存分区:将内存划分为多个分区,每个分区存储不同类型或不同生命周期的对象。
其他动态内存管理策略:
使用new和delete对:这是C++中常见的动态内存管理方法,通过new为对象分配内存,使用完后通过delete释放内存。
使用智能指针:如shared_ptr和make_shared,它们可以自动管理内存,当没有指针指向某个对象时,该对象的内存会自动释放,避免内存泄漏。

7.10本章小结

本章介绍了helllo的存储器地址空间;介绍了Intel逻辑地址到线性地址的变换-段式管理;介绍了Hello的线性地址到物理地址的变换-页式管理;介绍了TLB与四级页表支持下的VA到PA的变换;介绍了三级Cache支持下的物理内存访问;介绍了hello进程fork时的内存映射;介绍了hello进程execve时的内存映射;介绍了缺页故障与缺页中断处理;介绍了动态存储分配管理。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

初始化和配置硬件:在系统启动或驱动加载时,对硬件进行初始化,并设置必要的工作模式和参数。
设备文件接口:为应用程序提供统一的设备访问接口,如字符设备或块设备接口,使得用户空间的进程可以打开、读写以及控制设备。
数据传输处理:实现数据从主存到外部设备或从外部设备到主存的传输。
中断处理:响应设备产生的中断请求,执行相应的数据处理任务。
错误检测和恢复:监测设备运行状况,处理可能出现的错误并尝试恢复。

8.2 简述Unix IO接口及其函数

Unix IO接口为Unix系统下的输入/输出操作提供了一组统一的接口,这些接口使得开发者可以方便地对文件、设备等进行读写操作。
open():打开或创建一个文件或设备,并返回一个文件描述符。
read():从已打开的文件或设备中读取数据。
write():将数据写入已打开的文件或设备。
close():关闭已打开的文件或设备,并释放与之相关的资源。
lseek():更改文件位置指针,以便可以从特定位置读取或写入数据。
fcntl():对已打开的文件或设备执行各种操作,如获取/设置文件状态标志、获取/设置文件锁定等。
ioctl():对已打开的文件或设备执行特定于设备的操作。

8.3 printf的实现分析

在Linux中,printf的源码大致是这样的(如图 8.3.1)。通过va_start获取可变参数列表,实质也是通过地址操作,然后调用vsprintf,把格式化字符串根据参数列表生成实际显示用的字符串,最后使用调用write去调用Unix I/O的write函数。
在这里插入图片描述

                       图8.3.1 printf的源代码

其中调用的vsprintf代码如下图8.3.2所示。
在这里插入图片描述

                        图8.3.2 vsprintf代码

write的汇编代码见下图8.3.3。
在这里插入图片描述

                       图8.3.3 write汇编语言

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

getchar 是 C 语言中的一个标准库函数,用于从标准输入(通常是键盘)读取一个字符,并在读取成功后返回该字符的 ASCII 值。它在 <stdio.h> 头文件中定义。与 printf 类似,getchar 的具体实现也依赖于操作系统和 C 库的实现,但我们可以从一般性的角度来分析其工作原理。getchar 首先会检查是否有之前已经读取但尚未被程序使用的字符(即输入缓冲区中是否有数据)。如果有,它会直接从缓冲区中取出一个字符并返回其 ASCII 值。如果输入缓冲区中没有数据,getchar 会阻塞程序执行,等待用户输入一个字符。这通常涉及到与操作系统进行交互,以便接收来自键盘的输入。当用户输入一个字符并按下回车键时,操作系统会捕获这个输入,并将其放入输入缓冲区。getchar 函数会读取缓冲区中的第一个字符,并将其 ASCII 值返回给调用者。如果在读取字符的过程中发生错误(如输入流关闭、读取失败等),getchar 可能会设置全局变量 errno 以指示错误,并返回一个特殊的值(如 EOF,通常定义为 -1)来表示读取失败。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章介绍了Linux的IO设备管理方法;简述了Unix IO接口及其函数;分析了printf及getchar的实现。

结论

Hello.c经过预处理器(cpp)变为修改了的源程序(文本)hello.i,后通过编译器(cc1)变为汇编程序(文本)hello.s,再通过编译器(as)生成可重定位目标程序(二进制)hello.o,最后和共享链接库通过链接器链接生成可执行目标程序hello。当用shell输入命令行执行这个可执行程序时,系统会对命令行解析并查找可执行文件,接着shell通过系统调用(如fork)来创建一个新的进程成为shell的子进程,当键入不同的按键时,进程会接收相应的信号并作出相应的操作(停止、终止等)。当程序运行时也会调用printf、getchar函数在屏幕打印出我们想要的结果。
计算机系统是一个高度复杂的系统,涵盖了硬件、软件、网络等多个层面。然而,优秀的设计往往能够在这种复杂性中寻找到简洁的解决方案。简洁的代码、清晰的架构和直观的用户界面,都是对复杂性的有效管理。这让我意识到,在设计和实现系统时,追求简洁和清晰是至关重要的。

附件

列出所有的中间产物的文件名,并予以说明起作用。
hello.c 源程序文件

hello.i 预处理文件

hello.s 汇编文件

hello.o 可重定位目标文件

hello 可执行文件

hello.elf 可重定位目标文件的elf文件

hello.elf1 可执行文件的elf文件

参考文献

[1]网站:https://ysyx.oscc.cc/slides/hello-x86.html
[2]网站:http://t.csdnimg.cn/TYS4l
[3]兰德尔·E,布莱恩特等. 深入理解计算机系统[M]. 北京:机械工业出版社,2016.7(2022.1重印)

  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值