当前位置: 首页 > 工具软件 > ics-openvpn > 使用案例 >

HIT-ICS-计算机系统大作业-程序人生

蒯宏达
2023-12-01

Github网址:https://github.com/sunshibo1234/HIT-ICS—

摘 要

本文主要阐述利用gcc、edb等工具,hello程序在Linux下经过预处理、编译、汇编、链接生成可执行文件的过程。并对hello在加载运行时的进程管理、信号处理、段式管理、页表管理、虚拟地址与物理地址、三级缓存Cache、动态存储分配管理、和I/O进行了系统分析。

关键词:hello、P2P、020、进程、内存管理、I/O;

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

目 录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1数据

3.3.2赋值

3.3.3类型转换

3.3.4算数操作

3.3.5Sizeof()

3.3.6关系操作

3.3.7数组/指针/结构操作

3.3.8控制转移

3.3.9函数操作

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

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

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

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

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

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

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

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献

第1章 概述
1.1 Hello简介
P2P:hello.c经过cpp的预处理生成hello.i、ccl的编译生成hello.s、as的汇编生成hello.o、ld的链接最终成为可执行目标程序hello,在shell中键入启动命令后,shell为其fork产生一个子进程,然后hello便从Program变为了Process。

020:shell调用execve在新的子进程中加载并运行hello,在hello运行的过程中,还需要CPU为hello分配内存、时间片,使得hello看似独享CPU资源。系统的进程管理帮助hello切换上下文、shell的信号处理程序使得hello在运行过程中可以处理各种信号,当程序员主动地按下Ctrl+Z或者hello运行到return 0时,hello所在进程将被杀死,shell会回收它的僵死进程。

1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上

软件环境:Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位

开发与调试工具:gcc,gedit,edb,readelf,HexEdit

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

文件名

作用

hello.i

Hello.c预处理之后的文件

hello.s

Hello.i编译后的汇编文件

hello.o

Hello.s汇编后的可重定位的目标文件

hello

链接之后的可执行目标文件

hello.out

hello的反汇编文件,用来看链接后的反汇编代码

1.4 本章小结
本章大致主要简单介绍了 hello 的 p2p,020 过程,列出了本次实验信息:环境、中间结果,并且大致简介了hello程序从c程序hello.c到可执行目标文件hello的大致经过的历程。

(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理的概念:

预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。将所引用的所有库展开,处理所有的条件编译,并执行所有的宏定义,得到另一个通常是以.i作为文件扩展名的C程序。

预处理阶段作用:

1.处理宏定义指令预处理器,根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外。

  1. 处理条件编译指令

条件编译指令如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

3.处理头文件包含指令头文件包含指令如#include "FileName"或者#include 等。 该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。

4.处理特殊符号

预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号,FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

2.2在Ubuntu下预处理的命令
命令:cpp hello.c > hello.i

2.3 Hello的预处理结果解析
经过cpp预处理之后,生成了hello.i文件,共3060行,预处理对原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。另外,如果代码中有#define命令还会对相应的符号进行替换。

声明函数、定义结构体、定义变量、定义宏等内容也被加进了hello.i中。

上图为main函数段。

2.4 本章小结
本章主要介绍了预处理的概念与作用,如实现将定义的宏进行符号替换、引入头文件的内容、处理条件编译指令等,并通过hello.i进行具体分析。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译的概念:编译器将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。其以高级程序设计语言书写的源程序作为输入,而以汇编语言或机器语言表示的目标程序作为输出。

编译的作用:

1.扫描(词法分析):将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号。

2.语法分析:基于词法分析得到的一系列记号,生成语法树。

3.语义分析:由语义分析器完成,指示判断是否合法,并不判断对错。又分静态语义:隐含浮点型到整形的转换,会报warning,

4.源代码优化(中间语言生成):中间代码(语言)使得编译器分为前端和后端,前端产生与机器(或环境)无关的中间代码,编译器的后端将中间代码转换为目标机器代码,目的:一个前端对多个后端,适应不同平台。

5.代码生成:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等。

目标代码优化:选择合适的寻址方式,左移右移代替乘除,删除多余指令。

3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s

3.3 Hello的编译结果解析

3.3.1数据

  1. 字符串

hello.c程序中用到的字符串有两个:“用法: Hello 学号 姓名 秒数!\n”和“Hello %s %s\n”。编译器将字符串存放在.rodata节,如下图所示:

第一个字符串中的汉字被编码成UTF-8格式,一个汉字占三个字节。第二个字符串中的两个%s为用户在终端运行hello时输入的两个参数。

  1. 整数

hello.c程序中的整型变量有argc和i。

其中argc是是main函数的第一个参数,即从终端传入的参数个数,所以由寄存器%edi进行保存。argc的调用如下图所示,argc被存储到了栈中-20(%rbp)中。

i是局部变量,编译器会将局部变量保存在寄存器或者栈中,hello.s将i存储在栈中-4(%rbp)的位置,如下图所示。

3.3.2赋值
hello.c程序中的赋值操作只有i=0这一条,int占4个字节,所以用movl进行数据传送,将立即数传送到栈中。

3.3.3类型转换
hello.c中涉及的类型转换是:atoi(argv[3]),将字符串类型转换为整数类型。

此外还有unsigned/char/int/long/float/double之间的转换。

3.3.4算数操作
hello.c程序中的算数操作只有i++,i是int类型的,所以汇编代码只用addl就能实现。

下图为一些其它算数和移位运算:

3.3.5Sizeof()
sizeof是C/C++中的一个操作符(operator),简单的说其作用就是返回一个对象或者类型所占的内存字节数。

3.3.6关系操作
C语言中的关系操作有==、!=、>、<、>=、<=,这些操作在汇编语言中主要依赖于cmp和test指令实现,cmp指令根据两个操作数之差来设置条件码。cmp指令与SUB指令的行为是一样,而test指令的行为与and指令一样,除了它们只设置条件码而不改变目的寄存器的值。

(1) argc!=4,通过cmp设置条件码寄存器的值,作为je的判断依据。

(2) i<8同理,通过cmp设置条件码寄存器的值,作为jle的判断依据。

3.3.7数组/指针/结构操作
hello.c程序中唯一的数组是作为main函数的第二个参数argv[],数组的每个元素都是一个指向字符类型的指针。数组的起始地址存放在栈中-32(%rbp)的位置,通过获得的字符指针寻找字符串。

如下图所示,在访问argv[]所指向的内容时,首先寻找到字符串数组的首地址(绿色框),然后根据所要调用的参数寻找到这个参数的地址(橙色框),最后用计算出来的地址寻找字符串(黄色框)。

3.3.8控制转移
hello.c程序涉及到的控制转移有两处。

(1)判断argc是否等于4,用cmpl比较argc和4并设置条件码后,通过判断ZF位是否为零决定是否跳转到.L2,如果为0,说明argc等于4,代码跳转到.L2继续执行,如果不为0,则继续往下执行指令。

(2)判断循环变量i是否满足循环条件i<8,在汇编代码中是与7进行比较,如果小于等于7则跳转到.L4执行循环体,否则退出循环,继续往下执行指令。

3.3.9函数操作
函数是一种过程,提供了一种封装代码的方式。P调用Q时有如下行为:

传递控制:开始执行Q的时候,PC必须设置为Q的代码的起始地址,而在返回时要把PC设置为P中调用Q之后一条语句的地址。

传递数据:P能够向Q传递任意个数的参数,Q能够向P返回0或1个值。

分配和释放内存:开始Q时为Q分配必要的空间,而在Q返回前需要把已分配给Q的空间释放。

Hello.c程序调用了6个函数:

(1)main

main函数是程序的主函数,被保存在.text节,程序运行时,由系统启动调用main函数,两个参数分别是argc和argv[],分别被保存在%rdi和%rsi中。

(2)printf函数

第一个printf函数由于只有一个参数,所以被编译器优化为puts函数,参数被保存在寄存器%rdi中。

第二个printf函数有3个参数,分别被保存在寄存器%rdi、%rsi、%rdx中。

(3)exit函数

(4)atoi函数

hello.c中通过atoi函数把用户输入的第4个参数从字符串转化成整型。

(5)sleep函数

(6)Getchar函数

3.4 本章小结
本章主要讲述了编译的概念与作用;编译阶段中编译器如何处理各种数据和操作;以及c语言中各种类型和操作所对应的的汇编代码,如数据、赋值、类型转换、sizeof、算数操作、逻辑操作、移位操作、关系操作、数组指针结构操作、控制转移、函数操作等。

(第3章2分)

第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编器as将.s文件翻译成机器指令,把这些指令打包成一中叫做可重定位目标程序格式,并将结果保存在目标文件中。

汇编的作用:将编译器产生的汇编语言进一步翻译为计算机可以理解的机器语言,生成.o文件。

4.2 在Ubuntu下汇编的命令
命令:as hello.s -o hello.o

4.3 可重定位目标elf格式
(1) ELF头:

命令:readelf -h hello.o

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

(2)Section Headers

命令:readelf -S hello.o

节头部表包含了文件中出现的各个节的描述,包括节的类型、位置、大小等信息。由于是可重定位目标文件,所以每个节都从0开始,用于重定位。在文件头中得到节头表的信息,然后再使用节头表中的字节偏移信息得到各节在文件中的起始位置,以及各节所占空间的大小。

(3).rel.text节

offset:需要被修改的引用节的偏移Info:包括symbol和type两个部分,symbol在前面四个字节,type在后面四个字节,

symbol:标识被修改引用应该指向的符号,

type:重定位的类型

attend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整Name:重定向到的目标的名称。

(4).symtab

存放程序中定义和引用的函数和全局变量的信息。name是符号名称,对于可重定位目标模块,value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type要么是数据要么是函数。bind字段表明符号是本地的还是全局的。

4.4 Hello.o的结果解析
objdump -d -r hello.o

将反汇编的代码与hello.s进行比较,发现二者相似,但是反汇编代码中还有机器代码。

机器语言:机器语言是计算机能直接理解的语言,完全由二进制数构成,为了阅读的方便显示成了16 进制。每两个16进制数构成一个字节编码,是机器语言中能解释一个运算符或操作数的最小单位。

机器语言由三种数据构成。一是操作码,它具体说明了操作的性质和功能,每一条指令都有一个相应的操作码,计算机通过识别该操作码来完成不同的操作;二是操作数的地址,CPU通过地址取得所需的操作数;三是操作结果的存储地址,把对操作数的处理所产生的结果保存在该地址中,以便再次使用。

(1)分支转移:反汇编的跳转指令用的不是段名称比如.L3,而是用的确定的地址,因为,因为段名称只是在汇编语言中便于编写的符号,所以在汇编成机器语言之后显然不存在,而是确定的地址。

(2)函数调用:在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程序中,call的目标地址是当前下一条指令。这是因为hello.c中调用的函数都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text节中为其添加重定位条目,等待静态链接的进一步确定。

4.5 本章小结
本章主要讲述了汇编的概念与作用,分析了可重定位文件elf的格式,使用objdump得到反汇编代码并与hello.s进行比较,分析从汇编语言映射到机器语言汇编器需要实现的转换。

(第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.3 可执行目标文件hello的格式
readelf -a -W hello

(1) ELF Header:hello的文件头和hello.o文件头的不同之处如下图标记所示,Type类型为EXEC表明hello是一个可执行目标文件,有25个节。

(2) 节头部表Section Headers:Section Headers 对 hello中所有的节信息进行了声明,其 中包括大小 Size 以及在程序中的偏移量 Offset,因此根据 Section Headers 中的信息我们就可以用 HexEdit 定位各个节所占的区间(起始位置,大小)。其中 Address 是程序被载入到虚拟地址的起始地址。

(3) 重定位节.rela.text:

(4) 符号表

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

(1)在Data Dump中查看地址0x400000开始的内容,可以看到开头是ELF头部分。

(2)查看地址0x0x4002e0是.interp节,保存着linux动态共享库的路径。

(3)查看地址0x402000,发现是.rodata节,保存着hello.c中的两个字符串。

(4)查看地址0x404048,发现是.data节。

5.5 链接的重定位过程分析
objdump -d -r hello

通过分析hello与hello.o的不同,说明链接的过程。

(1)hello.o反汇编代码中代码的虚拟地址均为0,未完成可重定位的过程;而hello反汇编的代码有确定的虚拟地址,也就是说已经完成了重定位

(2)hello反汇编的代码中多了很多的节以及很多函数的汇编代码

hello重定位的过程:

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

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

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

(4)重定位过程

refptr=s+r.offset

refaddr=ADDR(s)+r.offset=ADDR(main)+r.offset=0x4010c1+0x1c=0x4010dd

*refptr=(unsigned)(ADDR(r.symbol)+r.addend-refaddr)

=ADDR(str1)+r.addend-refaddr

=0x401fa0-0x4010dd=(unsigned)0xec3

5.6 hello的执行流程
子程序名:

ld-2.27.so!_dl_start

ld-2.27.so!_dl_init

hello!_start

libc-2.27.so!__libc_start_main

libc-2.27.so!__cxa_atexit

libc-2.27.so!__libc_csu_init

libc-2.27.so!_setjmp

hello!main

hello!puts@plt

hello!exit@plt

hello!printf@plt

hello!sleep@plt

hello!getchar@plt

ld-2.27.so!_dl_runtime_resolve_xsave

ld-2.27.so!_dl_fixup

ld-2.27.so!_dl_lookup_symbol_x

libc-2.27.so!exit

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

对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。

PLT:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。

GOT:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。

GOT起始表位置为0x404000

调用dl_init后GOT的数据发生改变,出现了两个地址0x7f28182b0190和0x7f2818299bb0。这就是GOT[1]和GOT[2]。

GOT[1] 0x7f28182b0190指向重定位表

GOT[2] 0x7f2818299bb0 指向的目标程序是动态链接器ld-linux.so运行时地址。

5.8 本章小结
在本章中主要介绍了链接的概念与作用,并且讲述了hello.o是怎么链接成为一个可执行目标文件的过程,介绍了hello.o的ELF格式和各个节的含义,并且分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。

(第5章1分)

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

作用:进程为用户提供了以下假象:

(1) 我们的程序好像是系统中当前运行的唯一程序一样,我们的程序好像是独占的使用处理器和内存。

(2) 处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。

6.2 简述壳Shell-bash的作用与处理流程
1.概念:

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

2.功能:

其实shell 也是一支程序,它由输入设备读取命令,再将其转为计算机可以了解的机械码,然后执行它。各种操作系统都有它自己的 shell,以 DOS 为例,它的 shell 就是 command.com文件。如同 DOS 下有 NDOS,4DOS,DRDOS 等不同的命令解译程序可以取代标准的command.com ,UNIX 下除了 Bourne shell(/bin/sh) 外还有 C shell(/bin/csh)、Korn shell(/bin/ksh)、Bourne again shell(/bin/bash)、Tenex C shell(tcsh)等其它的 shell。UNIX/linux将 shell 独立于核心程序之外, 使得它就如同一般的应用程序, 可以在不影响操作系统本身的情况下进行修改、更新版本或是添加新的功能。

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

3.处理流程:

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

6.3 Hello的fork进程创建过程
首先,打开终端输入:./hello 1190201125 孙士博 1

接下来shell会分析这条命令,由于./hello不是一条内置的命令,于是判断./hello的语义是执行当前目录下的可执行目标文件hello,然后Terminal会调用fork床架一个新的运行的子进程,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,这就意味着,当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程与子进程之间的区别在于它们拥有不同的PID。

6.4 Hello的execve过程
当创建了一个子进程之后,子进程调用exceve函数在当前子进程的上下文加载并运行一个新的程序即hello程序,加载并运行需要以下几个步骤:

(1)删除已存在的用户区域。删除当前进程虚拟地址的用户部分中已存在的区域结构。

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

(3)映射共享区域。如果hello程序与共享对象链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域。

(4)设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。下一次调用这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

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

1.并发流:一个逻辑流的执行时间与另一个流重叠,成为并发流,这两个流成为并发的运行。多个流并发的执行的一般现象成为并发。

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

3.私有地址空间:进程为每个流都提供一种假象,好像它是独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个内存字节是不能被其他进程读或者写的,在这个意义上,这个地址空间是私有的。

4.操作系统内核使用—种称为上下文切换(context switch)的较高层形式的异常控制流来实现多任务。。内核为每个进程维持一个上下文(context)。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空 间的 页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度(scheduling),是由内核中称为调度 器(scheduler)的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了_个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换 1)保存当前进程的上下文,2)恢复某个先前被抢占的进程被保存的上下文,3)将控制传递给这个新恢复的进程。

hello在刚开始运行时内核为其保存一个上下文,进程在用户模式下运行,当没有异常或中断信号的产生,hello将一直正常地执行,而当出现异常或系统中断时,内核将启用调度器休眠当前进程,并在内核模式中完成上下文切换,将控制传递给其他进程。

当程序在执行sleep函数时,系统调用显式地请求让调用进程休眠,调度器抢占当前进程,并发生上下文切换,将控制转移到新的进程,此时计时器开始,当计时器达到传入的第四个参数大小(这里是1s)时,产生一个中断信号,中断当前正在进行的进程,进行上下文切换恢复hello的上下文信息,控制会转移到hello进程中。当循环结束后,程序调用getchar函数用getchar时,由用户模式进入内核模式,内核中的陷阱处理程序请求来自键盘缓冲区的信号传输,并执行上下文切换把控制转移给其他进程。数据传输结束之后,引发一个中断信号,控制回到hello进程中,执行return,进程终止。

6.6 hello的异常与信号处理
hello执行过程中可能出现四类异常:中断、陷阱、故障和终止。

(1)中断是来自I/O设备的信号,异步发生,中断处理程序对其进行处理,返回后继续执行调用前待执行的下一条代码,就像没有发生过中断。

(2)陷阱是有意的异常,是执行一条指令的结果,调用后也会返回到下一条指令,用来调用内核的服务进行操作。帮助程序从用户模式切换到内核模式。

(3)故障是由错误情况引起的,它可能能够被故障处理程序修正。如果修正成功,则将控制返回到引起故障的指令,否则将终止程序。

(4)终止是不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程序会将控制返回给一个abort例程,该例程会终止这个应用程序。

(1)正常运行

(2)按下 ctrl-z

输入ctrl-z会挂起前台的作业,但是hello进程并没有回收,而是运行在后台下,用ps命令可以看到,hello进程并没有被回收。此时他的后台 job号是 1,调用命令 fg 1 将其调到前台,此时shell程序首先打印hello的命令行命令,hello继续运行打印剩下的8条info,之后输入字串,程序结束,同时进程被回收。

(3)按下Ctrl+C

在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程组的每个进程,默认情况是终止前台作业。

(4)乱按

在程序运行过程中不停乱按,发现乱按只是将屏幕的输入缓存到stdin,当getchar 的时候读出一个’\n’结尾的字串(作为一次输入),其他字串会当做shell命令行输入。

6.7本章小结
本章主要介绍了进程的概念与作用,讲述了shell的作用和处理流程,描述了hello的fork进程的创建过程和execve的过程,最后分析了hello的执行过程和过程中出现的异常的处理,并对于每一种情况做了测试。

(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

逻辑地址:在有地址变换功能的计算机中,访内指令给出的地址(操作数)叫逻辑地址,也叫相对地址。分为两个部分,一个部分为段基址,另一个部分为段偏移量。

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

虚拟地址:CPU启动保护模式后,程序运行在虚拟地址空间中。与物理地址相似,虚拟内存被组织为一个存放在磁盘上的N个连续的字节大小的单元组成的数组,其每个字节对应的地址成为虚拟地址。虚拟地址包括VPO(虚拟页面偏移量)、VPN(虚拟页号)、TLBI(TLB索引)、TLBT(TLB标记)。

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

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

段选择符:

TI=0,选择全局描述符表(GDT),TI=1,选择局部描述符表(LDT)。

RPL=00,为第0级,位于最高级的内核态,RPL=11,为第3级,位于最低级的用户态,第0级高于第3级。

高13位-8K个索引用来确定当前使用的段描述符在描述符表中的位置。

段描述符和段描述符表

段描述符是一种数据结构,实际上就是段表项,分两类:

用户的代码段和数据段描述符

系统控制段描述符,又分两种:

特殊系统控制段描述符,包括:局部描述符表(LDT)描述符和任务状态段(TSS)描述符

控制转移类描述符,包括:调用门描述符、任务门描述符、中断门描述符和陷阱门描述符

描述符表实际上就是段表,由段描述符(段表项)组成。有三种类型

全局描述符表GDT:只有一个,用来存放系统内每个任务都可能访问的描述符,例如,内核代码段、内核数据段、用户代码段、用户数据段以及TSS(任务状态段)等都属于GDT中描述的段

局部描述符表LDT:存放某任务(即用户进程)专用的描述符

中断描述符表IDT:包含256个中断门、陷阱门和任务门描述符

段式管理:

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

页表将虚拟内存映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时,都会读取页表。页表是一个页表条目(PTE)的数组。虚拟地址空间的每个页在页表中一个固定偏移量处都有一个PTE。假设每个PTE是由一个有效位和一个n位地址字段组成的。有效位表明了该虚拟页当前是否被缓存在DRAM中。如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么一个空地址表示这个虚拟页还未被分配。否则,这个地址就指向该虚拟页在磁盘上的起始位置。

2.页式管理地址变换

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

MMU利用VPN来选择适当的PTE,将列表条目中PPN和虚拟地址中的VPO串联起来,就得到相应的物理地址。

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

正如我们看到的,每次CPU产生一个虚拟地址,MMU就必须查阅一个PTE,以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会要求从内存多取一次数据,代价是几十到几百个周期。如果 PTE 碰巧缓存在L1中,那么开销就下降到1 个或 2 个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU 中包括了一个关于PTE的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer,TLB)。

为了减少页表太大而造成的空间损失,可以使用多级页表的技术。

Core i7使用的是四级页表。在四级页表层次结构的地址翻译中,虚拟地址被划分为4个VPN和1个VPO。每个第i个VPN都是一个到第i级页表的索引,第j级页表中的每个PTE都指向第j+1级某个页表的基址,第四级页表中的每个PTE包含某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问四个PTE。

7.5 三级Cache支持下的物理内存访问
(1) 组选择取出虚拟地址的组索引位,将二进制组索引转化为一个无符号整数,找到相应的组。

(3) 行匹配把虚拟地址的标记为拿去和相应的组中所有行的标记位进行比较,当虚拟地址的标记位和高速缓存行的标记位匹配时,而且高速缓存行的有效位是1,则高速缓存命中。

(4) 字选择一旦高速缓存命中,我们就知道我们要找的字节在这个块的某个地方。因此块偏移位提供了第一个字节的偏移。把这个字节的内容取出返回给CPU即可。

(4)不命中如果高速缓存不命中,那么需要从存储层次结构中的下一层取出被请求的块,然后将新的块存储在组索引位所指示的组中的一个高速缓存行中。一种简单的 放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生冲突不命中,则采用最近最少使用策略 LFU 进行替换。

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

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 缺页故障与缺页中断处理
缺页故障:当指令引用一个相应的虚拟地址,而与改地址相应的物理页面不再内存中,会触发缺页故障。通过查询页表PTE可以知道虚拟页在磁盘的位置。缺页处理程序从指定的位置加载页面 到物理内存中,并更新PTE。然后控制返回给引起缺页故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中,因此指令可以没有故障的运行完成。

7.9动态存储分配管理
动态内存分配器维护者一个进程的虚拟内存区域,成为堆。分配器将堆视为一组不同的大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。分配器有两种基本风格。两种风格都是要求显示的释放分配块。

(1) 显式分配器:要求应用显示的释放任何已分配的块。例如C标准库提供一个叫做malloc程序包的显示分配器。

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

1.Applications 应用

可以处理任意的分配( malloc)和释放(free)请求序列

只能释放已分配的块

2.Allocators 分配器

无法控制分配块的数量或大小

立即响应 malloc 请求

比如, 不允许分配器重新排列或者缓冲请求

必须从空闲内存分配块

必须对齐块,使得它们可以保护任何类型的数据对象

8字节 (x86) or 16字节 (x86-64) 对齐在 Linux 上框

只能操作或改变空闲块

一旦块被分配,就不允许修改或移动它了

比如, 压缩已分配块的技术是不允许使用的

3.隐式空闲链表

一个块是由一个字的头部、有效载荷,以及可能的填充组成。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。块的头最后一位指明这个块是已分配的还是空闲的。

放置已分配的块当一个应用请求一个k字节的块时,分配器搜索空闲链表。查找一个足够大可以放置所请求的空闲块。分配器搜索方式的常见策略是首次适配、下一次适配和最佳适配。

分割空闲块一旦分配器找到一个匹配的空闲块,就必须做一个另策决定,那就是分配这个块多少空间。分配器通常将空闲块分割为两部分。第一部分变为了已分配块,第二部分变为了空闲块。

边界标记:在空闲块的“底部”标记大小/已分配,允许我们反查 “链表” ,但这需要额外的空间。

合并空闲块合并的情况一共分为四种:前空后不空,前不空后空,前后都空,前后都不空。对于四种情况分别进行空闲块合并,我们只需要通过改变头部的信息就能完成合并空闲块。

4.显式空闲链表

释放:

(1)LIFO (last-in-first-out) policy后进先出法

将新释放的块放置在链表的开始处

优点:简单,常数时间

缺点:研究表明碎片比地址排序更糟糕

(2)Address-ordered policy地址顺序法

按照地址顺序维护链表:
addr(祖先) < addr(当前回收块) < addr(后继)

缺点:需要搜索

优点:研究表明碎片要少于LIFO (后进先出法)

7.10本章小结
本章主要介绍了hello的存储地址空间、Intel的段式管理、hello的页式管理,以及TLB与四级页表支持下的虚拟地址到物理地址的变换过程和三级Cache支持下的物理内存访问;还分析了hello进程fork和execve时的内存映射、缺页故障的处理流程,还讲述了动态存储分配器的管理,包括隐式空闲链表和显式空闲链表。

(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
一个 Linux文件就是一个m字节的序列:

B0 , B1 , … , Bk , … , Bm-1

所有的I/O设备都被模型化为文件,而所有的输入和输出都被当作相应文件的读和写来完成,这种将设备优雅的映射为文件的方式,允许Linux内核引出一个简单的,低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数
1.打开文件

打开文件是通知内核你准备好访问该文件,返回一个小的描述符数字——文件描述符。返回的描述符总是在进程中当前没有打开的最小描述符。如果fd=-1说明发生错误

Linux内核创建的每个进程都以与一个终端相关联的三个打开的文件开始:

0: 标准输入 (stdin)

1: 标准输出 (stdout)

2: 标准错误 (stderr)

2.关闭文件

关闭文件是通知内核你要结束访问一个文件。关闭一个已经关闭的文件是导致线程程序灾难的一个因素,因此需要总是检查返回码,即使是看似良性的函数,比如close()。

3.改变当前文件的位置

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

4.读写文件

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

Unix I/O函数

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

(2)intclose(int fd);关闭一个打开的文件,返回操作结果。

(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的实现,首先来看看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函数首先arg获得第二个不定长参数,即输出的时候格式化串对应的值。

int vsprintf(char *buf, const char *fmt, va_list args)

{

char* p;

char tmp[256];

va_list p_next_arg = args;

for (p=buf;*fmt;fmt++) {

if (*fmt != ‘%’) {

*p++ = *fmt;

continue;

}

fmt++;

switch (*fmt) {

  case 'x':

 itoa(tmp, *((int*)p_next_arg));

 strcpy(p, tmp);

 p_next_arg += 4;

 p += strlen(tmp);

 break;

 case 's':

 break;

 default:

 break;

 }

}

return (p - buf);

}

vsprintf程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。在printf中调用系统函数write(buf,i)将长度为i的buf输出。

write:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL

在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,int INT_VECTOR_SYS_CALLA代表通过系统调用syscall。

sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret

syscall将字符串中的字节“Hello 1190201125 孙士博”从寄存器中通过总线复制到显卡的显存中.字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

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

https://www.cnblogs.com/pianist/p/3315801.html

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设备管理方法、Unix IO接口及其函数,分析了printf函数和getchar函数的实现。

(第8章1分)

结论
hello的经历:

(1)预处理,经过预处理器cpp的预处理,得到hello.i

(2)编译,编译器ccl将得到的hello.i编译成汇编文件hello.s

(3)汇编,汇编器as又将hello.s翻译成机器语言指令得到可重定位目标文件hello.o

(4)链接,链接器ld将hello.o与动态链接库链接生成可执行目标文件hello,至此,hello成为了一个可以运行的程序。

(5)运行,在shell中输入./hello 1190201125 孙士博,

(6)创建子进程,shell进程调用fork为其创建子进程

(7)加载,shell调用execve,execve调用loader,加映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。

(8)执行,CPU为其分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的控制逻辑流

(9)访问内存,当CPU访问hello时,将虚拟地址传给MMU,MMU通过查找TLB或主存把虚拟地址转换成物理地址并通过三级cache访存。

(10)动态申请内存,printf会调用malloc向动态内存分配器申请堆中的内存。

(11)信号与异常,hello运行过程中可能遇到各种信号与异常,shell为其提供了各种信号处理程序。

(12)结束,shell父进程回收子进程,hello结束了它的一生。

计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们依据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始时是ASCⅡ文本,然后被编译器和链接器翻译成二进制可执行文件。

处理器读取并解释存放在主存里的二进制指令。因为计算机花费了大量的时间在内存、I/O设备和 CPU寄存器之间复制数据,所以将系统中的存储设备划分成层次结构一CPU寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM主存和磁盘存储器。在层次模型中,位于更高层的存储设备比低层的存储设备要更快,单位比特造价也更高。层次结构中较高层次的存储设备可以作为较低层次设备的高速缓存。

操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象: 1)文件是对Ⅰ/O设备的抽象; 2)虚拟内存是对主存和磁盘的抽象; 3)进程是处理器、主存和I/O设备的抽象。

通过对hello.c的一步步分析,hello完成从了从Program到Process的转化,即使是一个很简单的程序,也需要很多步骤,需要很多函数、信号、缓存、I/O等协调配合,让我更加深入的理解了计算机系统。

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

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

文件名

作用

hello.i

Hello.c预处理之后的文件

hello.s

Hello.i编译后的汇编文件

hello.o

Hello.s汇编后的可重定位的目标文件

hello

链接之后的可执行目标文件

hello.out

hello的反汇编文件,用来看链接后的反汇编代码

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

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

[1] 兰德尔 E.布莱恩特. 深入理解计算机系统. 龚奕利 译.

[2] printf 函数实现的深入剖析 https://www.cnblogs.com/pianist/p/3315801.html

[3] https://blog.csdn.net/hulifangjiayou/article/details/40480467

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

 类似资料: