CS大作业:hello的一生

本文深入剖析了Hello程序从编写到运行的全过程,包括预处理、编译、汇编、链接等多个阶段,揭示了计算机系统如何协同工作使程序得以执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘 要:
一个Hello程序,看似简单,可是为了让它完成运行,需要系统的各个组成部分协调工作,本文将通过跟踪Hello 解释了当在系统上运行Hello程序时,系统发生的编译、链接、加载、进程管理、存储管理等过程,来揭秘程序运行的奥秘。

关键词:计算机系统,p2p,o2o;

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

目 录

第1章 概述… 5

1.1 Hello简介… 5

1.2 环境与工具… 6

1.3 中间结果… 6

1.4 本章小结… 6

第2章 预处理… 7

2.1 预处理的概念与作用… 7

2.2在Ubuntu下预处理的命令… 7

2.1预处理命令… 7

2.3 Hello的预处理结果解析… 7

2.4 本章小结… 9

第3章 编译… 10

3.1 编译的概念与作用… 10

3.2 在Ubuntu下编译的命令… 10

3.3 Hello的编译结果解析… 12

3.3.2 赋值… 13

3.3.3 类型转换… 13

3.3.4 条件判断语句及分支… 14

3.4 本章小结… 15

第4章 汇编… 16

4.1 汇编的概念与作用… 16

4.2 在Ubuntu下汇编的命令… 16

4.1汇编命令… 16

4.3 可重定位目标elf格式… 16

4.3Section Header 17

4.4.rela.text. 18

4.4 Hello.o的结果解析… 18

4.5 本章小结… 20

第5章 链接… 21

5.1 链接的概念与作用… 21

5.2 在Ubuntu下链接的命令… 21

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

5.3hello的符号表… 23

5.4 hello的虚拟地址空间… 23

5.4edb中各节点虚拟地址信息… 23

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

5.6 hello的执行流程… 26

5.7调用dl_init之后的全局偏移量表.got.plt. 28

5.8 本章小结… 28

第6章 hello进程管理… 29

6.1 进程的概念与作用… 29

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

6.3 Hello的fork进程创建过程… 29

6.4 Hello的execve过程… 30

6.5 Hello的进程执行… 31

6.6 hello的异常与信号处理… 32

6.8输入kill 35

6.9运行时乱按… 36

6.7本章小结… 37

第7章 hello的存储管理… 38

7.1 hello的存储器地址空间… 38

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

7.1逻辑地址到线性地址的变换图… 39

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

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

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

7.4物理内存的访问… 41

7.6 hello进程fork时的内存映射… 41

7.7 hello进程execve时的内存映射… 42

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

7.9动态存储分配管理… 44

7.10本章小结… 44

第8章 hello的IO管理… 45

8.1 Linux的IO设备管理方法… 45

8.2 简述Unix

8.3 printf的实现分析… 46

8.4 getchar的实现分析… 47

8.5本章小结… 47

结论… 48

附件… 49

参考文献… 50

第1章 概述

1.1
Hello简介

   用户由键盘输入hello.c。hello.c经过预处理(cpp)变成hello.i(修改了的源程序),经过编译(ccl)生成hello.s(汇编程序),经过汇编生成hello.o(可重定位目标程序),经过链接(ld)生成hello(可执行目标程序)。用户键入命令,bash自行fork一个process,并在这个process中调用execve执行hello。execve加载hello,并调用_start函数,不久控制权被转移到hello的main函数。hello调用write等系统函数在屏幕打印信息,随后退出,接下来终止的hello进程被父进程bash回收。

1.1 hello源代码

1.2
环境与工具

硬件环境:Intel Core i7-7500U x64CPU,4G
RAM,128G SSD +1T HDD.

软件环境:Ubuntu16.04.1
LTS

开发与调试工具:vim,gcc,ld,edb,readelf

1.3
中间结果

文件名称 文件作用

hello.i 预处理之后文本文件

hello.s 编译之后的汇编文件

hello.o 汇编之后的可重定位目标执行

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

h1.txt hello反汇编文件

h2.txt hello
.o反汇编文件

1.4
本章小结

本章简要介绍了hello的P2P,O2O过程,并列出了本次实验的环境和中间结果。

(第1章0.5分)

第2章 预处理

2.1
预处理的概念与作用

预处理器cpp根据以字符#开头的命令(宏定义、条件编译),修改原始的C程序,将引用的所有库展开合并成为一个完整的文本文件。C语言的预处理主要有三个方面的内容:宏定义、文件包含、条件编译。

宏定义只需将符号常量替换成后面对应的文本即可。

文件包含可以在一个文件中包含另一个文件的内容,被包含的文件称为头文件。头文件的内容可以有函数原型、宏定义、结构体定义。

条件编译是在条件满足时才编译某些语句。条件编译可以使目标程序变小,运行时间变短。同时有利于代码的模块化。

2.2在Ubuntu下预处理的命令

命令:gcc -E -o
hello.i hello.c

             2.1预处理命令

2.3 Hello的预处理结果解析

经过预处理之后,hello.c文件转化为hello.i文件。原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中如图2.2。打开该文件可以发现,文件长度变为3126行,如图2.3文件的内容增加,且仍为可以阅读的C语言程序文本文件。

                                             2.2头文件部分截图











                                             

                                       2.3hello.i部分截图

2.4
本章小结

本章节简单介绍了c语言在编译前的预处理过程,简单介绍了预处理过程的概念和作用,对预处理过程进行演示,并举例说明预处理的结果还有解析预处理的过程。

(第2章0.5分)

第3章 编译

3.1
编译的概念与作用

编译程序也称为编译器,是指把用高级程序设计语言书写的源程序,翻译成等价的汇编语言格式目标程序的翻译程序。

编译程序的基本功能是把源程序(高级语言)翻译成目标程序。除了基本功能之外,编译程序还具备语法检查、调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用以及人机联系等重要功能。

3.2
在Ubuntu下编译的命令

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

                                     3.1编译命令

3.2编译结果

3.3
Hello的编译结果解析

3.3.1 数据

   字符串:“Usage: Hello 学号 姓名!\n”,第一个printf传入的输出格式化参数,在hello.s中声明如图3.3,可以发现字符串被编码成UTF-8格式,一个汉字在utf-8编码中占三个字节,一个\代表一个字节。“Hello %s %s\n”,第二个printf传入的输出格式化参数,在hello.s中声明如图3.3。





                               3.3

hello.s中声明的字符串

   整数:sleepsecs在C程序中被声明为全局变量,且已经被赋值,编译器处理时在.data节声明该变量,.data节存放已经初始化的全局和静态C变量。在图3.4中,可以看到,编译器首先将sleepsecs在.text代码段中声明为全局变量,其次在.data段中,设置对齐方式为4、设置类型为对象、设置大小为4字节、设置为long类型其值为2。

















                               3.4

hello.s中sleepsecs的声明

   数组:取数组的第i位一般是按照取数组头指针加上第i位的偏移量来操作的,指针跟数组类似,如果x表示一个指针,rax表示其存储的寄存器,你要访问*x,那么就是(%rax)。结构也是类似的,通过结构在结构体中的偏移量来访问。





























                      

                              3.5hello.s中对数组的操作

3.3.2
赋值

   程序中涉及的赋值操作有: int sleepsecs=2.5 ,因为sleepsecs是全局变量,所以直接在.data节中将sleepsecs声明为值2的long类型数据。因为i是4B的int类型,所以使用movl进行赋值,汇编代码如图3.6。









                                             3.6赋值

3.3.3
类型转换

程序中涉及隐式类型转换的是:int sleepsecs=2.5,将浮点数类型的2.5转换为int类型。当在double或float向int进行类型转换的时候,程序改变数值和位模式的原则是:值会向零舍入。浮点数默认类型为double,所以上述强制转化是double强制转化为int类型。遵从向零舍入的原则,将2.5舍入为2。

3.3.4
条件判断语句及分支

   在main函数中,使用if语句进行了条件判断。cmpl语句进行判断条件的比较。如果条件满足则继续顺序执行,调用puts输出给定字符串(这里puts是对printf的优化),然后使用参数1调用exit结束程序。对应的汇编代码如下。























                        

                               3.7 if条件语句段对应的汇编代码

3.3.5对于四则运算符及其复合语句的处理

加法:x=x+y addq y,x

减法:x=x-y subq y,x

乘法:x=x*y imulq y,x

除法:z=x/y movq x,z

                        cqto

                        idivq

y

程序中涉及的算数操作有:

a.i++,对计数器i自增,使用程序指令addl,后缀l代表操作数是一个4B大小的数据。

b.汇编中使用leaq .LC1(%rip),%rdi,使用了加载有效地址指令leaq计算LC1的段地址%rip+.LC1并传递给%rdi。

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

1.main函数:

   传递控制,main函数因为被调用call才能执行,call指令将下一条指令的地址dest压栈,然后跳转到main函数。

   传递数据,外部调用过程向main函数传递参数argc和argv,分别使用%rdi和%rsi存储,函数正常出口为return 0,将%eax设置0返回。

   分配和释放内存,使用%rbp记录栈帧的底,函数分配栈帧空间在%rbp之上,程序结束时,调用leave指令,leave相当于mov %rbp,%rsp,pop %rbp,恢复栈空间为调用之前的状态,然后ret返回,ret相当pop IP,将下一条要执行指令的地址设置为dest。

2.printf函数:

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

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

3.exit函数:

   传递数据:将%edi设置为1。

   控制传递:call exit@PLT。

4.sleep函数:

   传递数据:将%edi设置为sleepsecs。

   控制传递:call sleep@PLT。

5.getchar函数:

   控制传递:call gethcar@PLT

3.4
本章小结

本章显示简述了编译的概念和作用,具体分析了一个c程序是如何被编译器编译成一个汇编程序的过程,还详细分析了不同的c语句和翻译成汇编语句之后的表示方法。

(第3章2分)

第4章 汇编

4.1
汇编的概念与作用

   汇编的概念是指的将汇编语言(xxx.s)翻译成机器指令,并将这些指令打包成一种叫做可重定位目标程序,并将这个结果保留在(xxx.o)中。这里的xxx.o是二进制文件。汇编过程的作用是将汇编指令转换成一条条机器可以直接读取分析的机器指令。

4.2
在Ubuntu下汇编的命令

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

4.1汇编命令

4.3
可重定位目标elf格式

ELF Header::用于总的描述ELF文件各个信息的段。

                                   4.2ELF头信息

Section Header:描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息

                  4.3Section Header

rela.text:重定位节,这个节包含了.text(具体指令)节中需要进行重定位的信息。这些信息描述的位置,在由.o文件生成可执行文件的时候需要被修改(重定位)。在这个hello.o里面需要被重定位的有printf , puts , exit , sleepsecs , getchar , sleep ,rodata里面的两个元素(.L0和.L1字符串)

               4.4.rela.text

4.4
Hello.o的结果解析

使用objdump -d
-r hello.o指令显示hello.o的反汇编结果,如图4.5。

                               4.5hello.o的反汇编结果

4.4.1分支跳转语句

我们可以发现,在hello.s中跳转到的目标位置都是用.L3/.L4来表示的,在hello.o反汇编之后,这些目标被用具体的地址位置代替。

4.4.2函数调用

在原先的hello.s中,调用一个函数只需被表示成call+函数名,但是在hello.o反汇编的结果中我们可以看见,这里的call是call一个具体的地址位置。

机器语言程序的是二进制的机器指令序列集合,是纯粹的二进制数据表示的语言,是电脑可以真正识别的语言。机器指令由操作码和操作数组成。汇编语言是以人们比较熟悉的词句直接表述CPU动作形成的语言,是最接近CPU运行原理的较为通俗的比较容易理解的语言。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。机器语言与汇编语言具有一一对应的映射关系,一条机器语言程序对应一条汇编语言语句,但不同平台之间不可直接移植。

4.5 本章小结

本章简述了hello.s汇编指令被转换成hello.o机器指令的过程,通过readelf查看hello.o的ELF、反汇编的方式查看了hello.o反汇编的内容,比较其与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的格式

5.1hello的文件头

由图5.3可以得到各段的基本信息。由于是可执行目标文件,所以每个段的起始地址都不相同,它们的起始地址分别对应着装载到虚拟内存中的虚拟地址。这样可以直接从文件起始处得到各段的起始位置,以及各段所占空间的大小。同时可以观察到,代码段是可执行的,但是不能写;数据段和只读数据段都不可执行,而且只读数据段也不可写。

                               5.2

hello.out的段头表

5.3hello的符号表

5.4
hello的虚拟地址空间

使用edb打开hello程序,通过edb的Data Dump窗口查看加载到虚拟地址中的hello程序。

在0x400000~0x401000段中,程序被载入,自虚拟地址0x400000开始,自0x400fff结束,这之间每个节的排列即开始结束同图5.2中地址中声明相同。

     5.4edb中各节点虚拟地址信息

如图5.2,查看ELF格式文件中的Program Headers,程序头表在执行的时候

被使用,它告诉链接器运行时加载的内容并提供动态链接的信息。每一个表项提

供了各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对齐

方面的信息。

5.5
链接的重定位过程分析

hello的反汇编相比hello.o的反汇编多了许多节,如:

节 作用

.interp 保存ld.so的路径

.rela.plt .plt的重定位项目

.init 初始化代码

.plt 动态链接过程链接表

.got 动态链接全局偏移量表,用于存放变量

.got.plt 动态链接全局偏移量表,用于存放函数

hello的重定位记录有两种,分别是PC相对地址的引用和绝对地址的引用。

进行重定位时,hello根据.rela.text和.rela.data中的重定位记录,在.symtab中查找需要修改的记录的符号,并结合符号与重定位记录中的位置信息对目标位置进行 修改。如果需要修改的符号是本地符号,则计算偏移量并修改目标位置;如果是共享库中的符号,则创建.got表项(如果是函数还需创建.plt项),并创建新的重定位记录指向.got表项。

5.5hello反汇编结果

5.6
hello的执行流程

1.先是加载程序_init (argc=1, argv=0x7fffffffde38, envp=0x7fffffffde48)

2.0x00000000004004d0 in _start ()

3.0x0000000000400480 in
__libc_start_main@plt ()

4.0x0000000000400670 in __libc_csu_init
()

5.0x0000000000400430 in _init ()

6.0x00000000004005b0 in frame_dummy ()

7.0x0000000000400540 in
register_tm_clones ()

8.0x00000000004005f2 in main ()

9.0x0000000000400460 in puts@plt ()

10.0x00000000004004a0 in exit@plt ()

11.0x0000000000400580 in
__do_global_dtors_aux ()

12.0x0000000000400500 in deregister_tm_clones
()

13.0x00000000004006e4 in _fini ()

5.7
Hello的动态链接分析

在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定(lazybinding),将过程地址的绑定推迟到第一次调用该过程时。

延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。两表内容分别为:

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

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

       5.6没有调用dl_init之前的全局偏移量表.got.plt













      5.7调用dl_init之后的全局偏移量表.got.plt





在edb调试之后我们发现原先0x00400a10开始的global_offset表是全0的状

态,在执行过_dl_init之后被赋上了相应的偏移量的值。这说明dl_init操作是给程

序赋上当前执行的内存地址偏移量,这是初始化hello程序的一步。

5.8
本章小结

在本章中主要介绍了链接的概念与作用,hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。

(第5章1分)

第6章 hello进程管理

6.1
进程的概念与作用

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

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

Shell的作用:Shell是一个用C语言编写的程序,他是用户使用Linux的桥梁。Shell 是指一种应用程序,Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。

   处理流程:

1)从终端读入输入的命令。

2)将输入字符串切分获得所有的参数

3)如果是内置命令则立即执行

4)否则调用相应的程序为其分配子进程并运行

5)shell应该接受键盘输入信号,并对这些信号进行相应处理

6.3
Hello的fork进程创建过程

在bash中输入
./hello ********** **
并敲击回车后,bash解析此条命令,发现./hello不是bash内置命令,于是在当前目录尝试寻找并执行hello文件。此时bash使用fork函数创建一个子进程(这个子进程得到与父进程用户级虚拟地址空间相同但是独立的一份副本),并更改这个子进程的进程组编号。并准备在这个子进程执行execve。

6.4
Hello的execve过程

每一个进程都有一段唯一属于自己的内存地址段,在execve运行时,开始先是从0x00400000(对于32位系统来说是0x8048000)开始程序的执行。先是从可执行文件中加载的内容,然后是运行时的堆栈和共享库的存储器映射区域。

6.1 启动加载器创建的系统映像

6.5
Hello的进程执行

Linux 系统中的每个程序都运行在一个进程上下文中,有自己的虚拟地址空间。当shell 运行一个程序时,父shell
进程生成一个子进程,它是父进程的一个复制。子进程通过execve 系统调用启动加载器。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk), 新的代码和数据段袚初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用程序的main
函数。

6.2上下文切换与并发

6.6
hello的异常与信号处理

6.3hello正常运行

如图6.4,这个操作向进程发送了一个sigint信号,让进程直接结束,输入ps命令可以发现当前hello进程已经被终止了。

6.4运行时输入ctrl+c

如图6.5,这个操作向进程发送了一个信号,让进程暂时挂起,输入ps命令符可以发现hello进程还没有被关闭。

6.5运行时输入ctrl+z

他可以使后台挂起的进程继续运行。如图6.6,先输出了3次,然后ctrl+c挂起之后,fg命令又可以让他继续进行,然后把剩下的7次输出完。

6.6输入fg

jobs命令可以查看当前的关键命令(ctrl+Z/ctrl+C这类)内容,比如这时候就会返回ctrl+c表示暂停命令

6.7输入jobs

                  6.8输入kill

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

          6.9运行时乱按

6.7本章小结

本阶段通过在hello.out运行过程中执行各种操作,了解了与系统相关的若干概念、函数和功能。分析了在程序运行过程中,计算机硬件、软件和操作系统之间的配合和协作的方式。

(第6章1分)

第7章 hello的存储管理

7.1
hello的存储器地址空间

逻辑地址:又称相对地址,是程序运行由CPU产生的与段相关的偏移地址部分。他是描述一个程序运行段的地址。

   物理地址:程序运行时加载到内存地址寄存器中的地址,内存单元的真正地址。他是在前端总线上传输的而且是唯一的。在hello程序中,他就表示了这个程序运行时的一条确切的指令在内存地址上的具体哪一块进行执行。

   线性地址:这个和虚拟地址是同一个东西,是经过段机制转化之后用于描述程序分页信息的地址。他是对程序运行区块的一个抽象映射。以hello做例子的话,他就是一个描述:“我这个hello程序应该在内存的哪些块上运行。”

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

先将逻辑地址分成段选择符+段描述符的判别符(TI)+地址偏移量的形式,然后先判断TI字段,看看这个段描述符究竟是局部段描述符(ldt)还是全局段描述符(gdt),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了。

                7.1逻辑地址到线性地址的变换图

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

CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。

7.2线性地址到物理地址的变换图

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

对于TLBT和TLBI来说,如果可以在TLB中找不到到对应的PPN,即可能出现缺页的情况,这时候就需要到页表中去找。此时,VPN被分成了更多段,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过寻址之后找到相应的PPN让你和和VPO拼接起来。

                        7.3TLB与4级页表下Core i7的地址翻译情况

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

得到物理地址之后,先将物理地址拆分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。

               7.4物理内存的访问

7.6
hello进程fork时的内存映射

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

7.7
hello进程execve时的内存映射

删除已存在的用户区域

1.创建新的私有区域(.malloc,.data,.bss,.text)

2.创建新的共享区域(libc.so.data,libc.so.text)

3.设置PC,指向代码的入口点

7.5加载器是如何映射用户地址空间区域的

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

情况1:段错误:首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)

情况2:非法访问:接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。

情况3:如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。

7.6故障处理流程

7.9动态存储分配管理

DRAM 缓存不命中称为缺页,即虚拟内存中的字不在物理内存中。缺页导致页面出错,产生缺页异常。缺页异常处理程序选择一个牺牲页,然后将目标页加载到物理内存中。最后让导致缺页的指令重新启动,页面命中。

7.10本章小结

本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以intel Core7在指定环境下介绍了VA到PA的变换、物理内存访问,还介绍了hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理。

(第7章 2分)

第8章 hello的IO管理

8.1
Linux的IO设备管理方法

首先是设备的模型化。在设备模型中,所有的设备都通过总线相连。每一个设备都是一个文件。设备模型展示了总线和它们所控制的设备之间的实际连接。在最底层,Linux 系统中的每个设备由一个 struct
device 代表,而Linux统一设备模型就是在kobject kset ktype的基础之上逐层封装起来的。设备管理则是通过unix io接口实现的。

8.2
简述Unix IO接口及其函数

打开和关闭文件

int open(char *filename, int flags,
mode_t mode);

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

int close(int fd);

进程通过调用close关闭一个打开的文件。

读和写文件

ssize_t read(int fd, void *buf, size_t
n);

read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。

ssize_t write(int fd, const void *buf,
size_t n);

write函数从内存位置buf复制之多n个字节到描述符fd的当前文件位置。

DIO *opendir(const char *name);

函数opendir以路径名为参数,返回指向目录流的指针。流是对条目有序列表的抽象,在这里是指目录项的列表。

struct dirent *readdir(DIR *dirp);

每次对readdir的调用返回的都是指向流dirp中下一个目录项的指针,或者,如果没有更过目录项则返回NULL。

int closedir(DIR *dirp);

函数closedir关闭流并释放其所有的资源。

I/O重定向

int dup2(int oldfd, int newfd);

dup2函数复制描述符表表项oldfd到描述符表项newfd,覆盖描述符表表项newfd以前的内容。如果newfd已经打开了,dup2会在复制oldfd之前关闭newfd。

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;

}

其中调用了 vsprintf,write两个函数。

8.1vsprintf实现代码

从上面vsprintf函数可以看出,这个函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。

   write函数是将buf中的i个元素写到终端的函数。

   Printf的运行过程:

   从vsprintf生成显示信息,显示信息传送到write系统函数,write函数再陷阱-系统调用 int

0x80或syscall.字符显示驱动子程序。从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4
getchar的实现分析

异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,中断请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后将该按键扫描码转换成ASCII码,保存到系统的键盘缓冲区之中。

getchar函数落实到底层调用了系统函数read,通过系统调用read读取存储在键盘缓冲区中的ASCII码直到读到回车符然后返回整个字串,getchar进行封装,大体逻辑是读取字符串的第一个字符然后返回。

8.5本章小结

本章节讲述了一下linux的I/O设备管理机制,了解了开、关、读、写、转移文件的接口及相关函数,简单分析了printf和getchar函数的实现方法以及操作过程。

(第8章1分)

结论

hello.c通过键盘鼠标等I/O设备输入计算机,并存储在内存中。然后预处理器将hello.c预处理成为文本文件hello.i。接着编译器将hello.i翻译成汇编语言文件hello.s。汇编器将hello.s汇编成可重定位二进制代码hello.o。链接器将外部文件和hello.o连接起来形成可执行二进制文件hello.out。shell通过fork和execve创建进程,然后把hello加载到其中。shell创建新的内存区域,并加载代码、数据和堆栈。hello在执行的过程中遇到异常,会接受shell的信号完成处理。hello在执行的过程中需要使用内存,那么就通过CPU和虚拟空间进行地址访问。Hello执行结束后,shell回收其僵尸进程,从系统中消失。

在做大作业的过程中,对每章内容进行了回顾,回顾了很多调试器的使用方法,如readelf,gcc,edb之类的,对整个程序的运行过程有了一个新的认识,加深了对一些工具的操作记忆。

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

附件

中间产物的文件名及作用。

文件名称 文件作用

hello.i 预处理之后文本文件

hello.s 编译之后的汇编文件

hello.o 汇编之后的可重定位目标执行

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

h1.txt hello反汇编文件

h2.txt hello
.o反汇编文件

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

参考文献

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

[1] ELF 构造:https://www.cs.stevens.edu/~jschauma/631/elf.html

[2]编译 百度百科:https://baike.baidu.com/item/编译/1258343?fr=aladdin

[3]printf函数详细讲解:https://www.cnblogs.com/windpiaoxue/p/9183506.html

[4]进程的睡眠、挂起和阻塞:https://www.zhihu.com/question/42962803

[5]虚拟地址、逻辑地址、线性地址、物理地址:https://blog.youkuaiyun.com/rabbit_in_android/article/details/49976101

[6] 内存地址转换与分段
https://blog.youkuaiyun.com/drshenlei/article/details/4261909

(参考文献0分,确实 -1分)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值