锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

HIT CSAPP——程序人生-Hello’s P2P

时间:2023-10-22 09:07:00 各式连接器fce17

计算机系统

大作业

题 目程序人生-Hellos P2P

专 业 航天学院

学 号 120L012012

班 级2036015

学 生 崔耕硕

指 导 教 师史先俊

计算机科学与技术学院

2022年5月

摘 要

本文主要介绍程序解释了计算机系统各模块之间的联系和功能,主要介绍了hello程序在linux如何从一个开始.c文件逐步成为可执行文件。本文包括预处理、编译、汇编、链接、流程管理、存储管理、IO探究管理等。

关键词:操作系统、过程、汇编、程序执行

目 录

第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简介

P2P过程:将hello.c经过预处理->编译->汇编->链接四步生成hello的二进制可执行文件,在shell中为其fork出进程并执行。

020过程:shell为其映射出虚拟内存,开始运行进程时分配并载入物理内存,执行hello程序,将output显示到屏幕,hello进程结束,shell回收内存空间。

1.2 环境与工具

1.2.1 硬件环境

X64 CPU;2GHz;2G RAM;256GHD Disk 以上

1.2.2 软件环境

Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;

1.2.3 开发工具

Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc;as,ld,vim,edb,readelf,VS

1.3 中间结果

预处理后的文件 hello.i;汇编文件 hello.s;可重定位目标文件 hello.o;可执行目标文件Hello;Hello.o的ELF格式elf.txt;Hello.o 的反汇编代码 disassemble_hello.s;hello的ELF 格式 helloELF.elf;hello 的反汇编代码 disassemble_hello_o.s。

1.4 本章小结

本章主要介绍了Hello的P2P、020的整个过程并介绍了实验的基本信息:环境、工具以及实验的中间结果。


第2章 预处理

2.1 预处理的概念与作用

预处理是计算机在处理一个程序时所进行的第一步,他直接对.c文件进行初步处理将处理后的结果保存在.i文件中,随后计算机再利用其它部分接着对.i文件进行处理。预处理以展开的 # 开头,试图解释为预处理指令。其中 ISO C/C++要求支持的包括#if、#ifdef、#ifndef、#else、#elif、#endif(条件编译)、#define(宏定义)、#include(源文件包含)、#line(行控制)、#error(错误指令)、#pragma(和实现相关的杂注)以及单独的#(空指令)。预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或编译。

作用:(1)头文件的展开:将程序中所用的头文件用其内容来替换头文件名。

(2)宏替换:扫描程序中的符号,将其替换成宏所定义的内容。

(3)去掉注释:去掉程序中的注释。

(4)条件编译:防止文件重复引用。

2.2在Ubuntu下预处理的命令

 图2.1 Ubuntu下预处理命令

 

2.3 Hello的预处理结果解析

在预处理前程序中包含开始的注释内容、头文件、全局变量和主函数。而左侧是预处理过后的文件,从全局变量的定义开始,与预处理之前的文件完全相同,这与2.1相符。

图2.2 预处理结果解析 

hello.i程序中并没有注释部分。预处理阶段,预处理器将需要用到的库的地址和库中的函数加入到了文本中,与原来不需预处理的代码一同构成了hello.i文件,用来被编译器继续编译。

 

 图2.3 预处理结果截图

图2.4 预处理结果截图

 

2.4 本章小结

本章主要介绍了预处理的概念和应用功能及Ubuntu下预处理的指令,同时具体到我们的hello.c文件的预处理结果hello.i文本文件解析,详细了解了预处理的内容。


第3章 编译

3.1 编译的概念与作用

概念:编译阶段将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切地描述了一条低级程序机器语言指令。        

词法分析对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生单词符号,把作为字符串的源程序改造为单词符号串的中间程序。语法分析以单词符号为输入,分析单词符号串是否形成符合语法规则的语法单位,最后看是否构成一个符合要求的程序,按语言的语法规则分析检查每条语句是否有正确的逻辑结构。代码优化对程序进行等价的变换,使得变换后的程序能产生更有效的目标代码。这种等价的变换不改变程序的运行结果,同时使得程序运行时间更短,占用的存储空间更小。如果在编译的过程中发现源程序有错误,会报告错误的性质和发生位置。但一般情况下,编译器只做语法检查和最简单的语义检查,而不检查程序的逻辑。

3.2 在Ubuntu下编译的命令

 

图3.1 Ubuntu下编译命令

3.3 Hello的编译结果解析

3.3.1汇编指令

汇编指令是汇编语言中使用的一些操作符和助记符,还包括一些伪指令(如assume,end)。用于告诉汇编程序如何进行汇编的指令,它既不控制机器的操作也不被汇编成机器代码,只能为汇编程序所识别并指导汇编如何进行。包含数据传输、算术运算、逻辑运算、串指令程序转移等。一般以.开头。

图3.2 hello.s部分指令

 

3.3.2.数据

关于数据的定义,hello.c中定义了一个“用法:Hello 学号 姓名 秒数”的字符串。对应到hello.s文件中,就是图3.3。类似的字符串、整数i、数组argc等均可见下图。我们可以看到定义的过程中用.string声明了这是一个字符串。需要注意的一点是main函数中定义的i变量、argc变量由于是局部变量,所以汇编器并没有单独的对他进行处理,而是直接将在这个变量放到了寄存器中。

图3.3 字符串1

图3.4 字符串2

图3.5 整数i

-4(%rbp)可见main函数中定义的整数i直接在寄存器中。

 

图3.6 整数argc与数组argv[]

可见数组argv的首地址在-32(%rbp)中。

3.3.3赋值

在计算机程序设计语言中,用一定的赋值语句去实现变量的赋值。可以注意到图3.5中,hello.s的36行中有一个对寄存器的寻址操作,这个操作给地址赋值0。由于i是main定义的局部变量,所以直接可以在栈中用一个单元保存这个值。

3.3.4 算数运算

 

图3.7 算术运算

此处为for循环中的i++。用add指令进行加1操作,由于int占4字节,在add后加上后缀l。

3.3.5 关系运算

 

图3.8 if语句的判断条件argc!=0

将数字4和argc进行比较,并用je指令进行跳转判断。je为相等则跳转,不相等则不跳转。

 

图3.9 for语句的循环判断条件i<8

将7和i进行比较并用jle指令跳转。如果jle为小于等于则跳转,否则不跳转。

3.3.6 控制转移

 

图3.10 if语句控制转移

用cmpl指令比较,用je指令跳转。若不等于4则不跳转而进入if中。

对for循环,用cmpl指令比较,用jle指令跳转。若小于等于7,对int类型也就是小于8,跳转进入for循环体内部。见图3.9.

3.3.7 函数操作

Hello调用函数操作,编译时所有的函数调用都转换成了指令call,后面调用函数的名字。

图3.11 hello.s中的函数调用

大部分的参数传递通过寄存器实现,通过寄存器最多传递6个参数,按照顺序依次为%rdi、%rsi、%rdx、%rcx、%r8、%r9。多余的参数通过栈来传递。调用main函数时,函数被系统启动函数__libc_start_main调用,call将下一条指令地址dest压栈,然后跳转至main函数。

对于exit函数:

 

图3.12 exit函数

 

图3.13 main函数返回

这里返回值为0。将%eax设置为0,然后返回。

 

图3.14 printf函数1

 

图3.15 printf函数2

其中,printf函数1通过把.LC0的首地址传入%rdi中进行参数传递,使用call puts@PLT进行函数调用。Printf函数2通过把argv[2]的地址传入%rdx中,把argv[1]的地址传入%rsi中,把将.LC1的首地址传入%rdi中进行3次参数传递,而后调用printf。

图3.16 atoi函数

该函数参数传递为把argv[3]数组地址传入%rdi中,而后进行函数调用,从atoi中返回值。

 

图3.17 sleep函数

该函数将atoi存储在寄存器%eax的返回值传入%rdi中,使用call sleep@PLT进行函数调用,值从sleep中返回。

图3.18 getchar函数

使用call getchar@PLT进行控制传递。

3.4 本章小结

在第3章中,我们了解C语言提供的抽象层下面的东西以了解机器级编程。通过让编译器产生机器级程序的汇编代码表示,了解了编译器和优化能力,以及机器、数据类型和指令集。


第4章 汇编

4.1 汇编的概念与作用

4.1.1概念

汇编器将hello.s文件翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式即可重定位文件,并将结果保存在hello.o中。hello.o是一个二进制文件。

4.1.2作用

汇编的作用是将在hello.s中保存的汇编代码翻译成可供机器执行的二进制代码,这样机器可以根据这些二进制代码真正的开始执行程序。

4.2 在Ubuntu下汇编的命令

 

图4.1 Ubuntu下汇编命令

4.3 可重定位目标elf格式

首先在文件夹路径下得到hello_o_elf.txt文件。

图4.2 readelf命令

分析elf头:

图4.3 ELF头

本虚拟机已汉化。由图可见ELF头以一个16字节的序列Magic开始,描述了生成该文件的系统的字的大小和字节顺序。剩下部分包含帮助链接器语法分析和解释目标文件的信息,如ELF 头的大小、程序头的大小、文件类型为可重定位文件等信息。

节头:节头记录了各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐等。由于是可重定位目标文件,每个节都从0开始,用于重定位。

 

图4.4节头

重定位节:包含了需要被修改的引用节的偏移量、信息、重定位的类型、符号值和重定位需要对被引用值的偏移调整量等。.rela.text中保存了代码的重定位信息,也就是.text节中的信息的重定位信息。可以看到这里面有.rodata,puts等很多代码的重定位信息。我们就拿第一条的信息来做分析。偏移量中保存了这个重定位信息在当前重定位节中的偏移量。第二个信息里面,前面的2个字节的信息保存了这个代码在汇编代码中被引用时的地址相对于所有汇编代码的偏移量,后面4个字节保存了重定位类型,一个是绝对引用,另一个是相对引用。

图4.5 重定位节

符号表存储了程序中定义和引用的函数和全局变量的信息。

 

图4.6 符号表

4.4 Hello.o的结果解析

输入objdump -d -r hello.o > hello_o.objdump,得到hello.o的反汇编代码文件hello_o.objdump文件,对比分析hello_o.objdump和hello.s:

图4.7 结果

 

 

图4.8 hello.o.objdump

hello.s对比可以发现,hello.s中的汇编指令被映射到二进制的机器语言。机器语言完全是二进制代码构成的。不同的汇编指令被映射到不同的二进制功能码,而汇编指令的操作数也被映射成二进制的操作数。从汇编语言转换成机器语言的过程中,一些操作数会出现不一致的情况:

(1)hello.s中的立即数都是用10进制数表示的。机器语言中,由于转换成了二进制代码,立即数都是用16进制数表示的。

(2)hello.s中的分支转移即跳转指令直接通过像.LC0,.LC1这样的助记符进行跳转,会直接跳转到相应符号声明的位置。从汇编语言转换成机器语言之后,助记符就不再存在了,因此机器语言中的跳转使用的是确定的地址。

(3)hello.s中的函数调用直接在call指令后面加上要调用的函数名。机器语言中,call指令后是被调函数的PC相对地址。由于调用的函数都是库函数,需要在动态链接后才能确定被调函数的确切位置,因此call指令后的二进制码为全0,同时需要在重定位节中添加重定位条目,在链接时确定最终的相对地址。

4.5 本章小结

本章介绍了汇编的概念和作用,通过对比hello.s和hello.o,分析了汇编的过程,同时分析了ELF格式下的可重定位目标文件。


5链接

5.1 链接的概念与作用

链接是将各种代码和数据的片段收集并组合成一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时,也就是在源代码被翻成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至执行于运行时,也就是由应用程序来执行。链接由链接器程序自动执行。链接包括两个主要任务:符号解析和重定位。

5.2 在Ubuntu下链接的命令

 

图5.1 在Ubuntu下链接的命令

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

    

 

图5.2 ELF头

  

图5.3 节头增加了数量

图5.4 程序头

描述了可执行文件中的节与虚拟空间中的存储段之间的映射关系。程序头是可执行文件hello特有的。

图5.5 dynamic动态节

图5.6 重定位节

图5.7 符号表

5.4 hello的虚拟地址空间

    

图5.8 程序头

图5.9 部分hello文件(edb打开)

可以看出段的虚拟空间从0x400000开始,到0x400ff0结束。在 0x400000~0x403e50段中程序被载入。这之间每个节的排列顺序与Section Headers中声明的顺序相同。由节头部表可以获得各个节的偏移量信息,从而得知各节在虚拟地址空间中的地址。例如,对于.rodata节,节头部表中给出了它的偏移量为0x600,大小为0x2f字节。它的虚拟地址空间就从0x400600开始对于.text节,节头部表中给出了它的偏移量为0x4d0,大小为0x122字节。它的虚拟地址空间就从0x4004d0开始,第一条指令的二进制机器码的第一个字节为0x31。

5.5 链接的重定位过程分析

进行反汇编处理:

图5.10 反汇编指令处理

hello与hello.o的不同之处在于,hello中的汇编代码从0x400000开始,而hello.o中的汇编代码从0开始,还没有涉及到虚拟内存地址。在hello.o中,只存在main函数的汇编指令;而在hello中,由于链接过程中发生重定位,引入了其他库的各种数据和函数,以及一些必需的启动/终止函数,还包括其他指令。对于hello,main函数中涉及重定位的指令的二进制代码被修改。在之前汇编的过程中,汇编器遇到对最终位置未知的目标引用,会产生一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。在链接过程中,链接器会根据重定位条目以及已知的最终位置对修改指令的二进制码。查看hello.o中的重定位条目,重定位条目给出了需要被修改的引用的节偏移、重定位类型、偏移调整等信息。

图5.11 新增其他函数指令

5.6 hello的执行流程

使用edb执行hello,调用顺序为从上到下。

程序名

地址

ld-2.27.so!_dl_start

0x7fce8cc38ea0

ld-2.27.so!_dl_init

0x7fce8cc47630

hello!_start

0x400500

libc-2.27.so!__libc_start_main

0x7fce8c867ab0

-libc-2.27.so!__cxa_atexit

0x7fce8c889430

-libc-2.27.so!__libc_csu_init

0x4005c0

hello!_init

0x400488

libc-2.27.so!_setjmp

0x7fce8c884c10

-libc-2.27.so!_sigsetjmp

0x7fce8c884b70

--libc-2.27.so!__sigjmp_save

0x7fce8c884bd0

hello!main

0x400532

hello!puts@plt

0x4004b0

hello!exit@plt

0x4004e0

ld-2.27.so!_dl_runtime_resolve_xsave

0x7fce8cc4e680

-ld-2.27.so!_dl_fixup

0x7fce8cc46df0

--ld-2.27.so!_dl_lookup_symbol_x

0x7fce8cc420b0

libc-2.27.so!exit

0x7fce8c889128

表1 hello调用顺序表

5.7 Hello的动态链接分析

由于编译器没有办法知道函数运行时的地址而需要链接器进行连接处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数,加载时动态链接器会重定位GOT中的每个条目,使得它包含目标的正确的绝对地址。dl_init调用之后,0x601008和0x601010处的两个8B数据分别发生改变,其中变化便是GOT[1]指向重定位表,用来确定调用的函数地址,然后在调用函数时,先跳转到PLT执行.plt中逻辑,然后访问动态链接器确定函数地址,重写GOT(以便再次访问该函数时可直接跳转),将控制传递给目标函数。

 

图5.12 hello_elf.txt中关于GOT的起始位置描述

 

图5.13 edb中查看

5.8 本章小结

本章主要介绍了链接的概念与作用与可执目标文件的格式,分析了hello的虚拟地址空间和重定位的过程。


6hello进程管理

6.1 进程的概念与作用

进程的定义是一个执行中程序的实例,拥有一个独立的逻辑控制流和私有的地址空间。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。在计算机中进程为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

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

shell是指为使用者提供操作界面的软件,是一个交互型应用级程序,它接收用户命令然后调用相应的应用程序。shell提供了用户与内核进行交互操作的接口,最重要的功能是命令解释。Linux系统上的所有可执行文件都可以作为shell命令来执行,同时它也提供一些内置命令。此外shell还包括通配符、命令补全、命令历史、重定向、管道、命令替换等功能。

处理流程:先从终端读入输入的命令行,然后解析输入的命令行,获得命令行指定的参数,最后检查命令是否是内置命令,如果是内置命令则立即执行,否则在搜索路径里寻找相应的程序,找到该程序则执行。

6.3 Hello的fork进程创建过程

当在shell中输入命令“./hello 1190200817 刘小川”时,shell解析输入的命令行,获得命令行指定的参数。由于./hello不是shell内置的命令,因此shell将hello看作一个可执行目标文件,在相应路径里寻找hello程序,找到该程序就执行它。shell会通过调用fork()函数创建一个子进程,新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同但独立的一个副本,包括代码段、数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,子进程可以读写父进程中打开的任何文件。父进程和子进程之间最大的区别在于它们的PID不同。hello程序之后就会运行在这个新创建的子进程的上下文中。

6.4 Hello的execve过程

shell创建一个子进程之后,这个子进程仍然是父进程的一个副本,因此需要在子进程中调用exceve()函数在当前进程的上下文中加载并运行我们需要的hello程序。execve函数加载并运行可执行文件filename,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。execve函数用hello程序有效替代当前程序,需要先删除当前进程虚拟地址的用户部分中的已存在的区域结构;然后映射私有区域,为新程序(即hello)的代码、数据、bss和栈区域等创建新的区域结构。所有这些区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零;然后映射共享区域。如果hello程序与共享对象(或目标)链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内;最后设置程序计数器。最后设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。当内核调度这个进程时它将从这个入口点开始执行。

6.5 Hello的进程执行

当子进程调用exceve()函数在上下文中加载并运行hello程序后,hello程序需要内核调度它而不立刻运行。进程调度是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行时,就说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,使用一种称为上下文切换的机制来将控制转移到新的进程。处理器提供了一种机制限制一个应用可以执行的指令以及它可以访问的地址空间范围。通常用某个控制寄存器的一个模式位来提供这种机制,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程运行在内核模式中,进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置;没有设置模式位时,进程运行在用户模式中,进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据,否则会导致保护故障。运行应用程序代码的进程初始时在用户模式中,进程需要通过中断、故障或者陷入系统调用这样的异常才能从用户模式变为内核模式。由于负责进程调度的是内核,因此内核调度需要运行在内核模式下。当内核代表用户执行系统调用时,可能会发生上下文切换,中断也可能引发上下文切换。同时系统通过某种产生周期性定时器中断的机制判断当前进程已经运行了足够长的时间,并切换到一个新的进程。

6.6 hello的异常与信号处理

hello执行过程中,四类异常都可能会出现,四类异常分别为中断、陷阱、故障和终止。中断,原因为来自I/O设备的信号问题。它是异步的,总是返回到下一条指令。陷阱是有意的异常,是同步行为,也总是返回到下一条指令。故障原因是潜在可恢复的错误,是同步的,有可能返回到当前指令。终止原因为出现了不可恢复的错误,是同步的,不会返回。在hello执行过程中可能发生,如果其他进程使用了外部I/O设备,那么在hello进程运行时可能会出现外部I/O设备引起的中断。将控制传递给适当的中断处理程序,处理程序返回时,就将控制返回给下一条指令,程序继续执行。hello中调用了系统调用sleep会产生陷阱。处理陷阱,将控制传递给适当的异常处理程序,处理程序解析参数,调用适当的内核程序。处理程序返回时,将控制返回给下一条指令。当hello进程刚从入口点开始执行时,会发生缺页故障。hello进程运行的过程中,也可能发生缺页故障。故障的处理:将控制传递给故障处理程序,如果处理程序能够修正这个错误情况,就将控制返回到引起故障的指令并重新执行它;否则终止引起故障的应用程序。hello执行过程中,DRAM或者SRAM可能发生位损坏,产生奇偶错误。发生错误时会将控制传递给终止处理程序,终止引起错误的应用程序。

对于信号处理,本文通过几个信号进行分析。

 

图6.1 空格信号

 

图6.2 回车信号

图6.3 Ctrl+C信号

 

图6.4 Ctrl+Z信号

由图可见空格不会打断信号处理,回车会正常结束程序。输入Ctrl+C,父进程收到SIGINT信号,终止并回收hello进程。输入Ctrl+Z,可见程序处于停止状态。输入pstree显示进程间联系。

 

图6.5 pstree进程树

6.7本章小结

本章介绍了进程的概念和作用,简述shell的工作过程,并分析了使用fork+execve加载运行hello,执行hello进程以及hello进程运行时的异常/信号处理过程。


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址是指由程序产生的与段相关的偏移地址部分。例如,在进行C语言指针编程中,可以使用&操作读取指针变量的值,这个值就是逻辑地址,是相对于当前进程数据段的地址。一个逻辑地址由两部份组成:段标识符和段内偏移量。线性地址是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址生成了一个线性地址。如果启用了页式管理,那么线性地址可以再变换产生物理地址。若没有启用页式管理,那么线性地址直接就是物理地址。虚拟地址和逻辑地址近似,因为虚拟内存空间的概念与逻辑地址类似,因此虚拟地址和逻辑地址实际上是一样的,都与实际物理内存容量无关。物理地址指存储器中的每一个字节单元都给以一个用来正确地存放或取得信息的唯一的存储器地址,又叫实际地址或绝对地址。

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

逻辑地址由段标识符和段内偏移量两部分组成。段标识符由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号,是对段描述符表的索引,每个段描述符由8个字节组成,具体描述了一个段。后3位包含一些硬件细节,表示具体是代码段寄存器还是栈段寄存器还是数据段寄存器等。通过段标识符的前13位,可以直接在段描述符表中索引到具体的段描述符。每个段描述符中包含一个Base字段,它描述了一个段的开始位置的线性地址。将Base字段和逻辑地址中的段内偏移量连接起来就得到转换后的线性地址。对于全局的段描述符放在全局段描述符表中,局部的段描述符放在局部段描述符表中。全局段描述符表的地址和大小存放在gdtr控制寄存器中,而局部段描述符表存放在ldtr寄存器中。给定逻辑地址,看段选择符的最后一位是0还是1用于判断选择全局段描述符表还是局部段描述符表。再根据相应寄存器,得到其地址和大小。通过段标识符的前13位,可以在相应段描述符表中索引到具体的段描述符,得到Base字段,和段内偏移量连接起来最终得到转换后的线性地址。

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

CPU的页式内存管理单元,负责把一个线性地址,转换为物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页,这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址。 物理页是分页单元把所有的物理内存也划分为固定长度的管理单位,长度一般与内存页是一一对应的。total_page数组有2^20个成员,每个成员是一个地址。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。分页单元中页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。每个活动的进程都有其独立的对应的虚似内存也对应了一个独立的页目录地址。运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。每个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位) 依据以下步骤进行转换:

(1)从cr3中取出进程的页目录地址;

(2)根据线性地址前十位,在数组中找到对应的索引项,因为引入了二级管理模式,页目录中的项是页表的地址。页的地址被放到页表中。

(3)根据线性地址的中间十位在页表中找到页的起始地址;

相关文章