HIT-步伐人生-Hello’s P2P-HIT哲崽

打印 上一主题 下一主题

主题 568|帖子 568|积分 1708






盘算机系统


大作业



题     目  步伐人生-Hello’s P2P 
专       业       人工智能领域      
学     号                
班     级         22WL028        
学       生          yxz        
指 导 教 师           郑贵滨         






盘算机科学与技能学院

2024年5月

摘  要

本文对hello步伐的生命周期举行了分析,首先完成hello.c源步伐的编写,之后使用C预处理器cpp对其举行预处理,生成hello.i,再运行C编译器(ccl)将其举行翻译生成汇编语言文件hello.s,然后运行汇编器(as)将其翻译成一个可重定位目的文件hello.o,末了运行链接器步伐ld将hello.o和系统目的文件组合起来,创建了一个可执行目的文件hello。当shell吸收到./hello的指令后开始调用fork函数创建hello进程,execve加载hello进入内存,由CPU控制步伐逻辑流的运行,停止,上下文切换和异常控制流的处理,末了结束进程并由父进程举行接纳,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简介

hello步伐的生命周期是从一个高级C语言步伐开始的,由于这种情势能够被人读懂。然而,为了在系统上运行hello.c步伐每条C语句都必须被其他步伐转化为一系列的低级机器语言指令。然后这些指令按照一种称为可执行目的步伐的格式打好包,并以二进制磁盘文件的情势存放起来。目的步伐也称为可执行目的文件,在这里,GCC编译器驱动步伐读取源步伐文件hello.c, 并把它翻译成一个可执行目的文件hello。之后用户通过shell键入./hello下令开始执行hello步伐,shell通过fork函数创建一个子进程,再由子进程执行execve函数加载hello。以上就是hello从源步伐到一个被执行的进程的P2P(program to process)过程了。
在execve执行hello步伐后,内核为hello进程映射假造内存。在hello进入步伐入口之后,hello相关的数据被内核加载到物理内存中,hello步伐开始正式被执行。内核还须要为hello分配时间片、逻辑控制流。末了,当hello步伐运行结束,终止成为僵死进程后,由shell接纳hello进程,在系统中删除与hello有关的数据内容。这便是hello的020(From Zero-0 to Zero-0)过程。
1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk

软件环境: Windows11 64位;VMware16.1.0;Ubuntu 20.04 LTS 64位

开辟与调试工具:Visual Studio2022、gcc、Objdump、edb等

1.3 中间结果

文件名

文件作用

hello.c

步伐的源代码
hello.i

hello.c文件预处理后的文本文件
hello.s

hello.i文件编译后的汇编文件
hello.o

hello.s汇编后的可重定位目的文件
hello

hello.s链接后的可执行文件

1.4 本章小结

本章简单介绍了hello的p2p,020过程,列出了本次实验信息:环境、中间结果,而且大致简述了hello步伐从c步伐hello.c到可执行目的文件hello经过的历程。


第2章 预处理

2.1 预处理的概念与作用

预处理的概念:
预处理器cpp根据以字符#开头的下令,修改原始的C步伐。比如hello.c中第1行的#include <stdio.h>下令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入步伐文本中。或是比如对宏#define PI_VALUE 3.14159,然后在步伐中使PI_VALUE的值都被更换为3.14159。预处理的结果就得到了另一个C步伐,通常是以.i作为文件扩展名。


图2.1-1 hello.c中包含的头文件

预处理的作用:
1.实现条件编译,通过预处理可以实现部分代码的在某些条件下的选择性编译。如#ifdef,#ifndef,#else,#elif,#endif等。这些伪指令的引入使得步伐员可以通过定义不同的宏来决定编译步伐对哪些代码举行处理。预编译步伐将根据有关的文件,将那些不须要的代码过滤掉。
2.实现宏定义,在预处理阶段用定义的现实数值将宏更换。
3.实现头文件引用,将头文件的内容复制到源步伐中以实现引用。如#include "FileName"或者#include 等。该指令将头文件中的定义齐备都参加到它所产生的输出文件中,以供编译步伐对之举行处理。
4.实现注释,将c文件中的注释从代码中删除。
5.实现特殊符号的使用。如处理#line、#error、#pragma以及#等。例如在源步伐中出现的LINE标识将被表明为当前行号(十进制数),FILE则被表明为当前被编译的C源步伐的名称。预编译步伐对于在源步伐中出现的这些串将用合适的值举行更换。
2.2在Ubuntu下预处理的下令

预处理下令:gcc hello.c -E -o hello.i

图2.2-1 Ubuntu下的预处理下令

2.3 Hello的预处理结果剖析

在运行2.2的预处理下令后,我们可以看到shell当前加载的目录下产生了新文件hello.i,如图2.3-1。

图2.3-1 生成的hello.i文件

打开hello.i文件后,我们可以看到,hello.i的内容较hello.c多了很多行,这是由于预处理后,包含的头文件中的内容被复制在了.i文件中,同时我们还可以发现,.c文件中的注释行被删除了。


图2.3-2 hello.c与hello.i内容对比

2.4 本章小结

本章介绍了预处理的相关概念及其所举行的一些处理。展示如安在ubuntu下用gcc对.c文件举行预处理,为文件后续的操纵打下基础。

第3章 编译

3.1 编译的概念与作用

编译的概念:在编译阶段,编译器检查是否有语法错误,检查无误后,编译器将.i文件翻译成.s文件,它包含一个汇编语言步伐,该步伐包含main函数的定义。
编译的作用:汇编语言步伐为不同高级语言的不同编译器提供了通用的输出语言,为低级机器语言。编译将高级语言转化为汇编语言能够检测代码的正确性,并为接下来将汇编语言生成机器可识别的机器码做准备。
3.2 在Ubuntu下编译的下令

Ubuntu下编译的下令:gcc -S -o hello.s hello.i

图3.2-1编译指令

3.3 Hello的编译结果剖析



图3.3 hello的编译结果

得到的汇编代码如上图所示。
3.3.1初始伪指令与主函数开头

图3.3.1-1 伪指令与主函数开头的汇编语言代码

所有以’.’开头的行都是指导汇编器和链接器工作的伪指令。
.file:声明源文件
.text:代码节
.section:    .rodata:只读代码段
.align 8:数据或者指令的地址对齐方式
.string:声明一个字符串(.LC0,.LC1)
.global:声明全局变量(main)
.type:声明一个符号是数据范例照旧函数范例(此处@function表现main为函数范例)
3.3.2 数据:
(1)字符串:
步伐中有两个字符串,两个字符串都在只读数据段中,如上图3.3.1-1中的.LC0,.LC1所示,两个字符串作为printf的参数。字符串被放在只读数据段中。
(2)局部变量i
main函数声明了局部变量i,编译器举行编译时将变量放置在堆栈中,通过rbp的相对偏移来访问。

图3.3.2‑1 局部变量i的相对偏移访问

(3)数组:
c代码中的数组访问有argv[1]、argv[2]、argv[3],在汇编代码中访问这三个量是通过数组首地址加偏移量的方式实现的,详细汇编代码如图3.3.2-2所示。


图3.3.2‑2数组元素访问的汇编表现

(4)形参有符号数argc、字符数组首地址*argc
如图3.3.2-3,符号数argc和字符型数组指针argv,根据寄存器使用规则,这两个参数分别通过%edi和%rsi通报。通过rbp的偏移量来访问。在步伐最开始,为main函数建立栈帧,并完成参数通报。argc存放在-20(%rbp),argv作为main函数的参数,数组的元素都是指向字符范例的指针,起始地址存放在栈中-32(%rbp)的位置。

图3.3.2‑3 形参argc、*argv的汇编表现


3.3.3 操纵
(1)赋值操纵:
在原本c步伐中的赋值操纵只有i=0这个赋值语句。在汇编中是通过mov立即数的方式体现的,movb:一个字节;movw:两个字节;movl:四个字节;movq:八个字节如图3.3.3-1:

图3.3.3‑1 i=0赋值操纵的汇编代码

在汇编器处理一些c语言代码的过程中也会产生一些赋值操纵,还有通过以lea(地址通报)来赋值的方式,如图3.3.3-2所示:

图3.3.3‑2 lea地址通报赋值操纵的汇编代码

(2)条件转移:
argc!=5;是在一条件语句中的条件判断argc是否为4,举行编译时,这条指令被编译为:cmpl       $5, -20(%rbp),如图3.3.3-3所示,同时这条cmpl的指令还有设置条件码的作用,当根据条件码来判断是否须要跳转到分支中。

图3.3.3‑3 argc !=5关系判断操纵的汇编代码

i<10在hello.c作为判断循环条件,被编译为:cmpl   $9, -4(%rbp),如图3.3.3-2所示,盘算 i与9是否相等,然后设置条件码,为下一步使用jle条件码举行跳转做准备,若不相等,则跳转到.L4,相等则运行下面的部分。

图3.3.3‑2 i<10关系判断操纵的汇编代码


(3)算数操纵
hello.c中举行了i++的算术操纵,i为int范例。在汇编代码中是通过add来实现的,如图3.3.3-5所示:

图3.3.3‑5 i++算数操纵的汇编代码

(4)范例转换
hello.c中的sleep(atoi(argv[4]));语句存在atoi()的范例转换,在hello.c中用atoi将字符串转化成int型,在hello.s中用call语句调用atoi函数逼迫处理该范例转换,如图3.3.-6所示。

图3.3.3‑5 atoi范例转换的汇编操纵的汇编代码

5)函数操纵:
在hello.c中多处涉及函数操纵,调用另一个函数来执行当前使命,如printf、atoi、getchar、sleep、exit,在hello.s中对此的处理均是先完成参数通报(有入口参数的情况下),然后在用call语句转到相应函数的入口处继续执行。

printf:%rdi通报:

图3.3.3‑6 printf函数的汇编操纵的汇编代码

exit:%edi通报:

图3.3.3‑7 exit函数的汇编操纵的汇编代码

atoi:用%rdi:

图3.3.3‑8 atoi函数的汇编操纵的汇编代码

sleep:用%edi通报:

图3.3.3‑9 sleep函数的汇编操纵的汇编代码

getchar:(无通报参数)

图3.3.3‑10 getchar函数的汇编操纵的汇编代码

函数返回:返回时,如有返回值,在返回之前会将返回值存放到%eax中,否则直接使用ret返回。在返回之前要恢复栈帧,删除被调用函数的栈帧,恢复调用函数的栈帧。函数返回时一个非常重要的指令为leave指令,leave指令的作用就是将被调用函数的栈帧抹除,实现过程如下图所示:

图3.3.3‑11函数返回操纵的汇编代码

3.4 本章小结

本章介绍了hello.i文件被编译为汇编代码的过程,同时对编译结果举行剖析。编译器通过词法分析和语法分析,来检查原始代码有没有错误,在确认没有错误之后,编译器会按照一定的规范生成与原始代码等价的汇编代码,在这个过程中,编译器大概会按照自己的明白,对原始代码结构和数据做出调整。

第4章 汇编

4.1 汇编的概念与作用

概念:汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制步伐的过程。汇编器(as)将汇编步伐翻译成机器语言指令,把这些指令打包成可重定位目的步伐的格式,并将结果生存在.o 目的文件中,.o 文件是一个二进制文件,它包含步伐的指令编码。
作用:将.s文件生成.o格式机器码,使其能被链接器ld链接生成可执行文件。
4.2 在Ubuntu下汇编的下令

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

图4.2‑1 在Ubuntu下汇编的下令

4.3 可重定位目的elf格式

4.3.1 ELF格式的详细内容

图4.3.1‑1 ELF格式的详细内容

4.3.2 ELF
打开ELF 的指令:readelf -a hello.o
打开之后,可以看到ELF文件从ELF头开始,如下图4.3.2-1所示,ELF头以一个16个字节的序列开始,这个序列形貌了生成该文件的系统的字巨细和字节顺序。在该ELF头中表现,以小端码机器存储,文件范例为可重定位目的文件。

图4.3.2‑1 hello.o的ELF头内容

ELF头中的Magic是一个定值,在步伐执行时会检查Magic的值是否正确,如果不正确则拒绝加载。ELF头告诉了我们文件的基本信息:
(1)类别是ELF64
(2)文件中的数据是按照2的补码储存的
(3)小端序(little endian)
(4)文件的范例是可重定位文件
(5)节头巨细为64字节
(6)节头的数量为14个
4.3.3 节头表
节头表告诉了我们每个节的巨细、名称、范例、读、写、执行权限以及对其方式。由于我们的步伐还未举行链接,因此每个节的起始位置都是0,在链接后会为每个节举行重定位以得到起始位置。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的巨细,如下图所示:

图4.3.3‑1 节头表的内容

4.3.4 符号表
存放步伐中定义和引用的函数和全局变量的信息。name是符号名称,对于可冲定位目的模块,value是符号相对于目的节的起始位置偏移,对于可执行目的文件,该值是一个绝对运行的地址。size是目的的巨细,type要么是数据要么是函数。Bind字段表明符号是本地的照旧全局的。同样由于还未举行链接重定位,偏移量Value还都是0。结果如下图4.3.4-1所示。

图4.3.4‑1 符号表的内容

4.3.5 重定位节

图4.3.5‑1 重定位节的内容

重定位节包含了.text文件中须要重定位的信息,在链接器将目的文件与别的文件举行链接时须要修改这些信息,可执行文件中不包含重定位节,在.text段,多了start函数为main的运行做准备。
Offset:须要被修改的引用节的偏移
Info:包括symbol和type两个部分,symbol在前面四个字节,type在后面四个字节
symbol:标识被修改引用应该指向的符号
type:重定位的范例
Type:告知链接器应该如何修改新的应用
Attend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整
Name:重定向到的目的的名称。
4.4 Hello.o的结果剖析

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

图4.4‑1 hello.o反汇编的内容

反汇编代码和汇编代码在指令格式上非常相似,但在以下几个方面存在不同:
1.立即数的引用不同,在反汇编中立即数是十六进制的,而汇编代码则是十进制。
2.反汇编的语言mov、add、push后无其他字符如b、w等。
3.子步伐的调用不同,反汇编代码中子步伐的调用是通过对主函数地址的相对偏移举行的,而在汇编代码中则是通过call直接加上函数名的方法举行的。
4.对全局变量的访问方式:反汇编语言通过pc相对寻址,通过rip+x的值举行访问,未重定位,故用0占位。在汇编语言中,通过.LC0+rip举行访问。
5.分支跳转不同,在反汇编代码中,分支转移是通过跳转到以主函数地址为基址的一个偏移地址中,而在汇编代码中则是通过.L4、.L3如许分块的方式来跳转的。
综上所述,反汇编代码与汇编代码在指令上是逐一对应的关系,只有在一些特殊的指令须要有引用的转化,其他地方险些完全一致。
4.5 本章小结

本章首先介绍了汇编的概念和作用,接着通过Linux汇编指令,对hello.s文件举行汇编,生成ELF可重定位目的文件hello.o。接着使用readelf工具,通过设置不同参数,查察了hello.o的ELF头、节头表、可重定位信息和符号表等,通太过析明白可重定位目的文件的内容。末了与hello.s比较,分析不同,并说明机器语言与汇编语言的逐一对应关系。 hello.o照旧不能直接运行,它还不是可执行步伐,须要进一步的链接来生成可执行步伐。

第5章 链接

5.1 链接的概念与作用

链接的概念:链接就是通过链接器将可重定位目的步伐(hello.o)与printf.o等库函数(包括共享库)链接起来,生成可重定位目的步伐(hello)。链接包括动态链接,静态链接。链接时间分为在第一次加载时链接,已经开始运行后链接。
链接的作用:链接可以在编译、汇编、加载和运行时执行。链接方便了模块化编程。链接器在软件开辟过程中扮演着一个关键的角色,由于它们使得分离编译成为大概。我们不消将一个大型的应用步伐组织为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译别的文件。
5.2 在Ubuntu下链接的下令

使用ld的链接下令,应截图,展示汇编过程! 留意不只连接hello.o文件
下令: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

图5.2‑1 Ubuntu下举行链接的下令

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

5.3.1 ELF

图5.3.1‑1 hello.o的ELF头内容

可以看到,可执行文件的ELF头与可重定位目的文件的ELF头有以下几个不同:
1.节头的数量产生了变化,从14变化成为27。
2.步伐的入口点不一样,由于连接上了库文件,使得main函数不再是从0x0开始。同理节头的开始位置也发生了变化。
3.文件的范例不同,可执行文件的范例不再是REL而是EXEC。
5.3.2 节头表

图5.3.2‑1 hello.o的节头表内容

节头表对hello中所有的节信息举行了声明,其中包括巨细Size以及在步伐中的偏移量Offset,因此根据Section Headers中的信息我们就可以用HexEdit定位各个节所占的区间(起始位置,巨细)。其中Address是步伐被载入到假造地址的起始地址。可以看到与hello.o不同,在可执行文件中经过重定位每个节的地址不再是0,而是根据自身巨细加上对齐规则盘算的偏移量。
5.3.3 符号表
Hello的符号表如下图5.3.3-1所示

图5.3.3‑1 hello.o的符号内容

在可执行文件中多出了.dynym节(也就是上方图中.dynsym中)。这里面存放的是通过动态链接剖析出的符号,这里我们剖析出的符号是步伐引用的头文件中的函数。
5.3.4 重定位节

图5.3.4‑1 hello.o的重定位节内容

可以看出,由于链接了别的头文件,重定位节的偏移量与hello.o已经完全不一样了。
5.4 hello的假造地址空间

使用edb加载hello,查察本进程的假造地址空间各段信息,并与5.3对照分析说明。
根据节头表的信息,我们可以知道ELF是从0x401000开始的,如图5.4-1所示。查察edb,可以看出hello的假造地址空间开始于0x401000,结束于0x401ff0,如下图。

图5.4‑1 elf的假造地址空间

我们可以看到右侧图中蓝色部分为hello 可执行文件,下方四个来自动态共享库ld-linux-x86.64.so。通过节头部表,我们可以找到edb的各个节的信息,如.plt.sec,假造地址开始于0x401090,字节偏移为0x1090。

图5.4‑2 .plt.sec的假造地址空间

5.5 链接的重定位过程分析

首先,可以看到的不同是文件中多了很多函数,一些在hello.o中没有的函数,这些都是在hello.c中没有定义却直接使用的函数,这些函数定义在共享库中,在链接时完成了符号剖析和重定位,如printf、sleep等链接的时间将头文件链接到了可执行文件中。
其次,在hello.o中call、jmp指令后紧跟着的是相对地址,而hello中紧跟的是假造内存简直定地址,原因在于链接器完成了重定位过程,可以确定运行时的地址。在hello.o反汇编代码中出现以main加上相对偏移的跳转已经全部被重写盘算,这是由于在重定位后main函数有了全新的地址,使得这个盘算成为大概。同时对子函数的call引用也在重定位后重写盘算来了

图5.5 链接的重定位过程分析

重定位的大要过程是链接器ld将所有链接文件中相同的节合并,并按照要求盘算新的偏移地址赋值给新的节。同时链接器按链接指令的顺序搜刮符号表,查找符号引用。重定位寻址分为两种,一个是绝对地址寻址,一个是PC偏移地址寻址。
hello重定位的过程:
(1)重定位节和符号定义链接器将所有范例相同的节合并在一起后,这个节就作为可执行目的文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,步伐中每条指令和全局变量都有唯一运行时的地址。
(2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目的模块中称为的重定位条目的数据结构。
(3)重定位条目当编译器遇到对终极位置未知的目的引用时,它就会生成一个重定位条目。代码的重定位条目放在.rel.txt。
5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及步伐终止的所有过程。请列出其调用与跳转的各个子步伐名或步伐地址。
从加载hello到_start,到call main,以及步伐终止的所有过程记载如下表,包括跳转的各个子步伐名和步伐地址。
步伐名称
地址
ld-2.32.so!_dl_start
0x7f96ed2e1ea0
ld-2.32.so!_dl_init
0x7f96ed2f0630
hello! _start
0x400500
libc-2.32.so! __libc_start_main
0x7fbdf0cccab0
hello!puts@plt
0x401030
hello!exit@plt
0x401060
hello!printf@plt
0x401050
hello!sleep@plt
0x401070
hello!getc@plt
0x401080
libc-2.32.so!exit
0x7f66b7b9e120
输出结果后结束:

图5.6 edb调试结束后表现结果

5.7 Hello的动态链接分析

在步伐中动态链接是通过耽误绑定来实现的,耽误绑定的实现依赖全局偏移量表GOT和过程连接表PLT实现。GOT是数据段的一部分,PLT是代码段的一部分。
PLT数组中每个条目时16字节,PTL[0]是一个特殊的条目,他跳转到动态链接器中。每个可被执行步伐调用的库函数都有自己的PLT条目。PLT[1]调用_libc_start_main函数负责初始化。GOT数组中每个条目八个字节。GOT[0]和GOT[1]中包含动态链接器剖析地址时会用的信息,GOT[2]时动态练级去在ld-linux.so模块的入口点。别的的每一个。
通过5.3.2的节头表我们可以找到.GOT.PLT的地址为0000000000404000
图5.7-1  节头表中.GOT.PLT的地址

在运行dl_start和dl_init之前,GOT.PLT表如图5.7-2所示

图5.7-2 运行dl_start之前.GOT.PLT表

调用dl_start之后,.GOT.PLT表变为图5.7-3所示。

图5.7-3  运行dl_start之后.GOT.PLT表

动态链接是一项有趣的技能。考虑一个简单的事实,printf,getchar如许的函数着实使用的太过频繁,因此如果每个步伐链接时都要将这些代码链接进去的话,一份可执行目的文件就会有一份printf的代码,这是对内存的极大浪费。为了遏制这种浪费,对于这些使用频繁的代码,系统会在可重定位目的文件链接时仅仅创建两个辅助用的数据结构,而直到步伐被加载到内存中执行的时间,才会通过这些辅助的数据结构动态的将printf的代码重定位给步伐执行。即是说,直到步伐加载到内存中运行时,它才知晓所要执行的代码被放在了内存中的哪个位置。这种技能被称为耽误绑定,将过程地址的绑定推迟到第一次调用该过程时。而那两个辅助的数据结构分别是过程链接表(PLT)和全局偏移量表(GOT),前者存放在代码段,后者存放在数据段。
5.8 本章小结

本章学习了链接的概念,通过对比hello.o和hello,明白了链接的作用和链接是怎样符号剖析、重定位的。这是步伐生成可执行文件的末了一步,也是将大型步伐项目分解成小模块的关键所在。

第6章 hello进程管理

6.1 进程的概念与作用

进程就是一个执行中的步伐的实例。系统中每个步伐都运行在某个进程的上下文中。进程提供给应用步伐两个假象:
独立的逻辑控制流:每个步伐好像独占CPU。(内核通过上下文切换机制来实现)私有的空间地址:每个步伐好像独占内存。(由内核的假造内存机制来实现)
6.2 简述壳Shell-bash的作用与处理流程

shell的作用:shell作为UNIX的一个重要组成部分,是它的外壳。也是用户与UNIX系统的交互作用界面。Shell是一个下令表明步伐。除此,它照旧一个高级步伐筹划语言。用shell编写的步伐称为shell过程。shell的一项主要功能是在交互方式下表明从下令行输入的下令。shell的另一项重要功能是制定用户环境,这通常在shell的初始化文件中完成。shell还能用作表明性的编程语言。
处理流程:
1、从终端读入输入的下令。
2、将输入字符串切分得到所有的参数
3、如果是内置下令则立即执行
4、否则调用相应的步伐执行
5、shell 应该担当键盘输入信号,并对这些信号举行相应处理
6.3 Hello的fork进程创建过程

输入./hello后,父进程会用fork函数创建子进程hello。新创建的子进程险些和父进程相同。子进程得到和父进程相同但独立的一份副本,包括代码和数据段,堆,共享库和用户栈。由于子进程有和父进程任何打开文件形貌符相同的副本,所以父进程打开的文件,子进程可以读写。父进程和子进程最大不同是它们有不同pid。
fork是并发运行的。fork执行一次,返回两次,第一次是在父进程中,返回子进程pid,第二次是在子进程中,返回0。如图6.3-1所示。
终端步伐通过调用fork()函数创建一个子进程,子进程得到与父进程完全相同但是独立的一个副本,包括代码段、段、数据段、共享库以及用户栈。子进程还得到与父进程任何打开文件形貌符相同的副本,父进程和子进程最大的不同时他们的PID是不同的。父进程与子进程是并发运行的独立进程,内核能够以任意方式瓜代执行它们的逻辑控制流的指令。在子进程执行期间,父进程默认选项是表现等待子进程的完成。

6.4 Hello的execve过程

Execve函数简单来说,就是加载并运行步伐。如果成功,调用1次,返回0次;如果失败返回-1。详细步骤如下:

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

(2)映射私有区域,所有新区域都是私有,写时复制.bss区域,请求二进制0,并映射到匿名文件中。代码和数据区域被映射到a.out文件中。

(3)映射共享区域。同时要删除原进程页表和Um-area-struct链表,创建新的列表Vm-area-struct 链表。

(4)将栈设定为请求二进制文件0的区域。映射到匿名文件且初长为0。

图6.4 通过execve函数创建进程

6.5 Hello的进程执行

调度:在进程执行的某些时候,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
逻辑控制流:即使在系统中通常有许多其他步伐正在运行,进程也可以向每个步伐提供一种假象,好像它在独占地使用处理器。如果使用调试器单步调试执行步伐,我们会看到一系列的步伐计数器(PC)的值,这些值唯一地对应于包含在步伐的可执行目的文件中的指令,或是包含在运行时动态链接到步伐的共享对象中的指令。这个PC值的序列叫做逻辑控制流,简称逻辑流。
并发流:系统为每个步伐都提供了一种只有它一个步伐在运行的假象,但是现实情况却不是如许的,系统中很有很多其他步伐在运行,比如我如今打字的word和我的假造机就是两个步伐,它们都在运行。那么处理器是如何执行它们的,以至于让它们看起来都在不绝止的不停运行呢?答案就是并发,如图6.5-1,处理器分时间段执行进程A、B、C,这个转换的时间非常短,所以看起来就好像每个进程都在持续不绝的在运行。多个逻辑控制流并发执行的一半征象被称为并发。一个进程和其他进程轮番运行的概念成为多使命,一个进程执行它的控制流的每一时间段就成为时间片。如图6.5-1中进程A就由两个时间片组成。
图6.5-1 进程时间片示意图

内核模式和用户模式:内核模式和用户模式不是两个进程,而是一个进程的不同模式,由一个模式位来控制,当设置了模式位时,进程就运行在内核模式中,这时间这个进程就可以执行指令集中的任何指令,而且可以访问系统中的任何内存位置。没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令,反之,用户步伐必须通过系统调用接口间接地访问内核代码和数据。运行步伐代码的进程一开始是处于用户模式,只有当发生停止、故障或者陷入系统调用如许的异常时,转而去执行异常处理步伐,这时进程才会变为内核模式。当它返回到应用步伐代码时,处理器就把模式从内核模式改为用户模式。
上下文切换:内核为每个进程维持一个上下文,上下文就是在进程执行的某些时候,内核可以决定枪战当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度。系统调用和停止也大概引发上下文切换。
hello进程执行,再进程调用execve函数之后,由上面分析可知,进程已经为hello步伐分配了新的假造的地址空间,而且已经将hello的.txt和.data节分配假造地址空间的代码区和数据区。最初hello运行在用户模式下,
结合进程上下文信息、进程时间片,论述进程调度的过程,用户态与核心态转换等等。输出Hello 2022113263 余欣哲,然后hello调用sleep函数之后进程陷入内核模式,内核不会选择什么都不做等待sleep函数调用结束,而是处理休眠请求主动释放当前进程,并将hello进程从运行队列中移出参加等待队列,定时器开始计时,内核举行上下文切换将当前进程的控制权交给其他进程,当定时器到时发送一个停止信号,此时进入内核状态执行停止处理,将hello进程从等待队列中移出重新参加到运行队列,成为就绪状态,hello进程就可以继续举行自己的控制逻辑流了。其工作机制示意如图6.5-2所示。

图6.5-2  hello的工作机制示意图

6.6 hello的异常与信号处理

6.6.1 异常与信号的简述
类别

原因

异步/同步

返回举动

停止

来自I/O设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在可恢复的错误

同步

大概返回到当前指令

终止

不可恢复的错误

同步

不会返回

异常可以分为如下四类:停止、陷阱、故障和终止。异常的同步异步指的是异常的发生和步伐的关系。
在hello步伐执行的时间如果从键盘键入ctrl+c如许就会受到SIGINT的终止信号,其他来自键盘的信号也类似。在hello结束的时间会向shell发送SIGCHLD信号告诉shell自己运行结束了。
6.6.2 通过键盘向hello发送信号
(1)乱按
乱按后,运行结果如图6.6.2-1所示
图6.6.2-1  乱按后hello运行结果图

可以看到,乱按不会影响步伐的运行,但是会在步伐运行结束后对shell发送许多无效指令,不外要留意由于hello步伐末了有一个getchar,因此第一个乱按的指令被getchar给读走了,不会成为发送给shell的无效指令。由于我们可以判断,我们乱按的内容被放入缓冲区,等待步伐执行结束被shell当作下令读走。
(2)crtl+z
输入crtl+z,结果如图6.6.2-2所示
图6.6.2-2 crtl+z后hello运行结果图

可以看到输入ctrl+z后步伐被放入背景并暂停运行。
(3)crtl+C
我们按动crtl+c之后,我们可以看到如图6.6.2-3所示的结果
图6.6.2-3  crtl+c后hello运行结果图

输入ctrl+c后步伐直接结束运行,回到shell等待输入下一条指令。
(4)ps下令
在hello步伐已经被crtl+z暂停后,我们键入ps下令,可以看到如图6.6.2-4的结果。通过ps指令我们可以看到当前在运行的进程及其pid。
图6.6.2-4 ps下令后hello运行结果图

(5)jobs下令
如图6.6.2-5所示,通过jobs下令我们可以看到所有在执行的下令。
图6.6.2-5  jobs下令后hello运行结果图

(6)pstree下令
如图6.6.2-6所示,通过pstree我们可以看到所有进程之间的父子关系,可以看到我们的hello进程是shell(bash)创建的进程。
图6.6.2-6 pstree下令后hello运行结果图

(7)fg下令
如图6.6.2-7所示,通过执行fg下令我们可以让暂停的进程重新开始工作。
图6.6.2-7 fg下令后hello运行结果图

(8)kill下令
如图6.6.2-8所示,可以看到kill成功的杀死了一个进程,fg无法将其唤醒。
图6.6.2-8 kill下令后hello运行结果图

6.7本章小结

可以说现代盘算机离不开进程的概念,只有充分了解进程的创建与异常流控制,才大概成为一个优秀的步伐员。这章我们讲述了一个进程是怎么在盘算机中被创建的,一个步伐是怎么通过子进程被执行的,这是P2P中的末了一步process。这一章我们还介绍了异常与信号,并现实对hello用各种信号举行测试,来了解常用信号的用途。
一个系统中有成百上千个步伐在同时运行,那么如何管理它们,让它们既能互不影响的运行,又能在须要的时间举行通信就是一个很重要的题目。一个系统要能够有效的运行,它必须建立一个简单但有效的模型。盘算机系统为了办理这个题目,提供了两个抽象:进程让每个步伐都以为只有它自己在运行,假造内存让每个步伐都以为它自己在独占整个内存空间。这两个抽象使得盘算机系统能够对每个步伐都够以一致的方式去管理。多使命就通过进程之间快速的切换来实现,步伐之间的影响就通过进程之间的通信——信号来实现。

第7章 hello的存储管理

7.1 hello的存储器地址空间

物理地址(physical address)用于内存芯片级的单位寻址,与处理器和CPU连接的地址总线相相应。是指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变动的终极结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变动成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
线性地址(Linear Address)是逻辑地址到物理地址变动之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变动以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。
假造地址(Virtual Address)假造内存为每个步伐提供了一个大的、一致的和私有的地址空间。其每个字节对应的地址成为假造地址。假造地址包括 VPO(假造页面偏移量)、VPN(假造页号)、TLBI(TLB 索引)、TLBT(TLB 标志)。
7.2 Intel逻辑地址到线性地址的变动-段式管理

一个逻辑地址由两部分组成,段标识符,段内偏移量。段标识符是一个16位长的字段组成,称为段选择符,其中前13位是一个索引号。可以通过段标识符的前13位,直接在段形貌符表中找到一个详细的段形貌符,这个形貌符就形貌了一个段。
转换的详细过程:首先,给定一个完备的逻辑地址,它的格式如下:[段选择符:段内偏移地址]。然后,看段选择符的T1=0照旧1,知道当前要转换是全局段形貌符表(存储全局的段形貌符),照旧局部段形貌符表(存储进程自己的段形貌符),再根据相应寄存器,得到其地址和巨细,得到一个数组。末了,拿出段选择符中前13位,查找到对应的段形貌符,进而找到基地址,base+offset得到线性地址。
在实模式下:逻辑地址CS:EA=CS*16+EA物理地址
在掩护模式下:以段形貌符作为下标,到GDT/LDT表查表得到段地址,
段内偏移量是在链接后就已经得到的32位地址,因此要想由逻辑地址得到线性地址,须要根据逻辑地址的前16位得到段地址,这16位存放在段寄存器中。
段寄存器(16位):用于存放段选择符。CS(代码段):步伐代码所在段;SS(栈段):栈区所在段;DS(数据段):全局静态数据区所在段;其他三个段寄存器ES、GS和FS可指向任意数据段。
段选择符中字段的含义如图7.2-1所示:

图7.2-1 段选择符中字段的含义

其中CS寄存器中的RPL字段表现CPU的当前特权级
TI=0,选择全局形貌符表(GDT);TI=1,选择局部形貌符表(LDT);RPL=00为第0级,位于最高级的内核态;RPL=11为第3级,位于最低级的用户态。高13位-8K个索引用来确定当前使用的段形貌符在形貌符表中的位置。

图7.2-2 段形貌符示意图

段形貌符是一种数据结构,等价于段表项,分为两类。一类是用户的代码段和数据段形貌符,一类是系统控制段形貌符。
形貌符表:现实上为段表,由段形貌符(段表项构成)分为三种范例:
全局形貌符表GDT:只有一个,用来存放系统内每个使命都大概访问的形貌符,例如,内核代码段、内核数据段、用户代码段、用户数据段以及TSS(使命状态段)等都属于GDT中形貌的段。
局部形貌符表LDT:存放某使命(即用户进程)专用的形貌符
停止形貌符表IDT:包含256个停止门、陷阱门和使命门形貌符
下图展示了逻辑地址到线性地址的转化过程:

图7.2-3 逻辑地址到线性地址的转化过程

首先根据段选择符的TI部分判断须要用到的段选择符表是全局形貌符表照旧局部形貌符表,随后根据段选择符的高13位的索引(形貌符表偏移)到对应的形貌符表中找到对应的偏移量的段形貌符,从中取出32位的段基址地址,将32位的段基址地址与32位的段内偏移量相加得到32位的线性地址。
7.3 Hello的线性地址到物理地址的变动-页式管理

Linux下,假造地址到物理地址的转化与翻译是依赖页式管理来实现的,假造内存作为内存管理的工具。概念上而言,假造内存被组织为一个由存放在磁盘上的N个连续的字节巨细的单位组成的数组。磁盘上数组的内容被缓存在物理内存中(DRAM cache)这些内存块被称为页 (每个页面的巨细为P = 2p字节)。
而分页机制的作用就是通过将假造和物理内存分页,而且通过MMU建立起相应的映射关系,可以充分使用内存资源,便于管理。一般来说一个页面的标准巨细是4KB,偶然可以达到4MB。而且假造页面作为磁盘内容的缓存,有以下的特点:DRAM缓存为全相联,任何假造页都可以放置在任何物理页中须要一个更大的映射函数,不同于硬件对SRAM缓存更复杂精密的更换算法太复杂且无穷制以致无法在硬件上实现DRAM缓存总是使用写回,而不是直写。
假造页面地集合被分为三个不相交的子集:已缓存、未缓存和未分配。

图7.3-1 假造页面的示意图

页表实现从假造页到物理页的映射,依赖的是页表,页表就是是一个页表条目(Page Table Entry,PTE)的数组,将假造页地址映射到物理页地址。这个页表是常驻与主存中的。

图7.3-2 页表的示意图

下图a展示了当页面掷中时,CPU硬件执行的步骤:
第1步:处理器生成一个假造地址,并把它传送给MMU;
第2步:MMU生成PTE地址,并从高速缓存/主存请求得到它;
第3步:高速缓存/主存向MMU返回PTE;
第4步:MMU构造物理地址,并把它传送给高速缓存/主存;
第5步:高速缓存/主存返回所请求的数据字给处理器

处理缺页如图b所示:
第1~3步:和图a中的第1步到第3步相同;
第4步:PTE中的有效位是零,所以MMU触发了一次异常,传给CPU中的控制到操纵系统内核中的缺页异常处理步伐;
第5步:缺页处理步伐确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘;
第6步:缺页处理步伐页面调入新的页面,并更新内存中的PTE;
第7步:缺页处理步伐返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的假造地址重新发送给MMU。由于假造页面如今缓存在物理内存中,所以就会掷中,在MMU执行了图b中的步骤之后,主存就会将所请求字返回给处理器。
7.4 TLB与四级页表支持下的VA到PA的变动

为了消除每次CPU产生一个假造地址,MMU就查阅一个PTE带来的时间开销,许多系统都在MMU中包括了一个关于PTE的小的缓存,称为翻译后被缓冲器(TLB),TLB的速度快于L1cache。

图7.4-1 TLB索引示意图

TLB通过假造地址VPN部分举行索引,分为索引(TLBI)与标志(TLBT)两个部分。如许,MMU在读取PTE时会直接通过TLB,如果不掷中再从内存中将PTE复制到TLB。同时,为了淘汰页表太大而造成的空间丧失,可以使用层次结构的页表页压缩页表巨细。corei7使用的是四级页表。


图7.4-2 页表工作机制示意图

在四级页表层次结构的地址翻译中,假造地址被分别为4个VPN和1个VPO。每个VPNi都是一个到第i级页表的索引,第j级页表中的每个PTE都指向第j+1级某个页表的基址,第四级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问四个PTE。

图7.4-3 四级页表工作机制示意图

在四级页表下,MMU根据假造地址不同段的数字通过TLB快速访问得到下一级页表的索引或者得到第四级页表中的物理页表然后与VPO组合,得到物理地址(PA)。
7.5 三级Cache支持下的物理内存访问

Core i7的内存系统如图7.5.1所示。

图7.5-1 Core i7内存系统示意图

首先,根据物理地址的s位组索引索引到L1 cache中的某个组,然后在该组中查找是否有某一行的标志等于物理地址的标志而且该行的有效位为1,如有,则说明掷中,从这一行对应物理地址b位块偏移的位置取出一个字节,若不满意上面的条件,则说明不掷中,须要继续访问下一级cache,访问的原理与L1相同,如果三级cache都没有要访问的数据,则须要访问内存,从内存中取出数据并放入cache。

图7.5-1 三级Cache下的物理内存访问

7.6 hello进程fork时的内存映射

新进程如今的假造内存刚好和调用fork时的假造内存相同。当这两个进程中的任一个厥后举行写操纵的时间,写时的复制机制就会创建新页面。如许就为每个进程保持了私有地址空间新的抽象概念。
当故障处理步伐留意到掩护异常进程试图在写时复制区域中的一个页面读写,它就会在物理内存中创建这个页面的一个新副本,更新页表条目指向这个新的副本,然后恢复这个页面的可写权限。

图7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目的文件hello中的步伐,用hello步伐有效地替代了当前步伐。加载并运行hello须要以下几个步骤:
1.删除已存在的用户区域,删除当前进程假造地址的用户部分中的已存在的区域结构。
2.映射私有区域,为新步伐的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其巨细包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。
3.映射共享区域,hello步伐与共享对象libc.so链接,libc.so是动态链接到这个步伐中的,然后再映射到用户假造地址空间中的共享区域内。
4.设置步伐计数器(PC),execve做的末了一件事情就是设置当前进程上下文的步伐计数器,使之指向代码区域的入口点。

图7.7 加载器是如何映射用户地址空间的区域的

7.8 缺页故障与缺页停止处理

缺页处理:页面掷中完全是由硬件完成的,而处理缺页是由硬件和操纵系统内核协作完成的。
处理流程:
(1)处理器生成一个假造地址,并将它传送给MMU
(2)MMU生成PTE地址,并从高速缓存/主存请求得到它
(3)高速缓存/主存向MMU返回PTE
(4)PTE中的有效位是0,所以MMU触发了一次异常,通报CPU中的控制到操纵系统内核中的缺页异常处理步伐。
(5)缺页处理步伐确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
(6)缺页处理步伐页面调入新的页面,并更新内存中的PTE
(7)缺页处理步伐返回到原来的进程,再次执行导致缺页的下令。CPU将引起缺页的假造地址重新发送给MMU。由于假造页面已经换存在物理内存中,所以就会掷中。
下图体现对VP3的引用不掷中,从而触发缺页。

图7.8-1 对VP3的引用不掷中从而触发缺页

缺页之后,缺页处理步伐选择VP4作为牺牲页,并从磁盘上用VP3的副本取代它。在缺页处理步伐重新启动导致缺页的指令之后,该指令将从内存中正常地读取字,而不会再产生异常
7.9动态存储分配管理

动态内存分配器维护着一个进程的假造内存区域,称为堆。分配器将堆视为一组不同巨细的块的集合来维护。每个块就是一个连续的假造内存片,要么是已分配的,要么是空闲的。已分配的块显式地保存为供应用步伐使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用步伐显式执行的,要么是内存分配器自身隐式执行的。
分配器分为两种基本风格:显式分配器、隐式分配器。
1.显式分配器:要求应用显式地释放任何已分配的块。
2.隐式分配器:要求分配器检测一个已分配块何时不再使用,那么就释放这个块,自动释放未使用的已经分配的块的过程叫做垃圾收集。
带边界标签的隐式空闲链表分配器原理:

图7.9-1 带边界标签的隐式空闲链表分配器原理

每个块增加四字节的头部和四字节的脚部生存块巨细和是否分配信息,可以在 常数时间访问到每个块的下一个和前一个块,使空闲块的合并也变为常数时间,而且可以遍历整个链表。隐式空闲链表即为,使用边界标签区分已分配块和未分配块,根据不同的分配战略(初次适配、下一次适配、最佳适配),遍历整个链表,一旦找到符合要求的空闲块,就把它的已分配位设置为1,返回这个块的指针。隐式空闲链表并不是真正的链表,而是"隐式"地把空闲块连接了起来(中间夹杂着已分配块)。
显式空闲链表的基本原理:

图7.9-2 显式空闲链表的基本原理

由于隐式空闲链表每次查找空闲快都须要线性地遍历整个链表,而其中的已分配块显然是不须要遍历的,所以浪费了大量时间,一种更好的方式是把空闲块组织成一个双向链表,每个空闲块中包含一个pred和succ指针,指向它的前驱和后继,在申请空闲块时,就不须要遍历整个堆,只须要使用指针,在空闲链表中遍历空闲块即可。一旦空闲块被分配,它的前驱和后继指针就不再有效,变成了有效载荷的一部分。显式空闲链表的已分配块与隐式空闲链表的堆块的格式相同。
7.10本章小结

本章介绍了hello的存储管理机制。讨论了假造地址、线性地址、物理地址,介绍了段式管理与页式管理、VA到PA的变动、物理内存访问,以及hello进程fork、execve时的内存映射、缺页故障与缺页停止处理、动态存储分配管理等。须要明白存储器层次结构,这部分内容十分重要,同时也很难以明白,须要我们花费较长时间去消化,但是这是值得的,由于它对应用步伐的性能有着巨大的影响。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

一个Linux文件就是一个m字节的序列:B0,B1,B2……Bm.
文件的范例有:
1.普通文件:包含任何数据,分两类
i.文本文件:只含有ASCII码或Unicode字符的文件
ii.二进制文件:所有其他文件
2.目录:包含一组链接的文件。每个链接都将一个文件名映射到一个文件
3.套接字:用于与另一个进程举行跨网络通信的文件
所有的IO设备(如网路、磁盘、终端)都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单低级的应用接口,称为UnixI/O,这使得所有的输入和输出都被当做相应文件的读和写来执行:
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数

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

从vsprintf生成表现信息,到write系统函数,到陷阱-系统调用int 0x80或syscall。
先找到printf的函数定义:

图8.3-1 printf的函数定义

其中va_start()和va_end是获取可变长度参数的函数,任何可变长度的变元被访问之前,必须先用va_start()初始化变元指针argptr。初始化argptr后,经过对va_arg()的调用,以作为下一个参数范例的参数范例,返回参数。末了取完所有参数并从函数返回之前。必须调用va_end()。由此确保堆栈的正确恢复。然后,printf调用了write函数,这是Unix I/O函数,用以在屏幕输出长度为i的在printbuf位置的字节。这里i=vsprintf(printbuf,fmt,args),,所以关键在于vsprintf函数。

图8.3-2 vsprintf的函数定义

vsprintf的功能就是将printf的参数按照各种各种格式举行分析,将要输出的字符串存在buf中,终极返回要输出的字符串的长度。
接着就轮到write系统函数了,在Linux下,write函数的第一个参数为fd,也就是形貌符,而1代表的就是标准输出。查察write函数的汇编实现可以发现,它首先给寄存器通报了几个参数,然后执行int INT_VECTOR_SYS_CALL,代表通过系统调用syscall,syscall将寄存器中的字节通过总线复制到显卡的显存中。表现芯片按照刷新频率逐行读取vram,并通过信号线向液晶表现器传输每一个点(RGB分量)。由此write函数表现一个已格式化的字符串。
字符表现驱动子步伐:从ASCII到字模库到表现vram(存储每一个点的RGB颜色信息)。
表现芯片按照刷新频率逐行读取vram,并通过信号线向液晶表现器传输每一个点(RGB分量)。
8.4 getchar的实现分析

当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个停止请求,停止请求抢占当前进程运行键盘停止子步伐,键盘停止子步伐先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成 ASCII 码,生存到系统的键盘缓冲区之中。
再看getchar的代码:

异步异常-键盘停止的处理:键盘停止处理子步伐。担当按键扫描码转成ascii码,生存到系统的键盘缓冲区。
可以看到getchar调用了read函数,read函数也通过sys_call调用内核中的系统函数,将读取存储在键盘缓冲区中的ASCII码,直到读到回车符,然后返回整个字符串,getchar函数只从中读取第一个字符,其他的字符被缓存在输入缓冲区。
8.5本章小结

本章主要讲述了Linux的I/O设备管理方法,Unix I/O接口及其函数,以及printf函数实现的分析和getchar函数的实现。


结论

hello所经历的过程:
(1)编写:通过编辑器输入hello.c的C语言代码
(2)预处理:预处理器对hello.c处理生成hello.i文件
(3)编译:编译器编译hello.i将其转化成汇编语言形貌的hello.s文件
(4)汇编:汇编器将hello.s文件翻译成可重定位文件hello.o
(5)链接:链接器将hello.o和其他目的文件举行链接,生成可执行文件hello
(6)运行:在shell中输入“./hello 学号 余欣哲 手机号 1”,开始运行hello步伐
(7)创建新进程:shell为hello步伐fork一个新进程
(8)加载:在新进程中调用execve函数,将hello步伐映射到假造内存中
(9)执行:内核调度该进程执行,举行假造地址的翻译,此时会发生缺页,开始加载hello代码和数据到对应的物理页中,然后开始执行。
(10)信号处理:在hello进程中,按下ctrl+z、ctrl+c等会发送信号给hello,举行调度。
(11)终止:输出完10遍对应的字符串后,执行getchar,等待用户输入,输入字符按下回车后,hello进程终止。
(12)接纳:hello进程终止后发送SIGCHLD信号给shell,shell将其退出状态 举行接纳,末了内核从系统中删除hello所有的信息
感悟:即使是一个再简单不外的步伐,它的运行也包含了浩繁操纵,须要软硬件共同、内核与操纵系统协作。盘算机系统的筹划与实现蕴含了多年以来浩繁技能职员的履历与聪明,通过不绝地完善发展,才有了如今比较完备的体系。我我赞叹于盘算机系统筹划的奇妙。时光荏苒,盘算机系统的学习暂时告一段落了。追念一路走来,学了C语言、数据结构,感觉固然学了不少的课,但是照旧有很多底层的东西照旧一概不知,一个C语言步伐是如何执行的?汇编代码是如何生成的?步伐是怎么链接的?反汇编是什么意思?这些题目,都在盘算机系统这门课给出了答案。我不由赞叹盘算机系统的筹划者的高超聪明。感谢郑老师让我们真正深入的明白了盘算机系统,这是其他很多学校无法做到的。盼望每一个上过这门课的同砚都能认真感悟盘算机的美,取得一个不错的分数!


附件

列出所有的中间产物的文件名,并予以说明起作用。

文件名

作用

hello.c

hello源代码
hello.i

预处理之后的文本文件
hello.s

hello的汇编代码
hello1.s

hello.o的反汇编代码
hello2.s

hello的反汇编代码
hello.o

hello的可重定位文件
hello

hello的可执行文件

参考文献

为完本钱次大作业你翻阅的册本与网站等

[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.


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

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

东湖之滨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表