目 录
第1章 概述
1.1 Hello简介
P2P:也就是我们所熟知的From Program to Process,从源文件到目标文件的转化首先是由编译器驱动程序来完成的,用高级文件编写hello.c文件,然后GCC编译器读取源文件hello.c,并把它编译为一个可以执行的目标文件hello。这个编译可以分为四个阶段完成。通过预处理、编译、汇编、链接四个阶段将它翻译成一个可执行目标文件。当在shell中输入这个可执行文件的名字时shell会调用fork函数为hello程序创建一个新进程。Linux系统中通过内置命令行解释器shell加载运行hello程序,为hello程序fork进程,至此,hello.c完成了P2P的过程。
O2O:shell通过execve在fork产生的子进程中加载执行hello可执行文件,首先删除已经存在于虚拟地址的用户部分的数据结构,并为hello文件创建新的区域结构,然后映射虚拟内存,设置程序计数器,使之指向代码区域的入口点,进入程序入口后,程序开始载入物理内存,而后进入main函数,CPU为hello分配时间片执行逻辑控制流。当程序运行结束后,shell 父进程负责回收 hello 进程,并且内核会从系统中删除hello所有痕迹。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU ,2.50GHz , 8G RAM
软件环境:Windows 10 64位 ,Oracle VM Virtualbox ,Ubuntu 20.04.4 64 位
开发工具:gcc + gedit , Codeblocks , gdb edb
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i ——预处理之后文本文件
hello.s ——编译之后的汇编文件
hello.o ——汇编之后的可重定位目标执行
hello ——链接之后的可执行目标文件
helloo.objdmp ——hello.o 的反汇编代码
helloo.elf ——hello.o 的 ELF 格式
hello.objdmp ——hello 的反汇编代码
hello.elf —— hello的ELF 格式
1.4 本章小结
我们了解hello.c进过编写、预处理、编译、汇编、链接和执行等阶段,同时本章也列举出了实验的软件、硬件环境以及编译调试工具,也列举出了生成的各个中间文件的名称以及他们的作用。充分体现了各种功能协调合作来完成计算机系统这一工作方式。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理其实一般指的就是程序源代码被翻译为目标代码的过程中,声称二进制之前的过程。
预处理器(cpp)根据hello.c的以字符#开头的命令,修改原始的源程序C文本文件,读取头文件<stdio.h>的内容,并且把它直接插入到程序文本中。生成了hello.c也就是后边的源程序文本经过修改之后的版本。所有的预处理命令都是以#开头的。在同一行中,只有空格和注释可以出现在预处理命令之前。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
由上图可以看出来,hello.i远远大于hello.c,打开hello.i后,发现hello.c的源代码在最下面,上面插入了很多其他的片段,这些代码便是处理#开头的命令,将其展开并插入到源程序。
2.4 本章小结
在本章中,详细介绍了预处理的概念和作用,演示了预处理在linux下的命令,直观的体会了预处理的效果。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译的概念:
编译就是把代码转换成汇编指令的过程。编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
编译的作用:
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序,该程序包含函数main的定义,其中每条语句都以一种文本格式描述了一条低级语言指令。编译主要作用除了是将文本文件hello.i翻译成文本文件hello.s之外,还在出现语法错误时给出提示信息。执行过程主要从其中四个阶段进行分析:
1.词法分析:词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。
2.语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位。
3.代码优化:代码优化是指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。
4.生成目标代码:目标代码生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
3.3.1开头部分
.file 声明源文件
.text 代码段
.section .rodata 以下是 rodata 节
.align 声明对指令或者数据的存放地址进行对齐的方式
.string: 字符串
.globl 声明一个全局变量
.type 用来指定是函数类型或是对象类型
3.3.2数据
通过hello.c我们可以看出来,变量有int类型的i,和两个参数int类型的argc、字符指针类型的argv[],在hello.s中,开辟出新的栈空间后,首先把寄存器%edi和%rsi内的数据存入了内存中,而且下面有将-20(%rbp)的值和立即数4比较,对应于源代码,可以确定-20(%rbp)内储存的是argc的值,即两个参数argc和argv[]的值首先分别保存在寄存器%edi和%rsi中,然后将寄存器中的值存到栈里。第31行将0存入内存-4(%rbp),然后跳到L3(进行比较),可以看出该内存存的是变量i。
3.3.3赋值
在源代码中发现赋值操作只有一处,即i=0,在hello.s中,如上图31行,通过指令movl $0, -4(%rbp)进行赋值,并将其赋值为0。 mov赋值指令有四种,分别为movb、movw、movl、movq,分别对应于1、2、4、8个字节,本指令使用的时movl是由于i的数据类型为整型,占4个字节,故代码后缀为l。在hello.s中,还发现有leaq .LC0(%rip), %rdi这样的运算,这样的运算是将前面运算的结果赋值给%rdi,这是一种比较高效的运算。
3.3.4 算术操作
在源代码中发现只有一处算术运算,即i++,在hello.s中,通过指令
addl $1, -4(%rbp)对储存在-4(%rbp)的值进行加一运算,并存储在-4(%rbp)中。addl的“l”同理于3.3.3。在hello.s中,还发现别的地方也出现了算术操作,比如一开始subq $32,%rsp 是将%rsp的值减去32并存在%rsp中,开辟了一块栈空间。在访问argv[]数组时,也使用了add的运算操作计算地址。
3.3.5 关系操作
在源代码中发现的关系操作,有argc!=4和i<8.在hello.s中,argc!=4通过指令cmpl $4, -20(%rbp) 、je .L2、......实现了不等于,因为关系操作一般都存在于条件句中,条件句通过cmp指令和跳转指令配合实现功能;而小于操作i<8通过指令cmpl $7, -4(%rbp)、jle .L4实现,将当前值与目标值作比较,如果小于等于7就跳转继续循环,否则就跳出循环。
3.3.6数组/指针/结构操作
通过对字符串指针数组argv寻址的方式来读取参数,通过mov以及add的偏移寻址,对argv的数组元素进行访问。对hello.s分析发现,argv数组的起始地址-32(%rbp),在寻找数组中元素的时候,通过指针偏移的方式进行寻找,每个元素之间的指针偏移量为8,故通过对临时寄存器%rax加8来访问argv[1],加16访问argv[2],加24访问argv[3]。
3.3.6控制转移
通过源代码发现,有一处if和一处for循环,在hello.s中,
对应于if(argc!=4),如果相等,就跳转到。L2,如果不相等,就执行退出的操作。
在L3中将i与7比较,如果小于等于,就跳转到上面继续循环,如果不满足,就执行下面的程序。
3.3.7函数操作
函数操作通过参数传递和函数调用来实现,首先将参数保存在寄存器中,然后使用call指令调用函数。
1、main函数:
参数传递:传入的参数为argc和argv,分别用寄存器%rdi和%rsi存储。
函数调用:被系统启动函数调用。
函数返回:设置%eax为0并且返回,对应return 0 。
2、exit函数:
参数传递:传入的参数为1,并将%edi 设置为 1,再执行退出命令
函数调用:if判断条件满足后并在if内容基本执行完成后被调用,对应于汇编 文件中语句call exit@PLT。
3、printf 函数:
参数传递:call puts 时传入了字符串参数首地址;for 循环中 call printf 传入了 argv[1]和 argc[2]的地址。
函数调用:第一次 printf 因为只有一个字符串参数,对应于call puts@PLT;第二次 printf 对应于 call printf@PLT。
4、sleep函数:
参数传递:传入参数atoi(argv[3])
函数调用:for循环下被调用,对应于汇编文件中的call sleep@PLT。
5、getchar
函数调用:在main中被调用,对应于汇编文件中的call gethcar@PLT。
3.4 本章小结
本章详细地描述了hello.i汇编得到hello.s的过程,详细的分析了编译器具体而言是怎么处理C语言的各个数据以及语句,各种数据类型各类赋值、逻辑上的各种操作,通过学习,我更加深入理解了计算机系统处理各种数据、文本文件、各种操作的方法,以及在计算及内部的各种存储形式。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编是将编译后的hello.s文件通过汇编器生成机器语言并存储到hello.o文件的过程,但是生成的hello.o是可重定位目标文件。
4.2 在Ubuntu下汇编的命令
gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
ELF Header:ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序: ELF头剩下的部分包含帮助链接器语法分析和解释目标文件信息。
Section Headers:节头部表,包含了文件中出现的各个节的语义,包括节的类型、位置和大小,描述了.o文件中各个节的名字、类型、地址、大小信息;
.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息;
4.4 Hello.o的结果解析
对比hello.s和helloo.objdump,发现有如下差异:
1.反汇编代码的左侧有相对于main函数的偏移量,而hello.s中没有。
3.反汇编代码的操作数是十六进制的,hello.s的操作数是十进制的。
3.反汇编代码的跳转指令后跟的是地址,hello.s的跳转指令后跟的是L2、L3等的段名称。
4.反汇编代码的函数调用call的目标地址是当前下一条指令,hello.s文件中,函数调用之后直接跟着函数名称。这是因为hello.c中调用的函数都是共享库中的函数,最终需要通过动态链接器才能确定函数运行时的执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将call指令后的相对地址设置为全0(main函数的地址),然后在.rel.text节中为其添加重定位条目,等待静态链接的进一步确定。
4.5 本章小结
本章当中,简述了汇编的概念与作用;演示了如何运用gcc等工具进行汇编操作,简单分析了可重新定位目标文件的elf格式中的ELF头、符号表、节头部表、重新定位中的内容;对hello.o文件的反汇编结果与源汇编(.s)文件进行了对照分析,得出了相同与不同之处。学习到了汇编语言与二进制机器语言之间的关系,对深入了解计算机系统有较大的帮助,总之我们了解到了文件执行机制,也证明链接的必要性。
第5章 链接
5.1 链接的概念与作用
链接就是将可重定位目标程序(二进制)通过链接器(ld)声称可执行目标文件(二进制)的过程:
链接可以执行于编译时(compile time),也就是在源化码被翻译成机器代码时;也可以执行于加载时(load time), 也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(run time),也就是由应用程序来执行。
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.3 可执行目标文件hello的格式
课本上可执行文件的格式如图所示:
ELF Header:ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序;
Section Headers:节头部表,描述了.o文件中各个节的名字、类型、地址、大小信息;
Program Headers:程序头部表,为链接器提供运行时的加载内容和提供动态链接的信息;
.symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息;
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
首先使用指令生成hello.objdump文件。
分析 hello 与 hello.o 的不同:
1.包含的函数不同:在hello.o的反汇编代码中,只有main函数的反汇编代码,但是在hello的反汇编代码中,链接加入了在 hello.c 中用到的库函数,如 exit、printf、sleep、getchar 等函数。
2.节不同:hello中增加了.init 和.plt 节。
3.函数调用: hello 中无 hello.o 中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。
4.地址访问:hello.o中的相对偏移地址变成了hello中的虚拟内存地址。 hello.o文件中对于某些地址的定位是不明确的,其地址也是在运行时确定的,因此访问也需要重定位,在汇编成机器语言时,将操作数全部置为 0,并且添加重定位条目。
链接的过程:从5.2的命令可以看出,链接就是把hello.o和其他需要的可重定位文件“合并”。先进行符号解析,再进行重定位生成可执行文件。
重定位的两步:
1、重定位节和符号的定义。在这一步中,链接器将所有相同类型的节合并成同一类型的新聚合节。包括hello.o在内的所有可重定位目标文件中的.data 节全被合并成一个节——输出的可执行目标文件hello中的.data节。然后,连接器将运行时的内存地址赋入新节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,hello中每条指令和变量都有唯一的运行时内存地址了。
2、重定位节中的符号引用。链接器依赖于hello.o中的重定位条目,修改代码 节和数据节中对每个符号的引用,使得它们指向正确运行时的地址。
5.6 hello的执行流程
加载:_dl_start、_dl_init
开始执行:_start、_libc_start_main
执行main:_main、_printf、_exit、_sleep、_getchar、_dl_runtime_resolve_xsave、_dl_fixup、_dl_lookup_symbol_x
退出:exit
调用与跳转的各个子程序名如下:
._dl_start, ._dl_init, ._cax_atexit, ._new_exitfn, ._libc_start_main,
._libc_csu_init, ._main, ._printf, ._atoi, ._sleep, ._getchar,._exit
._dl_runtime_resolve_xsave, ._dl_fixup, ._dl_lookup_symbol_x, .exit
5.7 Hello的动态链接分析
(以下格式自行编排,编辑时删除)
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
在本章中,介绍了链接的概念和作用,介绍了Linux下链接的指令,分析了hello.elf文件的内容,分析了hello的虚拟内存空间,分析了重定位的过程,讨论了hello的执行流程,对hello进行了动态链接分析,对链接有了更深的认识。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
6.2 简述壳Shell-bash的作用与处理流程
shell是一个应用程序,操作系统中提供了一个用户与系统内核进行交互的界面。
处理流程:1.读取用户的输入 ;2.分析输入内容,获得输入参数; 3.如果是内核命令则直接执行,否则调用相应的程序执行命令; 4.在程序运行期间,shell需要监视键盘的输入内容,并且做出相应的反应.
6.3 Hello的fork进程创建过程
输入命令行./hello 2021111801 王文煜
shell对命令行进行解析,由于第一个命令行参数不是一个内置的shell命令,shell会调用fork()创建子进程。
终端程序通过调用fork()函数创建一个子进程,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。
子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。
子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。
父进程和新创建的子进程之间最大的区别在于它们有不同的PID.
父进程与子进程是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。
6.4 Hello的execve过程
Hello程序在使用fork创建了子进程之后,在子进程中分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量,调用execve( )执行指定程序。
int main(int argc , char **argv , char *envp);
execve加载了Hello之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数,主函数如上述所示。
execve函数在当前进程中加载并运行新程序a.out可以大概分为以下四步:
1.首先它要删除已存在的用户区域。
2.然后映射私有区域。为Hello的代码、数据、bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时复制的。
3.再映射共享区域。如果由a.out程序与共享对象链接,比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。
4.最后设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
对于私有区域的不同映射
6.5 Hello的进程执行
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制, 限制一个应用可以执行的指令以及它可以访问的地址空间范围。
上下文信息:
上下文(context)就是内核重新启动一个被抢占的进程所需的状态,内核为每个进程维持一个上下文。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。内核为每个进程维持一个上下文。
进程时间片:
一个逻辑流的执行在时间上与另二个流重叠,称为并发流.这两个流被称为并发地运行。多个流并发地执行的般现象被称为并发。 一个进程和其他进程轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式::
处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。在hello进程执行的过程中,开始是在用户模式下运行,收到系统异常信号后户进入内核模式,运行信号处理程序,处理完成后会回到用户模式,即用户态和核心态的切换;在运行过程中,cpu不断切换上下文,将运行过程分成若干时间片即逻辑控制流,与其他进程交替占用着cpu,实现进程调度。
6.6 hello的异常与信号处理
1)异常类型有:中断、陷阱、故障、终止;
中断:来自处理器外部I/O设备的信号的结果,处理完总是返回到下一条指令;
陷阱:是有意的异常,是执行一条指令的结果,也总是返回到下一条指令;
故障:由错误情况引起,可能被故障处理程序修复,可能返回到当前指令或终止;
终止:不可恢复的致命错误造成的结果,终止处理程序不会将控制返回给应用程序。
2)产生信号有:SIGINT、SIGTRAP、SIGCONT、SIGCHLD;
SIGINT: Ctrl-Z命令会产生这个信号,使进程中断,切换到别的进程中;
SIGTRAP: sleep函数会触发这个信号,调用陷阱异常处理程序并跟踪陷阱;
SIGCONT: fg命令会向进程发送该信号,切换到该进程继续执行;
SIGCHLD: exit函数会发送这个信号,终止进程;
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
不停乱按空格
乱按Ctrl+C
按Ctrl-Z后运行pstree命令
PS
Kill
6.7本章小结
进程在shell的环境下用fork,wait创建并回收子程序,用execve加载新的程序,用键盘、函数反复地发送、捕获、接收、处理/放弃信号,一会运行程序一会处理信号。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 “段标识符:段内偏移量”。
线性地址:也叫虚拟地址,和逻辑地址类似,也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件也是内存的转换前地址。
虚拟地址:一个带虚拟内存的系统中,CPU从一个有N=2^n个地址空间中生成虚拟地址。虚拟地址其实就是线性地址。CPU在寻址的时候,是按照虚拟地址来寻址。CPU通过生成一个虚拟地址(VA)来访问主存,这个虚拟地址被送到内存之前先转换为适当的物理地址。对应于hello程序即为hello可执行文件转化为objdump时所显示的地址。
物理地址:计算机系统贮存被组织成一个由M个连续字节大小的单元组成的数组,每字节都有一个唯一的地址,即物理地址。对于hello程序为当hello运行时通过MMU将虚拟内存地址映射到内存中的地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
首先有CPU产生虚拟地址VA,MMU会根据下面机制将VA翻译成对应的PA:
将高位的VPN分成TLBT和TLBI去寻找,根据组索引找到对应组块,若标记位匹配即找到,然后获取表中对应PPN,再结合低位不变的VPO组成物理地址PA。
7.5 三级Cache支持下的物理内存访问
先由上述中获得PA,首先取组索引对应位,然后向L1 cache中寻找对应组
如果存在,检查对应行的有效位是否为1,并比较标志位,。如果上述条件均满足则命中。否则按顺序对L2 cache、L3 cache、内存进行相同操作,直到出现命中。然后向上级cache返回直到L1 cache,并将数据传送给CPU。数据块返回到L1 cache时,如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块按照一定的策略驱逐,将目标块放到被驱逐块的原位置。
如果不存在则可能时地址错误,发生终止异常。
7.6 hello进程fork时的内存映射
当fork函数被shell调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、 区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当两个进程的任意一个后来进行写操作时,写时复制机制就会创建新页面,因此也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
映射私有区城。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。 栈和堆区域也是请求二进制零的, 初始长度为零。
映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
下次再调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
7.8 缺页故障与缺页中断处理
缺页故障:当内存管理单元MMU在页表中查找虚拟地址对应的物理内存不在主存中时,即为缺页故障,此时需要操作系统将该部分内存从磁盘调入主存中;
缺页中断处理:当发生缺页故障时,系统会调用内核中的缺页异常处理程序,程序会在物理内存中选择一个牺牲页,将其调回磁盘,然后将所缺的页调入,并更新内存中的PTE,随后返回程序中断处的指令,继续重新查找访问该虚拟地址。
7.9动态存储分配管理
显示分配器:要求显式地释放任何已分配的块,比如C标准库的malloc;
隐式分配器:也叫垃圾收集器,要求分配器检测一个已分配块何时不再被程序使用,此时释放块。
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
7.10本章小结
本章我们介绍了存储器地址的各个概念,介绍了如何通过段式管理将逻辑地址转换成线性地址以及如何通过页式管理将线性地址转换为物理地址,介绍了TLB的概念和用法;简述了四级页级面表下的VA到PA的变换,以及三级Cache下的物理内存访问; 还介绍了 hello 进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。以hello问开始,很好的了解了计算机系统储存空间的分配以及虚拟空间与物理空间之间的转化方式。
(第7章 2分)
第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.c源代码文件,通过预处理器对hello.c文件进行扩展得到预处理后的hello.i文本文件,再交给编译器将其转换为汇编代码形式的hello.s文件,然后再交给汇编器将其翻译成机器语言指令形式的hello.o可重定位目标文件,然后再经过链接器将其与所需库函数得到可执行hello;此时再在shell中输入./hello的执行命令,操作系统就会调用fork为其创建子进程,再用execve函数将hello程序加载到该子进程,并映射虚拟内存;CPU会为其分配时间片,hello程序在其进程中执行自己的逻辑控制流;运行过程中hello程序可以通过MMU对地址的转换功能来访问内存,运行期间shell的信号处理函数可以接受程序的异常和用户的请求;程序运行结束时,shell父进程会回收子进程,同时内核删除为这个进程创建的所有数据结构,hello生命即结束。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.i ——预处理之后文本文件
hello.s ——编译之后的汇编文件
hello.o ——汇编之后的可重定位目标执行
hello ——链接之后的可执行目标文件
helloo.objdmp ——hello.o 的反汇编代码
Helloo.elf ——hello.o 的 ELF 格式
hello.objdmp ——hello 的反汇编代码
hello.elf —— hello的ELF 格式
(附件0分,缺失 -1分)
参考文献
- 兰德尔 E. 布莱恩特. 大卫R. 奥哈拉伦 《深入理解计算机系统》
- 深入理解计算机系统大作业——程序人生-Hello’s P2P_relocation section_馫BaSO4的博客-CSDN博客
- Shell执行流程详细解释 - 百度文库 (baidu.com)
- 编译_百度百科 (baidu.com)
- 预处理_百度百科 (baidu.com)
为完成本次大作业你翻阅的书籍与网站等
(参考文献0分,缺失 -1分)