计算机系统大作业

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业        计算学部       

学     号       120L021219       

班     级         2003001        

学       生          lt       

指 导 教 师          史先俊       

计算机科学与技术学院

2022年5月

摘  要

本文通过对hello程序开始运行到结束整个过程的分析,浅析计算机系统所起到的作用。在hello.c进行预处理、编译、汇编、链接,以及在shell中创建子进程、运行并最终回收的生命周期中,深入了解计算机系统的工作流程。

关键词:hello;计算机系统;进程;存储;I/O                           

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

目  录

第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:最初的hello.c文件经过预处理成为hello.i,再经过编译成为汇编文件hello.s,又在汇编器作用下成为可重定位目标文件hello.o,最后经过链接成为可执行文件hello。此时,在shell下输入命令行即可为其创建子进程。

O2O:在fork之后,shell又会调用execve在当前进程的上下文中加载和运行hello程序,同时映射虚拟内存并加载到物理内存。在CPU控制下程序开始执行,通过I/O输出内容到显示屏。结束后通过子进程回收消除hello存在的痕迹,结束了hello的一生。

1.2 环境与工具

硬件环境:X64 CPU

软件环境:Windows10 64位,VMware 15.5 Pro, Ubuntu 20.04

开发调试工具:gcc,edb,gdb,readelf,cpp,as,ld

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

Hello.i                       hello.c预处理后的文件

Hello.s                       编译后得到的文件

Hello.o                      汇编后得到的可重定位目标文件

Hello(.out)                链接后得到的可执行目标程序

Disas_hello.s             hello.o经过反汇编得到的文件

Elf.txt                        hello.o的elf格式文件

Hello1.elf                  hello的elf格式文件

1.4 本章小结

简要介绍了hello程序在系统中的生命流程,列出了本次大作业涉及的软硬件环境、开发调试工具以及中间文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分(以#开头的部分)作处理,处理完毕自动进入对源程序的编译。

    作用主要包括处理宏定义(#define指令)、文件包含(#include头文件)以及条件编译(根据可能存在的#ifdef来确定程序需要执行的代码段)以及忽略注释。

2.2在Ubuntu下预处理的命令

cpp hello.c > hello.i

 图2.1 预处理命令

2.3 Hello的预处理结果解析

仅23行的源程序hello.c结果预处理变成了3000多行的hello.i,这是因为预处理后将<stdio.h>等头文件中的程序都展开并存放到了预处理后的文件里。而我们原先的程序内容出现在文件的最后部分,如下图所示。

 图2.2 预处理得到的hello程序

此外,即使是头文件中也用到了其他的头文件,同样出现了#include命令。

 图2.3 预处理的头文件

2.4 本章小结

本章介绍了在预处理过程中预处理器的概念和作用(文件包含、宏定义、去注释等),并用展示了对hello.c文件的预处理过程与预处理结果。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

编译器可以将预处理后的hello.i文件翻译成hello.s文件,它包含一个汇编语言程序。

编译是利用编译程序从源语言编写的源程序产生目标程序的过程,也指代用编译程序产生目标程序的动作——其实就是把高级语言变成计算机可以识别的二进制语言的过程。

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

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

 

图3.1 编译命令

3.3 Hello的编译结果解析

3.3.1 数据

       1. 常量

              数字常量(如hello.c中的常数3和8)存储在.text节,字符型常量(如hello.c中的“Hello”)存储在.rotate节。

 图3.2 常量

2. 变量

局部变量是存储在栈中或寄存器中的。如hello.c中的循环计数变量i。观察汇编指令结构可以发现,此处变量i是存放在栈中,cmpl就是在比较栈中元素与数字7的大小,即循环条件。

                                                                 图3.3 局部变量i

3.3.2 赋值

              在进行循环时每次都会把i+1的值赋给i,如下图add所示。

 

图3.4 对变量i赋值

3.3.3 算术操作

              在上图3.4中对变量i进行算术操作+1,用于循环的正常运行。

3.3.4 关系操作

下图的cmpl就是典型的比较操作,判断循环变量i与7的大小,若相等则跳出循环。

 

图3.5 判断i==8

              另一处是判断变量argc是否与常数4相等。

 

图3.6 判断argc==4

3.3.5 数组操作

对Argv数组的调用如下图所示,依次处理存在栈中的数组元素,将其输出。

 

图3.7 调用数组argv

3.3.6 函数操作

Main函数:

Main函数的参数是整型变量argc和字符型数组argv,分别存在寄存器rdi和栈中,最终返回值是0.

 

图3.8 main函数

              Printf()函数:

       Printf()函数第一次是在判断argc是否等于4时调用,输出字符串;第二次是在循环中调用,输出字符串和数组元素。

 

图3.9 printf函数

              Sleep()函数:

       Sleep()函数把字符数组argv的元素转为整数后作为参数,然后暂停相应时间。

 

图3.9 sleep函数

              Exit()函数:

              Exit()函数以1为参数,当判断argc=4后执行,退出程序。

 

图3.10 exit函数

3.4 本章小结

本章介绍了编译器的概念和作用。编译器将预处理后的程序转换为汇编程序的过程,同时解析了源代码中的大部分操作对应的汇编指令。简单来讲,编译器做的就是在经过词义分析等阶段之后将其转换为汇编代码,是变成机器代码的中间过程。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

       用汇编语言编写的程序不能被直接识别,要由一种程序将汇编语言翻译成机器语言,这种起翻译作用就是汇编器要实现的功能。

汇编器将hello.s转换成二进制可重定位目标文件hello.o,把汇编指令转换成机器代码(二进制码),可以被计算机直接识别。

4.2 在Ubuntu下汇编的命令

as hello.s -o hello.o

 

图4.1 汇编命令

4.3 可重定位目标elf格式

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

4.3.1 命令

readelf -a hello.o > ./elf.txt

 

图4.2 生成elf格式

4.3.2 ELF头

ELF头以16字节序列(magic数)开始,用于确认文件类型,它描述了系统的字大小与字节顺序。ELF头剩下的部分包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

 

图4.3 ELF头

4.3.3 节头部表

描述不同节的位置和大小,目标文件的每个节都有一个固定大小的条目。

 

图4.4 节头部表

4.3.4 重定位节

重定位节.rela.text中包含了在代码中使用的一些外部变量等信息,在链接的时候需要根据重定位节的信息对这些变量符号进行修改。在下图可以看到8个重定位节对应的符号名称。

 

图4.5 重定位节

4.3.5 符号表

符号表.symtab存放在程序中定义和引用的函数和全局变量的信息,例如函数名main、printf等。

 

图4.6 符号表

4.4 Hello.o的结果解析

以下格式自行编排,编辑时删除

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

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.4.1 命令

objdump -d -r hello.o > Disas_hello.s

图4.7 反汇编命令

 

4.4.2 反汇编分析

对比反汇编结果和汇编指令hello.s后,可以发现以下显著差别。

一.反汇编中立即数的表示是0x十六进制,而hello.s中是十进制。

二.反汇编中条件跳转指令(如je)后面跟着的是地址偏移量(如je 0x2f),而hello.s中则是跳转到块L1、L2等。这是因为汇编后每一行指令都已经被分配了地址,所以反汇编的结果也以地址作为跳转的目标。而编译时没有分配地址,所以只能以L1、L2这样的形式表示分支跳转。

三.反汇编的函数调用也是以地址偏移量的形式,而hello.s是以函数名的形式调用。并且因为还没有进行链接,所以函数调用语句的相对地址都是0.

 

图4.8 反汇编部分结果

      

图4.9 hello.s部分结果

 

4.5 本章小结

经过预处理和编译后的程序,在编译器的作用下变成二进制文件,成为可供计算机直接识别的机器语言。这为下一步的链接操作做好了准备。通过对比分析反汇编后的代码和正常编译得到的代码,可以一定程度感受到汇编的作用。

(第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.1 链接命令

 

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

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

5.3.1 命令

readelf -a hello > hello1.elf

 

图5.2 ELF格式命令

5.3.2 ELF头

ELF头以16字节序列(magic数)开始,用于确认文件类型,它描述了系统的字大小与字节顺序。ELF头剩下的部分包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

 

图5.2 ELF头

5.3.3 节头

描述不同节的位置和大小,目标文件的每个节都有一个固定大小的条目。其中.interp用于保存程序执行前需要调用的解释器;note记录了一些辅助信息; gnu_stack使用系统栈所需要的权限信息,等等。

 

图5.3 节头

5.4 hello的虚拟地址空间

用edb打开hello后,可以在datadump中看到虚拟地址是从401000开始的。打开symbols窗口后,可以看到不同符号的地址信息。例如符号main的起始地址是401125。在之前的EFL头文件中,我们可以看到起始地址为4010f0,对应着符号表里的start,标志着程序的开始。

 

图5.4 edb查看符号

5.5 链接的重定位过程分析

以下格式自行编排,编辑时删除

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

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

5.5.1 命令

objdump -d -r hello >hello.out

 

图5.5 重定位

5.5.2 分析

得到的hello.out与之前的hello.o相比有着不少区别。

首先最明显的一点,也是在hello.o中提到的一点,就是程序的地址问题。原本在hello.o中因为没有进行链接,相对地址都是0.而在hello.out中则是确定的虚拟地址。

在hello.o中call后面跟的是地址偏移量,比如<main + 0x2f>,而hello.out中是直接寻址方式,直接给出虚拟地址,如*rax.

此外,hello.out中多了一些hello.o没有的东西,比如<_init>,<.plt>等段。

 

图5.6 hello.out

5.6 hello的执行流程

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

根据反汇编代码,运行调试后可以看出执行函数及虚拟内存地址如下:

401000 <_init>

401020 <.plt>

401030 puts@plt

401040 printf@plt

401050 getchar@plt

401060 atoi@plt

401070 exit@plt

401080 sleep@plt

4010f0 <_start>

401120 <_dl_relocate_static_pie>

401125<main>

4011c0 <__libc_csu_init>

401230 <__libc_csu_fini>

401238 <_fini>

5.7 Hello的动态链接分析

   (以下格式自行编排,编辑时删除

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

计算机通过GOT和过程链接表PLT的协作来解析函数的地址。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。

在之前得到的elf文件中,可以查找到got.plt的地址,并在edb里查看。

 

图5.7 .got.elf地址

可以看到,未调用dl_init前,该地址处的内容都是0.而调用之后,动态链接器会重定位GOT中的每个条目,使其出现变化。

 

图5.8 调用前

 

图5.9 调用后

5.8 本章小结

本章介绍了链接的作用,将hello.o链接成可执行文件hello。通过对hello的ELF结构与链接重定位过程的分析,了解到链接后程序的变化以及动态链接的特点。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

进程(Process)是指计算机中已运行的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。进程是程序真正运行的实例,若干进程可能与同一个程序相关,且每个进程皆可以同步或异步的方式独立运行。

在现代系统上运行一个程序时,我们会得到一个假象,好像我们的程序是系统中唯一运行的程序一样。我们的程序好像独占处理器和内存。处理器好像无间断地一条接一条执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。这些假象是通过进程的概念提供的。进程提供给应用程序的关键抽象:

1)一个独立的逻辑控制流,提供一个程序独占处理器的假象。

2)一个私有的地址空间,提供一个程序独占地使用内存系统的假象。

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

Shell是一个交互型应用级程序,能够读取来自用户输入的命令行,并对输入进行解析求值,运行相应的程序。Shell的处理流程如下:

  1. 读取从键盘输入的命令
  2. 判断命令是否正确,且将命令行的参数改造为系统调用execve() 内部处理所要求的形式
  3. 终端进程调用fork() 来创建子进程,自身则用系统调用wait() 来等待子进程完成
  4. 当子进程运行时,它调用execve() 根据命令的名字指定的文件到目录中查找可行性文件,调入内存并执行这个命令
  5. 如果命令行末尾有后台命令符号& 终端进程不执行等待系统调用,而是立即发提示符,让用户输入下一条命令;如果命令末尾没有& 则终端进程要一直等待。当子进程完成处理后,向父进程报告,此时终端进程被唤醒,做完必要的判别工作后,再发提示符,让用户输入新命令。[1]

6.3 Hello的fork进程创建过程

父进程通过调用fork函数创建一个新的运行的子进程。调用fork函数后,新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。fork被调用一次,却返回两次,子进程返回0,父进程返回子进程的PID。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。[2]

6.4 Hello的execve过程

exceve函数在当前进程的上下文中加载并运行可执行目标文件,并带参数列表和环境变量列表。只有当出现错误时,exceve才会返回到调用程序。所以,与fork一次调用返回两次不同,在exceve调用一次并从不返回。

当加载可执行目标文件后,exceve调用启动代码,启动代码设置栈,将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。[3]

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

在用户模式下执行第一个输出,然后调用sleep函数,同时进入内核模式。

内核主动释放进程,把hello从运行进程中暂时移出,并进行sleep计时,此时cpu控制切换上下文执行其他程序。计时结束后内核重新把hello移入运行序列,形成逻辑控制流。

在整个运行过程中,cpu会不断切换上下文,使运行过程切分成时间片,最大程度提高效率,完成调度。

 

图6.1 进程切换

6.6 hello的异常与信号处理

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.6.1 异常的种类和处理

异常可分为以下四种类型。

中断:来自I/O设备的信号,执行下一条指令

陷阱:有意的异常,执行下一条指令。

故障:可能修复的错误情况,如果能修正则返回命令重新执行,否则终止。

终止:不可恢复的错误,终止程序。

6.6.2 hello运行中的异常

       首先尝试乱按(非ctrl+c和ctrl+z),发现并不会影响程序正常运行。

 

图6.2 运行hello时乱按

如果输入ctrl+c,程序会收到SIGINT信号,使进程直接终止。

 

图6.3 运行hello时输入ctrl+c

如果输入ctrl+z,此时父进程会接收到信号SIGSTP并运行信号处理程序,使得进程暂时挂起。接下来我们可以用一系列命令查看挂起进程的信息。

 

图6.4 运行hello时输入ctrl+z

       输入命令ps,查看正在running的进程,可以看到被挂起的hello,以及各个进程的pid信息。

 

图6.5输入ctrl+z后用ps查看进程

       输入命令jobs,可以查看当前的作业列表。

 

图6.6输入ctrl+z后用jobs查看作业

      

输入命令pstree,将所有进程以树状图显示,这里只截取一部分。

 

图6.7输入ctrl+z后用pstree查看进程树

       输入命令fg可以将停止的进程hello重新调到前台运行,打印剩余的信息。

 

图6.8输入ctrl+z后用fg把hello继续执行

重新执行hello,并用ctrl+z停止。先输入ps查找hello的pid为3253,然后使用kill命令杀死进程。再次使用ps命令查看时,发现hello进程已经结束。

 

图6.9输入ctrl+z后用kill把hello杀死

6.7本章小结

本章主要了解进程的作用以及交互级应用型程序shell。不仅熟悉了进程的创建和执行,同时以可执行文件hello为例,进一步了解异常、信号处理和各种命令的使用。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

逻辑地址:逻辑地址是指在计算机体系结构中是指应用程序角度看到的地址,通过地址翻译器(address translator)或映射函数可以把逻辑地址转化为物理地址。通常是以 “段地址:偏移地址”的形式。例如hello反汇编代码中出现的相对寻址,就是在基地址基础上加上偏移地址的方式找到真正的地址。

线性地址: 线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。也就是hello反汇编中逻辑地址计算出的结果地址。

虚拟地址:与线性地址含义基本一致。虚拟地址又叫虚拟内存,是内存管理的一种方式, 它在磁盘上划分出一块空间由操作系统管理,当物理内存耗尽是充当物理内存来使用。它将多个物理内存碎片和部分磁盘空间重定义为连续的地址空间,以此让程序认为自己拥有连续可用的内存。

物理地址:物理地址在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址,又叫实际地址或绝对地址。虚拟地址经过地址翻译可以转为真正的物理地址。

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

逻辑地址由段标识符和相对位置偏移两部分组成。

段标识符是一个16位长的字段,称为段选择符。其中前13位是索引号,后三位表示是代码段寄存器还是数据段寄存器还是栈寄存器。可以通过索引号在段描述表中找到一个具体的段描述符,描述了一个段。

给出逻辑地址后,通过段选择符中的T1字段确定是全局段描述表还是局部段描述表,之后通过索引号找到具体的段描述符,得到其基地址,再加上相对位置偏移,完成了从逻辑地址到线性地址的变换,即完成了段式管理。

 

图7.1段选择符

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

(线性地址)虚拟地址VP和物理地址PP都由页号和偏移量组成,其中虚拟偏移量等于物理偏移量上。页表是条目由有效位和物理页号组成,当虚拟页号转为物理页号时,通过有效位判断虚拟地址是否缓存在物理内存中。若虚拟地址已缓存,则直接将相应页表条目中的物理页号和虚拟偏移量串联就得到了物理地址。

 

图7.2 基于页表的地址翻译

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

在将VA转为PA的过程中,如果PTE缓存在L1中,会带来一些不必要的开销和数据驱逐的风险。然而,为了消除这样的开销,计算机系统在MMU中包括了一个关于PTE的小的缓存——翻译后备缓存器(TLB)。

虚拟地址VA由虚拟页号VPN和虚拟页偏移VPO组成。若TLB命中,操作与7.3中相同,否则 VPN被划分为四级,每级被用作到一个页表的偏移量。通过寄存器确定第一级页表的起始地址,再通过第一片虚拟页号确定出在第一级页表中的偏移量,查找到页表条目。如果在物理内存中,开始确定第二级页表,以此类推,在第四级页表中查找到物理页号,再与虚拟偏移量组合成物理地址。

 

图7.3 基于TLB和k级页表的地址翻译

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

MMU将物理地址发给L1缓存,缓存从物理地址中取出缓存偏移CO、缓存组索引CI以及缓存标记CT。

若CI所指示的组有与CT匹配且有效的条目,则为一个命中条目,读出在偏移量CO处的数据字节,并把它返回给MMU,随最后传递给CPU。否则,如果不命中,则在下一级cache或是内存中寻找需要的内容,储存到上一级cache后再一次请求读取。

 

图7.4 三级cache下的物理内存访问

7.6 hello进程fork时的内存映射

当fork被调用后,内核为新进程创建数据结构。为了给新进程创建虚拟内存,系统创建了当前进程的mm_struct、区域结构(vm_area_struct)和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有写时复制。

当fork从新进程返回,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

execve加载后,新的hello程序取代原先的程序,同时删除当前虚拟地址中的区域结构。

然后映射私有区域(私有、写时复制),为新程序的代码、数据、堆栈创建新的区域结构。

再映射共享区域,与动态链接到这个程序的共享程序链接,映射到虚拟地址的共享区域中。最后设置PC,使其指向代码入口处。

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

缺页故障:引用一个虚拟地址,在MMU中查找页表时发现该对应的物理地址不在物理内存中,从而引发的故障。

缺页处理:缺页处理程序在物理内存中选择牺牲页,如果这个页已经被修改了,则把它换到磁盘。然后调入新的页面,并更新内存中的PTE,并返回到原来的进程,再次执行导致缺页的命令。CPU将引起缺页的虚拟地址重新发送给MMU。因为虚拟页面已经换存在物理内存中,所以就会命中。

 

图7.5 缺页故障与处理

7.9动态存储分配管理

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

动态储存分配管理通过动态内存分配器实现。动态内存分配器维护一个进程的虚拟区域——堆。堆是一个不同大小块的集合,块可分为已分配和未分配两种。未分配的块会保持状态直到被分配,已分配的块会供程序使用直到被释放。动态内存的分配通常可以分为显式空闲链表管理和隐式空闲链表管理两种。

显式空闲链表是将空闲块组织为某种形式的显式数据结构。根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。例如堆可以组织成一个双向链表,在每个空闲块中,都包含一个前驱与一个后继指针。放置策略与上述放置策略一致。

隐式空闲链表管理:带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额外填充以及一个字的尾部组成的。当一个应用请求一个k字节的块时,分配器在空闲链表中寻找一个符合大小的空闲块来放置它。分配器有三种放置策略:首次适配、下一次适配与最佳适配。在释放一个已分配块的时候需要考虑是否能与前后空闲块合并,减少系统中碎片的出现。[4]

 

图7.6 隐式空闲链表

7.10本章小结

本章主要介绍hello的存储管理,首先讨论了不同类型地址的区别与转换关系,如逻辑地址到线性地址、VA到PA等。在此基础上分析了fork和exceve的工作机制,最后进一步了解虚拟内存的缺页处理和动态存储分配。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件。所有的I/O设备都被模型化为文件,甚至内核也被映射为文件。这样所有的输入和输出信息都可以通过文件的读写来实现。

设备管理:unix io接口。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O。包括文件的打开和关闭、文件读写以及改变文件的位置。

8.2 简述Unix IO接口及其函数

Unix IO 接口及函数:

  1. 打开文件

打开文件是通知内核准备好访问该文件,同时返回一个小的描述符数字---- 文件描述符。返回的描述符总是在进程中当前没有打开的最小描述符。

函数——int open(char *filename, int flags, mode_t mode)

将filename转换为一个文件描述符,并且返回描述符数字,。参数Flags指明了进程打算如何访问这个文件。Mode参数指定了新文件的访问权限位。

  1. 关闭文件

关闭文件是通知内核你要结束访问一个文件,内核会删除期间创建的数据结构,并恢复描述符。关闭一个已经关闭的文件是导致线程程序灾难的一个因素。

函数——int close(int fd)

关闭fd文件,返回操作结果。成功则返回0,否则返回-1.

  1. 读写文件

写文件从内存复制字节到当前文件位置,然后更新文件位置;写文件从内存复制字节到当前文件位置,然后更新文件位置。返回值表示的是实际传送的字节数量。

函数——ssize_t read(int fd,void *buf,size_t n)

               ssize_t write(int fd, const void *buf, size_t n)

read从当前描述符为fd文件位置复制n个字节到内存位置,然后更新文件位置。返回值表示的是实际传送的字节数量。Write与之类似,只是从读变为写。

8.3 printf的实现分析

[转]printf 函数实现的深入剖析 - Pianistx - 博客园

 

图8.1 printf

      Printf函数的第一个参数是字符型指针fmt,后面是不定长的参数。在其内部又调用了函数vsprintf与write。其中vsprintf的作用就是格式化,用于接受确定输出格式的格式字符串fmt,对个数变化的参数进行格式化。而write函数会调用syscall函数,显示格式化了的字符串。

如此,从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等. 接着调用字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)

最后显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

 

图8.2 write

8.4 getchar的实现分析

Getchar函数读取输入,并返回字符的ascii码值。在读取结束或者失败的时候,会返回EOF(-1).

当用户从键盘进行输入时,系统就会进行异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。尔后getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章介绍了IO设备管理,尤其是Unix下的IO接口及其函数。并针对具体的两个函数printf与getchar进行分析。

(第81分)

结论

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。

看似简单的hello程序,实则从编写完成到最终的运行经历了颇为复杂的过程。

  1. 预处理:经过预处理器作用,hello.c文件进行头文件展开、宏定义、忽略注释等步骤,变成hello.i文件。
  2. 编译:预处理后的hello.i文件在编译器作用下,成为hello.s汇编语言程序。
  3. 汇编:hello.s经过源程序分析、代码优化等环节,被汇编器转化为二进制的可重定位目标文件hello.o。
  4. 链接:链接器将汇编后的hello.o文件与动态链接库链接,使其最终成为可执行目标文件hello。
  5. 进程与存储:在交互级应用型程序shell下输入命令行,此时shell会fork一个子进程,用于执行hello文件。然后shell接着调用execve函数,把hello程序映射到虚拟内存,并加载到物理内存运行。
  6. 输入输出:通过系统的I/O进行输入和输出,例如hello中的printf函数。这些标准I/O函数是对Unix中I/O函数的包装和调用。
  7. 回收:hello程序运行结束后,系统会接收到信号,将终止的进程清除。

跟随hello程序短暂的一生,我回顾了计算机系统的大部分内容。简单的输出背后,是预处理器、编译器、汇编器、链接器、shell程序、虚拟内存、物理内存、I/O设备等部分协同合作的结果,让人不禁感叹计算机系统的复杂和精密。

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

附件

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

Hello.i                       hello.c预处理后的文件

Hello.s                       编译后得到的文件

Hello.o                      汇编后得到的可重定位目标文件

Hello(.out)                链接后得到的可执行目标程序

Disas_hello.s             hello.o经过反汇编得到的文件

Elf.txt                        hello.o的elf格式文件

Hello1.elf                  hello的elf格式文件

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

参考文献

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

[1]  Shell执行流程详细解释_菜鸟也疯狂_的博客-CSDN博客_shell 运行进程

[2]  教材

[3]  教材

[4]  https://blog.csdn.net/qq_40365987/article/details/124394139

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值