程序人生-Hello’s P2P

摘 要
[摘 要] 本文集中阐述了hello.c在linux系统下通过预处理,编译,汇编,链接等过程,分别讲述了程序在转化为进程,并生存和消亡的全过程。在Linux下利用相关工具,如objdump、readelf和edb,依次解读了各个步骤中所生成的中间文件(hello.i、hello.s、hello.o和hello)。以此为基础,总结并讨论了hello的进程管理、存储管理和IO管理中的相关内容。
[关键词] 计算机系统;hello;预处理; 编译;汇编链接;进程;虚拟内存;I/O

目 录

第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过程:
1.程序员编写原始代码,形成最初的hello.c
2.由预处理器(cpp)进行预编译,根据#开头的命令进行代码的修改,形成预编译后的hello.i
3.由编译器(cll)将hello.i翻译成汇编程序hello.s
4.由汇编器(cs)将hello.s翻译成机器语言并打包成可重定位目标文件hello.o
5.由链接器(ld)合并获得可执行目标文件hello
6.由shell启动程序,调用fork创建子进程,至此完成P2P过程
②O2O过程:
1.由shell通过execve在fork产生的子进程中加载hello
2.映射虚拟函数,进入程序的入口,载入物理内存,执行main函数
3.hello通过IO管理来输入输出
4.程序结束后,shell回收hello进程,内核删除hello的所有痕迹,至此完成O2O过程
1.2 环境与工具
①硬件环境:X64 CPU;2.2GHz;32G RAM;1024GHD Disk
②开发与调试工具:Windows10 64位;Vmware 11以上;Ubuntu 19.04 LTS 64位;Visual Studio 2019 64位;CodeBlocks 64位;vi/vim/gedit+gcc;edb
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名称 文件作用
hello.c C代码
hello.i 预处理的文本文件
hello.s 汇编后文本文件
hello.o 可重定位文件
hello 可执行程序
hello.elf hello的elf文件
hello_elf.txt hello.o查看elf信息生成的相关文本。
hello_od.s hello.o 的反汇编
a.out hello.o链接库文件产生的可执行程序
hello_o.elf hello.o 的elf信息
hello.elf a.out的elf信息
hello_d.s a.out 的反汇编

1.4 本章小结
本章主要介绍了hello的p2p,o2o的过程以及实验的环境,软件。列出了为编写本论文,生成的中间结果文件的名字,文件的作用等。

第2章 预处理
2.1 预处理的概念与作用
①预处理的概念:
预处理指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。由预处理器对程序的源代码文本进行处理,这个过程对源代码没有任何解析。预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。结果是得到另一个C程序,通常是以.i作为文件扩展名。
②预处理的作用:
预处理主要有三个方面的内容:
1.宏定义:将宏名替换为文本(字符串或代码)。
2.文件包含:预处理程序将查找指定的被包含文件,并将其复制插入到#include命令出现的位置上。比如hello.c中第1行的#include命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插人程序文本中。
3.条件编译:有些语句希望在条件满足时才编译,预处理过程中根据条件决定需要编译的代码。
2.2在Ubuntu下预处理的命令
使用的命令为:gcc -m64 -no-pie -fno-PIC -E -o hello.i hello.c
处理结果为生成一个hello.i文件,如图2-1所示
在这里插入图片描述
图2-1

2.3 Hello的预处理结果解析
生成的hello.i的文本内容如图2-2所示。共3042行。源程序的代码出现在hello.i的末尾,如图2-3所示。从文本中也可以看到,预处理的过程会将include的内容处理并加入代码当中。如stdio.h库的全部内容。当处理
在这里插入图片描述
图2-2
在这里插入图片描述
图2-3
2.4 本章小结
本章阐述了预处理的概念及作用(宏定义;文件包含;条件编译)。通过对hello.c的预处理生成了hello.i文件并进行了分析。

第3章 编译
3.1 编译的概念与作用
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
①概念:
编译是编译器(ccl)将文本文件hello.i翻译成文本文件hello.s的过程, hello.s包含一个汇编语言程序。
②作用:
编译主要有以下几个过程:1.词法分析 2.语法分析 3.语义分析 4.源代码优化 5.代码生成,目标代码优化。
在编译阶段,gcc编译器通过检查代码的规范性、正确性等,检查无误后,编译器将文本文件hello.i翻译成文本文件hello.s,这份文本文件包含了一个汇编语言程序。这是一种比较低级的机器语言指令,汇编语言为高级语言的编译器提供相同的输出语言。
3.2 在Ubuntu下编译的命令
所使用的命令为gcc -m64 -no-pie -fno-PIC -S -o hello.s hello.i。生成了hello.s文件,如图3-1所示。
在这里插入图片描述
图3-1

3.3 Hello的编译结果解析
3.3.1数据类型
①字符串类型:
汇编文件hello.s中,共有两个字符串:“Usage: Hello 学号 姓名!\n"和"Hello %s %s\n”都存在只读数据段中,均作为printf参数,,对应中文字符可以看出对中文字符进行了utf-8编码,中文汉字以‘\347’开头,占三个字符,而全角字符‘!’占用两个字符。
在这里插入图片描述
图3-2
②整型:
在这里插入图片描述
图3-3

程序中片段如下:
在这里插入图片描述
图3-4

变量i存放在一个内存空间中(-4(%rbp)),对于局部变量,只有在程序开始使用他(初始化)的时候才会申请一个内存空间。变量的类型转换是隐式转换。
③数组:在hello.c中有对数组的应用,如图3-4所示。在hello.i中的体现如图3-5所示。
在这里插入图片描述
图3-5
在程序中有一个数组argv[],argv[[1]]和argv[[2]]作为for循环中printf的参数。
由取argv[[1]]和argv[[2]]值的汇编语句可知argv的首地址是-32(%rbp)。argv[2]作为printf函数的第三个参数,应当存于寄存器%rdx中,因此可推断argv[2]地址为-0x16(%rbp);argv[1]作为printf第二个参数,应当存于寄存器%rsi中,因此可推断argv[1]地址为-0x2A(%rbp)中,数组首地址位于-0x32(%rbp) ,以上所占字节数为8。
3.3.2赋值操作
程序中涉及的赋值操作有i=0,因为 i 是 4B 的 int 类型,所以使用 movl 将初值0赋给-4(%rbp),汇编代码如图3-6所示
在这里插入图片描述
图3-6
3.3.3算术操作
Hello.c中的主要的算术操作为循环遍历增加(i++)以及语句argc!=4
编译器将i++翻译为addl$1,-4(%rbp),如图3-8所示:
在这里插入图片描述
图3-7
3.3.4关系操作
在hello.c中出现的关系操作类型有:!=,<,如图3-8所示
在这里插入图片描述
图3-8
其中argc!=4被编译为cmpl $4, -20(%rbp),如图3-9所示
在这里插入图片描述
图3-9
i<8被编译为cmpl $7, -4(%rbp),如图3-10所示
在这里插入图片描述
图3-10
3.3.5数组/指针/结构操作
程序中对argv[[1]],argv[[2]]的寻址被编译为基址+偏移的寻址方式。
-32(%rbp)存放的是argv的首地址,在首地址上+8,+16得到argv[[1]],argv[[2]]的地址。
3.3.6控制转移
c文件中有if执行的跳转和for循环执行的跳转,在.s文件中,都是通过cmp和jxx指令组合实现的
3.3.7函数操作
hello.c中出现了如下甘薯:
①int main(int argc, char *argv[])
参数传递与函数调用:内核执行c程序时调用特殊的启动例程,并将启动例程作为程序的起始地址,从内核中获取命令行参数和环境变量地址,执行main函数。
函数退出:hello.c中main函数有两个出口,第一个是当命令行参数数量不为3时输出提示信息并调用exit(1)退出main函数;第二个是命令行参数数量为3执行循环和getchar函数后return 0的方式退出函数。
函数栈帧结构的分配与释放:main函数通过pushq %rbp、movq %rsp, %rbp、subq $32, %rsp 为函数分配栈空间,如果是通过exit函数结束main函数则不会释放内存,会造成内存泄露,但是程序如果通过return正常返回则是由指令leave即mov %rbp,%rsp,pop %rbp恢复栈空间。
②exit():
参数传递与函数调用:在hello.c中设置%edi值为0表示赋给exit函数第一个变量,然后通过call函数调用exit()。
③printf()
参数传递与函数调用:printf参数根据字符串中的输出占位符数量来决定的。
④getchar()
参数传递与函数调用:main函数通过call指令调用getchar()函数,且getchar()函数无参数。
函数返回:getchar()函数返回值类型为int,如果成功返回用户输入的ASCII码,出错返回-1。

3.4 本章小结
本章主要介绍了hello.i编译为hello.s的具体操作,阐述了hello.c中的各种操作及数据类型在hello.s中的具体体现,各部分的表达式。主要对编译命令和编译结果进行解析,并加以说明。

第4章 汇编
4.1 汇编的概念与作用
①概念:
汇编过程是将编译后hello.s中的汇编语言通过汇编器翻译为机器指令的过程。汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。
②作用:
将汇编代码转变为机器指令
4.2 在Ubuntu下汇编的命令
所使用的命令为gcc -m64 -no-pie -fno-PIC -c -o hello.o hello.s。在Ubuntu下的操作截图如4-1所示,执行完该命令后可以发现文件夹中多出了一个hello.o的文件。
在这里插入图片描述
图4-1
4.3 可重定位目标elf格式
使用readelf -a hello.o指令查看hello.o的ELF格式相关信息:
①ELF Header:
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,节头部表中条目的大小和数量等。如图4-2
在这里插入图片描述
图4-2
②Section Headers:节头部表,包含了文件中出现的各个节的语义。记录了每个节的名称、类型、属性(读写权限)、在ELF文件中所占的长度、对齐方式和偏移量。如图4-3
在这里插入图片描述
图4-3
③重定位节.rela.text ,一个.text 节中位置的列表,包含.text 节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。如图 4.4,图中 8 条重定位信息分别是对.L0(第一个 printf 中的字符 串)、puts 函数、exit 函数、.L1(第二个 printf 中的字符串)、printf 函数、 sleepsecs、sleep 函数、getchar 函数进行重定位声明。如图4-4。重定位条目告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。如图,偏移量是需要被修改的引用的节偏移,符号标识被修改引用应该指向的符号。类型告知链接器如何修改新的引用,加数是一个有符号常数,一些类型的重定位要用它对被修改引用的值做偏移调整。
在这里插入图片描述
图4-4
④符号表。它存放在程序中定义和引用的函数和全局变量的信息,.symtab符号表不包含局部变量的条目。如图4-5所示。

在这里插入图片描述
图4-5
4.4 Hello.o的结果解析
反汇编查看hello.o和hello.s的内容对比如图4-6,4-7所示。
在这里插入图片描述
图4-6
在这里插入图片描述
图4-7
对照分析:
①操作数:
hello.s中的操作数是进制,hello.o反汇编代码中的操作数是十六进制。
②分支转移:
跳转语句之后,hello.s中使用段的标号(如:.L3)作为分支后跳转的地址,反汇编代码中用相对main函数起始地址的偏移表示跳转的地址。
③函数调用:
hello.s中函数调用后直接跟着函数的名字,而反汇编代码中call指令之后是函数的相对偏移地址。因为函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。在机器语言中call后的地址为全0.在重定位节中有对应的重定位条目,链接之后确定地址。
④全局变量:
hello.s中使用段名称+%rip访问,反汇编代码中使用0+%rip访问。机器语言中待访问的全局变量地址为全0.在重定位节中有对应的重定位条目,链接之后确定地址。访问数据时,hello.s依靠段名称和标记来确定访问的内容,helloo_disas.txt中通过重定位的绝对引用确定地址来访问。
4.5 本章小结
本章通过对汇编后产生的 hello.o 的可重定位的 ELF 格式的考察、对重定位项目的举例分析以及对反汇编文件与 hello.s 的对比,从原理层次了解了汇编这一过程实现的变化。

第5章 链接
5.1 链接的概念与作用
①概念:
链接是将各种代码和数据片段收集并组合称为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
②作用:
将函数库中相应的代码组合到目标文件中。
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/x86_64-linux-gnu/crtn.o hello.o -lc -z relro -o hello -lpthread生成可执行目标文件。如图5-1所示。
在这里插入图片描述
图5-1
5.3 可执行目标文件hello的格式
①ELF Header
在这里插入图片描述
图5-2
②Section Headers
在这里插入图片描述
图5-3

5.4 hello的虚拟地址空间
反汇编结果中.text节的机器代码与edb中使用Data Dump查看对应地址空间各段信息相同。
在这里插入图片描述
图5-4

5.5 链接的重定位过程分析
以main函数部分为例分析,如图5-5所示
在这里插入图片描述
图5-5
可以看出,最左边一列的地址已更改:hello.o是从0开始的两位数字,而hello是使用虚拟内存空间的地址(从0x400000开始的数字)。 最初在hello.o中设置的重定位条目,例如被调用函数没有明确的调用地址(全为0),这时,链接完成后,将其填充到虚拟内容空间中的显式地址中 。
在这里插入图片描述
图5-6
从图5-6中的表中可以看出:从该表中,我们可以看到该表确实将一些函数存储在main函数中调用的共享库中。特别是,.plt,即PLT [0],该命令是对GOT [0],GOT [1]的操作,用于将动态链接器地址压入堆栈并跳转到重定位表。请注意,PLT中的每个条目为16个字节,GOT表中的每个条目为8个字节。刚刚好。 ①关于节数:hello.o中只有一个.text节,并且只有一个主要函数,函数地址也是默认的0x000000。 Hello中的.init,.plt和.text分为三个部分,每个部分都有许多功能。这些外部函数都是我们用来链接命令的链接系统对象文件(crti.o,crtend.o等)。的。还需要注意.plt节,即进程链接表。该表与GOT(全局偏移表)结合使用,以通过延迟绑定机制在共享库中动态重定位功能。 ②函数调用:hello.o中的函数调用为main加相对偏移量,该函数的虚拟地址直接在hello中使用。 ③Hello中的地址全部替换为虚拟地址,请参见ELF文件中的段表。 ④链接后,Hello.o中的重定位条目不再显示在hello中。
5.6 hello的执行流程
函数名 地址
在这里插入图片描述

5.7 Hello的动态链接分析
对于动态共享链接库中的PIC函数,编译器无法预测该函数的运行时地址,因此您需要添加重定位记录并等待动态链接器处理。 为避免在运行时修改调用模块的代码段,链接器使用延迟绑定策略。
在这里插入图片描述
图5-7
5.8 本章小结
本章了解了链接的概念作用,分析可执行文件 hello 的 ELF 格式及其虚拟地址空间,同时通过实例分析了重定位过程、加载以及运行时函数调用顺序以及动态链接过程,深入理解链接和重定位的过程。

第6章 hello进程管理
6.1 进程的概念与作用
①概念:
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。 ②作用:
进程的概念为我们提供这样一种假象,就好像我们的程序是系统中当前运行的唯一程序一样,我们的程序好像是独占地使用处理器和内存,处理器好像是无间断地一条接一条地执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
作用:作为C编写的程序,它是用户使用Linux的桥梁。Shell是一种应用程序,它为用户访问操作系统内核提供了一个交互界面。用户可以通过shell向操作系统发出请求,操作系统选择执行命令。
处理流程:
①用户键入命令,shell读取该命令。
②通过praseline builtin函数分割字符串,获取命令。
③判断是否为内置命令,若是,则立即执行。
④若不是,则调用相应的程序为其分配子进程执行。
⑤Shell可以异步接收来自I/O设备的信号,并对这些中断信号进行处理。
6.3 Hello的fork进程创建过程
当在终端中输入./hello 学号 姓名时。shell会通过上述流程处理,首先判断出它不是内置命令,所以会认为它是一个当前目录下的可执行文件hello。在加载此进程时shell通过fork创建一个新的子进程。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库和用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。子进程与父进程有不同的pid。父进程和子进程是并发运行的独立进程,内核可以任意方式交替执行他们的逻辑控制流中的指令,所以这会导致我们不能简单的凭直觉判断指令执行的顺序。父进程会默认等待子进程执行完之后回收子进程,但是也会有产生僵死进程的情况,父进程可以调用waitpid函数等待其子进程终止或停止
6.4 Hello的execve过程
execve函数在新创建的子进程的上下文中加载并运行hello程序。execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。只有发生错误时execve才会返回到调用程序。所以,execve调用一次且从不返回。
加载并运行hello需要以下几个步骤:
①删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。
②映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。
③映射共享区域。如果hello程序与共享对象链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
④设置程序计数器。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时,它将从这个入口点开始执行。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
进程上下文包含了进程执行需要的所有信息:用户地址空间(包括程序代码,数据,用户堆栈等);控制信息(进程描述符,内核堆栈等)硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
为了控制进程的执行,内核挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,从而完成上下文切换;中断CPU上执行的进程前后是在同一个进程上下文中,只是由用户态转向内核态执行。
6.6 hello的异常与信号处理
程序正常输出结果如图6-1:输出8次hello,每次输出的时间间隔取决于用户输入的秒数。
在这里插入图片描述
图6-1
①乱按:乱按的结果会实时显示在输出结果中,如图6-2
在这里插入图片描述
图6-2
②回车:回车被认定为换行,实时显示,如图6-3
在这里插入图片描述
图6-3
③Ctrl-Z:输入ctrl-z会发送一个SIGTSTP信号到当前进程组中的每一个进程,
默认情况下结果是停止(挂起)前台作业,如图6-4
在这里插入图片描述
图6-4
④Ctrl-C:程序被终止,如图6-5
在这里插入图片描述
图6-5
⑤ps:内置函数,用于查看当前正在运行的一些进程(部分),如图6-6
在这里插入图片描述
图6-6

⑥jobs:内置函数,用于查看任务列表,如图6-7
在这里插入图片描述
图6-7
⑦pstree:内置函数,用于查看进程树,如图6-8
在这里插入图片描述
图6-8
⑧fg:内置函数,用于将一些停止的进程恢复到前台运行,向处于停止状态的进程发送SIGCONT信号,恢复这些进程,并在前台开始运行,如图6-9
在这里插入图片描述
图6-9
⑨kill:如果pid大于零,则kill函数发送信号号码sig给进程pid;如果pid等于零,那么kill发送信号sig给调用进程所在进程组中的每个进程,包括调用进程自己;如果pid小于零,kill发送信号sig和进程组|pid|中的每个进程,如图6-10
在这里插入图片描述
图6-10
6.7本章小结
本章介绍了hello程序是如何在我们的计算机中运行的。Hello是以进程的形式运行,每个进程都处在某个进程的上下文中,每个进程也都有属于自己的上下文,用于操作系统进行进程调度。用户通过shell和操作系统交互,向内核提出请求,shell通过fork函数和execve函数来运行可执行文件。操作系统中有一套异常控制的系统,用于保障程序运行。异常的种类分为较低级的中断,终止,陷阱和故障,还有较高级的上下文切换和信号机制。

第7章 hello的存储管理
7.1 hello的存储器地址空间
①逻辑地址:
由程序产生的与段相关的偏移地址部分。在这里是hello.o里的相对偏移地址。
②线性地址:
地址空间是一个非负整数地址的有序集合,如果地址空间中的整数是连续的,那么它是一个线性地址空间。在这里是hello里的虚拟内存地址。
③虚拟地址:
CPU通过生成一个虚拟地址。在这里也是hello里的虚拟内存地址。
④物理地址:
用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。计算机系统的主存被组织成一个由M 个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。在这里是hello在运行时虚拟内存地址对应的物理地址。
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理通过段寄存器(如CS等)与偏移地址(16/32/64,EA)的组合来完成逻辑地址到线性地址的变换。在实模式下,逻辑地址CS: EA所转换得到的物理地址为CS * 16 + EA。在保护模式下,以段描述符作为下标,到GDT/LDT表中查表获得短地址,将段地址加上偏移地址,得到线性地址,完成转换。转换的过程如图7-1
在这里插入图片描述
图7-1
7.3 Hello的线性地址到物理地址的变换-页式管理
虚拟内存被组织为N个连续字节大小的单元的阵列,存储在磁盘上。 每个字节都有一个唯一的虚拟地址作为数组的索引。 磁盘上的数据分为多个块,这些块充当磁盘和主存储器之间的传输单元。 虚拟页是分为固定大小的块的虚拟内存。 物理内存分为物理页,其大小与虚拟页的大小相同。
在这里插入图片描述
图7-2
7.4 TLB与四级页表支持下的VA到PA的变换
TLB是一个小的虚拟可寻址缓存,其中每一行都包含一个由单个PTE组成的块,TLB通常具有高度的关联性。 对于虚拟地址,如果TLB命中,则MMU从TLB中获取PTE,否则它将在L1缓存中查找PTE。 使用多级页表,仅第一级页表需要驻留在内存中,其他页表可以根据需要创建,传入或传出。 虚拟地址分为多个VPN和一个VPO。 在四级页表中,需要访问四个页表以获得PPN并连接到PPO以获取物理地址。
在这里插入图片描述
图7-3
7.5 三级Cache支持下的物理内存访问
图7-4显示了Intel Core i7 L1的缓存结构。 64组,每组8行,每行64个字节,我们刚得到的物理地址,PPN和组索引CT相等,通过CT查找相应的组,继续使用标记位CI和偏移位CO查找相应的内存 。
在这里插入图片描述
图7-4
7.6 hello进程fork时的内存映射
Linux将虚拟内存区域与磁盘上的对象相关联,以初始化该虚拟内存区域的内容。此过程称为内存映射。 虚拟内存区域可以映射到两种类型的对象之一。
当前进程调用fork函数时,内核会为新进程创建各种数据结构,并为其分配唯一的PID。 为了为此新进程创建虚拟内存,它创建了当前进程的mm_struct,段结构和页表的完整副本。 它将两个进程中的每个页面都标记为只读,并将两个进程中的每个段结构都标记为私有写时复制。
当fork在新进程中返回时,新进程的虚拟内存与调用fork时存在的虚拟内存完全相同。 当这两个进程中的任何一个稍后写入时,写时复制机制将创建一个新页。因此,将为每个进程维护私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
Execve需要执行得步骤如下:
①删除已存在的用户区域:
②删除当前进程虚拟地址的用户部分中的已存在的区域结构。
③映射私有区域:
为新程序的代码、数据、bss 和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域是请求二进制零的,映射到匿名文件,其大小包含在 hello 中,栈和堆地址也是请求二进制零的,初始长度为零。
④映射共享区域:
hello 程序与共享对象 libc.so 链接,libc.so 是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
⑤设置程序计数器(PC):
execve 做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页.这个异常导致控制转移到内核的缺页处理程序,处理程序随后就执行下面的步骤:
①处理器将虚拟地址发送给MMU
②MMU使用内存中的页表生成PTE地址
③有效位为零,因此MMU触发缺页异常
④缺页处理程序确定物理内存中牺牲页(若页面被修改,则换出到磁盘)
⑤缺页处理程序调入新的页面,并更新内存中的PTE
⑥缺页处理程序返回到原来进程,再次执行缺页的指令
7.9动态存储分配管理
动态内存分配器维护进程的虚拟内存区域,称为堆。不同系统之间的细节有所不同,但不会失去多功能性。假定堆是一个请求二进制零的区域,该区域在未初始化的数据区域之后立即开始并向上增长(到更高的地址)。对于每个进程,内核都维护一个变量brk,该变量指向堆的顶部。
分配器将堆维护为不同大小的块的集合。每个块都是已分配或可用的连续虚拟内存。分配的块已明确保留供程序使用。空闲块可用于分配。空闲块将保持空闲状态,直到由应用程序明确分配为止。分配的块将保持分配状态,直到被释放。此版本由应用程序明确执行。 ,由内存分配器本身隐式地执行。
分配器有两种基本样式。两种样式都要求应用程序显式分配块。它们之间的区别是哪个实体负责释放分配的块

显式分配器:
需要应用程序显式释放所有分配的块。例如,C标准库提供了一个称为malloc包的显式分配器。 C程序通过调用malloc函数分配一个块,并通过调用free函数释放一个块。.C++中的new和delete运算符等效于C中的malloc和free。

隐式分配器:
要求分配器检测程序何时不再使用分配的块,然后释放该块。隐式分配器也称为垃圾收集器,自动释放未使用的已分配块的过程称为垃圾收集。例如,Lisp,ML和Java等高级语言都依赖垃圾回收来释放分配的块。
7.10本章小结
本节阐述了hello在运行过程中的储存空间,引出了逻辑地址,线性地址,虚拟地址以及物理地址的概念,讲解了逻辑地址到线性地址再到物理地址的转换,了解了基于3级cache的内存访问以及动态内存分配的有关内容。并以此为基础,讲解了fork和exceve是的内存映射。

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有的 IO 设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许 Linux 内核引出一个简单低级的应用接口,称为 Unix I/O。
8.2 简述Unix IO接口及其函数
①open()用来打开文件,打开一个已经存在的文件或者创建一个新文件
②close(),关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
③读取文件read(),从当前文件位置复制字节到内存,函数从描述符为fd 的当前文件位置复制最多n个字节到内存位置buf。返回值一1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。
④写入文件write(),从内存复制字节到当前文件位置,write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
⑤改变文件位置lseek()。
8.3 printf的实现分析
vsprintf的作用是格式化。 它接受确定输出格式的格式字符串fnt。 使用格式字符串格式化参数数量以产生格式化的输出,并返回要打印的字符串的长度。 在写代码中,首先将几个参数传递给寄存器,然后系统调用sys_call。 syscall通过总线将字符串中的字节从寄存器复制到图形卡的视频内存。 视频存储器存储字符的ASCII码。 字符显示驱动程序子程序:从ASCII到字体库以显示vram(存储每个点的RGB颜色信息)。 显示芯片根据刷新频率逐行读取vram,并通过信号线将每个点(RGB分量)传输到LCD
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章简述了Linux的I/O设备管理机制,Unix I/O接口及函数,并简要分析了printf函数和getchar函数的实现。

结论
Helllo是大多数程序员编写的第一个程序。虽然只需要短短几行就可以完成并且运行。但在运行背后所涉及到的东西就很多了。既有系统软件的协同处理,也有软硬件得配合,深究下去得话水是很深的。
最后,对hello一生中所经历的过程逐条进行总结:
①编写:创建源程序hello.c
②预处理:hello.c进行预处理生成hello.i,可以在hello.i中找到外部文件帮助它的痕迹
③编译:hello.i编译生成hello.s,其中的内容被编译为汇编语言
④汇编:hello.s汇编生成hello.o,其中汇编语言逐条转换为机器语言
⑤链接:hello.o终于链接生成了可执行目标文件hello
⑥在shell中运行hello
⑦shell中调用fork函数创建子进程,并调用execve函数加载并运行hello
⑧系统在hello的时间片里执行hello发出的指令,hello访问内存中的数据。hello有自己的虚拟地址空间,当有访存操作时,MMU 把虚拟地址翻译成物理地址,通过三级 cache 访问内存。
⑨处理运行中收到的信号。程序运行时可能会有许多异常,比如 ctrl+z/c,hello 中的 sleep 函数,会触发上下文切换,hello 会在用户态和内核态切换。printf、getchar 函数会调用系统I/O从缓冲区读取字符进行处理。
⑩直到收到SIGINT信号,进程终止。hello被shell父进程回收子进程。hello进程消失

附件
① hello.c:源代码
② hello.i:预处理结果
③ hello.s:编译结果
④ hello.o:汇编产生的可重定位目标文件o
⑤ hello:hello.o经过链接的形成的可执行目标文件hello
⑥ hello_elf.txt:hello.o查看elf信息生成的相关文本。
⑦ hello_od.s hello.o 的反汇编
⑧ a.out hello.o链接库文件产生的可执行程序
⑨ hello_o.elf hello.o 的elf信息
⑩ hello.elf a.out的elf信息
⑪ hello_d.s a.out 的反汇编

参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值