文章目录

  • 第1章 概述
    • 1.1 Hello简介
    • 1.2 环境与工具
    • 1.3 中间结果
    • 1.4 本章小结
  • 第2章 预处理
    • 2.1 预处理的作用及概念
    • 2.2 在Ubuntu下预处理的命令
    • 2.3 Hello的预处理结果解析
    • 2.4 本章小结
  • 第3章 编译
    • 3.1 编译的概念与作用
    • 3.2 在Ubuntu下编译的命令
    • 3.3 Hello的编译结果解析
      • 3.3.1 数据
      • 3.3.2 赋值
      • 3.3.3 算术操作
      • 3.3.4 类型转换
      • 3.3.5 关系操作
      • 3.3.6 控制转移指令
      • 3.3.7 函数操作(不妨令P调用Q)
    • 3.4 本章小结
  • 第4章 汇编
    • 4.1 汇编的概念与作用
    • 4.2 在Ubuntu下汇编的命令
    • 4.3 可重定位目标elf格式
    • 4.4 Hello.o的结果解析
    • 4.5 本章小结
  • 第5章 链接
    • 5.1 链接的概念与作用
    • 5.2 在Ubuntu下链接的命令
    • 5.3 可执行目标文件hello的格式
    • 5.4 hello的虚拟地址空间
    • 5.5 链接的重定位过程分析
    • 5.6 hello的执行流程
    • 5.7 Hello的动态链接分析
    • 5.8 本章小结
  • 第6章 hello进程管理
    • 6.1 进程的概念与作用
    • 6.2 简述壳Shell-bash的作用与处理流程
    • 6.3 Hello的fork进程创建过程
    • 6.4 Hello的execve过程
    • 6.5 Hello的进程执行
    • 6.6 hello的异常与信号处理
    • 6.7 本章小结
  • 第7章 hello的存储管理
    • 7.1 hello的存储器地址空间
    • 7.2 Intel逻辑地址到线性地址的变换-段式管理
    • 7.3 Hello的线性地址到物理地址的变换-页式管理
    • 7.4 TLB与四级页表支持下的VA到PA的变换
    • 7.5 三级Cache支持下的物理内存访问
    • 7.6 hello进程fork时的内存映射
    • 7.7 hello进程execve时的内存映射
    • 7.8 缺页故障与缺页中断处理
    • 7.9 动态存储分配管理
    • 7.10 本章小结
  • 第8章 hello的IO管理
    • 8.1 Linux的IO设备管理方法
    • 8.2 简述Unix IO接口及其函数
    • 8.3 printf的视线分析
    • 8.4 getchar的实现分析
    • 8.5 本章小结
  • 结论
  • 附件
  • 参考文献

题目:程序人生-Hello’s P2P
专业:计算学部
学号:1190500524
班级:1903011
学生:李天正
指导教师:史先俊

摘要:
  Hello world程序的一生从hello.c文件开始,经过cpp(预处理器),ccl(编译器),as(汇编器)后变为可执行文件hello.o,再经过ld(链接器)的符号解析和重定位后变为一个可执行文件hello并向屏幕输出信息。本文拟通过分析一个hello.c完整的生命周期,从他被编译,到汇编,链接,到在系统中运行并被回收的过程,了解Linux操作系统关于进程管理、存储管理和I/O管理的知识,对Linux操作系统下一个程序周期完整的生命周期有一个更深刻的认识。
关键词:
  预处理,编译,汇编,链接,进程,虚拟内存,I/O

第1章 概述

1.1 Hello简介

  //根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
  P2P:在Linux中,hello.c经过cpp预处理,ccl编译,as汇编并最终被ld链接生成可执行目标程序hello,在shell中键入命令后,shell为其fork一个子进程,到这个时候hello便从一个程序变成了一个进程。
  020:shell为此子进程execve,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数执行目标代码,CPU为运行的hello进程分配时间片执行逻辑控制流。程序运行结束后,shell父进程负责回收hello进程,内核删除与之相关的数据结构。

1.2 环境与工具

  //列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
  硬件环境:X64 CPU; 2GHz; 16G RAM; 512G HD Disk
  软件环境:Windows10 64位; Vmvare15; Ubuntu 20.04 LTS 64位
  开发与调试工具:gcc; vim; edb; readelf; HexEdit

1.3 中间结果

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

文件名称 文件作用
hello.i hello.c预处理之后的文本文件
hello.s hello.i编译后的文本文件
hello.o hello.s汇编之后的文本文件
hello 链接之后的可执行目标文件
hello.out hello反汇编之后的可重定位目标文件

1.4 本章小结

  本章主要介绍了hello的P2P,O2O过程,简要介绍了本次实验所需的软硬件环境信息,文件的中间结果以及hello从C语言源程序hello.c到可执行目标文件hello的过程。

第2章 预处理

2.1 预处理的作用及概念

  概念:预处理器cpp在程序源代码被编译之前,根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的库文件展开合并成一个完整的文本文件,并不涉及到对程序的源代码进行解析。
  作用:
  1.执行宏替换,用实际值替换#define定义的字符串;
  2.处理条件编译指令,条件编译指令如#ifdef,#ifndef,#else,#elif,#endif等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉;
  3.处理头文件,如#include “FileName”或者#include等。该指令将头文件中的定义加入到产生的输出文件中,供编译程序进行处理。

2.2 在Ubuntu下预处理的命令

命令:cpp hello.c > hello.i

2.3 Hello的预处理结果解析

  经预处理之后,hello.c文件变为hello.i文件,使用Text Editor工具打开hello.i,发现文件内容被扩展,仍然为可阅读的C语言程序文本文件。对源文件中的宏进行宏展开,如将头文件的内容添加到该文件中,用实际的值替换#difine定义的内容,通过条件是否处理指令等。

2.4 本章小结

  本章主要介绍了预处理的概念以及作用,并结合hello.c预处理后的hello.i文件对整个过程进行了简要分析。

第3章 编译

3.1 编译的概念与作用

  概念:编译器将文本文件hello.i翻译成文本文件hello.s,包含一个汇编语言程序。以高级程序程序编写的源程序作为输入,以汇编语言的目标程序作为输出。
  作用:将高级语言编写的源程序翻译成汇编语言目标程序。涉及到用相关文法构建语法分析树的过程。同时,在编译时还会进行语法检查和一定的优化。

3.2 在Ubuntu下编译的命令

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

3.3 Hello的编译结果解析

3.3.1 数据

1.字符串:程序中有两个字符串,都存在只读数据区.rodata中作为printf函数的参数;
2.局部变量i:i是用来控制循环的计数器,编译器会将局部变量保存在寄存器或栈中
3.数组char *argv[]:main函数的第二个参数,数组的每个元素都是一个指向字符类型的指针,通过字符指针寻找字符串;
4.立即数:在汇编代码中显示的数。

3.3.2 赋值

  程序中主要的赋值操作为i=0,在汇编代码中用mov指令实现,根据数据类型有四种后缀:b(一个字节),w(两个字节),l(四个字节),q(八个字节)。

3.3.3 算术操作

汇编指令算数操作代码为:

指令 效果
leaq a, b b=&a
inc a a++
dec a a–
neg a a=-a
add a, b b=b+a
sub a, b b=b-a

在hello.s文件中仅有i++操作,用addl实现

3.3.4 类型转换

  本程序中类型转换仅有一处,用atoi函数将命令行第三个字符串参数转换为整型。

3.3.5 关系操作

1.argc!=4,比较的同时设置条件码,决定是否跳转;
2.i<8,作为判断条件,设置条件码决定是否跳转。

3.3.6 控制转移指令

1.cmpl比较完后,通过判断条件码ZF是否为0决定是否跳转到.L2,为0则跳转;

2.cmpl比较完后,判断i是否小于等于7,若是则跳转到.L4执行。

3.3.7 函数操作(不妨令P调用Q)

  调用函数过程如下:
    1.传递控制:当运行Q时,程序计数器PC必须设置为Q代码的起始地址,返回时将程序计数器PC设置为P调用Q后面那条指令的地址;
    2.传递数据:P必须向Q提供一个或多个参数(寄存器或栈),Q必须想P返回一个值;
    3.分配和释放内存:开始时,Q为局部变量分配空间,返回时释放这部分空间。
  hello.c涉及到的函数有:
    1.main函数:参数为argc和argv[];
    2.print函数:参数为那两个字符串;
    3.exit函数:参数为1;
    4.sleep函数:参数为atoi(argv[3]);
    5.getchar函数:没有参数。
  返回值存储在%eax中。

3.4 本章小结

  本章主要讲述了编译阶段编译器如何处理各种数据和操作,C语言中各数据类型和操作对应的汇编代码。并结合具体实例hello.s文件理解这个过程。通过理解这种机制,我们可以将汇编语言翻译为C语言进行反汇编工作。

第4章 汇编

4.1 汇编的概念与作用

  概念:汇编器as将.s文件翻译成机器指令,将这些指令打包为可重定位目标文件格式的文件,并将结果保存在目标文件中。
  作用:将编译器产生的汇编语言程序进一步翻译成机器语言程序,便于计算机理解,生成.o二进制文件。

4.2 在Ubuntu下汇编的命令

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

4.3 可重定位目标elf格式

  使用readelf指令查看生成的hello.o文件,部分如下
  1.ELF头:ELF头有一个16字节的Magic序列,这个序列描述了声称该文件的系统的字大小和字节顺序。剩下的部分包含帮助连接器语法分析和解释目标文件的信息。包括ELF头的大小、目标文件类型、机器类型、字节头部表的文件偏移和节头部表中条目的大小和数量;

  2.节头部表:包含了文件中出现的各个节的含义,包括节的地址、偏移量和大小等信息;

  3…rela.text节:存放着代码的重定位条目。当链接器将目标文件与其他目标文件结合时,会结合这个节,修改.text节中相应的信息。如下图的重定位信息依次对应.L0, puts, exit, .L1, print, atoi, sleep, getchar;

  .rela.text包含如下内容:

Offset 偏移位置
Info 包括symbol和type两部分,symbol代表重定位目标在.symtab中的偏移量,type代表重定位的类型(PC相对地址引用和绝对地址引用)
Addend 有符号常数
Type 重定位目标类型
Name 重定位目标名称

  4…rela.eh_frame节:.eh_frame节的重定位信息;
  5…symtab节:符号表,用来存放程序中定义和引用的函数或全局变量的信息。重定位需要引用的符号都在其中声明。name是字符串表中的字节偏移,指向符号的以null结尾的字符串名字。value是符号的地址,对于可重定位模块来说,value是距定义目标的节的起始位置的偏移。对于可执行目标文件来说,value是一个绝对运行时地址。size是目标的大小。type是数据或者函数。binding表示符号是本地符号还是全局符号。ABS表示不该被重定位的符号。UNDEF表示未定义的符号,即本目标引用的其他模块定义的符号。COMMON表示还未被分配位置的未初始化目标数据。

4.4 Hello.o的结果解析

命令:objdump -d -r hello.o

hello.s:

  由以上两张图的对比可知,汇编语言的指令大同小异。反汇编代码展示的不仅有汇编代码,还有机器代码,机器语言程序是二进制机器指令的集合,是二进制数据文件,电脑可以直接识别并执行。机器指令由操作码,操作数地址和操作结果的存储地址三部分组成。而每一条汇编语言都可以用机器指令表示。通过分析发现这两个文件的不同地方在于:
  1.分支转移:反汇编得到的代码中,跳转指令的操作数不再是.L2之类的代码段名称,而是具体的地址,因此操作数会发生相应的变化;
  2.函数调用:在hello.s中,函数调用是call加调用的函数名,而反汇编文件是call加下一条指令的地址。因为hello.c调用的是共享库中的函数,需要通过动态链接器才能确定函数的运行时地址。在机器语言中全部初始化为0,等待重定位条目被解释;
  3.访问字符串常量,在hello.s中,使用.L0(%rip)的形式访问,而在反汇编文件中以0x0(%rip)的形式访问,同样等待重定位条目被解释。

4.5 本章小结

  本章主要介绍了从hello.s到hello.o的汇编过程,通过查看hello.o可重定位目标文件的ELF格式代码和使用objdump反汇编生成的代码进行对比,简要了解了从汇编语言到机器语言的映射关系。

第5章 链接

5.1 链接的概念与作用

  概念:将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可以直接被加载到内存并执行。链接发生的时间可以不同,链接可以执行于编译时,加载时和运行时。有动态链接和静态链接两种类型,由链接器进行执行。
  作用:使得文件可以分离编译。在大型软件开发过程中,可以将大型应用程序分配给不同个人进行开发,而不用组合成一个巨大的源文件。可以独立的修改和编译这些模块。有任何一个模块发生改变时,只需要重新链接即可。节省了成本和开发时间。

5.2 在Ubuntu下链接的命令

命令:ld -o hello -dynamic-linkker /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的格式

1.ELF头:hello的文件头和hello.o的文件头不同之处如下标记,Type类型为EXEC表示hello是一个可执行目标文件;
2.节头部表:节头部表对hello所有的节信息进行了声明,其中包括大小Size,偏移量Offset以及程序载入内存的虚拟地址的起始地址Address。因此根据节头部表中的信息我们可以利用HexEdit定位各个节所占的区间;
3.重定位节.rela.text;

4.符号表.symtab;

5.4 hello的虚拟地址空间

  通过查看edb,了解hello的虚拟地址空间开始于0x400000,结束于0x400ff0:
  通过查看节头部表,可以找到各个节的信息,如0x402000为.rodata节,保存着两个字符串:

5.5 链接的重定位过程分析

使用命令:objdump -d -r hello > hello.out,获得hello的反汇编代码

通过分析hello.ob与hello.out文件,发现以下不同的地方:
hello.out文件:

hello.ob文件:

1.两者的汇编代码完全相同,但虚拟地址不同:hello.ob中都是相对偏移,而在hello.out文件中全部变成了CPU直接寻址的绝对地址。链接器将hello.o中的偏移量加上虚拟内存起始地址和.text节的偏移量就得到了hello.out中的地址。控制转移指令后的地址由偏移量变成了偏移量加上函数的起始地址,call后的地址重定位后计算出实际运行时地址;
2.hello.out文件中多了一些节,这些节有其特定的含义,如.init是程序初始化需要执行的代码,.plt是动态链接的过程链接表,.fini是程序正常终止需要执行的代码等。
重定位过程如下:
  1.重定位节和符号定义:链接器将所有类型的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,这一步完成时,程序中每个指令和全局变量都有唯一的运行时地址;
  2.重定位节中的符号引用:链接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。链接器依赖于可重定位目标模块中重定位条目这个数据结构;
  3.重定位条目:当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。重定位条目放在.rel.text节。

5.6 hello的执行流程

程序名称 程序地址
ld-2.27.so!_dl_start 0x7fce8cc38ea0
ld-2.27.so!_dl_init 0x7fce8cc47630
hello!_start 0x400500
libc-2,27.so!_libc_start_main 0x7fce8c867ab0
libc-2.27.so!_cxa_atexit 0x7fce8c889430
libc-2.27.so!_libc_csu_init 0x4005c0
libc-2.27.so!_setjmp 0x7fce8c884c10
libc-2.27.so!exit 0x7fce8c889128

5.7 Hello的动态链接分析

  动态链接的基本思想是将程序按照模块拆分成各个相对独立部分,在程序运行时才将他们链接在一起形成一个完整的程序,而不是像静态链接那样将所有程序模块都链接成一个单独的可执行文件。
  对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,因为定义它的共享模块在运行时可以被加载到任意位置,因此需要添加重定位记录,等待动态链接器处理,为了避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略,将过程地址的绑定推迟到第一次调用该过程时。链接器使用过程链接表PLT和全局偏移量表GOT共同实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中的地址跳转到目标函数。

PLT:PLT是一个数组,每个条目是16字节代码,PLT[0]是一个图书条目,跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目,每个条目都负责调用一个具体的函数。
GOT:GOT是一个数组,每个条目是8字节地址。和PLT联合使用时,GOT[0]和GOT[1]包含链接器在解析函数地址时用到的信息。GOT[2]是动态链接器在ld-linux.so模块的入口点。其他每个条目都对应于一个被调用的函数,地址在运行时解析,每个条目都有一个相匹配的PLT条目。

查看hello的ELF文件,发现GOT起始表位置:

调用_start后GOT内容发生改变,改变后的GOT表如下:

GOT[2]指向目标程序是动态链接器ld-linux.so运行时地址:

5.8 本章小结

  本章主要介绍了链接的概念与作用。简要阐述了hello.o文件是如何链接成一个可执行目标文件的过程。还介绍了hello.o的ELF格式和各个节的含义,分析了hello的虚拟地址空间,重定位过程和动态链接过程。

第6章 hello进程管理

6.1 进程的概念与作用

  概念:进程是对处理器、主存和I/O设备的抽象表示。是一个执行中的程序的实例,也是系统进行资源分配和调度的基本单位。一般情况下,包括文本数据、数据区域和堆栈。文本区域存储处理器执行的代码,数据区域存储变量和进程执行期间动态分配的内存,堆栈区域存储着活动过程中调用的指令和本地变量。
  作用:提供一个假象,好像一个程序独自使用内存系统,处理器像是在无间断执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

  作用:shell-bash是一个C语言程序,代表用户执行进程(一个命令行解释器)。它交互的解释和执行用户输入的命令,能够通过调用系统级的函数或功能执行程序、建立文件等。同时它还会协调程序间的运行冲突,保证程序能够以并行的形式高速执行。除此之外,bash还提供了一个图形化的界面,提高了交互的速度。
处理流程:
  1.终端进程读取用户由键盘输入的命令行;
  2.分析命令行,获取命令行参数,并构造传递给execve的argv向量;
  3.检查第一个命令行参数是否是一个内置的shell命令,如果不是则fork创建子进程;
  4.在子进程中,用2获得的参数,调用execve执行指定程序;
  5.如果用户要求后台运行,则shell返回,否则shell使用waitpid等待作业终止后返回。

6.3 Hello的fork进程创建过程

  1.打开terminal输入:Linux>./hello 1190500524 李天正 1;
  2.shell分析这条指令,发现不是内置命令,于是判断./hello是执行当前目录下的可执行目标文件hello,于是调用fork函数创建一个新的运行子进程 ,子进程得到与父进程用户级虚拟地址空间相同(但独立)的一份副本(包括代码段、数据段、共享库以及用户栈),意味着父进程调用fork时,子进程可以读写父进程打开的任何文件,而在子进程执行期间,父进程一直等待子进程完成。父进程与子进程的区别在于他们拥有不同的PID。

6.4 Hello的execve过程

  fork之后,子进程调用execve函数,该函数在新创建的子进程上下文中加载并运行hello程序。execve函数加载并执行可执行目标文件filename,带参数列表argv和环境变量列表envp。只有发生错误时execve才会返回调用程序。因此execve调用一次且从不返回。

加载并运行hello步骤:
  1.删除已存在的用户区域,删除当前进程虚拟地址用户部分已存在的区域结构;
  2.映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data节。bss区域是请求二进制0的,映射到匿名文件中。堆栈区域也是请求二进制0的,初始长度为0;
  3.映射共享区域,如果hello程序与共享对象链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内;
  4.设置程序计数器PC,设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时,它将从这个入口点开始执行。

6.5 Hello的进程执行

  系统中每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,他是栈、通用目的寄存器的内容、PC、环境变量以及打开文件描述符的集合。
  一个进程执行他的控制流的一部分的每一时间段叫做时间片。处理器通常用某个控制寄存器的一个模式位来提供用户模式和内核模式的功能。设置了模式位时,进程就运行在内核模式中,该进程可以执行指令集中的任何指令,可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中,不允许执行特权指令。
  在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程的决定叫做调度。上下文切换的流程是:保存当前进程的上下文,恢复某个先前被抢占的进程被保存的上下文,最后将控制传递给这个新恢复的进程。过程如图所示:

  对于hello程序。hello在刚运行时,内核为其保存一个上下文,进程在用户模式下运行,没有异常或中断信号的产生,hello就将一直正常的执行。如果产生了异常或者系统中断时,内核将启用调度器休眠当前进程,并在内核模式中完成上下文切换,将控制传递给其他进程。
  当程序在执行sleep函数时,系统调用显式的请求让调用进程休眠,调度器抢占当前进程,并产生上下文切换,将控制转移到新的进程。计数器到时后,产生一个中断信号,中断当前正在运行的进程,进行上下文切换恢复hello的上下文信息,控制会回到hello进程中。
  当循环结束后,程序调用getchar函数,hello之前运行在用户模式,进行read调用后掉入内核,内核中的陷阱处理程序请求来自键盘缓冲区的DMA传输,并且完成缓冲区到内存的数据传输后,引发一个中断信号,此时内核从其他进程上下文切换回hello进程。

6.6 hello的异常与信号处理

1.hello执行过程中出现的异常:
  1)中断:来自I/O设备的信号,异步发生,中断处理程序对其进行处理,返回后继续执行调用前待执行的下一条代码;
  2)陷阱:有意的异常,是执行一条指令的结果,调度后也会返回到下一条指令,用来调度内核服务进行操作,帮助程序从用户模式切换到内核模式;
  3)故障:由错误引起,它可能被故障处理程序修正。如果修正成功,则将控制返回到引起故障的指令,否则终止程序;
  4)终止:不可恢复的致命错误造成的结果,处理程序会将控制返回到一个abort例程,该例程会终止这个应用程序。

2.hello执行过程中可能出现的信号

3.hello对各种信号的处理的分析
1)正常运行hello程序,执行后被回收

2)运行过程中乱按,内容保存在缓冲区,hello结束后作为命令行参数

3)运行时按下Ctrl-C,会向进程发送SIGINT信号,信号处理程序终止并回收进程

4)运行时按下Ctrl-Z,shell收到SIGSTP信号,该信号处理函数是打印屏幕回显,将hello挂起,通过ps命令可以看到hello进程并未被回收,状态是Stopped,使用fg 1命令可以将其调至前台,此时shell程序首先打印hello的 命令行命令,然后继续打印剩下的信息。再按下Ctrl-Z,将进程挂起

6.7 本章小结

  本章主要介绍了进程的概念和作用,阐述了shell的作用和处理流程。重点描述了hello进程调用fork函数创建子进程和execve函数执行进程的过程。最后分析了hello程序执行过程中可能会出现的异常以及对这些异常的处理。

第7章 hello的存储管理

7.1 hello的存储器地址空间

  逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或指令的地址。是由一个段标识符加指定段内相对地址的偏移量,表示为[段标识符:段内偏移量]。
  线性地址:又叫虚拟地址。和逻辑地址类似,也是一个不真实的地址,线性地址对应内存的转换前地址。
  虚拟地址:同线性地址。
  物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线完成的。在前端总线上传输的内存地址就是物理内存地址。

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

  逻辑地址由两部分组成,分别为段标识符和段内偏移量。
  段标识符:由16位长的字段组成,称为段选择符。其中,前13位是一个索引号,索引号是段描述符的索引,段描述符具体地址描述了一个段,段描述符数组称为段描述符表,因此可以通过段标识符的前13位在表中找到一个具体的段描述符,每个段描述符由8字节组成。后3位包括一些硬件细节,表示该段寄存器的属性。
段描述符的构成:
1.Base字段:表示包含段的首字节的线性地址,即一个段开始位置的线性地址;
2.全局段描述符:放在全局段描述符表(GDT中);
3.局部段描述符:放在局部段描述符表(LDT中)。

  段式管理可以针对不同类型段采取不同保护,按段为单位进行共享。因此,段式管理没有内部片,便于实现内存共享。

逻辑地址转换为线性地址的步骤:
  1.给定一个完整的逻辑地址[段选择符:段内偏移地址];
  2.得到段选择符的T1字段,了解当前要转换的是GDT还是LDT中的段,再根据相应寄存器得到其地址和大小;
  3.得到段选择符的前13位,查找对应的段描述符,以得到基地址;
  4.Base+offset,就是要转换的线性地址。

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

  页式管理将程序的逻辑地址空间划分为固定大小的页,物理内存划分为同样大小的页框。程序加载时,可以将任意页放入内存的任意页框。这个方法需要CPU的硬件支持,来实现逻辑地址和物理地址之间的映射。页式存储管理方式中地址结构由两部分组成,分别为VPN(虚拟页号)和VPO(虚拟页偏移量)。

优缺点:
  1.优点:没有外部片,不必连续存放,便于改变程序占用空间的大小;
  2.缺点:要求程序全部加载到内存。

  页式系统中进程建立时,操作系统为进程的所有页分配页框,撤销时收回。程序运行时,进程可以动态的申请空间,因此操作系统需要为申请的空间分配物理页框。为了完成这些功能,操作系统必须记录系统内存中实际的页框使用情况,在进程切换时,正确的切换两个不同的进程地址空间到物理内存空间的映射。
  每次将虚拟地址转换为物理地址时,都会查询页表判断一个虚拟页是否缓存在DRAM的某个地方,如果不在,通过查询页表条目可以知道虚拟页在磁盘的位置。页表将虚拟页映射到物理页。

具体过程如下:
  1.给定一个n位的虚拟地址,拆分为p位的虚拟页面偏移(VPO)和一个n-p位的虚拟页号(VPN);
  2.MMU利用VPN选择适当的PTE,根据PTE,我们就得到了虚拟页的信息;
  3.如果虚拟页是已缓存的,直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址;
  4.如果虚拟页未缓存,会触发一个缺页故障,调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,再执行这个导致缺页的指令。

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

  为了消除每次产生一个虚拟地址MMU就查阅一个PTE带来的时间开销,现代许多系统都在MMU中包括了一个关于PTE的小缓存,称为翻译后备缓冲器(TLB),TLB的速度快于一级cache。
  TLB通过虚拟地址的VPN部分进行索引,分为索引(TLBI)与标记(TLBT)两个部分,这样,MMU在读取PTE时会通过TLB,不命中再从内存将PTE复制到TLB。同时为了减少页表太大造成的空间损失,可以使用层次结构的页表页压缩页表大小。
  在四级页表层次结构的地址翻译中,CPU产生虚拟地址VA,VA传送给MMU,MMU使用前36位VPN作为TLBT(前32位)+TLBI(后4位)向TLB中匹配。如果命中,得到PPN与VPO组合成PA。否则,MMU向页表中查询,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,如果在物理内存且权限符合,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA,并且向TLB中添加条目。如果查询PTE发现不在物理内存中,则引发缺页故障。如果权限不足,则引发段错误。过程如图所示:

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

  以一级Cache为例。L1Cache有64组,组索引位s=6,每组有8行,由于每块大小为64B,故块偏移为6,标记位为40位。
具体过程如下:
  1.组选择:取出虚拟地址的组索引位,将二进制组索引转化为一个无符号整数,找到相应的族;
  2.行匹配:将虚拟地址的标记位与相应组中所有行的标记位进行比较,当虚拟地址标记位和高速缓存标记位匹配,且该行有效位为1,则高速缓存命中;
  3.字选择:一旦高速缓存命中,我们就知道要找的字节在这个块的某个地方,块偏移位提供了第一个字节的偏移;
  4.若不命中,则需要从L2中取出被请求的块,将新的块存储在组索引位指示的组中的一个高速缓存行中。放置策略如下:有空闲块直接放置,否则采用LRU策略进行替换。

7.6 hello进程fork时的内存映射

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

7.7 hello进程execve时的内存映射

  execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序替代当前程序。具体过程如下:
  1.删除已存在的用户区域,删除当前进程虚拟地址的用户部分已存在的结构;
  2.映射私有区域,为新程序的代码、数据、bss和堆栈区域创建新的区域结构,这些区域都是私有、写时复制的。代码和数据区域被映射为hello文件的.text节和.data节,bss区域是请求二进制0的,映射到匿名文件,堆栈区域也是请求二进制0的,初始长度为0;
  3.映射共享区域:hello程序与共享对象libc.so链接,libc.so是动态链接的,映射到用户虚拟地址空间中的共享区域内;
  4.设置程序计数器PC:设置当前进程上下文的程序计数器,使之指向代码区域的入口点。

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

  缺页故障是一种常见的故障,当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址对应的物理地址不在内存中,必须从磁盘中取出的时候就会发生故障,流程图如下所示:
  缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,将其交换出去,换入新的页面并更新页表。当缺页处理子程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU能正常翻译VA。

7.9 动态存储分配管理


1.基本原理:在程序运行时程序员使用动态内存分配器获得虚拟内存。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的。已分配的块显式的保留供应用程序使用。空闲块保持空闲,直到显式的被应用所分配。一个已分配的块保持已分配状态,直到其被释放。

2.类型:
  1)显式分配器:要求应用显式的释放任何已分配的块;
  2)隐式分配器:应用检测到已分配块不再被程序所使用,就释放这个块。

3.带边界标签的隐式空闲链表分配器原理
  一个块是由一个字的头部、有效载荷、一些可能的填充和脚部组成。头部编码了这个块的大小,块是已分配还是空闲的。如果我们强加一个双字的对齐约束条件,那么块大小就总是8的倍数,且块大小的最低3位总是0.因此,我们只需要内存大小的29个高位,释放剩余的3位来编码其他信息。我们用其中的最低位来指明这个块是已分配的还是空闲的。

4.隐式空闲链表寻找空闲块的方式

1)首次适配:从头开始搜索空闲链表,选择第一个合适的空闲块,保持在线性时间,但会在链表起始处留下小碎片;
2)下一次适配:和首次适配相似,只是从链表中上一次查询结束的地方开始,优点是比首次适配更快,但内存利用率比首次适配低;
3)最佳适配:每次查询链表选择一个最好的空闲块,剩余最少的空闲空间,优点是碎片最小,提高了内存利用率,但速度较慢。
6.显式空闲链表

  显式空闲链表将空闲块组织成链表形式的数据结构。堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个前驱和后继指针。可以使首次适配的分配时间从块总数的线性时间减少到空闲块数量的线性时间。缺点是空闲块必须足够大。

7.链表的维护方式
1)后进先出(LIFO):将新释放的块放置在链表开始处。使用LIFO和首次适配,分配器会先检查最近使用过的块,在这种情况下,释放另一个块可以在常数时间内完成,如果使用了边界标记,那么合并也可以控制在线性时间;
2)按照地址顺序:链表每个块的地址都小于它后继的地址,这样释放一个块需要线性时间搜索合适的前驱。这种方法的首次适配比LIFO首次适配有更高的内存利用率,接近最佳适配的利用率。

7.10 本章小结

  本章主要介绍了hello的不同地址空间及其相互转换,hello的页式管理,以及TLB与四级页表支持下VA到PA的变换过程和三级Cache支持下的物理内存访问。阐述了hello进程调用fork函数和execve函数的内存映射、缺页故障的处理流程和动态存储分配器的管理。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

  所有的I/O设备都被模型化为文件,所有的输入和输出都被当做相应文件的读和写完成,这种将设备优雅的映射成文件的方式,允许Linux内核引出一个简单的,低级的应用接口,称为Unix I/O,这使得所有的输入输出都能以一种统一且一致的方式来执行。
  设备的模型化:文件
  设备管理:unix io接口

8.2 简述Unix IO接口及其函数

Unix I/O接口的几种操作:
  1.打开文件:程序要求内核打开文件,内核返回一个小的非负整数(描述符),用于标识这个文件。程序只要记录这个描述符便能记录打开文件的所有信息;
  2.shell在进程的开始为其打开三个文件:标准输入、标准输出和标准错误;
  3.改变当前文件的位置:对于每个打开的文件,内核保存着一个文件位置k,初始为0.这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作显式地设置文件的当前位置为k;
  4.读写文件:一个读操作就是从文件复制n个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k>=m时执行读操作会触发一个称为EOF的条件,应用程序能够检测到这个条件,在文件结尾处没有明确的EOF符号;
  5.关闭文件:内核释放打开文件时创建的数据结构以及其占用的内存资源,并将描述符恢复到可用的描述符池中。无论一个进程因为何种原因停止时,内核都会关闭所有打开的文件并释放他们的内存资源。

Unix I/O函数:
  1.int open(char* filename, int flags, mode_t mode),open函数将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符,flags参数指明了进程打算如何访问这个文件,mode参数指定了新文件的访问权限位;
  2.int close(int fd),关闭一个打开的文件,返回操作结果;
  3.ssize_t read(int fd, void *buf, size_t n),read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,0表示EOF,否则返回值表示的实际传送的字节数量;
  4.ssize_t write(int fd, const void *buf, size_t n),write函数从内存位置buf赋值至多n个字节到描述符fd的当前文件位置。

8.3 printf的视线分析

  printf函数的实现:首先arg获得第二个不定长参数,即输出的时候格式化串对应的值,并返回字串的长度。printf函数体如下:
  在printf中调用系统函数write(buf, i),将长度为i的buf输出,在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址。


  int INT_VECTOR_SYS_CALLA代表通过系统调用syscall。syscall将字符串中的字节”Hello 1190500524李天正”从寄存器中通过总线复制到显卡的显存中,如下如所示:

  显存中存储的字符的ASCII码。字符显示驱动子程序将通过ASCII码在字模库找到点阵信息将点阵信息存储到vram中。显示芯片按照一定的刷新频率逐行读取vram,并通过信号线向屏幕传输每一个点(RGB分量)。于是我们的打印字符串”Hello 1190500524李天正”就显示在了屏幕上。

8.4 getchar的实现分析

  当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换为ASCII码,保存到系统的键盘缓冲区中。
  getchar函数调用了read函数,read函数也通过sys_call调用内核中的系统函数,将读取存储在键盘缓冲区中的ASCII码,直到回车符,然后返回整个字符串,getchar函数只从中读取第一个字符,其他的字符被缓存在输入缓冲区。代码如下图所示:

8.5 本章小结

  本章介绍了Linux系统中I/O设备的管理方法,Unix I/O接口和函数,并且分析了printf和getchar函数是如何通过Unix I/O函数来实现的。

结论

经过这么多步骤,我们的hello程序诞生了。
  hello.c:编写C程序,这是一个二进制文本文件,hello.c中每个字符都是ASCII编码表示。
  hello.i:hello.c经过预处理变为hello.i。
  hello.s:hello.i经过编译变为hello.s。
  hello.o:hello.s经过汇编变为hello.o。
  hello:hello.o与动态链接库链接称为可执行文件hello。到这一步hello程序就诞生了。
  运行:在终端输入1190500524 李天正 1.
  创建子进程:终端输入的不是一个内置的shell命令,因此shell调用fork函数创建一个子进程。
  加载:shell调用execve函数,execve调用启动加载器,加载映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。
  上下文切换:hello调用sleep函数后陷入内核模式,处理休眠请求主动释放当前进程,内核进行上下文切换将当前进程的控制权交给其他进程,当sleep函数调用完成时,内核执行上下文切换将控制传递给当前进程。
  动态申请内存:hello程序执行printf函数时,会调用malloc向动态内存分配器申请堆中的内存。
  信号管理:当程序运行的时候我们输入Ctrl-C,内核会发送SIGINT信号给进程并终止前台作业。当输入Ctrl-Z的时候,内核会发送SIGTSTP信号给进程,并将前台作业停止挂起。
  终止:当子进程执行完成时,内核安排父进程回收子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有数据机构。

感悟:
  1.抽象是计算机系统设计和实现最重要的思想:计算机系统提供不同的抽象层次表示,来隐藏实际实现的复杂性。如虚拟机是对操作系统、处理器、主存和I/O设备的抽象,进程是处理器、主存和I/O设备的抽象。虚拟内存是对主存和I/O设备的抽象,文件是对I/O设备的抽象;
  2.计算机系统构造是个层次结构,设计复杂:计算机系统的层次结构兼具了速度和成本的考量,引入Cache,大大减少了程序的运行时间,而CPU中旁路的设计也消除了数据冒险,乱序超标量处理器性能很完善;
  3.计算机系统考虑周全:对于不同情况,计算机系统通常会采用不同的处理。如TLB是全相联高速缓存,L1,2,3是组相联告诉缓存等。
  总而言之,经过这次大作业,我对一个程序完整的运行过程有了更加深刻的理解。

附件

列出所有的中间产物的文件名,并予以说明起作用。
附件1:hello.c:源程序;
附件2:hello.i:预处理之后的文本文件;
附件3:hello.s:编译之后的汇编文件;
附件4:hello.o:汇编之后的可重定位目标文件;
附件5:hello:链接之后的可执行目标文件;
附件6:hello.elf:hello.o的elf格式,查看hello.o的各节信息;
附件7:hello.ob:hello.o的反汇编文件;
附件8:hello.out:hello的反汇编文件。

参考文献

[1] Randal E. Bryant David R. O’Hallaron. Computer Systems A Progammer’s Perspective Third Edition
[2] https://www.cnblogs.com/diaohaiwei/p/5094959.html
[3] https://www.cnblogs.com/pianist/p/3315801.html
[4] https://www.cnblogs.com/xelatex/p/3491305.html

哈尔滨工业大学计算机系统大作业-程序人生 Hello‘s P2P相关推荐

  1. 哈尔滨工业大学计算机系统大作业 程序人生-Hello’s P2P

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 计算机科学与技术学院 2021年5月 摘  要 本文以hello程序从hello.c到进程各个阶段所 ...

  2. 哈尔滨工业大学计算机系统大作业——程序人生-Hello’s P2P

    目录 摘要 第1章 概述 1.1 Hello简介 1.1.1 P2P:From Program to Process 1.1.2 020:From Zero-0 to Zero-0 1.2 环境与工具 ...

  3. 哈尔滨工业大学计算机系统大作业--程序人生

    计算机系统   大作业 题     目  程序人生-Hello's P2P      专       业   计算机科学与技术        学    号        2021110xxx      ...

  4. HIT 深入理解计算机系统 大作业 程序人生-Hello’s P2P

    HIT 深入理解计算机系统 大作业 程序人生-Hello's P2P 本论文旨在研究 hello 在 linux 系统下的整个生命周期.结合 CSAPP 课本, 通过 gcc 等工具进行实验,从而将课 ...

  5. 【2022】哈工大计算机系统大作业——程序人生Hello’s P2P

    2022哈工大计算机系统大作业--程序人生Hello's P2P 摘要 第1章 概述 1.1 Hello简介 1.2 环境与工具 1.3 中间结果 1.4 本章小结 第2章 预处理 2.1 预处理的概 ...

  6. 哈尔滨工业大学CSAPP大作业程序人生

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算学部 学 号 1190202126 班 级 1936602 学 生 李映泽 指 导 教 师 刘宏伟 计算机科学与技术学院 20 ...

  7. 哈工大 计算机系统大作业 程序人生-Hello’s P2P From Program to Process

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 120L020512 班    级 2003004 学       生 黄鹏程 指 导 ...

  8. 哈工大2021春计算机系统大作业 程序人生-Hello’s P2P

          计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机类 学     号 1190200613 班     级 1903004 学       生 ...

  9. HIT 计算机系统 大作业 程序人生-Hello’s P2P

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算机类 学 号 1180300223 班 级 1803002 计算机科学与技术学院 2019年12月 摘 要 本文主要介绍了一个 ...

最新文章

  1. 结构化方法与面向对象方法之比较
  2. Maven 排除依赖jar包
  3. 科大星云诗社动态20210425
  4. JZOJ 5414. 【NOIP2017提高A组集训10.22】幸运值
  5. Apache——启动错误:[Cannot load modules/mod_actions.so into server]解决方案
  6. JS计算本周一和本周五的日期
  7. 弹性伸缩Auto Scaling产品全面升级,轻松应对业务负载变化,张弛有度,收放自如!...
  8. mysql 机器复制_MySQL复制在同一台机器上
  9. Kali Linux 无线渗透测试入门指南 第十章 WPS 和 探针
  10. BlockingQueue - LinkedBlockingQueue常用API
  11. 基于map函数生成星战片头动画
  12. .so文件(so文件是什么)
  13. python中forward(200)什么意思_Python中的Phyllotaxis模式| 算法植物学的一个单位
  14. 李开复:人工智能对人类真正的威胁是什么?
  15. PloneBook中文版
  16. 北航计算机学院吉祥物,北航软件学院吉祥物征集令
  17. cadence 16.60破解方式及文件下载地址
  18. SD卡 (SD miniSD microSD SDIO)知识详解
  19. 什么是root?我来告诉你为什么它叫root
  20. aegisub32汉化_Aegisub中文版(aegisub字幕特效)V3.2.3 免费版

热门文章

  1. ZooKeeper 原理及其在 Hadoop 和 HBase 中的应用
  2. Android实训案例(三)——实现时间轴效果的ListView,加入本地存储,实现恋爱日记的效果!
  3. canvas制作柱形图/折线图/饼状图,Konva写动态饼状图
  4. python与excel教程_办公自动化系列(2) | Python与Excel交互教程 - 交互演示
  5. Centos7服务器安装tomcat8
  6. android实现朋友圈播放视频,实现类似朋友圈视频的滚动播放功能
  7. 1-6 JAVA [设计一个BankAccount类]
  8. 彩世界导航谈网站导航该如何优化?
  9. 在Windows环境下Webots与Ros的联合仿真
  10. win7下虚拟机VMWare装linux(ubantu)后挂载win7共享目录