本文介绍了在Linux环境下,从源程序hello.c开始,对其分步进行预处理、编译、汇编、链接,直到应用程序运行并结束过程中的相关内容,包括对于进程、存储以及IO等方面的分析。
关键词:计算机系统,Linux,GCC,hello.c;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
PTP:首先得到hello源代码,然后经过cpp的预处理得到hello.c文件,通过ccl的编译、as的汇编得到hello.s,通过ld将其与一些库链接生成hello可执行文件;然后在shell当中键入启动命令./hello运行程序,shell会调用fork为其创建一个子进程,hello就从一个程序成为了一个进程。
020:在执行hello程序之后,shell通过execve进行加载到虚拟内存中。之后程序载入物理内存,然后进入 main函数执行代码。然后在程序执行结束后,进程保持终止状态,父进程进行回收后,shell保持最初状态。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上
CodeBlocks 64位;vi/vim/gedit+gcc;EDB
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名字 | 文件作用 |
hello.i | 预处理后的文件 |
hello.s | 编译后的文件 |
hello.o | 可重定位的目标文件 |
hello | 可执行的目标程序 |
1.4 本章小结
本章简要概述了作业的环境,对hello进行了简单介绍,分析了P2P和020的过程,列出了任务过程中出现的中间文件及其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:
预处理器(CPP)根据以字符#开头的命令(宏定义、条件编译等),修改原始的C程序,并将其引用的所有库进行展开合并,生成以.i结尾的文本文件。
作用:
(1)在预处理阶段,预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,将用#include形式声明的文件复制到新的程序中。
(2)在预处理阶段,预处理器将程序中间出现的所有的宏名称全部用宏定义当中的字符串进行代换(#define定义的字符串)。
(3)根据#if后面的条件去编译相应的程序部分。
(4)删除所有的注释。
2.2在Ubuntu下预处理的命令
命令:gcc -E hello.c -o hello.i
可以看出,在命令输入之后,在同一目录下生成了hello.i文件
2.3 Hello的预处理结果解析
hello.i文件的部分内容如下所示
打开hello.i文件,比起源文件内容明显增加,有3060行可以阅读的C语言文本文件。可以看出,预处理器删除了源文件当中全部的注释,对源文件中的宏进行了宏展开,将stdio.h stdlib.h unistd.h当中的内容插入到程序当中,同时也对#define相应的符号进行了替换。
2.4 本章小结
本章主要介绍了函数的预处理的概念及作用,及其所实现的相关的功能,同时介绍了GCC下预处理对应的指令,解析了预处理文本文件内容,对预处理的结果有了一定的了解,更好的了解了预处理的过程。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译过程是将与处理完的源程序进行一系列的词法分析,语法分析,语义分析以及优化后生成相应的汇编程序。
作用:
(1) 词法分析:词法分析通过使用lex程序实现词法的扫描,按照用户之前描述好的规则将输入的字符串分割成为一个个的记号。
(2) 语法分析:语法分析器通过给定的语法规则,将词法分析产生的记号序列进行解析,然后将他们构成一棵语法树。
(3) 语义分析:通过语义分析判断语句是否有实际的意义。
(4) 中间代码的生成:对于在编译期间可以确定的值,编译器对其进行优化,将语法树转换成为一般与目标机器和环境无关的中间代码。
(5) 目标代码生成与优化:代码生成器将中间代码转换成为机器码。
(6) 目标代码优化:目标代码优化器对目标代码进行优化。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s
可以看出,在命令输入之后,在同一目录下生成了hello.s文件
3.3 Hello的编译结果解析
hello.s文件的内容如下所示
3.3.1 汇编指令
.file(第1行):声明源文件
.text(第2行):代码节
.section(第3行):
.align(第4行):数据或者指令的地址对其方式
.string(第6行):声明一个字符串(.LC0,.LC1)
.global(第10行):声明全局变量(main)
.type(第11行):声明一个符号是数据类型还是函数类型
3.3.2 数据
本程序用到了字符串常量和变量
字符串常量:
变量:i 、argc 、argv共三个
(1)i:在循环中用到,为局部变量,保存在堆栈中。
(2)argc:main()函数的参数, 为输入参数个数,为外部参数,保存在寄存器当中。
(3)argv: main()函数的参数,存放各参数,其中argv[0]为该文件路径,为外部参数,保存在寄存器当中。
3.3.3 赋值
是对i的赋值,通过mov指令实现
3.3.4 算术运算
是i++,通过add指令实现
3.3.5 比较判断
(1)if条件判断,通过cmpl指令实现
(2)for循环的条件判断,通过cmpl指令实现
3.3.4 控制转移
if条件分支引起的跳转以及for循环分支引起的跳转,通过cmpl指令设置条件码后通过jmp指令跳转。
3.3.5 函数调用
设置寄存器值后通过call指令调用
3.4 本章小结
本章主要介绍了编译的概念、作用、在Ubuntu下的命令,以及hello.s文件中各种数据类型和操作的解释,说明了修改后的源文件hello.i翻译成为二进制文件hello.s的过程。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编的概念是指的将汇编语言程序翻译成机器指令,并将这些指令打包成一种叫做可重定位目标程序,并将这个结果保留.o目标文件中。
作用:实现了文本文件到二进制文件的转化,将汇编指令转换成一条条机器可以直接读取分析的二进制机器指令。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令
命令:gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
(1)ELF头
ELF头以16B的序列Magic(魔数)开始。
Magic描述了生成该文件的系统的字的大小和字节顺序,7f 、45、4c、46分别对应ascii码的Del(删除)、字母E、字母L、字母F。这四个字节被称为ELF文件的魔数,操作系统在加载可执行文件时会确认魔数是否正确,如果不正确则拒绝加载。
ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中:
第五个字节标识ELF文件是32位(01)还是64位(02)的;
第六个字节标识该ELF文件字节序是小端(01)还是大端(02)的;
第七个字节指示ELF文件的版本号,一般是01;
后九个字节ELF标准未做定义。一般为00。
- 节部头表
可知此elf文件中共有13个节,包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。每个节都从0开始,用于重定位。
- 重定位节
链接器在处理目标文件时,需要对目标文件中某些部分进行重定位,这些重定位的信息都记录在ELF文件的重定位表中。
- 查看符号表
存放程序中定义和引用的函数和全局变量的信息。
- 重定位条目
描述了需要进行重定位的各种信息,包括需要进行重定位符号的位置、重定位的方式、名字。
4.4 Hello.o的结果解析
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
hello.o
hello.s
机器语言:是二进制机器指令的集合,纯粹的二进制数据表示的语言,是电脑可以识别的语言。
机器语言与汇编语言的映射关系:每一条汇编语言操作码都可以用机器二进制数据来表示,反之同理,即所有的汇编语言和二进制机器语言存在一一映射的关系。
不同之处:
- Hello.o的反汇编文件helloo.txt中有机器码,而hello.s没有Hello.s中的代码分段书写,而helloo.txt中已经成为一个整体hello.s中操作数是十进制,而helloo.txt中是十六进制
- 从数据访问上看,汇编语言中,访问printf中的字符串是用段名称+%rip,而在机器语言对应的反汇编程序中为0+%rip。
- 从分支转移上看,在汇编语言中,分支转移是由助记符来标识,而在机器语言反汇编程序中,分支转移直接跳入目的地址。
- 从函数调用上看,汇编语言中,函数调用紧跟函数名称,而在反汇编程序中,call的目标地址是下一条指令的地址。这是因为需要通过动态链接器才能确定函数的运行时执行地址,在汇编时是不确定地址,将其call指令后的相对地址设置为全0,然后在.rela.text 节中为其添加重定位条目。
4.5 本章小结
本章介绍了汇编的概念、作用、在Ubuntu下执行汇编的方法,readelf查看hello.o的ELF,分析了hello.o的elf格式,反汇编的方式查看hello.o反汇编的内容,比较其与hello.s之间的差别,比较了hello.o的反汇编代码和hello.s的区别。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:printf函数存在于printf.o的单独的预编译好的的目标文件中,而这个文件必须以某种方式合并到我们的hello.o程序中。链接器(ld)就负责这种合并。
作用:将各个模块之间相互引用的部分正确的衔接起来,合并得到可执行目标文件可以加载到内存中,由系统执行。
注意:这儿的链接是指从 hello.o 到hello生成过程。
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的格式
(1)ELF头
指令:readelf -h hello
可知hello文件类型为EXEC,是一个可执行目标文件,文件中共有27个节。
- 节头
指令:readelf -S hello
可知各段的基本信息,包括各段的起始地址,大小等。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
观察Data Dump窗口可以看出,虚拟地址从0x00401000开始。可以查询各个节的信息,起始地址,大小等。
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
objdump -d -r hello的main函数如下:
objdump -d -r hello.o 的main函数如下:
对比发现:hello比hello.o多出了许多文件节。比如.init节和.plt节、hello中有确定的虚拟地址,已经完成了重定位,而hello.o中虚拟地址均为0,未完成重定位、hello中增加了外部链接的函数,比如printf函数。
链接的过程:把编译好的目标文件和其他的一些目标文件和库链接在一起,形成最终的可执行文件。
重定位的过程:重定位:链接器在完成符号解析以后,就把代码中的每个符号引用和正好一个符号定义(即它的一个输入目标模块中的一个符号表条目)关联起来。此时,链接器就知道它的输入目标模块中的代码节和数据节的确切大小。然后就可以开始重定位步骤了,在这个步骤中,将合并输入模块,并为每个符号分配运行时的地址。在hello到hello.o中,首先是重定位节和符号定义,链接器将所有输入到hello中相同类型的节合并为同一类型的新的聚合节。例如,来自所有的输入模块的.data节被全部合并成一个节,这个节成为hello的.data节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每一个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。然后是重定位节中的符号引用,链接器会修改hello中的代码节和数据节中对每一个符号的引用,使得他们指向正确的运行地址。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
ld-2.23.so!_dl_start
ld-2.23.so!_dl_init
hello!_start
libc-2.23.so!_libc_start_main
libc-2.23.so!_cxa_atexit
libc-2.23.so!_libc_csu.init
hello!_init
libc-2.23.so _setjmp
libc-2.23.so exit
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
对于动态共享链接库中的PIC函数,编译器添加了重定位记录,等待动态链接器来处理,同时,链接器采用了延迟绑定的方法。可以看出,在dl_init之前, PIC函数调用的目标地址都实际指向PLT中的代码逻辑,初始时每个GOT条目都指向对应的PLT条目的第二条指令。在dl_init后,部分数据信息发生变动。
查询hello可以看出.got起始位置为0x403ff0,.got.plt起始位置为0x404000。
通过edb可以看到运行前此时为全0。
运行之后结果如下,可以看出其已经动态链接,GOT条目已经改变。
5.8 本章小结
本章介绍了链接的概念、作用、在Ubuntu下用ld指令执行链接的方法,分析比较hello和hello.o的反汇编代码,并对hello的elf格式进行了详细的分析对比,详细了解了重定位过程以及用edb进行动态链接的分析,在最后对hello进行了动态链接分析。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:一个执行中程序的实例,系统中的每个程序都运行在某个进程的上下文中。每个进程都有它自己的地址空间,一般情况下包括代码段、数据段、和堆栈段,包括了存放在内存中的程序的代码和数据,它的栈、通用目的寄存器、程序计数器、环境变量以及打开文件描述符的集合。
作用:进程主要为用户提供了下列两个抽象:
(1)一个独立的逻辑流,提供程序独占使用处理器的抽象。
(2)一个私有的虚拟地址空间,提供程序独占使用整个系统内存的抽象。
6.2 简述壳Shell-bash的作用与处理流程
shell的作用:
shell是用户和Linux内核之间的接口程序。在提示符下输入的每个命令都由shell先解释然后传给Linux内核。可以调用其他程序,给其他程序传递数据和参数,并且获取程序的处理结果。同时可以在多个程序之间传递数据,把一个程序的输出作为另外一个程序的输入。并且shell本身也可以被其他程序进行调用。
shell的处理流程:
(1)接收stdin上输入的命令行。
(2)调用parseline函数解析命令行参数,并且构造argv向量。
(3)调用builtin_command函数,检查第一个命令行参数是否是shell命令。
是则执行
(4)如果不是,那么shell创建一个子进程,并且在子进程中执行程序,如果后台运行则shell返回顶部,等待下一命令。否则使用waitpid函数等待终止,终止后,shell返回顶部,等待下一命令。
6.3 Hello的fork进程创建过程
在命令行输入./hello命令运行hello程序时,此命令不是内置命令,因此调用fork()函数创建子进程。得到与父进程虚拟地址空间相同(但是独立的)一份副本,包括代码和数据段、堆、共享库和用户栈。还获得与父进程任何打开文件描述符相同的副本。父进程调用fork函数时,子进程可以读写父进程中打开的任何文件,父进程和新创建的子进程之间的最大的区别在于他们有不同的pid。
shell调用fork函数为运行Hello程序做准备。
6.4 Hello的execve过程
(1)./hello命令使得fork()被调用创建了一个子进程
(2)execve函数在当前进程的上下文当中加载并且运行一个新的程序。
(3)execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量envp。只有当出现错误的时候,才会返回到调用的程序。
(4)在shell的子进程当中执行execve函数,将参数传递给Hello程序,并且执行Hello程序。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
当内核调度一个新的进程的运行的时,内核就会抢占当前进程,通过使用一种上下文切换的较为高层的形式异常控制流将控制转移到新的进程。用户态核心态转换,也就是进程hello初始运行在用户模式中,直到它通过执行系统调用函数sleep或者exit时便陷入到内内核。内核中的处理程序完成对系统函数的调用。之后,执行上下文切换,将控制返回给进程hello系统调用之后的那条语句。在Hello程序的运行的过程当中,不断地进行着上下文的切换,使得Hello程序被切分成一个个时间片,和其他进程交替的占用着处理器。从而实现了进程调度。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
hello程序出现的异常可能有中断、陷阱、故障、终止。
中断是在hello程序执行的过程中可能会出现外部I/O设备引起的异常。陷阱是陷阱是有意的异常,是执行一条指令的结果,hello执行sleep函数的时候会出现这个异常。故障是在执行hello程序的时候,可能会发生缺页故障。终止是终止时不可恢复的错误,在hello执行过程可能会出现DRAM或者SRAM位损坏的奇偶错误。
可能产生的信号有:SIGINT、SIGKILL、SIGSEGV、SIALARM、SIGCHLD。
按下Ctrl-C: 在键盘上输入Ctrl+c会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业。
按下Ctrl-Z,并运行ps: 输入ctrl-z,内核会发送SIGSTP。SIGSTP默认挂起前台hello作业,但 hello进程并没有回收,而是运行在后台下,通过ps指令可以对其进行查看。
Ctrl-z后运行fg:进程继续运行
Ctrl-z后运行jobs:进程仍在后台
Ctrl-z后运行pstree:
Ctrl-Z后按下kill 命令:内核会发送SIGKILL信号给指定的pid(hello程序),杀死hello程序
终端随意输入:没有影响
6.7本章小结
本章介绍了进程、Shell的概念和作用、shell如何调用fork和execve函数、以及通过对hello的创建、加载和终止、进程执行、异常与信号处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:汇编程序中地址。逻辑地址由段基址和偏移量组成。段基址确定它所在的段居于整个存储空间的位置,偏移量确定它在段内的位置。
线性地址:也叫虚拟地址,逻辑地址经过段机制后转化为线性地址,为[描述符:偏移量]的组合形式,线性地址对应硬件页式内存的转换前地址。分页机制中线性地址作为输入。
虚拟地址:在一个带虚拟内存的系统中,CPU从一个有N=2^n个地址的地址空间中生成虚拟地址。这个地址空间称为虚拟地址空间。
物理地址:真实的物理内存地址,CPU通过地址总线的寻址,找到真实的物理内存对应地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式存储管理中以段为单位分配内存,每段分配一个连续的内存区,但各段之间不要求连续,内存的分配和回收类似于动态分区分配,由于段式存储管理系统中作业的地址空间是二维的,因此地址结构包括两个部分:段号和段内位移。
段式管理通过偏移地址来完成逻辑地址的空间表示,在段式管理地址变换的过程中间,需要位运行的进程建立一个段表,将程序的地址空间划分为若干个段,这样每个进程有一个二维的地址空间。
线性地址的计算过程是计算有效地址EA后取出段寄存器DS对应的描述符cache中的段基址,最后得到线性地址LA=段基址+EA。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址被划分为固定长度的组,成为页。为了节约页表所占用的内存空间,线性地址空间通过页目录表和页表两级查找转换成为物理地址。页目录表的物理地址存储在cr3寄存器当中。
页命中时,处理器生成一个虚拟地址,并把它传送给MMU,从而生成PTE地址,并向高速缓存/主存请求,在高速缓存/主存向MMU返回PTE后,MMU构造物理地址,并把它传送给高速缓存/主存,最后高速缓存/主存返回所请求的数据字给处理器。
MMU从cr3寄存器当中取出进程的页目录地址,根据线性地址高10位找到索引,得到了下一级页表的地址,根据线性地址的中间10位,在页表当中得到页的起始地址,通过页的起始地址和低10位的虚拟地址相加,得到最终的物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB是一个小的,虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。虚拟地址传送给MMU时,MMU在TLB中进行相应的匹配,如果命中,那么将得到PPN,其与VPO进行组合将会得到52位的物理地址PA。
如果TLB没有命中,那么MMU向页表当中进行相应的查询,CR3确定第一级页表的起始地址。VPN1确定在确定在第一级页表中的偏移量,查询出PTE,如果在物理内存中且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA,并且向TLB中添加条目。
如果查询PTE的时候找不到,那么会引发缺页异常。
7.5 三级Cache支持下的物理内存访问
在得到物理地址之后,先将物理地址拆分成CT、CI、CO,先对一级cache进行访问,先找组索引位,然后与标志位对比。如果未能寻找到标记位为有效的字节,即一级cache不命中那么访问二级cache,如果二级cache不命中,那么访问三级cache,依然不命中,那么访问主存,如果主存缺页中断那么就访问硬盘。
7.6 hello进程fork时的内存映射
当fork函数被shell调用时,内核为hello进程创建各种数据结构及task_struct,并分配给它一个唯一的PID,为这个进程创建一个单独的虚拟地址空间。为了给进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
先删除已存在的用户区域,即删除当前进程虚拟地址的用户部分中的已存在的区域结构。
之后映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和和数据区域被映射为文件中的.text和.data区。Bss区域是请求二进制0的,映射到匿名文件,其大小包含在文件中。栈和堆区域也是请求二进制0的,初始长度为0。
之后映射共享区域,如果程序与共享对象链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
最后设置PC,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:当CPU想要读取虚拟内存中的某个数据,而这一片数据恰好存放在主存当中时,就称为页命中。相对的,如果DRAM缓存不命中,则称之为缺页。
缺页中断处理:发生缺页故障时,触发缺页异常处理程序,选择出物理内存中的牺牲页,如果这个页已经被修改了,则把它复制回磁盘。程序调入新的页面,并更新内存的PTE,接着执行命令。CPU将引起缺页的虚拟地址重新发送给MMU。
7.9动态存储分配管理
动态内存管理的基本方法:使用动态内存分配器维护堆,分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。空闲块可用来分配。需要时选择一个合适的空闲块进行内存分配。
策略:(1)记录空闲块:显示空闲链表,隐式空闲链表,分离空闲链表,红黑树;(2)放置策略:首次适配、下一次适配、最佳适配;(3)合并策略:立即合并、延迟合并
7.10本章小结
本章主要介绍了虚拟内存,CPU访问主存的方式,动态内存分配等内容。
虚拟内存是对主存的抽象。在内存映射的过程当中,通过对段式和页式存储,页表的存储管理,虚拟地址物理地址的转换,进程的加载时的内存映射,缺页故障和处理,动态内存分配等保证进程运行。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件,包含普通文件、进制文件、目录、套接字、命名通道、符号链接、字符和块设备。
设备管理:unix io接口,所有的I/O设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。
操作:打开和关闭文件、读写文件、改变当前文件的位置、关闭文件。
8.2 简述Unix IO接口及其函数
接口:
(1)打开文件:程序向内核发出请求,内核返回一个非负整数,即描述符,在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息,应用程序只需要记住这个描述符。
(2)linux shell创建的每个进程都有三个文件:标准输入,标准输出和标准错误。头文件定义了三个常量用来代替显式的描述符值。
(3)改变当前的文件位置:对于打开的文件,内核保持文件位置,初始为0,是从文件开头起始的字节偏移量,程序可通过执行seek,显式地将改变当前文件位置。
(4)读写文件。读操作就是从文件复制多个字节到内存,从当前文件位置k开始,在一个给定大小的区域末尾执行读操作会触发一个称为EOF的条件,应用程序能检测到这个条件。类似地,写操作就是从内存复制字节到一个文件。
(5)关闭文件。当程序完成对文件的访问后,它向内核发出关闭请求。作为响应,内核释放数据结构,并将描述符恢复为可用。进程无论因为何种原因终止时,内核都会关闭所有打开的文件并释放内存。
函数:
(1)open()函数用于打开或创建文件。
(2)close()函数用于关闭一个被打开的的文件。
(3)read()函数用于从文件读取数据。
(4)write()函数用于向文件写入数据。
(5)lseek()函数用于在指定的文件描述符中将将文件指针定位到相应位置。
8.3 printf的实现分析
printf函数定义:
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
getchar函数定义:
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的I/O设备管理方法、Unix的接口及其函数,了解了linux的I/O设备管理机制,开、关、读、写、转移文件的接口及相关函数,以及printf和getchar函数的实现方法以及操作过程。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
- 编写hello.c源文件;
- 对hello.c执行预处理得到hello.i;
- 对hello.i执行编译得到hello.s;
- 对hello.s执行汇编得到hello.o;
- 将hello.o和动态链接库进行链接成为可执行程序hello;
(6)./hello运行程序,此时shell调用fork()函数创建子进程并在子进程中执行execve()函数加载程序到虚拟内存,MMU将程序中间的虚拟地址翻译成为实际的物理地址,此后执行逻辑控制流,正常结束程序或者出现异常,调用信号处理函数进行处理,此后进程结束,占用资源将被释放和回收。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
计算机的每一步设计都是前人深思熟虑,历经迭代,经过严格规定处理正常情况或异常情况之后的结果,其知识的深度与广度每一寸都凝聚着前人的心血。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
文件名字 | 文件作用 |
hello.i | 预处理后的文件 |
hello.s | 编译后的文件 |
hello.o | 可重定位的目标文件 |
hello | 可执行的目标程序 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] Computer Systems:A Programmer's Perspective
[2] [转]printf 函数实现的深入剖析 - Pianistx - 博客园
[3] https://blog.csdn.net/oceanstudy123/article/details/106024041
[4] linux sda sda1区别,linux – / dev / sda和/ dev / sda1之间的区别_小狐狸与小道士的博客-CSDN博客
[5] https://www.cnblogs.com/pianist/p/3315801.html
(参考文献0分,缺失 -1分)