2019 HITICS大作业

程序人生-Hello’s P2P

摘 要
本文在linux操作系统下对C语言程序hello.c的运行全过程进行了分析。 分析了从c文件转化为可执行文件过程中的预处理、编译、汇编和链接阶段,和可执行文件执行过程中的进程管理、存储空间管理和I/O管理的原理。

关键词:预处理,编译,汇编,链接,进程,加载运行,异常处理,虚拟内存,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。书写完代码之后,由于程序文本中存在之类的称为“头文件”的编译预处理指令,我们接下来需要将其通过预处理器修改原始的C程序。将头文件的内容插入至hello.c中,得到hello.i。接下来,hello.i将通过编译器ccl被翻译成汇编语言,它告诉我们这一程序运行每一步所要执行的指令,该汇编语言文件称为hello.s。hello.s将通过汇编器被翻译成由0和1组成的二进制文本,也就是机器代码。它被称作可重定位目标文件,即hello.o。又由于我们在编写代码时,调用了我们并未定义,而是系统已经定义好的系统库函数,所以我们需要将这些库函数的可重定位目标文件与hello.o连接起来,利用链接器得到最终的可执行目标文件hello。
O2O:shell作为命令行解释器,在接收到运行hello的指令后,会调用操作系统内核代码中的加载器来运行它。加载器会通过execve函数在fork函数产生的子进程上下文中加载hello。具体步骤为:先删除当前虚拟地址的用户部分已存在的数据结构,为hello的代码、数据、bss和栈区域创建新的区域结构,然后映射共享区域,设置程序计数器,使之指向代码区域的入口点,执行程序的第一条指令,CPU为hello分配时间片执行逻辑控制流。hello通过Unix I/O管理来控制输出。hello执行完成后shell会回收hello进程,并且内核会从系统中删除hello所有痕迹。至此,hello结束了它的一生。
1.2 环境与工具
硬件环境:Intel Core i7-8750H CPU; 2.20GHz;16RAM
软件环境:Windows 10;Ubuntu18.04;VMware Workstation 14 Pro
实验工具:CodeBlocks 64位;readelf;vim+gcc;Hexedit;edb
1.3 中间结果
文件名 作用
hello.c 大作业源程序
hello.i 预处理后的源程序
hello.s hello.i经编译后的汇编程序文件
hello.o hello.s经过汇编器生成的可重定位目标文件
hello Hello.o与其他函数链接后的可执行目标文件
Asm_hello_o hello.o反汇编生成的汇编文件
ELF_hello_o hello.o的ELF格式
Asm_hello hello反汇编生成的汇编文件
ELF_hello hello的ELF格式

1.4 本章小结
本章介绍了hello程序诞生的P2P与O2O过程,以及实验的环境,所需工具和程序的中间文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

  1. 概念:预处理一般指程序源代码(hello.c)通过预处理器(cpp)将源代码开头的编译预处理指令,即#开头的内容(头文件,宏定义等)进行解释,将其插入至源代码中,得到预处理后的源程序,即hello.i的过程。
    2019 HITICS大作业_第1张图片
    图2.1.1 程序源代码中的头文件
    2.作用:
    1)通过宏定义,将程序中的特定字符串进行替代
    2)通过头文件,不必使程序员在编写代码时就将文件内容全部写入,使代码更加简洁。

2.2在Ubuntu下预处理的命令
2019 HITICS大作业_第2张图片
图2.2.1 在Ubuntu下预处理的命令

我们可以看到,-E指令是用来查看hello.c经预处理之后得到的hello.i的文本内容,目录中也确实出现了hello.i文件。

2.3 Hello的预处理结果解析
2019 HITICS大作业_第3张图片
图2.3.1 hello.i文件的末尾部分

hello.i文件过于庞大,我们可以看到它将近有4000行。原本的编译预处理指令消失了,取而代之的是具体翔实的声明。
以下是对hello.i文件内容的部分展示。它们分别描述了运行库在计算机上的位置以及函数所需函数的声明。
2019 HITICS大作业_第4张图片
图2.3.2 hello.i文件中的部分内容
2019 HITICS大作业_第5张图片
图2.3.3 hello.i文件中的部分内容

2.4 本章小结
本章主要讲解了预处理的概念与作用,并展示了预处理在Ubuntu上的指令,对hello.i文件的内容分析以及hello.i文件的部分内容。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

  1. 概念:编译主要指将已经修改过的程序源代码,即hello.i文件,通过编译器(ccl)翻译生成描述程序执行指令的汇编代码文件,即hello.s。
  2. 作用:将高级语言进行处理,转换成低级语言,方便程序的进一步翻译与执行。
    3.2 在Ubuntu下编译的命令
    2019 HITICS大作业_第6张图片
    图3.2.1 生成汇编文件所需的命令

3.3 Hello的编译结果解析
hello.s文件内容如下:

2019 HITICS大作业_第7张图片2019 HITICS大作业_第8张图片
图3.3.1 汇编代码内容

  1. 数据
    1.全局变量:由程序源代码可以看出,该程序无全局变量。
    2.局部变量:

i是一个局部变量。局部变量一般存在寄存器或堆栈中。由汇编代码可以得到i存于%ebx中。
在这里插入图片描述
图3.3.2 局部变量i的存储位置
3.字符串:程序中出现了两个字符串,分别为"用法: Hello 学号 姓名 秒数!\n"以及"Hello %s %s\n"。它们都位于只读数据段中。
在这里插入图片描述
图3.3.3 程序中的字符串

2.赋值
i = 0在程序执行循环操作时被赋值。对应指令是:movl $0, %ebx。
mov指令其他类型如下:
2019 HITICS大作业_第9张图片
图3.3.4 mov指令(图片来自CSAPP的PDF版截图)
3.算术操作
常用的算术逻辑指令为:
2019 HITICS大作业_第10张图片
图3.3.5 算术逻辑指令(图片来自CSAPP的PDF版截图)

在汇编代码中主要体现为每次对循环变量i加1.
在这里插入图片描述
4.关系操作
关系操作主要是指cmp指令与test指令,分别用于比较两个数的大小以及判断两个数是否相等。这两个指令不会改变寄存器中的值,只会改变条件码。
在程序中具体指:
1)判断循环变量i是个否满足循环条件
在这里插入图片描述
2)判断argc是否等于4
在这里插入图片描述
5.数组/指针/结构操作
循环体中取数组元素值的操作为:

程序中对argv[1],argv[2]的寻址被编译为基址+偏移的寻址方式。
(%rbp)+8存放的是argv[1],(%rbp)+16存放的是argv[2]。
6.控制转移
控制转移主要为jmp,以及jxx指令。
在程序中,它对应着:
1)在argc不等于4时的条件跳转
在这里插入图片描述
2)在循环变量小于特定值时的跳转
在这里插入图片描述
7.函数操作
程序中的主要函数有:
1.Main函数:被系统函数_libc_start_main调用。内核执行c程序时调用特殊的启动例程,并将启动例程作为程序的起始地址,从内核中获取命令行参数和环境变量地址,执行main函数。

2.Getchar函数:main函数通过call指令调用getchar()函数,且getchar()函数无参数。getchar()函数返回值类型为int,如果成功返回用户输入的ASCII码,出错返回-1。
在这里插入图片描述
图3.3.6 getchar函数的调用
3.Printf函数:printf参数根据字符串中的输出占位符数量来决定的。
在这里插入图片描述
图3.3.7 printf函数的调用
4.Exit函数:使程序根据用户设定好的某种情况代号退出。Exit(1)说明程序以情况1退出。
在这里插入图片描述
图3.3.8 exit函数的调用
5.Sleep函数:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
在这里插入图片描述
图3.3.9 sleep函数的调用

3.4 本章小结
本章主要介绍了编译的概念与作用,并对汇编文件进行了详细的分析。从数据的赋值,到算术逻辑指令,再到函数的调用与控制的传递。汇编代码展示了计算机执行该程序的思想与步骤,帮助我们在机器层面上分析程序的执行过程,值得仔细钻研。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

  1. 概念:汇编主要是指将汇编代码通过汇编器翻译成计算机真正可以识别的二进制机器代码,其文本被称为可重定位目标文件,即hello.o。
  2. 作用:使计算机能够实现真正意义上的理解代码,便于计算机将其与其他文件整合并执行。
    4.2 在Ubuntu下汇编的命令
    2019 HITICS大作业_第11张图片
    图4.2.1 将汇编代码翻译生成可重定位目标文件所需命令

4.3 可重定位目标elf格式
通过命令生成ELF格式文件
2019 HITICS大作业_第12张图片
图4.3.1 生成hello.o的ELF格式文件

其内容如下所示:

图4.3.2 ELF文件内容
1.ELF头:
ELF头首先以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。剩下部分列出了包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小,目标文件的类型(如可重定位,可执行或共享),机器类型(如X86-64),节头部表的文件偏移,以及节头部表中条目的大小和数量。
2019 HITICS大作业_第13张图片2019 HITICS大作业_第14张图片2019 HITICS大作业_第15张图片2019 HITICS大作业_第16张图片2019 HITICS大作业_第17张图片
图4.3.3 ELF头

2.节:
夹在ELF头和节头部表的部分都是节。以下列出部分节的内容
1).text:已编译程序的机器代码,文件中信息如下:
在这里插入图片描述
图4.3.4 .text节

2).rela.text:一个.text节中位置的列表,用于重定位中确定代码对应的绝对物理地址。信息如下:
在这里插入图片描述
图4.3.5 .rela.text节

3).data:存放已初始化的全局和静态C变量。局部C变量在运行时被保存到栈中,既不出现在.data节,也不出现在.bss节。信息如下:
在这里插入图片描述
图4.3.6 .data节

4).bss节:存放未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它只是一个占位符,其目的是提高空间效率。程序运行时,在内存中分配这些变量,初始值为0。信息如下:
在这里插入图片描述
图4.3.7 .bss节

5).rodata节:只读数据,信息如下:
在这里插入图片描述
图4.3.8 .rodata节

6).symtab节:存放在程序中定义和引用的函数和全局变量信息的符号表,不包含局部变量的信息。如下所示:
在这里插入图片描述
图4.3.9 .symtab节

7).strtab节: 一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。字符串表就是已null结尾的字符串序列。
在这里插入图片描述
图4.3.10 .strtab节
综合信息如下:
2019 HITICS大作业_第18张图片
图4.3.11 节头
3.重定位条目
汇编器在遇到对最终位置未知的目标引用时,就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中,已初始化数据的重定位条目放在.rel.data中。条目格式如下:
2019 HITICS大作业_第19张图片

图4.3.12 重定位条目结构体(图片来自CSAPP的PDF版截图)

Offset为节偏移;type为重定位类型(相对引用还是绝对引用);symbol为符号名字;addend为一个常数。

重定位算法如下所示:
2019 HITICS大作业_第20张图片
图4.3.13 重定位算法(图片来自CSAPP的PDF版截图)

重定位节信息如下所示:
2019 HITICS大作业_第21张图片
图4.3.14 重定位节

4.符号表
每个可重定位目标模块都有一个符号表,它包含符号的定义和引用的符号的信息。符号表是由汇编器构造,使用编译器输出到汇编语言.s文件中的符号。每个符号表是一个条目的数组,信息如下所示:
2019 HITICS大作业_第22张图片
图4.3.15 符号表

4.4 Hello.o的结果解析
通过objdump -d -r hello.o命令得到hello.o的反汇编文件
2019 HITICS大作业_第23张图片
图4.4.1 hello.o的反汇编文件

反汇编内容如下:
2019 HITICS大作业_第24张图片
图4.4.2 反汇编文件的内容

我们可以看出反汇编文件asm_hello_o与汇编文件hello.s的区别:

1.反汇编文件中出现了16进制数的机器码,这些16进制数即为每一条汇编代码的对应机器代码。
2019 HITICS大作业_第25张图片
图4.4.3 指令对应机器码

2.反汇编代码中出现了相对偏移地址。作为可重定位目标文件的反汇编,我们可以推导出:可重定位目标文件在重定位之前,其物理地址是不确定的,每一个目标模块的地址都是从0开始,只有在这些模块重定位后,才会确定每个模块的真实物理地址。
2019 HITICS大作业_第26张图片
图4.4.4 地址偏移

3.在反汇编文件中,对每一个符号都表明了引用类型。
2019 HITICS大作业_第27张图片
图4.4.5 引用类型

4.汇编文件中使用段的标号作为分支后跳转的地址,反汇编代码中用相对main函数起始地址的偏移量来表示跳转的地址。
2019 HITICS大作业_第28张图片
图4.4.6 跳转地址

5.汇编文件中函数调用后直接跟着函数的名字,反汇编代码中函数调用的目标地址是当前的下一条指令,也是用相对main起始地址的偏移量来表示。
2019 HITICS大作业_第29张图片 2019 HITICS大作业_第30张图片
图4.4.7 调用函数
4.5 本章小结
本章主要介绍了汇编的概念与作用,介绍了可执行目标文件的相关信息,主要包括ELF头,节,重定位条目,符号表,还比较分析了反汇编文件与汇编文件的不同。可以看出可重定位目标文件就是在为模块整合做准备。
(第4章1分)

第5章 链接

5.1 链接的概念与作用

  1. 概念:链接就是将许多可重定位目标文件进行整合,形成一个能够映射在绝对物理地址上的文件,得到可以被执行的可执行目标文件。
  2. 作用:将hello.c中不需要由程序员编辑定义的标准函数库中相应的函数代码组合到hello.o中,使其能够执行。
    5.2 在Ubuntu下链接的命令
    通过命令:
    ld -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 /usr/lib/gcc/x86_64-linux-gnu/7/crtbegin.o /usr/lib/gcc/x86_64-linux-gnu/7/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o hello.o -lc -z relro -o hello
    将hello.o以及标准库中的函数链接起来。
    2019 HITICS大作业_第31张图片
    图5.2.1 hello.o的链接命令
    5.3 可执行目标文件hello的格式
    2019 HITICS大作业_第32张图片
    图5.3.1 生成hello.out的ELF格式文件

1.ELF头:用于描述文件的总体格式,与可重定位文件不同的是,它还包含了程序的入口点,即程序将要执行的第一条指令的地址。
2019 HITICS大作业_第33张图片
图5.3.2 可执行目标文件的ELF头

2.节
可执行目标文件的节与可重定位目标文件的节有许多相同的,这里列出部分不同的节并说明其作用:
1)interp节:指定动态链接器在操作系统中的位置
2)dynamic节:该节中保存了动态链接器所需要的基本信息,是一个结构数组
3).init节:该节定义了一个小函数_init,程序的初始化代码会调用它。
4).plt 过程链接表,包含动态链接器调用从共享库导入的函数所必须的相关代码,存在于text段中。
5).got节保存全局偏移表。它和.plt节一起提供了对导入的共享库函数访问的入口,由动态链接器在运行时进行修改。
6).dynsym节:保存共享库导入的动态符号信息,在.text段中存储
7).dynstr节:保存动态符号字符串表,存放一系列字符串,代表了符号的名称,以null作为终止符。

具体信息如下:

2019 HITICS大作业_第34张图片
图5.3.3 节头信息

3.程序头:将程序分为数段
具体信息如下:
2019 HITICS大作业_第35张图片
图5.3.4 程序头表

5.4 hello的虚拟地址空间

  1. hello.o经过重定位后成为了可以被执行的可执行目标文件,其ELF的格式信息如下所示:
    2019 HITICS大作业_第36张图片
    图5.4.1 可执行文件的ELF信息

2.虚拟地址各段空间信息:
1)PHDR段:指定程序头表在文件及程序内存映像中的位置和大小
在这里插入图片描述
可以看出:起始地址为0x400040,大小为0x1c0,Edb相应内容为:
2019 HITICS大作业_第37张图片
图5.4.2 PHDR的edb内容

2)INTERP段: 指定要作为解释程序调用的以空字符结尾的路径名的位置和大小
在这里插入图片描述
可以看出:起始地址为0x400200,大小为0x1c,edb相应内容为:
在这里插入图片描述
图5.4.3 INTERP的edb内容

3)LOAD段:指定可装入段,通过p_filesz和p_memsz进行描述。文件中的字节会映射到内存段的起始位置
在这里插入图片描述
可以看出,第一个LOAD段起始地址为0x400000,大小为0x8f4
第二个LOAD段起始地址为0x600e00,大小为0x270
Edb相应内容为:
2019 HITICS大作业_第38张图片2019 HITICS大作业_第39张图片

图5.4.4 LOAD的edb内容

4)DYNAMIC段: 指定动态链接信息
在这里插入图片描述
可以看出,起始地址为0x600e10,大小为0x1e0
2019 HITICS大作业_第40张图片
图5.4.5 DYNAMIC的edb内容

5)NOTE段: 指定辅助信息的位置和大小
在这里插入图片描述
可以看出,起始地址为0x40021c,大小为0x20
在这里插入图片描述
图5.4.6 NOTE的edb内容

6)GNU_STACK段: 权限标志,标志栈是否是可执行的
在这里插入图片描述

7)GNU_RELRO段: 指定在重定位结束之后哪些内存区域需要设置只读
在这里插入图片描述
可以看出,起始地址为0x600e00,大小0x200
在这里插入图片描述
图5.4.7 GNU_RELRO的edb内容

5.5 链接的重定位过程分析
通过objdump命令生成hello的反汇编文件
2019 HITICS大作业_第41张图片
图5.5.1 生成反汇编文件

hello.o的反汇编文件和hello的反汇编文件有许多不同,具体如下:
1)Hello的反汇编文件中多出来许多函数的信息,这是将其他目标模块重定位的结果
2019 HITICS大作业_第42张图片
图5.5.2 hello反汇编文件中的其他函数

2)Hello中的地址为具体地址,而hello.o中的地址为地址偏移
2019 HITICS大作业_第43张图片
图5.5.3 地址表示不同

3)Hello.o中存在许多用于重定位的机器码占位符,而hello中无此项,因为已经完成重定位了
2019 HITICS大作业_第44张图片2019 HITICS大作业_第45张图片

图5.5.4 占位符消失

5.6 hello的执行流程
Hello执行流程如下:
程序加载 ld-2.27.so!_dl_start 0x7f5d6118fea0
ld-2.27.so!_dl_init 0x7f5d6119e630
程序运行 hello!_start 0x4005c0
hello!puts@plt 0x400560
程序退出 hello!exit@plt 0x4005a0

5.7 Hello的动态链接分析
在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定,将过程地址的绑定推迟到第一次调用该过程时。
延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:
1.PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
2.GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
在这里插入图片描述

图5.7.1 .got.plt节

在节头表中找到GOT的起始位置为0x601000
调用_dl_start之前可以看出有16个为0的字节:
在这里插入图片描述

在调用之后字节值发生了变化

在这里插入图片描述
GOT[[2]]是动态链接器在ld-linux.so模块中的入口点,共享库模块信息如下:
2019 HITICS大作业_第46张图片
图5.7.2 共享库模块

5.8 本章小结
本章主要介绍了链接的概念作用,分析可执行文件hello的ELF格式及其虚拟地址空间,同时通过实例分析了重定位过程、加载以及运行时函数调用顺序以及动态链接过程,深入理解了链接和重定位的过程。

(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用

  1. 概念:进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量、以及打开文件描述符的集合。
    2 .作用:我们会得到一种假象,好像我们的程序是当前唯一运行的程序,我们的程序独占处理器和内存,我们程序的代码和数据好像是系统内存中唯一的对象
    6.2 简述壳Shell-bash的作用与处理流程
    Shell是一个命令行解释器,它输出一个提示符,等待输入一个命令行,然后执行这个命令。
    它会首先解析以空格分隔的命令行参数,并构造最终会传递给execve的argv向量,然后它会判断命令是否为shell内置命令,如果是,则解释,如果不是,则判定它是待执行的程序,shell会创建一个子进程,在该进程的上下文中运行该程序。
    6.3 Hello的fork进程创建过程
    在终端中输入./hello 学号 姓名 秒数,shell判断它不是内置命令,于是会加载并运行当前目录下的可执行文件hello。此时shell通过fork创建一个新的子进程。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库和用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。子进程与父进程有不同的pid。fork被调用一次,返回两次。在父进程中fork返回子进程的pid,在子进程中fork返回0。二者并发执行,相互独立。
    6.4 Hello的execve过程
    execve函数加载并运行hello,当加载器运行时,它创建一个内存映像。在程序头部表的引导下,加载器将可执行文件的片复制到代码段和数据段,接下来,加载器跳转到程序的入口点,即_start函数的地址,这个函数是在系统目标文件ctrl.o中定义的,对所有的c程序都一样。_start函数调用系统启动函数_libc_start_main,该函数定义在libc.so里,初始化环境,调用用户层的main函数,处理main函数返回值,并且在需要的时候返回给内核。
    6.5 Hello的进程执行
    系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
    系统会同时进行多个进程的执行,在一个进程执行时,其他进程处于挂起状态,好像每个进程独占CPU,它被称为逻辑控制流。
    2019 HITICS大作业_第47张图片
    图6.5.1 逻辑控制流(图片来自CSAPP的PDF版截图)

此外,进程也为每个程序提供一个他们独占内存的假象,称为私有地址空间。
2019 HITICS大作业_第48张图片
图6.5.2 私有地址空间(图片来自CSAPP的PDF版截图)

在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程的决定叫做调度,这一过程称为上下文切换。
2019 HITICS大作业_第49张图片
图6.5.3 上下文切换(图片来自CSAPP的PDF版截图)

6.6 hello的异常与信号处理

  1. 异常:控制流的突变,用来响应某种变化。
    异常分为4类:
    1)中断:异步异常,来自处理器外部的I/O设备。异常处理后会执行下一条指令
    2)陷阱:同步异常,是执行系统调用函数的结果。函数调用结束后会执行下一条指令
    3)故障:同步异常,由错误情况引起,如缺页,浮点异常等等。异常处理成功则重新执行该指令,否则程序终止
    4)终止:同步异常,由致命错误造成。该异常将终止程序

  2. 信号处理:在发生异常时会产生信号,用来通知系统。
    常用信号为:
    2019 HITICS大作业_第50张图片
    图6.6.1 信号类型(图片来自CSAPP的PDF版截图)

3.在输入./hello 学号 姓名 循环间隔后,对进程的几种处理方式:

1)程序正常退出:字符串输出8次后正常退出,进程被回收

2)Ctrl+c:使内核发送一个SIGINT信号,使程序终止,信号处理程序会回收子进程

3)随便乱按:输入字符后键入回车键会将字符串内容存入缓冲区,作为终端的下一条命令

4)Ctrl+z:程序将被暂时挂起,并没有被回收,它会等待其他信号令其继续运行
6.7本章小结
本章主要介绍了进程的概念与作用,shell的作用和处理流程,执行hello时的fork和execve过程。并分析了hello的进程执行和异常与信号处理过程。了解了几种命令对程序可能造成的影响。

(第6章1分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

  1. 逻辑地址:在有地址变换功能的计算机中,访内指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。hello.o中的相对偏移地址即为逻辑地址。
  2. 线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址,是连续的。
  3. 虚拟地址:程序访问存储器所使用的逻辑地址称为虚拟地址。虚拟地址通过页表与物理地址对应,即页表为虚拟地址到物理地址的映射。大小为2^N
  4. 物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址。它对应系统的内存字节,大小不一定是2的次幂。
    7.2 Intel逻辑地址到线性地址的变换-段式管理
    一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
    7.3 Hello的线性地址到物理地址的变换-页式管理
    虚拟地址通常被分为2部分,一部分用于存储虚拟页面的偏移量(VPO),一部分用来存储虚拟页面的页号(VPN)。
    页表是虚拟地址到物理地址的映射,以PTE数组存储,每个数组元素为对应的物理页号(PPN)。虚拟地址将VPN传至页表,根据对应的PTE找到虚拟地址对应的PPN,在通过VPO与PPN拼接得到物理地址。
    2019 HITICS大作业_第51张图片
    图7.3.1 虚拟地址通过页表与物理地址联系(图片来自CSAPP的PDF版截图)
    7.4 TLB与四级页表支持下的VA到PA的变换
    TLB是页表的缓存,其作用就是使系统能够更快地翻译地址。虚拟地址的VPN会首先传递至TLB,看是否存在缓存命中,如果命中,则直接得到PPN,不命中则取页表中寻找,并将结果存入TLB。
    多级页表中,除最后一级页表外,每一个页表的数组元素都是下一级的一整个页表,这样做的目的是节省空间。
    2019 HITICS大作业_第52张图片
    图7.4.1 多级页表支持下的地址翻译(图片来自CSAPP的PDF版截图)
    7.5 三级Cache支持下的物理内存访问
    Core i7就是通过四级页表翻译地址,三级cache访问物理内存,如下图所示:
    2019 HITICS大作业_第53张图片
    图7.5.1 CPU示意图(图片来自CSAPP的PDF版截图)

处理流程如下:
2019 HITICS大作业_第54张图片
图7.5.2 访问数据的过程(图片来自CSAPP的PDF版截图)

CPU传递虚拟地址至MMU,MMU首先将VPN传递至TLB,查看是否存在缓存命中,如果命中,则直接生成PPN,如果不命中,则将VPN传递至页表,通过4级页表查找PPN,并将结果写入TLB中。PPN与PPO(即VPO)拼接得到物理地址。物理地址载利用cache访问数据,若命中,则将结果传递至CPU,如果不命中,则在主存中查找数据,并将数据存入cache中。

7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的pid。为了给这个新进程创建虚拟内存。它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记位只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面。
7.7 hello进程execve时的内存映射
execve函数在hello进程中加载并运行hello,主要步骤如下:
1.删除已存在的用户区域
2.映射私有区域
3.映射共享区域
4.设置程序计数器

映射空间区域如下:
2019 HITICS大作业_第55张图片
图7.7.1 程序映射的空间区域(图片来自CSAPP的PDF版截图)
7.8 缺页故障与缺页中断处理
虚拟内存中,页表不命中即为缺页。缺页处理如下:
1.控制传递给操作系统内核中的缺页异常程序
2.缺页异常处理程序确定出物理内存中牺牲页,如果这个页面已经被修改了,则把它换出到磁盘
3.缺页异常处理程序调用新的页面,更新PTE
4.控制转移至原来的进程,重新执行导致缺页的指令,这次会得到一个页命中

5.缺页处理程序不是直接就替换,它会经过一系列的步骤:
1) 虚拟地址是合法的吗?如果不合法,它就会触发一个段错误
2) 试图进行的内存访问是否合法?意思就是进程是否有读,写或者执行这个区域的权限
3) 经过上述判断,这时才能确定这是个合法的虚拟地址,然后才会执行上述的替换。
2019 HITICS大作业_第56张图片
图7.8.1 缺页情况(图片来自CSAPP的PDF版截图)
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
1.隐式空闲链表:
2019 HITICS大作业_第57张图片
图7.9.1 隐式空间链表(图片来自CSAPP的PDF版截图)

空闲块通过头部中的大小字段隐含地连接着。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。
1)放置策略:首次适配、下一次适配、最佳适配。
首次适配从头开始搜索空闲链表,选择第一个合适的空闲块。下一次适配从上一次查询结束的地方开始。最佳适配检查每个空闲块,选择适合所需请求大小的最小空闲块。
2)合并策略:立即合并、推迟合并。
立即合并就是在每次一个块被释放时,就合并所有的相邻块;推迟合并就是等到某个稍晚的时候再合并空闲块。

带边界标记的合并:
2019 HITICS大作业_第58张图片
图7.9.2 带边界标记链表(图片来自CSAPP的PDF版截图)

在每个块的结尾添加一个脚部,分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态,从而使得对前面块的合并能够在常数时间之内进行。

2.显式空闲链表
2019 HITICS大作业_第59张图片
图7.9.3 显示空间链表(图片来自CSAPP的PDF版截图)

每个空闲块中,都包含一个pred(前驱)和succ(后继)指针。使用双向链表使首次适配的时间减少到空闲块数量的线性时间。
空闲链表中块的排序策略:一种是用后进先出的顺序维护链表,将新释放的块放置在链表的开始处,另一种方法是按照地址顺序来维护链表,链表中每个块的地址都小于它后继的地址。
分离存储:维护多个空闲链表,每个链表中的块有大致相等的大小。将所有可能的块大小分成一些等价类,也叫做大小类。
分离存储的方法:简单分离存储和分离适配。
7.10本章小结
本章主要介绍了程序的存储结构,通过段式管理在逻辑地址到虚拟地址,页式管理从虚拟地址到物理地址。程序访问过程中的cache结构和页表结构,进程如何加载自己的虚拟内存空间,内存映射和动态内存分配。

(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法
设备的模型化:所有的IO设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数

  1. read 和 write – 最简单的读写函数;
  2. readn 和 writen – 原子性读写操作;
  3. recvfrom 和 sendto – 增加了目标地址和地址结构长度的参数;
  4. recv 和 send – 允许从进程到内核传递标志;
  5. readv 和 writev – 允许指定往其中输入数据或从其中输出数据的缓冲区;
  6. recvmsg 和 sendmsg – 结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力。
    8.3 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;
    }
    vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。接着从vsprintf生成显示信息,到write系统函数,直到陷阱系统调用 int 0x80或syscall。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
    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;
    }
    异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
    getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
    8.5本章小结
    本章主要介绍了Linux的IO设备管理方法,unix IO接口及其函数,分析了printf函数和getchar函数。

(第8章1分)

结论

Hello程序的一生:
1.被程序员编写成程序源代码,该文件称为hello.c
2.Hello.c文件中的编译预处理指令所指内容经过预处理器被穿插在程序源代码中,成为修改过的源程序,称为hello.i
3.Hello.i经过编译器生成代表程序执行顺序的指令语言——汇编程序,其文件称为hello.s
4.hello.s经过汇编器翻译成可重定位目标文件,用于之后的模块整合
5.Hello.o经过与其他目标模块整合,形成可执行目标文件hello
6.创建进程:在shell利用./hello运行hello程序,父进程通过fork函数为hello创建进程
7.加载程序:通过加载器,调用execve函数,删除原来的进程内容,加载我们现在进程的代码,数据等到进程自己的虚拟内存空间。
8.执行指令:CPU取指令,顺序执行进程的逻辑控制流。这里CPU会给出一个虚拟地址,通过MMU从页表里得到物理地址, 在通过这个物理地址去cache或者内存里得到我们想要的信息
9.异常(信号):程序执行过程中,如果从键盘输入Ctrl-C等命令,会给进程发送一个信号,然后通过信号处理函数对信号进行处理。
10.结束:程序执行结束后,父进程回收子进程,内核删除为这个进程创建的所有数据结构

计算机程序的执行是一个复杂的过程,我们学习的只能说是看到了计算机复杂机制的冰山一角。我们只是从比较宽泛,普遍的角度来遵循一个程序的开始到结束。计算机本身的运行机制也是十分复杂,从基础的门电路,到一些基本的逻辑部件,再到由许多部件组成的集成电路,通过这些硬件大厦来构建操作系统与应用软件。未知的知识还有许多。总而言之,计算机世界的大门才刚刚打开,我们需要学习的事物还有很多。

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

附件

1.hello.c 程序的原始代码
2.hello.i 将编译预处理指令内容插入源代码后的文件
3.hello.s 记录汇编代码的汇编文件
4.hello.o 可重定位目标文件
5.hello 可执行目标文件
6.asm_hello_o hello.o的反汇编文件
7.asm_hello hello的反汇编文件
8.ELF_hello_o hello.o的ELF格式文件
9.ELF_hello hello的ELF格式文件

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

参考文献

[1] 兰德尔E.布莱恩特 大卫R.奥哈拉伦. 深入理解计算机系统(第3版)
[2] ELF文件-段和程序头:https://blog.csdn.net/u011210147/article/details/54092405
[3] 编译 百度百科:https://baike.baidu.com/item/编译/1258343?fr=aladdin
[4] 键盘的中断处理
https://blog.csdn.net/xumingjie1658/article/details/6965176

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

你可能感兴趣的:(2019 HITICS大作业)