计算机系统大作业

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业       计算机系          

计算机科学与技术学院

2022年5月

摘  要

在编译源文件的过程中,gcc通过调用c预处理器,c编译器,汇编器,连接器,将C语言源文件进行预处理、编译、汇编、链接,最终形成可执行目标文件。本文以hello程序从hello.c到进程各个阶段所经历的过程为背景,利用gcc,edb等工具介绍hello的整个生命过程,深入了解了汇编、编译流程、操作系统资源管理的相关知识,从而系统地总结计算机系统这门课的全部内容。

关键词:编译;hello程序;计算机系统;                           

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第1章 概述............................................................................................................. - 4 -

1.1 Hello简介...................................................................................................... - 4 -

1.2 环境与工具..................................................................................................... - 4 -

1.3 中间结果......................................................................................................... - 4 -

1.4 本章小结......................................................................................................... - 5 -

第2章 预处理......................................................................................................... - 6 -

2.1 预处理的概念与作用..................................................................................... - 6 -

2.2在Ubuntu下预处理的命令.......................................................................... - 6 -

2.3 Hello的预处理结果解析.............................................................................. - 7 -

2.4 本章小结......................................................................................................... - 7 -

第3章 编译............................................................................................................. - 8 -

3.1 编译的概念与作用......................................................................................... - 8 -

3.2 在Ubuntu下编译的命令............................................................................. - 8 -

3.3 Hello的编译结果解析.................................................................................. - 8 -

3.4 本章小结....................................................................................................... - 11 -

第4章 汇编........................................................................................................... - 12 -

4.1 汇编的概念与作用....................................................................................... - 12 -

4.2 在Ubuntu下汇编的命令........................................................................... - 12 -

4.3 可重定位目标elf格式............................................................................... - 12 -

4.4 Hello.o的结果解析.................................................................................... - 15 -

4.5 本章小结....................................................................................................... - 16 -

第5章 链接........................................................................................................... - 17 -

5.1 链接的概念与作用....................................................................................... - 17 -

5.2 在Ubuntu下链接的命令........................................................................... - 17 -

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

5.4 hello的虚拟地址空间................................................................................ - 20 -

5.5 链接的重定位过程分析............................................................................... - 21 -

5.6 hello的执行流程........................................................................................ - 22 -

5.7 Hello的动态链接分析................................................................................ - 22 -

5.8 本章小结....................................................................................................... - 23 -

第6章 hello进程管理................................................................................... - 24 -

6.1 进程的概念与作用....................................................................................... - 24 -

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

6.3 Hello的fork进程创建过程..................................................................... - 24 -

6.4 Hello的execve过程................................................................................. - 25 -

6.5 Hello的进程执行........................................................................................ - 25 -

6.6 hello的异常与信号处理............................................................................ - 26 -

6.7本章小结....................................................................................................... - 26 -

第7章 hello的存储管理............................................................................... - 28 -

7.1 hello的存储器地址空间............................................................................ - 28 -

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

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

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

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

7.6 hello进程fork时的内存映射.................................................................. - 31 -

7.7 hello进程execve时的内存映射.............................................................. - 31 -

7.8 缺页故障与缺页中断处理........................................................................... - 31 -

7.9动态存储分配管理....................................................................................... - 32 -

7.10本章小结..................................................................................................... - 33 -

第8章 hello的IO管理................................................................................. - 34 -

8.1 Linux的IO设备管理方法.......................................................................... - 34 -

8.2 简述Unix IO接口及其函数....................................................................... - 34 -

8.3 printf的实现分析........................................................................................ - 34 -

8.4 getchar的实现分析.................................................................................... - 35 -

8.5本章小结....................................................................................................... - 36 -

结论......................................................................................................................... - 36 -

附件......................................................................................................................... - 37 -

参考文献................................................................................................................. - 38 -

第1章 概述

1.1 Hello简介

P2P: 全称为From Program to Progress。这个过程经过预处理、编译、汇编、链接等一系列的复杂动作生成一个可执行目标文件。在shell中键入启动命令后,shell为其fork产生一个子进程,然后hello便从程序变为了进程。

020:全称为From 0 to 0,Hello的出生是由操作系统进行存储、地址翻译、内存访问,通过按需页面调度来开始这段生命。当hello结束运行后,父进程便会回收hello,删除相关数据。

1.2 环境与工具

1.2.1 硬件环境

X64 CPU;1.60GHz;16GRAM;256GHD Disk

1.2.2 软件环境

Windows10 64位;Vmware;Ubuntu16.04

1.2.3 开发工具

Visual Studio 2022;CodeBlocks;vi/vim/gedit+gcc

1.3 中间结果

文件

说明

hello.c

hello程序的源代码

hello.i

hello.c预处理后的文件

hello.s

hello.i编译后的汇编文件

hello.o

hello.s汇编后的可重定位文件

hello

hello.o链接后的可执行文件

1.4 本章小结

本章简要介绍了hello程序的P2P020,列出了实验环境和工具以及中间过程文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:

预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。

预处理的作用:

概念:预处理器cpp根据#开头的宏定义、条件编译等命令,修改原始c程序,将引用的库展开合并成一个完整的文本文件。

作用:

1.处理条件编译指令,将#ifdef、#else等指令进行处理,把某部分代码包含进来或排除在外,将不必要的代码过滤掉。

2.处理#define、#include等指令,把相应的定义和对应的头文件加入进来,使得编译程序对其处理。

3.处理一些特殊符号例如LINE、FILE等,用合适的值对其进行替换。

2.2在Ubuntu下预处理的命令

命令:gcc hello.c -E -o hello.i

2.3 Hello的预处理结果解析

观察生成的hello.i。我们发现代码数量大幅度增加,源码出现在代码末尾。这些是cpp所填充的,预处理对原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。例如声明函数、定义结构体、定义变量、定义宏等内容。最后hello.c文件变为hello.i文件。

2.4 本章小结

本章介绍了预处理的概念、作用、命令及其所进行的一些处理,例如实现将定义的宏进行符号替换、引入头文件的内容、根据指令进行选择性编译等。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

编译是利用编译程序从源语言编写的源程序产生目标程序的过程也可以理解是用编译程序产生目标程序的动作。简而言之,编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。

编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。

3.2 在Ubuntu下编译的命令

命令:gcc hello.i -S -o hello.s

3.3 Hello的编译结果解析

3.3.1工作伪指令:

.file:声明源文件

.text:代码节

.section:

.rodata:只读代码段

.align:数据或者指令的地址对其方式

.string:声明一个字符串(.LC0,.LC1)

.global:声明全局变量(main)

.type:声明一个符号是数据类型还是函数类型

3.3.2数据

字符串:程序中有两个字符串,这两个字符串都在只读数据段中,printf()scanf()中的字符串都被存储在.rodata节中。

立即数:立即数在汇编代码中的呈现形式最为简单易辨认。即数顾名思义,是直接显式使用的一类数据,在汇编代码中通常以$符号作为标识。下图就是其中一个例子,表示rsp中存储的数据值减去32。

寄存器变量:寄存器变量通过特定的寻址方式进行引用。如下图中的例子,表示的就是将寄存器%edi中存储的值,加载到以现在栈指针%rbp指向的位置基础上,减去20所对应的地址中去。

3.3.3数据传送指令

汇编代码中数据移动使用MOV类,这些指令把数据从源位置复制到目的位置,不做其他变化。movb、movw、movl、movq是其中最常用的指令,这些指令执行相同的操作,区别在于他们所移动的数据大小不同,指令的最后一个限制字符必须和寄存器所对应的数据大小保持一致。

接着用上个单元的例子,这里movl与movq就是直接传递数据。

3.3.4算术运算指令

x86-64中的运算指令如下图所示:

用之前的例子,这里使用subq算数运算指令,实行减法。

3.3.5关系运算指令

CPU维护着一组单个位的条件码,它们描述了最近算数运算或逻辑运算的属性。常见的条件码有:

CF:进位标志

ZF:零标志

SF:符号标志

OF:溢出标志

算数运算会设置条件码。此外,CMP指令和TEST指令能够设置条件码并不改变其他寄存器。CMP指令与SUB指令行为一致;TEST指令与AND指令行为一致。如下例:这是一个和4进行的比较运算,不相等时回继续向下,相等时程序会跳转到.L2。

3.3.6数组运算

主函数main的参数中有指针数组char *argv[]

在argv数组中,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。因为char* 数据类型占8个字节,根据.L4:对比原函数可知通过M[%rbp-32+16]和M[%rbp-32+8],分别得到argv[1]和argv[2]两个字符串的首地址。

3.3.7函数调用

X86-64中,过程调用传递参数规则:第1~6个参数一次储存在%rdi、%rsi、%rdx、%rcx、%r8、%r9这六个寄存器中,剩下的参数保存在栈当中。

在函数调用时,包括了如下机制:A调用B

传递控制。程序计数器设置为B的地址,返回时,计数器设为B之后指令的地址。传递数据:A向B提供一个或多个参数,B向A提供一个返回值。分配和释放内存:在开始时,Q 可能需要为局部变量分配空间,而在返回 前,又必须释放这些空间。

在hello程序中,我们调用了printf, sleep等函数。我们观察printf("用法: Hello 学号姓名秒数!\n");的汇编代码可知,我们将字符串.LC0放入了寄存器%edi作为参数,通过call调用了函数puts,.LC0的字符串是puts函数的第一个参数。

3.4 本章小结

本章简述了编译的概念与作用,对hello.s中的汇编代码进行了简单解析,梳理了文件中伪指令、各类数据、数据传送、算术运算、数组运算函数调用等在汇编语言中的解释。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

汇编是将高级语言转化为机器可直接识别执行的代码文件的过程,汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中。

4.2 在Ubuntu下汇编的命令

命令:gcc hello.s -c -o hello.o

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

用命令readelf -h hello.o查看ELF头:

我们可以获取elf文件的类型,程序系统位数,数据采用补码表示,小端存储等信息。

用命令readelf -S hello.o查看节头表:

节头表包含了文件中出现的各个节的语义,包括节的类型、位置和大小等信息。 每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小,同时可以观察到,代码是可执行的,但是不能写。

由上图可以看出,该节头表中一共有14项,其中第一项为空。剩下13项对应于可重定位文件中每一节的相关内容,其中包含每一节的名字,类型,地址(由于还未重定位所以每一节的地址都用0代替),在文件中的偏移量,节的大小,访问权限,对齐方式等等。

用命令readelf -s hello.o查看符号表:

该表中有main,printf等函数的类型,大小,以及一些全局变量的信息。

        用命令readelf -r hello.o查看重定位节:

在上图中,offset是需要被修改的引用的节偏移,Sym.标识被修改引用应该指向的符号。Type告知连接器如何修改新的引用,Addend是一个有符号常数,一些类型的重定位要使用它对被修改的引用的值做偏移调整。

4.4 Hello.o的结果解析

objdump -d -r hello.o  分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

使用命令objdump -d -r hello.o生成反汇编代码如下:

将上图和前面的汇编文件比较可以看出两者汇编语言及指令完全一样,但是反汇编代码中不仅有汇编代码,还有其对应的机器语言代码。机器语言代码是完全面向计算机的二进制数据表示的语言。机器语言代码中包含操作码,数据,寄存器编号等内容,其中机器语言的每一个操作码,寄存器编号等都与汇编语言一一对应。机器语言中的数据采用小端存储的二进制形式表示,而在汇编语言中采用的是顺序十六进制形式表示。通过这些映射关系就可以实现机器语言与汇编语言的一一对应。

在控制转移上,hello.s使用.L2和.LC1等段名称进行跳转,而反汇编代码使用目标代码的虚拟地址跳转。不过目前留下了重定位条目,跳转地址为零。它们将在链接之后被填写正确的位置。

在函数调用上,hello.s直接call函数名称,而反汇编代码中call的是目标的虚拟地址。但和上一条的情况类似,只有在链接之后才能确定运行执行的地址,目前目的地址是全0,并留下了重定位条目。

4.5 本章小结

本章介绍了汇编的概念和作用。经过汇编器,汇编语言转化为机器语言,hello.s文件转化为hello.o可重定位目标文件。我们研究了可重定位目标文件elf格式,接触了了readelf命令、ELF头、节头部表、重定位节、符号表。此外,我们对比hello.s和hello.o的反汇编文件,分析两者之间的差别。

(第41分)

5章 链接

5.1 链接的概念与作用

链接是将各种代码个数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。链接器使得分离编译成为可能。

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的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

    ELF头:

类型变为EXEC可执行文件,有27个节。

节头表:

其对Hello中所有节的信息进行了声明,包括大小size和偏移量offset,根据里面的信息可以定位各个节所占的区间,地址为被加载到虚拟地址的初始地址。

符号表:

发现经过链接之后符号表的符号数量增加,这说明经过链接之后引入了许多其他库函数的符号,一并加入到了符号表中。

重定位节:

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。  

查看edb得出,hello虚拟地址起始于0x400000,结束于0x400ff0

根据前面的节头部表,可以通过edb查看各个节的信息。

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

使用命令objdump -d -r hello查看hello可执行文件的反汇编条目

可以观察到hello的反汇编代码与hello.s的返沪编代码在结构和语法上是基本完全相同的,只不过hello的反汇编代码多了一些内容。

1、hello反汇编代码中函数调用时不再仅仅储存call当前指令的下一条指令,而是已经完成了重定位,调用的相应函数已经有对应的明确的虚拟地址空间。

2、hello.o的反汇编代码的地址从0开始,而hello的反汇编代码从0x400000开始。这说明hello.o还未实现重定位的过程,每个符号还没有确定的地址,而hello已经实现了重定位,每个符号都有其确定的地址。

3、hello反汇编代码中多了很多节,显然这些节是经过链接之后加入进来的。例如.init节就是程序初始化需要执行的代码所在的节,.dynamic节是存放被ld.so调用过的动态链接信息的节等等。

5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

hello_start

0x4010f0

hello_libc_start_main

0x2f12271d

hello_main

0x401125

hello_printf

0x4010a0

hello_sleep

0x4010e0

hello_getchar

0x4010b0

hello_exit

0x4010d0

5.7 Hello的动态链接分析

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

共享链接库代码是动态的目标模块,在程序开始运行或者调用程序加载时,可以自动加载该代码到任意的一个内存地址,并和一个在目标模块内存中的应用程序链接起来,这个过程就是对动态链接的重定位过程。

动态的链接器在正常工作时采取了延迟绑定的链接器策略,由于静态的编译器本身无法准确预测变量和函数的绝对运行时地址,动态的链接器需要等待编译器在程序开始加载时再对编译器进行延迟解析,这样的延迟绑定策略称之为动态延迟绑定。在plt和got中分别存放着链接器的目标变量和函数的运行时地址。

首先查看与动态链接相关的.plt段和.got:

链接前后的.got.plt表中内容:

0404000到0404010之间的一段数据发生了变化。此变化便是GOT表中加载了共享库的内容。

5.8 本章小结

本章介绍了链接的概念和作用,详细介绍了hello.o是如何链接生成一个可执行文件,分析了hello的ELF格式文件,比较了反汇编代码,同时展示了可执行文件中不同节的内容,用edb执行了hello程序的全部流程,最后分析了程序是如何实现的动态链接的。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

进程是一个执行中程序的实例,系统的每个程序都运行在某个进程的上下文。上下文是由程序正确运行所需的状态组成的,这个状态包括存放在内存里的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开文件描述符的集合。进程的存在向用户提供了一种假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

作用:Shell是用户与操作系统之间完成交互式操作的一个接口程序,它为用户提供简化了的操作。Shell最重要的功能是命令解释,从这种意义上说,Shell是一个命令解释器。Linux系统上的所有可执行文件都可以作为Shell命令来执行。当用户提交了一个命令后,Shell首先判断它是否为内置命令,如果是就通过Shell内部的解释器将其解释为系统功能调用并转交给内核执行;若是外部命令或实用程序就试图在硬盘中查找该命令并将其调入内存,再将其解释为系统功能调用并转交给内核执行。

处理流程:

终端进程读取用户由键盘输入的命令行。

分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量。

检查首个命令行参数是否是一个内置的shell命令。

如果不是内部命令,调用fork()创建新进程/子进程。

在子进程中,用步骤2获取的参数,调用execve()执行指定程序。

如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid等待作业终止后返回。

如果用户要求后台运行(如果命令末尾有&号),则shell返回。

6.3 Hello的fork进程创建过程

终端程序通过调用fork()函数创建一个子进程,子进程得到与父进程完全相同但是相互独立的副本,包括代码段、段、数据段、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,父进程和子进程的PID是不同的,是并发运行的独立进程,内核能够以任意方式交替执行它们的逻辑控制流的指令。在子进程执行期间,父进程默认选项是显示等待子进程的完成。

以我们的hello为例,当我们输入 ./hello 120L020128 姚海鹏 1 的时候, shell对我们输入的命令进行解析,由于我们输入的命令不是一个内置的shell命令,因此shell会调用fork()创建一个子进程。

6.4 Hello的execve过程

Execve的参数包括需要执行的程序(通常是argv[0])、参数argv、环境变量envp。

1. 删除已存在的用户区域。

2. 映射私有区:为Hello的代码、数据、.bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的。

3. 映射共享区:比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。

4. 设置PC:exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。

5. execve在调用成功的情况下不会返回,只有当出现错误时,例如找不到需要执行的程序时,execve才会返回到调用程序。

6.5 Hello的进程执行

逻辑控制流和时间片:一系列程序计数器 PC 的值的序列叫做逻辑控制流,进程是轮流 使用处理器的,在同一个处理器核心中,每个进程执行它的流的一部分后被抢占,然后轮到其他进程。

用户模式和内核模式:用户模式的进程不允许执行特殊指令,不允许直接引用地址空间中内核区的代码和数据。内核模式进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。

上下文:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。当内核选择一个新的进程运行时,则内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。

调度的过程:在对进程进行调度的过程,操作系统主要做了两件事:加载保存的寄存器,切换虚拟地址空间。

6.6 hello的异常与信号处理

 异常和信号异常可以分为四类:中断、陷阱、故障、终止。

下面是程序运行中根据用户输入而发生的各种情况:

(1)随便乱按:在屏幕上显示输入的内容。

(2)Ctrl+z:如果输入Ctrl+Z会发送一个SIGTSTP信号给前台进程组的每个进程,结果是停止前台作业。

(3)Ctrl+c:如果在程序运行过程中输入Ctrl+C,会让内核发送一个SIGINT信号给到前台进程组中的每个进程,结果是终止前台进程。

(4)使用ps查看进程信息:

(5)使用jobs查看作业信息,可以发现hello程序处于停止状态:

6.7本章小结

本章简述了进程、shell的概念与作用,分析了hello程序使用fork创建子进程的过程以及使用execve加载并运行用户程序的过程,运用上下文切换、用户模式、内核模式、内核调度等知识,分析了hello进程的执行过程,最后分析了hello对于异常以及信号的处理并进行了实际操作。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:编译器进行编译时,产生汇编汇编代码,每局代码以及每条数据都会有自己的逻辑地址。

线性地址:是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生段中的偏移地址,加上相应段的基地址就生成了一个线性地址。

虚拟地址:CPU启动保护模式后,程序hello运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中。CPU在启动的时候是运行在实模式的,Bootloader以及内核在初始化页表之前并不使用虚拟地址,而是直接使用物理地址的。

物理地址:用于内存芯片级的单元寻址,与处理器和CPU链接的地址总线相对应。

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

逻辑地址的表示形式为[段标识符:段内偏移量],这个表示形式包含完成逻辑地址到虚拟地址(线性地址)映射的信息。

逻辑地址实际是由48位组成的,前16位是段选择符,后32位是段内偏移量。通过段选择符,我们可以获得段基地址,再与段内偏移量相加,即可获得最终的线性地址。

段标识符又名段选择符,是一个16位的字段,包括一个13位的索引字段,1位的TI字段和2位的RPL字段。

通过段标识符的前13位,可以直接在段描述符表中索引到具体的段描述符。每个段描述符中包含一个Base字段,它描述了一个段的开始位置的线性地址。将Base字段和逻辑地址中的段内偏移量连接起来就得到转换后的线性地址。全局的段描述符,放在全局段描述符表中,每个进程自己的段描述符,放在局部段描述符表中。全局段描述符表存放在gdtr控制寄存器中,而局部段描述符表存放在ldtr寄存器中。

逻辑地址到线性地址的变换过程为:给定逻辑地址,看段选择符的最后一位是0还是1,从而判断选择全局段描述符表还是局部段描述符表。通过段标识符的前13位,得到Base字段,和段内偏移量连接起来最终得到转换后的线性地址。

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

分页机制在分段机制的基础上完成线性地址到物理地址转换的过程。分段机制把逻辑地址转换成线性地址,而分页则把线性地址转换成物理地址。

分页可以用于任何一种分段模型。处理器分页机制会把线性地址空间(段已映射到其中)划分成页面,然后这些线性地址空间页面被映射到物理地址空间的页面上。分页机制有几种页面级保护措施,可和分段机制保护机制合用或替代分段机制的保护措施。例如,在基于页面的基础上可以加强读/写保护。另外,在页面单元上,分页机制还提供了用户-超级用户两级保护。

我们通过设置控制寄存器CR0的PG位可以启用分页机制。

如果PG=1,则启用分页操作,处理器将线性地址转换成物理地址。

如果PG=0,则禁用分页机制,此时分段机制产生的线性地址被直接用作物理地址。

分段机制在各种可变长度的内存区域上操作。与分段机制不同,分页机制对固定大小的内存块(称为PAGE)进行操作。分页机制把线性和物理地址空间都划分成页面。线性地址空间中的任何页面可以被映射到物理地址空间的任何页面上。并在这两个空间之间提供了任意映射。

在保护模式中,8086允许线性地址空间直接映射到大容量的物理内存(如4GB的RAM)上,或者(使用分页)间接地映射到较小容量的物理内存和磁盘存储空间中。这后一种映射线性地址空间的方法被称为虚拟存储或者需求页(Demand-paged)虚拟存储。

当使用分页时,处理器会把线性地址空间划分成固定大小的页面(一般长度4KB),这些页面可以映射到物理内存中或磁盘存储空间中。当一个程序(或任务)引用内存中的逻辑地址时,处理器会把该逻辑地址转换成一个线性地址,然后使用分页机制把该线性地址转换成对应的物理地址。

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

每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。

将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。下图是使用k级页表的地址翻译。

处理器生成一个虚拟地址,并将其传送给MMU。MMU用VPN向TLB请求对应的PTE,如果命中,则跳过之后的几步。MMU生成PTE地址(PTEA).,并从高速缓存/主存请求得到PTE。如果请求不成功,MMU向主存请求PTE,高速缓存/主存向MMU返回PTE。PTE的有效位为零, 因此 MMU触发缺页异常,缺页处理程序确定物理内存中的牺牲页 (若页面被修改,则换出到磁盘——写回策略)。缺页处理程序调入新的页面,并更新内存中的PTE。缺页处理程序返回到原来进程,再次执行导致缺页的指令。

在多级页表的情况下,无非就是不断通过索引 – 地址 – 索引 - 地址重复四次进行寻找。

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

在从TLB或者页表中得到物理地址后,根据物理地址从cache中寻找。到了L1里面以后,寻找物理地址要检测是否命中,不命中则紧接着寻找下一级cache L2,接着L3,如果L3也不命中,则需要从内存中将对应的块取出放入cache中,其中可能会发生块的替换等其它操作。这里就是使用到CPU的高速缓存机制了,一级一级往下找,直到找到对应的内容。

7.6 hello进程fork时的内存映射

shell调用fork时,内核为新进程创建各种数据结构并分配唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程的两个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

7.7 hello进程execve时的内存映射

execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:

(1)删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。

(2)映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。

(3)如果hello与共享对象链接,那么这些对象都被动态链接到这个程序,然后映射到用户虚拟地址空间中的共享区域。

(4)设置程序计数器,使之指向代码区域的入口点,下次调用这个进程时,从这个入口点开始执行。

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

缺页故障:当指令引用一个相应的虚拟地址,而与该地址相应的物理页面不再内存中,会触发缺页故障。通过查询页表PTE可以知道虚拟页在磁盘的位置。缺页处理程序从指定的位置加载页面到物理内存中,并更新PTE。然后控制返回给引起缺页故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中,因此指令可以没有故障的运行完成。

7.9动态存储分配管理

Printf会调用malloc,请简述动态内存管理的基本方法与策略。

动态内存分配器维护者一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同的大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。分配器有两种基本风格。两种风格都是要求显示的释放分配块。

(1) 显式分配器:要求应用显示的释放任何已分配的块。例如C标准库提供一个叫做malloc程序包的显示分配器。

(2) 隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,那么就释放这个块。隐式分配器也叫垃圾收集器。

隐式空闲链表:空闲块通过头部的大小字段隐含地连接着。分配器遍历堆中所有的块,间接地遍历整个空闲块的集合。

隐式空闲链表的整体形式:

显示空闲链表:显示空闲链表是将空闲块组织为某种形式的显示数据结构。在每个空闲块中,都包含一个前驱和后继的指针。显示空闲链表
显示空闲链表是将空闲块组织为某种形式的显示数据结构。在每个空闲块中,都包含一个前驱和后继的指针。

7.10本章小结

本章介绍了从逻辑地址到线性地址到物理内存最后再到CPU三级缓存的转换过程,其中涉及段式管理,页式管理以及L1L2L3三级缓存。接着介绍了缺页中断,以及linux动态内存分配和glibc中的动态内存分配函数malloc

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。Linux就是基于Unix I/O实现对设备的管理。

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

Linux以文件的方式对I/O设备进行读写,将设备均映射为文件。对文件的操作,内核提供了一种简单、低级的应用接口,即Unix I/O接口。

打开文件:int open(char *filename, int flags, mode_t mode);

关闭文件:int close(int fd);

读文件:ssize_t read(int fd, void *buf, size_t n);

写文件:ssize_t write(int fd, const void *buf, size_t n);

8.3 printf的实现分析

printf函数:

vsprintf函数:

vsprintf函数将所有的参数内容格式化之后存入buf,返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

getchar:

异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键 的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子 程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码 转换成 ASCII 码,保存到系统的键盘缓冲区之中。

getchar 函数落实到底层调用了系统函数 read,通过系统调用 read 读取存储在 键盘缓冲区中的 ASCII 码直到读到回车符然后返回整个字串,getchar 进行封装, 大体逻辑是读取字符串的第一个字符然后返回。

8.5本章小结

本章介绍了Linux的IO设备管理方法,以及unix的IO接口及其函数。最后分析了glibc中printf和getchar的实现。

(第81分)

结论

(1)hello.c是存储在磁盘上的一个文本文件。

(2)hello.c通过C预处理器生成hello.i文本文件。

(3)hello.i通过编译器编译到hello.s汇编文件。

(4)hello.s汇编到二进制可重定位目标文件hello.o。

(5)hello.o链接生成可执行文件hello。

(6)bash进程调用fork函数,生成子进程。

(7)shell使用fork创建子进程,使用execve加载并运行hello程序。这一过程中,涉及到了虚拟内存,内存映射等知识。

(8)hello的输入输出与外界交互,与linux I/O息息相关。

(9)shell父进程回收hello进程,内核删除hello用户地址空间的内存映射页面。

计算机系统这门课程加深了我对于计算机有了解,也让我切实感受到了计算机的魅力,感受到了实际编写代码的乐趣,希望未来的学习可以让我在计算机方面拥有更大的收获。

(结论0分,缺失 -1分,根据内容酌情加分)

附件

列出所有的中间产物的文件名,并予以说明起作用。

文件

说明

hello.c

hello程序的源代码

hello.i

hello.c预处理后的文件

hello.s

hello.i编译后的汇编文件

hello.o

hello.s汇编后的可重定位文件

hello

hello.o链接后的可执行文件

(附件0分,缺失 -1分)

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1]  Pianistx - 博客园 (cnblogs.com)

[2] 深入了解计算机系统(第三版)2016 Bryant,R.E. 机械工业出版社

[3] 虚拟地址、逻辑地址、线性地址、物理地址_rabbit_in_android的博客-CSDN博客

(参考文献0分,缺失 -1分)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值