HIT计算机系统大作业2022

本文详细探讨了从源代码到可执行程序的全过程,涵盖预处理、编译、汇编、链接及进程管理等阶段。通过分析hello.c程序,揭示了计算机系统如何将源代码转化为可执行文件,并在shell环境下执行。同时,阐述了进程的创建、内存管理和I/O处理,深入剖析了动态链接、存储分配以及异常处理机制。
摘要由CSDN通过智能技术生成

计算机系统

大作业

计算机科学与技术学院
2021年5月
摘 要
学习计算机系统后,从底层对一个简单的Hello.c如何一步步编译,链接,加载,执行等进行浅层的描述。
关键词:计算机系统,底层。

目 录

第1章 概述 - 4 -
1.1 Hello简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 5 -
2.1 预处理的概念与作用 - 5 -
2.2在Ubuntu下预处理的命令 - 5 -
2.3 Hello的预处理结果解析 - 5 -
2.4 本章小结 - 5 -
第3章 编译 - 6 -
3.1 编译的概念与作用 - 6 -
3.2 在Ubuntu下编译的命令 - 6 -
3.3 Hello的编译结果解析 - 6 -
3.4 本章小结 - 6 -
第4章 汇编 - 7 -
4.1 汇编的概念与作用 - 7 -
4.2 在Ubuntu下汇编的命令 - 7 -
4.3 可重定位目标elf格式 - 7 -
4.4 Hello.o的结果解析 - 7 -
4.5 本章小结 - 7 -
第5章 链接 - 8 -
5.1 链接的概念与作用 - 8 -
5.2 在Ubuntu下链接的命令 - 8 -
5.3 可执行目标文件hello的格式 - 8 -
5.4 hello的虚拟地址空间 - 8 -
5.5 链接的重定位过程分析 - 8 -
5.6 hello的执行流程 - 8 -
5.7 Hello的动态链接分析 - 8 -
5.8 本章小结 - 9 -
第6章 hello进程管理 - 10 -
6.1 进程的概念与作用 - 10 -
6.2 简述壳Shell-bash的作用与处理流程 - 10 -
6.3 Hello的fork进程创建过程 - 10 -
6.4 Hello的execve过程 - 10 -
6.5 Hello的进程执行 - 10 -
6.6 hello的异常与信号处理 - 10 -
6.7本章小结 - 10 -
第7章 hello的存储管理 - 11 -
7.1 hello的存储器地址空间 - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理 - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理 - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 11 -
7.5 三级Cache支持下的物理内存访问 - 11 -
7.6 hello进程fork时的内存映射 - 11 -
7.7 hello进程execve时的内存映射 - 11 -
7.8 缺页故障与缺页中断处理 - 11 -
7.9动态存储分配管理 - 11 -
7.10本章小结 - 12 -
第8章 hello的IO管理 - 13 -
8.1 Linux的IO设备管理方法 - 13 -
8.2 简述Unix IO接口及其函数 - 13 -
8.3 printf的实现分析 - 13 -
8.4 getchar的实现分析 - 13 -
8.5本章小结 - 13 -
结论 - 14 -
附件 - 15 -
参考文献 - 16 -

第1章 概述
1.1 Hello简介
P2P:Program to Progress, 对于hello.c,一开始只是文本形式的Program,C语言类型。在经过cpp的预处理生成了hello.i预处理文件,之后交给ccl进行编译,生成了hello.s汇编文件。再将汇编文件交给as生成可重定位文件hello.o,接下来,经由链接器ld,生成可执行文件hello。这样hello就变成了一个可以执行的Program。之后在vim中输入命令行,shell在解析命令行之后fork()一个子进程并在其中execve()执行这个程序,并提供argv参数列表和envp环境变量列表。这样程序完成了P2P的过程。
020:Zero to Zero,刚开始内存中是没有程序信息的,程序需要加载到内存中才能运行,execve时,hello会被分配一个虚拟内存,对应着一个物理内存,里面存放着程序需要运行的信息。之后便可以执行程序,CPU和系统通过上下文切换,进程管理,有条不紊的进行一个有一个Prosess。最终hello程序返回,会被shell回收,对应内存和虚拟内存随即释放,结束运行。
1.2 环境与工具
软件环境:windows10 64位,WPS office,Ubuntu-20.04.4。
硬件环境:AMD Ryzen 5 4500U with Radeon Graphics,12.0GBRAM。
开发工具:Ubuntu下EDB调试环境,GCC。
1.3 中间结果
hello.c:给定的C语言源文件,可以预处理生成hello.i预处理文件。
hello.i:预处理文件,可以编译生成hello.s汇编代码文件。
hello.s:汇编代码文件,可以汇编生成hello.o可重定位目标文件。
hello.o:可重定位目标文件,可反汇编生成汇编代码,也可查看elf信息。可以链接生成hello可执行文件。
hello:可执行文件,可以运行。可反汇编生成汇编代码,也可查看elf信息。
1.4 本章小结
通过预处理,汇编等步骤我们可以一步步生成中间文件直到可执行文件并在sheel下运行和终止,完成整个P2P,020。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
概念:
程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。典型地,由预处理器(preprocessor) 对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的单位——(用C/C++的术语来说是)预处理记号(preprocessing token)用来支持语言特性(如C/C++的宏调用)。

作用:
预处理中会展开以#起始的行,试图解释为预处理指令(preprocessing directive) ,其中ISO C/C++要求支持的包括#if/#ifdef/#ifndef/#else/#elif/#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令) 。预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。

2.2在Ubuntu下预处理的命令

在这里插入图片描述
2.3 Hello的预处理结果解析
Hello.c大小为527字节,hello.o大小为64.7KB,文件大小的改变因为#后面内容的添加。
2.4 本章小结
本章预处理了Hello.c,生成了Hello.i,比较了二者的不同。
(第2章0.5分)

第3章 编译
3.1 编译的概念与作用

概念:将程序员所撰写的编程语言翻译成汇编语言的过程:编译器将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。其以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。
作用:通过词法分析,语法分析,语义分析,之后进行优化。最后可以生成相应的汇编代码(hello.s)。编程语言转化为机器可识别的预言。

3.2 在Ubuntu下编译的命令
在这里插入图片描述

3.3 Hello的编译结果解析
在这里插入图片描述在这里插入图片描述

.file:声明源文件;
.text:代码节
.rodata:只读代码段;
.align:数据或者指令的地址对齐方式;
.string:声明一个字符串(.LC0,.LC1);
.global:声明全局变量;
.type:声明一个符号是数据类型还是函数类型。

首先压栈,然后指针指向栈顶,-32(%rbp)和-20(%rbp)是俩个main参数,接下来cmpl比较判断,是则跳过je,否则进入.L2。
.L2先创造了i=0,然后跳到.L3。

.L3先判断是否i==7,是则往下走,否则进入.L4。
.L4先取到main的两个参数,然后调用.L1,然后取第三个参数,调用sleep和auto函数,i加1结束。

调用函数:传递控制:进行过程B的时候,程序计数器必须设置为B的代码的起始 地址,然后在返回时,要把程序计数器设置为A中调用 B 后面那条指令的地址。传递数据:A必须能够向B提供一个或多个参数B必须能够向A中返回 一个值。分配和释放内存:在开始时,B可能需要为局部变量分配空间,而在返回前,又必须释放这些空间。
涉及函数:main函数,printf,exit,sleep ,getchar。main函数的参数是argc和argv。printf函数的参数argv[0],和argv[1]。
exit参数是1,sleep函数参数是atoi(argv[3])
函数的返回值存储在%eax寄存器中。
类型转换:atoi(argv[3]),将字符串类型转换为整数类型。
3.4 本章小结
本章将hello.i编译为hello.s,分析了汇编代码执行过程。
(第3章2分)

第4章 汇编
4.1 汇编的概念与作用

概念:汇编是指把汇编语言书写的程序翻译成与之等价的机器语言程序的翻译程序的过程。汇编程序输入的是用汇编语言书写的源程序,输出的是用机器语言表示的目标程序。
作用:将汇编语言.s文件翻译成可重定位目标文件.o,将汇编语言翻译为机器语言。
4.2 在Ubuntu下汇编的命令
在这里插入图片描述

4.3 可重定位目标elf格式
在这里插入图片描述

ELF头:以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。
节头部:指明各个节的名称、类型、起始地址和偏移量。
.txt:已编译程序的机器代码。
.rel.text:.text节中位置的列表。
.data:已初始化的全局和静态变量。
.symtab:一个符号表。
.bss:未初始化的全局和静态变量。
Section Headers:节头部表,包含了文件中出现的各个节的语义,包括节 的类型、位置和大小等信息。 由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,同时可以观察到,代码是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。
.symtab: 存放程序中定义和引用的函数和全局变量的信息。name是符号名称,对于可冲定位目标模块,value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type要么是数据要么是函数。Bind字段表明符号是本地的还是全局的。
重定位节.rela.text中包含.text节的重定位信息,在链接时程序将通过这些信息和代码提供的偏移找到正确的需要调用的函数地址。本程序中,需要重定位的包括.rodata节中的两个数据,全局变量sleepsecs, 函数puts, exit, printf, sleep, getchar。

在这里插入图片描述
在这里插入图片描述

4.4 Hello.o的结果解析
机器语言的调用跳转是相对寻址,而汇编语言是绝对寻址。.o文件中每条语句都有了一个偏移量,便于跳转寻址。
在这里插入图片描述

需要重定位的地方有区别。hello.s中跳转用的标签在hello_asm.txt中全部转变成了地址。

4.5 本章小结
本章对hello.s进行了汇编,生成了hello.o可重定位目标文件。
分析了可重定位文件的ELF头、节头部表等节。
比较了hello.s和hello.o反汇编代码的不同之处。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
概念:以一组可可重定位的目标文件和命令行参数为输入,生成一个完全链接的,可以加载和运行的可执行目标文件为输出。
作用:使分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而实可以分解为更小的、更好管理的模块,可以独立地修改和编译单一模块。
5.2 在Ubuntu下链接的命令
在这里插入图片描述

5.3 可执行目标文件hello的格式
在这里插入图片描述

在节头部表中,hello_elf.txt中地址全部都是0,而在hello_out_elf.txt中有了相对应的地址。与hello_elf.txt相比,hello_out_elf.txt少了许多节,比如.bss等。又多了一些节,如.note.ABI.tag。
在这里插入图片描述

文件的最后还多了链接信息。
5.4 hello的虚拟地址空间
在这里插入图片描述

.interp:size:0x1c,address:0x004002e0;
在这里插入图片描述

.init:size:0x1b,address:0x00401000。初始化。
在这里插入图片描述

.text:size:0x145,address:0x004010f0。.text中存放的是代码。
5.5 链接的重定位过程分析
hello重定位的过程:
重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。代码的重定位条目放在.rel.txt。
不同:hello增加了许多外部函数。增加了.init, .plt, .fini等等一些节。偏移地址变为了虚拟内存的地址。链接时,链接器通过符号表和节头了解到.data和.text在每个文件中的偏移和大小,进行合并,然后为新的合并出来的数据和代码节分配内存,并映射虚拟内存地址。最后修改对各种符号的引用,完成重定位。

5.6 hello的执行流程
在这里插入图片描述

004010f0调用start函数
0x401125调用main函数
0x401000调用_init函数
0x404050调用_end函数
0x404018调用puts函数
000000404020调用printf函数
000000404028调用getchar函数
000000404030调用atoi函数
000000404038调用exit函数
000000404040调用sleep函数
5.7 Hello的动态链接分析
GOT表:
概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。
作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld。
在函数调用时,首先跳转到PLT执行.plt中操作,第一次访问跳转时GOT地址为下一条指令,将函数序号入栈,然后跳转到PLT[0],之后将重定位表地址入栈,访问动态链接器,在动态链接器中使用在栈里保存的函数序号和重定位表计算函数运行时的地址,重写GOT,返回调用函数.之后如果还有对该函数的访问,就不用执行第二次跳转,直接参看GOT信息.

在这里插入图片描述
在这里插入图片描述

5.8 本章小结
本章讲述了链接的概念与作用、hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程.

第6章 hello进程管理
6.1 进程的概念与作用
概念:操作系统对一个正在运行的程序的一种抽象。是一个执行中程序的实例.每次用户通过向shell 输入一个可执行目标文件的名字,运行程序时, shell 就会创建一个新的进程.
作用:一个程序在系统上运行时,操作系统会提供一种程序在独占这个系统,包括处理器,主存,I/O设备的假象。处理器看上去在不间断地一条一条执行程序中的指令.进程也被称为计算机科学中最伟大的创新。
6.2 简述壳Shell-bash的作用与处理流程
shell是一个应用程序,他在操作系统中提供了一个用户与系统内核进行交互的界面。处理过程:从终端读入输入的命令。将输入字符串切分获得所有的参数如果是内置命令则立即执行否则调用相应的程序为其分配子进程并运行shell应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
Fork函数再进程的当前位置创建一个新进程,新进程具有与原进程完全相同的状态(除PID)
6.4 Hello的execve过程
Execve函数在当前进程的上下文中加载并运行一个新程序。当读取文件出现错误时,返回原程序,否则不返回。
6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
逻辑控制流:一系列程序计数器PC的值的序列叫做逻辑控制流,进程是轮流使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程。
时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
用户模式和内核模式:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。
当调用函数之前,如果主程序不被抢占则顺序执行,假如发生被抢占的情况,则进行上下文切换,上下文切换是由内核中调度器完成的,当内核调度新的进程运行后,它就会抢占当前进程,保存以前进程的上下文,然后恢复新被保存的上下文,将控制传递给这个新恢复的进程 ,来完成上下文切换。
6.6 hello的异常与信号处理
hello程序出现的异常可能有:
中断:在hello程序执行的过程中可能会出现外部I/O设备引起的异常。
中断处理:中断是异步发生的,是来自处理器外部的 I/O 设备的信号的结果。硬件中断的异常处理程序称为中断处理程序。
陷阱:陷阱是有意的异常,是执行一条指令的结果,hello执行sleep函数的时候会出现这个异常。
陷阱处理:陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
故障:在执行hello程序的时候,可能会发生缺页故障。
故障处理:当故障发生时,处理器将控制转移给故障处理程序。如果故障处理程序能够修正这个错误,它就将控制返回给引起故障的指令,从而重新执行它,否则,处理程序返回到内核中的 abort 例程,abort 例程会终止引起故障的应用程序。
终止:终止时不可恢复的错误,在hello执行过程可能会出现DRAM或者SRAM位损坏的奇偶错误。
终止处理:终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如 DRAM 或者 SRAM 位被损坏时发生的奇偶错误。终止处理程序不会将控制返回给应用程序。

在这里插入图片描述

按下Ctrl+c后会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业
按下 ctrl-z 后默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台。

在这里插入图片描述

运行程序时乱按结果是程序运行情况和前面的相同,不shell将我们刚刚乱输入的字符除了第一个回车按下之前的字符当做getchar的输入之外,其余都当做新的shell命令,在hello进程结束被回收之后,将会在命令行中尝试解释这些命令。中间没有任何对于进程产生影响的信号被产生。
在这里插入图片描述

6.7本章小结
本章讲述了进程的概念作用,shell的基本原理,shell如何调用fork和execve。hello进程在执行时会遇到什么样的情况(包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令的处理),它对这些情况如何做出反应。以及一些常见异常和其信号处理方法。
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址(Logical Address) 是指由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干。
线性地址:地址空间中的整数是连续的。虚拟地址:为了简化讨论,我们总是假设使用的是线性地址空间。在一个带虚拟内存的系统中,CPU从一个有N=2"个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间。
物理地址:计算机系统的主存被组织成一个个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。
在hello加载进内存时会分配虚拟内存。虚拟地址总是从0x00400000开始的。这些虚拟地址通过页表映射了主存中的物理地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在段式存储管理中,将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。为了实现段式管理,操作系统需要如下的数据结构来实现进程的地址空间到物理内存空间的映射,并跟踪物理内存的使用情况,以便在装入新的段的时候,合理地分配内存空间。进程段表:描述组成进程地址空间的各段,可以是指向系统段表中表项的索引。每段有段基址(baseaddress),即段内地址。段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)。
7.3 Hello的线性地址到物理地址的变换-页式管理
将各进程的虚拟空间划分成若干个长度相等的页,页式管理把内存空间按页的大小分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。
7.4 TLB与四级页表支持下的VA到PA的变换
首先虚拟地址是由VPN和VPO组成的,VPN可以作为在TLB中的索引,如上图所示,TLB可以看作是一个PTE的cache,将常用的PTE缓存到TLB中,加速虚拟地址的翻译。TLB是具有高相连度的,应该是为了一次多存一些PTE。如果能够在TLB中找到与VPN对应的PTE,即为TLB hit,TLB直接给出PPN,然后PPO即为VPO,这样就构成了一个物理地址。
如果不能做到TLB hit就要到四级页表当中取寻址,在i7中VPN有36位,被分成了四段,从左往右的前三个九位的地址分别对应于在前三级页表当中的偏移,偏移在页表中所对应的页表条目指向某一个下一级页表,而下一个9位VPN就对应的是在这个页表中的偏移。最后一级页表中的页表条目存放的是PPN
比如VPN1在第一级页表中对应于一个页表条目,这个页表条目指向下一级页表中的某个页表,再依靠VPN2在这个页表中找到它对应的页表条目,同样,这个也表条目指向的是第三级页表中的某个页表,再依靠VPN3找到在这个页表中与之对应的页表条目,这个页表条目指向的是第四级页表中的某个页表,再依靠VPN4找出与之对应的页表条目,这个页表条目中存放的是PPN,在四级页表中最多可以存放512G的内存内容,显然一般是用不了那么多的。
最后再把VPO拿来当成PPO就能找到在对应的物理页上存放的内容了。
7.5 三级Cache支持下的物理内存访问
物理地址分为标记,组索引和块偏移。首先,在L1中匹配组索引位,若匹配成功,则根据标记和偏移的匹配结果决定缺失或是命中。若组索引匹配不成功,则进入下一级cache,重复直至进入内存。
7.6 hello进程fork时的内存映射
Fork会为新进程(子进程)复制一个与父进程完全相同只读数据空间,并为其分配另一片内存和虚拟地址。分配时会将其标记为私有,防止过程中被父进程影响。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制,当fork函数在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当两个进程中的任一个后来进行写操作时,写时复制就会创建新页面。
7.7 hello进程execve时的内存映射
调用Execve时,系统首先删除了当前进程中用户部分已有的结构,然后映射私有区域(建立新的文件结构,包括.data在内的各种节),共享区域(当前进程的动态链接),并设置PC。
7.8 缺页故障与缺页中断处理
段错误:地址不合法,即无法匹配到已有的区域结构中;非法访问:没有应有的读写权限;正常缺页:选择一页进行替换。
对于一个访问虚拟内存的指令来说,如果发生了缺页现象,CPU就会触发一个缺页异常。缺页异常会调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,例如存放在PP3中的VP4,如果VP4已经被更改,那就先将他存回到磁盘中。
找到了要存储的页后,内核会从磁盘中将需要访问的内存,例如图7.8.1所示的VP3放入到之前已经操作过的PP3中,并且将PTE中的信息更新,这样就成功的将一个物理地址缓存在了页表中。当异常处理返回的时候,CPU会重新执行访问虚拟内存的操作,这个时候就可以正常的访问,不会发生缺页现象了。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆.系统之间细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址) .对于每个进程,内核维护着一个变量brk, 它指向堆的顶部.
分配器将堆视为一组不同大小的块的集合来维护.每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的.已分配的块显式地保留为供应用程序使用.空闲块可用来分配.空闲块保持空闲,直到它显式地被应用所分配.一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的.
分配器有两种风格,显示分配器(要求应用显式地释放任何已分配的块),隐式分配器(要求分配器检测一个已分配块是否仍然需要,不需要则释放)。
分配策略:
1.空闲链表:
隐式:在每块的头,尾部增加32位存储块大小,以及是否空闲。
显式:在隐式的基础上在头部增加对前后空闲块的指针。
分离:同时维护多个空闲链表。
2.带边界标记的合并:
利用每块头尾的大小和空闲状态信息合并空闲块。
3.无合适空闲块时,申请额外的堆空间。
7.10本章小结
本章讲述了hello的存储器地址空间以及其线性地址到物理地址的变换-页式管理、intel的段式管理、TLB与四级页表支持下的VA到PA的变换、三级cache支持下物理内存访问, hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理等内容.
(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
一个Linux文件就是一个m个字节的序列:B0,B1,……,Bk,……,B(m-1)。
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种同一且一致的方式来执行。
设备的模型化:文件
设备管理:unix io接口
Unix文件类型有
普通文件 包含用户/应用程序数据(二进制,文本等)的文件,除了“字节顺序”以外,操作系统对格式一无所知
目录文件 一个包含有名字和其它文件位置的文件
字符设备和块设备 终端(Terminals特殊字符)和磁盘(disks特殊块)
FIFO(管道) 一种用来进程内部通信的文件类型
Socket 一种用来不同进程问的网络通信文件路径
8.2 简述Unix IO接口及其函数
Unix IO接口:
打开文件,内核返回一个非负整数的文件描述符,通过对此文件描述符对文件进行所有操作。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(文件描述符0)、标准输出(描述符为1),标准出错(描述符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,他们可用来代替显式的描述符值。
改变当前的文件位置,文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。
读写文件,读操作:从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n;写操作:从内存复制n个字节到文件,当前文件位置为k,然后更新k

关闭文件。当应用完成对文件的访问后,通知内核关闭这个文件。内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中
Unix I/O函数:
open 函数:打开一个已存在的文件或者创建一个新文件的
函数原型:int open(char *filename, int flags, mode_t mode);
open 函数将filename 转换为一个文件描述符,并且返回描述符数字.返回的描述符总是在进程中当前没有打开的最小描述符.flags 参数指明了进程打算如何访问这个文件,mode 参数指定了新文件的访问权限位.返回:若成功则为新文件描述符,若出错为-1.
close 函数:关闭一个打开的文件.
函数原型:int close(int fd);
返回:若成功则为0, 若出错则为-1.
3.read 和write 函数:执行输入和输出的.
ssize_t read(int fd, void *buf, size_t n);
read 函数从描述符为fd 的当前文件位置复制最多n 个字节到内存位置buf .返回值-1表示一个错误,而返回值0 表示EOF.否则,返回值表示的是实际传送的字节数量.
返回:若成功则为读的字节数,若EOF 则为0, 若出错为-1.
ssize_t write(int fd, const void *buf, size_t n);
write 函数从内存位置buf 复制至多n 个字节到描述符fd 的当前文件位置.图10-3 展示了一个程序使用read 和write 调用一次一个字节地从标准输入复制到标准输出.返回:若成功则为写的字节数,若出错则为-1.
8.3 printf的实现分析
char *fmt是可变形参。
va_list的定义:typedef char *va_list。是一个字符指针。对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。
vsprintf返回的是一个长度,返回的是要打印出来的字符串的长度。
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
write(buf, i)是将第i个数据打印出来。在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,int INT_VECTOR_SYS_CALLA代表通过系统调用syscall。
syscall函数就是不断的打印出字符,直到遇到:’\0’ 停止。
printf函数实现过程:
(1):vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
(2):vsprintf的输出到write系统函数中。在Linux下,write通过执行syscall指令实现了对系统服务的调用,从而使内核执行打印操作。内核会通过字符显示子程序,根据传入的ASCII码到字模库读取字符对应的点阵,然后通过vram(显存)对字符串进行输出。
(3):显示芯片将按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量),最终实现printf中字符串在屏幕上的输出。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

getchar函数通过调用read函数来读取字符。read函数由三个参数,第一个参数为文件描述符fd,fd为0表示标准输入;第二个参数为输入内容的指针;第三个参数为读入字符的个数。read函数的返回值是读入字符的个数,若出错则返回-1。
当程序调用getchar时.程序就等着用户按键。用户输入的字符被存放在键盘缓冲区中。直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。getchar函数的返回值是用户输入的字符的ASCII码,若文件结尾(End-Of-File)则返回-1(EOF),且将用户输入的字符回显到屏幕。
异步异常-键盘中断的处理:
键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回.
8.5本章小结
本章讲述了Linux的IO设备管理方法,Unix IO接口及其函数。以及对函数printf和getchar的深入分析。

(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
形成一个可运行的hello程序经过了很多步骤。最开始,hello.c在预处理器中经过预处理,与所有的外部库合体成为hello.i;再在编译器中经过编译,成为了机器可识别的hello.s;之后,汇编器又将hello.s转换为可重定位的目标文件hello.o,,在虚拟内存中记载文件内容;最后,连接器会把hello.o进行链接,使得其可以将所用的库和函数整合到一个文件中。
紧接着运行程序,在shell中输入“./hello”时,bash会新建一个进程,先fork一个子进程,然后清空当前进程的数据并加载hello,从函数的入口进入,开始执行,由于各种原因,hello可能会暂时的休息(系统调用或者计时器中断),这时我们保留当前进度,并切换上下文,内核去处理别的进程,提高效率。我们还可以输入信号来终止或挂起hello进程,hello输出信息时需要调用printf和getchar,而printf和getchar的实现需要调用Unix I/O中的write和read函数,而它们的实现需要借助系统调用I/O,在最后结束之后bash等到exit,作为hello的父进程回收hello。随后,内核删除他所有的数据,hello的此次旅途也就到达终点。
感悟:运行一个小小的hello程序却包含了一个程序从诞生,一步步处理,最终在终端显示,最后结束的普遍规律和流程。对计算机系统的深入学习让我更加理解如何写出一个优秀的程序以及如何修改修正自己的程序。对计算机系统的更深入理解也提高了我的专业水平,理解了计算机领域的广阔复杂与深邃。
(结论0分,缺失 -1分,根据内容酌情加分)

附件
在这里插入图片描述

hello.c:C语言编写的源程序文件。
hello.i:hello.c经预处理得到的ASCII码的中间文件。
hello.s:hello.i编译之后得到的一个ASCII汇编语言文件。
hello.o:hello.s汇编之后得到的一个可重定位目标文件。
hello:hello.o和标准的C库进行链接得到的可执行目标文件。
hello_asm.txt:hello的反汇编代码。
hello_out_asm.txt:hello.o的反汇编代码。
hello_elf.txt:hello的elf格式文件。
Hello_out_elf.txt:hello.o的elf格式文件。
列出所有的中间产物的文件名,并予以说明起作用。
(附件0分,缺失 -1分)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值