HIT计算机系统大作业 程序人生-Hello’s P2P

 第一章 概述

 1.1 Hello简介

P2P(From Program to Process)过程:

hello的生命周期是从一个高级C语言程序开始的,分为四个阶段:首先经过预处理器cpp进行预处理,生成文本文件hello.i,然后经过编译器ccl生成汇编语言文件hello.s,接着经过汇编器as生成可重定位目标文件hello.o,最后经过链接器ld将其与引用到的库函数链接,生成可执行文件hello。再通过系统创建一个新进程并且把程序内容加载,实现有程序到进程的转化。

O2O(From Zero-0 to Zero-0)过程:

程序运行前,shell通过fork函数创建了一个新的进程,shell调用execve函数将hello程序加载到相应的上下文中,将程序内容载入物理内存。然后调用main函数。程序运行结束后,父进程回收进程,释放虚拟内存空间,删除相关内容。

1.2 环境与工具

1.2.1 硬件环境:

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

1.2.2 软件环境

Windows11 64位;Vmware 17 Player;Ubuntu 16.04 LTS 64位/优麒麟 64位

1.2.3 开发工具

 codeblocks;gdb;Objdump;HexEditor

1.3 中间结果

hello.c 源程序

hello.i 预处理后文件

hello.s 编译后的汇编文件

hello.o 汇编后的可重定位目标执行文件

hello 链接后的可执行文件

hello.elf  hello.o的ELF格式

hello1asm.txt  hello.o的反汇编代码

hello2asm.txt hello的反汇编代码

hello.elf:hello.o用readelf -a hello.o指令生成的文件
hello1.elf:hello用readelf -a hello指令生成的文件

1.4 本章小结

本章根据hello的自白总体介绍了hello程序“一生”的过程,以及进行实验时的软硬件环境及开发与调试工具等基本信息。

第2章 预处理

2.1 预处理的概念与作用

1.预处理概念: 

预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,处理完毕自动进入对源程序的编译。C语言提供多种预处理功能,主要处理#开始的预处理指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等

2.预处理作用:根据源代码中的预处理指令修改源代码,预处理从系统的头文件包中将头文件的源码插入到目标文件中,宏和常量标识符已全部被相应的代码和值替换,最终生成.i文件。

2.2在Ubuntu下预处理的命令

gcc -E -o hello.i hello.c

 

2.3 Hello的预处理结果解析

结果分析:

经过预处理之后,hello.c变为hello.i文件,打开hello.i程序,总共有3091行,内容大大增加,且仍为可以阅读的C语言程序文本文件。对原程序中的宏进行了宏展开,头文件中的内容被包含进该文件中。例如声明函数、定义结构体、定义变量、定义宏等内容,如果代码中有#define命令还会对相应的符号进行替换。

观察发现,其中的注释已经消失,前一部分的代码为,被加载到程序中的头文件;程序的最后一部分与hello.c中的main函数完全相同。

 

图2-3-1 hello.c预处理结果

 

图2-3-2 hello.c预处理结果

 

2.4 本章小结

本章首先介绍了预处理的概念与作用,接着以hello.c为例,演示了在Ubuntu下如何预处理程序,生成hello.i文件,并对结果进行分析.

第3章 编译

3.1 编译的概念与作用

1.编译的概念:编辑器(ccl)将预处理后的文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。

2.编译的作用:编译是把高级语言转化为机器二进制代码十分重要的一环。在编译之后,高级语言翻译成了更接近机器语言的汇编语言,使生成过程更加方便顺畅。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s  

 

3.3 Hello的编译结果解析

3.3.1 文件信息

.file:声明源文件:“hello.c”

.text:代码段

.section: 区段

.rodata:只读代码段

.align:数据或者指令的地址对齐方式(8)

.string:声明一个字符串(.LC0,.LC1)

.global:声明全局变量(main)

.type:声明一个符号是数据类型还是函数类型(main—function)

3.3.2.数据:

hello.s文件中的主要数据类型有:整型、字符串、指针数组。

一、整型

(1)main的参数argc:

 

argc:表示输入的参数个数,存储在寄存器%edi中,然后又通过movl -20(%rbp)指令被存入-20(%rbp)。

(2)局部变量int i

局部变量存储在栈中,当进入函数main的时候,会根据局部变量的需求,在栈上申请一段空间供局部变量使用。当局部变量的生命周期结束后,会在栈上释放。

二.字符串

在main函数前,在.rodata处的.LC0和.LC1已经存储了字符串常量,标记该位置是代码是只读的。在main函数中使用字符串时,得到字符串的首地址。

 

三:指针数组

argv[]作为参数,是命令行中输入的字符串。argv的首地址被存放在寄存器%rsi中,后来被存放在栈中空出寄存器,便于函数调用。

 

3.3.3 操作

一.for循环

这个循环中,如果i<=7,则跳转到.L4进行循环,否则顺序往下进行其他操作。

 

二.赋值操作

movl $0,-4(%rbp)将i赋值为0

 

三.算数运算

 

四.对数组的操作

先找到数组的首地址,然后加上偏移量即可

通过addq $8,%rax  addq $16,%rax  addq $24,%rax分别得到argv[1]和argv[2] 和argv[3]

五.函数的调用和返回

函数的前六个参数有寄存器传参,返回值存在%rax寄存器中。在函数调用时,先将相应的值存入相应的寄存器,然后使用call指令调用函数和ret指令返回函数。

对printf函数的调用:取得argv数组的第二个和第三个元素argv[1]和argv[2]放入寄存器%rsi和%rdx,然后42行取得了字符串的地址作为第一个参数,三个参数都确定后,用call指令调用printf函数。

3.4 本章小结

本章首先介绍了编译的概念和作用,然后在Ubuntu下以hello.s为例,通过分析其汇编程序,理解编译器是如何处理各种数据类型和各类操作的。编译是从高级语言程序生成可执行文件的过程中的关键一步。

第4章 汇编

4.1 汇编的概念与作用

概念:将.s汇编程序翻译成机器语言,把这些机器语言指令打包成可重定位目标程序的格式,并将结果保存在.o目标文件中,这个过程就叫做汇编。

作用:翻译生成机器语言,机器语言是计算机能直接识别和执行的一种语言,方便机器直接分析

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

 

4.3 可重定位目标elf格式

    

 

图4-3-1 典型的ELF可重定位目标文件

使用readelf获得hello.o的elf格式,命令为:readelf -a hello.o > hello.elf。

得到hello.elf

 

图4-3-2 使用readelf获得hello.o的elf格式

分析ELF格式:

1.ELF头

ELF头以16字节的序列开始,描述了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部分包含帮助两届其语法分析和解释目标文件的信息,包括ELF头的大小、目标文件的类型(如可执行、可重定位或者共享的)、机器类型、节头部表的文件偏移以及节头部表中条目的大小和数量。

 

图4-3-3 ELF头

2.节头部表

节头部表:包括每个节的节名、偏移和大小

 

图4-3-4 节头部表

3.重定位条目

在 as到对最终位置未知的目标引用时,就会生成一个重定位条目,告诉 ld在将目标文件合并成可执行目标文件时修改这个引用。

指令中引用的重定位条目放在.rela.text中,数据引用的重定位条目放在.rel_data中。

typedef struct{

long offset;        /*需要被修改的引用的节偏移*/

long type:32,     /*重定位类型*/

symbol:32; /*标识被修改引用应该指向的符号*/

long attend;       /*符号常数,对修改引用的值做偏移调整*/

}Elf64_Rela;

 

图4-3-5 重定位条目

 4.符号表:.symtab

     存放在程序中定义和引用的函数和全局变量的信息。

 

 

        图4-3-6 符号表

4.4 Hello.o的结果解析

以下格式自行编排,编辑时删除

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

 

  图4-4-1 hello.o的反汇编代码

 

 

图4-4-2 hello.s文件内容

(1)分支转移:

跳转语句之后,hello.s中是.L2和.L3等段名称,而反汇编代码中跳转指令之后是相对偏移的地址。

(2)操作数:

hello.s中的操作数时十进制,hello.o反汇编代码中的操作数是十六进制。

(3)函数调用:

hello.s中,call指令之后是函数名称。因为函数只有在链接之后才能确定运行执行的地址,在.rela.text节中添加了重定位条目,所以hello.o中call指令之后是相对偏移地址。

(4)指令:

汇编中mov、push、sub等指令都有表示操作数大小的后缀(b : 1字节、w :2 字节、l :4 字节、q :8字节),而反汇编得到的代码中没有。

4.5 本章小结

本章首先介绍了汇编的概念和作用,然后对hello.s文件进行汇编,生成ELF可重定位目标文件hello.o,接着使用readelf工具,查看了hello.o的ELF头、节头表、可重定位信息和符号表等,通过分析理解可重定位目标文件的内容。最后将其与hello.s比较,分析不同,并说明机器语言与汇编语言的一一对应关系。

5链接

5.1 链接的概念与作用

概念:链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载(复制)到内存并执行。

作用:链接使得分离编译,一个大的应用程序可以被分解为更小、更好管理的模块,可以独立地修改和编译这些模块。

注意:这儿的链接是指从 hello.o 到hello生成过程。

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

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

先生成hello的ELF格式文件:

命令:readelf -a hello > hello1.elf

   图5-3-1 使用命令readelf生成hello可执行文件的elf格式

分析:

  1. ELF头

如下图所示,ELF头描述文件的总体格式。它还包括程序的入口点,即程序运行时要执行的第一条指令的地址。

 

      图5-3-2 hello1文件的ELF头

2.节头表

Section Headers对hello中所有的节信息进行了声明,其中包括大小Size以及在程序中的偏移量Offset。

 

    图5-3-3 hello1文件的节头表

3.程序头表

 

      图5-3-4 hello1文件的程序头表

4.段节:

 

       图5-3-5 hello1文件的段节

5.重定位节

5.4 hello的虚拟地址空间

使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。   

由下图,可以看到虚拟地址空间的起始地址为0x400000。

       图5-4-1 edb查看hello的虚拟地址空间

  

5.5 链接的重定位过程分析

先objdump -d -r hello查看hello的反汇编代码:

 

图5-5-1 hello.o反汇编

 

图5-5-2 hello.o反汇编

 

图5-5-3 hello.o反汇编

将它与hello.o文件的反汇编代码进行比较后,可得下面几处不同:

(1)地址的访问:

可以看到,hello.o中的相对地址到了hello中变成了虚拟内存地址。而hello.o文件中对于.rodata(只读数据)的访问,$0x0和0(%rip),是因为它们的地址也是在运行时确定的,因此访问也需要重定位。

(2)链接增加新的函数:在hello中链接入了在hello.c中用到的函数,如exit、sleep、printf、getchar等函数。

(3)增加的节:增加了.init和.plt节,和一些节中定义的函数。

(4)函数调用:

hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。

5.5.2    hello的重定位过程分析:

重定位过程合并输入模块,并为每个符号分配运行时地址,将多个单独的代码节和数据节合并为单个节,将符号从它们的在.o文件的相对位置重新定位到可执行文件的最终绝对内存位置,更新所有对这些符号的引用来反映它们的新位置。

hello重定位的过程:

(1)重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。

(2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。

(3)重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目,代码的重定位条目放在.rel.txt中

5.6 hello的执行流程

hello执行流程可大致分为如下部分:开始执行部分,_start、_libc_start_main;执行main部分,_main、_printf、_exit、_sleep、_getchar;退出部分,exit。

其调用与跳转的各个子程序名或程序地址如下。

_start 0x4010f0

_libc_start_main 0x2f12271d

main 0x401125

_printf 0x401040

_exit 0x401070

_sleep 0x401080

_getchar 0x401050

5.7 Hello的动态链接分析

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。

调用之前:

 

  图5-7-1调用之前

调用之后:

图5-7-2 调用之后

5.8 本章小结

本章首先介绍了链接的概念和作用,说明了可执行目标文件的结构,及重定位过程。并且以可执行目标文件hello为例,具体分析了各个段、重定位过程、虚拟地址空间、执行流程等。当分离编译时,我们可以考虑把程序分解为更小、更好管理的模块,可以独立地修改和编译。

6hello进程管理

 

6.1 进程的概念与作用

概念:进程是一个执行中程序的实例。进程指程序的一次运行过程。更确切说,进程是具有独立功能的一个程序关于某个数据集和的一次运行活动,具有动态含义。

作用:提供给应用程序的关键抽象:

(1)一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。

(2)一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

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

作用:

Linux系统中,Shell是一个交互型应用级程序,为使用者提供操作界面,用户访问此界面可以访问操作系统内核的服务。Shell可以接收用户命令,然后调用相应的应用程序。

处理流程:

1)从终端读入输入的命令。

2)将输入字符串分割获得所有的参数

3)判断是否是内置命令,如果是内置命令则立即执行

4)否则调用相应的程序为其分配子进程并运行

5)Shell应该接受键盘输入信号(ctrl+c,ctrl+z),并对这些信号进行相应处理。

6.3 Hello的fork进程创建过程

进程的创建采用fork函数:pid_t fork(void)

Shell(父进程)通过fork 函数创建一个新的运行的子进程。该子进程会得到与父进程完全相同但是独立的一个副本(最大区别是PID),子进程与父进程虚拟地址空间相同的,包括代码和数据段、堆、共享库以及用户栈。

6.4 Hello的execve过程

当创建了一个子进程之后,子进程会调用exceve函数,在当前的子进程的上下文加载并运行一个新的程序(hello程序),且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序,调用成功不会返回。与fork不同,fork一次调用两次返回,execve一次调用从不返回。步骤如下所示:

首先,删除已存在的用户区域。这一步骤会删除之前进程在用户部分已存在的结构;

之后,为hello程序创建新的数据(包括代码、数据、堆和栈等),这些新创建的数据都是私有的。虚拟地址空间的代码和数据区域被映射为hello文件的.txt与.data区,其他的区域,如.bss区域(请求二进制零的,映射匿名文件)、栈和堆区域(请求二进制零的,初始的长度为0)等也都在此时完成相应的操作;

随后,映射共享区域。在此步骤中,如果hello程序要完成与共享对象的链接,则通过动态链接与hello程序关联,再通过映射到用户虚拟地址的共享区域;

最后,设置程序计数器。在这一步骤中,需要设置当前进程的上下文中的程序计数器,使得它指向代码区域的入口,从而能够正常运行程序。

6.5 Hello的进程执行

操作系统所提供的进程抽象:

(1)逻辑控制流:在系统中通常有许多其他程序在运行,进程可以向每个程序提供一种假象,好像它在独占地使用处理器。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称为逻辑流。

(2)时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

(3)上下文切换:如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程,上下文就是内核重新启动一个被抢占的进程所需要的状态,是一种比较高层次的异常控制流。

(4)用户模式和内核模式:shell使得用户可以有机会修改内核,所以需要设置一些防护措施来保护内核,如限制指令的类型和可以作用的范围。

(5)上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由一些对象的值组成,包括: 通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等。内核为每个进程维持了一个上下文。

6.6 hello的异常与信号处理

1.异常和信号异常种类

类别     原因            异步/同步       返回行为

中断  来自I/O设备的信号  异步       总是返回到下一条指令

陷阱  有意的异常          同步       总是返回到下一条指令

故障  潜在可恢复的错误    同步      可能返回到当前指令或终止

终止  不可恢复的错误      同步         不会返回

1.键盘上操作导致的异常:

(1)运行时输入回车:

(2)运行时输入Ctrl+C:

shell收到SIGINT信号,shell结束并回收了hello进程

(3)运行时输入Ctrl+Z:

 

shell收到SIGSTP信号,在控制台输出提示信息,挂起当前hello进程

(4)不停乱按:

Shell将乱输入的字符除了第一个回车按下之前的字符当做getchar的输入之外,其余都当做新的Shell命令,在hello进程结束被回收之后,将会在命令行中尝试解释这些命令。中间没有任何对于进程产生影响的信号被产生。

输入ps命令,查看当前存在的进程:

 

输入jobs命令,显示当前暂停的进程:

 

输入pstree,以树状图形式显示所有进程:

 

 

 

 

 

 输入fg,使停止的进程收到SIGCONT信号,重新在前台运行:

 

使用kill命令:运行hello程序,将其挂起一次,使用kill函数杀死它。

6.7本章小结

本章主要介绍了程序如何从可执行文件到进程的过程。介绍了shell的处理流程和作用。也介绍了fork函数和execve函数,及上下文切换机制等。

第7章 hello的存储管理

7.1 hello的存储器地址空间

1、逻辑地址:程序经过编译后出现在汇编代码中的地址。

2、线性地址:逻辑地址向物理地址转化过程中的一步,逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式,分页机制中线性地址作为输入。

3、虚拟地址:就是线性地址。

4、物理地址:是加载到内存地址寄存器的地址,内存单元真正的地址,CPU通过地址总线的寻址,找到真实的物理内存对应地址

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

一个逻辑地址由两部份组成,段标识符、段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节

索引号就是“段描述符”的索引,段描述符具体地址描述了一个段。很多个段描述符,就组了一个数组,叫“段描述符表”,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符。每一个段描述符由8个字节组成。全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。

Linux通过分段机制,将逻辑地址转化为线性地址。给定一个完整的逻辑地址[段选择符:段内偏移地址]。首先,看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。然后,拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,基地址就知道了。最后,把基地址 + 偏移量,就是要转换的线性地址了。

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

线性地址即虚拟地址(VA)到物理地址(PA)之间的转换通过分页机制完成,而分页机制是对虚拟地址内存空间进行分页。

系统将虚拟页作为进行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。

n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择适当的PTE,根据PTE,可以得知虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。VPO与PPO相同。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。

7.4 TLB与四级页表支持下的VA到PA的变换

Core i7采用四级页表的层次结构。CPU产生虚拟地址VA,虚拟地址VA传送给MMU,MMU使用VPN高位作为TLBT和TLBI,向TLB中寻找匹配。如果命中,则得到物理地址PA。如果TLB中没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。

 

图7.4 Core i7四级页表层次结构

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

Cache的访问需要把一个物理地址分为标记、组索引、块偏移三个部分。通过组索引找到地址对应Cache的组号,再通过标记及Cache有效位来判断Cache是否保存有效内容。如果匹配成功则命中,通过块偏移读取数据;如果匹配失败则不命中,在下一级存储空间进行寻找。在L1找不到,则去L2;在L2找不到则去L3;在L3找不到则去主存。

7.6 hello进程fork时的内存映射

在shell输入命令行./hello后,内核调用fork创建子进程,为hello程序的运行创建上下文,并分配一个与父进程不同的PID。通过fork创建的子进程拥有父进程相同的区域结构、页表等的一份副本,同时子进程也可以访问任何父进程已经打开的文件。为了给子进程创建虚拟内存,创建了当前进程的 mm_struct、区域结构和页表的原样副本。将这两个进程的每个页面都标记为只 读,并将两个进程中的每个区域结构都标记为私有的写时复制。

7.7 hello进程execve时的内存映射

execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。

加载并运行 hello 需要以下几个步骤:

1.删除当前进程虚拟地址的用户部分中已存在的用户区域

2.映射私有区域,为新程序的代码、数据、bss和栈创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区,bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中,栈和堆地址也是请求二进制零的,初始长度为零。

3.映射共享区域,将hello与libc.so动态链接,然后再映射到虚拟地址空间中的共享区域。

4.设置当前进程上下文程序计数器(PC),使之指向代码区域的入口点。

7.8 缺页故障与缺页中断处理

当我们的指令中对取出一个虚拟地址时,若我们发现对该页的内存访问是合法的,而找对应的页表项式发现有效位为0,则说明该页并没有保存在主存中,出现了缺页故障。如果程序执行过程中发生了缺页故障,则内核调用缺页处理程序。

处理程序执行如下步骤:

1.检查虚拟地址是否合法,如果不合法则触发一个段错误,程序终止。

2.检查进程是否有读、写或执行该区域页面的权限,如果不具有则触发保护异常,程序终止。

3.两步检查都无误后,内核选择一个牺牲页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程,再次执行触发缺页故障的指令。

7.9本章小结

本章主要介绍了hello的存储器地址空间、intel的段式管理、页式管理,TLB与四级页表支持下的VA到PA的变换、三级cache支持下物理内存访问, hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理等内容。这部分内容十分重要,同时也很难以理解,需要花费较长时间去消化,但是这是值得的,因为它对应用程序的性能有着巨大的影响。

结论

1.输入:将hello.c代码从键盘输入。

2.预处理(cpp):hello.c经过了预处理,头文件被引入、宏被展开等,变成了hello.i文件

3.编译(ccl):将hello.i文件进行翻译生成汇编语言文件hello.s。

4.汇编(as):将hello.s翻译成一个可重定位目标文件hello.o。

5.链接(ld):将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello,至此可执行hello程序正式诞生。

6.运行:在shell中输入./hello 并按下回车键

7.创建子进程:由于终端输入的不是一个内置的shell命令,因此shell调用fork 函数创建一个子进程。

8.加载程序:shell调用execve函数,启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。

9.执行指令:CPU为进程分配时间片,在一个时间片中,hello享有CPU资源, 顺序执行自己的控制逻辑流,像是在独占地使用CPU的全部资源,执行完当前指令,读取下一条指令,程序计数器PC更新,循环往复,执行完hello程序的指令。

10.访问内存:内存管理单元MMU将逻辑地址转换为线性地址,即虚拟地址,再将虚拟地址转换为物理地址。通过高速缓存系统,hello程序访问内存或磁盘(当前不在缓存中)的数据。

11.动态申请内存:hello程序调用了printf函数,而printf函数会调用malloc函数,通过动态内存分配器向堆结构中申请内存,变为已分配状态。在程序运行过程中,也可以调用free函数,通过动态内存分配器向堆结构释放指定的内存空间,变为空闲状态。在程序结束时,会自动释放申请的内存空间。

12.信号管理:当程序在运行的时候我们输入Ctrl+c,内核会发送SIGINT信号给进程并终止前台作业。当输入Ctrl+z时,内核会发送SIGTSTP信号给进程,并将前台作业停止挂起。通过shell输入kill、fg则可以分别杀死hello进程、唤起被挂起的hello进程。

13终止:当子进程执行完成时,内核安排父进程回收子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有数据结构。

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法:

通过学习计算机系统这门课,我了解了许多计算机的原理方面的知识,提升了自己的学习能力,受益匪浅。

附件

hello.c 源程序

hello.i 预处理后文件

hello.s 编译后的汇编文件

hello.o 汇编后的可重定位目标执行文件

hello 链接后的可执行文件

hello.elf  hello.o的ELF格式

hello1asm.txt  hello.o的反汇编代码

hello2asm.txt hello的反汇编代码

hello.elf:hello.o用readelf -a hello.o指令生成的文件
hello1.elf:hello用readelf -a 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.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值