程序人生-CSAPP大作业

目  录

第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.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:from program to process ,即从程序到进程Program是指编译出来的hello.c文件,process是指OS(进程管理)为通过编译系统(complication system)编译出的可执行文件hello在壳(shell)中利用fork函数创建子进程、调用execve函数来执行此文件等操作。

 

图1.1编译系统

在编译系统中,hello.c经过预处理器(cpp)预处理转换为hello.i,接着编译器(ccl)将hello.i翻译成文本文件hello.s,汇编器(as)将hello.s翻译成机器语言指令,并将这些指令打包的结果保存在目标文件hello.o中。

我们的hello程序调用了printf函数,printf存在于printf.o中,hello.o与printf.o通过链接的方式合并成可执行文件hello了。

    020;from Zero-0 to Zero-0,即程序从无到有再到无。hello程序是程序员编写的hello.c经编译系统处理而来的。在OS操作系统中,shell中的execve函数加载并执行行hello,为其分配虚拟内存映射到物理内存中。接下来开始运行hello文件,当我们输入字符串./hello后,shell程序将字符逐一读入寄存器,再把它放到内存中。然后shell接收到我们从键盘传递的相关信号后并执行一系列指令来加载hello文件,这些指令将hello目标文件中的代码和数据从磁盘复制到主存。随后处理器就开始执行hello的main程序中的机器语言指令。这些指令将字符串中的字节从主存复制到寄存器文件,再从寄存器文件复制到显示设备,最终显示在屏幕上。就这样,hello程序已经完成了它的使命,会被shell回收,hello的生命就此结束。

1.2 环境与工具

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

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

开发工具:Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc

1.3中间结果

hello.c :hello程序代码(文本文件)

hello.i :预处理产生的修改了的源程序(文本文件)

hello.s :编译产生的汇编文件(文本文件)

hello.o :可重定位的目标文件(二进制文件)

hello :可执行文件(二进制文件)

hello_rel.s :hello.o的反汇编文件

1.4 本章小结

本章简述了hello从生成到执行再到被回收的一生,并列出了本文要用到的hello相关文件。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理指的是预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。

我们运用预处理指令进行预处理,这样使程序易于修改,使源程序在不同的执行环境下能够进行恰当的编译。

2.2在Ubuntu下预处理的命令

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

                        

                                                    图2.1 预处理指令的结果

2.3 Hello的预处理结果解析

                         

                                                                图2.2 hello.c程序

    

                                                            图2.3 hello.i程序

hello.c程序只有短短的十八行,而hello.i程序竟超过了3000行,其中真正程序之前是将#引入系统库的内容读进程序文本中真正对应程序代码我们能在hello.i文件的末尾看到之前编写的hello代码。

2.4 本章小结

本章讲述了将hello.c翻译成可执行文件的第一步:预处理。预处理能够使程序易于修改,使源程序在不同的执行环境下能够进行恰当的编译,为接下来的翻译进程开辟了一条通路。

(第2章0.5分)


第3章 编译

3.1 编译的概念与作用

编译是编译器(ccl)将代码转换为汇编指令的过程。

注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序。编译的作用:编译而成的汇编语言程序中,每一条汇编语言都对应着低级机器语言指令。汇编语言为不同高级语言的不同编译器提供了通用的输出语言,这使得程序在各种情况下都能顺利翻译。       

3.2 在Ubuntu下编译的命令

命令:gcc -S hello.i -o hello.s 

                                                           图3.1 命令的结果

3.3 Hello的编译结果解析

3.3.1 汇编代码中以“.”为开头的指令

(即伪操作(Pseudo-operation))

图3.2 汇编代码中的伪指令

其含义如以下表格:

.file

声明源文件

.text

代码段

.global

声明一个全局可见的名字(可能是变量,也可以是函数名)

.align

对指令或数据的存放地址进行对齐

.type

用来指定一个符号的类型是函数类型或者是对象类型

.size

指定一个符号的大小

.section

定义内存段,汇编语言程序在其中定义元素

表3.1 汇编代码中的伪指令的含义

3.3.2 数据

3.3.2.1常量

      

图 3.3 常量的应用

直接用立即数表示,本图表示将%rax加16.

3.3.2.2变量

hello函数定义了局部变量int i

                                          

图3.3 hello.i的部分汇编代码

我们将代码与汇编代码结合,可知-4(%rbp)表示局部变量i,所以说明程序将局部变量i存放在栈中。

3.3.2.3 字符串

printf("用法: Hello 学号 姓名 秒数!\n");

printf("Hello %s %s\n",argv[1],argv[2]);

这两个printf函数中的格式字符串均被放到.rodata段,定义为.string类型,及字符串类型,如下是它们的汇编代码表示。

图3.4 printf函数内字符串的汇编代码表示

其中汉字用utf-8编码表示,每个汉字占3个字节。

3.3.2.4 数组

hello.c中含有数组argv[],汇编代码中对该数组的调用如图3.5所示:

                                

图3.5 汇编代码中对argv[]的调用(部分)

由汇编代码可推断-32(%rbp)为argv的首地址,因为数组存放指针类型数据,大小为8,所以argv[0]地址需要加8,然后通过(%rax)寻址argv[1]。同理argv[2]argv[3]类似。

3.3.3 赋值操作

                     

对.L2进行分析,可知.L2中第一句就是将0赋给i。赋值操作使用mov指令,根据数据大小使用不同的后缀,后面也会遇到这种情况。

指令

b

w

l

q

大小

1B

2B

3B

4B

表2 汇编指令中表示的数据大小

3.3.4 sizeof

对.L4中汇编代码进行分析,可以知道可推断-argv[0]地址加8,通过(%rax)寻址argv[1]。我们可知指针类型数据大小为8

3.3.5 算术操作

                                                   

框中进行的是subq操作,也就是减法操作。该减法操作是将栈扩充,为数组argv[]腾出寄存空间。

                                              

框中分别进行addq和leaq操作。

addq是加法操作,本框中的操作是将数组首地址加8来访问数组中的下一个元素argv[1]。

leaq是加载地址操作。框中.LC1(%rip), %rdi的含义是计算printf函数中格式串"用法: Hello 学号 姓名 秒数!\n"的地址,并传给%rdi

3.3.6 关系操作

                                            

框中进行的是“==”操作,对应的是源代码中的if(argc!=4)语句,将argc与4比较,若不等于4,则继续执行后面的打印字符串和退出操作。

                                    

框中进行的是“<=”操作,对应的是循环中的for(i=0;i<8;i++)语句,将i与7比较,如果i不大于7,则跳转至.L4继续执行循环内操作,否则跳出循环。

3.3.7 数组操作

对.L4部分代码进行分析:-32(%rbp)为argv的首地址,并将该地址寄存到%rax中,因为数组存放指针类型数据,大小为8,所以argv[0]地址需要加8,然后通过(%rax)寻址argv[1]并将argv[1]寄存到%rsi上。同理argv[0]地址加上偏移量16,可得到argv[2]的地址,并寄存在%rdx中。

3.3.8 控制转移

3.3.8.1 if 语句

                               

如上图:比较-20(%rbp)(即argc)与4是否相等,若相等则表明条件不满足跳到.L2执行之后的语句,若不相等则表明条件满足,继续执行if内的语句因此这段代码对应了源代码中的if(argc!=4)语句。

3.3.8.2 for循环

                                         

程序先在.L2中对循环变量i(即-4(%rbp))赋初值0,然后跳到.L3判断i是否满足循环条件,若满足则跳到.L4执行循环体,否则执行循环体之后的语句,在循环体.L4的最后每次对循环变量i执行加1操作。可知这些反汇编代码对应的是源代码中的“for(i=0;i<8;i++)”。

3.3.9 函数操作

3.3.9.1 参数传递

                                  

这几行代码对应源代码中的printf("用法: Hello 学号 姓名 秒数!\n")和exit(1)这两行语句。其中汇编代码将字符串内的内容传递到%rdi,并用printf函数进行传参,又将立即数1传递到%edi,并用exit函数进行传参。

3.3.9.2 函数调用

根据call指令,我们可以知道调用函数的情况,本程序中调用了puts、sleep、printf、getchar函数。

3.3.9.3 函数返回

                                      

从.L3汇编代码可以看到返回%eax,而%eax的值被赋为0,相当于源代码中的“return 0”语句。

3.4 本章小结

本章我们通过汇编的概念,将hello.i编译成hello.s。我们又分析了hello.s中的汇编代码,对其汇编语言里的数据类型,条件控制,数组,字符串等等在汇编语言中的操作进行了较为详细的解释。我们可以通过源代码与汇编代码相互对照,发现汇编代码的奥秘。

(第3章2分)


第4章 汇编

4.1 汇编的概念与作用

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

     作用:将汇编语言转换为机械语言,方便接下来的链接。

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

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

图4.1 命令的结果

4.3 可重定位目标elf格式

通过命令readelf -a hello.o可查看hello.o的elf信息。

4.3.1 elf头

                     

                                                                      图4.2 elf头

我们可以看出ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。

通过观察,我们可以获取以下信息:

文件类型:可重定位文件(REL)

机器类型:x86-64小端(Advanced Micro Devices X86-64)

入口地址:0x0,表明程序在虚拟地址0x0处开始运行。

ELF头大小(Size of this header):64bytes

节头部表的大小(Size of section headers):64bytes

节的个数(Number of section headers):14

                   

                                                                    图4.3 节头部表

节头表描述了每个节的名称、大小、类型、位置和偏移。

4.3.2 代码重定位节(.rela.text节)

     该列表体现了需要重定位条目的偏移量、重定位类型、符号值等信息。相关内容对应及作用如下:

offset

需要被修改的引用的字偏移

type

重定位类型告知链接器如何修改新的引用

symbol

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

              addend

有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整

表4.1 .rela.text节中的重定位信息解读

其中表中的类型为最基本的类型:R_X86_64_PC32和R_X86_64_32。其中R_X86_64_PC32采用相对寻址,R_X86_64_32采用绝对寻址的方式。

除了.rela.text节,还有.rela.eh.frame节。.rela.eh.frame列表信息与前者一致,这里不再赘述。

                                                       图4.4 .rela.eh.frame表

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

4.3.3 symtab节

                                                                图4.5 .symtab表

符号表,用来存放程序中定义和引用的函数和全局变量的信息。重定位需要引用的符号都在其中声明。

4.4 Hello.o的结果解析

使用objdump -d -r hello.o>hello_rel.s 得到hello.o的反汇编代码,如下图所示

 

 

图4.6 hello.o的反汇编代码

1.反汇编.o文件得到的汇编代码和汇编.i文件得到的汇编代码的不同之处:

(1)汇编中mov、push、sub等等的指令后都有表示操作数大小的后缀,比如q、l等,反汇编得到的代码中没有。

(2)汇编代码中有很多以“.”开头的伪指令,用来指导汇编器和链接器工作,反汇编得到的代码中没有。

(3)汇编代码中调用函数是用“call 函数名”来表示,而反汇编代码中用“call 数字”表示,且相对应的机器代码中有PC相对引用的占位符。

(4)汇编代码中跳转“jmp Lable”的形式,在反汇编代码中直接为“jmp地址”的形式。

2.机器语言的构成:

机器语言由二进制的机器指令序列集合构成,机器指令由操作码和操作数组成。

与汇编语言的映射关系:一条汇编语言对应一条机器指令,其中每条机器指令的长度不一定一致。(因为X86-64为CISC(复杂指令计算机),其指令编码长度可变(0-15个字节),与RISC(精简指令计算机)相对,其指令长度固定。

4.5 本章小结

本章汇编器将汇编指令翻译成了机械指令,.s文本文件变成了.o二进制文件,为接下来的链接做铺垫。我们详细分析了hello.o的elf头相关信息,并比较了hello.o与hello.i的汇编代码的区别。

(第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.1 链接命令及结果

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

5.3.1 elf头

我们输入readelf -h hello来获取hello的elf头。

                                                        图 5.2 hello文件的elf头

  hello的elf头和hello.o的elf头有许多异同点。

相同点:两者Magic、类别、版本、系统架构相同。

不同点:1.hello是可执行文件,hello.o是可重定位文件。

  1. hello的入口点地址不为0,hello.o的入口点地址为0。
  2. hello的节的个数为27大于hello.o的节的个数。
  3. hello的program header数目和大小均不为零,而后者为零。

5.3.2 节头

  我们输入命令readelf -S hello来获取节头信息。

图 5.3 hello文件的节头信息

5.3.3 可重定位段

我们输入命令readelf -r hello来获取可重定位段信息。

图 5.3 hello文件的可重定位段信息

5.3.3 .symtab节

我们输入命令readelf -s hello来获取symtab表信息。

图 5.4 hello文件的symtab表信息

   与hello.o的symtab表对比可知,hello的symtab表符号数量明显增大,说明经过链接之后引入了许多其他库函数的符号,并加入到symtab表中。

5.4 hello的虚拟地址空间

            

图 5.4 hello地址空间展示(部分)

hello虚拟地址空间的起始地址为0x401000,根据5.3中节头部表,我们可以找到对应的节的虚拟空间对应位置。例如我们可以根据.text节的地址0x4010f0,我们可在edb中查看该地址的相关信息。

图 5.5 hello地址空间对照实例  

5.5 链接的重定位过程分析

通过命令objdump -d -r hello,我们调出了hello的汇编代码。

hello与hello.o的不同点:

  1. hello.o中,main函数地址从0开始,hello.o中保存的是相对偏移地址;而在hello中main函数0x401125开始,即hello中保存虚拟内存地址。
  2. hello中多了.init节、.plt、.plt.sec和.fini段。.init节定义函数_init,用于程序的初始化代码,还有初始化程序执行环境;.plt段为程序执行时的动态链接;.fini段用于返回值。

3.在hello中加入了在hello.c中用到的函数,如exit、printf、sleep、getchar等函数,我们可以通过callq指令直白地了解hello具体调用了哪些函数。而hello中callq调用函数部分,只是简单地以main加上偏移值的地址呈现。

hello是如何重定位的:

①首先计算需要被重定位的位置

②然后链接器计算出运行时需要重定位的位置:

refaddr = ADDR(.text) + r.offset

③然后更新该位置

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

实例分析:以hello.o中main函数反汇编代码中sleep函数为例

                          

 

从中我们可以知道:

 r.offset=0x78

 r.symbol=sleep

 r.type=R_X86_64_PLT32

 r.addend=-4

由main地址可知ADDR(s)=ADDR(.text)=0x401125,

ADDR(r.symbol)=ADDR(sleep)=0x4010e0.

refaddr = ADDR(.text) + r.offset=0x40119d

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

我们得到对sleep的重定位引用的地址为3f与hello反汇编代码40119c中3f一致。

5.6 hello的执行流程

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

程序名称

 ld-2.32.so!_dl_start

 ld-2.32.so!_dl_init

 libc-2.32.so!__libc_start_main+0

hello!puts@plt

hello!exit@plt

hello!printf@plt

hello!sleep@plt

hello!getc@plt

hello!_start

ld-2.32.so!exit

5.7 Hello的动态链接分析

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

在程序中动态链接是通过延迟绑定来实现的,延迟绑定的实现依赖全局偏移量表GOT和过程连接表PLT实现。GOT是数据段的一部分,PLT是代码段的一部分。

PLT数组中每个条目时16字节,PTL[0]是一个特殊的条目,它跳转到动态链接器中。每个可被执行程序调用的库函数都有自己的PLT条目。PLT[1]调用_libc_start_main函数负责初始化。GOT数组中每个条目八个字节。GOT[0]和GOT[1]中包含动态链接器解析地址时会用的信息,GOT[2]包含动态链接在ld-linux.so模块的入口点。

 

通过5.3节的表头可知.got.plt的地址为0x403048.

5.8 本章小结

通过链接,hello程序得以链接上程序启动时真正需要的一系列工作内容,包括共享库等等。在链接后,hello程序能够正式运行,至此,从hello.c的文本文件,经过编译、汇编、链接,生成了可执行文件。

    

(第5章1分)


6hello进程管理

6.1 进程的概念与作用

     概念:进程是一个执行中程序的实例。

     作用:进程提供给应用程序关键抽象:

  1. 一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。
  2. 一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

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

     作用:连接了用户和内核,让用户能够更加高效、安全、低成本地使用内核。

     处理流程:1.读取用户输入的命令

               2.将用户输入的命令进行解析,若命令为内置命令,则立即执行

         3.shell通过fork创建子进程,通过execve函数加载可执行文件,shell自身则调用wait()函数来等待子进程完成

         4.shell还会接收用户输入的信号并执行相应操作

         5.shell会回收已执行完的进程

6.3 Hello的fork进程创建过程

 Shell运行hello时,父进程通过fork函数生成这个程序的进程。这个子进程几乎与父进程相同,子进程得到与父进程相同的虚拟地址空间(独立)的一个副本,包括代码,数据段,堆,共享库以及用户栈,并且和父进程共享文件。它们之间最大的不同是PID不同。Fork函数在子进程PID为0,父进程中PID不为0.

6.4 Hello的execve过程

当创建了一个新运行的子进程后,子进程调用execve函数在当前子进程的上下文中加载并运行hello程序。execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp。正常情况下execve调用一次且不返回。只有当出现错误时,例如找不到hello,execve才会返回到调用程序。

6.5 Hello的进程执行

进程上下文信息:内核为已经加载并运行的进程hello维持了一个上下文,上下文是内核重新启动一个被抢占的所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程的进程表,以及包含进程已打开文件信息的文件表。

上下文切换:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策叫做调度。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用上下文切换的机制来将控制转移到新的进程。这种切换可以出现在内核代表用户执行系统调用时、中断时等等。比如hello中对sleep函数的调用,就会使进程从用户模式切换到内核模式。在内核模式中对该进程进行适当的处理时,调度器就会执行上下文切换,重新开始另一个进程,直到原进程再次从内核模式切换到用户模式。用户模式下某些操作还会使进程切换到内核模式,以此类推。

6.6 hello的异常与信号处理

6.6.1 异常与信号的描述

1.异常的有关描述:

异常可以分为四类:中断、陷阱、故障和终止。

类别

      原因

    异步/同步

    返回行为

      中断

来自I/O设备的信号

      异步

总是返回到下一条指令

      陷阱

有意的异常

      同步

总是返回到下一条指令

      故障

潜在可恢复的错误

      同步

可能返回到当前指令

      终止

不可恢复的错误

      同步

不会返回

2.信号的有关描述:

信号允许进程和内核中断其他进程。每种信号类型都对应于某种系统事件。详细的Linux信号及处理方式见下表:

 

表 6-1 Linux信号表

6.6.2 键盘上各种操作导致的异常

  1. hello正常运行时:

        

 

      程序会输出8行所输入的字符串,此时进程已被回收。

  1. 使用Ctrl+Z

 Ctrl+Z会挂起前台进程,此时hello会被挂起,但是没有终止,更没有回收

使用ps指令看到该进程依然存在,使用jobs指令发现其作业序号不空,使用fg指令将上一个进程调度到前台,可以发现它能够继续执行。

3.使用Ctrl+C

输入Ctrl+C使hello终止并被回收使用ps指令看到该进程存在,使用jobs指令发现作业序号为,使用fg指令显示当前无此任务,可以发现它能够继续执行

    4.乱按

出现了很有趣的现象,乱按时字符串会进入缓冲区,在getchar时被读入。但一旦输入回车,就会在读入getchar之后当作命令行参数进行解读。

6.7本章小结

本章围绕着进程,讲述了shell的作用,并应用shell执行hello中的fork和execve函数。本章还对hello进行了异常和信号处理的相关分析。我们通过键盘向hello输入不同的指令,出现的效果也不同。Shell和信号处理真是神奇!

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:在计算机体系结构中是指应用程序角度看到的内存单元、存储单元、网络主机的地址,又叫相对地址。它是在网络层及以上使用的地址。

线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。逻辑地址加上基地址就是线性地址。

虚拟地址:虚拟地址则是操作系统对存储器和I/O设备的抽象,虚拟地址系统为每一个进程都提供一个看起来独立的地址空间,形成每一个进程都运行在独立的地址空间的假象。这样做能够大大方便内存管理、简化链接简化加载等。在hello中,我们看到的.text段的起始地址是0x4010f0,这是一个虚拟地址,需要在MMU中转化为物理地址。

物理地址: 指的是真实物理内存中地址,更具体一点就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。

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

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

逻辑地址由段标识符和段内偏移量两部分组成。段标识符由一个16位长的字段组成,称为段选择符而段内偏移量由一个32位长的字段组成。段标识符也称为段选择符。其组成为:

图7.1 逻辑地址结构图

Index(索引号):bit位3-15,指定相应段描述符的入口

TI(Table Indicator)标志:bit位2,指明段描述符在GDT(TI=1)还是在LDT(TI=0)。

RPL(请求者特权级):bit位0-1,当相应的段选择符装入到CS寄存器中时指示出CPU当前的特权级

我们可以通过索引号在段描述符表中找到一个具体的段描述符,根据TI内的值来确定段描述符的存储位置。其中,段描述符在GDT和LDT中,TI标志所指明的GDT和LDT表示的是该段描述符是在全局描述符表(GDT)还是局部描述符表(LDT)。每个段描述符都含有描述段开始位置的线性地址的Base段。要将逻辑地址转换为线性地址,首先通过索引号找到段描述符的位置,并通过其中的Base段找到段的基址基址加上逻辑地址中的段内偏移量就是线性地址。

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

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

Linux中线性地址可认为是虚拟地址。可使用页表来实现线性地址到物理地址的转换,如下图所示:

              

图7.2 使用页表实现线性地址到物理地址的转换

 由图可知,虚拟地址由虚拟页号(VPN)和虚拟页偏移量(VPO)组成。物理地址由物理页号(PPN)和物理页偏移量(PPO)组成。其中VPO和PPO相同。虚拟地址先通过VPN在页表中寻找相匹配的PPN,PPN和VPO串联起来就实现了从虚拟地址到物理地址的翻译了。

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

 

图7.3 Core i7中虚拟地址转换为物理地址的情况

图7.4使用四级页表实现线性地址到物理地址的转换

本节以Core i7为例进行TLB与四级页表支持下VA到PA的转换。VPN由TLBT(标记位)和TLBI(索引位)组成。VPN利用这两个部分先在L1的TLB中进行查询。若发生命中,则之后的步骤同7.3节;若不发生命中则调用四级页表进行图7.4的转换步骤。

36位VPN被划分为四个9位的片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN提供到一个L1 PTE的偏移量,这个PTE包含L2 页表的基地址。VPN 2提供到一个L2 PTE的偏移量,以此类推。最后在L4页表中对应的PTE中取出PPN,与VPO连接,形成物理地址PA。

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

本节继续引用7.4节提到的Core i7继续分析。

如图7.4所示,物理地址由标记位(CT)、索引位(CI)和偏移位(CO)组成。物理地址先访问L1 d-cache,通过调用物理地址的CT、CI和CO来查找L1 d-cache中是否有匹配项。若L1命中,则直接取出数据。若L1不命中,则调用L2、L3和主存,若还是不命中,则调用U盘,直到找到为止。

7.6 hello进程fork时的内存映射

当fork函数被当前进程调用时,内核为新进程创建了各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每一个区域结构都标记为私有的写时复制。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任意一个后来进行写操作时,写时复制机制就会创建新页面。

7.7 hello进程execve时的内存映射

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

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

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

(2)映射私有区域:为新程序hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为可执行文件的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的,初始长度为零。

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

(4) 设置程序计数器(PC):execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码的入口点。

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

缺页故障:DRAM缓存不命中。

图7.5缺页的处理

当触发缺页异常时,缺页处理程序确定出物理内存中的牺牲页,如果这个页面已经被修改了,则把它移出磁盘。接着,缺页处理程序页面调入新的页面,并更新内存中的PTE。随后缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页异常的虚拟地址重新发给MMU。因为虚拟页面现在缓存在物理内存中,所以就会发生命中,之后也不会再发生缺页异常了。

7.9动态存储分配管理

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

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

7.10本章小结

本章首先对四个地址概念做了介绍。然后对段页式管理做了简单介绍,用Core i7介绍了TLB与页表支持下虚拟地址到物理地址的转换和cache与虚拟内存的实际使用。通过虚拟内存,我们对fork和execve函数的映射关系有了全新理解,对于hello的诞生有了更清楚认识。本章也介绍了缺页故障与缺页中断处理。可见hello的诞生离不开虚拟地址到物理地址的转换。

(第7章 2分)


8hello的IO管理

8.1 Linux的IO设备管理方法

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

设备的模型化:文件

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

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

8.3 printf的实现分析

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

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

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

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

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

8.4 getchar的实现分析

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

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

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

8.5本章小结

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

(第8章1分)

结论

1.hello所经历的过程:

预处理:将.c文件处理成加入了部分信息的.i文件。

编译:编译器将.i文件翻译成由汇编语言描述的.s文件。

汇编:汇编器将.s文件转化成由机器代码构成的可重定位目标文件.o。

链接:通过符号解析、重定位和一系列静态库、动态库的链接,形成真正可以被加载的可执行文件。

运行:在shell中输入hello和命令行参数,shell将运行hello程序,并用fork函数建立一个子进程并在子进程中用execve函数执行 hello程序。

异常:在运行过程中,使用Ctrl+C/Ctrl+Z向进程发送信号,进程会执行信号处理程序,进行上下文切换,调用对应程序进行对信号的处理。

内存管理:在加载过程中,虚拟内存系统为hello提供了一套独立的虚拟内存空间。通过MMU可以将虚拟地址转化为物理地址,在保证各个进程互不干扰又“看似独立运行”的背景下完成任务

程序终止:在执行完最后一个函数后,程序退出,子进程终止。等待父进程对其进行回收,至此,hello的一生就此完结。

2.感想:hello是程序员编写出来第一个程序,代码看起来只有短短十来行。但通过学习计算机系统这门课程,我们就能发现简短的代码里大有学问。我从中明白了hello从出生到生命结束居然经历了这么多过程,并大受震憾。预处理、编译、汇编、链接等环节环环相扣,从而衍生出了通用的汇编语言、虚拟内存空间等内容,我敢说计算机系统的构造是人类智慧的结晶。

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


附件

hello.c

hello源代码

hello.i

预处理之后的文本文件

hello.s

hello的汇编代码

hello_rel.s

hello.o的反汇编代码

hello1.s

hello的反汇编代码

hello

hello的可执行文件

hello.o

hello的可重定位文件

hello.o.elf

hello.o的elf头相关文件

hello.elf

hello的elf头相关文件

 其中hello1.s、hello.o.elf和hello.elf为终端运行指令时粘贴在.txt文件的代码

(附件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] 大卫R.奥哈拉伦,兰德尔E.布莱恩特. 深入理解计算机系统[M]. 机械工业出版社,2016.

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值