HIT-CSAPP大作业-程序人生

计算机系统

大作业

题     目  程序人生-Hellos P2P  

专       业            计算机科学与技术            

学     号          1190200825              

班   级             1903003           

学       生           杨贤航       

指 导 教 师            郑贵滨         

计算机科学与技术学院

20216

摘  要

摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

本文介绍了hello在计算机系统中灿烂的一生。通过对从源文件hello.c经预处理、编译、汇编及链接成为可执行文件hello,再通过shell的动态链接执行hello进程的故事到进程被回收这个质朴却复杂的计算机系统运行过程,来对计算机系统的运行加以解释和说明。主要针对程序的编译过程、进程管理、存储管理、IO管理进行研究和探讨。

关键词:hello;计算机系统;预处理;编译;汇编;链接;进程管理;存储管理;I/O管理;

          

目  录

第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简介

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

 P2P:From Program to Process,即 从程序到过程。在 linux 中,我们的 hello.c 文件经过编译器 cpp 的预处理成为 hello.i、再经过编译器 ccl 的编译成为 hello.s、接着被汇编器 as 汇编成 hello.o、最终经过链接器 ld 的链接最终成为 可执行目标程序 hello,执行此文件,操作系统会为其 fork 产生子进程,再调 用 execve 函数加载进程。至此,P2P 结束。

020:From Zero-0 to Zero-0,shell 通过 execve 加载并执行 hello,映射虚 拟内存。先删除当前虚拟地址的数据结构并为 hello 文件创建新的区域结构。 进入程序入口后,程序开始载入物理内存,然后进入 main 函数执行目标代码, CPU 为运行的 hello 分配时间片执行逻辑控制流。当程序运行结束后,shell 父进程负责回收 hello 进程,内核删除相关数据结构。

1.2 环境与工具

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

软件:VMware

硬件:华为matebook d     win10 64位

开发调试工具:gcc gedit gdb

1.3 中间结果

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

文件名称

文件作用

hello.c

源文件

Hello.i

预处理后的文件

Hello.s

编译后的汇编文件

Hello.o

汇编后的可重定位文件

hello

链接后的可执行文件

Hello.elf

hello的elf文件

Hello.elf

hello.o的elf文件

1.4 本章小结

本章分析了 hello.c 的 P2P 和 020 的过程,列出了本次大作业的环境和工具,并且 大致阐明了任务过程中出现的中间结果及其作用。 


第2章 预处理

2.1 预处理的概念与作用

主要作用:

处理关于 “#” 的指令

【1】删除#define,展开所有宏定义。

【2】处理条件预编译 例如#if, #ifdef, #if, #elif,#endif

【3】处理“#include”预编译指令,将包含的“.h”文件插入对应位置。这可是递归进行的,文件内可能包含其他“.h”文件。

【4】删除所有注释。例如/**/,//。

【5】添加行号和文件标识符。用于显示调试信息:错误或警告的位置。

【6】保留#pragma编译器指令。(1)设定编译器状态,(2)指示编译器完成一些特定的动作。

2.2在Ubuntu下预处理的命令

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

操作步骤:gcc -E hello.c -o hello.i

2.3 Hello的预处理结果解析

查看源文件,hello.c一共有三个头文件,分别为#include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>,故在预处理过程中对这三个文件进行了读取插入操作

截取了hello.i文件末尾,与hello.c文件中main函数一致,印证了预处理过程之对头文件进行操作,对main函数这些函数符号不做改动。

2.4 本章小结

本章对预处理做了一个介绍,通过hello.c预处理到hello.i的过程,清楚明白地介绍了预处理过程程序的过程和目的。通过实例可以发现,预处理阶段对后续程序的编译汇编都有很大的作用。


第3章 编译

3.1 编译的概念与作用

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

       

主要作用:1.扫描(词法分析),2.语法分析,3.语义分析,4.源代码优化(中间语言生成),5.代码生成,目标代码优化。

【1】将源代码程序输入扫描器,将源代码的字符序列分割成一系列记号。

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

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

  动态语义:在运行时才能确定:例1除以3

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

【5】编译器后端主要包括:代码生成器:依赖于目标机器,依赖目标机器的不同字长,寄存器,数据类型等

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

3.2 在Ubuntu下编译的命令

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

应截图,展示编译过程!

 操作步骤:gcc -s hello.c -o hello.

3.3 Hello的编译结果解析

3.3.1声明

.file 声明源文件

.text 代码段

.section .rodata 以下是 rodata 节

.align 声明对指令或者数据的存放地址进行对齐的方式

.string: 字符串

.globl 声明一个全局变量

.type 用来指定是函数类型或是对象类型

其它声明:

.size 声明大小

.long 长整型数据

3.3.2 数据

1)局部变量:

hello.c文件,若argc!=3,则执行printf函数,传入参数 int argc,作为main函数的第一个参数传入,被储存在寄存器%edi之中。通过指令movl %edi, -20(%rbp)得出编译器将argc存储在栈上空间-20(%rbp)中。

通过.L3和.L4组成的循环中不难发现,-4(%rbp)中存放的值就是i,在.L2通过movl $0, -4(%rbp)赋初值0给i

  1. 立即数:
    其他整形数据的出现都是以立即数的形式出现的,直接硬编码在汇编代码中。例如$1,$3等

3)字符串

观察图,看到前几行中有指令

.LC0:

.string “\347\224\250\346\263\225: Hello \345\255\246\345\217\267 \345\247\223\345\220\215 \347\247\222\346\225\260\357\274\201”

.LC1:

.string “Hello %s %s\n”

其中在.LC0中的字符串被编码为UTF-8格式,一个汉字在UTF-8编码中占据3个字节。

第二个字符串为.LC1中的"Hello %s %s”编译时将这两个字符串保存在.roda中。

数组

分析发现,argv数组的起始地址-32(%rbp),在寻找数组中元素的时候,通过指针偏移的方式进行寻找,每个元素之间的指针偏移量为8,故通过对临时寄存器%rax加8来访问argv[1],加16访问argv[2],加24访问argv[3]。

3.3.3赋值

赋值操作只有一处,即在定义整型变量i时,通过指令movl $0, -4(%rbp)对储存在-4(%rbp)处的进行赋值,并将其赋值为0. mov赋值指令有四种,分别为movb、movw、movl、movq,本指令使用的时movl是由于i的数据类型为整型,属于Inetl数据类型中的双字,故代码后缀为l。

3.3.4sizeof

i为整型变量,故为4字节。
argc为整型变量,故为4字节。
*argv为指针型字符串,其中每个元素的sizeof为8字节。

3.3.5算术操作

如上图.L4最后一段,addl $1, -4(%rbp),i的值加一

3.3.6关系操作

如上图,.L3中cmpl $9, -4(%rbp),与hello.c中i<10对应。编译结果与C代码有出入,是编译器在编译的过程中对程序优化的结果

3.3.7数组/指针操作

分析发现,argv数组的起始地址-32(%rbp),在寻找数组中元素的时候,通过指针偏移的方式进行寻找,每个元素之间的指针偏移量为8,故通过对临时寄存器%rax加8来访问argv[1],加16访问argv[2],加24访问argv[3]。

3.3.8控制转移

.LFB6:

cmpl $4, -20(%rbp)

je .L2

结合3.3.6中关系操作的分析,这个控制转移指令等价于hello.c中的C代码

if(argc!=3){

printf(“用法: Hello 学号 姓名 !\n”);

exit(1);

}

中的if跳转指令,这个if跳转指令,设置了略条件码,通过判断ZF零标志得出的结果为0,判断是否跳转暖到.L2中,否则顺序执行下一条语句。

.L3:

cmpl $7, -4(%rbp)

jle .L4

结合3.3.5中关系操作的分析,这个控制转移指令等价于hello.c中的C代码

for(i=0;i<10;i++){

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

sleep(sleepsecs);

}

的for循环跳转指令,for循环指令通过比较i与10(或者9)的值,判断是否跳出循环,从而进入.L4的执行。

3.3.9函数操作

1函数是一种过程,假设过程P调用过程Q吗Q执行后返回到P,这些动作包含限免一个或多个机制:

1)传递控制:进行过程 Q 的时候,程序计数器必须设置为 Q 的代码的起始 地址,然后在返回时,要把程序计数器设置为 P 中调用 Q 后面那条指令的 地址。

2)传递数据:P 必须能够向 Q 提供一个或多个参数,Q 必须能够向 P 中返回 一个值。

3)分配和释放内存:在开始时,Q 可能需要为局部变量分配空间,而在返回前,又必须释放这些空间。

1.main函数:

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

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

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

2.printf函数:

参数传递:第一次 printf 将%rdi 设置为“Usage: Hello 学号 姓名! \n”字符串的首地址。第二次 printf 设置%rdi 为“Hello %s %s\n” 的首地址,设置%rsi 为 argv[1],%rdx 为 argv[2]。

函数调用:第一次 printf 因为只有一个字符串参数,所以 call puts@PLT;第二次 printf 使用 call printf@PLT。

3.exit函数:

参数传递:传入的参数为1,并将%edi 设置为 1,再执行退出命令

函数调用:if判断条件满足后并在if内容基本执行完成后被调用,对应于汇编 文件中语句call exit@PLT。

4.sleep函数:

参数传递:传入参数argv[3]

函数调用:for循环下被调用,对应于汇编文件中的call sleep@PLT。

5.getchar

函数调用:在main中被调用,对应于汇编文件中的call gethcar@PLT。

3.4 本章小结

本章内容,通过对文本文件hello.c的学习,再次强调了对汇编语言的掌握程度的重要性。

编译器将高级语言编译成汇编语言,在以上的分析过程中,详细的分析了编译器是怎么处理C语言的各个数据类型以及各类操作的,按照不同的数据类型和操作格式,解释了hello.c文件与hello.s文件间的映射关系。

第4章 汇编

4.1 汇编的概念与作用

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

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

4.2 在Ubuntu下汇编的命令

gcc -c hello.s -o hello.o

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。ELF头内的type、machine如下图所示

其中.test为程序代码,.data是初始化的全局变量,.bss是未初始化的全局变量,.rodata是只读数据节,.symtab是符号节,.strtab是字符串节。

4.4 Hello.o的结果解析

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

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

主要有以下不同:

1、在.s中跳转主要是跳转到名为L1之类的段,在反汇编中直接是跳转到具体的偏移地址。

2、.s的立即数使用的是十进制,而反汇编中使用的是十六进制。

3、call一个函数的时候.s文件中是call一个名字,而在反汇编中使用的是偏移地址,而且恰恰是下一条指令。主要是系统的库函数还没有被链接,所以默认为0。

机器语言的构成:机器可以识别机器语言,机器语言操作码和操作数组成。

与汇编语言的映射关系:机器语言与汇编语言是一一对应的映射关系。

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

4.5 本章小结

通过对汇编代码进行汇编,生成机器代码的可重定位文件,然后使用readelf进行阅读对ELF文件有了更深入的了解。再对反汇编文件和原汇编语言文件进行对比发现了机器代码和汇编代码的关系。

(第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

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

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

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

用readelf查看,程序入口,包括elf header的大小和程序头的大小,31个section header等。

这是hello的节头,可以看到大小、地址以及偏移量等。

下图为程序头

程序头表描述的是可执行文件中的节与虚拟空间的存储段之间的映射关系。 一个表项说明说明虚拟地址空间中一个连续的段或一个特殊的节。

 程序头表信息有若干个表项,其中若干个为可装入段(即 Type=LOAD) 一个可装入段是包括 ELF 头、程序头表、.init、.text 和.rodata 节,映射到虚拟 地址的一个区域,按一定大小对齐,具有只读/执行权限(Flg=RE),是只读代码段。

5.4 hello的虚拟地址空间。

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

在edb中打开hello,通过Data Dump查看hello程序的虚拟地址空间各段信息。 在 Memory Regions 选择 View in Dump 可以分别在 Data Dump 中看到只读内存段 和读写内存段的信息。 结合 5.3 中获得的指令与地址的关系表,就可以很轻易地查看。

5.5 链接的重定位过程分析

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

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

分析 hello 与 hello.o 的不同: 1.链接增加新的函数: 在 hello 中链接加入了在 hello.c 中用到的库函数,如 exit、printf、sleep、getchar 等函数。 2.增加的节: hello 中增加了.init 和.plt 节,和一些节中定义的函数。 3.函数调用: hello 中无 hello.o 中的重定位条目,并且跳转和函数调用的地址在 hello 中都 变成了虚拟内存地址。对于 hello.o 的反汇编代码,函数只有在链接之后才能 确定运行执行的地址,因此在.rela.text 节中为其添加了重定位条目。 4.地址访问: hello.o 中的相对偏移地址变成了 hello 中的虚拟内存地址。而 hello.o 文件中对 于某些地址的定位是不明确的,其地址也是在运行时确定的,因此访问也需要重 定位,在汇编成机器语言时,将操作数全部置为 0,并且添加重定位条目。

根据 hello 和 hello.o 的不同,分析出链接的过程为: 链接就是链接器(ld)将各个目标文件(各种.o 文件)组装在一起,文件中的 各个函数段按照一定规则累积在一起。

结合 hello.o 的重定位项目,分析 hello 中对其怎么重定位的: 反汇编见附件 objdump.txt 与objdump_o.txt。 重定位过程合并输入模块,并为每个符号分配运行时地址,主要有以下两步:
1.重定位节和符号的定义。 在这一步中,链接器将所有相同类型的节合并成同一类型的新聚合节。包括 hello.o 在内的所有可重定位目标文件中的.data 节全被合并成一个节——输出的可 执行目标文件 hello 中的.data 节。 然后,连接器将运行时的内存地址赋入新节,赋给输入模块定义的每个节, 以及赋给输入模块定义的每个符号。当这一步完成时,hello 中每条指令和变量都 有唯一的运行时内存地址了。 2.重定位节中的符号引用。链接器依赖于 hello.o 中的重定位条目,修改代码 节和数据节中对每个符号的引用,使得它们指向正确运行时的地址。

5.6 hello的执行流程

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

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

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

hello!_init

libc-2.27.so!_setjmp

 libc-2.27.so!_sigsetjmp

libc-2.27.so!__sigjmp_save

 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前后,这些项目的内容变化。要截图标识说明。分析 hello 程序的动态链接项目,通过 edb 调试,分析在 dl_init 前后,这些项 目的内容变化。要截图标识说明。 首先在节头部表中找到 GOT 的首地址——0x00403ff0,在 edb 中查看其变化

    图 5.7.1

    图 5.7.2 发现其从起始地址开始 16 字节全为 0,调用_dl_init 函数后,GOT 中内容就发 生了变化。
    计算机系统基础课程报告
  • 22 -

    图 5.7.3 其中,GOT[[2]]是动态链接器在 ld-linux.so 模块中的入口点,也就是对应地址。
    (因为 hello 程序需要调用由共享库定义的函数 printf 等。) 这就是动态链接的过程。

5.8 本章小结

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


6hello进程管理

6.1 进程的概念与作用

概念:进程是执行中程序的抽象

作用:1.每次运行程序时,shell 创建一新进程,在这个进程的上下文切换中运行这个 可执行目标文件。应用程序也能够创建新进程,并且在新进程的上下文中运行它 们自己的代码或其他应用程序。

       2.进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处 理器;一个私有的地址空间,如同程序独占内存系统。

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

shell是你(用户)和Linux(或者更准确的说,是你和Linux内核)之间的接口程序。你在提示符下输入的每个命令都由shell先解释然后传给Linux内核。

shell 是一个命令语言解释器(command-language interpreter)。拥有自己内建的 shell 命令集。此外,shell也能被系统中其他有效的Linux 实用程序和应用程序(utilities and application programs)所调用。

处理流程:

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量

(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令

(4)如果不是内部命令,调用fork( )创建新进程/子进程

(5)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。

(6)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait等待作业终止后返回。

(7)如果用户要求后台运行(如果命令末尾有&号),则shell返回;

6.3 Hello的fork进程创建过程

根据 shell 的处理流程,可以推断,输入命令执行 hello 后,父进程如果判断不 是内部指令,即会通过 fork 函数创建子进程。子进程与父进程近似,并得到一份 与父进程用户级虚拟空间相同且独立的副本——包括数据段、代码、共享库、堆 和用户栈。 父进程打开的文件,子进程也可读写。二者之间最大的不同或许在于 PID 的 不同。 Fork 函数只会被调用一次,但会返回两次,在父进程中,fork 返回子进程的 PID,在子进程中,fork 返回 0.

6.4 Hello的execve过程

以下格式自行编排,编辑时删除)execve 函数在加载并运行可执行目标文件 Hello,且带列表 argv 和环境变量列 表 envp。该函数的作用就是在当前进程的上下文中加载并运行一个新的程序。 只有当出现错误时,例如找不到 Hello 时,execve 才会返回到调用程序,这里 与一次调用两次返回的 fork 不同。 在 execve 加载了 Hello 之后,它调用启动代码。启动代码设置栈,并将控制 传递给新程序的主函数,该主函数有如下的原型: int main(int argc , char **argv , char *envp); 结合虚拟内存和内存映射过程,可以更详细地说明 exceve 函数实际上是如何 加载和执行程序 Hello: 1.删除已存在的用户区域(自父进程独立)。 2.映射私有区:为 Hello 的代码、数据、.bss 和栈区域创建新的区域结构,所 有这些区域都是私有的、写时才复制的。

6.5 Hello的进程执行

上下文就是内核重新启动一个被强占的进程所需的状态。
进程时间片是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间。

进程执行,就是我们的进程hello通过一个个上下文切换,用户态和核心态的转换。处理器虽然只能同时处理一个进程,但可以通过不断的上下文切换以及用户态与核心态的转换,给不同的进程分配不同的时间片,就可以形成好像多个进程在同时进行的错觉。

有了进程hello之后,在处理缓冲区的输入时,进入到了内核模式,然后中断后重新通过上下文的切换回到用户模式。

6.6 hello的异常与信号处理

异常的种类有:中断、陷阱、故障、终止。

对于hello来说,这四种异常都是有可能的。比如中断可能是来自处理器外部的I/O设备的信号的的结果。对于陷阱,hello的exit以及sleep都会发生。对于故障,比如缺页故障。对于终止,比如硬件错误,DRAM和SRAM被损坏时发生的奇偶校验错误。
会产生的信号很多

尝试:1、乱按。运行时的乱按并不会影响程序的运行,但因为程序中存在 getchar 函数,按到 回车键时,getchar 会读入回车符及之后字符串,导致在程序运行完成后,将这些 字符串作为 shell 的命令行输入。

Ctrl-Z

Ps:命令列出当前系统中的进

Pstree: pstree 与 ps 相似,但 pstree 命令是以树状图显示进程间的关系

Jobs:列出了当前 shell 环境中已启动的任务状态。

Fg:

Ps:.ctrl+z 与 ps 后,kill 输入 kill -9 PID,杀死进程。

  1. Ctrl-C。

运行时键入 Ctrl+c,将使得程序终止并被回收。

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

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

6.7本章小结

以下格式自行编排,编辑时删除)本章了解了 hello 进程的执行过程,主要是 hello 的创建、加载和终止,通过 键盘输入,对 hello 执行过程中产生信号和信号的处理过程有了更多的认识,对使 用 linux 调试运行程序也有了更多的新得。 

(第6章1分)


7hello的存储管理

7.1 hello的存储器地址空间

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

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

逻辑地址:逻辑地址即为程序产生的与段相关的偏移地址部分,是由一个段标识符加上一个指定段内相对地址的偏移量, 表示为 [段标识符:段内偏移量]。

结合hello程序,即为hello.o文件中的地址,就是相对偏移地址。

线性地址:逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。hello中的虚拟内存地址。

虚拟地址:一个带虚拟内存的系统中,CPU从一个有N=2^n个地址空间中生成虚拟地址。虚拟地址其实就是线性地址。CPU在寻址的时候,是按照虚拟地址来寻址。CPU通过生成一个虚拟地址(VA)来访问主存,这个虚拟地址被送到内存之前先转换为适当的物理地址。对应于hello程序即为hello可执行文件转化为objdump时所显示的地址。

物理地址:计算机系统贮存被组织成一个由M个连续字节大小的单元组成的数组,每字节都有一个唯一的地址,即物理地址。对于hello程序为当hello运行时通过MMU将虚拟内存地址映射到内存中的地址。

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

以下格式自行编排,编辑时删除逻辑地址空间:段地址:偏移地址

这是指把一个程序分成若干段进行存储,每一段都是一个逻辑实体。段式管 理的产生与程序的模块化有极大关系。段式管理通过段表进行,包括段号或段名、 段起点、装入位、段的长度等。 此外,段式管理还需要主存占用区域表、主存可用区域表。 在段式存储管理系统中,除了为每个段分配一个连续的分区便于找寻外,进 程中的各个段可以不连续地存放在内存的不同分区中。 之后在程序加载时,操作系统为所有段分配其所需内存,物理内存的管理采 用动态分区的管理方法,这能够利用碎片化的空间。 段式管理是不连续分配内存技术中的一种,最大特点在于非常直观,按程序 段、数据段等有明确逻辑含义的“段”来分配内存空间。克服了页式的、硬性的、 非逻辑划分给保护和共享与支态伸缩带来的不自然性。 段另一个好处就是可以充分实现共享和保护,便于动态申请内存,管理和使 用统一化,便于动态链接,其缺点是有碎片问题。

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

以下格式自行编排,编辑时删除)页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。

磁盘中的字节都有唯一的虚拟地址,然后虚拟内存被分割成虚拟也。虚拟页的大小P=2p字节。然后有个页表条目PTE的数组,虚拟地址空间中的每个页在页表中都有一个固定偏移量。

每个虚拟地址都映射到磁盘的一个页。然后页表中有效位为1的。可以映射到DRAM中,也就是物理内存。有效位为0,物理页号为null的就是说还未分配,物理页号不为空那就是没有存储在物理内存中。

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

以下格式自行编排,编辑时删除)如图所示为四级页表的VA到PA的转换。CR3控制寄存器指向第一级页表(L1)的起始位置。CR3的值是每个进程上下文的一部分,每次上下文切换时,C3的值都会被恢复。

36位VON被划分为了9位的片,每个片被用做到一个页表的偏移量。CR3寄存器包含L1也标的物理地址。VPN1提供到一个L1PET的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2PTE的偏移量,以此类推。

而VPO可以直接转化为VPO从而和PPN结合然后转化为物理地址。

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

I7处理器封装包括四个核、一个大的所有核共享的L3高速缓存,以及一个DDR3内存控制器。每个核包括一个层次结构的TLB、一个层次结构的数据和指令高速缓存,以及一组快速的点到点链路,这种链路基于quickpath技术,是为了让一个核和外部I/O桥直接通信。TLB是虚拟寻址的,是四路组相联的。每一级高速缓存都是类似的。

如图所示,一个地址前t位为标记,s位为组索引,b位为块偏移。然后就可以去高速缓存中寻找。如果命中则取出。如果不命中则需要逐级往下寻找并取出然后放在相应索引的组的一个行中。如果有空闲则直接放入,如果没有则需要寻找牺牲块,可以用LFU方法去寻找

7.6 hello进程fork时的内存映射

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

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的人一个后来进行写操作时,写时复制机制就回创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

7.7 hello进程execve时的内存映射

以下格式自行编排,编辑时删除)程序执行了execve调用:execve(“hello”,NULL,NULL);

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

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

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

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

下一次调度这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。

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

当指令引用一个虚拟地址,而与改地址相对应的物理页面不在内存中,因此必须从磁盘中取出时,就会发生故障。就像我们将在第九章中看到的那样,一个页面就是虚拟内存的一个连续的块(典型的是4KB)。缺页处理程序从磁盘加载适当的页面,然后将控制返回给引起故障的指令。当指令再次执行时,相应的物理页面已经驻留在内存中了,指令就可以没有故障地运行完成了。

7.9动态存储分配管理

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

动态内存分配器维护着堆,堆顶指针是brk。有两种风格,一种叫显式分配器,使用malloc和free等;一种叫隐式分配器,也叫垃圾收集器。

显式分配器必须要满足以下条件:1、处理任意请求序列;2、立即响应请求;3、只使用堆;4、对齐块;5、不修改已分配的块。在这些限制条件下,分配器试图实现吞吐率最大化和使用率最大化。吞吐率就是每个单位时间里完成的请求数。内存利用率可以用峰值利用率来衡量,也就是有效载荷占已堆当前当前大小的比值。

造成堆利用率的一个原因是碎片现象。碎片分为内部碎片和外部碎片。为了实现一个分配器,必须考虑:1、空闲块的组织;2、放置;3、分割;4、合并。
可以设计一个隐式空间链表

7.10本章小结

在本章中,对虚拟内存相关的知识进行了回顾,对 hello 程序在执行过程中对 存储空间的影响做了分析,并做节归纳了段式与页式管理的内容,对 TLB 与四级 页表支持下的 VA 到 PA 的变换,三级 Cache 支持下的物理内存访问等内容也做出 了讨论。最后还列出了动态存储分配管理的内容。

=
8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

 所有的 I/O 设备都被抽象为文件(例如网络、磁盘和终端),而所有的输入和 输出都被抽象为相应文件的读和写来完成,这种将设备映射为文件的方式,允许 Linux 内核引出一个简单的,低级的应用接口,成为 Unix I/O,这样做可以使所有 的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述 Unix IO 接口及其函数

8.2 简述Unix IO接口及其函数

将设备映射为文件的方式,允许 Unix 内核引出一个简单、低 级的应用接口。 1.打开文件,内核记录文件的信息 2.shell 会有三个打开的文件:标准输入,标准输出,标准错误。 3.改变文件位置 4.读写文件 5.关闭文件 Unix I/O 接口函数: open 函数:调用 open 函数打开或创建一个文件。 create 函数:创建一个文件,也可通过以特定参数使用 open 来实现。 close 函数:对读文件进行关闭。 Iseek 函数:为一个打开的文件设置其偏移量。 read 函数:从打开的文件中读数据到 buf。 write 函数:写入文件。 pread,prwrite 函数:主要用于解决文件共享问题。 dup 函数:复制一个现存的文件描述符。 syns 函数:用于解决延迟写问题,保证磁盘上实际文件系统和缓冲区高速缓 存中内容的一致性。

8.3 printf的实现分析

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

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

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

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

以下为可以查到的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程序按照格式fmt结合参数args生成格式化之后的字符串,并返回字串的长度。

然后让我们追踪下write:

write:

mov eax, _NR_write

mov ebx, [esp + 4]

mov ecx, [esp + 8]

int INT_VECTOR_SYS_CALL

这里是给几个寄存器传递了几个参数,然后一个int结束。将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址。

再来看看sys_call的实现:

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将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。

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

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

完成了printf。

8.4 getchar的实现分析

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

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

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。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;

}

当使用getchar时,程序发生陷阱的异常。当按键盘时会产生中断。

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

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

8.5本章小结

本章主要讲了 Linux 的 IO 设备管理方法、Unix IO 接口及其函数,分析了 printf 函数和 getchar 函数的实现。通过对这些知识点讲解,对系统的IO有了更深刻地理解。

(第8章1分)

结论

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

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

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

hello 所经历的过程: 1. IO 设备编写 hello,以文件的方式储存在主存中。 2.hello.c 被预处理 hello.i 文件

  1. hello.i 被编译为 hello.s 汇编文件

4.hello.s 被汇编成可重定位目标文件 hello.o

5.链接器将 hello.o 和外部文件链接成可执行文件 hello

6.在 shell 中键入命令后,通过 exceve、fork 加载并运行 hello

7.在一个时间片中,hello 程序以进程形式存在,有自己的 CPU 资源,顺序执 行逻辑控制流

8.hello 的虚拟地址通过 TLB 和页表翻译为物理地址

 9.三级 cache 支持下的 hello 物理地址访问

10.hello 在运行过程中会有异常和信号等

11.printf 会调用 malloc 通过动态内存分配器申请堆中的内存

 12.shell 父进程回收 hello 子进程,内核删除为 hello 创建的所有数据结构

感悟: 通过对计算机系统的深入理解,我理解了计算机的运行原理,理解了程序从预处理、编译、汇编、连接,到以进程存在,被回收的全过程。

在编写代码时, 逐渐能从计算机的底层语言思考问题,对程序进行优化,编写对编译器友好的代 码。
附件

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

文件名称

文件作用

hello.c

源文件

Hello.i

预处理后的文件

Hello.s

编译后的汇编文件

Hello.o

汇编后的可重定位文件

hello

链接后的可执行文件

Hello.elf

hello的elf文件

Hello.elf

hello.o的elf文件


参考文献

  1.   用 Hello's P2P 实例一文带你彻底看懂 linux 全过程,看不懂来打我! https://blog.csdn.net/JAck_chen0309/article/details/85488496
  2.  https://www.csdn.net/ csdn官网
  3. 教学群里发的PPT和文档
  4. ] LINUX 逻辑地址、线性地址、物理地址和虚拟地址 https://www.cnblogs.com/zengkefu/p/5452792.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值