程序人生-Hello’s P2P

 

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业        计算学部          

学     号       ***         

班   级         ***        

学       生         李维研       

指 导 教 师          ***         

计算机科学与技术学院

2022年5月

摘  要

本文中利用计算机系统所学的知识,基于Linux平台,通过gcc、objdump、gdb、edb等工具,从c源程序的预处理开始,跟踪分析程序编译、汇编、链接、进程管理、存储管理、I\O管理整个过程,感受hello程序的一生。

关键词:计算机系统;hello的一生;预处理;编译;汇编;链接;进程管理;存储管理与IO管理;                            

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

目  录

第1章 概述 - 4 -

1.1 Hello简介 - 4 -

1.2 环境与工具 - 4 -

1.3 中间结果 - 4 -

1.4 本章小结 - 4 -

第2章 预处理 - 6 -

2.1 预处理的概念与作用 - 6 -

2.2在Ubuntu下预处理的命令 - 6 -

2.3 Hello的预处理结果解析 - 6 -

2.4 本章小结 - 7 -

第3章 编译 - 9 -

3.1 编译的概念与作用 - 9 -

3.2 在Ubuntu下编译的命令 - 9 -

3.3 Hello的编译结果解析 - 9 -

3.4 本章小结 - 13 -

第4章 汇编 - 14 -

4.1 汇编的概念与作用 - 14 -

4.2 在Ubuntu下汇编的命令 - 14 -

4.3 可重定位目标elf格式 - 14 -

4.4 Hello.o的结果解析 - 17 -

4.5 本章小结 - 18 -

第5章 链接 - 19 -

5.1 链接的概念与作用 - 19 -

5.2 在Ubuntu下链接的命令 - 19 -

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

5.4 hello的虚拟地址空间 - 21 -

5.5 链接的重定位过程分析 - 22 -

5.6 hello的执行流程 - 23 -

5.7 Hello的动态链接分析 - 23 -

5.8 本章小结 - 24 -

第6章 hello进程管理 - 25 -

6.1 进程的概念与作用 - 25 -

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

6.3 Hello的fork进程创建过程 - 25 -

6.4 Hello的execve过程 - 26 -

6.5 Hello的进程执行 - 26 -

6.6 hello的异常与信号处理 - 26 -

6.7本章小结 - 28 -

第7章 hello的存储管理 - 29 -

7.1 hello的存储器地址空间 - 29 -

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

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

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

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

7.6 hello进程fork时的内存映射 - 32 -

7.7 hello进程execve时的内存映射 - 32 -

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

7.9动态存储分配管理 - 32 -

7.10本章小结 - 33 -

第8章 hello的IO管理 - 34 -

8.1 Linux的IO设备管理方法 - 34 -

8.2 简述Unix IO接口及其函数 - 34 -

8.3 printf的实现分析 - 34 -

8.4 getchar的实现分析 - 35 -

8.5本章小结 - 35 -

结论 - 36 -

附件 - 37 -

参考文献 - 38 -


第1章 概述

1.1 Hello简介

在linux中,hello.c经过预处理、编译、汇编、链接最终成为可执行目标程序hello,在shell中键入相应命令行参数后,shell解析命令并构建argc,argv[]和envp[]参数列表,shell再调用fork()函数创建一个子进程,子进程获得与父进程完全相同的虚拟存储空间的一个备份。将解析得到的argc,argv[]和envp[]参数列表传给execve()函数作为参数,execve()函数启动加载器,在当前的上下文中开始执行可执行文件hello第一条指令即将控制转移到子进程,于是hello便从Program变成Process,这就是P2P的过程。

开始执行程序后,随着虚拟存储的访问程序开始载入物理内存,然后进入 main函数执行目标代码,CPU通过上下文切换为运行的hello分配时间片执行逻辑控制流。当hello进程运行结束后,内核将子进程的退出状态传递给父进程,父进程shell(或init进程)负责回收hello进程,内核删除相关数据结构,这就是是020的过程。

1.2 环境与工具

硬件环境:X64 CPU;2.19GHz;4.00GB RAM

软件环境:Windows 10 64位;Ubuntu 16.04 LTS

开发工具:gcc;gdb;objdump;edb;

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

文件名

描述

Hello.c

源代码

Hello.i

预处理后产生的代码

Hello.o

汇编之后的文件

Hello.s

编译之后的文件

hello

链接后产生的可执行文件

1.4 本章小结

本章简要介绍了hello的一生和本文使用的环境和工具,列出了研究hello的一生产生的中间文件。

(第1章0.5分)


第2章 预处理

2.1 预处理的概念与作用

概念:所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是C语言的一个重要功能, 它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理, 处理完毕自动进入对源程序的编译。C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。最后将修改之后的文本进行保存,生成.i文件,预处理结束。

作用:在预处理的过程中,计算机利用预处理器(cpp)进行处理。而预处理主要有3个方面的内容,分别是根据字符“#”后所跟的具体语句进行不同处理。它们分别为宏定义、文件包含、条件编译。

宏定义在预处理的过程中会进行宏替换。在具体语句中表现为#define。

文件包含指的是对代码中出现的#include语句进行处理。#include指令能够告诉预处理器读取源程序中所引用的系统的源文件,并且将这一段代码直接插入到程序文件中,最终保存为.i文件中。

条件编译指的是针对#ifdef、#ifndef等语句进行的处理。条件编译能够根据#if的不同条件决定需要进行编译的代码,#endif是结束这些语句的标志。使用条件编译可以使目标程序变小,在满足条件之后才会进行编译。

2.2在Ubuntu下预处理的命令

预处理的命令:

图2-1

2.3 Hello的预处理结果解析

分析:原来的hello.c程序变成了3000多行其中原来的hello程序从3046行开始,前面的内容是对文件#include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>的源码加入到文件中,再对宏定义的扩展,选择条件编译。

Hello.i的部分截图:

图2-2

图2-3

2.4 本章小结

本章介绍了预处理的概念和作用,以及预处理执行的命令和实例解析。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译:编译是指将一个经过预处理的高级语言程序文本(.i文件)经过词法分析、语法分析、语义分析以及经过一系列优化翻译成能执行相同操作的等价ASII码形式汇编语言文件(.s文件)的过程。

作用:主要是将C语言文件翻译为汇编语言文件。下面是编译的流程:

词法分析:词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。

语法分析:按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构

代码优化:代码优化是指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码

目标代码:目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。   

3.2 在Ubuntu下编译的命令

图3-1

3.3 Hello的编译结果解析

3.3.1数据:

.file:声明源文件

图3-2

.text:代码节

图3-3

.section:

图3-4

.rodata:只读代码段

图3-5

.align:数据或者指令的地址对其方式

图3-6

.string:声明一个字符串(.LC0,.LC1),两个printf中的字符.LC0和.LC1声明,字符串存储在.rodata节中。

图3-7

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

图3-8

.type:声明一个符号是数据类型还是函数类型

图3-9

局部变量i声明:由图可知i变量在-4(%rbp)位置中,即在用户栈里。

图3-10

命令行参数 argc: 由shell解析后构造出,传递给execve作为参数启动加载器,存放在main的栈帧之上。

char* argv[]:传入main函数的参数,存储在%rsi中,在程序运行时被放入栈中使用

立即数:在代码段中,运行程序时放入栈或寄存器中。

3.3.2赋值

局部变量i的赋值:
i为未初始化的局部变量,其存储在用户栈中,赋值直接使用mov指令即可。

3.3.3算术操作

在循环操作中,使用了自加++操作符:

在每次循环执行的内容结束后,对i进行一次自加,栈上存储变量i的值加1

图3-11

3.3.4关系操作和控制转移

if(argc!=4)判断传入参数是否为四个:

图3-12

对于for循环中的循环执行条件(i = 0; i < 8; i++)汇编代码为:

图3-13

3.3.5数组/指针/结构操作

char argv[] ,记录函数执行时输入的命令行,argv作为存放char指针的数组同时是也第二是个参数传入。

Argv中每个元素char大小为8B,argv指针指向已经分配好的的连续空间(若为链表则不一定连续)。argv作为数组名,也是数组的首地址。在main函数内访问数组元素argv[1],argv[2]时,按照起始地址argv大小8B计算数据地址取数据,在hello.s中,使用两次(%rax)取出其值。如下图

图3-14

图3-15

对比原函数可知通过%rsi-8和%rax-16,分别得到argv[1]和argv[2]两个字符串

3.3.6函数操作

main函数:

参数传递:传入参数argc和argv[],分别用寄存器%rdi和%rsi存储。

函数调用:被系统启动函数调用。

函数返回:设置%eax为0并且返回,即return 0 。

图3-16

Printf函数:

参数传递:if中调用时传入了字符串参数首地址;for循环中时传入了 argv[1] 和argc[2]的地址。

函数调用:

 

图3-17

sleep函数:

参数传递:传入参数atoi(argv[3])

函数调用:for循环下被调用,call sleep

图3-18

getchar函数:

函数调用:在main中被调用

图3-19

此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析

3.4 本章小结

本章对函数的编译过程进行了分析,同时借用hello程序作为实例分析编译结果。对于c语言中的数据与操作进行了分析。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编概念:

汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标文件hello.o中的过程成为汇编。

作用:

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

4.2 在Ubuntu下汇编的命令

汇编指令:gcc -c hello.s -o hello.o

运行截图:

图4-1

4.3 可重定位目标elf格式

ELF头:以16B的序列Magic开始,Magic描述了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型、机器类型、字节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量等信息。

图4-2

图4-3

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

节头部表:记录了每个节的名称、类型、属性(读写权限)、在ELF文件中占的度、对齐方式和偏移量。

.text节:以编译的机器代码。

.rela.text节:一个.text节中位置的列表。

.data节:已初始化的静态和全局C变量。

.bss节:未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。

.rodata节:存放只读数据,例如printf中的格式串和开关语句中的跳转表。

.comment节:包含版本控制信息。

.note节:注释节详细描述。

.eh_frame节:处理异常。

.rela.eh_frame节:一个.eh_frame节中位置的列表。

.shstrtab节:该区域包含节区名称。 .symtab节:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。

.strtab节:一个字符串表,其内容包括.symtab和.debug节中的符号表,以及节头部的节名字。

.symtab节:本节用于存放符号表。

图4-4

重定位节:rela.text:.text节相关的可重定位信息。当链接器将某个目标文件和其他目标文件组合时,. text节中的代码被合并后,一些指令中引用的操作数地址信息或跳转目标指令位置信息等都可能要被修改。通常,调用外部函数或者引用全局变量的指令中的地址字段需要修改。

.data节和.text节各会有一个重定位条目表.rel.data和.rel.text;包含对应节的重定位信息。每个重定位的条目有如下的结构:offset指示该重定位条目的节偏移,type:指示重定位的类型。在这里有两种类型,如图所示。同时,还有一个重定位条目的名称。Attend用于一些重定位中使用被修改引用的值作偏移调整。

如下图,.rela.text节中记录了各个函数名和变量名对应的偏移,值,类型,符号值等信息。

图4-5

4.4 Hello.o的结果解析

图4-6

图4-7

机器语言的构成:可以看出,机器语言就是一堆16进制序列(实际是01序列),指令和寄存器由特定的值代表,立即数由小端表示的16进制数表示,由指令分隔开连续的16进制序列,从而与汇编语言一一对应。每条指令对应的01序列的含义有不同的规定,例如push %rbp,指令为55H = 01010101B,其中高5位01010为push的操作码,为小端法,即A0,后三位101为rbp的编号,为5。再例如leave指令为C9H = 11001001B,没有显示操作数,故8位都是指令操作码。

与hello.s的对比:数的表示:hello.s中的操作数时十进制,hello.o反汇编代码中的操作数是十六进制。

 分支转移:跳转语句之后,hello.s中是.L2和.LC1等段名称,而反汇编代码中跳转指令之后是相对偏移的地址,也即间接地址。

函数调用:hello.s中,call指令使用的是函数名称,而反汇编代码中call指令使用的是main函数的相对偏移地址。因为函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。

4.5 本章小结

这一章我们介绍了汇编概念与作用,使用# gcc -c hello.s -o hello.o得到.o文件并使用readelf工具分析可重定位目标elf格式,重点介绍了重定位项目,在objdump操作进行反汇编比较与原汇编语句的不同之处说明机器语言的构成,与汇编语言的映射关系。

(第4章1分)


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-1

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

ELF头:

图5-2

图5-3

图5-4

程序头:

图5-5

hello的ELF头和hello.o的ELF头大体一致,但是类型REL (可重定位文件)变为了EXEC (可执行文件),程序头从无到有,节头和字符串表索引节头的数量变多。
节头表中给出了hello各段的基本信息,括各段的起始地址,大小等

5.4 hello的虚拟地址空间

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

在5.3程序头部表部分中说过,程序头部表描述了可执行文件连续的片和连续的内段的映射关系,对应图5.5具体看一下对应关系:

PHDR保存程序头表,从0x400040开始。

INTERP指定在程序已经从可执行映射到内存之后,必须调用解释器:通过链接其他库,来满足未解决的引用。通常/lib/ld-linux-so.2、/lib/ld-linux-ia-64.so.2等库,用于在虚拟地址空间中插入程序运行所需的动态库。这里从0x400200开始。

LOAD表示一个从二进制文件映射到虚拟地址空间的段。其中保存了常量数据(如字符串),程序的目标代码等。从上图看出LOAD段从0x400000开始。

DYNAMIC段保存了其他动态链接器(即,INTERP中指定的解释器)使用的信息。这里从0x600e50开始。

NOTE保存了专有信息。

虚拟地址空间的各个段,填充了来自ELF文件中特定段的数据。程序头部表下面的部分,指出了哪些节载入到哪些段。

下面使用edb打开hello程序,通过edb的Data Dump窗口查看hello程序:

图5-6

5.5 链接的重定位过程分析

objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

图5-7

Hello的反汇编结果与hello.o的反汇编结果相比,多了以下的节:

_init 程序初始化代码

gmon_start call_gmon_start函数初始化gmon profiling system,这个系统是在编译程序时加上-pg选项,程序通过gprof可以输出函数调用等信息

_dl_relocate_static_pie 静态库链接

.plt 动态链接-过程链接表

Puts(等函数)@plt 动态链接各个函数

_start 编译器为可执行文件加上了一个启动例程

__libc_csu_init 程序调用libc库用来对程序进行初始化的函数,一般先于main函数执行

_fini 当程序正常终止时需要执行的代码

链接的过程:

根据hello和hello.o的不同,分析出链接的过程为:

链接就是链接器(ld)将各个目标文件(各种.o文件)组装在一起,文件中的各个函数段按照一定规则累积在一起。

5.6 hello的执行流程

使用edb执行hello,从加载hello到_start,到call main,以及程序终止的过程如下:

函数名

地址

hello!sleep@plt

0x401080

hello!printf@plt

0x401040

hello!exit@plt

0x401070

hello!puts@plt

0x401030

hello!_start

0x4010f0

hello!getchar@plt

0x401050

libc-2.31.so!exit

0x7f29410cfa70

libc-2.31.so!__libc_start_main

0x7f29401acfc0

ld-2.31.so!_dl_init

0x7f29401caf50

 ld-2.31.so!_dl_start

0x7f29401ffac0

5.7 Hello的动态链接分析

对于变量而言,我们利用代码段和数据段的相对位置不变的原则计算正确地址。对于库函数而言,需要plt、got合作,plt初始存的是一批代码,它们跳转到got所指示的位置,然后调用链接器。初始时got里面存的都是plt的第二条指令,随后链接器修改got,下一次再调用plt时,指向的就是正确的内存地址。plt就能跳转到正确的区域。

图5-8

 图5-9

分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

5.8 本章小结

本章主要了解温习了在linux中链接的过程。通过查看hello的虚拟地址空间,并且对比hello与hello.o的反汇编代码,更好地掌握了链接与之中重定位的过程。

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

概念:

进程的经典定义就是一个执行中程序的实例。在现代系统上运行一个程序时,我们会得到一个假象,就好像我们的程序是系统中当前运行的唯一的程序一样。我们的程序好像是独占地使用处理器和内存。处理器就好像是无间断地一条接一条地执行 我们程序中的指令。最后,我们程序中的代码和数据好像是系统内存中唯一的对象。这些假象都是通过进程的概念提供给我们的。

作用:

“进程”的引入为应用程序提供了以下两方面的抽象:一个独立的逻辑控制流和一个私有的虚拟地址空间。每个进程拥有一个独立的逻辑控制流,使得程序员以为自己的程序在执行过程中独占使用处理器;每个进程拥有一个私有的虚拟地址空间,使得程序员以为自己的程序在执行过程中独占存储器。

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

作用:shell-bash是一个交互型的应用级程序,它代表用户运行其他程序。能够执行一系列的读/求值(read/evaluate)步骤,然后终止。

处理流程:

读入用户输入命令

将用户输入的命令进行解析

若命令为内置命令则立即执行

否则若是一个可执行文件,则会在一个新子进程的上下文中加载并运行这个文件,若用户要求在后台运行,那么shell继续等待用户输入其他的命令,否则shell将等待该程序运行结束后,再进行下一条命令的处理。

shell还会接收用户通过键盘输入的信号(ctrl+z ctrl+c等),并执行相应的操作。

6.3 Hello的fork进程创建过程

在终端中输入./hello执行hello程序,这时候,shell会先判断是否为内置命令,在发现不是内置命令之后,就把这条命令当作一个可执行程序的名字,然后shell会执行fork子进程,然后在子进程的上下文中运行这个可执行目标文件。

子进程相当于父进程的一份复制品,它的上下文、代码、数据段、堆,共享库以及用户栈由父进程创建的副本而来,与父进程是完全相同的。要注意的是,父进程和子进程是相互独立,并发进行的。

6.4 Hello的execve过程

fork创建子进程后,使用execve在当前进程中载入并运行一个新程序即hello。
execve函数加载hello并带参数列表argv和环境变量envp。它会覆盖当前进程的代码、数据、栈,但是保留PID,继承已打开的文件描述符和信号上下文。

6.5 Hello的进程执行

(1) 时间片:

一个进程执行他的控制流的一部分的每一个时间段叫做时间片(time slice),多任务也叫时间分片(time slicing)。

(2) 进程的上下文切换:

调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被强占的进程。这种决策就叫调度(是由内核中的调度器的代码处理的)。

上下文切换:在内核调度了一个新的进程运行时,它就抢占当前进程,并使用一种上下文切换的机制来控制转移到新的进程。

1)保存当前进程的上下文

2)恢复某个先前被强占的进程被保存的上下文

3)将控制传递给这个新恢复的进程

(3)用户态与内核态的转换:处理器通常使用某个控制寄存器中的模式位来提供这种功能。当设置了模式位时,进程运行在内核模式中;否则运行在用户模式中。用户模式中的进程不允许执行特权指令,任何这样的尝试都会导致致命的保护故障。Hello进程首先是处于用户模式中,想要转换位内核模式的唯一方法是通过诸如终端,故障或陷入系统调用这样的方式。

(4)hello进程调度:调度是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行时,我们就说内核调度了这个进程。在内核调度了一个新的进程运行时,他就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。上下文切换包括:保存当前进程上下文,恢复某个先前被抢占进程的上下文,将控制传递给这个新恢复的进程。当操作系统调度hello进程时,它首先保存前一个进程的上下文信息,然后进入内核模式,将控制转移到hello,然后切换为用户模式。

6.6 hello的异常与信号处理

在hello运行的时候,会有异常控制流出现:

1)调用sleep函数:会发送STPSIG信号,让hello进程休眠。

2)定时器计时结束:定时器计时,会发送一个中断信号,被信号处理器处理,切换到hello进程。

图6-1

3)输入ctrl+c,shell父进程收到SIGSTP信号,信号处理程序将hello进程挂起,放到后台,ps看到hello进程并没有被回收,通过ps命令我们可以看到,hello命令并没有结束。接下来我们用fg命令将JID最大的放到前台,也就是刚刚挂起的hello进程,可以看到进程又继续执行完了剩下的7次循环。

图6-2

4)输入Ctrl+C操作后的结果。从键盘中输入Ctrl+C后,shell父进程会收到一个SIGINT信号,这个信号的功能是直接将子进程结束。可以在ps中看到,已经没有了hello进程。说明Ctrl+C会直接将进程结束并回收掉。

图6-3

5) jobs命令可以查看当前执行的关键操作是什么,比如我们执行了一个Ctrl+Z命令将进程挂起后,用jobs命令就能看到Ctrl+Z这条命令。

图6-4

6)pstree命令将所有的进程按照树状结构打印出来。这样我们就可以知道不同进程之间的关系。

图6-5

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结

本章简述了进程管理的一些简要信息,比如进程的概念作用,shell的基本原理,shell如何fork和execve我们的hello进程,我们的hello进程在执行时会遇到什么样的情况它是怎么被执行的。又介绍了一些常见异常和其信号处理方法。

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:又称相对地址,是程序运行由CPU产生的与段相关的偏移地址部分。他是描述一个程序运行段的地址。

物理地址:程序运行时加载到内存地址寄存器中的地址,内存单元的真正地址。他是在前端总线上传输的而且是唯一的。在hello程序中,他就表示了这个程序运行时的一条确切的指令在内存地址上的具体哪一块进行执行。

线性地址:是经过段机制转化之后用于描述程序分页信息的地址。他是对程序运行区块的一个抽象映射。

虚拟地址:其实虚拟地址跟线性地址是一个东西,都是对程序运行区块的相对映射。

就hello而言,他是在物理地址上运行的,但是对于CPU而言,CPU看到的hello运行的地址是逻辑地址,在具体操作的过程中,CPU会将逻辑地址转换成线性地址再变成物理地址。

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

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

索引号,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。

这里面,我们只用关心Base字段,它描述了一个段的开始位置的线性地址。

全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。

 GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。

 给定一个完整的逻辑地址段选择符+段内偏移地址,

 看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。

 拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。把Base + offset,就是要转换的线性地址了

图7-1

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

IA-32和x86-64采用段页式虚拟存储管理方式,通过分段方式完成逻辑地址到线性地址的转换后,再进一步通过分页方式将线性地址转换为物理地址。虚拟地址空间中的每个页在页表中一个固定的偏移量处都有一个PTE。PTE包含了一个有效位和一个n位字段,有效位表明了该虚拟页当前是否被缓存在DRAM中。因为DRAM是全相联的,所以任意物理页都可包含任意虚拟页。

n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO)和一个n-p位的虚拟页号(VPN)。MMU利用VPN来选择适当的PTE.将页表条目中的物理页号和VPO串联起来就是相应的物理地址。因为物理和虚拟页面都是P字节的,所以物理页面偏移和虚拟页面偏移是相同的。

图7-2

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

首先(以32位为例,64位也是一样)在TLB中查找是否有页表项,将虚拟地址根据页的大小分为虚拟页号和页内地址,再根据TLB的条目数分为标记位和组索引,在TLB(在cache里)中遍历各个条目(由页表基址寄存器值+组索引),如果标记位相同且有效位为1则读出相应的物理页号,和页内地址拼接则得出物理地址。

如果TLB中没有找到符合的条目,即TLB缺失,则在主存中查找四级页表。将虚拟页号根据四级页表的页表项数量分为四个虚拟页号,根据四个虚拟页号(前三个为页目录索引,第四个为页表索引),在四级页表中找出物理页号,如果相应页表条目为已缓存,则读出物理页号和页内地址相拼接得到物理地址。否则做缺页处理。

图7-3

图7-4

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

经过地址翻译后,物理地址由52位构成。L1 cache由64组组成,每一组有8行。每一行的大小为64字节。所以块偏移位CO为6位.同时因为有64组,所以组索引位也有6位。剩下的40位作为标记位。根据给定的物理地址,访问L1 cache。首先根据组索引位找到对应的组别,然后逐个搜索该组别中是否有与该请求的物理地址标记相同的行。

如果存在且该行的标记为有效,则为命中,取出该行块偏移所指示的内容,返回到CPU中。否则需要从L2 cache中利用相同的物理地址请求。如果还没命中,还需要再从L3 cache中请求,以此类推,直到从主存中请求数据。

7.6 hello进程fork时的内存映射

Shell通过调用函数fork()创建一个子进程,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,新创建的子进程获得与父进程完全相同的虚拟存储空间中的一个备份,包括只读段,可读写数据段,堆以及用户栈等。

7.7 hello进程execve时的内存映射

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

1、删除已存在得用户区域;

2、映射私有区域;

3、映射共享区域;

4、设置程序计数器。

execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点,下一次调度这个进程时,他将从这个入口点开始执行。

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

缺页现象的发生是由于页表只相当于磁盘的一个缓存,所以不可能保存磁盘中全部的信息,对于有些信息的查询就会出现查询失败的情况,也就是缺页。

对于一个访问虚拟内存的指令来说,如果发生了缺页现象,CPU就会触发一个缺页异常。缺页异常会调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,例如图7-7中存放在PP3中的VP4,如果VP4已经被更改,那就先将他存回到磁盘中。

找到了要存储的页后,内核会从磁盘中将需要访问的内存,例如图7-7所示的VP3放入到之前已经操作过的PP3中,并且将PTE中的信息更新,这样就成功的将一个物理地址缓存在了页表中。当异常处理返回的时候,CPU会重新执行访问虚拟内存的操作,这个时候就可以正常的访问,不会发生缺页现象了。

7.9动态存储分配管理

动态内存管理的基本方法与策略:

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长(向更高的地址)。对于每个进程,内核维护着一个变量brk。它指向堆的顶部。

分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显示地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显示地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

分配器有两种风格:

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

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

带边界标签的隐式空闲链表:

1)堆及堆中内存块的组织结构:

在内存块中增加4B的Header和4B的Footer,其中Header用于寻找下一个blcok,Footer用于寻找上一个block。Footer的设计是专门为了合并空闲块方便的。因为Header和Footer大小已知,所以我们利用Header和Footer中存放的块大小就可以寻找上下block。

2)隐式链表

所谓隐式空闲链表,对比于显式空闲链表,代表并不直接对空闲块进行链接,而是将对内存空间中的所有块组织成一个大链表,其中Header和Footer中的block大小间接起到了前驱、后继指针的作用。

3)空闲块合并

因为有了Footer,所以我们可以方便的对前面的空闲块进行合并。合并的情况一共分为四种:前空后不空,前不空后空,前后都空,前后都不空。对于四种情况分别进行空闲块合并,我们只需要通过改变Header和Footer中的值就可以完成这一操作。

显示空间链表基本原理:

将空闲块组织成链表形式的数据结构。堆可以组织成一个双向空闲链表,在每个空闲块中,都包含一个pred(前驱)和succ(后继)指针。

使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。

7.10本章小结

     本章介绍了储存器的地址空间,讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念,还有进程fork和execve时的内存映射的内容。描述了系统如何应对那些缺页异常,最后描述了malloc的内存分配管理机制(C语言为例)。可以看到真正高效的运行起一个程序来是很复杂的。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O

8.2 简述Unix IO接口及其函数

Unix I/O 接口:

(1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个 I/O 设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文 件的所有信息。

(2)Shell 创建的每个进程都有三个打开的文件:标准输入,标准输出,标准错误。

(3)改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位 置 k,初始为 0,这个文件位置是从文件开头起始的字节偏移量,应用 程序能够通过执行 seek,显式地将改变当前文件位置 k。

(4)读写文件:一个读操作就是从文件复制 n>0 个字节到内存,从当前文件位置 k开始,然后将 k增加到 k+n,给定一个大小为 m 字节的而文件,当 k>=m 时,触发 EOF。类似一个写操作就是从内存中复制 n>0 个字节到一个文件,从当前文件位置 k 开始,然后更新k。

(5)关闭文件,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。

Unix I/O 函数:

(1)int open(char* filename,int flags,mode_t mode) ,进程通过调用 open 函 数来打开一个存在的文件或是创建一个新文件的。 open函数将filename 转换为一个文件描述符,并且返回描述符数字,返回的描述符总是在 进程中当前没有打开的最小描述符,flags 参数指明了进程打算如何访 问这个文件,mode 参数指定了新文件的访问权限位。

(2)int close(fd),fd 是需要关闭的文件的描述符,close 返回操作结果。

(3) ssize_t read(int fd,void *buf,size_t n),read 函数从描述符为 fd 的当前文件位置赋值最多 n 个字节到内存位置 buf。返回值-1 表示一个错误,0 表示 EOF,否则返回值表示的是实际传送的字节数量。

4) ssize_t wirte(int fd,const void *buf,size_t n),write函数从内存位置 buf 复制至多 n 个字节到描述符为 fd 的当前文件位置。

8.3 printf的实现分析

  printf需要做的事情是:接受一fmt的格式,然后将匹配到的参数按照fmt格式输出。图8-1是printf的代码,我们可以发现,他调用了两个外部函数,一个是vsprintf,还有一个是write。

图8-1

       从图中的vsprintf函数可以看出,这个函数的作用是将所有的参数内容格式化后存入buf,然后返回格式化数组的长度。write函数是将buf中的i个元素写到终端的函数。write函数是将buf中的i个元素写到终端的函数。

Printf的运行过程:

从vsprintf生成显示信息,显示信息传送到write系统函数,write函数再陷阱-系统调用 int 0x80或syscall.

[转]printf 函数实现的深入剖析 - Pianistx - 博客园

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

异步异常-键盘中断的处理:键盘中断处理子程序,第一次getchar()时,确实需要人工的输入,但是如果你输了多个字符,以后的getchar()再执行时就会直接从缓冲区中读取了。实际上是 输入设备->内存缓冲区->程序getchar

getchar等调用read系统函数,通过系统调用读取按键ascii码,,一旦键入回车,getchar就进入缓冲区读取字符,一次只返回第一个字符作为getchar函数的值,如果有循环或足够多的getchar语句,就会依次读出缓冲区内的所有字符直到’\n’

8.5本章小结

本章主要阐述了Linux的IO设备管理办法以及IO接口实现与相应的函数实现。分析了getchar()和printf()函数的实现。

(第8章1分)

结论

Hello的一生:

预处理,C语言编译器对各种预处理命令进行处理,包括对头文件的包含,宏定义的扩展,条件编译的选择等,将hello.c转换为hello.i。

编译,将C语言文件hello.i翻译为汇编语言文件hello.s

汇编,将汇编语言代码文件hello.s转换为可重定位目标文件hello.o

链接,将多个可重定位目标文件hello.o、libc.a等经过符号解析和重定位结合,形成一个可执行目标文件hello

运行,在shell下输入命令./hello 120L020107 李维研 1,shell解析命令并构造参数列表

Fork子进程,shell调用fork创建一个子进程,具有和父进程完全相同的虚拟存储空间备份

加载,shell将构造好的参数列表传给execve作为参数,启动加载器并开始执行hello的第一条指令,实现在当前进程上下文中运行hello

执行:CPU执行指令;内核处理异常,调度程序。进程处理信号。还会访问存储器等。

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

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


附件

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

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

文件名

描述

Hello.c

源代码

Hello.i

预处理后产生的代码

Hello.o

汇编之后的文件

Hello.s

编译之后的文件

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.

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值