计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 信息安全
学 号 2021112136
班 级 2103201
学 生 车振宁
指 导 教 师 刘宏伟
计算机科学与技术学院
2022年5月
本论文详细论述了hello.c在Linux系统下的整个生命周期:从原始程序的现在再到执行,再到最后的终止回收。这一过程实现了了预处理、编译、汇编、链接、加载、运行、终止、存储、回收等操作,对计算机系统有了更深的认识。
关键词:P2P;计算机系统;进程;程序;编译原理;变量生存期;
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
1.1.1P2P(from program to process)
由.c文件经过预处理得到.i文件,在经过编译的到.s文件,然后经过汇编器汇编得到.s文件,最后连接器链接得到可执行目标程序hello。
Linux系统中,在shell中上输入./hello运行hello,shell会调用fork函数为hello生成子进程,再调用加载器execve在子进程中加载hello程序,于是hello从程序转换为了进程。
1.1.2 020(from zero-0 to zero-0)
zero-开始:程序本来在磁盘上保存,经过子进程调用execve加载后,内存创建了新的区域提供给hello的代码区、数据区以及运行时堆栈,映射虚拟内存,载入物理内存,然后进入程序入口处开始执行。
zero-结束:hello执行完成后,操作系统回收子进程,内核从系统中删除了hello的所有相关数据,hello所占的所有资源清零,hello以zero的形式结束了它的一生。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;16G RAM;256GHD Disk
软件环境:Windows 10、Vmware16.2.4、Ubuntu 22.04.2
开发工具:gedit,vim,edb,vscode,readelf,HexEdit
1.3 中间结果
文件名 | 文件功能 |
hello.c | 源程序 |
hello.i | 预处理得到的文本文件 |
hello.s | 编译得到的汇编文件 |
hello.o | 汇编得到的可重定位目标文件 |
hello | 链接得到的可执行目标文件 |
hello.elf | hello.o的ELF格式 |
hello1.elf | hello的ELF格式 |
1.4 本章小结
本章整体介绍了hello的实现换个运行过程,简要介绍了hello.c的P2P与020,配置了所需实验环境和工具,得到了整个过程的中间文件和结果。
第2章 预处理
2.1 预处理的概念与作用
2.1.1预处理的概念:C语言预处理是指在编译阶段之前,对源代码进行的一系列处理操作。预处理器会根据预处理指令(以#开头的语句)对源代码进行处理,生成一份新的代码文件,供编译器使用。
2.1.2预处理的作用:
宏定义:使用#define指令定义宏,可以将一些常用的代码片段或常量定义为宏,在代码中使用时可以直接使用宏名,提高代码的可读性和重用性。
文件包含:使用#include指令可以将其他文件中的代码包含到当前文件中,方便代码的组织和管理。
条件编译:使用#if和#ifdef指令可以根据条件来选择编译哪些代码,可以根据不同的平台或编译选项来选择不同的代码。
其他:预处理器还可以进行一些其他的操作,比如#error指令可以在编译时输出错误信息,#pragma指令可以控制编译器的行为等。
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
通过linux下的gedit工具打开hello.i文件,我们可以看到代码中包含了#include文件中的函数代码
Main函数代码位于文件末端
由预处理结果我们可以看到,预处理器(cpp)按照#的顺序进行预处理内容展开
(在hello.c中为stdio.h,unistd.h,stdlib.h),最终的hello.i文件中没有#开头的预处理命令,其包含了#include指示的文件的所有源代码以及重定位,符号引用等等,形成了hello.i文件。文件大小远远超过hello.c。
2.4 本章小结
通过本章具体的hello.c生成hello.i实例,深刻理解了预处理过程的步骤的生成文件。并对生成文件做出了详细的解析。
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念:编译的过程是进行词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件的过程。
3.1.2编译的作用;
编译的作用是将高级计算机语言所写作的源代码程序翻译为汇编语言程序,在这个过程中,会进行以词法分析、语法分析、语义分析来生成汇编语言程序,且编译器可能在这个过程中根据编译选项对程序进行一些适当的优化。
编译可以将高级程序设计语言翻译成较为底层的汇编语言,在这个过程中产生的汇编程序里的指令更加贴近于机器指令也更为贴近机器真正执行程序的过程,可以让程序员更好明白程序在机器层面上的底层实现。
词法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,方法分为两种:自上而下分析法和自下而上分析法。
优化代码:将C语言源代码转换为汇编语言可以更直观地了解程序的执行过程,帮助开发者更好地优化代码。
调试程序语义分析:汇编语言中的指令和C语言源代码是一一对应的,可以帮助开发者更深入地理解计算机底层的运行机制,通过汇编语言代码来定位和调试程序中的错误。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1:数据
main的局部变量i,保存在栈中。
(2)常量字符串:
位于只读数据段(.rodata)中,作为printf函数的参数。
(3)立即数
以$开头的数字,直接出现于汇编代码中。
(4)函数参数argc、数组函数参数*argv[]
作为传给main函数的参数,均被放入栈中,前者指示参数个数,后者每个元素存放一个指向字符的指针。
3.3.2:赋值
在hello.s中,赋值操作由mov指令实现,使用了movq指令。
movb:一个字节,movw:“字”,movl:“双字”,movq:“四字”3.3.3 类型转换
hello.c中atoi(argv[3])将字符串类型转换为整型。int、float、double、short、char可以进行相互转化。
3.3.4 算数操作
.c中的自加操作。
对应完成上述自加操作的汇编代码。下面是跳转判断指令。
其他汇编代码算术操作如下:
3.3.5关系操作
判断argc是否为4,若不为4则打印用法后退出,若为4则继续。
3.3.6 控制转移
.c 文件中的跳转代码。
在hello.s中通过cmp指令设置条件码,依据条件码进行控制转移。
与
其中,je用于判断cmpl产生的条件码,若两个操作数的值不相等则跳转到指定地址,jle用于判断cmpl产生的条件码,若后一个操作数的值小于等于前一个则跳转到指定地址。
3.3.7函数操作
调用函数时有以下操作:(P调用函数Q)
传递控制:调用过程Q的时候,程序计数器(%rip)必须设置为函数Q代码的起始地址,将P程序的下一条指令压入栈中。然后在返回时,要把程序计数器(%rip)设置为压入栈中的指令
传递数据:函数P必须能够向函数Q提供一个或多个参数Q必须能够向P中返回一个值。
分配和释放内存:在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些空间。
hello.c中的函数操作:
main函数:参数是int argc,char *argv[]
printf函数:参数是argv[1],argv[2],作用为打印所选内容。
exit函数:参数是1,功能为退出程序。
sleep函数:参数是atoi(argv[3]),功能为进程休眠atoi(argv[3])秒。
getchar函数:无参数,功能为从stdin流中截取一个字符。
atoi函数:参数是argv[3],功能为将所选字符串转化为整型变量。
3.4 本章小结
本章展示了hello.i通过编译得到hello.s文件,并对该文件的汇编指令进行解析,分析了其汇编代码中的数据和操作,加深了对汇编的理解。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:将汇编语言转换为机器语言的过程。结果保存在.o文件中,是一个二进制文件。
汇编的作用:完成汇编指令文件向可重定位目标文件的转化。把汇编语言翻译成计算机能够直接执行的0、1机器语言,把文本文件转化成二进制文件。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
应截图,展示汇编过程!
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.3.1
Elf头:
ELF头以一个16字节的序列(Magic)开始,它描述了系统的字的大小和字节顺序(大端序或者小端序)。ELF头剩下部分的信息包含帮助连接器语法分析和解释目标文件的信息。其中包括ELF头的类别,数据,版本,类型程序入口地点和起始地址等信息。
4.3.2
节头部表
在ELF头与节头部表之间的都为节。其描述了.o文件中出现的各个节的信息,包括节的名称、类型、地址、偏移量、所占空间大小等信息。
名称 | 内容 |
.text | 已编译程序的机器代码 |
.rodata | 只读数据 |
.data | 已初始化的全局和静态C变量 |
.bss | 未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量 |
.symtab | 一个符号表,存放一些在程序中定义和引用的函数和全局变量的信息 |
.rel.text | 一个.tex节中位置的列表 |
.rel.data | 被模块引用或定义的所有全局变量的重定位信息 |
.debug | 一个调试符号表 |
.line | 原始C源程序中的行号和.text节中机器指令之间的映射 |
.strtab | 一个字符串表(包括.symtab和.debug节中的符号表) |
4.3.3重定位
文件中有两个重定位节,分别是.rela.text和.rela.eh_frame节。不同的重定位节对应不同节的重定位信息。连接器在处理目标是要对目标文件进行重定位,这时需要重定位节中的信息才能知道如何进行重定位。.rela.text中保存了.text节中需要被修正的信息,注明了偏移量、寻址方式等信息。
.rela.eh_frame中保存了.eh_frame节重定位信息。
4.3.4符号表
符号表:符号表中为程序定义的函数以及全局变量信息,值得注意的是局部变量是存储在内存的栈中的。
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
机器语言是二进制的指令,每一串01数字就是一个机器指令,对应着一句汇编指令。二者以一种映射关系对应。
汇编文件中操作数为十进制,反汇编为十六进制
汇编文件中call和jump使用.L表示段名称,而反汇编中使用pc相对寻址。地址是有实际的数字呈现的。同时我们也可以发现,反汇编中的文件前有机器代码,每一段机器指令都对应一条汇编语言指令。可以认为这两种指令为双射关系。
不同之处:hello.s中跳转的目标地址直接记为段名称(L2,L3等),
而hello.o中跳转需确定目标地址
hello.s中函数调用是call+函数名称,但hello.o中call后跟的是所调用的函数相对调用之的函数的起始位置的相对地址。
4.5 本章小结
本章介绍了hello.s变为hello.o的过程,对可重定位ELF格式进行雷昂系的分析。深入理解了汇编代码和机器指令的关系。
第5章 链接
5.1 链接的概念与作用
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.1.1链接的概念
链接是指通过链接器,将各种代码与数据片段收集并合成为一个完全链接的单一文件的过程,可被加载到内存中并执行。其可以执行于编译时,可以执行于加载时(加载器加载到内存执行),或者执行与运行时,由应用程序加载执行。
5.1.2 链接的作用
链接器在软件开发过程中扮演着一个关键的角色,因为它们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其它文件。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
在终端shell中输入readelf -a hello > hello1.elf生成hello程序的ELF格式文件,并保存为hello1.elf(与第四章中的elf文件区分)
5.3.1ELF头
与hello.o的头文件不同,hello中Type为EXEC(Executable file) ,即hello为一个可执行目标文件,根据Number of section headers可知有25个节。
5.3.2节头
描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
5.3.3程序头
包含执行程序系统所需的段的信息。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
根据节头部表,通过edb可以找到各节的信息。
在edb中的虚拟地址和在elf文件中看到的虚拟地址是对应相同的,在edb中我们可以选择打开symbol viewer进行loaded symbls,也会加载出节头的虚拟地址信息。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
与hello.o的区别:
Hello.o没有经过链接,main的地址从0开始。hello将hello.o需要重定位的部分重定位后将其地址转变成了虚拟空间中绝对地址。
Hello.o中不存在调用库函数代码,hello中将库函数添加到了文件,并添加了节
在helllo.o的反汇编程序中,图中选中的函数调用指令由于还没有分配虚拟地址,所以只能用偏移量跳转,而右侧链接后已经分配好了虚拟地址,可以直接用call虚拟地址进行跳转.
跳转指令
函数调用中,链接后可以采用虚拟地址跳转
5.6 hello的执行流程
使用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的动态链接分析
当程序调用共享链接库时,我们无法预测这个函数的地址,因为定义他的模块可以再运行时加载到任何位置。所以我们要为每一个重定位生成一个解析然后再连接他们。采用延迟绑定的策略,把过程地址的加载推迟到第一次调用该进程。动态链接器使用GOT(全局偏移量表)和PLT(过程链接表)实现函数的动态链接。其中GOT 中存放函数目标地址,PLT使用 GOT中地址跳转到目标函数。
链接器采用延迟绑定的策略来避免运行时修改调用模块的代码段。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。
dl_init前:
got起始位置位于0x404000处
查看edb相关信息
GOT表位置在调用dl_init之前0x404008后的16个字节均为0:
dl_init后:
16字节的内容已经改变,即.got.plt的条目已经发生变化
5.8 本章小结
本章围绕链接展开,分析了ubuntu下ELF文件的各部分信息,并且使用edb加载hello。通过将反汇编hello文件和hello.o反汇编文件进行对比,比较两个反汇编文件的不同。详细的解释了重定位过程中对文件进行的操作。并对动态链接的过程也进行了深入的了解。
第6章 hello进程管理
6.1 进程的概念与作用
狭义定义:进程就是一段程序的执行过程。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的作用
进程提供给应用程序两个关键抽象:一个独立的逻辑控制流,程序貌似独占处理器;一个私有的地址空间,如同程序独占内存地址。
6.2 简述壳Shell-bash的作用与处理流程
shell是一个交互型的应用级程序,它代表用户运行其他程序,解释命令
shell处理中包括了:
读入命令:读取用户输入的命令
语法分析:对用户的指令进分析
内部指令执行:判断是否为内部指令,若是则在内部进行操作,执行后跳回1
外部程序调用:使用fork与exec家族函数进行系统调用,把执行程序调入内存。
接受signal:通过signal决定对程序执行的操作,如放到前台 杀死进程(同样通过signal实现)
使用wait家族函数等待程序执行结束
6.3 Hello的fork进程创建过程
输入命令执行hello后,父进程判断是不是内部指令。如果不是,则fork函数创建子进程。子进程获取了与父进程的上下文,包括栈、通用寄存器、程序计数器,环境变量和打开的文件相同的一份副本。Fork函数调用一次,返回两次,在父进程中,fork返回子进程的PID,在子进程中,fork返回0。子进程可以读取父进程打开的任何文件。当子进程运行结束时,父进程如果仍然存在,则执行对子进程的回收,否则就由init进程回收子进程。
6.4 Hello的execve过程
执行exexve时将会加载并运行程序。加载器将删除子进程现有的虚拟内存段,创建一组新的段(栈与堆初始化为0)通过将虚拟地址空间中的页映射到可执行文件的页大小的片, execve 调用驻留在内存中的、被称为启动加载器的操作系统代码,并创建一组新的代码、数据、堆和栈段。 新的代码和数据段被初始化为可执行文件中的内容。最后加载器(execve)设置PC指向_start 地址,_start 最终调用 hello中的 main 函数。
当出现错误时,execve才会返回-1到调用程序,execve调用成功则不会产生返回。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
(1)上下文信息:上下文信息是操作系统内核重新启动一个挂起的进程所需要恢复的原来的状态。它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的信息构成。
(2)时间片;一个进程执行它的控制流的一部分的每一时间段叫做时间片。
6.5.3进程调度过程
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
以执行sleep函数为例,sleep函数请求调用休眠进程,sleep将内核抢占,运行其他程序所挂载的进程,进入sleep倒计时,当倒计时结束后,hello程序重新抢占内核,返回hello所在的进程继续执行。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
可能出现的异常:
中断:来自I/O设备的信号,异步发生,中断处理程序对其进行处理,返回后继续执行调用前待执行的下一条代码
陷阱:有意的异常,是执行一条指令的结果,调用后也会返回到下一条指令,用来调用内核的服务进行操作。帮助程序从用户模式切换到内核模式。
故障:由错误情况引起的,它可能能够被故障处理程序修正。如果修正成功,则将控制返回到引起故障的指令,否则将终止程序。
终止:不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程序会将控制返回给一个abort例程,该例程会终止这个应用程序。
可能产生的信号
①不停乱按:
除了ctrl+c或ctrl+z,对进程不会造成影响
②ctrl+z:SIGTSTP
进程被暂时挂起
输入ps,该程序只是挂起而不是终止
ctrl+c:SIGINT
进程被SIGINT信号中断。
fg命令:使后台挂起的进程继续运行。
被SIGTSTP挂起的进程hello可以用fg指令继续运行。
jobs:显示目前运行的关键作业
pstree:以树状图的形式展现所有运行的进程
kill命令:向指定的进程或进程组传递指定的信息
6.7本章小结
本章介绍了进程的概念与作用,以及Shell-bash的基本概念。在这一章中根据hello可执行文件研究了fork, execve函数的原理与执行过程。介绍了hello异常与信号处理,分析并测试了在hello执行过程中外部键盘输入时的处理结果。
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
1.逻辑地址:程序经过编译后出现在汇编代码中的地址。
2.线性地址:逻辑地址向物理地址转换时的中间环节,hello中的偏移地址加上相应段的基地址就是线性地址。
3.虚拟地址:就是线性地址
4.物理地址:实际出现在CPU外部地址总线上的地址信号,表示实的物理内存所对应的地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在段式管理下,逻辑地址由两部分组成:段选择器和偏移量。段选择器指示了要访问的段,偏移量指示了在该段内的偏移量。
将段选择器和偏移量组合起来,就可以得到一个逻辑地址。然后,通过查找段描述符表,可以找到对应的段描述符,从而得到该段的基地址和限长。基地址加上偏移量就得到了线性地址。
如果线性地址超出了段的限长,则会触发异常。如果线性地址没有超出段的限长,则可以将其转换为物理地址,然后进行访问。
7.3 Hello的线性地址到物理地址的变换-页式管理
在页式管理下,线性地址由两部分组成:页目录项和页表项的偏移量。页目录项指示了要访问的页表,页表项的偏移量指示了在该页表内的偏移量。
将页目录项和页表项偏移量组合起来,就可以得到一个线性地址。然后,通过查找页目录表和页表,可以得到对应的物理页框号。将物理页框号和线性地址的偏移量组合起来,就可以得到物理地址。
在页式管理下,每个进程有自己的页目录表和页表,因此不同进程之间的线性地址可能对应不同的物理地址。此外,如果线性地址没有对应的物理页框号,则会触发缺页中断,需要将相应的页面从磁盘中加载到内存中。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB(Translation Lookaside Buffer)是一个高速缓存,用于存储最近访问过的页表项。当CPU需要访问一个虚拟地址时,首先会在TLB中查找相应的页表项,如果找到了,就可以直接得到物理地址;如果没有找到,就需要通过四级页表来进行变换。
在四级页表支持下,虚拟地址由四部分组成:页目录项、页表项、页内偏移和进程号。首先,CPU会使用页目录项和页表项偏移量来查找页表项,如果TLB中没有缓存,就需要在四级页表中进行查找。如果找到了页表项,就可以得到物理页框号。然后,将物理页框号和页内偏移组合起来,就可以得到物理地址。
如果TLB中没有缓存,并且四级页表中也没有找到相应的页表项,则会触发缺页中断。操作系统会将相应的页面从磁盘中加载到内存中,并更新页表项和TLB,然后再次执行地址变换。
7.5 三级Cache支持下的物理内存访问
在三级Cache支持下,物理内存访问的过程可以分为三个阶段:L1 Cache、L2 Cache和L3 Cache。
首先,当CPU需要访问一个物理地址时,会首先在L1 Cache中查找是否有相应的缓存。如果有,就可以直接得到数据;如果没有,则需要到L2 Cache中查找。如果没有,则需要到L3 Cache中查找。如果L3也没有,则需要到主存中查找。
如果查找到,则将数据复制到上级的所有Cache中,返回cpu。
如果在主存中也没有找到相应的数据块,则会触发缺页中断,操作系统会将相应的页面从磁盘中加载到内存中,并更新相应的缓存和页表,然后再次执行物理内存访问。
7.6 hello进程fork时的内存映射
在fork()系统调用执行时,操作系统会为新进程创建一个与原进程相同的内存映射,包括代码段、数据段、堆、栈等。具体来说,内存映射会包括以下内容:
代码段:新进程的代码段与原进程相同,都指向相同的物理页面。
数据段:新进程的数据段与原进程相同,都指向相同的物理页面。
堆:新进程的堆与原进程相同,但是它们各自拥有独立的虚拟地址空间和物理地址空间,因此它们不会相互影响。
栈:新进程的栈与原进程相同,但是它们各自拥有独立的虚拟地址空间和物理地址空间,因此它们不会相互影响。
7.7 hello进程execve时的内存映射
当一个进程执行execve()系统调用时,它会加载一个新的程序到内存中,替换当前进程的代码段、数据段、堆、栈等。因此,execve()系统调用执行后,进程的内存映射会发生变化,具体包括以下几个方面:
代码段:当前进程的代码段会被新程序的代码段所替换,即原来的代码段会被卸载,新程序的代码段会被加载到相同的虚拟地址空间中。
数据段:当前进程的数据段会被新程序的数据段所替换,即原来的数据段会被卸载,新程序的数据段会被加载到相同的虚拟地址空间中。
堆:当前进程的堆会被清空,新程序需要重新分配堆空间。
栈:当前进程的栈会被清空,新程序需要重新分配栈空间。
7.8 缺页故障与缺页中断处理
CPU发生缺页故障,进入内核态。
操作系统根据虚拟地址的页号,查找页表,判断需要加载的页面是否在磁盘中。
如果需要加载的页面在磁盘中,则操作系统会将相应的页面从磁盘中读取到内存中,并更新页表,将虚拟地址与物理地址进行映射。
如果需要加载的页面不在磁盘中,则会触发页面置换算法,将某些页面从内存中换出到磁盘中,以腾出空间来加载新的页面。
页面加载完成后,操作系统将控制权返回给用户程序,程序重新执行缺页故障引起的指令。
7.9动态存储分配管理
动态存储分配,即指在目标程序或操作系统运行阶段动态地为源程序中的量分配存储空间,动态存储分配包括栈式或堆两种分配方式。动态存储分配方式是不一次性将整个程序装入到主存中。可根据执行的需要,部分地动态装入。同时,在装入主存的程序不执行时,系统可以收回该程序所占据的主存空间。再者,用户程序装入主存后的位置,在运行期间可根据系统需要而发生改变。此外,用户程序在运行期间也可动态地申请存储空间以满足程序需求。
7.10本章小结
本章介绍了存储管理和虚拟页表的相关内容。介绍了hello进程中,段式管理,也介绍了存储映射和虚拟页表和hello进程的fork和execve的存储映射,和动态存储分配
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
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码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
(1)编写:程序员编写高级语言程序代码。
(2)预处理(cpp):对hello.c进行预处理,将文件包含、宏定义、条件编译的代码直接插入hello.c中
(3)编译(ccl):将hello.i文件转为汇编语言程序hello.s
(4)汇编(as):将hello.s文件转为可重定位目标文件hello.o
(5)链接(ld):将hello.o文件与各种链接库,目标文件连接成为可执行目标文件。
(6)运行:在shell中输入指令运行,和发送信号。
(7)创建子进程:在shell调用fork()函数创建子进程。
(8)加载(execve):shell调用execve函数映射虚拟内存,进入程序入口后程序开始载入物理内存,最后进入main函数执行
(9)执行指令:CPU为进程分配时间片,在一个时间片中,hello享有CPU资源, 顺序执行自己的控制逻辑流。
(10)访问内存:在主存中建立虚拟页表mmu通过页表将程序中使用的虚拟内存地址映射成物理地址。
(11)动态申请内存:函数调用malloc动态申请内存
(12)信号:在程序运行的时候发送信号,产生中断。
(13)终止:当子进程执行完成时退出,内核使用waitpid函数调用父进程回收子进程,并将子进程的退出状态传递给父进程,最终内核完全删除子进程的所有数据结构以及系统资源。是谓ZERO TO ZERO.
经过本次实验我对计算机系统有了更加深入的了解,对进程的产生和运行,再到中止有了更深刻的理解。掌握了深入理解计算机底层运行的能力和解决问题的能力。对后与课程的学习奠定了理论和能力基础。
附件
列出所有的中间产物的文件名,并予以说明起作用。
文件名 | 文件功能 |
hello.c | 源程序 |
hello.i | 预处理得到的文本文件 |
hello.s | 编译得到的汇编文件 |
hello.o | 汇编得到的可重定位目标文件 |
hello | 链接得到的可执行目标文件 |
hello.elf | hello.o的ELF格式 |
hello1.elf | hello的ELF格式 |
参考文献
- C语言编译概念理解_c语言编译是什么意思_大大大大大板牙的博客-CSDN博客 C语言编译概念理解
- 通过readelf工具解析ELF可重定位目标文件_readelf magic_BUFANG_XF的博客-CSDN博客 通过readelf工具解析ELF可重定位目标文件
- 进程(一段程序的执行过程)_百度百科 进程
文献:
[1] Randal E.Bryant, David O'Hallaron. 深入理解计算机系统[M]. 机械工业出版社.2018.4