步伐人生-HELLO‘S P2P

打印 上一主题 下一主题

主题 1039|帖子 1039|积分 3117

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x




计算机体系


大作业



计算机科学与技能学院

20245

摘  要

本文利用计算机体系知识对hello的一生进行了周游,从预处置惩罚、编译、汇编、链接、进程管理、存储管理与IO管理几个方面,对hello步伐以及相干计算机体系知识进行了分析与论述。进一步加深对编译体系中各部分功能的明白,而且梳理了关于非常与进程、虚拟内存、体系级I/O等部分的知识点。
关键词:计算机体系编译体系;进程管理;存储管理;I/O管理                           

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分








目  录


第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.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本章小结

结论

附件

参考文献



第1章 概述

1.1 Hello简介

(1)P2P的过程:
P2P,即From Program to Process,指的是从步伐员利用编辑器编写好步伐代码后,将步伐转化为进程的过程。这一过程通常是由编译体系和进程管理共同实现的。
编译体系包罗预处置惩罚器、编译器、汇编器和链接器等组成部分,并提供编译驱动步伐,能够代表用户在必要时调用预处置惩罚器、编译器、汇编器和链接器。接下来,以GNU编译体系为例,形貌GCC驱动步伐将步伐员利用ASCII码文本编辑的步伐代码(Program)一步步转化为二进制文本的可实行步伐的过程。

预处置惩罚器根据原始C步伐中的预处置惩罚指令,向源文件中插入体系头文件等文本,产生修改后的ASCII文本hello.i。编译器将hello.i翻译成汇编语言步伐hello.s,再由汇编器将其翻译成机器语言指令,生成可重定位目标步伐hello.o。链接器以一组可重定位目标步伐和命令行参数作为输入,输出完全链接的、可以加载和运行的可实行目标文件hello。通过编译体系完成了了从步伐到可实行目标文件的转化。
可实行目标文件hello以二进制文件的情势存储在磁盘中,运行hello时,我们必要在命令行中输入./hello。由于这并不是内置指令,进程管理利用fork()函数为实行hello创建了新的子进程(Process),并利用exceve()函数加载可实行的hello步伐,shell调用加载器loader,从磁盘中读取hello并将可实行文件hello的代码和数据复制到子进程的内存中,将控制转移到步伐的开头,即完成了从Program到Process的变化。

(2)020的过程:
020,即 From Zero to Zero,是指hello步伐在运行前不占据内存,运行后也会清除内存的过程,即从无到有再到无。
在预备过程中,进程管理首先会为hello步伐fork一个新的子进程,并将hello步伐从磁盘读取到内存中,将子进程的当进步程影像替换为hello步伐的进程映像。操纵体系会为其分配虚拟内存,并映射到物理内存,实现了从无到有的过程。在实行过程中,CPU会利用三级cache和TLB等进行指令或数据的读取,并实行这些指令,并调用I/O设备进行相应的输出,有了更多资源。当步伐运行完毕后,子进程发送信号给父进程,并由父进程回收子进程的资源,操纵体系把子进程移除,释放其占用的内存等资源,实现了从有到无的过程。
因此,hello步伐在运行过程中就是进程、资源等020的过程。
1.2 情况与工具

硬件情况:X64 CPU;2GHz;2GRAM;256GB SSD
软件情况:Windows11 64位;Vmware 16;Ubuntu 20.04 64位;
开发与调试工具:gcc;edb;gdb;readelf;objdump等
1.3 中间效果

文件名

文件作用

hello.i

预处置惩罚后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

汇编后得到的可重定位目标文件

hello.elf

hello.o输出的ELF格式文件

hello.asm

hello.o反汇编得到的汇编语言文件

hello

链接后得到的二进制可实行文件

helloout.elf

hello输出的ELF格式文件

Helloout.asm

hello反汇编得到的汇编语言文件


1.4 本章小结

本章首先介绍了对hello的一生有高度概括性的P2P与020的概念,同时介绍了开发软硬件情况、工具以及过程中产生的中间文件效果。

(第1章0.5分)



第2章 预处置惩罚

2.1 预处置惩罚的概念与作用

预处置惩罚是指预处置惩罚器根据以#字符开头的命令,修改原始的C步伐得到修改后的C步伐的过程,通常是以.i为文件扩展名。通过GCC编译器调用预处置惩罚器cpp,将hello.c文件处置惩罚后生成hello.i文件,cpp对hello.c开头中读取体系头文件的指令部分做了处置惩罚,将体系头文件直接插入步伐文本中,得到修改后的C步伐,hello.i也是ASCII文本,可以用文本编辑器打开查看。
作用:
(1)处置惩罚#include指令,将体系头文件直接插入到文本中
(2)删除全部注释
(3)删除#define指令,展开全部的宏定义
(4)处置惩罚条件预编译指令,比如#if、#ifdef、#elif、#else、#endif
(5)添加原步伐中对应的行号和文件标识
(6)保存#pragma编译器指令
2.2在Ubuntu下预处置惩罚的命令

由于要求使用gcc –m64 –no-pie –fno-PIC作为编译指令,因此预处置惩罚生成hello.i的指令为:gcc –m64 –no-pie –fno-PIC  -E  hello.c  -o  hello.i,文件夹中生成了预处置惩罚器输出的效果hello.i。


图1 预处置惩罚指令及输出效果

2.3 Hello的预处置惩罚效果解析

由于hello.c与hello.i都是ASCII文本,以是都可以用文本编辑器来打开查看。
Hello.c的内容如下图所示:









图2  hello.c文本内容

hello.i的内容如下图所示:





图3 hello.i文本内容

从图中可以看出,预处置惩罚删除了源步伐中1-5行的全部注释,而且处置惩罚了全部#include预编译指令,将对应的体系头文件直接插入了文本中(如stdio.h),导致文本文件由23行增长到了3061行。将源步伐中的main函数原样保存并修改了缩进,放在文件的最末端,而且预处置惩罚器还为步伐添加了行号(如main函数前的#11,体现处于源步伐的11行)。
2.4 本章小结

本章重点介绍了在编译hello.c文件过程中,预处置惩罚指令的概念及作用,展示了预处置惩罚指令及输出的hello.i文件,并对原C步伐和修改后的C步伐的内容进行了比对,分析了其中各要素的关系及预处置惩罚器所作出的处置惩罚,进一步体现了预处置惩罚器的作用。
(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

编译是指编译器将C语言编写的文本文件hello.i翻译成汇编语言文件hello.s的过程,依旧可以使用文本编辑器来查看。其汇编语言步伐中,每条语句都以一种文本格式形貌了一条或多条低级机器语言指令,为差别高级语言的差别编译器提供了通用的输出语言。
作用:
(1)将更靠近语言的高级语言步伐翻译为更靠近机器语言的汇编语言步伐
(2)为差别高级语言的差别编译器提供了通用的输出语言
(3)每条语句都形貌了低级机器语言指令,便于步伐员查看低层机器实现的逻辑
(4)编译器会对步伐进行优化,从而提升步伐运行服从。
3.2 在Ubuntu下编译的命令

在预处置惩罚完的根本上,利用命令gcc –m64 –no-pie –fno-PIC  -S  hello.i  -o  hello.s进行编译,并在文件中生成编译后的汇编语言文件hello.s。


图4 编译指令及生成的文件

3.3 Hello的编译效果解析

由于hello.s也可以用文本编辑器打开,查看并分析编译器对C语言中各种数据范例与操纵的处置惩罚方式。hello.s的内容如下图所示:

















图5 hello.s的内容

3.3.1字符串常量


图6 .LC0中的字符串常量

.LC0中在“.string”后面声明白一个字符串常量,数据存储在.rodata后面,说明这是只读数据存储区。

3.3.2整型常量


图7 整型常量

以“$ + 整型数字”的情势将整型变量存储为立即数,例如C步伐中的语句:

  • if(argc!=5){
其中的整型常量5,在.LFB6中以立即数$5的情势体现。

3.3.3参数
hello.c中只有main一个函数,其两个参数分别为int argc和char *argv[],即整型变量argc和字符指针数组argv。这两个参数分别应存储在寄存器ebi与寄存器rsi中。在C语言中,这是两个特别的参数,他们用于从命令行接受参数。argc是一个整数,存储了从命令行通报给步伐的参数的数目,其中步伐名被认为是第一个参数。argv是一个字符指针数组,每个字符串体现一个命令行参数,argv[0]中存储的是步伐名。步伐通过对参数argc来进行判断相当于对命令行参数的个数进行判断。


图8 参数

从图中可以看出,main函数中对参数进行了进一步处置惩罚,将这两个参数都压入栈,分别存在-20(%rbp)与-32(%rbp)的位置。cmpl指令将-20(%rbp)与常量5进行比较,正好对应原C步伐中的语句:

  • if(argc!=5){
进一步验证了,edi寄存器中存储的值就是第一个参数argc,而且这条语句是为了判断传入命令行参数是否为5。

3.3.4局部变量
原C步伐中的局部变量只有int范例的变量i,分析会汇编代码得出,汇编代码对局部变量声明的次序与C步伐差别。C步伐在main函数一开始就声明白变量i,但是没有对其赋初值,而是直到进入循环才第一次赋初值:



图9 hello.c中的局部变量

而汇编代码对局部变量的声明进行了修改,只有argc==5即命令行参数为5,跳转到.L2后,才声明变量i,将其存储在栈中-4(%rbp)的位置,并将其值初始化为0,,随后在.L3与.L4中对其进行各种操纵。

图10 .L2中声明变量i



图11 .L3与.L4中对变量i进行操纵


3.3.5非静态函数
hello.c步伐只有main一个函数,而且为非静态函数,hello.s利用.globl将非静态函数main声明为了全局符号,体现其可以被其他模块引用。


图12 非静态函数


3.3.6赋值操纵符
main函数中只有一处赋值操纵,是在循环中对变量i进行初始化,C步伐中的语句为:

  • for(i=0;i<10;i++){
汇编步伐hello.s中对应的赋值语句是:


图13 赋值操纵

由局部变量分析可知,-4(%rbp)位置中存储着整型局部变量i,因此这条语句实现了将i赋值为0,通过mov操纵,将立即数0放入对应内存位置中实现。i是int范例数据,占4B,因此使用movl操纵,体现目的内存中存储的是双字变量。

3.3.7范例转换操纵
步伐中调用atoi函数将字符指针范例的参数argv[4]强制转换为int范例,作为sleep函数的参数。

  • sleep(atoi(argv[4]));
在汇编语言中可以找到相应的操纵:

图14 范例转换操纵

首先将参数的值传入rdi寄存器,作为参数调用atoi函数,而且在eax寄存器中得到atoi函数返回的int范例数据,自此实现了强制范例转换,得到了可以作为sleep函数参数的整型数据。

3.3.8算数操纵
步伐中的算数操纵主要是在循环中对变量i进行自增操纵,原语句为:

  • for(i=0;i<10;i++){
hello.s中是通过add指令实现的上述操纵:


图15 算数操纵

该指令的意思是,从内存-4(%rbp)的位置取出数据(i),将其+1,并将效果写回该内存位置中,由于该位置存储的是整型变量,因此使用addl指令,实现了自增运算操纵。

3.3.9关系操纵
步伐中有两处关系操纵,原语句分别如下:

  • if(argc!=5){

  • for(i=0;i<10;i++){
hello.s文件中对应的汇编代码分别如下:



图16 关系操纵

cmpl指令将栈中-20(%rbp)位置的数据与5作比较,由参数分析可知,此处存放的是参数argc,因此相当于将argc与5进行比较。je指令实现如下功能——如果argc与5相称即命令行参数为5,则跳转到.L2中,否则继承实行下面的代码。通过进一步分析可知,.L2中对应的是argc==5情况下的C语言代码,下文中有对第五个参数argv[4]的引用。而继承实行的代码对应的是argc!=5情况下的代码,因此实现了关系操纵,并实现了if语句的分支。
类似,将-4(%rbp)处的数据,即局部变量i与9进行比较,如果i<=9,则跳转到.L4中,否则继承实行下面的操纵。

3.3.10数组操纵
步伐在调用printf时访问了三个数组元素,分别为argv[1]-argv[3],即实行步伐时命令行输入的第2-4个参数。对应的汇编代码如下:







图17 数组操纵

由参数分析可知,汇编步伐将指向数组argv的指针存储在-32(%rbp)位置中,首先从该位置中读出数组指针,指向数组的第一个元素,将其值存在%rax中,分别访问rax+24、rax+16、rax+8这三个指针,分别是指向argv[3]-argv[1]的指针,读取指针指向的内存位置,相当于分别读取了argv[3]-argv[1]。

3.3.11控制转移
步伐中有两处控制转移操纵,分别为if语句与for循环,原代码如下:

  • if(argc!=5){

  • for(i=0;i<10;i++){


hello.s文件中对应的汇编代码分别如下:



图18 控制转移

由关系操纵可知,cmpl与je共同完成了对于argc是否等于5的判断。je指令实现如下功能——如果argc与5相称,则跳转到.L2中,否则继承实行下面的代码。通过进一步分析可知,.L2中对应的是argc==5情况下的C语言代码,而继承实行的代码对应的是argc!=5情况下的代码,实现了if语句的控制转移。
类似,cmpl与jle共同完成了对i 是否小于等于9的操纵,如果i<=9,则跳转到.L4中,否则继承实行下面的操纵,实现了for循环的控制转移。

3.3.12函数操纵
步伐中共有main、printf、exit、atoi、sleep、getchar六个函数。

  • main函数
main函数的两个参数分别是argc与argv,见参数分析。通过使用call内置指令来调用,而且在main函数内部调用了别的五个函数。
main函数中声明白一个局部变量i,见局部变量分析。
(2)printf函数
Main函数中两次调用printf函数,第一次只传了一个字符串常量做参数,汇编代码调用puts来实现:


图19 printf函数1

第二次调用printf函数,分别传入了argv[3]-argv[1]三个数组元素,将其值分别存储在rcx、rdx、rsi寄存器中,相当于访问参数argv[3]-argv[1]的值,并将其倒序传入寄存器中作为参数调用printf函数。并传入了.LC1中的字符串常量作为参数,具体实现如下:


图20 printf函数2


  • exit函数

exit函数只有一个常量1作为参数,将立即数1存储到寄存器edi中,作为参数调用exit函数:

图21 exit函数


  • atoi和sleep函数
首先将参数的值传入rdi寄存器,作为参数调用atoi函数,而且在eax寄存器中得到atoi函数返回的int范例数据,作为sleep函数参数的整型数据再次传入edi寄存器中,而且调用sleep函数。


图22 atoi函数与sleep函数


  • getchar函数
由于getchar函数没有参数,因此当判断for循环竣事后,直接调用getchar函数,不必要进行参数通报。

图23 getchar函数

3.4 本章小结

本章介绍了将hello.i文件编译为hello.s文件过程中,编译的具体概念及作用,而且展示了编译的命令和输出的文件。
详细展示了hello.s文件中各部分的内容,而且从字符串常量、整型常量、参数、局部变量、非静态函数、赋值操纵、范例转换、算数操纵、关系操纵、数组操纵、控制转移和函数操纵12个方面来详细比较了源代码与汇编代码之间的关系和区别,表明了汇编代码是如何实现源代码的功能的,详细分析了编译效果。
(第3章2分)


第4章 汇编


4.1 汇编的概念与作用

汇编是指汇编器as将汇编语言文件hello.s翻译成机器语言指令,并将其打包成为可重定位目标文件的格式,并将效果保存在目标文件hello.o中。由于hello.o是一个二进制的机器语言文件,因此无法用文本编辑器打开。
作用:
将汇编语言翻译成机器可以直接识别并实行的二进制机器语言,并生成二进制的可重定位目标步伐。
4.2 在Ubuntu下汇编的命令

汇编的指令为gcc –m64 –no-pie –fno-PIC  -c  hello.s  -o  hello.o,文件夹中生成了预处置惩罚器输出的效果hello.o,而且体现是不可用文本编辑器打开的格式。


图24 汇编指令

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的根本信息,特别是重定位项目分析。
通过readelf -a hello.o > hello.elf指令将hello.o输出到hello.elf文件中。


图25 生成elf文件指令


ELF文件是目标文件的标准二进制格式,广泛应用于现代linux和unix操纵体系中,包含步伐的可实行代码、数据和其他信息,其格式如下图所示:

图26 elf文件格式

打开hello.elf文件,可以看到elf文件中标注出了各部分,分别对其进行分析。


  • ELF头

图27 elf头

ELF头以一个16字节的序列开始,这个序列形貌了生成该文件的体系的字的大小和字节次序。ELF头剩下的部分包含资助链接器语法分析和表明目标文件的信息,包罗ELF头的大小、目标文件的范例、机器范例、节头部表的文件偏移,以及节头部表中条目的大小和数目。
例如,hello.elf文件的ELF头部分形貌了如下信息:体系字的大小为2B、接纳补码和小端序体现、目标文件范例为REL可重定位文件、ELF文件的版本号为当前版本、节头部表的文件偏移是1224B、ELF头的长度为64B、节头部表的大小为64B、节头部表条目的大小为14。


  • 节头


图28 节头

节头部表形貌了ELF文件中各节的名称、范例、大小、所在、全体大小、偏移、权限、旗标、链接、信息、对齐等信息。


(3)重定位节


图29 重定位节


重定位节包罗rela.txt和rela.eh_frame两个节,rela.text节是一个.text节中位置的列表,当链接器将这个目标文件与其他文件组适时,必要修改这些位置,.rela.eh_frame节包含了对eh_frame节的重定位信息,重定位节中存储了符号的偏移量、信息、范例、符号值和符号名称。


(4)符号表

图30 符号表

符号表中枚举出了步伐中全部定义和引用符号的信息,包罗三种符号:全局符号(非静态的C函数和全局变量)、外部符号(引用的其他模块定义的全局符号)、局部符号(静态的C函数和全局变量),而且不包含对应于本地非静态步伐变量的任何符号,这些符号在运行中由栈进行管理,链接器并不进行处置惩罚。
.symtab节中的符号表中含有17个条目,列出了文件、各节、和各种函数的信息,由于步伐中只有一个局部变量i没有全局变量,因此符号表中并没有变量对应的符号。
4.4 Hello.o的效果解析

使用指令:objdump -d -r hello.o 来对hello.o实行反汇编,并将其得出的效果与编译生成的hello.s进行对照,分析其中的区别和关系。

图31 反汇编指令


图32 hello.s内容1




图33 反汇编内容


  • 最首要的区别是,反汇编生成的文件中,在每一行汇编指令的前面都加上了对应的机器语言指令,而且在机器语言指令前加上了指令的起始所在(偏移量)。

  • 数制发生了变化,汇编代码中的立即数和操纵数都以十进制的情势体现,而反汇编得到的代码中,立即数和操纵数都以十六进制的情势体现。







图34 数制变化


  • 分支转移时,目标所在的位置体现发生了变化。在原汇编代码中,接纳段名称体现目标跳转所在,而在反汇编代码中,直接使用所在+段内偏移给出目的跳转所在。




图35 目的跳转所在变化



  • 常量读取与函数调用发生了变化。汇编代码调用字符串常量时,直接调用.LC0,而反汇编代码中对应.rodata部分的重定位条目R_X86_64_32,在进行参数调用时要常量的所在进行修改。读取常量的方式发生了变化,但是传参的方式没有改变,都是使用寄存器edi。进行函数调用时,汇编代码中的函数调用在反汇编代码中要通过PLT进行重定位,因此标注了R_X86_64_PLT32条目,而且在callq语句中增长了返回所在。




图36 常量读取与函数调用


(5)在汇编代码中,有.cfi_*开头的指令,这些指令是调试信息,指示编译器如何处置惩罚栈帧,因此在反汇编代码中直接删掉了这些语句。

图37 调试信息

4.5 本章小结

本章介绍了汇编的概念和作用,并利用汇编指令将hello.s生成了可重定位目标文件hello.o文件。通过readelf读出hello.elf文件,对ELF文件格式进行了详细的论述,而且表明了hello.elf文件中各部分中所包含的信息及其内容,包罗ELF头、节头部表、重定位节和符号表,来论述每个节的性子、信息和内容。
利用objdump对hello.o进行反汇编,将其内容与原始汇编步伐hello.s中的内容进行详细的对比分析,并分析其对照关系,论述了其中的区别和缘故原由,对于汇编的作用有了进一步的相识。
(第4章1分)


5链接


5.1 链接的概念与作用

链接是指链接器将多个已经预编译好的.o可重定位目标步伐合并成一个可实行目标文件的过程,这个文件可以被加载到内存中由体系实行。

作用:


  • 分离编译:链接是由链接器自动实行的,链接器在软件开发中扮演着一个关键的脚色,由于它们使得分离编译成为大概。我们不用将大型的应用构造为一个巨大的源文件,而是可以把它们分解成更小、更好管理的模块,可以独立地进行修改和编译这些模块,我们改变这些中的一个时,只必要重新编译这一个模块并重新进行链接,而不必重新编译其他文件。
  • 符号解析:编译过程中大概必要将多个可重定位目标文件进行链接,链接器负责解析变量和函数名符号的引用,使符号在多个目标文件之间进行正确的链接和实行。
  • 重定位:可重定位目标文件中的代码段和数据段接纳的是各自的相对所在(相对于段的起始所在),链接器将这些所在转换为绝对所在,确保在将多个目标文件链接为一个步伐后能够正确的实行每一个文件中的功能。
(4)消除冗余代码:链接器将多个目标文件合并成为一个可实行文件,包含全部的代码和数据信息,同时进行肯定的优化,移除冗余的操纵和代码,包管步伐能够正确高效运行。

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。



图38 链接指令

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

分析hello的ELF格式,用readelf等列出其各段的根本信息,包罗各段的起始所在,大小等信息。
通过readelf指令来输出elf文件helloout.elf,用文本编辑器查看并分析其各段的根本信息。


图39 生成helloout.elf文件


  • ELF头

图40 elf头

与hello.elf相同,helloout.elf的ELF头同样以一个16字节的序列开始,这个序列形貌了生成该文件的体系的字的大小和字节次序。ELF头剩下的部分包含资助链接器语法分析和表明目标文件的信息,包罗ELF头的大小、目标文件的范例、机器范例、节头部表的文件偏移,以及节头部表中条目的大小和数目。
helloout.elf文件的ELF头部分形貌了如下信息:体系字的大小为2B、接纳补码和小端序体现、目标文件范例为EXEC可实行文件、ELF文件的版本号为当前版本、节头部表的文件偏移是14208B、ELF头的长度为64B、步伐头的大小为56B,包含的条目的大小为12、节头部表的大小为64B,节头部表条目的大小为27且索引为26。


  • 节头部表

图41 节头部表

节头部表形貌了ELF文件中各节的名称、范例、大小、所在、全体大小、偏移、权限、旗标、链接、信息、对齐等信息。


  • 步伐头表

图42 步伐头表

hello.elf文件中没有步伐头,helloout.elf的步伐头表包含多个步伐头,每个步伐头形貌了文件中的一个段。段是可实行文件中的逻辑区块,大概包含代码、数据或其他信息。步伐头表中包含的信息有段的范例、偏移量、虚拟所在、物理所在、文件大小、占用内存大小、权限标志和对齐方式等信息。

(4)段节表

图43 段节表


(5)动态段

图44 动态段

动态段中包含了与动态链接有关的信息,包罗共享库、符号表和重定位表等,确保步伐在运行时能够正确加载和解析所需的共享库和符号,从而实现动态链接的功能。

(6)重定位节

图45 重定位节表



(7)符号表

图46 符号表


5.4 hello的虚拟所在空间

使用edb加载hello,查看本进程的虚拟所在空间各段信息,加载后edb的界面如下图所示:

图47 edb加载hello

通过ELF文件,可以查看各段的所在。












图48  虚拟所在空间分析


打开edb通过bookmarks查看虚拟所在各段信息,并将其与elf文件中的虚拟所在进行比对。通过比对发现,edb中体现的各段的虚拟所在,与elf文件中标注的虚拟所在是逐一对应的。

5.5 链接的重定位过程分析

5.5.1反汇编分析
实行指令objdump -d -r hello得到hello反汇编的代码,并与hello.o反汇编的到的代码进行对比,对比效果如下:


  • 分配虚拟内存所在




图49 分配虚拟内存所在

hello.o对应的反汇编步伐中,指令前的所在都是相对于函数main开始的相对所在,颠末链接后,指令前的所在修改为CPU可以寻址的虚拟内存所在,说明链接为每个指令都分配了新的虚拟内存所在。

(2)增长函数


图50 链接增长函数

颠末链接,反汇编代码中多了很多原来没有的函数,包罗init、.plt等体系函数,也增长了在main函数中调用的puts、atoi、printf、getchar等函数,而且也为这些函数分配了相应的虚拟内存所在,确保在实行步伐的过程中,能够正常的调用这些函数。

(3)符号解析和函数调用




图51 符号解析和函数调用

hello.o中包含了未解析的符号(如外部函数调用),因此这些符号必要进行重定位,因此在反汇编步伐中标注了R_X86_64_PLT32条目。颠末链接,hello中的全部符号已经被解析而且重定位完成,链接器将目标文件中未解析的符号替换函数对应的虚拟内存所在和偏移量,包管步伐在运行过程中可以正确地找到这些符号并实行函数内部的操纵。


  • 内存所在访问




图52 内存所在访问

对某些常量的访问,在链接后酿成了对应的虚拟所在,通过gdb调试可知,0x402008内存所在中存储的就是0x0。

(5)目标跳转所在




图53 目标跳转所在

颠末链接后,跳转指令的目标跳转所在也由相对main的相对指令所在变为可以由CPU直接寻址的虚拟内存所在。

5.5.2重定位的过程
(1)重定位节和符号定义。链接器将全部相同范例的节合并为同一范例的新的聚合节。差别的可重定位目标文件中的.data有差别的内容和位置,链接器将这些节进行重定位,得到hello中的.data节。然后,链接器将运行时内存所在赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。
(2)重定位节中的符号引用。链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确运行时的所在。这个过程中链接器依赖于hello.o中的重定位条目。
5.6 hello的实行流程

使用gdb实行hello,将主要函数都打上断点,来观察步伐中函数实行的次序。以argc!=5为例,实行步伐时不给出除了步伐名外的其他参数,因此argc=1。下面是利用gdb调试得到的函数实行次序信息:




图54 gdb调试信息

通过上面的调试信息可以得知,步伐首先实行_init,随后调用_start体系函数,在体系函数中会进入__libc_start_main函数中,然后进入__libc_csu_init函数中,函数会再一次调用_init,然后进入main函数,由于输入的argc!=5,因此会调用puts函数,随后调用exit函数,末了实行_dl_fini和_fini函数退出步伐。
当实行步伐时一共输入五个参数即argc==5时,main函数会调用printf函数、atoi函数、sleep函数、getchar函数,并终极调用_dl_fini和_fini竣事步伐。

子步伐名与步伐所在:
步伐名

步伐所在

_init()

0x401000

_start()

0x4010f0

libc_start_main()

0x7ffff7c29dc0

__libc_csu_init()

0x4011c0

main()

0x401125

puts()

0x401090

exit()

0x4010d0

printf()

0x4010a0

atoi()

0x4010c0

sleep()

0x4010e0

getchar()

0x4010b0

_dl_fini()

-

_fini()

0x401238

表1 步伐名与所在

5.7 Hello的动态链接分析

在调用共享库函数时,编译器不能预测这个函数的运行时的所在,因此要为该引用生成一条重定位记录,然后动态链接器在步伐加载的时候再解析它。耽误绑定是通过GOT和PLT实现的。在hello输出的ELF格式文件中,可以在节头部表中找到.got段。

图53 got段

GOT是数据段的一部分,PLT是代码段的一部分。GOT是由所在构成的数组,每个元素为8个字节,在和PLT使用进行动态链接时,GOT[0]和GOT[1]包含动态链接器在解析函数所在会使用的各种信息。GOT[2]是动态链接器在ld-linux.so模块的入口点,其每个条目对应一个被调用的一个函数,所在在函数使用时被解析。
在动态链接前后,GOT段的内容如下图所示。






  • 动态链接前 (b)动态链接后
图54 动态链接前后对比

5.8 本章小结

本章介绍了毗连的概念和作用,并通过链接指令将多个可重定位目标文件作为输入,得到了终极的可实行文件hello。
利用readelf工具生成ELF文件helloout.elf,并分析了ELF文件中各部分的主要内容及作用。用edb查看了hello的虚拟所在空间,并发现ELF文件中各节的所在与edb中体现的虚拟所在逐一对应。
对hello进行反汇编,得到反汇编步伐并与hello.o得到的反汇编步伐进行比较,分析其区别以及链接产生这些区别的缘故原由,并对链接中符号解析和重定位进行了分析,着重叙述了重定位的过程。
通过gdb调试相识了hello步伐实行过程中调用函数的流程及各函数的所在,并利用edb对hello实行过程中的动态链接进行了分析。
(第5章1分)



6hello进程管理


6.1 进程的概念与作用

进程的经典定义就是一个实行中步伐的实例。体系中的每个步伐都运行在某个进程的上下文中。上下文是由步伐正确运行所需的状态组成的,这个状态包罗存放在内存里的步伐的代码和数据,它的栈,通用目的寄存器的内容,步伐计数器,情况变量以及打开文件形貌符的集合。
进程是操纵体系对正在运行的步伐的一种抽象,是对处置惩罚器、主存和I/O设备的抽象体现。在一个体系上可以同时运行多个进程,而每个进程都好像在独占硬件。
作用:
(1)独占运行:在现代体系上运行hello时,我们会得到一个假象,就好像体系上只有这个步伐在运行,步伐看上去是独占地使用处置惩罚器、主存和I/O设备。处置惩罚器看上去就像在不中断地一条接一条地实行hello步伐中的指令。
(2)资源管理:操纵体系通过进程来管理、分配和回收相应的资源,使进程之间的资源相对独立,在并发实行步伐的过程中不会相互干扰。
(3)并发运行:无论是单核还是多核体系处置惩罚器都可以通过上下文切换来交错实行进程的指令,从而显现出CPU看上去是在并发地实行多个进程的假象。
(4)提供抽象:进程是操纵体系的提供的三个根本抽象之一,是对处置惩罚器、主存和I/O设备的抽象体现。
6.2 简述壳Shell-bash的作用与处置惩罚流程

shell 是一个交互型应用级步伐,代表用户运行其他步伐。Shell是信号处置惩罚的代表,负责各进程创建与步伐加载运行及前后台控制,作业调用,信号发送与管理等,是一个交互型应用级步伐,代表用户运行其他步伐。
Bash是很多linux体系的默认shell步伐,是GNU项目的一部分,同时比sh提供了更多的功能。
shell的处置惩罚流程:

  • 读取用户的命令行,解析命令行参数,如果是空命令行则直接跳过
  • 判断命令是否是内置指令,如果是则立即实行
  • 如果命令并非内置指令,则利用fork创建一个新的子进程,利用execve将步伐的代码和数据等信息映射到子进程中,在子进程中实行新任务
  • 如果命令是前台任务则等待其实行完毕并返回,如果是后台任务则将其放在后台实行。
  • 在任务实行过程中,还可以对非常与信号进行处置惩罚,而且做出相应的反应。
6.3 Hello的fork进程创建过程

在实行hello时,输入的正确命令行参数应为

  • ./Hello 学号 姓名 手机号 秒数
因此shell会读取并解析命令行,发现这并非一个内置指令,并通过fork创建新的子进程。父进程通过调用fork函数创建一个新的运行的子进程,fork函数被调用一次,却返回两次,子进程返回0,父进程返回子进程的PID。
新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟所在空间相同的(但是独立的)一份副本(代码、数据段、堆、共享库以及用户栈),但子进程有差别于父进程的PID。
6.4 Hello的execve过程

Execve 是一个体系调用,用于在当进步程的所在空间中实行一个新的步伐。与execve差别,execve不创建新的进程,而是用新的步伐替换当进步程的代码段、数据段、堆栈段,但保存进程 ID 和一些属性。首先进程调用execve函数,通报步伐的路径、参数列表和情况变量列表信息。内核根据路径查找可实行文件,将其从磁盘复制到内存中,并加载到进程的所在空间中。只有当出现错误时,例如找不到filename,execve才会返回到调用步伐。
运行时,创建一个内存映像,在步伐头部表的引导下,加载器将可实行文件复制到代码段和数据段并跳转到步伐的入口,_start函数调用体系启动函数,_libc_start_main,初始化情况,调用用户层的main函数,实行main函数中的内容。

6.5 Hello的进程实行

上下文信息:是内核重新启动一个被抢占的进程所需的状态,由一些对象的值组成,包罗寄存器、PC、栈和各种内核数据结构。
进程时间片:是操纵体系分配给每个进程的CPU时间段。在时间片轮转调度中,每个进程被分配一个固定长度的时间片来运行。如果时间片竣事时进程还未竣事,则该进程会被停息,CPU先实行下一个进程,实现进程之间的切换。
用户态:进程处于用户模式中,此时进程不答应实行特权指令,也不答应直接饮用所在空间中内核区的代码和数据。
核心态:进程处于内核模式中,可实行指令会合的任何指令,可以访问体系中任何内存位置。
用户态与核心态的转换:当非常发生时,控制通报到非常处置惩罚步伐,进程从用户模式转化为内核模式;当它返回到应用步伐代码时,再转换到用户模式。

图55 进程调度

通过上面这些处置惩罚,体系可以正常的调用并实行hello步伐,以图55为例表明hello的进程调度过程。
Hello进程创建后等待实行,当hello被CPU选中实行后,进入hello的时间片并实行步伐,时间片竣事后,如果hello还未实行完,则保存上下文信息,CPU先实行下一个进程,再次轮到hello进程时再恢复上下文信息。正常实行过程中,hello进程处于用户态,当非常发生时,控制通报到非常处置惩罚步伐,由用户态转换为核心态。
6.6 hello的非常与信号处置惩罚

正常运行步伐的情况:

图56 正常运行


  • 乱按
Shell会将乱按的内容都会被缓存,在实行完hello后将缓存中的每一行当成命令行来实行。



图57 乱按


  • ctrl+z
按下ctrl+z会体现进程已停止,hello进程被挂起。由于hello是前台进程,按下ctrl+z后会向hello进程发送SIGTSTP信号,停息进程的实行,在CPU的进程调度中停息hello的时间片。
输入ps可以查看当进步程,发现hello进程在列,说明hello只是被挂起、停息实行,而非被竣事并删除。
实行jobs指令输出作业列表中的全部作业信息,可以看到hello进程也在其中,而且状态信息为已停止。
实行fg,向前台进程hello发送SIGCONT信号,继承实行前台进程hello。可以看到输入fg后hello继承实行。停息之前hello输出了2次,继承之后hello输出了8次,一共10次,由此可以判断ctrl+z是让前台进程停息,而且输入fg之后是继承进行而非重新实行。



图58 ctrl+z

在输入ctrl+z后,还可以实行pstree和kill指令。
实行pstree后将全部进程以树状图的情势体现:

图59 pstree指令

实行kill指令可以将hello进程杀死,但是子进程还在等待父进程回收:


图60 kill指令


  • ctrl+c
与输入ctrl+z时的情况进行对比,输入ctrl+c后,向前台进程hello发送SIGINT信息,竣事进程的实行。
输入ps发现当进步程中没有hello,而jobs和fg的实行效果都体现当前没有前台进程,由此可以判断输入ctrl+c后直接竣事了当进步程,而且回收了子进程。

图61 ctrl+c


6.7本章小结

本章首先介绍了进程的概念与作用,而且分析了shell的作用与处置惩罚流程,给出了shell从取命令行到实行命令行的过程。随后具体分析了hello实行过程中,利用fork创建子进程的过程与利用execve加载进程,简述hello的创建、加载、实行、调度和终止。
在hello实行过程中,通过键盘的差别操纵向进程发送差别的信号,进程产生了差别的反应。对于非常产生的信号以及进程对应的行为进行了分析。
(第6章1分)


7hello的存储管理


7.1 hello的存储器所在空间

(1)逻辑所在:
在有所在变换功能的计算机中,访问指令给出的所在 (操纵数) 叫逻辑所在,也叫相对所在。要颠末寻址方式的计算或变换才得到内存储器中的实际有效所在,即物理所在。
将hello.o进行反汇编得到的文件中,其中调用函数以及做目的跳转所在时使用的是逻辑所在:



图62 逻辑所在

(2)线性所在:
是逻辑所在到物理所在变换之间的中间层。在分段部件中逻辑所在是段中的偏移所在,段中偏移所在加上段的基所在就是线性所在。

(3)虚拟所在:
虚拟所在是Windows步伐时运行在386掩护模式下,步伐访问存储器所使用的逻辑所在称为虚拟所在,在实行过程中由操纵体系将其转换为真正的物理所在。
将hello进行反汇编得到的文件中,调用函数以及做目的跳转所在时使用的是虚拟所在:



图63 虚拟所在

(4)物理所在:
是在所在总线上,以电子情势存在的,使得数据总线可以访问主存的某个特定存储单位的内存所在。在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单位给以一个独立的存储器所在。
7.2 Intel逻辑所在到线性所在的变换-段式管理

在段氏管理中,一个步伐被分为多个段,每个段都用段名来标识,段由基所在,内部的所在可以通过偏移量来体现。一个逻辑所在由两部分组成——段形貌符和段内偏移量。段形貌符的结构如下图所示:

图64 段形貌符

段形貌符的高13位是索引号;TI位用来进行形貌符表的选择,TI=0时体现选择GDT,从GCT中取出一个形貌符,TI=1时体现选择LDT;RPL字段体现哀求的特权级别。
体系中有一个全局形貌符表GDT,存储体系全局的段形貌符,另有一个局部形貌符表LDT,存储特定进程的段形貌符。被选中的段形貌符会先被送至形貌符cache,当TI=0时,从全局形貌符表中取出相应的段形貌符,并获取32位段基址。利用段基址和逻辑所在中的段内偏移量相加就可以得到计算后的线性所在。当TI=1时,则从LDT中选择匹配的段形貌符进行计算。

图65 获取段基址

7.3 Hello的线性所在到物理所在的变换-页式管理

页表就是一个页表条目的数组,将虚拟所在页映射到物理所在页,每次所在翻译硬件将一个虚拟所在转换为物理所在时,都会读取页表。
虚拟内存的管理是通过页表来完成的,虚拟所在空间中的每个页在页表中一个固定偏移量处都有一个PTE,每个PTE包含一个有效位和一个物理页号。有效位指示了该虚拟页是否当前被缓存在DRAM中,如果有效位是1,物理页号就体现了DRAM中相应物理页的起始位置。发生缺页时,体系必要从磁盘中读取相应的数据。
n位的虚拟所在包含两部分:p位的虚拟页面偏移VPO和虚拟页号VPN。MMU利用VPN来选择得当的PTE,将页表条目中的物理页号和VPO串联起来就是相应的物理所在。
7.4 TLB与四级页表支持下的VA到PA的变换

TLB是翻译后备缓冲器,用于加速所在的翻译。MMU使用虚拟所在的VPN部分来访问TLB,查找虚拟所在的条目。

图65 TLB

如果TLB掷中,则直接得到物理所在,减少内存访问;TLB不掷中时会产生额外的内存访问,通过多级页表查找。



  • TLB掷中 (b)TLB不掷中
图66 访问TLB

多级页表用于减少常驻于内存中的页表大小,将VPN划分为相同大小的差别部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。TLB未掷中时,使用多级页表来查找,步调如下:
(1)从一级页表PML4中使用虚拟所在的最高级部分索引,获取指向下一级页表的物理所在。
(2)从二级页表PDP中使用虚拟所在的下一级部分索引,获取指向下一级页表的物理所在。
(3)从三级页表PD中使用虚拟所在的次级部分索引,获取指向下一级页表的物理所在。
(4)从四级页表PT中使用虚拟所在的最低级部分索引,获取终极的物理页框所在。
(5)将物理页框所在与虚拟所在中的页内偏移组合,形成完备的物理所在。
(6)将新的虚拟所在到物理所在的映射更新到TLB中,以便下次访问加快速度。

图67 多级页表

当MMU翻译每一个虚拟所在时,它还会更新别的两个内核缺页处置惩罚步伐会用到的位。每次访问一个页时,MMU都会设置A位,称为引用位。内核可以用这个引用位来实现它的页替换算法。每次对一个页进行了写之后,MMU都会设置D位,又称修改位或脏位。修改位告诉内核在复制替换页之前是否必须写回牺牲页。内核可以通过调用一条特别的内核模式指令来清除引用位或修改位。
core i7接纳四级页表层次结构,每个进程有它本身私有的层次结构。下图给出了Core i7 MMU如何使用四级的页表来将虚拟所在翻译成物理所在。36位VPN被划分成四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理所在。VPN1提供到一个Ll PTE的偏移量,这个PTE包含L2页表的基所在。VPN 2提供到一个L2 PTE的偏移量,以此类推。

图68 core i7页表翻译

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

得到物理所在后,将物理所在分为CT(缓存标记)、CI(缓存索引)和CO(块偏移)。根据CI查找对应的组并判断是否掷中,从而返回对应物理所在中的数据。
具体过程如下:
(1)CPU发出访存哀求。
(2)查抄L1缓存,利用CI找到对应的组,再比较CT,若掷中,即CT相同且有效位是1,则返回数据;如果未掷中,则继承L2。
(3)查抄L2缓存,如果掷中,必要将其缓存在L1并返回,如有空闲块则存放在空闲块中,否则根据替换策略选择牺牲块。若未掷中,则继承L3。
(4)查抄L3缓存,找到后必要缓存在L1、L2并返回,替换策略同上。
(5)若数据不在L1、L2、L3中,则必要从主存中读取数据,加载到缓存中,再数据返回给CPU。
7.6 hello进程fork时的内存映射

调用fork函数时,创建一个新的运行的子进程,fork函数被调用一次,却返回两次,子进程返回0,父进程返回子进程的PID。子进程最初的内存空间是与父进程虚拟所在空间相同但是独立的一份副本,包罗代码、数据段、堆、共享库以及用户栈,但子进程有差别于父进程的PID。
为了给这个新的进程创建虚拟内存,fork创建了当进步程的mm_struct、区域结构和页表的原样副本,将两个进程的每个页面都标记为只读,并将两个进程的每个区域结构都标记为私有的写时复制。当这两个进程中的任一个进行写操纵时,写时复制机制就会创建新页面,从而为每个进程保持了私有空间所在。
7.7 hello进程execve时的内存映射

调用execve函数时启动加载器,在当进步程中加载并运行包含在可实行目标文件hello中的步伐,用可实行目标文件hello步伐替换当出息序的所在空间。
execve内存映射的步调:
(1)清空现有所在空间,删除当进步程虚拟所在中已存在的用户部分。
(2)映射私有区域,为新步伐的代码、数据、bss和栈区域创建新的区域结构,如7.6中所分析的,这些接纳写时复制机制新写入的区域都是私有的。
(3)映射共享区域,hello步伐与共享库libc.so链接,libc.so是动态链接到这个步伐中的,然后再映射到用户虚拟所在空间中的共享区域内。
(4)设置步伐计数器(PC),execve做的末了一件事变就是设置当进步程上下文的步伐计数器,使之指向代码区域的入口点。

图68 execve函数

7.8 缺页故障与缺页中断处置惩罚

7.8.1缺页故障:
DRAM缓存不掷中称为缺页。当MMU试图翻译某个虚拟所在A时,如果在TLB中找不到相应的页表项,说明发生了一个缺页故障非常,进而调用内核中的缺页非常处置惩罚步伐。

7.8.2缺页中断处置惩罚:
缺页故障会导致CPU产生一个中断哀求,这就是缺页中断。缺页中断触发操纵体系的中断处置惩罚步伐,处置惩罚缺页故障。
缺页中断处置惩罚的流程如下:
(1)查抄虚拟所在是否合法,如果没有匹配到任何效果,说明所在A是不合法的,于是报出段错误并终止这个进程。
(2)查抄内存访问是否合法,即对内存进行的读、写操纵是否有权限,如果进行了非法的访问则触发掩护非常并终止这个进程。
两步查抄都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。恢复进程上下文,就可以重新实行引发缺页故障的指令,并对虚拟所在进行正常的翻译。

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。对每个进程,内核维护着一个变量brk,它指向堆的顶部。分配器将堆视为一组差别大小的块的集合来维护。每个块就是一个一连的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式的保存为供应步伐使用。空闲块保持空闲,直到它被应用所分配。已分配的块保持已分配状态,直到它被释放,这种释放要么是应用步伐体现实行的,要么是内存分配器自身隐式实行的。
体现分配器要求应用体现地释放任何已分配的块。隐式分配器要求分配器检测一个已分配块何时不再被步伐所使用,那么就释放这个块。
隐式空闲链表:空闲块通过头部中的大小字段隐含地链接着,分配器可以可以通过遍历堆中全部的块,从而间接的遍历整个空闲块的集合。
放置已分配的块:当一个应用哀求一个k字节的块时,分配器搜刮空闲链表,查找一个大小足够放置所哀求块的空闲块。分配器实行这种搜刮的方式是由放置策略确定的。一些常见的策略是首次适配、下一次适配和最佳适配。
(3)分割空闲块:一旦分配器找到一个匹配的空闲块,它就必须做别的一个决策决定,那就是分配这个空闲块中多少的空间。一个选择是用整个空闲块,当放置策略倾向于产生好的匹配,这一决策产生的额外内部碎片也是可接受的;当匹配不太好,分配器通常会选择将这个空闲块分割为两部分,第一部分酿成分配块,剩下的酿成一个新的空闲块。
(4)若分配器找不到合适的空闲块,一个选择是合并那些在内存中物理上相邻的空闲块来创建一些更大的空闲块。如果还是不能生成足够大的块,就会通过sbrk函数,向内核哀求额外的堆内存。至于合并策略,分配器可以选择立即合并或推迟合并。
(5)显式空闲链表:根据定义,步伐不必要一个空闲块的主体,以是实现体现空闲链表的指针可以存放在这些空闲块的主体里面。
(6)分离的空闲链表:维护多个空闲链表,其中每个链表中的块有大致相称的大小,从而减少分配时间。分离存储方法包罗简单分离存储、分离适配、伙伴体系等方法。
7.10本章小结

本章首先介绍了hello的存储所在空间,随后以hello为例分别分析了intel的段式管理及逻辑所在到线性所在的变换、页式管理及线性所在到物理所在的变换。分别分析了指定情况下虚拟所在到物理所在的转换和物理内存访问及其具体流程。
分析了hello进程fork和execve时的内存映射,缺页故障、缺页中断处置惩罚和流程,末了介绍了动态存储分配管理,包罗体现分配器、隐式分配器与各种分配方式。
(第7章 2分)


8hello的IO管理


8.1 Linux的IO设备管理方法

全部的I/O设备(例如网络、磁盘和终端)都被模子化为文件,而全部的输入和输出都被当做对相应文件的读和写来实行,这种将设备优雅地映射为文件的方式,答应Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得全部的输入和输出都能以一种统一且同等的方式来实行。
Linux的设备管理的主要任务是控制设备完成输入输出操纵,把各种设备硬件的复杂物理特性的细节屏蔽起来,提供一个对各种差别设备使用统一方式进行操纵的接口。Linux把设备看作是特别的文件,体系通过处置惩罚文件的接口—虚拟文件体系VFS来管理和控制各种设备。
设备的模子化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数

8.2.1 IO接口及操纵
全部的I/O设备(例如网络、磁盘和终端)都被模子化为文件,而全部的输入和输出都被当做对相应文件的读和写来实行,这种将设备优雅地映射为文件的方式,答应Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得全部的输入和输出都能以一种统一且同等的方式来实行。

(1)打开文件
 一个应用步伐通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做形貌符,它在后续对此文件的全部操纵中标识这个文件。内核记录有关这个打开文件的全部信息。应用步伐只需记着这个形貌符。
Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(形貌符为0)、标准输出(形貌符为1)和标准错误(形貌符为2)。头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,它们可用来取代显式的形貌符值。
                          
(2)改变当前文件的位置
 对于每个打开的文件,内核保持着一个文件位置k,初始为0。这个文件位置是从文件开头起始的字节偏移量,应用步伐能够通过实行seek操纵,显式地设置文件的当前位置为k。

(3)读写文件
一个读操纵就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增长到k+n。给定一个大小为m字节的而文件,当k>=m时实行读操纵会触发一个成为end-of-file(EOF)的条件,应用步伐能检测到这个条件。在文件末了处并没有明确的“EOF符号”。
类似地,写操纵就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

(4)关闭文件
当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为相应,内核释放文件打开时创建的数据结构,并将这个形貌符恢复到可用的形貌符池中。无论一个进程由于何种缘故原由终止时,内核都会关闭全部打开的文件并释放它们的内存资源。

8.1.2 IO接口及函数
(1)打开和关闭文件
进程是通过调用open函数来打开一个已存在的文件大概创建一个新文件的,open函数将filename转换为一个文件形貌符,而且返回形貌符数字。返回的形貌符总是在进程中当前没有打开的最小形貌符。flags参数指明白进程打算如何访问这个文件,也可以是一个或更多位掩码的或,为写提供一些额外的指示。mode参数指定了新文件的访问权限位。
作为上下文的一部分,每个进程都有一个umask,它是通过调用umask函数来设置的。当进程通过带某个mode参数的open函数调用来创建一个新文件时,文件的访问权限位被设置成mode&~umask。
末了进程通过调用close函数关闭一个打开的文件。关闭一个已关闭的形貌符会出错。


  • 读和写文件
应用步伐是通过分别调用read和write函数来实行输入与输出的。
read函数从形貌符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1体现一个错误、返回值0体现EOF,其他返回值体现的是实际传送的字节数目。
write函数从内存位置buf复制至多n个字节到形貌符fd的当前文件位置。


  • 结实读写
RIO会自动处置惩罚不敷值,在像网络步伐这样轻易出现不敷值的应用中,RIO包提供了方便、结实和高效的I/O。RIO提供了两类差别的函数:无缓冲的输入输出函数(rio_readn和rio_writen)和带缓冲的输入函数(rio_readnb)。
无缓冲的输入输出函数直接在内存和文件之间传送数据,没有应用级缓冲。
带缓冲的输入函数高效地从文件中读取文本行和二进制数据,这些文件的内容缓存在应用级缓冲区内,带缓冲的RIO输人函数是线程安全的。


  • 读取文件元数据
应用步伐通过调用stat和fstat函数检索关于文件的信息。stat函数以一个文件名作为输入,并填写stat数据结构中的各个成员。fstat相似,只不过是以文件形貌符而不是文件名作为输入。
当讨论web服务器时,stat数据结构中有两个成员必要留意。st_size成员包含了文件的字节数大小,st_mode成员编码了文件访问许可位和文件范例。、


  • 读取目次内容
应用步伐用readdir系列函数来读取目次的内容。
函数opendir以路径名为参数,返回指向目次流的指针。每次对readdir的调用返回都是指向流dirp中下一个目次项的指针,如果没有更多目次项则返回NULL。若出错,readdir返回NULL并设置errno。函数closedir关闭流并释放全部找资源。

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;
  • }
printf调用的vsprintf原函数如下:

  • int vsprintf(char *buf, const char *fmt, va_list args) 

  •     char* p; 
  •     char tmp[256]; 
  •     va_list p_next_arg = args; 
  •    
  •     for (p=buf;*fmt;fmt++) { 
  •         if (*fmt != '%') { 
  •         *p++ = *fmt; 
  •         continue; 
  •         } 
  •         fmt++; 
  •         switch (*fmt) { 
  •             case 'x': 
  •             itoa(tmp, *((int*)p_next_arg)); 
  •             strcpy(p, tmp); 
  •             p_next_arg += 4; 
  •             p += strlen(tmp); 
  •             break; 
  •             case 's': 
  •                 break; 
  •             default: 
  •                 break; 
  •             } 
  •     } 
  •     return (p - buf); 

vsprintf函数将全部的参数内容格式化之后存入buf,然后返回格式化数组的长度。write函数将buf中的i个元素写到终端。从vsprintf生成体现信息,到write体系函数,到陷阱-体系调用 int 0x80或syscall.字符体现驱动子步伐:从ASCII到字模库到体现vram(存储每一个点的RGB颜色信息)。体现芯片按照刷新频率逐行读取vram,并通过信号线向液晶体现器传输每一个点(RGB分量)。
vsprintf函数将参数按照格式字符串将内容格式化后存入缓冲区buf。格式化完成后,printf通过调用write体系函数,将格式化的字符串输出。
调用write输出触发了从用户态到核心态的转换,这通过int 0x80大概syscall陷阱指令实现。体系调用处置惩罚步伐接收体系调用哀求,并调用sys_write来处置惩罚标准输出操纵。
字符体现驱动子步伐通过字模库将ASCII字符转换为像素点的矩阵体现,这些像素点被写入体现内存vram存储每一个点的RGB颜色信息。体现芯片按照刷新频率读取vram逐行读取数据,并通过信号线向液晶体现器传输每一个点,终极实现printf的功能将数据输出到屏幕上。

8.4 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时,步伐等待直到用户输入,当用户按下回车键之后,getchar才开始从标准输入中每次读取一个字符。
getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保存在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
getchar调用read体系函数,以从标准输入设备读取数据。调用read陷阱指令导致hello进程在用户态和内核态之间进行转换。在内核中,sys_read体系函数处置惩罚哀求,从内核的键盘缓冲区中读取数据。sys_read体系调用完成后,内核返回用户态,继承实行用户步伐。getchar函数将read函数返回的字符返回给调用getchar的步伐进行进一步处置惩罚。
当用户按下键盘上的一个键时,产生中断信号,通知CPU发生了按键事件。键盘中断触发后,CPU停息当前实行的指令,并跳转到内核中专门处置惩罚键盘中断的非常处置惩罚步伐。每次按键都会产生扫描码,体现按键的物理位置。中断处置惩罚步伐读取这些扫描码,通过键盘映射表将其转换为ASCII码,末了将转换后的字符存储在体系的键盘缓冲区。
8.5本章小结

本章主要介绍了linux体系的I/O接口与设备管理方法,并详细论述了I/O接口操纵和函数部分。末了分析了printf和getchar函数的实现,以及其实现过程的具体流程和非常处置惩罚。
(第8章1分)

结论

(1)步伐员相识C语言步伐根本输入输出知识后,编写C语言文件hello.c。
(2)预处置惩罚器对hello.c进行预处置惩罚,根据以#字符开头的命令修改原始的C步伐得到修改后的hello.i文件。
(3)编译器将C语言编写的hello.i翻译成更靠近机器语言的汇编语言文件hello.s。其中每条语句都以一种文本格式形貌了一条或多条低级机器语言指令,为差别高级语言的差别编译器提供了通用的输出语言。
(4)汇编器将汇编语言文件hello.s翻译成机器语言指令,并将其打包成为可重定位目标文件hello.o。
(5)链接器将多个已经预编译好的可重定位目标步伐合并成一个可实行目标文件hello,hello可以被加载到内存中由体系实行。
(6)实行hello时,shell会读取并解析命令行发现这并非内置指令,通过fork创建新的子进程,并通过execve加载步伐的内存。
(7)hello进程实行过程中,服从CPU的进程调度和上下文切换机制,同时对非常做出正确的相应。
(8)步伐实行完毕后,由父进程回收子进程,hello进程从作业列表中消散。
(9)hello的一生中,有所在之间的转换、内存从无到有再到无、各种工具之间协同合作、各种输出设备之间调度共赢。

如果步伐员不相识以上计算机体系知识,也可以通过调用GCC编译器调用编译体系中的各部分,利用hello.c成功的生成可实行步伐hello,而且实行hello得到正确的输出效果。
hello的一生竣事了,但是它并没有彻底消散。由hello.c生成的hello仍然保存在磁盘中,伴随步伐员走向更广阔、更深奥的计算机体系天地。

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

附件

文件名

文件作用

hello.i

预处置惩罚后得到的文本文件

hello.s

编译后得到的汇编语言文件

hello.o

汇编后得到的可重定位目标文件

hello.elf

hello.o输出的ELF格式文件

hello.asm

hello.o反汇编得到的汇编语言文件

hello

链接后得到的二进制可实行文件

helloout.elf

hello输出的ELF格式文件

Helloout.asm

hello反汇编得到的汇编语言文件


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


参考文献


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



  • Randal E.Bryant, David O'Hallaron. 深入明白计算机体系[M]. 机械工业出版社.2018.4 
  • printf 函数实现的深入剖析https://www.cnblogs.com/pianist/p/3315801.html
  • 线性所在_百度百科
  • 二进制所在_百度百科
  • 逻辑所在_百度百科
  • 逻辑所在 线性所在 虚拟所在 物理所在关系https://blog.csdn.net/icandoit_2014/article/details/87897495


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



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

卖不甜枣

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表