哈工大计算机系统HITICS大作业

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业         计算学部        

学     号        1190200707       

班     级          1936601       

学       生          张修语      

指 导 教 师           刘宏伟        

摘  要

本文讲述了hello.c程序在Linux环境下编写完成后到运行的生命历程,借助有关工具分析hello.c经过预处理、编译、汇编、链接等各个过程最终实现的原理,分析了这些过程中产生的文件的相应信息和作用。并且在此基础上理解进程管理、存储管理、IO管理等知识,对计算机系统的工作原理有了更加深入的理解。

关键词:Hello程序;实现过程;计算机系统                           

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

目  录

第1章 概述... - 4 -

1.1 Hello简介... - 4 -

1.2 环境与工具... - 4 -

1.3 中间结果... - 4 -

1.4 本章小结... - 5 -

第2章 预处理... - 6 -

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

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

2.3 Hello的预处理结果解析... - 7 -

2.4 本章小结... - 7 -

第3章 编译... - 8 -

3.1 编译的概念与作用... - 8 -

3.2 在Ubuntu下编译的命令... - 8 -

3.3 Hello的编译结果解析... - 8 -

3.3.1数据... - 9 -

3.3.2赋值操作... - 10 -

3.3.3类型转换... - 10 -

3.3.4算术操作... - 11 -

3.3.5关系操作... - 11 -

3.3.6数组操作... - 11 -

3.3.7控制转移... - 11 -

3.3.8函数操作... - 12 -

3.4 本章小结... - 12 -

第4章 汇编... - 13 -

4.1 汇编的概念与作用... - 13 -

4.2 在Ubuntu下汇编的命令... - 13 -

4.3 可重定位目标elf格式... - 13 -

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

4.5 本章小结... - 18 -

第5章 链接... - 19 -

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

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

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

5.4 hello的虚拟地址空间... - 24 -

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

5.6 hello的执行流程... - 25 -

5.7 Hello的动态链接分析... - 25 -

5.8 本章小结... - 26 -

第6章 hello进程管理... - 27 -

6.1 进程的概念与作用... - 27 -

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

6.3 Hello的fork进程创建过程... - 27 -

6.4 Hello的execve过程... - 27 -

6.5 Hello的进程执行... - 28 -

6.6 hello的异常与信号处理... - 28 -

6.7本章小结... - 32 -

第7章 hello的存储管理... - 33 -

7.1 hello的存储器地址空间... - 33 -

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

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

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

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

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

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

7.8 缺页故障与缺页中断处理... - 36 -

7.9动态存储分配管理... - 36 -

7.10本章小结... - 37 -

第8章 hello的IO管理... - 38 -

8.1 Linux的IO设备管理方法... - 38 -

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

8.3 printf的实现分析... - 39 -

8.4 getchar的实现分析... - 39 -

8.5本章小结... - 40 -

结论... - 41 -

附件... - 42 -

参考文献... - 43 -

第1章 概述

1.1 Hello简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

P2P: Program to Process

在Linux环境中,hello.c经过cpp的预处理、ccl的编译、as的汇编、ld的链接最终成为可执行目标程序hello,在终端中执行此文件时,操作系统会为其fork产生子进程,再通过execve加载此进程。

020: From Zero-0 to Zero-0

操作系统调用execve后映射虚拟内存,先删除当前虚拟地址的数据结构并为hello创建新的区域结构,进入程序入口后载入物理内存,再进入main函数执行代码。代码完成后,父进程回收hello进程,内核删除相关数据结构。

1.2 环境与工具

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

硬件环境:X64 i7 CPU;16GRAM;512GDisk

软件环境:Windows10;VirtualBox;Ubuntu 20.04

开发/调试工具:gcc;gdb;objdump;readelf;edb

1.3 中间结果

hello.c:源代码文件

hello.i:预处理后生成的文件

hello.s:编译后生成的文件

hello.o:汇编后生成的可重定位目标程序

hello_o_r.txt:hello.o的elf格式,分析汇编器和链接器行为

hello_o_o.txt:hello.o的反汇编文件

hello:链接之后生成的可执行程序

hello.txt:hello的反汇编文件

1.4 本章小结

本章主要介绍了hello程序的P2P,020过程,以及实验时的软硬件环境及开发与调试工具并介绍了在本论文中生成的中间结果文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理的概念是:在编译之前进行的处理。C语言的预处理主要有以下三个方面的内容:1. 宏定义;2. 文件包含;3. 条件编译,预处理命令以符号#开头,如#if, #endif, #define, #include等。

预处理的作用:预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中。结果就得到了另一个C程序,通常以.i作为文件扩展名。

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

预处理前的文件仅30行,预处理后的文件达到了三千多行,但仔细观察可见main函数部分并未做出任何改变,因此可见预处理仅仅是将#include文件库函数的代码复制上去

2.4 本章小结

本章介绍了预处理的概念和作用,同时介绍了Ubuntu环境下预处理的命令,并对预处理生成的hello.i文件进行了简要分析。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

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

概念:指利用编译程序从预处理文本文件(.i)产生汇编程序(.s)的过程。

作用:将输入的高级程序设计语言源程序翻译成以汇编语言或机器语言表示的目标程序作为输出。

3.2 在Ubuntu下编译的命令

gcc -S -o hello.s hello.i

3.3 Hello的编译结果解析

3.3.1数据

(1) 全局变量:

在此程序中仅有一个全局变量sleepsecs,且已被赋初值,根据已有知识应将此变量放于.data中,.align表示四字节对齐,.size表示此变量长度为四字节,最后.long将其赋初值2

(2) 常量:

此程序中有两个常量,分别对应printf输出的两个字符串,根据已学的知识,的值应将其放于.rodata中,分别用.LC0和.LC1指示

(3) 局部变量:

通过分析下方循环体的内容可知,用于循环计数的局部变量i 存放在%rbp-4的内存地址处

(4) 函数参数

如图,函数参数同样是存放在栈上的

3.3.2赋值操作

此程序中一共含有两次赋值操作:sleepsecs的赋值和循环过程中i的赋值。

sleepsecs的 赋值操作已经在上方详细阐述过,在此处不再赘述。

下面分析循环体中i的赋值:

读上述代码可知,首先执行L2,将i赋值为0,后续与9进行比较以达成循环。所以,38行代码即为对i的赋值操作

3.3.3类型转换

此程序仅包含一次强制类型转换,即全局变量sleepsecs赋值为2.5时,编译器将其自动视为int类型的2了。

3.3.4算术操作

       此程序仅包含一次算术操作,即循环体中i的i++操作,继续分析循环体可知,54行处的addl操作即为此操作,实际效果为addl a, b ->b =a+b

3.3.5关系操作

       一次是判断argc是否不等于3,另一次是判断i在循环条件中是否小于10

(1)此处判断argc是否不等于3,若argc和3相等跳转,跳转到L2。

(2)此处判断i在循环条件中是否小于10,若i小于等于9跳转,跳转到L4。

3.3.6数组操作

       此代码中通过首地址+偏移量的方式访问数组argv[]中的元素,其中,数组首地址位于%rbp-32的位置,每个数组元素长度为8,因为数组元素类型是指针类型,在程序中具体操作如下图:

3.3.7控制转移

两次转移与3.3.5中的两次关系操作一一对应,分别对应源代码中的if分支语句和for循环判断语句

3.3.8函数操作

       计算编译后汇编语言执行call语句的次数,即可与源代码的五次调用函数的操作一一对应,分别是一次printf函数,一次exit函数,一次printf函数,一次sleep函数和一次getchar函数:

(1)printf函数

(2)exit函数

(3)printf函数

(4)sleep函数

(5)getchar函数

3.4 本章小结

本章介绍了编译的概念和作用,并针对具体的例子hello.s,按操作类型分类对hello.s进行解析,并且与源代码对比分析,得到了编译器解析代码时一些具体的思路和流程。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

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

作用:将汇编代码转换成真正机器可以读懂的二进制代码。

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式

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

符号表:

重定位节:

4.4 Hello.o的结果解析

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

与第三章的hello.s对比,首先在指令前增加了其十六进制表示,即机器语言。其次在操作数上,hello.s中操作数为十进制,而hello.o的反汇编中操作数为十六进制。在条件跳转语句上,hello.o的反汇编文件用的是相对偏移量,而hello.s中是函数名:

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

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

(2)分支转移的不一致:hello.s中的分支转移(即跳转指令)直接通过像.LC0,.LC1这样的助记符进行跳转,会直接跳转到相应符号声明的位置。助记符只是帮助程序员理解的,从汇编语言转换成机器语言之后,助记符就不再存在了,因此机器语言中的跳转使用的是确定的地址。下图中的main+0x29就表明要跳转到距main函数偏移量为0x29的位置。

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

4.5 本章小结

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

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

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

5.4 hello的虚拟地址空间

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

虚拟地址空间起始地址为0x400000结束地址为0x400ff0

下图为.txt节地址的起始地址0x400550,大小为0x132.

5.5 链接的重定位过程分析

hello和hello.o相比,首先多了很多经过重定位之后的函数,如_init、puts@plt等,hello.o在.text段之后只有一个main函数;hello.o的地址是从0开始的,是相对地址,而hello的地址是从0x401000(_init的地址)开始的,是已经进行重定位之后的虚拟地址;在hello的main函数中,条件跳转指令和call指令后均为绝对地址,而hello.o中是相对于main函数的相对地址。

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

hello重定位的过程:

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

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

(3)重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。

5.6 hello的执行流程

函数名                      地址

ld-2.27.so!_dl_start   

ld-2.27.so!_dl_init    

hello!_start                 0x400550

hello!_init                  0x4004c0

hello!main                 0x400582

hello!puts@plt           0x4004f0

hello!exit@plt            0x400530

hello!printf@plt         0x400500

hello!sleep@plt         0x400540

hello!getchar@plt      0x400510

sleep@plt                  0x400540

5.7 Hello的动态链接分析

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

全局偏移表GOT是一个数组,每个条目是一个8字节的地址,与PLT结合使用。GOT [0]和GOT [1]包含动态链接器解析函数地址时将使用的信息,GOT [2]是1d-linux.so模块中动态链接器的入口点。其余的每个条目对应于一个被调用的函数,该函数的地址需要在运行时解析。每个条目都有一个匹配的PLT条目。根据节头表,就可以得到PLT段存储GOT的起始地址为0x6008b8

查看EDB中的初始GOT条目。除了PLT[0]之外,每个PLT对应的got条目最初指向该PLT的第二条指令。如PLT[1]对应地址0x6008d0处的GOT[3],0x6008d0处的值是0x400466,它正好指向PLT[1]的第二条指令。第一次调用函数时,动态链接器会修改相应的GOT条目。

改变后的GOT表如下:

5.8 本章小结

本章通过对hello可执行程序的分析,回顾了链接的基本概念,文件的重定位过程,动态链接过程,虚拟地址空间,可重定位目标文件ELF格式的各个节等与链接有关的内容。链接的过程在软件开发中扮演一个关键的角色,它们使得分离编译成为可能。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

进程是一个执行中的程序的实例,每一个进程都有它自己的地址空间,一般情 况下,包括文本区域、数据区域、和堆栈。文本区域存储处理器执行的代码;数 据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储区着活动 过程调用的指令和本地变量。

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

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

Shell是指为使用者提供操作界面的软件(命令解析器)。它接受用户命令,然后调用相应的应用程序。Linux系统中所有的可执行文件都可以作为Shell命令来执行。

处理流程:首先对用户输入的命令进行解析,判断命令是否为内置命令,如果为内置命令,调用内置命令处理函数;如果不是内置命令,就创建一个子进程,将程序在该子进程的上下文中运行。判断为前台程序还是后台程序,如果是前台程序则直接执行并等待执行结束,如果是后台程序则将其放入后台并返回。同时Shell对键盘输入的信号和其他信号有特定的处理。

6.3 Hello的fork进程创建过程

父进程通过调用fork函数创建一个新的运行的子进程。Fork函数调用一次返回两次。

Shell通过fork创建一个新的子进程,它将在这个子进程上加载并运行hello。

6.4 Hello的execve过程

Shell通过调用execve函数加载并运行可执行目标文件hello。execve函数调用一次从不返回。

当execve加载了hello之后,它调用__libc_start_main。其设置栈,并将控制转移给新程序的主函数。

6.5 Hello的进程执行

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

逻辑流的执行在时间上与另一个流重叠,这称为并发流。这两个流称为并发运行。这个过程还为每个程序提供了一种假象,好像它只使用系统地址空间一样。

处理器通常使用控制寄存器中的模式位来提供一种机制来限制应用程序可以执行的指令范围和可以访问的地址空间。此寄存器描述进程当前享有的特权。设置模式位后,进程以内核模式运行。在内核模式下运行的进程可以执行指令集中的任何指令并访问系统中的任何内存位置。未设置模式位时,进程以用户模式运行。在用户模式下,不允许进程执行特权指令,也不允许进程直接引用地址空间内核区域中的代码和数据。

运行应用程序代码的进程以用户模式启动。当异常发生时,控制传递给异常处理程序,处理器将模式从用户模式更改为内核模式。处理器在内核模式下运行,当它返回到应用程序代码时,处理器将模式更改回用户模式。

内核为每个进程维护一个上下文。它由一些对象的值组成,包括通用寄存器、浮点寄存器、程序计数器、用户堆栈等。在进程执行的某个时刻,内核可以决定抢占当前进程并启动先前抢占的进程。这个决定被称为调度。在内核调度一个新进程运行之后,它会抢占当前进程,并使用一种称为上下文切换的机制将控制转移到新进程。

上下文切换:1.保存当前进程的上下文;2.恢复先前抢占进程的保存上下文;3.将控制转移到新恢复的流程。

在Hello中,当程序执行sleep函数时,sleep显式地请求调用进程睡眠。调度器抢占当前进程并通过上下文切换将其传输到新进程。sleep函数结束后,通过上下文切换返回Hello函数。

6.6 hello的异常与信号处理

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

hello程序出现的异常可能有:

中断:在程序执行的过程中可能会出现外部I/O设备引起的异常。

陷阱:陷阱是有意的异常,是执行一条指令的结果。

故障:在执行程序的时候,可能会发生缺页故障。

终止:终止时不可恢复的错误。

在发生异常时会发出信号,比如缺页故障会导致OS发生SIGSEGV信号给用户进程,而用户进程以段错误退出。

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

回车与Ctrl+Z

Ctrl+C

ps、jobs、pstree、fg、kill等

 

乱按

6.7本章小结

本章介绍了有关进程管理的多个概念。介绍了Shell的作用和处理流程,以及利用fork创建子进程、利用execve加载进程的方法。展示hello程序执行的具体过程,以及异常信号的处理机制。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

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

逻辑地址:

在有地址变换功能的计算机中,访问指令给出的地址 (操作数) 叫逻辑地址,也叫相对地址。要经过寻址方式的计算或变换才得到内存储器中的物理地址。

线性地址:

线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。

物理地址:

在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址,又叫实际地址或绝对地址。

虚拟地址:

程序访问存储器所使用的逻辑地址称为虚拟地址。

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

逻辑地址由段标识符和段内偏移量两部分组成。段标识符由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号,是对段描述符表的索引,每个段描述符由8个字节组成,具体描述了一个段。后3位包含一些硬件细节,表示具体是代码段寄存器还是栈段寄存器还是数据段寄存器等。通过段标识符的前13位,可以直接在段描述符表中索引到具体的段描述符。每个段描述符中包含一个Base字段,它描述了一个段的开始位置的线性地址。将Base字段和逻辑地址中的段内偏移量连接起来就得到转换后的线性地址。

对于全局的段描述符,放在全局段描述符表中,局部的(每个进程自己的)段描述符,放在局部段描述符表中。全局段描述符表的地址和大小存放在gdtr控制寄存器中,而局部段描述符表存放在ldtr寄存器中。

给定逻辑地址,看段选择符的最后一位是0还是1,用于判断选择全局段描述符表还是局部段描述符表。再根据相应寄存器,得到其地址和大小。通过段标识符的前13位,可以在相应段描述符表中索引到具体的段描述符,得到Base字段,和段内偏移量连接起来最终得到转换后的线性地址。

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

       n位的虚拟地址包含两个部分,一个p位的虚拟页面偏移(VPO)和一个(n-p)位的虚拟页号(VPN)。

VPN作为到页表中的索引。若对应页表项的有效位为1,则取出物理页号(PPN)与物理页偏移量(PPO,等于VPO)组合形成物理地址。否则缺页,触发页面调度。

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

如果按照上述模式,每次CPU产生一个虚拟地址并且发送给地址管理单元,MMU就必须查找一个PTE行来用将虚拟地址翻译成物理地址。为了消除这种操作带来的大量时间开销,MMU中被设计了一个关于PTE的小的缓存,称为翻译后备缓冲器(TLB)也叫快表。例如当每次cpu发现需要重新翻译一个虚拟地址时,它就必须发送一个vpn得到虚拟地址mmu,发送一个vpo位得到一个l1高速缓存.例如当我们使用mmu向一个tlb的组请求一个页表中的条目时,l1高速缓存通过一个vpo位在页表中查找一个相应的数据标记组,并在页表中读出这个组里的个数据标记和相应的数据关键字.当mmu从一个tlb的组得到一个ppn时,代表缓存的工作在这个组的请求之前已经完全准备好,这个组的ppn与就已经可以与这些数据标记文件中的一个虚拟地址进行很好的匹配了。

corei7采用四级页表层次结构,每个四级页表进程都有他自己私有的页表层次结构,这种设计方法从两个基本方面就是减少了对内存的需求,如果一级页表的pte全部为空,那么二级页表就不会继续存在,从而为进程节省了大量的内存,而且也只有一级页表才会有需要总是在一个内存中。四级页表的层次结构操作流程如下:36位虚拟地址被寄存器划分出来组成四个9位的片,每个片被寄存器用作到一个页表的偏移量。cr3寄存器内储存了一个l1页表的一个物理起始基地址,指向第一级页表的一个起始和最终位置,这个地址是页表上下文的一部分信息。vpn1提供了到一个l1pet的偏移量,这个pte寄存器包含一个l2页表的起始基地址.vpn2提供了到一个l2pte的偏移量,一共四级,逐级以此层次类推。

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

       cache结构如下

如果选中的组存在一行有效位为1,且标记位与地址中的标记位相匹配,我们就得到了一个缓存命中,否则就称为缓存不命中。如果缓存不命中,那么它需要从存储器层次结构的下一层中取出被请求的块,然后将新的块存储在组索引位指示组中的一个高速缓存行中,具体替换哪一行取决于替换策略,例如LRU策略会替换最后一次访问时间最久远的那一行。

7.6 hello进程fork时的内存映射

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

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,为每个进程保持了私有地址空间的抽象概念。同时延迟私有对象中的副本直到最后可能的时刻,充分利用了稀有的物理内存。

7.7 hello进程execve时的内存映射

       加载并运行a.out的过程中:

1. 删除已存在的用户区域。

2. 映射私有区域。代码和数据区域被映射为a.out文件中的.text和.data区。.bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为零。

3. 映射共享区域。如果a.out程序与共享对象(或目标)链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

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

在指令请求一个虚拟地址时,MMU中查找页表,如果对于的物理地址没有存在主存内部,以至于我们必须要从磁盘中读出数据,这就是缺页故障(中断)。

在发生缺页中断之后,系统会调用内核中的一个缺页处理程序,选择一个页面作为牺牲页面。具体的操作过程如下:

1. CPU生成一个虚拟地址,并把它传送给MMU.

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

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

4. PTE中的有效位是零,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序.

5. 缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘.

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

7. 缺页处理程序返回地址到原来的缺页处理进程,再次对主存执行一些可能导致缺页的处理指令,cpu,然后将返回地址重新再次发送给处理程序mmu.因为程序中虚拟的页面现在已经完全缓存在了物理的虚拟内存中,所以处理程序会再次命中,主存将所请求字符串的返回地址发送给虚拟内存的处理器。

7.9动态存储分配管理

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

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。对每个进程,内核维护一个变量brk,指向堆的顶部。分配器将堆视作一组不同大小的块的集合,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。分配器有两种基本风格:显式分配器和隐式分配器。显式分配器要求应用显式地释放任何已分配的块,隐式分配器要求分配器检测一个已分配块何时不再被程序所用,那么就释放这个块。隐式分配器又叫垃圾收集器,而自动释放未使用的已分配的块的过程叫做垃圾收集。

为实现动态内存分配器,可以使用隐式空闲链表。当一个应用请求k字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块。分配器执行这种搜索的方式是由放置策略确定的。一些常见的策略是首次适配、下一次适配和最佳适配。一旦分配器找到一个匹配的空闲块,就需要决定分配这个空闲块中多少空间。一个选择是用整个空闲块,但这样会造成内部碎片。如果匹配不太好,那么分配器会将这个空闲块分割,第一部分变成分配块,剩下的变成一个新的空闲块。利用边界标记,可以允许在常数时间内进行对前面块的合并。这种思想是在每个块的结尾添加一个脚部,其中脚部就是头部的一个副本。这样分配器就可以通过检查它的脚部,判断前面一个块的起止位置和状态,这个脚部总是在距离当前块开始位置一个字的距离。但是这种方法也存在潜在缺陷,就是在应用程序操作许多个小块时,会产生显著的内存开销。

7.10本章小结

本章主要介绍了hello的存储器的地址空间,介绍了四种地址空间的差别和地址的相互转换。同时介绍了hello的四级页表的虚拟地址空间到物理地址的转换。阐述了三级cashe的物理内存访问、进程 fork 时的内存映射、execve 时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

一个Linux文件就是一个m个字节的序列:

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

设备的模型化:文件

设备管理:unix io接口

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函数的函数体

int printf(const char *fmt, ...)

{

int i;

char buf[256];

  

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

     i = vsprintf(buf, fmt, arg);

     write(buf, i);

  

     return i;

    }

printf函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。printf中调用了两个函数,分别为vsprintf和write。

vsprintf函数根据格式串fmt,并结合args参数产生格式化之后的字符串结果保存在buf中,并返回结果字符串的长度。

write函数将buf中的i个字符写到终端,由于i保存的是结果字符串的长度,因此write将格式化后的字符串结果写到终端。

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

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

8.4 getchar的实现分析

getchar函数体

int getchar(void)

{

    static char buf[BUFSIZ];

    static char* bb=buf;

    static int n=0;

    if(n==0)

    {

        n=read(0,buf,BUFSIZ);

        bb=buf;

    }

    return (--n>=0)?(unsigned char)*bb++:EOF;

}

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

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

8.5本章小结

本章中简单的描述了linux的io的接口及其设备和管理模式,unixio的接口及其使用的函数,还有常见的printf函数和pritgetchar函数.

(第81分)

结论

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

hello.c: c程序源代码,是一个二进制文本文件,hello.c中的每个字符均用ascall编码表示。

hello.i:hello.c经过预处理阶段变为hello.i。

hello.s:hello.i经过编译阶段变为hello.s。

hello.o:hello.s经过汇编阶段变为hello.o。

hello:hello.o与可重定位目标文件和动态链接库链接成为可执行文件hello。

运行:在终端输入./hello 1190200707 张修语。

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

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

上下文切换:hello调用sleep函数之后进程陷入内核模式,处理休眠请求主动释放当前进程,内核进行上下文切换将当前进程的控制权交给其他进程,当sleep函数调用完成时,内核执行上下文切换将控制传递给当前进程。

动态申请内存:当hello程序执行printf函数是, 会调用 malloc 向动态内存分配器申请堆中的内存。

信号管理:当程序在运行的时候我们输入Ctrl+c,内核会发送SIGINT信号给进程并终止前台作业。当输入Ctrl+z时,内核会发送SIGTSTP信号给进程,并将前台作业停止挂起。

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

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

计算机系统的设计与人的思维方式有着很大的区别,特别是层序结构存储器,条理清晰层层深入,让我大受震撼,而汇编语言、数据表示、异常处理等内容,仅几个简单的数字或标记就能将复杂的算法与基层的计算机指令进行联系,还可以给操作系统报各种不同的错误,实在是精巧严谨。通过学习hello的程序人生,了解计算机这些底层结构,无疑对我自己以后的程序人生有着极大的启发和帮助。

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

附件

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

(附件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分)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值