2018CS大作业

 

摘  要

本文对“Hello”示例程序的执行全过程进行了分析和介绍,主要包括预处理、编译、汇编、链接、进程管理、存储管理和I/O管理等过程的操作和处理结果的分析,演绎了Hello从诞生到谢幕的完美生命历程。通过对此程序处理过程的状态和操作的描述以及各小节的分析,将有助于我们对计算机系统进行更加深入的理解。

 

关键词:编译;汇编;内存管理;进程管理;存储管理,I/O管理。                           

 

 

 

 

 

 

 

目  录

 

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

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

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

1.3 中间结果............................................................................................................ - 5 -

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的结果解析........................................................................................ - 14 -

4.5 本章小结.......................................................................................................... - 15 -

第5章 链接............................................................................................................. - 16 -

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

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

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

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

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

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

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

5.8 本章小结.......................................................................................................... - 19 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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的实现分析....................................................................................... - 34 -

8.5本章小结........................................................................................................... - 35 -

结论............................................................................................................................ - 35 -

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

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

 

 

 

第1章 概述

1.1 Hello简介

首先需要说明一下,P2P和O2O两个缩略语,它们分别是Program to process(P2P)和Zero-0 to Zero-0(O2O)的缩写;源程序文件hello.c为用C语言编写的 文本文件其从hello.c到生成hello的过程见图1-1。

 

 

图1-1:从hello.c到生成hello的过程

 

从程序到进程的转化P2P(From Program to Process),在编译器中写出hello.c的源代码,编译器驱动程序读取源程序文件hello.c,通过预处理生成 hello.i然后通过编译器生成汇编程序hello.s,在经过汇编器变成可重定位目标程序hello.o(二进制的文件)。最后再通过链接器与标准C库链接,最终变成一个可执行文件hello。此时在shell里面输入字符串“./hello”,shell程序便会将字符读入寄存器,再放入内存,然后shell通过调用函数创建一个新运行的子进程(为父进程shell的一个复制),子进程启动加载器。加载器删除子进程现有的虚拟内存段,创建新的内存区域,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。加载器跳转到起始地址,调用应用程序的主函数,程序从内存读取指令字节,待执行的程序被分解成几个阶段,每个阶段完成指令执行的一部分,最终成为一个Process运行在内存中,至此完成了一个P2P的转化

O2O(From Zero-0 to Zero-0)过程的实现,程序通过上面的进程执行之后将会处于一个已经终止的状态之中,直到被其父进程回收后退出,此时,shell会恢复到调用hello之前的状态再次回到zero,也就实现了一个O2O的过程。

 

1.2 环境与工具

硬件环境为:

CPU为 X64;主频为2.8 GHz;RAM为4G;硬盘为1THD。

软件环境为:

Windows8  64位和Ubuntu 16.04 LTS 64位等。

开发工具主要包括:

gcc;objdump;gdb;gredit等。

1.3 中间结果

为编写本论文,生成的中间结果文件的名字,文件的作用,见下表:

表一:中间结果列表

序号

中间结果文件名称

文件的作用

1

hello.i

预处理阶起到加载头文件、进行宏替换和条件编译的作用

2

hello.s

编译后生成的汇编语言文本文件

3

hello.o

将汇编语言翻译成机器语言指令,并打包生成可重定位目标程序

 

1.4 本章小结

       本节概述了hello.c从编写完成,到通过编译器被编译成可执行文件,再到在系统中被执行,后被回收的一个完成过程。这里通过简单的步骤展示了一个程序的普遍历程。

 

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:

预处理pre-treatment,是在编译之前进行的处理,其命令以符号“#”开头,C语言的预处理主要包括宏定义、文件包含和条件编译等三方面内容。

 

预处理的作用:

根据源代码中的预处理指令修改自己编写的源代码。预处理指令指示预处理程序修改源代码。编译程序在对程序进行编译处理之前,会自动运行预处理程序对源代码进行编译预处理,这部分工作不需要人为的参与。其作用有删除#define展开所有的宏定义、处理所有条件编译指令、处理#include预编译指令和删除所有的注释。

 

2.2在Ubuntu下预处理的命令

预处理的命令为:gcc hello.c -E -o hello.i

 

图2-1 Ubuntu下预处理命令

 

图2-2:在ubuntu下预处理的结果

 

2.3 Hello的预处理结果解析

hello.c文件转化为hello.i文件,通过递归进行的过程将头文件包含的文件被插入到该预编译指令的位置。原文件中的宏也进行了宏展开,文件的内容有所增加,不过仍为可以阅读的文本文件

2.4 本章小结

本阶段通过使用Ubuntu下的预处理指令将hello.c转换成了.i的文件。通过转化使得程序在后序的操作中不受阻碍,方可以进行下一步的汇编处理。预处理看似简单,却是一个必不可少的步骤。

 

第3章 编译

3.1 编译的概念与作用

    编译是把高级程序设计语言经过预处理之后的源程序hello.i,翻译成汇编文本格式的目标程序的翻译程序。编译程序属于计算机系统中的中间翻译程序。它的输入为预处理后的源程序hello.i,输出为汇编语言形式的目标程序hello.s。

编译程序的作用是把源程序hello.i翻译成目标程序hello.s。在其基本功能之外,编译程序还有语法检查、修改手段、调试措施、覆盖处理、不同语言合用等主要功能。

3.2 在Ubuntu下编译的命令

在Ubuntu下编译的命令是gcc hello.i –S –o hello.s(图3-1)生产的文件为hello.s文件(图3-2)

图3-1 Ubuntu下编译命令

 

图3-2 编译结果hello.s文件

3.3 Hello的编译结果解析

3.3.1数据、赋值和类型转换

在hello.c中,有一个全局变量int sleepsecs=2.5,编译器将其编译成.:

       .type      sleepsecs, @object

       .size       sleepsecs, 4

sleepsecs:

       .long      2

       .section  .rodata

 

这个过程包含了数据、赋值和类型转换的内容,可以看到编译后,sleepsecs存放在了.rodata节中,同时因为sleepsecs被定义为了int型,其赋予的初值2.5,会进行自动隐式的类型转换,转换为2(int型)存入sleepsecs。

 

3.3.2算术操作和关系操作

 

编译器将i++编译为:

addl         $1, 28(%esp);

 

 

图3-3 算数操作i++

 

将i<10编译为:

       cmpl      $9, 28(%esp)

       jle      .L4

 

 

图3-4 关系操作i<10

 

编译器将argc!=3编译为: 

       cmpl      $3, 8(%ebp)

       je    .L2

 

图3-5 关系操作argc!=3

 

3.3.3数组、指针和结构操作

 

编译器对指针和对数组的操作(printf函数中)编译的结果见图3-6

 

 

图3-6数组、指针和结构操作

 

3.3.4控制转移

 

将if,for等控制转移语句通过编译器编译后,用cmp进行后,使用了条件跳转指令进行跳转。

编译器将if(argc!=3)编译为:

 

       cmpl      $3, 8(%ebp)

       je       .L2

 

图3-5

 

编译器将for循环当中的比较和转移编译为:

 

       cmpl      $9, 28(%esp)

       jle      .L4

 

图3-4

 3.3.5函数操作

 

编译器将printf("Hello %s %s\n",argv[1],argv[2]);编译的结果见图3-7,调用printf过程里,对数组(argv[1]和argv[2])进行了访问。

 

 

3-7函数操作编译printf("Hello %s %s\n",argv[1],argv[2])

 

编译器将sleep(sleepsecs);编译的结果见图3-8,在这里取了sleepsecs这个全局变量,调用了sleep函数  。

 

 

3-8函数操作编译sleep(sleepsecs)

 

3.4 本章小结

编译阶段通过编译器将高级语言(hello.i)编译成了汇编语言这种直接面向处理器的程序设计语言(hello.s)。可直接对寄存器和存储器进行操作,使执行速度更快。本章进行程序代码进行比较,并对汇编代码进行了解析。

 

第4章 汇编

4.1 汇编的概念与作用

概念:

汇编是将编译后的文件hello.s 翻译成机器语言指令二进制程序的过程,其指令打包成relocatable object program (可重定位目标程序) 的格式,保存在目标文件hello.o 中。

 

作用:

汇编器能够将汇编代码转变为机器可以执行的命令,通常每一个汇编语句对应一条机器指令。向汇编器输入用汇编语言书写的源程序,可输出用机器语言表示的目标程序。汇编较之编译过程要简单一些,指令大体上保持一个对应的关系,即可根据对照表一一对应进行翻译。

 

4.2 在Ubuntu下汇编的命令

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

图4-1 Ubuntu下汇编命令

4.3 可重定位目标elf格式

用“adelf -h hello.o”指令可查看信息结果见图4-2,在文件头信息中可知,本文件是可重定位目标文件,所以每个节都从0开始,用于重定位。

 

图4-2 hello.o的文件头信息

 

用readelf -S hello.o指令可查看节头表,结果见图4-3。可以看到各节的大小和相关操作,节头表中的字节偏移信息可以展示各节在文件中的起始位置。

 

图4-3 hello.o的节头表

 

重定位节.rela.text 中的位置的列表(图4-4),“信息”的前四位为在symbol中的偏移量,后四位为在type中的偏移量。列表中包含了.text节需要重定位的信息,当通过链接器将其和另外的文件组合时,需要对第一个printf中的字符串(.L0)、puts函数、exit函数、第二个printf中的字符串(.L1)、printf函数、sleepsecs等“符号名称”对应的八项内容进行重定位声明。

 

图4-4重定义节信息

 

4.4 Hello.o的结果解析

 

机器语言是制数据表示的语言,可以称之为二进制的机器指令序列集合 ,由操作码和操作数组成,是0和1的指令代码,直观性差,易出错。汇编语言的主体是汇编指令,是更接近CPU运行原理的且比较容易理解的语言可以说是机器指令便于记忆的书写格式。而机器指令在不同平台之间是不能直接移植的,也就是说不同型号计算机之间的机器语言是不相通的,汇编语言在不同的设备上对应着不同的机器语言指令集合。

hello.o反汇编和hello.s的比较:

用objdump -d -r hello.o可查看hello.o的反汇编。

图4-2 hello.o反汇编

通过对比两个文件,可以看到汇编器在汇编hello.s时,每条语句都加上了具体的地址,同时全局变量和常量也都安排在了具体的地址当中,操作数也由hello.s文件中的十进制数,变成了hello.o文件中的十六进制数。另外函数名称被函数相对偏移地址替换了,跳转语句也由原来对应的符号变成了相对偏移地址。

4.5 本章小结

在此阶段完成用了汇编器将汇编语言到机器语言的转化过程,运用Ubuntu下的汇编指令将hello.s转换为helllo.o可重定位目标文件。本章通过将helllo.o文件反汇编结果与hello.s汇编程序代码进行比较,分析出了机器语言虽然灵活、可直接执行和速度快,但编程序时需要处理每条指令、数据的存储分配和输入输出,编程人员甚至需要在编程过程中记忆每步使用工作单元的状态。可以说除了生产计算机企业的专业人员外,绝大多数的程序员已不再使用机器语言了。不过我们对这一最基础的语言稍作了解,有助于我们更加理解计算机系统的执行方式,提高编程质量。
第5章 链接

5.1 链接的概念与作用

链接的概念:

链接是将各种代码和数据片段组合成一个文件的过程,主要包括符号解析和重定位两个步骤。在符号解析步骤中,链接器将符号引用与一个明确的符号定义进行关联,并将多个单独的代码、数据节合并为一个节。同时对符号在 hello.o文件的相对位置进行重新定位到可执行文件在内存中的绝对位置,并更新所有引用反映出这些符号的新位置。

链接的作用:

由于目标代码不能直接执行,为使目标代码成为可执行的程序,需要通过链接操作生成可执行程序(hello)。链接操作最重要的作用是将函数库中相对应的各代码组合到目标文件中。链接器使分离编译成为了可能。链接程序就是将这些分别在不同的目标文件中编译或汇编的代码收集汇总到一个可直接执行的文件中,同时链接程序还连接目标程序和使用的标准库函数的代码,连接目标程序和由计算机的操作系统提供的存储分配程序和输入/输出设备等资源。

5.2 在Ubuntu下链接的命令

用gcc -o hello hello.o链接命令。

图5-1 Ubuntu下链接指令

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

在ELF格式文件中,对readelf中节头部分进行分析(图5-2):

hello中包括大小、在程序中的偏移量的所有的节信息在节头中都进行了声明,所以根据节头中这些信息我们可以用HexEdit确定各个节起始位置(程序被载入到虚拟地址的起始地址)和大小,进一步确定其所占的区间。

图5-2 ELF格式文件中 hello节头信息

5.4 hello的虚拟地址空间

使用edb加载hello,查看此进程的虚拟地址空间信息,与5.3中的图5-2对照分析说能够看到各节的二进制信息,代码段开始于0x400550处(图5-3)。

 

 

图5-3使用edb查看信息

 

5.5 链接的重定位过程分析

hello相对于hello.o不同如下:

 

  1. hello.o中的相对偏移地址、跳转以及函数调用的地址都变成了hello中虚拟内存地址;
  2. hello同hello.o相比增加了外部链接函数。

 

重定位的过程:

链接器在完成符号解析后把代码中的每个符号引用和符号定义关联起来。根据输入目标模块中的代码节、数据节的大小进行重定位,合并输入模块,为各符号分配运行时的地址。首先重定位节和符号定义,链接器将输入到hello中相同类型的节合并为一个新的聚合节。接下来链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节和符号,至此程序中的每条指令和全局变量的运行时内存地址就唯一了。下一步是重定位节中的符号引用,即修改hello中的代码节和数据节中对各符号的引用,使其指向正确的运行地址,完成重定位。

 

图5-4 反汇编结果

5.6 hello的执行流程

使用edb执行hello,从加载hello到_start,到call main,以及程序终止的所有过对应的程序名称如下:

在main函数之前执行的程序是:_start、 __libc_start_main@plt、 __libc_csu_init、 _init、 frame_dummy和 register_tm_clones。

在main函数之后执行的程序是:exit、 cxa_thread_atexit_impl和 fini。

图5-5 执行流程

5.7 Hello的动态链接分析

 动态链接不同于像静态链接把所有程序模块都链接成一个的可执行文件的思路,它是把程序按模块拆分成各自相对独立部分,在程序运行时通过连接过程链接在一起形成一个程序,动态链接是在程序运行时进行连接的,但是在形成可执行文件时,也用到动态链接库。如果程序引用了外部函数,在形成可执行文件时会检查如果这个函数名是一个动态链接符号,可执行程序不对这个符号进行重定位这个过程是在执行程序时完成的,所以每次运行程序,ld的地址都是不同的。

在调用dl_init之前,0x601008后的16个字节均为0:

图5-6 初始前内容

调用dl_init之后,动态链接器在解析函数地址时会使用的信息发生改变。

图5-7 初始后内容

 

5.8 本章小结

在本章中主要实现了对hello.o的链接,生成了可执行文件。明确了链接的概念与作用,分析了hello的虚拟地址和动态链接过程,有助于链接过程中的具体细节的理解。

 

第6章 hello进程管理

6.1 进程的概念与作用

进程的概念:进程(Process)是系统进行资源分配和调度的基本单位,是计算机程序关于某数据集合的一次运行活动,它有自己的地址空间,通常包括文本区域、数据区域和堆栈等

进程的作用:进程是操作系统结构的基础,进程申请和拥有系统的资源,是一个动态的、活动的实体。进程为用户提供了我的程序是系统中当前运行的唯一程序的体验,程序好像独占各种资源。。特别是对于早期面向进程设计的计算机,进程是程序的基本执行实体;而当代的面向线程设计的计算机,进程是线程的容器。

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

壳Shell-bash的作用:Shell是一个应用级程序,提供了一个交互型的界面,用户通过它访问操作系统内核的服务,它代表用户运行其他程序,可以保证操作系统的安全性,。

处理流程:shell 执行一系列的读、判断求值的步骤,读取来自用户的一个命令行。求值解析命令行,将命令翻译给内核并将内核处理结果翻译给用户,并代表用户运行程序。

具体流程见图6-1,shell接收命令,切分字符串得到参数,求值解析命令是否是内部命令,如果是分解传给Linux 内核执行;如果不是,继续解析是否为应用程序(包括系统本身的程序和应用软件),如果找到应用程序分解后传给Linux 内核执行,如果不是或搜索不到则显示错误信息。

 

   

 

6-1 Shell-bash处理流程

 

6.3 Hello的fork进程创建过程

在终端Terminal中键入 ./hello,shell通过解析确认其为应用程序,之后终端通过调用fork 函数,创建一个子进程,子进程得到一份虚拟地址空间副本(与父进程虚拟地址空间相同,但是独立的),除了拥有不同的PID外(子进程fork函数返回0,父进程返回新创建子进程的进程ID),相当于克隆了一个自己,父进程与子进程是并发运行的两个独立的进程,内核能以任意方式交替执行其逻辑控制流的指令。但在子进程执行期间,父进程默认等待子进程的完成。

6.4 Hello的execve过程

    当调用fork函数之后,子进程调用execve函数进行命令行参数传入,execve仅进行一次系统调用从不返回(与fork 一次调用返回两次不同),execve在当前进程中加载并运行hello程序,加载器删除子进程现有的虚拟内存段,创建一组新的代码、数据、堆和栈段,并初始化为零,通过将虚拟地址空间将新创建的代码和数据段初始化为可执行文件中的内容,接下来加载器设置PC指向_start地址调用hello中的main函数。execve 函数加载并运行可执行目标文件当出现错误时,返回到调用程序。

6.5 Hello的进程执行

    加载器设置PC指向_start地址,由_start调用hello中的main函数。通过逻辑控制流,在不同的进程间切换即内核挂起正在CPU上执行的进程,并恢复之前挂程序起的某个进程的执行,分配给某个进程的时间是进程的时间片。用户态和核心态之间需要某些条件进行切换。核心态中程序可以执行一些特权功能,用户态中则不允许。新启动一个被抢夺的进程的条件是上下文信息。

进程上下文切换的步骤,首先是确定否作上下文切换(系统设置有适当的时机检查和分析是否可作上下文切换)。作上下文切换则,保存当前执行进程的上下文(指调用上下文切换程序之前的执行进程)。确保上下文切换程序不破坏原进程的上下文结构。再通过进程调度算法,选择一个处于就绪状态的进程,并恢复或装配所选进程的上下文,最后将CPU控制权交到所选进程手中。

6.6 hello的异常与信号处理

Hello在执行的过程中,可能会出现异常,这些异常可能是处理器或外部I/O设备引起的,这一类称为外部异常,常见的有时钟中断、外部设备的I/O中断等;也可能是执行指令导致的陷阱、故障和终止,这一类称为同步异常。

再说明一下陷阱、故障和终止这三个概念,陷阱是指有意的执行指令的结果;故障是指非有意的可能被修复的结果;终止是指非故意的不可修复的致命错误。

在发生异常时会产生信号。常见信号种类如下表所示:

序号

ID

名称

默认行为

相应事件

1

2

SIGINT

终止

键盘中断

2

9

SIGKILL

终止

杀死程序

3

11

SIGSEGV

终止

无效内存引用

4

14

SIGALRM

终止

alarm函数定时器信号

5

17

SIGCHLD

忽略

一个子进程停止(终止)

    在程序运行过程中,向当前进程发送一个SIGINT键盘中断信号,需要键入Ctrl-C使当前进程中断,如图6-1所示。接下来输入回车键盘中断信号将会被忽略,如图6-2所示。

图6-1 输入Ctrl-C的运行结果

图6-2 输入回车的运行结果

如果在程序运行过程中输入Ctrl-Z,将会向前台进程组中的进程会发送一个 SIGTSTP 信号,将其挂起,如图6-3所示

图6-3 输入Ctrl-Z的运行结果

Ctrl-z后运行ps查看进程及其运行时间,结果如图6-4所示。

图6-4 输入ps查看进程及其运行时间

运行jobs查看当前暂停的进程,结果如图6-5所示

图6-5 输入jobs查看当前暂停的进程

运行fg使进程在前台执行,结果如图6-6所示。

图6-6 输入fg使进程在前台执行

使用kill杀死特定进程,如图6-7所示。

图6-7 使用kill杀死特定进程

6.7本章小结

在本章通过对进程的定义与作用阐述,和Shell的一般处理流程的分析、创建进程、进程执行和异常与信号处理等过程,进一步明确了系统、程序、进程之间的关系。
第7章 hello的存储管理

7.1 hello的存储器地址空间

在计算机中物理地址用于内存芯片级的单元寻址,它是与CPU、处理器连接的地址总线相互对应。

目前使用的操作系统都提供了虚拟内存,它是一种内存管理的抽像。虚拟内存中的地址被进程使用,那就是虚拟地址,虚拟地址转换成真正的物理地址是需要操作系统协助相关硬件来转换完成。在本文中hello.s用的就是虚拟空间的虚拟地址。

线性地址是虚拟地址到物理地址变换的中间层,也是处理器可寻址的内存空间中的地址。程序代码在运行中会产生逻辑地址,也就是段中的偏移地址,段基址与偏移地址相加就成了线性地址。如果分页机制被采用,那么物理地址是需要线性地址再经过变换才能产生。如果分页机制没有被采用,那线性地址与物理地址就是相同。在机器语言指令中,用来指定一条指令或者一个操作数的地址是逻辑地址。它是把段式内存管理方式保存下来的产物,是Intel为了更好的兼容的结果。

线性地址是通过逻辑地址经过分段转化的(查询段表)。线性地址经过分页转化(查询页表)为物理地址。

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

一个逻辑地址的组成:段标识符和段内偏移量。段标识符是由16位字段组成,前13位代表索引号。后面3位包含一些硬件细节(如图7-1):

图7-1段标识符

    段描述符具体地址描述了一个段,很多个段描述符,就构成了一个数组,这个数组叫“段描述符表”, 那么就可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。每一个段描述符由8个字节组成[1],如下图7-2所示:

图7-2段描述符

在段描述符中的Base字段用来描述了该段的起始位置的线性地址。一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,就放在“局部段描述符表(LDT)”中。GDT和LDT的使用主要由段选择符中的T1字段表示的,0代表用GDT,1代表用LDT。具体的使用步骤是:

给定一个完整的逻辑地址,[段选择符:段内偏移地址]。

看段选择符中T1的取值,如果是0,则要转换是GDT中的段,如果是1,则要转换的是LDT中的段,再根据相应寄存器,得到寄存器地址和大小。就获得了一个数组了。

在这个数组根据段选择符中前13位,查找到对应的段描述符,得到了Base,即基地址。

把基址和偏移地址相加,就获得了要转换的线性地址。

 

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

    页式内存管理单元的作用是负责把一个线性地址翻译称为与之对应的一个物理地址。为了提高管理的效率,把线性地址分成固定长度单位的组,称之为页(page),例如32位的计算机,线性地址最大空间为4G,那么用4KB为一个页将线性地址进行划分,4G的线性地址就被划分为一个大数组,包含有2的20次方页。我们称这个大数组为页目录。页目录中的每一个目录项,就是一个与地址——对应的页的地址。还有一种“页”,称为物理页,或者称为页框、页桢。物理页的长度一般是与内存页一一对应的。

 

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

    Core i7 MMU怎样使用四级页表将虚拟地址翻译成物理地址,图7-3给出了其工作机制。把36位的V*N分成4个9位的片,其中的每一片对应一个页表的偏移量。L1页表的物理地址存放在CR3寄存器中。V*N1存储一个到L1PTE的偏移量,这个PTE包含L2页表的基地址。v*n2提供一个到L2PTE的偏移量,依次类推。

    通过四级页表,查找到对应的PPN,与VPO组合成PA,也可以说VPO直接对应了PPO,按照这种机制,形成PA并且向TLB添加相应的条目。

图7-3四级页表工作机制

注:图中PT:页表,PTE:页表条目,VPN:虚拟页号,VPO:虚拟页偏移,PPN:物理页号,PPO:物理页偏移量。

 

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

    CPU发出一个虚拟地址直接在TLB里搜索,假如搜索成功,直接发送到L1cache里,假如没有搜索到,那就先在页表里找到,然后再发送过去,到了L1中,工作机制还是类似,寻找物理地址又要检测是否搜索成功,如果没有搜索成功,就向L2/L3中查找。这时CPU高速缓存就会发挥作用,CPU按照这种工作机制再与TLB搭配,使机器在翻译地址时,性能得到充分发挥。

7-4三级cache访存a

7-5三级cache访存b

7.6 hello进程fork时的内存映射

    当shell调用fork 函数时,内核就会给hello进程创建各种需要的数据结构,并给它分配一个唯一的PID 。由于需要给hello进程创建虚拟内存,fork函数创建hello进程的mm_struct 、区域结构和页表的原样副本。两个进程中的每个页面都被标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork函数在hello进程中返回时,新进程拥有和调用fork进程相同的虚拟内存,之后的写操作采用写时复制机制创建新页面。

7.7 hello进程execve时的内存映射

execve函数在当前进程中成功加载并运行存在于可执行目标文件hello中的程序,用hello程序替代了当前程序,hello程序加载并运行需要以下几个过程。

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

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

3.映射共享区域。如果hello程序与标准C库libc.so等链接,那这些对象都是动态链接到该程序上的,之后再映射到虚拟空间中的共享区域中。

4.设置程序计数器。Execve最后需要做的就是设置当前进程的程序计数器,使之指向代码区域的入口。

 

7-6 execve加载

 

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

    DRAM缓存不命中称为缺页,也就是指虚拟内存中的字不在物理内存中情况,如果缺页情况发生,会导致页面出错,产生缺页异常。缺页异常处理的方法是选择一个牺牲页,之后再将目标页加载到物理内存中。然后将产生缺页的指令重启,页面命中。

7.9动态存储分配管理

  有些程序需要的空间大小在程序运行时才能知道,为了满足这种需求,需要动态分配空间[2]。程序员使用动态内存分配器(例如malloc)帮助运行的程序获得虚拟内存。动态内存分配器管理一个进程的虚拟内存区域,称之为堆。分配器将堆看作一组不同大小的块的集合来管理,每个块不是空闲的就是已分配的。分配器的类型分为显式分配器、隐式分配器。显式分配器要求显式地释放任何已分配的块,隐式分配器是通过检测已分配块,当发现不再被程序使用时,将这个块释放。

    动态内存管理策略有首次适配、下一次适配、最佳适配等。首次适配从头开始搜索空闲链表,选择第一个合适的空闲块。搜索时间与总块数(包括已分配和空闲块)成线性关系。首次适配会在靠近链表起始处留下小空闲块的“碎片”。下一次适配和首次适配类似,只是从链表中上一次查询结束处开始搜索。比首次适应更加有效率,避免重复扫描无用块。最佳适配是通过查询链表,选择一个最好的空闲块来满足适配,且剩余最少空闲空间。它足能够保证碎片最小,大大地提高内存利用效率。

7.10本章小结

本章通过对hello的存储管理介绍,细致的了解了hello存储地址空间以及地址空间的管理方法,逻辑地址、线性地址、物理地址之间的变换方式,在指定条件下 的VA到PA的变换。HELLO在加载进程中,SHELL调用FORK函数,EXECVE函数时内存映射的管理模式以及发生缺页故障时的处理方法。加深了动态存储分配管理的认识,对以后编写运行速度快的代码帮助很大。
第8章 hello的IO管理

8.1 Linux的IO设备管理方法

所有的设备都被模型化成文件,对这些设备的管理都通过接口的方式实现。所有的I/ O 设备如磁盘、终端等都被认为是一个文件,那么对而所有的I/ O 设备都被当作对相应文件的读和写来操作。这种将设备模型化为文件的方式,允许Linux 内核采用一个简单的、低级的应用接口,我们称它为Unix I/O,这就是Unix I/O接口。

8.2 简述Unix IO接口及其函数

Unix I/O接口:

打开文件。应用程序请求内核打开一个想要的文件,也就是告诉它想要访问该文件所代表的某个I/O 设备。内核会返回一个小的非负整数,它会在之后对此文件的所有操作中标识此文件。

改变当前文件位置。对每个打开的文件,内核始终会保持着文件的位置k,初始为0。位置k是从文件开头起始的字节偏移量。

读文件。一个读文件就是从文件复制x>0 个字节到内存,从当前文件位置k 开始,将k增加到k+x。

写文件。一个写文件就是从内存复制x>0 个字节到文件,从当前文件位置k 开始,将k增加到k+x。

关闭文件。应用完成对文件的访问后,它就通知内核关闭该文件。

Unix I/O函数:

read和readn  最简单的读函数和原子性读操作。

Write和writen 最简单的写函数和原子性写操作。

recv 和 send  允许从进程到内核传递标志;

readv  允许指定往其中输入数据的缓冲区;

 writev 允许指定从其中输出数据的缓冲区;

8.3 printf的实现分析

printf函数代码如下所示:

int printf(const char fmt, …)

{

int i;

char buf[256];

va_list arg = (va_list)((char)(&fmt) + 4);

i = vsprintf(buf, fmt, arg);

write(buf, i);

return i;

}

(char*)(&fmt) + 4) 表示的是…可变参数中的第一个参数的地址。fmt是一个指针,指向第一个const参数(const char *fmt)中的第一个元素。vsprintf的作用是格式化。它接受确定输出格式的字符串fmt。从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

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

8.4 getchar的实现分析

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

8.5 本章小结

本章通过对hello中包含的函数的介绍,了解了函数相对应的unix I/O,简单地了解了I/O接口及其工作方式,通过对printf的实现分析,对硬件设备的使用和管理的技术方式有了基础的认识。

 

 

结论

hello.c通过我的双手在I/O设备下诞生后,需要经历以下一系列过程,才能真正完成它的使命:

预处理阶段:预处理器通过对hello.c初步处理,将外部库合并剔出注释形成hello.i文本文件。

编译阶段:编译器将hello.i编译成hello.s汇编语言文件。

汇编阶段:汇编器将hello.s汇编成可重定位二进制代码hello.o

链接阶段:链接器将hello.o与可重定位目标文件和动态链接库链接成为hello.out可执行程序。

运行阶段:shell通过fork以及execve创建新进程,加载hello。Shell在这个过程中起到了至关重要的作用,帮助hello创建新的内存空间,同时创建了一组新的代码、数据、堆和栈,并把相应的内容安排好。

意外情况:hello在工作过程中,如果遇到ctrl+z或ctrl+c,则分别挂起、停止

完成使命:hello终于完成了任务,shell把完成使命的hello从它的名单中删除,hello就正式结束了工作历程。

 我对计算机系统的设计实现的深切感悟:计算机系统能够通过简单的0和1二进制码,完成人类高级语言下达的指令、任务,真的非常神奇。计算机最底层只认识0和1,也就是机器语言,高级语言需要通过汇编语言这个中间翻译,把自己想要表达的想法、命令通过汇编语言翻译成计算机底层能够听懂的语言,看似一道简单的命令,计算机系统需要比较庞大、复杂的软硬件系统共同协作才能完成,这里体现了人类伟大的智慧。

我的创新理念就是随着计算机多核运算的发展,多进程并行执行的处理、分配将是一个需要优化的重点,可以设计一个相对独立的时序优化模块,将大型程序的执行速度提升到一个新的高度。另外还可以系统的从仿生的视角优化系统结构,促进人工智能的发展。

 

附件

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

 

序号

中间结果文件名称

文件的作用

1

hello.i

预处理阶起到加载头文件、进行宏替换和条件编译的作用

2

hello.s

编译后生成的汇编语言文本文件

3

hello.o

将汇编语言翻译成机器语言指令,并打包生成可重定位目标程序

4

hello

连接后得到的可执行目标文件

 

参考文献

[1]  《Unix操作系统》(第五版)O'Reilly 机械工业出版社

[2]  徐炜民 计算机体系结构(第三版) 电子工业出版社

[3]  《UNIX Network Programming Volume.1.3rd.Edition》

[4]  LINUX操作系统VIM的安装和配置 .[OL].[2018-01-23]. https://jingyan.baidu.com/article/046a7b3efd165bf9c27fa915.html 

你可能感兴趣的:(2018CS大作业)