HIT-ICS 2021大作业——程序人生-Hello’s P2P

摘要

        本文从程序员的角度,以hello.c程序为例,从预处理开始到hello运行结束,进程回收,从预处理、编译、汇编、链接、进程/存储/IO管理的方面进行系统性的分析,简要地描述了hello程序从诞生到灭亡的全过程。同时,也作为对计算机系统课程的总结。

关键词:计算机系统;程序;进程。

目  录

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.4 本章小结....................................................................................................... - 10 -

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

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

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

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

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

4.5 本章小结....................................................................................................... - 14 -

第5章 链接........................................................................................................... - 15 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6.7本章小结....................................................................................................... - 21 -

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

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

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

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

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

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

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

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

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

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

7.10本章小结..................................................................................................... - 26 -

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

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

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

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

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

8.5本章小结....................................................................................................... - 27 -

结论......................................................................................................................... - 28 -

附件......................................................................................................................... - 29 -

参考文献................................................................................................................. - 30 -

第1章 概述

1.1 Hello简介

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

        hello.c程序(program)的初始状态是由程序员创建的文本文件,用于完成以验证argc和argv数组为主的,有关C语言参数表示的一系列实验验证。当程序正确编写后,程序员可以通过IDE或是在命令行中键入命令gcc -m64 -Og -no-pie -fno-PIC hello.c -o hello等进行预处理、编译、汇编、链接等一系列操作后生成可执行文件hello。

        在运行可执行文件hello的过程中,涉及到操作系统的进程管理和存储管理等一系列技术。进程管理中使用fork()函数创建新的子进程,使用execve()函数加载并运行这个hello程序,mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存,hello中对库函数的调用依赖于mmap。Hello程序运行时消耗CPU资源,操作系统使用逻辑控制流的方式,将某一些细小的时间片分配给hello进程,使其看上去好像在独占地使用CPU。

        在存储管理中,MMU(内存管理单元)需要根据CPU中页表基址寄存器从页表中获得虚拟地址(VA)索引和物理页号,再由此找到偏移量组合形成物理地址(PA),以从物理内存中获得所需的数据和指令。TLB作为页表的缓存,三级Cache作为主存的缓存存在,以利用其硬件速度上的优势,提高搜索的效率;页表分级的目的是节省空间,提高存储的空间利用率;Pagefile虚拟内存作为主存的后备,用户储存超出RAM大小的数据。

        信号处理提供了诸如SIG_INT的一系列信号和中断、阻塞等一系列处理机制,IO管理用于连接IO设备和计算机CPU并提供二者之间的信号传递和处理。同样的,流水线式的异常处理机制使得用户看上去(对多核处理器确实可能是)键盘、主板、显卡和屏幕的工作是同时的,即用户在shell中输入指令(包括参数)、按下回车,屏幕立即有变化,而执行过程中ctrl+c等按键也有不同的效果。

        当进程运行完毕(hello中一般指return,有时也可能因为出错直接exit),该进程形成僵死进程,而当初fork的父进程负责回收子进程。在命令行执行时,这个父进程就是bash进程,它负责为hello“收尸”。

1.2 环境与工具

        X64 CPU;2GHz;2G RAM;256GHD Disk 以上

        Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位;

        GDB/OBJDUMP;EDB

1.3 中间结果

        hello.c(源文件)

        hello.i(预处理中间文件)

        hello.s(编译产生的中间文件)

        hello.o(汇编产生的可重定位文件)

        hello(链接产生的可执行文件)

1.4 本章小结

        本章对hello的P2P,020的整个过程作了简要介绍,列出了运行的环境及使用的工具以及中间结果,是后面章节的概括。

第2章 预处理

2.1 预处理的概念与作用

        预处理指驱动程序运行C预处理器(cpp),它将C的源程序hello.c翻译成一个ASCII码的中间文件hello.i:

        gcc hello.c -E -o hello.i

        这一过程包括以下内容:

        (1)将所有的#define删除,并展开所有的宏定义;
        (2)处理所有的预编译指令,例如:#if,#elif,#else,#endif;
        (3)处理#include预编译指令,将被包含的文件插入到预编译指令的位置;
        (4)添加行号信息文件名信息,便于调试;
        (5)删除所有的注释:// /**/;
        (6)保留所有的#pragma编译指令,因为在编写程序的时候,我们经常要用到#pragma指令来设定编译器的状态或者是指示编译器完成一些特定的动作。
        (7)生成.i文件。

2.2在Ubuntu下预处理的命令

Ubuntu下执行预处理命令生成hello.i文件

        在命令行键入命令gcc hello.c -E -o hello.i,回车执行后生成中间文件hello.i。

2.3 Hello的预处理结果解析

        Hello.c的预处理中间文件hello.i用gedit打开后,发现是由源程序经(1)去注释 (2)宏替换 (3)头文件展开 (4)条件编译后生成的。尤其是宏替换和头文件展开在源代码前序部分生成了一段很长的代码(如图所示)。

部分hello.i代码

2.4 本章小结

        预处理命令,可以在编译器编译之前,提前进行一些操作,比如定义常量,还可以进行条件编译以方便调试,可以进行文件引入来导入一些预先写好的模块,便于程序的组织和调试和一些特殊的编程技巧的实现,是一项非常有用的功能。

第3章 编译

3.1 编译的概念与作用

        编译是指驱动程序运行C编译器(ccl),将预处理生成的中间文件hello.i翻译成一个ASCII汇编语言文件hello.s的过程。包括以下过程:

(1)扫描,语法分析,语义分析,源代码优化,目标代码生成,目标代码优化;

(2)生成汇编代码;

(3)汇总符号;

(4)生成.s文件

3.2 在Ubuntu下编译的命令

        在命令行键入命令gcc -S hello.i -o hello.s,回车执行后生成汇编文件hello.s。

Ubuntu下执行编译命令生成hello.s文件

3.3 Hello的编译结果解析

        使用gedit打开hello.s文件后,发现文件的源码部分已经由C语言代码变为汇编语言代码,而展开的头文件、预编译指令都不在文件中。Hello.s中的汇编代码具有明显的loop结构和条件控制转移结构,与源码逻辑是符合的。

部分hello.s代码

3.3.1 函数调用

        Hello.s中主要采用call命令调用函数,包括printf、atoi、sleep、exit、getchar等等,这些函数的传参方式都是寄存器传参,然而值得注意的是,在hello.s文件中,main函数的传参是寄存器传参。函数的返回方面,main函数在.L3中使用ret语句正常返回,在.LBF6中因参数个数不等于4,调用exit(1)结束程序,所以无返回值,main函数中调用的函数多为库函数,按各自逻辑进行返回。

 

.L3和.LFB6对应的汇编代码

       3.3.2 变量

       Hello.s中,局部变量int i被存放在栈中最靠近栈底的4个字节的位置,即-4(%rbp)处,随着每次循环进行自加,并与7比较,大于7时跳出循环。

与i相关的循环体部分代码

       3.3.3 赋值操作

       局部变量i在定义时不赋初值,仅到当进入循环体时被赋值为0,赋值操作采用mov命令,将立即数存入栈中i的位置。

对i的赋值操作

       3.3.4 算术操作

       局部变量i在每次循环后自加,自加采用add命令,将立即数1直接加到栈中i的位置处。

对i的算术操作

       3.3.5 关系操作

       局部变量i在每次自加后,都需要判断与7的关系,以决定是否跳转回到循环体,或者跳出循环返回,关系判断使用cmp和jle命令。源码中要求i<8,i<=7也能表达相同的意思。

对i的关系操作

       除此以外,在程序运行伊始,需要判断main函数的参数个数argc是否为4,这里使用到关系操作!=,使用cmp和je命令。Je用于判断相等,其作用和jne是对称的,具体使用由编译器决定。

对argc的关系操作

       3.3.6 数组操作

       在循环体中要求打印参数argv[1]和argv[2],并由argv[3]决定sleep的时长。正如前文中介绍的,main函数的参数数组元素的地址存储在栈中,因此读取参数数组的元素时使用mov语句,将第0个参数的地址移至寄存器rax中,使用rax加上偏移量计算各个参数的地址,最后取值放入printf函数的传参寄存器中。

对参数数组argv[]的操作

       3.3.7 控制转移

       在判断参数个数argc是否为4的分支中采用条件控制转移语句,源码中的if语句,对应到汇编代码中是cmp+je语句,代码在上文中贴出,这里不再赘述。

3.4 本章小结

本节涉及到的指令全部为gun汇编程序(gas)的伪汇编指令,相比最后的汇编指令内容更为精简,方便阅读、分析。程序将常量放入.rodata节,初始化全局变量放入.data节,通过标签定义和跳转等方式定义许多操作,为后序的汇编和链接生成可执行文件准备。

第4章 汇编

4.1 汇编的概念与作用

       汇编是指驱动程序运行汇编器(as),将hello.s翻译成一个由机器语言构成的可重定位目标文件(relocatable object file)hello.o的过程,包括了以下内容:

(1)根据汇编指令和特定平台,把汇编指令翻译成二进制形式;

(2)合并各个section,合并符号表;

(3)生成.o文件。

4.2 在Ubuntu下汇编的命令

在命令行键入命令gcc -c hello.s -o hello.o,回车执行后生成汇编文件hello.o。

Ubuntu下执行汇编命令生成hello.o文件

4.3 可重定位目标elf格式

        Hello.o的elf头由.text、.rela.text(重定位节,包含.text节需要进行重定位的信息,在.o生成可执行文件的时候会被修改,hello.o需要被重定位的是printfputsexitsleepsecsgetcharsleep.rodata中的.L0.L1)、.data、.bss、.rodata、.comment、.note.GNU-stack、.note.gnu.property、.eh_frame、.rela.eh_frame(.eh_frame节重定位信息)、.symtab、.strtab、.shstrtab构成,使用readelf -S -W hello.o指令列出各节的信息(包括大小Size、偏移Off等)如下图所示:

readelf列出各节信息

4.4 Hello.o的结果解析

执行反汇编语句截图

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

        反汇编的结果与hello.s基本一致,在某些细节上有些许差别。例如:objdump反汇编输出的立即数都是用十六进制表示的,而hello.s中是十进制;hello.s中调用函数使用的是call+函数名,在反汇编中使用的是与main的相对地址表示(这个地址显然不是被调函数的地址,被调函数的信息在下一行中给出);hello.s中条件控制转移的跳转指令的跳转点在反汇编中也是使用相对地址进行表示。

        机器语言由指令和操作数构成,其指令种类、操作数个数决定了指令的长短和内容。机器语言中的某些组合和汇编语言中的指令相对应,某些组合和汇编语言中的操作数相对应。例如:十六进制的55代表push %rbp指令,7e代表jle,01可以代表立即数$0x1,当然,同一个指令也可能映射到不同的机器码上,其中的规则是复杂的。

4.5 本章小结

汇编是将计算机不能读懂的汇编语言翻译成计算机能读懂的机器语言的不可缺少的重要步骤。

5章 链接

5.1 链接的概念与作用

       驱动程序运行连接器程序ld,将hello.o和一些必要的系统目标文件组合起来,创建一个可执行目标文件(executable object file)的过程。包括以下内容:

        (1)合并各个.obj文件的section,合并符号表,进行符号解析;

        (2)符号地址重定位;

        (3)生成可执行文件

5.2 在Ubuntu下链接的命令

        在命令行键入命令gcc hello.o -o hello,回车执行后生成汇编文件hello。

Ubuntu下执行链接命令生成hello文件

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

hello的elf头格式

        使用readelf展开hello的elf头如上图所示,hello的elf包括若干节,诸如.interp、.note.gnu.property、.note.gnu.build-id、.note.ABI-tag、.gnu.hash、.dynsym、.dynstr、.gnu.version、.gnu.version_r、.rela.dyn、.rela.plt、.init、.plt、.plt.got、.plt.sec、.text、.fini、.rodata、.eh_frame_hdr、.eh_frame、.init_array、.fini_array、.dynamic、.got、.data、.bss、.comment、.symtab、.strtab、.shstrtab节。并从图中可以读出各段的起始地址、大小等信息,如:.bss节的起始地址为0x4010,大小为8个字节。

5.4 hello的虚拟地址空间

Hello虚拟地址空间分布情况

    本进程的虚拟地址空间各段信息如上图所示,包含了hello文件、libc库、ld库等组成部分。实际运行中,所有虚拟地址空间段大小都为0x1000,且一段中包含一个或多个5.3中的程序段。动态链接库中的文件映射到内存的内容,与hello文件中映射到内存的内容地址间隔较大。程序中还包括[stack],[vvar],[vdso],[vsyscall]等特殊用途的地址段。

5.5 链接的重定位过程分析

        使用objdump -d -r hello输出hello的十分详细的节信息,发现其包含的节是hello.o的子集,仅保留:.init、.plt、.plt.got、.plt.sec、.text、.fini节的详细信息。

        hello中包含一些外部文件的宏定义、变量、库函数和操作系统的启动代码等,且.o文件.text节从0开始,而可执行文件.text节并非从0开始。

        重定位的过程分为符号解析和重定位两步。

        符号解析:目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量。符号解析的目的是将每个符号引用正好和一个符号定义关联起来

        重定位:编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位。

        hello.o的文件中包含一些重定位条目,这些重定位条目告诉链接器32位PC相对地址或32位绝对地址进行重定位,这些重定位条目通过计算地址或直接调用保存的绝对地址,达到重定位的目的。

        无论何时汇编器遇到对最终位置未知的目标引用,会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中,已初始化数据的条目放在.rel.data中。

5.6 hello的执行流程

程序运行调用(途径)的所有函数如下所示:

_start                                                                                               //程序开始

__libc_start_main

__GI___cxa_atexit

__internal_atexit

__GI___cxa_atexit

__internal_atexit

__new_exitfn

__internal_atexit

__GI___cxa_atexit

__libc_start_main

_setjmp

__sigsetjmp

__sigjmp_save

__libc_start_main                                                         //准备调用main函数

__GI_exit

__run_exit_handlers

__GI___call_tls_dtors

__run_exit_handlers

__do_global_dtors_aux

deregister_tm_clones

__do_global_dtors_aux

_fini

__run_exit_handlers

_IO_cleanup

_IO_unbuffer_all

_IO_cleanup

_IO_flush_all_lockp

_IO_cleanup

_IO_unbuffer_all

_IO_cleanup

_IO_unbuffer_all

_IO_new_file_setbuf

_IO_default_setbuf

_IO_new_file_sync

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

_IO_new_file_setbuf

_IO_unbuffer_all

_IO_new_file_setbuf

_IO_default_setbuf

_IO_new_file_sync

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

__GI__IO_setb

_IO_default_setbuf

_IO_new_file_setbuf

_IO_unbuffer_all

_IO_cleanup

__run_exit_handlers

__GI__exit                                                                                      //程序结束

5.7 Hello的动态链接分析

        动态链接库中的函数在程序执行的时候才会确定地址,所以编译器无法确定其地址,在汇编代码中也无法像静态库的函数那样体现。

        hello程序对动态链接库的引用,基于数据段与代码段相对距离不变这一个事实,因此代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量。

        GNU编译系统采用延迟绑定技术来解决动态库函数模块调用的问题,它将过程地址的绑定推迟到了第一次调用该过程时。

        延迟绑定通过全局偏移量表(GOT)和过程链接表(PLT)实现。如果一个目标模块调用定义在共享库中的任何函数,那么就有自己的GOT和PLT。前者是数据段的一部分,后者是代码段的一部分。

5.8 本章小结

        链接是组建大型程序和团队编程不可缺少的重要部分,掌握链接器的一些原理和动态链接是非常有必要的,也是学习库打桩等强大机制的基础。虽然hello.c很简单,但是也需要和标准库进行链接。了解hello.c链接的来龙去脉,对掌握链接技术很有帮助。

6章 hello进程管理

6.1 进程的概念与作用

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

        进程提供给应用程序两个关键的抽象:

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

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

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

        Shell是一个交互型的应用级程序,它代表用户运行其他程序。最早的shall是sh程序,后面出现了一些变种,比如csh、tcsh、ksh和bash。Shell执行一系列的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行。求值步骤解析命令行,并代表用户运行程序。

        bash 是一个为GNU项目编写的Unix shell,也就是linux用的shell。

6.3 Hello的fork进程创建过程

        shell调用fork函数,形成自身的一个拷贝(子进程),为运行hello做准备。

6.4 Hello的execve过程

        在shell的子进程中执行execve函数,将参数传给Hello程序,并执行hello。

6.5 Hello的进程执行

        一开始,hello运行在用户模式,当程序收到一个信号(可能是用户的键盘输入或者硬件中断等)时,进入内核模式,运行信号处理程序,之后再返回用户模式。在hello运行的过程中,cpu不断切换上下文,使hello程序运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。

6.6 hello的异常与信号处理

(1)输入Ctrl-C时,程序终止,如下图所示:

程序终止

处理过程是向程序发送SIG_INT信号,程序执行默认行为:终止前台进程。

(2)输入Ctrl-Z时,程序挂起,如下图所示:

程序挂起

处理过程是向程序发送SIGSTP信号,程序执行默认行为:挂起程序,之后会返回shell中。

(3)乱按+回车

会将输入的字符串显示在两组本应输出的Hello字符串之间,并不会有其他变化。

乱按+回车

(4)ps jobs pstree fg kill命令

ps:显示当前进程的状态

当前程序状态

jobs:查看后台运行的进程(在上文中已经演示过了)

fg:恢复一个后台进程

恢复一个后台进程

pstree:显示进程树

部分进程树内容

kill:结束一个进程

可能会产生IO中断、时钟中断、系统调用等等,会产生SIGINT、SIGSTP等信号。

6.7本章小结

        linux命令行shell是一个非常强大的工具,用它可以更方便的执行Hello和发送各种命令请求。通过信号等方式可以实现异常处理,让Hello在顺序执行者也能处理一些突发状况和实现一些功能。进程调度实现了各个进程计算资源合理分配,互不干扰,提高了系统稳定性和效率。

7章 hello的存储管理

7.1 hello的存储器地址空间

        (1)物理地址(physical address)

        用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。在hello从预处理到运行结束的过程中,并观察不到这个地址。

        (2)逻辑地址(logical address)

        逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址,是编写hello程序代码时使用到的地址。比如说argv[]数组的首地址argv。

        (3)线性地址(linear address)或也叫虚拟地址(virtual address)

        跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址,是hello程序运行时使用的地址,在edb中可以查看到。

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

        Intel使用段式管理处理逻辑地址到线性地址的变换。

        逻辑地址=段选择符+逻辑地址偏移量。Intel设置了四类段寄存器:SS(栈段寄存器)、ES/GS/FS(辅助段寄存器)、DS(数据段寄存器)、CS(代码段寄存器)用于存放段选择符。段选择符由三个字段组成:索引、TI、RPL,TI决定描述符表的类型,RPL决定进程的状态转换,索引用来确定当前使用的段描述符在描述符表中的位置。Intel处理器中,通过段选择符查表获得段基址、段限、存取权限信息。通过基址寄存器、变址寄存器、比例因子和逻辑地址的偏移量可以计算出一个有效地址EA,再加上段基址得到线性地址。

        线性地址到主存地址的转换可以通过分页完成。

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

        分页的基本原理是把内存划分成大小固定的若干单元,每个单元称为一页(page),每页包含4k字节的地址空间(为简化分析,我们不考虑扩展分页的情况)。这样每一页的起始地址都是4k字节对齐的。为了能转换成物理地址,我们需要给CPU提供当前任务的线性地址转物理地址的查找表,即页表(page table)。

        为了节约页表占用的内存空间,x86将线性地址通过页目录表和页表两级查找转换成物理地址。32位的线性地址被分成3个部分:最高10位 Directory 页目录表偏移量,中间10位 Table是页表偏移量,最低12位Offset是物理页内的字节偏移量。页目录表的大小为4k(刚好是一个页的大小),包含1024项,每个项4字节(32位),项目里存储的内容就是页表的物理地址。如果页目录表中的页表尚未分配,则物理地址填0。页表的大小也是4k,同样包含1024项,每个项4字节,内容为最终物理页的物理内存起始地址。

        每个活动的任务,必须要先分配给它一个页目录表,并把页目录表的物理地址存入cr3寄存器。页表可以提前分配好,也可以在用到的时候再分配。

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

        TLB(翻译后备缓冲器)是一个小的、虚拟寻址的缓存,其中每一行都保存着一个由单个PTE组成的块。TLB作为页表的缓存,加速了从页表中寻找对应页表项的过程。四级页表中每个上级页表的PTE都负责映射一个下级的页面,这样的展开方式使得未分配虚拟地址空间(大部分的情况)不会被储存在页表中,这是对虚拟地址空间(磁盘空间)的极大节约。

        VA由两个部分组成,即虚拟页号+虚拟页偏移量。其中虚拟页号(VPN)用于作为到页表的索引(当然,还是优先在TLB中检索),一级一级向下寻找到物理页号,而虚拟页偏移量=物理偏移量。最后物理也好和物理页偏移量组成了PA。

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

        先访问一级缓存,不命中时访问二级缓存,再不命中访问三级缓存,再不命中访问主存,如果主存缺页则访问硬盘。当然,访问不命中后需要从下级的命中的缓存(最坏的状况是硬盘)中拉取对应的页进行替换。

7.6 hello进程fork时的内存映射

        (1)执行新进程(hello)时,为这个新进程创建虚拟内存。

        (2)创建当前进程的mm_struct, vm_area_struct和页表的原样副本,两个进程中的每个页面都标记为只读。两个进程中的每个区域结构(vm_area_struct) 都标记为私有的写时复制。

        (3)在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存, 随后的写操作通过写时复制机制创建新页面。

7.7 hello进程execve时的内存映射

        (1)删除已存在的用户区域

        (2)创建新的区域结构: 代码和初始化数据映射到.text和.data区(目标文件提供), .bss和栈映射到匿名文件

        (3)设置PC,指向代码区域的入口点

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

        在虚拟内存的习惯说法中,DRAM缓存不命中被称为缺页。缺页引发缺页中断,缺页处理子程序确定物理内存中的牺牲页(若页面被修改,则换出到磁盘),缺页处理程序调入新的页面,并更新内存中的PTE,然后返回原进程,再次执行缺页指令。

7.9动态存储分配管理

        基本方法:维护一个虚拟内存区域“堆”,将堆视为一组不同大小的 块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的,需要时选择一个合适的内存块进行分配。

        (1)记录空闲块,可以选择隐式空闲链表,显示空闲链表,分离的空闲链表和按块大小排序建立平衡树

        (2)放置策略,可以选择首次适配,下一次适配,最佳适配

        (3)合并策略,可以选择立即合并,延迟合并

        (4)需要考虑分割空闲块的时机,对内部碎片的忍耐阈值.

7.10本章小结

        通过高速缓存、虚拟内存、动态内存分配,可以实现快速、高效、利用率高的储存空间管理。可以通过内存映射等方式实现文件共享。储存管理是一个相当重要、值得研究的机制。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

        设备的模型化:将设备抽象成文件

        设备管理:通过unix io接口管理

8.2 简述Unix IO接口及其函数

        打开和关闭文件:open()和close()

        读写文件:read()和write()

        改变当前的文件位置lseek()

8.3 printf的实现分析

        https://www.cnblogs.com/pianist/p/3315801.html

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

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

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

8.4 getchar的实现分析

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

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

8.5本章小结

        输入输出看似简单,实际是一个非常精巧的过程,从程序发出请求到系统函数调用到设备相应,需要执行许多步骤,往往也是拖慢程序的主要因素和一些崩溃异常的高发地,需要谨慎选用函数、命令实现目的。

结论

        hello的源码hello.c文件,要生成可执行文件,首先要进行预处理,其次要进行编译生成汇编代码,接着进行汇编处理生成目标文件,目标文件通过链接器形成一个可执行文件。

        可执行文件需要一个执行环境,它可以在linux下通过shell进行运行,与计算机其他经常文件同步运行,并通过异常处理机制相应信号。在运行的过程中,程序通过Intel内存管理机制一步步访问逻辑地址、虚拟地址、物理地址,从而进行数据交换,还可以通过IO机制进行输入输出交互。

        通过学习计算机系统这门课程,我深感计算机这个庞大体系的复杂、精巧,从电路到电路组合,再到硬件集成、软件调配,每一处都井井有条、随处显现着前人的智慧。不少复杂概念的学习,通过这门课感觉仅仅只是入了个门,距离熟练运用甚至涉及差距仍然较大,但不妨碍进行一些思维上的创新。

附件

hello.c(源文件)

hello.i(预处理中间文件)

hello.s(编译产生的中间文件)

hello.o(汇编产生的可重定位文件)

hello(链接产生的可执行文件)

参考文献

[1]  深入理解计算机系统(原书第3版)/(美)兰德尔·E·布莱恩特(Randel E.Bryant)等著;龚奕利,贺莲译. —北京:机械工业出版社,2016.7

[2] https://www.cnblogs.com/hyfer/p/10188436.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值