【无标题】

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业            计算机           

学     号            120L022427           

班     级            2003008           

学       生            李健宇         

指 导 教 师            吴锐           

计算机科学与技术学院

2021年5月

摘  要

本文主要阐述hello程序在linux下的生命周期,借助相应工具探讨hello程序从hello.c经过预处理、编译、汇编、链接生成可执行文件的过程,分析了这个过程产生的文件的相应信息及作用。

关键词:程序;预处理;编译;汇编;链接;shell;进程;                           

(摘要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简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

P2P:在linux环境下,程序员编写的hello.c经过cpp的预处理得到hello.i

文件;经过cc1编译生成hello.s的汇编文件;经过as的处理变为可重定位文件hello.o;最后由ld链接生成可执行文件hello。在shell中输入命令后,fork产生一个子进程,然后hello就变成了进程。

020:在调用execve函数执行hello后,内核为hello映射虚拟内存。进入程序入口后,hello开始载入物理内存中,进入main函数执行代码。CPU为hello分配时间片、执行逻辑控制流。当hello运行结束后,成为僵尸进程,shell负责回收hello,同时删除hello相关的数据内容。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

硬件环境:Intel Core i7-10875H CPU、2.30GHz、32G RAM、954GHD Disk

软件环境:Windows10 64位;Vmware 10;Ubuntu 20.04 LTS 64位

工具:Codeblocks、gcc、Objdump、edb、Hexedit

1.3 中间结果

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

hello.c                       源程序

hello.i                       预处理后文件

hello.s                      编译后的汇编文件

hello.o                             汇编后的可重定位目标执行文件

hello                         链接后的可执行文件

hello.elf                 hello.o的ELF格式

hello1.txt                  hello.o的反汇编

hello2.txt                  hello的反汇编代码

hello1.elf                  hello的ELF格式

1.4 本章小结

本章总体介绍了hello程序一生的过程、实验过程中用到的开发环境以及调试工具等基本信息。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理概念:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。

预处理作用:预处理程序读入所有包含的文件以及待编译的源代码,然后生成源代码的预处理版本。在预处理版本中,宏和常量标识符已全部被相应的代码和值替换掉了。如果源代码中包含条件预处理指令(如#if),那么预处理程序将先判断条件,再相应地修改源代码。

2.2在Ubuntu下预处理的命令

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

应截图,展示预处理过程!

2.3 Hello的预处理结果解析

       可以看到,hello.i比hello.c内容大大增加,且仍是可阅读的C语言程序文本文件,这是因为头文件的内容被复制到了hello.i中,例如声明函数、定义结构体、定义变量、定义宏等内容,如果代码中有#define命令还会对相应的符号进行替换。

2.4 本章小结

概括了预处理的概念和作用,详细说明了ubuntu下预处理命令,并分析了.i文件。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

编译的概念:编译是利用编译程序从预处理文本文件(.i)产生汇编程序(.s)的过程。

编译的作用:把源程序翻译成目标程序,进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信息。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

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

3.3 Hello的编译结果解析

3.3.1汇编初始部分

3.3.2数据

1.字符串

存在于只读数据段中

2.局部变量i

       main函数中声明了一个局部变量i,存于堆栈中,被放置于栈中-4(%rbp)的位置

3.参数argc、argv

       参数argc、argv是用户传入main函数的参数,也被置于堆栈中

4.立即数

       直接体现于汇编代码中

3.3.3赋值操作

hello中的有赋值操作i=0,在汇编代码中主要用mov指令实现

mov指令根据操作数的大小可分为:

       movb:一个字节

movw:“字”

movl:“双字”

movq:“四字”

3.3.4算数操作

       hello.c中的算数操作为i++,在汇编代码中用addl指令实现

3.3.5关系操作

hello.c中 “if(argc!=4)”,这是一个条件判断语句,在进行编译时,被编译为:cmpl$4, -20(%rbp)。比较后设置条件码,根据条件码判断是否需要跳转。

hello.c源程序中的for循环条件是for(i=0;i<8;i++),该条指令被编译为cmpl$7,-4(%rbp)。同样在判断后设置条件码,为下一步的jle利用条件码跳转做准备。

3.3.6控制转移指令

对i赋初值后无条件跳转

根据之前关系操作得到的条件码,执行跳转。

3.3.7函数操作

       利用call指令来调用函数,将传入函数的参数保存在栈中或者按照rdirsirdxrcx的顺序存在寄存器里,函数返回值保存在寄存器%rax

3.4 本章小结

本章介绍了hello.i文件被编译为汇编代码的过程。对汇编代码中的指令进行了解析,解释了C语言中的各种操作是怎样在汇编程序中实现的,帮助我们更好理解高级语言与汇编代码的转化。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,将结果保存在目标文件hello.o中,hello.o文件是一个二进制文件,它包含函数main的指令编码,这个过程称为汇编。

4.2 在Ubuntu下汇编的命令

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

4.3 可重定位目标elf格式

典型的ELF可重定位目标文件:

      

linux下生成hello.o文件elf格式的指令:readelf -a hello.o > hello.elf

      

分析hello.elf:

  1. ELF头:ELF头(ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。

  1. 节头:记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。

  1. 重定位节:保存的是.text节中需要被修正的信息;任何调用外部函数或者引用全局变量的指令都需要被修正;调用外部函数的指令需要重定位;引用全局变量的指令需要重定位; 调用局部函数的指令不需要重定位;在可执行目标文件中不存在重定位信息。本程序需要被重定位的是printf、puts、exit、sleepsecs、getchar、sleep和.rodata中的.L0和.L1。.rela.eh_frame节是.eh_frame节重定位信息。

  1. 符号表:.symtab,一个符号表,它存放在程序中定义和引用的函数和全局变量的信息,一些程序员错误地认为必须通过-g选项来编译一个程序,才能得到符号表信息。实际上每个可重定位目标文件在.symtab中都有一张符号表(除非程序员特意用STRIP命令去掉它)。然而,和编译器中的符号表不同,.symtab符号表不包含局部变量的条目。

4.4 Hello.o的结果解析

反汇编命令:objdump -d -r hello.o > hello1.txt

与hello.s的差异:

  1. 分支跳转:

反汇编的跳转指令用的不是段名称比如.L3,而是用的确定的地址。但在反汇编代码中,分支转移表示为主函数+段内偏移量。反汇编代码跳转指令的操作数使用的不是段名称,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。

hello.s:

      

hello1.tst:

  1. 对函数的调用与重定位条目对应:

在可重定位文件中call后面不再是函数的具体名称,而是一条重定位条目指引的信息。而在汇编文件中可以看到,call后面直接加的是文件名。

hello.s:

hello1.txt:

  1. 立即数变为16进制格式

在编译文件中,立即数全部是以16进制表示的,因为16进制与2进制之间的转换比十进制更加方便,所以都转换成了16进制。

hello.s:

hello1.txt:

4.5 本章小结

本章介绍了程序的汇编过程,分析了一个可重定位目标文件中的内容,剖析了这些内容在接下来的链接过程中能起什么作用,并对比了汇编与反汇编代码有何异同。充分理解这一章的内容能够帮助我们理解链接的过程。

(第41分)

5章 链接

5.1 链接的概念与作用

概念:链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存中并执行。链接可以中兴于编译时、也就是源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时。也就是由应用程序来执行。

作用:链接器在软件开发过程中扮演着一个关键的角色,因为它们使得分离编译成为可能。我们不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其它文件。

注意:这儿的链接是指从 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

对hello反汇编:objdump -D hello >hello2.txt

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

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

命令:readelf -a hello > hello1.elf

  1. ELF头:hello的文件头和hello.o文件头的不同之处如下图标记所示,hello是一个可执行目标文件,有27个节

  1. 节头:对 hello中所有的节信息进行了声明,包括大小和偏移量

  1. 重定位节.rela.text:

  1. 符号表symtab:

5.4 hello的虚拟地址空间

根据节头表的信息,我们可以知道ELF是从0x400000开始的

.text节是从0x4010f0开始的

edb查看这部分内容,确实可以看到这里是函数的入口

    

.rodata段从0x402000开始

    

.data段从0x404048开始

    

5.5 链接的重定位过程分析

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

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

链接命令:objdump -d -r hello > hello2.txt

    

可以看到的是文件里增加了很多函数,这是由于链接时将头文件也链接到了可执行文件中。

       然后观察主函数,我们不难发现在hello.o反汇编代码中出现以main加上相对偏移的跳转已经全部被重写计算,这是因为在重定位后main函数有了全新的地址,使得这个计算成为可能。同时对子函数的call引用也在重定位后重写计算来了。

     

       综上所述,重定位的大体过程是链接器ld将所有链接文件中相同的节合并,并按照要求计算新的偏移地址赋值给新的节。同时链接器按链接指令的顺序搜索符号表,查找符号引用。

5.6 hello的执行流程

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

      1.开始执行:_start_libc_start_main

     2.执行main_main_printf_exit_sleep_getchar

     3.退出:exit

程序名                                    程序地址

_start                                      0x4010f0
_libc_start_main                      0x2f12271d
main                                       0x401125
_printf                                   0x401040
_exit                                       0x401070
_sleep                                    0x401080
_getchar                                 0x401050

5.7 Hello的动态链接分析

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。

延迟绑定是通过GOTPLT实现的,根据hello ELF文件可知,GOT起始表位置为0x404000

GOT表位置在调用dl_init之前0x404008后的16个字节均为0

调用dl_init之后的.got.plt

可以看到.got.plt的条目已经发生变化。

5.8 本章小结

本章介绍了程序链接已经加载动态库的过程。这是程序生成可执行文件的最后一步,也是将大型程序项目分解成小模块的关键所在。本章通过可执行文件的程序头来分析重定位的过程,并解析了一个程序运行的全过程。最后简单介绍了动态链接这一现代计算机中极为重要的部分是怎么运作的。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

作用:进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

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

作用:Shell是指为使用者提供操作界面的软件(命令解析器)。它接受用户命令,然后调用相应的应用程序。Linux系统中所有的可执行文件都可以作为Shell命令来执行。

处理流程:首先对用户输入的命令进行解析,判断命令是否为内置命令,如果为内置命令,调用内置命令处理函数;如果不是内置命令,就创建一个子进程,将程序在该子进程的上下文中运行。判断为前台程序还是后台程序,如果是前台程序则直接执行并等待执行结束,如果是后台程序则将其放入后台并返回。同时Shell对键盘输入的信号和其他信号有特定的处理。

6.3 Hello的fork进程创建过程

在终端中输入命令行./hello 1190201016 石衍 1后,首先shell对我们输入的命令进行解析,由于我们输入的命令不是一个内置的shell命令,因此shell会调用fork()创建一个子进程,子进程几乎但不完全与父进程相同。通过fork函数,子进程得到与父进程用户级虚拟地址空间相同的但是独立的一份副本,拥有不同的PID。

6.4 Hello的execve过程

当创建了一个子进程之后,子进程调用exceve函数在当前子进程的上下文加载并运行一个新的程序,即hello程序,需要以下步骤:

①删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。

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

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

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

6.5 Hello的进程执行

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

hello程序的执行是依赖于进程所提供的抽象的基础上,进程提供给应用程序的抽象有:

1.一个独立的逻辑控制流,它提供一个假象,好像我们的进程独占的使用处理器

2. 一个私有的地址空间,它提供一个假象,好像我们的程序独占的使用CPU内存。

操作系统所提供的进程抽象:

①逻辑控制流:如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称为逻辑流。

②上下文切换:如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程,上下文就是内核重新启动一个被抢占的进程所需要的状态,是一种比较高层次的异常控制流。

③时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

④用户模式和内核模式:shell使得用户可以有机会修改内核,所以需要设置一些防护措施来保护内核,如限制指令的类型和可以作用的范围。

⑤上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由 通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内 核数据结构等对象的值构成。

hello进程的执行:在进程调用execve函数之后,进程已经为hello程序分配了新的虚拟的地址空间,最初hello运行在用户模式下,输出hello 1190201016 石衍,然后调用sleep函数进程进入内核模式,运行信号处理程序,之后再返回用户模式。运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。

6.6 hello的异常与信号处理

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

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

1.异常和信号异常种类
类别                      原因                异步/同步                    返回行为
中断                来自I/O设备的信号     异步          总是返回到下一条指令
陷阱                有意的异常                  同步          总是返回到下一条指令
故障                潜在可恢复的错误       同步          可能返回到当前指令或终止
终止                不可恢复的错误           同步          不会返回

2.运行结果
正常运行

按下 ctrl+z

     

输入ctrl-z默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下,用ps命令可以看到,hello进程并没有被回收。此时他的后台 job 号是 1.

      

然后是jobs:

      

通过jobs命令我们可以看到所有在执行的命令。

接下来是pstree

     

通过pstree我们可以看到所有进程之间的父子关系,可以看到我们的hello进程是shellbash)创建的进程。

再之后是fg命令:

通过执行fg命令我们可以让暂停的进程重新开始工作。

最后是kill命令:

     

kill原意只是杀死一个进程,但时至今日kill以及成为发送信号的工具,在这里我选择用9号信号杀死进程。可以看到kill成功的杀死了一个进程,fg无法将其唤醒。

按下Ctrl+c

     

在键盘上输入Ctrl+c会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业,用ps查看前台进程组发现没有hello进程

     

不停乱按

     

无关输入被缓存到stdin,并随着printf指令被输出到结果。

6.7本章小结

本章介绍了有关进程管理的多个概念。介绍了Shell的作用和处理流程,以及利用fork创建子进程、利用execve加载进程的方法。展示hello程序执行的具体过程,以及异常信号的处理机制。(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:格式为“段地址:偏移地址”,是CPU生成的地址,在内部和编程使用,并不唯一。

线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址。

虚拟地址:保护模式下程序访问存储器所用的逻辑地址。

物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。CPU通过地址总线的寻址,找到真实的物理内存对应地址。在前端总线上传输的内存地址都是物理内存地址。

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

在 Intel 平台下,逻辑地址(logical address)是 selector:offset 这种形式,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。如果用 selector 去 GDT( 全局描述符表 ) 里拿到 segment base address(段基址) 然后加上 offset(段内偏移),这就得到了 linear address。我们把这个过程称作段式内存管理。

一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段(段选择符)。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。

全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。

给定一个完整的逻辑地址段选择符+段内偏移地址,看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。Base + offset = 线性地址。

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

虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。VM系统将虚拟内存分割,称为虚拟页,类似地,物理内存也被分割成物理页。利用页表来管理虚拟页,页表就是一个页表条目(PTE)的数组,每个PTE由一个有效位和一个n位地址字段组成,有效位表明了该虚拟页当前是否被缓存在DRAM中,如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始位置。如果发生缺页,则从磁盘读取。

MMU利用页表来实现从虚拟地址到物理地址的翻译。

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

Core i7采用四级页表的层次结构。CPU产生虚拟地址VA,虚拟地址VA传送给MMU,MMU使用VPN高位作为TLBT和TLBI,向TLB中寻找匹配。如果命中,则得到物理地址PA。如果TLB中没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。

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

Cache的访问并不复杂,对Cache的访问需要把一个物理地址分为标记、组索引、块偏移三个部分。首先我们通过组索引来找到我们的地址在Cache中所对应的组号,再通过标记和Cache的有效位来判断我们的内容是否在Cache中。若命中则通过块偏移读取我们要的数据,若不命中则从下一级Cache中寻找(下一级Cache不一定真的是Cache,比如对L3来说,它的下一级Cache就是主存)。

先来讨论一级Cahce,Core i7CPU的L1 Cache大小为32kb,每组八路,每个块大小为64字节。通过计算可以得出这个Cahce一共有64组。而我们知道,i7CPU的物理地址是52位,因此我们可以分析出这个Cache对物理地址的划分如图:

通过MMU将虚拟地址转化成物理地址后,计算机就通过提取中的组索引在L1中搜索组,再通过标记位匹配。如果匹配成功且有效位是1,则将块偏移指向的块中的内容交还给CPU,否则未命中,需要从下一级Cache中在重复上述操作。当我们找到内容后需要将内容写回我们的L1中,如果L1中没有空闲块,即有效位为0的块则需要牺牲一块内容,我们通常采用LRU算法来进行这一过程。对L2、L3的访问也是这样,因此就不再赘述。

7.6 hello进程fork时的内存映射

在shell输入命令行后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。

7.7 hello进程execve时的内存映射

execve 函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运 行包含在可执行目标文件 hello 中的程序,用 hello 程序有效地替代了当前程序。 它可能会自动覆盖当前进程中的所有虚拟地址和空间,删除当前进程虚拟地址的所有用户虚拟和部分空间中的已存在的代码共享区域和结构,但它不会自动创建一个新的代码共享进程。

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

如果程序执行过程中发生了缺页故障,则内核调用缺页处理程序。

处理程序执行如下步骤:

1.检查虚拟地址是否合法,如果不合法则触发一个段错误,程序终止。

2.检查进程是否有读、写或执行该区域页面的权限,如果不具有则触发保护异常,程序终止。

3.两步检查都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。

7.9动态存储分配管理

基本方法与策略:通过维护虚拟内存(堆),一种是隐式空闲链表,一种是显式空闲链表。显式空闲链表法是malloc(size_t size)每次声明内存空间都保证至少分配size_t大小的内存,双字对齐,每次必须从空闲块中分配空间,在申请空间时将空闲的空间碎片合并,以尽量减少浪费。

7.10本章小结

本章介绍了程序是如何组织储存器的。先从程序所使用的不同地址开始,分别介绍了逻辑地址、虚拟地址(线性地址)以及物理地址。并介绍了计算机是怎么一步步将地址从逻辑地址变化到虚拟地址再从虚拟地址变化到物理地址的。其中着重介绍了虚拟地址和物理地址之间的映射,以及进程是怎么映射到虚拟地址空间的。之后还介绍程序是怎么利用Cache来获取物理地址中所存放的数据的。最后简单介绍了虚拟地址中极为重要的概念——缺页异常,以及简单介绍了动态内存分配机制。

充分理解这一章的内容可以帮助我们了解一个程序内部的数据是怎么组织的,可以帮助我们更好的了解程序是怎样正确运行的,是菜鸟程序员升级成高级程序的必经之路。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

一个linux文件就是一个m个字节的序列。所有的I/O设备(如网络、磁盘和终端)都被模型话为文件,而所有的输入和输出都被当做相应的文件的读和写来执行。这种将设备优雅地映射为文件的方式,运行linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

Unix IO接口:

    1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。

    2.Linux Shell创建的每个进程都有三个打开的文件:标准输入(描述符为0),标准输出(描述符为1),标准错误(描述符为2)。

    3.改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始为0,这个文件位置是从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。

    4.读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时,触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

    5.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件,作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放他们的内存资源。

Unix I/O函数:

    1.int open(char* filename,int flags,mode_t mode)

    进程通过调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位。

    2.int close(fd)

    进程通过调用close函数关闭一个打开的文件,fd是需要关闭的文件的描述符。

    3.ssize_t read(int fd,void *buf,size_t n)

    read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的是实际传送的字节数量。

    4.ssize_t wirte(int fd,const void *buf,size_t n)

write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。

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;

}

其中传递参数中的...表示不确定个数。

函数中的va_list实际上就是typedef后的char*。而va_list arg = (va_list)((char*)(&fmt) + 4);这句操作实际上就是得到了...中的第一个量。

之后我们调用vsprintf函数。vsprintf函数将我们需要输出的字符串格式化并把内容存放在buf中。并返回要输出的字符个数i。然后调用系统函数write来在屏幕上打印buf中的前i个字符,也就是我们要输出的格式串。

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

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

最后程序返回我们实际输出的字符数量i

8.4 getchar的实现分析

先来看getchar的源代码:

int getchar(void)

{

    static char buf[BUFSIZ];

    static char* bb=buf;

    static int n=0;

    if(n==0)

    {

        n=read(0,buf,BUFSIZ);

        bb=buf;

    }

    return (--n>=0)?(unsigned char)*bb++:EOF;

}

可以看到getchar的底层实现是通过系统函数read实现的。getchar通过read函数从缓冲区中读入一行,并返回读入的第一个字符,若读入失败则返回EOF。read的具体实现如下:

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

Linux提供了一种简单使用的抽象——将系统的IO设备抽象成文件,系统的输入和输出被抽象成文件的写和读操作。在此基础上,Linux对系统IO的操作可以以打开文件、改变文件位置、读写文件、关闭文件的操作进行。同时分析了printfgetchar的实现。

(第81分)

结论

用计算机系统的语言,逐条总结hello所经历的过程:

1.预处理:hello.c预处理到hello.i文本文件;

2.编译:hello.i编译到hello.s汇编文件;

3.汇编:hello.s汇编到二进制可重定位目标文件hello.o

4.链接:hello.o链接生成可执行文件hello

5.创建子进程:bash进程调用fork函数,生成子进程;

6.加载程序:execve函数加载运行当前进程的上下文中加载并运行新程序hello

7.访问内存:hello的运行需要地址的概念,虚拟地址是计算机系统最伟大的抽象;

8.交互:hello的输入输出与外界交互,与linux I/O息息相关;

9.终止:hello最终被shell父进程回收,内核会收回为其创建的所有信息。

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

附件

hello.c                       源程序

hello.i                       预处理后文件

hello.s                      编译后的汇编文件

hello.o                             汇编后的可重定位目标执行文件

hello                         链接后的可执行文件

hello.elf                 hello.o的ELF格式

hello1.txt                  hello.o的反汇编

hello2.txt                  hello的反汇编代码

hello1.elf                  hello的ELF格式

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

参考文献

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

[1] 深入理解计算机系统原书第3版-文字版.pdf

[2]linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址) - 刁海威 - 博客园

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

[4]内存地址转换与分段_drshenlei的博客-CSDN博客

[5]fork()创建子进程步骤、函数用法及常见考点(内附fork()过程图)_小岳王子的博客-CSDN博客_fork子进程

[6] https://blog.csdn.net/rabbit_in_android/article/details/49976101

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值