程序人生-Hello’s P2P

摘  要

本文主要通过分析 hello 程序的一生,即预处理、编译、汇编、链接、进程管理等整个生命周期。回顾了这学期计算机系统这门课的几乎所有知识。分析过程中使用 ubuntu作为操作系统,并使用了一些工具辅助完成,目的是对计算机系统的工作及原理有更深的了解。本文在理论上探讨了这些工具的原理和方法,还实际演示了它们的操作,阐述了计算机系统的工作原理和体系结构,帮助读者更深入地理解和掌握C语言程序的编译和执行过程。

关键词:计算机系统;P2P;O2O;程序运行                         

目  录

第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:即From Program to Process。指从hello.c(Program)变为运行时的进程(Process)。hello.c源程序经过预处理器(cpp)处理生成一个 hello.i 文件,也就是修改了的源程序。由编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,接下来汇编器(as)将hello.s翻译成机器语言指令,将其打包成可重定位目标程序的格式并保存在二进制文件hello.o中。再由链接器生成可执行目标程序 hello,此时在 shell 中调用相关命令将为其创建进程(Process),执行程序。

020:即From Zero-0 to Zero-0。指最初内存并无hello文件的相关内容,在 shell 中输入相关命令后,shell 将调用 fork 函数为这一程序创建进程,之后将通过 exceve 在进程的上下文中加载并运行 hello,将进程映射到虚拟内存空间,并加载需要的物理内存。当执行结束后父进程将回收这一进程,内核将清除hello这一进程的相关信息,这一进程就结束了。

1.2 环境与工具

硬件环境:处理器:Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz   1.99 GHz

RAM:8.00G

系统类型:64位操作系统,基于x64的处理器

软件环境:Windows11 64位,VMware,Ubuntu 20.04

开发与调试工具:Visual Studio 2022 64位;vim objump edb gcc readelf等工具

1.3 中间结果

中间结果文件名称

文件作用

hello.i

预处理后的文件

hello.s

汇编程序

hello.o

可重定位目标文件

hello

可执行目标程序

elf.txt

hello.o的elf格式文件

helloo.asm

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

hello.elf

hello的elf格式文件

hello.asm

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

1.4 本章小结

本章首先介绍了hello的P2P、020流程,包括流程的设计思路和实现方法;然后详细说明了本实验所需的硬件配置、软件平台、开发工具以及实验过程中的各个中间结果文件的名称和功能。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理器(cpp)根据以#字符开头的命令,修改原始的c程序。
作用:
     1.处理头文件:比如hello.c的第一行的#include<stdio.h>命令告诉预处理器读取系统有文件stdio.h的内容,并把它直接插入程序文本中。
     2.处理宏定义:对于#define指令,进行宏替换,对于代码中所有使用宏定义的地方使用符号表示的实际值替换定义的符号
     3.处理条件编译:根据可能存在的#ifdef来确定程序需要执行的代码段。
     4.处理特殊符号:例如#error等,预编译程序可以识别一些特殊的符号,并在后续过程中进行合适的替换。
     5.删除c语言源程序中的注释部分。

2.2在Ubuntu下预处理的命令

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

图 2.2 在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

图2.3-1 hello.c 源代码

图2.3-2 hello.i 具体信息

打开预处理后的hello.i文件观察,发现有几点变化:代码行数由原来的24行变为3092行。代码中的注释被删除。源文件中include的三个头文件已经被预处理器写入.i文件。源文件经过了预处理得到了便于编译器工作的.i文件。预处理器不会对头文件中的内容做任何计算或转换,只是简单地复制和替换。

2.4 本章小结

本章讲述了在linux环境中,用命令对C语言程序进行预处理,以及预处理的含义和作用。并用hello程序演示了从hello.c到hello.i的过程,分析了预处理后的结果。通过分析,可以发现预处理后的文件hello.i包含标准输入输出库stdio.h的内容,一些宏和常量的定义,以及一些行号信息和条件编译指令。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

3.1.1编译的概念

编译是指编译器将源程序转换为计算机可以识别的机器语言——汇编语言,编译可以分为分析和整合两部分,分析过程将源程序分成多个结构,校验其格式,收集源程序信息,并将其放在符号表中;整合过程根据分析过程传递的信息构造目标程序。最后生成hello.s文件

3.1.2编译的作用

计算机程序编译的作用是使高级语言源程序变为汇编语言,提高编程效率和可移植性。计算机程序编译的基本流程包括词义分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成等阶段。

编译的流程:

1.词义分析:词法分析器读取源程序的字符流并对其进行扫描,将其组成有意义的词素序列,传递给语法分析。

2.语法分析:语法分析其用词法单元的第一个分量来创建语法树,树中的每个非叶结点都表示一个运算,左右结点表示运算分量。

3.语义分析:语义分析器语法树和符号表中的信息来检查源程序是否和语言定义的语义一致。语义分析器也收集类型信息,以便后续的中间代码生成器使用。

4.中间代码生成:生成一个明确的低级类机器语言的中间表示。

5.代码优化:生成效率更高,更好的目标代码

6.目标代码生成:将生成的中间代码映射为机器代码,为每个变量分配寄存器或内存位置,并且将中间指令翻译成机器指令序列。

3.2 在Ubuntu下编译的命令

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

图 3.2 在Ubuntu下预处理的命令

3.3 Hello的编译结果解析

3.3.1汇编初始部分

在main函数前有一部分字段展示了段名称及指令:

图 3.3-1 hello.s文件初始部分

.file

声明源文件名称

.text

代码段:包含程序的可执行指令。程序的主要代码、函数定义等都放在这个段中。每个可执行文件至少有一个 .text 段。

.section  .rodata

只读数据段:用于存放只读数据,比如字符串常量、只读数组等。

.align

对齐指令:用于确保后续的内容在内存中的地址是8字节对齐

.string 

定义了存储在只读数据段中的字符串。

.globl 

声明 `main` 函数是全局的,这样链接器在其他文件中也能看到它。

.type

声明一个符号的类型:指定 `main` 是一个函数,便于链接器和调试器识别符号

.LC0 .LC1

局部标签,用于标识这些字符串常量的地址。

3.3.2 数据

(1)常量

数字常量:源代码中使用的数字常量都是储存在.text 段的,包括比较时候使用的数字变量 5,循环时使用的循环比较变量等数字常量都是储存在.text 段的,具体情况如下:

图 3.3-2 hello.s文件中数字常量示例

字符串常量:有两个字符串存放在只读数据段中

图 3.3-3 hello.s文件中字符串常量示例

(2)变量

程序中无全局变量,仅有局部变量i、argc和指针数组argv[],均保存在栈中。

局部变量 i:储存在栈中地址为-4(%rbp)的位置。

图 3.3-4 hello.s文件中局部变量i

局部变量 argc:在程序运行的时候输入的变量的个数,它储存在栈中地址为-20(%rbp)的位置,操作是与 5 比较之后确定有一部分代码是否执行,具体汇编代码如下:

图 3.3-5 hello.s文件中局部变量argc

局部变量 argv:一个保存着输入变量的数组,具体汇编代码段如下:

图 3.3-6 hello.s文件中局部变量argv[]

3.3.3赋值

hel1o.c中的赋值操作有for循环开头的i=0,该赋值操作体现在汇编代码上,是用mov指令实现,如:。由于int型变量i是一个32位变量,使用movl传递双字实现。

3.3.4算术操作

每次循环结束的时候都对其进行+1 操作,该操作体现在汇编代码则使用指令add实现,同样,由于变量i为32位,使用指令addl。指令如下:

图 3.3-7 hello.s文件中加法操作

3.3.5关系操作

1.判断argc是否等于4,使用cmp S1,S2根据S2-S1的值设置条件码,等于0则ZF=1,否则ZF=0,为接下来的跳转做准备。

图 3.3-8 hello.s文件中关系操作
   2.判断i是否小于等于8,将条件码设置为i - 8,为下一句跳转指令做准备。

图 3.3-9 hello.s文件中关系操作

3.3.6控制转移

1.判断argc是否等于5,若不等于,则继续执行,若等于,则跳转至L2处继续执行。

图 3.3-10 hello.s文件中控制转移操作

2.无条件跳转,以跳到L3,即循环部分代码。

图 3.3-11 hello.s文件中控制转移操作

3.第三处是判断是否达到循环终止条件(i<=9),hello.s中是比较i和9,若小于等于则跳回L4重复循环,否则顺序执行。

图 3.3-12 hello.s文件中控制转移操作

3.3.7函数调用

1.调用printf()输出一个字符串常量,参数存在%rdi中,设置为待传递字符串"用法: Hello 学号 姓名 手机号 秒数!\n"的起始地址。调用printf()输出字符串常量和两个char指针argv[1],argv[2]指向的字符串,字符串常量作为参数1在%rdi中,两个指针作为参数2、3分别存在%rsi和%rdx中。

图 3.3-13 hello.s文件中printf函数

2.调用atoi(),将%rdi设置为argv[3],call调用atoi函数进行字符串到整型数的换。调用sleep(),将atoi转换后的值保存到%edi中,然后使用call调用。

图 3.3-14 hello.s文件中atoi、printf函数

3.调用exit,参数是1,将立即数1保存到%edi,然后调用exit。

图 3.3-15 hello.s文件中exit函数

4.调用getchar(),没有参数。

图 3.3-16 hello.s文件中getchar函数

3.4 本章小结

本章主要介绍了在将修改了的源程序文件转换为汇编程序的时候主要发生的变化以及汇编代码文件中主要存在的部分以及源代码中的一些主要的操作对应的汇编代码中的汇编代码的展现形式。总的来说,编译器做的就是在进行词义分析和语义分析之后判断源代码符合语法要求之后将其转换为汇编代码。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器as将hello.s翻译成机器语言指令,把这些指令打包成一种叫可重定位目标程序的格式,并将结果保存在目标文件hello.o中。其中hello.o是一个二进制文件。
    作用:产生机器语言指令,使得机器能够识别。

4.2 在Ubuntu下汇编的命令

汇编的命令:gcc hello.s -c -o hello.o

图 4.2-1在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

在shell中输入readelf -a hello.o >elf.txt 指令获得 hello.o 文件的 ELF 格式:

图 4.3-1在Ubuntu下得到hello.o文件elf格式的命令

4.3.1 ELF头

ELF头由以16字节序列Magic开始,描述了生成该文件的系统的字的大小和字节顺序,剩下部分包含 ELF 头的大小、目标文件的类型、机器类型、字节头部表(section header table)的文件偏移,及节头部表中条目的大小和数量等。

图 4.3-2ELF头

4.3.2 节头表

描述了.o 文件中每一个节出现的位置,大小,目标文件中的每一个节都有一个固定大小的条目(entry)。具体内容如下图所示:

图 4.3-3节头表

4.3.3 重定位节

重定位节中包含了在代码中使用的一些外部变量等信息,在链接的时候需要根据重定位节的信息对这些变量符号进行修改。链接的时候链接器会根据重定位节的信息对外部变量符号决定选择何种方法计算正确的地址,通过偏移量等信息计算出正确的地址。本程序需要重定位的信息有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar这些符号同样需要与相应的地址进行重定位。具体重定位节的信息如下图所示:

图 4.3-4重定位节

4.3.4符号表

.symtab一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。Name是字符串中的字节偏移,指向符号的以null结尾的字符串名字,value是据定义目标的节的起始位置偏移,size是目标的大小(以字节为单位)。Type是符号的种类,有函数、数据、文件等,Binding简写为bing,表示符号是本地的还是全局的,符号表还可以包含各个节的条目,以及对应原始源文件的路径名的条目。

图 4.3-5符号表

4.4 Hello.o的结果解析

shell中输入 objdump -d -r hello.o > helloo.asm 指令输出hello.o的反汇编文件,并与第3章的hello.s文件进行对照分析。

反汇编和hello.s在代码段很相像,但是反汇编左侧增加了汇编语言对应的机器语言指令。机器语言是由0/1所构成的序列,在终端显示为16进制表示的。

1.分支转移:

hello.s分支转移目标位都是使用.L*表示,hello.o反汇编之后,目标位置变成具体的地址。段名称在hello.s只是助记符,在hello.o机器语言中不存在。

2.函数调用:

hello.s中函数调用是call+函数名,在反汇编文件中目标地址变成了当前的PC值,因为都调用外部函数,所以需要在链接阶段重定位。

3.操作数:

hello.s中立即数是十进制的,反汇编文件中都是二进制的,在终端显示的时候转换成了十六进制。

4.5 本章小结

这一章介绍了汇编的概念和作用。以Ubuntu系统下的hello.s文件为例,说明了如何汇编成hello.o文件,并生成ELF格式的可执行文件hello.elf。将可重定位目标文件改为ELF格式观察文件内容。通过分析hello.o的反汇编代码(和hello.s的区别和相同点,理解汇编语言到机器语言的转换过程。

(第41分)

5章 链接

5.1 链接的概念与作用

概念:链接是将各种不同文件的代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存并执行。
    作用:把预编译好了的若干目标文件合并成为一个可执行目标文件。使得分离编译称为可能,不用将一个大型的应用程序组织为一个巨大的源文件,而是可以把它分解为可独立修改和编译的模块。当改变这些模块中的一个时,只需简单重新编译它并重新链接即可,不必重新编译其他文件。

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的格式

命令: readelf -a hello > hello1.elf

5.3.1ELF头

hello1.elf中的ELF头与hello.elf中的ELF头包含的信息种类基本相同,以描述了生成该文件的系统的字的大小和字节顺序的16字节序列Magic开始,剩下的部分包含帮助链接器语法分析和解释目标文件的信息。与hello.elf相比较,hello1.elf中的基本信息未发生改变(如Magic,类别等),而类型发生改变,程序头大小和节头数量增加,并且获得了入口地址。

5.3.2节头
描述了各个节的大小、偏移量和其他属性。链接器链接时,会将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。详细内容如下:

5.4 hello的虚拟地址空间

使用edb打开hello可执行文件,可以在edb的Data Dump窗口看到hello的虚拟地址空间分配的情况,具体内容截图如下:

可以发现这一段程序的地址是从 0x401000 开始的,并且该处有 ELF 的标识,可以判断从可执行文件时加载的信息。

由5.3可知,.init段的起始地址是0x401000.

使用edb查询可得到以下结果:

5.5 链接的重定位过程分析

在Shell中使用命令objdump -d -r hello > hello.asm生成反汇编文件hello.asm。

在链接过程中,hello中加入了代码中调用的一些库函数,例如getchar,puts,printf等,同时每一个函数都有了相应的虚拟地址。对于全局变量的引用,由于hello.o中还未对全局变量进行定位,因此hello.o中用0加上%rip的值来表示全局变量的位置,而在hello中,由于已经进行了定位,因此全局变量的的值使用一个确切的值加上%rip表示全局变量的位置。hello中增加了.init和.plt节,和一些节中定义的函数。hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。这是由于hello.o中对于函数还未进行定位,只是在.rel.text中添加了重定位条目,而hello进行定位之后自然不需要重定位条目。地址访问:在链接完成之后,hello中的所有对于地址的访问或是引用都调用的是虚拟地址地址。

链接的过程:
链接主要分为两个过程:符号解析和重定位。
符号解析:目标文件定义和引用符号,符号解析将每个符号引用和一个符号定义关联起来。
重定位:重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的聚合节。然后链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。至此程序中每条指令和全局变量都有唯一的运行内存地址。重定位节中的符号引用。这一步中链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目的数据结构。

5.6 hello的执行流程

通过edb的调试,一步一步地记录下call命令进入的函数。

所有过程:

1.开始执行:_start、_libe_start_main

2.执行main:_main、printf、_exit、_sleep、getchar

3.退出:exit

子程序名或程序地址:

程序名

程序地址

_start  

0x4010f0

_libc_start_main

0x2f12271d

main

0x401125

_printf

0x4010a0

_sleep 

0x4010e0

_getchar 

0x4010b0

_exit

0x4010d0

5.7 Hello的动态链接分析

 动态的链接器在正常工作时链接器采取了延迟绑定的链接器策略,将过程地址的绑定推迟到第一次调用该过程时。

如果一个目标模块调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT。PLT是一个数组,PLT[0]跳转到动态链接器中,PLT[1]调用系统启动函数,初始话执行环境,调用main函数并处理返回值。GOT是一个数组,和PLT联合使用时,GOT[0]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]时动态链接器在ld-linux.so模块中的入口点。其余的每个条目对应一个被调用的函数,其地址在运行时被解析。用gdb调试hello程序,输入命令info files,得到GOT起始表位置为:0x404000

调用dl_init函数的前后变化

GOT表位置在调用dl_init之前0x404008后的16个字节均为0

调用了dl_init之后字节改变了

5.8 本章小结

在链接过程中,各种代码和数据片段收集并组合为一个单一文件。利用链接器,分离编译称为可能,我们不用将应用程序组织为巨大的源文件,只是把它们分解为更小的管理模块,并在应用时将它们链接就可以完成一个完整的任务。
经过链接,已经得到了一个可执行文件,接下来只需要在shell中调用命令就可以为这一文件创建进程并执行该文件。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

进程定义:

狭义定义:进程是正在运行的程序的实例。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程作用:

1.进程为用户提供了以下假象:我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

2.方便shell程序的构造。每次用户通过向shell 输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。

3.两个关键抽象。进程提供给应用程序两个关键抽象:一个独立的逻辑控制流;一个私有的地址空间。

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

Shell的概念:

shell是一个交互型应用级程序,代表用户运行其他程序。是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。

Shell的功能:

它代表用户运行其他程序。接收用户输入的命令并把它送入内核去执行。.实际上Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核。不仅如此,Shell有自己的编程语言用于对命令的编辑,它允许用户编写由shell命令组成的程序。Shell编程语言具有普通编程语言的很多特点,比如它也有循环结构和分支控制结构等,用这种编程语言编写的Shell程序与其他应用程序具有同样的效果。

处理流程:

 shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序(这里的应用程序可以是Linux本身的实用程序,如ls和rm,也可以是购买的商业程序,如xv,或者是自由软件,如emacs)。然后shell在搜索路径里寻找这些应用程序(搜索路径就是一个能找到可执行程序的目录列表)。如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。

6.3 Hello的fork进程创建过程

父进程通过调用 fork 函数创建一个新的运行的子进程。调用 fork 函数后,新创建的子进程几乎但不完全与父进程相同:子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本,这意味着当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。fork 被调用一次,却返回两次,子进程返回 0,父进程返回子进程的 PID。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。

6.4 Hello的execve过程

exceve 函数在当前进程的上下文中加载并运行一个新程序。exceve 函数加载

并运行可执行目标文件,并带参数列表和环境变量列表。只有当出现错误时,exceve

才会返回到调用程序。所以,与 fork 一次调用返回两次不同,在 exceve 调用一次并从不返回。当加载可执行目标文件后,exceve 调用启动代码,启动代码设置栈,将可执行目标文件中的代码和数据从磁盘复制到内存中,然后通过跳转到程序的第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。

6.5 Hello的进程执行

进程调度:即使在系统中通常有许多其他程序在运行,进程也可以向每个程序提供一种假象,好像它在独占地使用处理器。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一的对应于包含在运行时动态链接到程序的共享对象中的指令。这个PC的序列叫做逻辑控制流,或者简称逻辑流。进程是轮流适用处理器的,每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。
    在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。
    内核模式转变到用户模式:操作系统内核使用上下文切换来实现多任务。内核为每个进程维持一个上下文,它是内核重启被抢占的进程所需的状态,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构的值。
    进程执行到某些时刻,内核可决定抢占该进程,并重新开启一个先前被抢占了的进程,这种决策称为调度。内核调度一个新的进程运行后,通过上下文切换机制来转移控制到新的进程:1)保存当前进程上下文;2)恢复某个先前被抢占的进程被保存的上下文3)将控制转移给这个新恢复的进程。当内核代表用户执行系统调用时,可能会发生上下文切换,这时就存在着用户态与核心态的转换。

6.6 hello的异常与信号处理

6.6.1异常的分类

类别

原因

异步/同步

返回行为

中断

来自 I/O 设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在可恢复的错误

同步

可能返回到当前指令

终止

不可恢复的错误

同步

不会返回

6.6.2异常的处理方式

6.6.3运行结果及相关命令

1.正常执行状态:在程序正常运行时,打印提示信息,以回车为标志结束程序,并回收进程。

2.运行时按下Ctrl + C:进程收到 SIGINT 信号,终止 hello。在ps中没有它的PID,在job中也没有,可以看出hello已经被永远地停止了。

3.Ctrl+Z:进程收到 SIGSTP 信号, hello 进程被挂起。ps查看它的进程PID,可知 hello的PID是25257; jobs查看hello的后台 job号是1,用调用 fg 1把它调回前台。

4.运行中不停乱按:会将屏幕的输入缓存到缓冲区,乱码被认为是命令。

5.kill,杀死进程,hello被终止。

6.pstree命令,显示所有进程的树状结构。

6.7本章小结

本章介绍了hello进程的执行过程。主要是hello的创建、加载和终止,通过键盘输入。在hello运行过程中,内核有选择地对其进行管理,决定何时进行上下文切换。在hello运行过程中,接受到不同的异常信号时,异常处理程序将对异常信号做出回应,执行对应指令,每种信号有不同的处理机制,对不同的异常信号

hello有不同的处理结果。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:逻辑地址指由程序产生的与段相关的偏移地址部分,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的实际有效地址,即物理地址。从hello的反汇编代码中看到的地址,它们需要通过计算,通过加上对应段的基地址才能得到真正的地址,这些便是hello中的逻辑地址。
    线性地址:是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生逻辑地址,hello的反汇编文件中看到的地址(即逻辑地址)中的偏移量,加上对应段的基地址,便得到了hello中内容对应的线性地址。
    虚拟地址:有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的,是hello中的虚拟地址。
    物理地址:是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。在hello的运行中,在访问内存时需要通过CPU产生虚拟地址,然后通过地址翻译得到一个物理地址,并通过物理地址访问内存中的位置。

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

逻辑地址由段选择符和偏移量组成,线性地址为段首地址与逻辑地址中的偏移量组成。其中,段首地址存放在段描述符中。而段描述符存放在描述符表中,也就是GDT(全局描述符表)或LDT(局部描述符表)中。

    段式管理特点:
1.段式管理以段为单位分配内存,每段分配一个连续的内存区。
2.由于各段长度不等,所以这些存储区的大小不一。
3.同一进程包含的各段之间不要求连续。
4.段式管理的内存分配与释放在作业或进程的执行过程中动态进行。

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

页式管理是一种内存空间存储管理的技术,将内存划分为固定大小的块,称为页(Page)。每个页对应一个物理内存块,称为页框(Frame)。页表用于管理线性地址到物理地址的映射关系。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

下面为页式管理的图示:

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.5 三级Cache支持下的物理内存访问

MMU 将物理地址发给 L1 缓存,缓存从物理地址中取出缓存偏移 CO、缓存组索引 CI 以及缓存标记 CT。若缓存中 CI 所指示的组有标记与 CT 匹配的条目且有效位为 1,则检测到一个命中条目,读出在偏移量 CO 处的数据字节,并把它返回给 MMU,随后 MMU 将它传递给 CPU。若不命中,则在下一级 cache 或是主存中寻找需要的内容,储存到上一级 cache 后再一次请求读取。

7.6 hello进程fork时的内存映射

当 fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的 PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_ struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念.

7.7 hello进程execve时的内存映射

假设运行在当前进程中的程序执行了如下的execve调用:

execve("hello",NULL,NULL);

execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用 hello 程序有效地替代了当前程序。加载并运行 hello需要以下几个步骤:

  1. 删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
  2. 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。
  3. 映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C库1ibc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
  4. 设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的人口点。

下一次调度这个进程时,它将从这个人口点开始执行。Linux将根据需要换入代码和数据页面。下图概括了私有区域的不同映射:

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

页面命中完全是由硬件完成的,而处理缺页是由硬件和操作系统内核协作完成的:

1. 处理器生成一个虚拟地址,并将它传送给 MMU

2. MMU 生成 PTE 地址,并从高速缓存/主存请求得到它

3. 高速缓存/主存向 MMU 返回 PTE

4. PTE 中的有效位是 0,所以 MMU 出发了一次异常,传递 CPU 中的控制

到操作系统内核中的缺页异常处理程序。

5. 缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则

把它换到磁盘。

6. 缺页处理程序页面调入新的页面,并更新内存中的 PTE

7. 缺页处理程序返回到原来的进程,再次执行导致缺页的命令。CPU 将引起

缺页的虚拟地址重新发送给 MMU。因为虚拟页面已经换存在物理内存中,

所以就会命中。

缺页的操作图如下:

7.9动态存储分配管理

定义:动态存储分配管理是一种内存管理方法。对内存空间的分配、回收等操作在进程执行过程中进行,以便更好地适应系统的动态需求,提高内存利用率。

分配器的基本风格

1. 显示分配器:要求应用显示地释放任何已分配的块。

2. 隐式分配器:要求分配器检测一个已分配的块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器。

基本方法与策略

  1. malloc和free函数

C标准库提供了一个称为 malloc程序包的显式分配器。程序调用malloc,malloc可以通过使用map和munmap函数,显式地分配和释放堆内存,或者还可以使用sbrk函数。

  1. 隐式空闲链表

任何实际的分配器都需要一些数据结构,允许它来区别块边界,以及区别已分配块和空闲块。大多数分配器将这些信息嵌入块本身。一个简单的方法如下图所示。

带边界标记的隐式空闲链表的每个块是由一个字的头部、有效载荷、可能的额

外填充以及一个字的尾部组成的。当一个应用请求一个 k 字节的块时,分配器搜索空闲链表,查找一个符合大小的空闲块来放置这个请求块。分配器有三种放置策略:首次适配、下一次适配和最佳适配。在释放一个已分配块的时候需要考虑是否能与前后空闲块合并,减少系统中碎片的出现。

3.显式空闲链表

将空闲块组织为某种形式的显式数据结构。因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面。

例如,堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个pred(前驱)和 succ(后继)指针,如下图所示。

7.10本章小结

本章主要介绍了hello进程在执行的过程中的虚拟内存与物理内存之间的转换关系,以及一些支持这些转换的硬件或软件机制。同时介绍了在发生缺页异常的时候系统将会如何处理这一异常。最后介绍了动态内存分配的作用以及部分方法与策略。

(第7 2分)

结论

hello经历的过程

1.源文件编写:用文本编辑器写出hello的源程序文件。

2.预处理:预处理器对hello.c进行预处理,生成hello.i文本文件,将源程序中使用到的外部库插入到文件中。

3.编译:编译器对hello.i进行语法分析、优化等操作生成hello.s汇编文件。

4.汇编:as将hello.s翻译机器更易读懂的机器代码hello.o,它是一个二进制文件。

5.链接:链接器ld将hello.o和其他用到的文件进行合并链接,生成可执行文件hello。

6.运行程序:在终端中输入运行命令,shell进程调用fork为hello创建子程序,然后调用execve启动加载器,加映射虚拟内存。

7.执行指令:CPU为程序分配时间片,在一个时间片中hello使用CPU资源顺序执行控制逻辑流。

8.异常处理:hello执行的过程中可能收到来自键盘输入的信号,调用信号处理程序进行处理。

9. 进程结束:shell作为父进程回收子进程,内核删除为这个进程创建的所有数据结构。

感悟:通过分析hello的一生,把本课程的知识联系到了一起,有了更加完整的知识体系。

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

附件

中间结果文件名称

文件作用

hello.i

预处理后的文件

hello.s

汇编程序

hello.o

可重定位目标文件

hello

可执行目标程序

elf.txt

hello.o的elf格式文件

helloo.asm

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

hello.elf

hello的elf格式文件

hello.asm

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

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

参考文献

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

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

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值