hello的一生

摘 要

Hello.c被编写完成,然后经历预处理,编译,汇编,链接,加载并运行进程(其间fork函数为其创建子进程,execve函数为其调用启动加载器,内核为其调度,此进程和其他进程并行运行,其中的操作可能涉及访问内存,MMU将虚拟内存映射成物理地址,或者动态内存的申请、分配,异常信号出现的处理),最后结束运行,父进程回收子进程,删除相关数据和文件,hello的一生就会这样的完结。

关键词:编译,汇编,链接,存储……;

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

第1章 概述

1.1 Hello简介

翻译成文本文件hello.s),汇编(汇编器将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序,并将结果保存在hello.o中),链接(链接器负责处理合并),最后得到一个可执行目标文件,可以被加载到内存里,由系统执行。之后将它的文件名输入到称为shell的应用程序中,最终加载并运行这个文件,此时OS会使用fork函数形成一个子进程,分配相应的内存资源,并使用execve函数加载进程。这就是Hello的P2P过程。

Shell执行一系列指令来加载可执行的hello文件,这些指令将hello目标文件中的代码和数据从磁盘复制到内存,利用直接存储器存取技术,数据可以不通过处理器而直接从磁盘到达内存,一旦目标文件的代码和数据被加载到主存,处理器就开始执行hello程序的main程序中的机器语言指令。CPU为运行的程序分配时间执行片执行逻辑控制流。程序执行完毕之后,shell父进程回收子进程hello,并且内核会重系统中删除hello相应痕迹,这就是hello的O2O过程。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

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

软件环境:Window10;Ubuntu18.04

开发与调试工具:GDB,readlef,edb-debugger

1.3 中间结果

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

文件名

文件的作用

Hello.i

预处理生成的修改了的源程序(文本)

Hello.s

汇编程序(文本)

Hello.o

可重定位的目标程序(二进制)

Hello

可执行的目标程序(二进制)

1.4 本章小结

本章主要概述了hello的一生,介绍了环境与工具和中间结果,为下面详尽描述hello的一生做准备。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理阶段是预处理器(cpp)根据以字符#开头的命令修改原始的C程序。比如将头文件<stdio.h>的内容直接插入程序文本中。结果文件通常是以.i作为文件的扩展名。

作用:

(1)#define 预处理:将所有的#define删除,并展开所有的宏定义。

(2)预编译指令:处理预编译指令,比如#if,#endif等

(3)#include头文件:将相关的头文件插入到预编译指令的位置

(4)添加行号信息文件名称

(5)删除注释

(6)保留所有的#pragma编译指令

2.2在Ubuntu下预处理的命令

gcc –E –o
hello.i hello.c

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

应截图,展示预处理过程!

图2.2.1 Linux下的预处理命令

图2.2.2 预处理之后的文件,如红圈所示

2.3 Hello的预处理结果解析

以下是预处理之后的文件,可以看到与之前的hello.c相比有了十足的改变:

图2.3.1
hello.i部分代码

图2.3.2 hello.i
部分代码(2)

如图可知,预处理器将所有的#define删除,并展开所有的宏定义,然后将头文件<stdio.h><unistd.h><stdlib.h>直接插入文件,并去掉了注释,将原来的文件扩充到现在有3118行的样子。现在的文件仍然是C语言可读的,只是较之前的hello.c有了相应的补充。

2.4 本章小结

本章总结了预处理的概念与作用,并利用Linux系统初步实践了预处理过程。得到预处理之后的文件后,又进行了分析,验证了之前的总结。预处理指令根据字符#开头的命令修改了原C语言程序,将#define删除并展开宏定义,将头文件插入程序,删掉注释,扩充程序,最终完成预处理过程。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编译器将文本文件hello.i翻译成文本文件hello.s它包含一个汇编语言程序,该程序办函函数main的定义。汇编语言为不同的高级语言的不同编译器提供了通用的输出语言。

作用:

编译程序通过五个阶段把一个源程序翻译成目标程序,分别是词法分析,语法分析,语义检查和中间代码生成,代码优化,目标代码生成。

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

3.2 在Ubuntu下编译的命令

gcc -m64 -no-pie
-fno-PIC -S hello.i > hello.s

图3.2.1 编译指令

图3.2.2 编译产生的文件(最右)

3.3 Hello的编译结果解析

图3.3.1 编译结果(部分)

图3.2.2 编译结果(部分)

3.3.1 数据

3.3.1.1 整数

Hello.s中有很多整数,其中int sleepsecs 是在hello.c中被定义为全局变量且赋值,编译器在.data节中就声明了此变量,然后在.text声明其为全局变量,之后在.data段中设置对齐方式为4,设置类型为long,大小为4小节,值为2。

图3.3.1.1sleepsecs的编译

int i:hello.s中i被储存在栈的-4(%rbp)中,看出i占据了栈中的4B.

int argc:作为第一个参数传入,是main的默认参数。

3.3.1.2 字符串(或字符串数组)

hello.s中有两个字符串,分别是:“Usage: Hello 学号 姓名!\n"和"Hello %s %s\n”。

第一个字符串被转换成UTF-8格式,如图所示。

图3.3.1.2.1
第一个字符串的编译结果

第二个字符串"Hello %s %s\n"是printf输出的格式化参数,在hello.s结果如下:

图3.1.1.2.2
第二个字符串的编译结果

3.3.1.3 数组

Char *argv[] main,是main函数输入的参数,是存放char指针的数组。

图3.3.1.3 有关数组的编译结果

3.3.2 赋值

int
sleepsecs时有赋值,直接在data节里将sleepsecs声明为值为2的long。

i=0:整形数据的赋值用mov指令完成。

图3.3.2 赋值的编译结果

3.3.3 类型转换

Int sleepsecs=2.5:由于sleepsecs是int整数型的,但2.5是float型,所以编译器将2.5自动向下取整,转换成2赋值给sleepsecs。

3.3.4 算术操作

i++:编译器将i++编译成

3.3.5 关系操作

其间有两个关于关系的操作:

i<10:编译器将其编译成如图所示:

图3.3.5.1 i<10的编译结果

Argc!=3:编译器将其编译成如图所示:

图3.3.5.2 argc!=3的编译结果

3.3.6 数组/指针/结构操作

Printf函数中有一系列对指针和数组的操作的编译:

图3.3.6 printf有关于指针和数组的编译结果

3.3.7 控制转移

if跳转和for的跳转:使用cmpl的比较操作,然后使用条件跳转指令来跳转。其中第一个跳转是argc!=3,如果符合条件就跳转,不符合就调用printf函数。而for循环中有i<10的比较,用这个来实现控制转移操作,如果不符合条件就跳出循环。

3.3.8 函数操作

程序中涉及函数操作的有三个函数:

Main函数:main函数只有被系统启动函数_libc_start_main调用才会执行,然后进行传递数据加分配并释放内存操作。

Printf函数:传递数据并在屏幕上打印。Printf编译结果如下:

图3.3.8.1
printf函数的编译结果

Sleep函数:传递输入参数sleepsecs,然后call
sleep@PLT

图3.3.8.2 sleep函数的编译结果

3.4 本章小结

本章先是总结了编译的概念和作用,然后完成了对hello.c的编译工作,并详细分析了编译产生的文件hello.s。在编译阶段,编译器将高级语言编译成汇编语言其有机器相关性,高速度和高效率,编写和调试复杂性等特点。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

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

   汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应着一条机器指令。汇编相对于编译过程来说比较简单,根据汇编指令和机器指令的对照表一字一字翻译即可。

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

4.2 在Ubuntu下汇编的命令

gcc -m64 -no-pie -fno-PIC -E hello.s >
hello.o

图4.2.1 hello.c汇编结果

4.3 可重定位目标elf格式

图4.3.2 readelf列出的节头信息

节头部表,包含了文件中出现的各个节的语义,包含节的类型,位置,对齐等信息。

指令:gcc -m64
-no-pie -fno-PIC readelf -a hello.o > helloo.elf

图4.3. readelf列出的重定位节信息

重定位节是一个.text节中位置的列表,包含.text节中需要进行重定位的信息,当连接器把这个文件和其他文件组合时,需要改变这些位置。

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

4.4 Hello.o的结果解析

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

机器语言指的是二进制的机器指令集合,而机器指令是由操作码和操作数构成的。汇编语言的主体是汇编指令。汇编指令和机器指令的差别在于指令的表示方法上,汇编指令是机器指令便于记忆的书写格式。而且不同型号的计算机其机器语言是不相通的,按照一种计算机的机器指令编写的程序,不能在另一种计算机上执行。

分支转移:在反汇编代码中,跳转指令的操作数使用的不是段名称,因为段名称只是在汇编语言里出现的便于编写的助记符,所以其反汇编之后就不存在了,与汇编语言不同,反汇编跳转指令后面的操作数直接是确定的地址。

函数调用:在汇编语言中,函数调用直接跟着函数名称;但在反汇编程序中,call的目标地址是当前下一条指令。Hello.c中调用的函数是共享库中的函数,最终徐亚通过链接才能确定函数运行时候的执行地址,但在汇编成为机器语言时,对于这些地址不确定的函数调用,将其相对地址全设置为0,然后在.rela.text中为其添加重定位条目,等待静态链接。

//说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.5 本章小结

本章先是总结了汇编的概念和作用,并用readelf查看了elf文件的格式,并用odjump查看反汇编代码,与之前第三章的汇编语言做对比,发现了很多不同之处,其说明了反汇编和汇编的各自特性。作为机器可以直接执行的语言,机器语言和汇编语言存在着映射关系,能够反映机器执行程序的逻辑。当然,生成最终的可执行文件还需要经过链接这一步,这就是下一章的内容。

(第4章1分)

第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.2.1 链接指令输入

图5.2.2 可执行程序已经生成

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

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

使用readelf –a
hello > hello.elf指令生成ELF格式的文件。

ELF头:

图5.3.1 ELF头的信息

段头部表:

图5.3.2 段头部的信息

Programm Headers:

图5.3.3 ProgrammerHeader的信息

Dynamic section:

图5.3.4 Dynamicsection的信息

Symbol
table:

图5.3.5 Symbol table的信息

Version
needs section:

图5.3.6 Version needs
section的信息

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间

图5.4.1 edb打开.text代码段

代码段开始于0x400000处,程序被载入,然后查看elf文件里面的programheaders,程序头表在执行时被使用,它负责提供链接器加载的内容并提供动态链接的信息。

图5.4.1 edb打开.text代码段

PHDR保存程序头表,INTERP指定在程序已经从可执行文件映射到内存之后必须调用的解释器。LOAD表示一个需要从二进制文件映射到虚拟地址空间,它保存了字符串,数组等的常量数据和目标代码等。DYNAMIC保存了动态链接器使用的信息,NOTE保存辅助信息,GNU_STACK是一个权限标志,它表示栈是否是可执行的。最后一个是制定在重定向之后的那些内存区域是否需要设置只读。

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

5.5 链接的重定位过程分析

图5.5.1 得到反汇编代码的结果

通过输入指令得到了相关的反汇编代码,比较发现了很多不同:

  1. 函数调用:
    

反汇编对于外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库中的函数已经加入PLT中,链接器计算的相对距离将对动态链接库的函数的调用值发生修改,改成PLT中相应函数与下条指令的相对地址,指向对应函数。

  1. .rodata引用:
    

链接器反汇编的时候,发现两个类型为R_X86_64_PC32的重定位对于.rodata和.text两节之间的相对距离是确定的。于是链接器直接把地址改成了两个地址之差,直接指向相应的字符串。

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

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

5.6 hello的执行流程

在main函数之前执行的函数有:

_start;

_libc_start_main@plt;

__libc_csu_init;

_init;

frame_dummy;

register_tm_clones;

在main函数之后执行的程序有:

Exit();

cxa_thread_atexit_impl;

fini;

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

5.7 Hello的动态链接分析

在_dl_init前后设置断点,然后调试:

图5.7.1 在_dl_init前后设置断点

发生改变的信息如下:

图5.7.2 在_dl_init前后断点的执行结果

动态共享链接库中的PIC函数,编译器没有办法预测函数的运行地址,所以需要添加重定位记录,等待动态链接器的处理。动态链接器使用过程链接表PLT和全局偏移量表GOT实现函数的动态链接。在_dl_init调用钱,GOT存放给的屙屎PLT中函数调用指令的下一条指令。在调用之后发生了如上图的变化,而在之后的函数调用过程中,首先函数跳转到PLT中执行.plt中的逻辑结构,第一次访问跳转时GOT地址为下一条指令。将函数压进栈,然后跳转到PLT[0],在这个地址将重定位表地址压栈,然后访问动态链接器重写GOT。再将控制传递给目标函数。这样的话,如果之后再有同样的函数调用,就会直接跳转到目标函数里去了。

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

5.8 本章小结

在本章,我们首先总结概括了链接的概念与作用,然后我们查看并分析了ELF格式,分析了hello的虚拟空间地址空间,重定向过程,执行过程和动态链接分析,这一章让我们充分理解了链接是如何发生的,它究竟是怎样的一个过程。

(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程的经典定义是一个执行中的程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的代码和数据,它的栈、通用目的寄存器中的内容、程序计数器、环境变量和打开文件描述符的集合。进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

作用:在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

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

Shell字面理解就是个“壳”,是操作系统(内核)与用户之间的桥梁,充当命令解释器的作用,将用户输入的命令翻译给系统执行。Linux中的shell与Windows下的DOS一样,提供一些内建命令(shell命令)供用户使用,可以用这些命令编写shell脚本来完成复杂重复性的工作。

处理流程:

从终端读入输入的命令,将输入字符串切分获得所有的参数,如果是内置命令则立即执行,否则调用相应的程序为其分配子进程并运行。shell应该接受键盘输入信号,并对这些信号进行相应处理,并及时回收进程。

6.3 Hello的fork进程创建过程

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

6.4 Hello的execve过程

execve函数加载并运行可执行目标文filename, 且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到filename, execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve 调用一次并从不返回。

6.5 Hello的进程执行

在获取程序进程ID(PID)之后,hello等待被内核执行,内核为hello保存一个上下文,这个上下文包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构(比如页表、进程表、文件表)等等。

Execve函数在当前进程的上下文中加载并运行一个新程序,当hello在内核中运行的时候,并行的也有其他进程,一个进程执行它的控制流的一部分的每一时间段叫做时间片,所以内核可以控制到底是否需要枪战当前进程,并重新开始一个先前被挂起的进程或者别的进程,这个过程成为调度。

在此前提下,hello程序与操作系统其他的进程通过内核的调度,切换上下文,并行地完成进程。当程序在完成一些操作的时候,例如调用一些系统函数,访问一些系统参数,当前程序需要核心权限,这时候就要让程序从用户态转变为核心态,通过信号函数的作用完成这个转换。

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

6.6 hello的异常与信号处理

1.中途按下Ctrl-z

当按下ctrl-z之后,程序马上终止.shell父进程收到SIGSTP信号,信号处理函数的逻辑是打印屏幕回显、将hello进程挂起,通过ps命令我们可以看出hello进程没有被回收,然后fg将其调到前台,然后shell程序首先打印hello的命令行命令,hello继续运行打印剩下的6条info,之后输入字串,程序结束,同时进程被回收。

图6.6.1 中途按下Ctrl-z

2.中途按下Ctrl-c

当按下ctrl-c之后,shell父进程收到SIGINT信号,信号处理函数的逻辑是结束hello,并回收hello进程。然后fg就不会继续执行未完成的任务.

图6.6.2 中途按下Ctrl-c

3.中途按乱按

首先可以随便按回车和其他的字符等等,不会对程序的运行造成影响.因为乱按只是将屏幕的输入缓存到stdin,当getchar的时候读出一个’\n’结尾的字串(作为一次输入),其他字串会当做shell命令行输入。

图6.6.3 中途乱按

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

6.7本章小结

在本章中,我们首先总结了进程的概念与作用,然后我们完整地分析并实践了hello程序是如何从头到尾运行的,并分析了可能出现的异常与信号处理,做出总结。

(第6章1分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址是程序代码经过编译之后出现在汇编程序中的地址,由选择符和偏移量组成。

线性地址是逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的形式。

虚拟地址:其实就是这里的线性地址。

物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。 CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。

结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

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

一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符由16位字段组成,前13位为索引号。

索引号是段描述符的索引,很多个描述符,组成了一个数组,叫做段描述表,可以通过段描述标识符的前13位,再这个表中找到一个具体的段描述符,这个描述符就描述了一个段,每个段描述符由八个字节组成。段描述符中的base字段,描述了段开始的线性地址,一些全局的段描述符,放在全局段描述符表中,一些局部的则对应放在局部段描述符表中。由T1字段决定使用哪个。

给定一个完整的逻辑地址[段选择符:段内偏移地址]。看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。可以得到一个数组。取出段选择符中前13位,在数组中查找到对应的段描述符,得到Base,也就是基地址。线性地址 =
Base + offset。

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

线性地址即虚拟地址,用VA来表示。VA被分为虚拟页号(VPN)与虚拟页偏移量(VPO),CPU取出虚拟页号,通过页表基址寄存器(PTBR)来定位页表条目,在有效位为1时,从页表条目中取出信息物理页号(PPN),通过将物理页号与虚拟页偏移量(VPO)结合,得到由物理地址(PPN)和物理页偏移量(PPO)组合的物理地址。

图7.3.1 使用页表的地址翻译(图来自教材)

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

图7-5给出了Core i7 MMU如何使用四级的页表来将虚拟地址翻译成物理地址。36位VPN 被划分成四个9位的片,每个片被用作到一个页表的偏移量。CR3 寄存器包含Ll页表的物理地址。VPN 1提供到一个Ll PET的偏移量,这个PTE包含L2页表的基地址。VPN 2提供到一个L2 PTE的偏移量,以此类推。

Core i7是四级页表进行的虚拟地址转物理地址。48位的虚拟地址的前36位被分为四级VPN区。结合存放在CR3的基址寄存器,由前面多级页表的知识,可以确定最终的PPN,与VPO结合得到物理地址。

图7.4.1 Corei7页表翻译(图来自教材)

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

图7.5.1 Corei7的内存系统(图来自教材)

CPU发出一个虚拟地址再TLB里搜索,如果命中,直接发送到L1cache里,如果没有命中,就现在也表里加载到之后再发送过去,到了L1中,寻找物理地址又要检测是否命中,如果没有命中,就向L2/L3中查找。这就用到了CPU高速缓存,这种机制加上TLB可以是的机器再翻译地址的时候性能得以充分发挥。

图7.5.2 Corei7地址翻译的概况(图来自教材)

7.6 hello进程fork时的内存映射

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

当fork 在hello进程中返回时,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.8 缺页故障与缺页中断处理

缺页故障是一种常见的故障,当指令引用一个虚拟地址,在MMU中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障。

缺页中断处理:缺页处理程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令再次发送VA到MMU,这次MMU就能正常翻译VA了。

7.9动态存储分配管理

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

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

基本方法:首次适配,下一次适配和最佳适配。首次适配是从开始处往后搜索,下一次适配是从上一次适配发生处开始搜索,最佳适配依次检查所有块,性能要比首次适配和下一次适配都要高。

策略:隐式空闲链表和显示空闲链表。任何实际的分配器都需要一些数据结构,允许其来区别块边界,以及区别已分配块和空闲块。大多数分配器将这些信息嵌入块本身。

隐式空闲链表:通过头部中的大小字段隐含的连接。分配器可以通过遍历堆中的所有块,从而间接地遍历整个空闲块地集合。

显示空闲链表:因为根据定义,程序不需要一个空闲块地主题,所以实现这个数据结构地指针可以存放在这些空闲块的主体里。

Printf会调用malloc,请简述动态内存管理的基本方法与策略。

7.10本章小结

本章先是总结了hello的地址存储空间,然后详细探讨了如何将虚拟地址转化成物理地址,其间有各种转换的方式,之后分析了出现缺页故障和中断的时候该如何处理,最后我们探讨了动态内存分配的问题,这个问题我们在实验中探讨并实现过。

(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix
IO接口及其函数

Linux提供如下 IO接口函数:

(1)read和write:最简单的读写函数;

(2)readn和writen:原子性读写操作;

(3)recvfrom和sendto:增加了目标地址和地址结构长度的参数;

(4)recv和send:允许从进程到内核传递标志;

(5)read和writev:允许指定往其中输入数据或从其中输出数据的缓冲区;

(6)recvmsg和sendmsg:结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力。

8.3 printf的实现分析

int
printf(const char fmt, …)

{

int i;

char
buf[256];

va_list arg
= (va_list)((char)(&fmt) + 4);

//arg是一个指针,表示的是被省略参数中的第一个参数。

i =
vsprintf(buf, fmt, arg);

//vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt(输入)。//用格式字符串对个数变化的参数进行格式化,产生格式化输出。

write(buf,
i);// 接受buf与需要输出的参数个数,

//执行写操作,把buf中的i个元素的值写到终端

return i;

}

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

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

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

8.4 getchar的实现分析

int
getchar(void)

{

char c;

return
(read(0,&c,1)==1)?(unsigned char)c:EOF

//read函数的第一个参数是描述符fd,0代表标准输入

//第二个参数是输入内容的指针,这里是所读取字符的地址

//最后一个参数等于1表示读入字符数为1

}

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章总结了Linux的IO设备的管理方法,然后分析了Linux提供的各种接口函数,其次又详细分析了printf和getchar的内部函数构造和原理。

(第8章1分)

结论

用计算机系统的语言,逐条总结hello所经历的过程。

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

Hello.c被编写完成,然后经历预处理,编译,汇编,链接,加载并运行进程(其间fork函数为其创建子进程,execve函数为其调用启动加载器,内核为其调度,此进程和其他进程并行运行,其中的操作可能涉及访问内存,MMU将虚拟内存映射成物理地址,或者动态内存的申请、分配,异常信号出现的处理),最后结束运行,父进程回收子进程,删除相关数据和文件,hello的一生就会这样的完结。其中的每一个小过程都有着十分复杂而严密的原理和执行方法,让人着实惊叹计算机完成一个简单程序也是十分不容易的发展。更让我们加深对计算机系统的认识和理解。这门课很深奥,即使用hello的一生窥探其冰山一角,都觉得精妙绝伦,我是一定还要加油学习的了。

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

附件

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

文件名

作用

Hello.i

预处理过程产生的文件

Hello.s

编译过程产生的文件

Hello.o

汇编过程之后产生的文件

Hello.elf

Hello.o文件反汇编之后的文件

Helloo.elf

Hello反汇编产生的文件

hello

Hello.c产生的可执行文件

(附件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.

[7]
Randal E. Bryant & David R. O’Hallaron《深入理解计算机系统第三版》

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值