程序人生-Hello’s P2P

计算机系统

大作业

题     目  程序人生-Hello’s P2P   

专       业   未来技术            

学     号   7203610123          

班     级   2036011             

学       生   王紫芊            

指 导 教 师   刘宏伟              

计算机科学与技术学院

2021年5月

摘  要

hello.c是任何学习计算机的学生在初入计算机领域时打开探索大门的钥匙。hello.c虽然只有短短几行代码,最终屏幕上的那一句hello world!却蕴含着整个计算机系统的有条不紊的工作秩序与机制。本文将对hello.c程序的完整生命周期进行针对计算机系统的逐步分析,探索程序的预处理、编译、汇编、链接和相应系统的进程管理、存储管理、I/O管理过程,将hello.c程序从开始到结束的生命历程完整地展现出来。

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

目  录

第1章 概述......................................... - 4 -

1.1 Hello简介.................................. - 4 -

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

1.3 中间结果..................................... - 4 -

1.4 本章小结..................................... - 4 -

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

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

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

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

2.4 本章小结..................................... - 5 -

第3章 编译......................................... - 6 -

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

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

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

3.4 本章小结..................................... - 6 -

第4章 汇编......................................... - 7 -

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

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

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

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

4.5 本章小结..................................... - 7 -

第5章 链接......................................... - 8 -

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

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

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

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

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

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

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

5.8 本章小结..................................... - 9 -

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

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

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

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

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

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

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

6.7本章小结................................... - 10 -

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

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

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

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

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

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

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

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

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

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

7.10本章小结................................. - 12 -

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

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

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

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

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

8.5本章小结................................... - 13 -

结论..................................................... - 14 -

附件..................................................... - 15 -

参考文献............................................. - 16 -

第1章 概述

1.1 Hello简介

我们编写的hello.c源程序经过程序的预处理形成helllo.i文件,经过编译器生成汇编语言文件hello.s,经过汇编器翻译成可重定位目标文件hello.o,链接器又将它和引用的库函数链接起来生成可执行文件hello。接着通过在shell中输入./shell命令,shell通过fork函数为可执行目标文件创建了一个新的进程,之后再调用execve把程序加载到进程中,这样就实现了P2P。接着内核会在虚拟内存中加载并运行hello程序的相关代码和数据,调度器为进程规划时间片。当程序运行结束后,shell作为其父进程会负责将其回收,内核删除相关数据,这样就实现了020。

1.2 环境与工具

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

软件环境:Windows10 64位;VirtualBox 11;Ubuntu 16.04 LTS 64位

开发工具:Visual Studio 2010 64位;gcc/as/ld/vim/edb/readelf/gedit

1.3 中间结果

文件名称

文件功能

hello.c

源程序

hello.i

预处理后的文件

hello.s

编译后的汇编文件

hello.o

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

hello

链接后的可执行目标文件

hello_o.txt

hello.o反汇编后的文本文件

hello.txt

hello反汇编后的文本文件

hello_o_elf.txt

hello.o的ELF格式

hello_elf.txt

hello的ELF格式

1.4 本章小结

本章对hello的P2P、020的过程进行了简要的介绍,并对实验环境和中间文件进行了一系列基本信息的说明。

第2章 预处理

2.1 预处理的概念与作用

概念:为编译做的预备工作的阶段,做些代码文本的替换工作,处理以#开头的指令,比如拷贝 #include 包含的文件代码、#define 宏定义的替换、条件编译等。c提供的预处理功能主要有以下三种:宏定义、文件包含、条件编译。

作用:根据源代码中的预处理指令修改源代码,预处理从系统的头文件包中将头文件的源码插入到目标文件中,宏和常量标识符已全部被相应的代码和值替换,最终生成.i文件。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

经预处理后的hello.i文件变为三千多行,内容大大增加,经阅读后发现main函数位于hello.i文集文件的最末尾。原hello.c文件中的头文件#include <stdio.h>、#include <unistd.h> 、#include <stdlib.h>的内容被包含进hello.i文件中,例如声明函数、定义结构体、定义变量、定义宏等内容。因原hello.c文件中不包含#define宏定义的命令,所以预处理中暂不存在宏定义的替换。

 

 

2.4 本章小结

本章介绍了预处理的概念和作用,在ubuntu终端输入指令生成了hello.i文件,并对生成的文件进行的查看。

 

第3章 编译

3.1 编译的概念与作用

概念:编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个hello.c相对应的汇编语言程序。

作用:对预处理文件进行语法分析、语义分析,确认所有都符合语法规则之后,将其转换成汇编程序。

3.2 在Ubuntu下编译的命令

 

3.3 Hello的编译结果解析

3.3.1伪指令

.file 声明源文件.text 代码节.section.rodata 只读数据段.align 声明对指令或者数据的存放地址进行对齐的方式.string 声明一个字符串.globl 声明全局变量.type 声明一个符号是函数类型还是数据类型

3.3.2数据

字符串:有两个字符串,这两个字符串都在只读数据段中,作为printf函数的参数。

 

数组:char *argv[]作为main函数的第二个参数,数组的首地址存放在栈中-32(%rbp)的位置,被多次调用。

 

局部变量:main函数中声明了一个局部变量i,用于遍历操作。局部变量i放在栈上-4(%rbp)的位置。这里我们还可以看到进行了赋值操作(movl),对i赋初值为0。 

立即数:立即数直接体现在汇编代码中。

参数argc:用户传给main函数的参数之一。

3.3.3全局函数

声明的全局函数即是主函数main。 

3.3.4类型转换

程序中只使用了一个类型转换操作atoi,把字符串类型转为int型数。

3.3.5算术操作

在程序的for循环体结构中,有局部变量i的循环加一操作。

3.3.6关系操作与控制转移

判断argc是否是!= 4,如果argv等于4,就将控制转移到L2处的指令,不等于4则继续执行下一条指令。

判断i 是否<= 7,如果成立,就跳到L4继续执行循环内部的内容,如果不成立,就继续执行下面的指令。 

3.3.7函数操作

hello.c中包括的函数操作有:puts,exit,printf,atoi,sleep,getchar,以及先前提到的main主函数。

main:参数是argc(%edi保存),argv(%rsi保存)。返回值是0。

 

printf: 参数传递:leaq .LC1(%rip), %rdi。

puts: 参数传递:leaq .LC0(%rip), %rdi。

exit:参数是1。

atoi:参数传入argv[3]。 

sleep:参数是atoi完的argv[3]。 

getchar:无参数传入,返回整型。

3.4 本章小结

本节介绍了编译的概念和作用,并基于示例hello.c的汇编程序进行了数据、函数、关系、转移等不同操作多方面的查看和解读。

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器(as)将汇编文件转换为二进制可重定位文件的过程。

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

 

4.3 可重定位目标elf格式

4.3.1 ELF

以一个16字节的序列Magic开始,描述了生成该文件的系统的字的大小和字节顺序。剩下的部分包含了系统信息、编码方式、ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。

4.3.2 节头

记录各节名称、类型、地址、偏移量、大小等信息。

4.3.3 重定位节

重定位节.rela.text包含了对.text节进行重定位的信息。各个段引用的外部符号等在链接时需要通过重定位对这些位置的地址进行修改。链接器会通过重定位节的重定位条目计算出正确的地址。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。本程序需要被重定位的是.rodata中的.L0和.L1,puts ,exit ,printf,atoi,sleep,getchar。.rela.eh_frame节是.eh_frame节重定位信息。

4.3.4 符号表

.symtab符号表,它定义和引用的函数和全局变量的名称、类型、大小等信息。

4.4 Hello.o的结果解析

命令:objdump -d -r hello.o > hello_o.txt,得到hello.o的反汇编文件。

从操作数上来看,hello.s中的操作数为十进制,而hello.o反汇编文件中的操作数为十六进制。

从函数调用上来看,hello.s直接call后面加函数的名称,而反汇编文件中call后不再是函数的具体名称,而是一条重定位条目指引的信息。机器代码除了第一字节都是零,调用的函数在别的库中还未链接,链接后才能确定运行执行的地址。

从控制转移上来看,hello.s的跳转采用了L1、L2这种段名称,而反汇编文件中使用的是目标代码的虚拟地址。留下了重定位条目,只有在链接后才能被填写出正确的地址,这与函数调用类似。

4.5 本章小结

本节对hello.s进行了汇编,生成hello.o可重定位目标文件,并对可重定位目标文件的格式和内容进行了分析。同时比较了hello.s和hello.o的反汇编文件的内容有什么不同,分析了汇编语言与机器语言的对应关系。

5章 链接

5.1 链接的概念与作用

概念:链接是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时,也可以执行于加载时,甚至执行于运行时。

作用:链接器使得分离编译成为可能。一个大型的应用程序可以分解为更小、更好管理的一个个可以独立地修改和编译的模块。当我们修改模块中的一个时,只需简单地重新编译它,并重新链接,而不必重新编译其它文件。

5.2 在Ubuntu下链接的命令

 

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

5.3.1 ELF头

具体类别和可重定位目标文件差不多,文件类型变成了:EXEC(可执行文件)。

5.3.2 节头

记录各节名称、类型、地址、偏移量、大小等信息。

5.3.3 重定位节

列出函数详细的重定位信息。

5.3.4 符号表 

5.4 hello的虚拟地址空间

在0x400000~0x401000段中,程序被载入,虚拟地址0x400000开始,到0x400fff结束。根据5.3中的节头部表,可以通过edb找到各个节的信息。

比如.init段地址是0x401000。

.text段地址是0x4010f0,大小为0x145。 

5.5 链接的重定位过程分析

链接过程中加入了c语言中用到的库函数,比如printf、getchar等等。

hello中增加了.init和.plt节,和一些节中定义的函数。 

hello实现了调用函数以及跳转时的重定位,调用函数和跳转时调用的地址已经是函数确切的虚拟地址。 

重定位的过程:重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。重定位节中的符号引用这一步中,链接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。代码的重定位条目放在.rela.txt

5.6 hello的执行流程

开始:_start、_libc_start_main

执行主函数:_main、_printf、_exit、_sleep、_getchar

退出:exit

0x4010f0<_start>

0x2f12271d <_libc_start_main >

0x401125<main>

0x4010a0<_printf >

0x4010e0<_sleep >

0x4010b0<_getchar >

0x4010d0<exit>

5.7 Hello的动态链接分析

动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序。在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,解决方法是为该引用生成一条重定位记录,动态链接器在程序加载的时候再解析它。

通过GOT和PLT实现,由ELF文件,查看与动态链接相关的.plt段和.got段。

观察链接前.got.plt表中内容: 

链接后: 

可以发现,0404000到0404010之间的一段数据发生了变化,即是GOT表中加载了共享库的内容。

5.8 本章小结

本节简述了链接的概念与作用,查看了经过链接生成的hello 文件并且比对了hello与hello.o反汇编文件的异同,分析了hello的虚拟地址空间,重定位过程,执行过程和动态连接过程。

6章 hello进程管理

6.1 进程的概念与作用

概念:进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的(静态的),进程是活的(动态的)。进程是操作系统进行资源分配的单位。

作用:进程提供给应用程序两种关键抽象:一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器;一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用内存系统。

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

Shell 是一种命令行解释器, 其读取用户输入的字符串命令, 解释并且执行命令. 它是一种特殊的应用程序, 介于系统调用/库与应用程序之间, 其提供了运行其他程序的的接口.它可以是交互式的, 即读取用户输入的字符串;也可以是非交互式的, 即读取脚本文件并解释执行, 直至文件结束. 无论是在类 UNIX, Linux 系统, 还是 Windows, 有很多不同种类的 Shell: 如类 UNIX, Linux 系统上的 Bash, Zsh 等; Windows 系统上的 cmd, PowerShell 等。

处理流程:1. 读取从键盘输入的命令2. 判断命令是否正确,且将命令行的参数改造为系统调用execve() 内部处理所要求的形式3. 终端进程调用fork() 来创建子进程,则用系统调用wait() 来等待子进程完成4. 当子进程运行时,它调用execve() 根据命令的名字指定的文件到目录中查找可行性文件,调⼊内存并执行这个命令5. 如果命令行末尾有后台命令符号&终端进程不执行等待系统调用,而是立即发提示符,让用户输⼊下一条命令;如果命令末尾没有&则终端进程要一直等待。当子进程完成处理后,向父进程报告,此时终端进程被唤醒,做完必要的判别工作后,再发提示符,让用户输⼊新命令。

6.3 Hello的fork进程创建过程

在终端中输入./hello 7203610123 王紫芊 1,首先shell会解析输入的命令,由于该命令并不是一个内置的shell命令,因此shell会调用fork()创建一个新的运行的子进程,子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。父进程和子进程之间最大的区别是它们有不同的PID。fork调用一次,返回两次。一次返回至父进程,返回的是子进程的pid;一次返回至子进程,返回值为0。父子进程是并发运行的独立进程。

6.4 Hello的execve过程

execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量envp。只有当出现错误时,例如找不到hello,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并从不返回。

argv变量指向一个以null结尾的指针数据,每个指针都指向一个参数字符串,argv[0]是可执行目标文件的名字。环境变量envp也指向一个以null结尾的指针数组,其中每个指针都指向一个环境变量字符串,每个串都是形如“name=value”的名字-值对。

execve调用启动加载器。加载器会创建一组新的代码、数据、堆、栈并初始化为0。通过虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码数据初始化。然后跳转到_start地址,最终调用main函数。

6.5 Hello的进程执行

上下文信息:上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器等各种内核数据结构等对象的值构成。

上下文切换:当内核选择一个新的进程运行时,则内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程:保存以前进程的上下文;恢复新进程被保存的上下文;将控制传递给这个新恢复的进程,来完成上下文切换。

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

用户模式和内核模式:进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置;用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据。

在进程调用execve函数为hello程序分配了新的虚拟空间后,hello最初运行在用户模式下。hello首先在用户模式下输出hello 7203610123 王紫芊 1,然后调用sleep,显式地请求让调用进程休眠,此时内核会处理休眠请求,保存hello进程的上下文,主动释放hello进程,把hello进程从运行队列转移到等待队列,进程进入内核模式。处理完信号后,又返回用户模式,CPU开始处理其他进程。当sleep结束后,发送信号给内核,信号处理后,hello程序得以重新回到用户模式继续运行。

6.6 hello的异常与信号处理

hello运行过程中会出现的异常有:

 

 

按下Ctrl+Z,进程收到 SIGSTP 信号,hello进程挂起。

用ps命令查看发现hello在进程列表里,说明hello只是被暂停,没有被终止和回收。

再用jobs查看当前暂停的进程,列出hello和它目前的状态,此时hello的后台 job号是1。 

调用fg,恢复第一个后台作业到前台,hello进程又继续在前台运行,输出没输出完的字符串。 

Ctrl+C:进程收到 SIGINT 信号,结束 hello。查询不到其PID,在job中也没有显示,hello已经被彻底结束。 

随便乱按:屏幕的输入缓存到缓冲区,输入的混乱字符只是随着程序本来的输出一起连续输出为结果在屏幕上。 

6.7本章小结

本节介绍了进程的概念与作用,说明了shell的作用和处理流程、hello程序的进程执行过程以及程序运行过程中各种异常与信号处理。

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:程序经过编译后出现在汇编代码中的地址。逻辑地址用来指定一个操作数或者是一条指令的地址。指hello.o中的内容。

线性地址:逻辑地址向物理地址转化过程中的一步,向物理地址变换之间的中间层。逻辑地址经过段机制后转化为线性地址,程序hello中会产生段中的偏移地址,加上相应段的基地址就成为了一个线性地址。

虚拟地址:就是线性地址。

物理地址:物理内存中的地址。CPU通过地址总线的寻址,找到真实物理内存相对应地址。

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

一个逻辑地址由两部份组成,段标识符和段内偏移量。

段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号,这就是“段描述符”,段描述符具体地址描述了一个段,我们可以理解为把虚拟内存分为一个一个的段,这样可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的就放在所谓的“局部段描述符表(LDT)”中。段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。

当给定一个完整的逻辑地址,首先看段选择符的T1=0还是1,确定放在GDT还是LDT里。再根据相应寄存器,得到其地址和大小。就得到一个数组了。再拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样就知道了基地址。把base和offset相加,就是线性地址。

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

CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。线性地址被分为以固定长度为单位的组,称为页。如果CPU没有MMU,或者MMU没有启用,CPU核在取指令或访问内存时发出的地址将直接传到CPU芯片的外部地址引脚上,直接被内存芯片接收,这称为物理地址。如果CPU启用了MMU(内存管理单元),CPU核发出的地址将被MMU截获,从CPU到MMU的地址称为虚拟地址,而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。Linux采用了分页的方式来记录对应关系。所谓的分页,就是以更大尺寸的单位页来管理内存。在Linux中,通常每页大小为4KB。

分页单元中,页目录是唯一的,它的地址放在CPU的cr3寄存器中,是进行地址转换的开始点。每一个进程,因为都有其独立的对应的虚似内存,那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中。每一个32位的线性地址被划分为三部份,面目录索引(10位):页表索引(10位):偏移(12位)

依据以下步骤进行转换:

从cr3中取出进程的页目录地址;根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。页的地址被放到页表中。根据线性地址的中间十位,在页表中找到页的起始地址;将页的起始地址与线性地址中最后12位相加,得到最终我们想要的物理地址。

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

TLB是一种高速缓存,内存管理硬件使用它来改善虚拟地址到物理地址的转换速度。当CPU要访问一个虚拟地址/线性地址时,CPU会首先根据虚拟地址的高20位(20是x86特定的,不同架构有不同的值)在TLB中查找。如果是表中没有相应的表项,称为TLB miss,需要通过访问慢速RAM中的页表计算出相应的物理地址。同时,物理地址被存放在一个TLB表项中,以后对同一线性地址的访问,直接从TLB表项中获取物理地址即可,称为TLB hit。

Core i7采用四级页表的层次结构。CPU产生虚拟地址VA传送给MMU,MMU用VPN向TLB请求对应的PTE。如果命中,则得到物理地址PA。如果没有命中,MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA。在多级页表的情况下,无非就是不断通过索引 – 地址 – 索引 - 地址重复四次进行寻找。

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

CPU缓存是(Cache Memory)位于CPU与内存之间的临时存储器,它的容量比内存小但交换速度快。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。得到物理地址之后,物理地址可被分割成标记位CT、组索引CI和块偏移CO,先到L1根据组索引寻找正确的组,接着根据CT、CO依次去搜索,如果命中就返回数据,不命中就在下一级缓存L2中寻找,同理L3。搜索直到命中,然后再一级一级向上传,有空闲块的话则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,然后将目标块放到空出来的位置上。

7.6 hello进程fork时的内存映射

fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配一个唯一的PID,同时为这个新进程创建虚拟内存,子进程也可以访问任何父进程已经打开的文件。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任意一个后来进行写操作时,写时复制机制就会创建新页面,为每个进程保持了私有地址空间。

7.7 hello进程execve时的内存映射

execve调用启动加载器代码,在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello替代了当前运行的程序,步骤如下:首先删除已存在的用户区域。然后映射私有区域,为新程序的代码、数据等等创建新的区域结构,所有这些新的区域都是私有的、写时复制的。比如代码和数据区域被映射为hello文件中的.text和.data区。然后映射共享区域,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。最后设置程序计数器(PC),即设置当前进程上下文的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时,将从这个入口点开始执行。

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

当MMU翻译某个地址时,触发了缺页异常。控制便会转移到内核的缺页处理程序。处理程序执行如下步骤:

1.检查虚拟地址是否合法,如果不合法,触发一个段错误,程序终止。

2.检查进程是否有读、写或执行该区域页面的权限,如果不具有,触发保护异常,程序终止。

3.前两步检查都无误后,内核确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。缺页处理程序页面调入新的页面,并更新内存中的PTE。之后重新返回原来的进程,再次执行导致缺页的命令,虚拟地址重新发送给MMU。因为这次虚拟页面已经换存在物理内存中,所以就会命中。

7.9动态存储分配管理

动态储存分配管理使用动态内存分配器(如malloc)来进行。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合。每个块就是一个连续的虚拟内存页,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块直到它显式地被应用所分配。释放一个已分配的块,要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。动态内存分配主要有两种基本方法与策略:

显式分配器:要求用户显示的分配/释放堆空间,如C/C++用的就是显式的堆分配器

隐式分配器:分配器会自动回收用户使用的存储空间,如Java使用的就是隐式的堆分配器

1.隐式空闲链表的对块格式:

2.采用边界标记的隐式空闲链表:在空闲块的“底部”标记大小/已分配,允许我们反查链表。 

3.显式空闲链表:在空闲块中使用指针连接空闲块,仅仅需要关注空闲块,仍然需要边界标记来进行空闲块合并,每个空闲块中,都包含一个前驱和后继指针。 

隐式的分配时间是块总数的线性时间,而显式的是空闲块数量的线性时间。维护链表的顺序有:后进先出和按照地址顺序维护。

4. 分离的空闲链表:维护多个空闲链表,将所有可能的块大小划分为大小类。基本方法有:简单分离存储、分离适配、伙伴系统。

7.10本章小结

本节简述了系统对于hello的存储地址空间管理,分析了程序的虚拟地址逐步翻译为物理地址以及访问物理内存的过程,分析程序运行过程中fork,execve函数进行的内存映射,简述系统缺页异常处理过程以及动态存储分配方式。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:Linux将文件所有的I/O设备都模型化为文件,甚至内核也被映射为文件。因此所有的输出和输入都能被当做文件的读和写操作。

设备管理:linux内核有一个简单低级的接口,即unix io接口。使得所有的输入输出能够以一种统一且一致的方式来执行。

8.2 简述Unix IO接口及其函数

接口:

1.打开文件:通过内核打开相应的文件,内核返回一个非负整数,叫做描述符,用来表示这个文件,内核记录有关文件的所有信息。应用程序只需记住这个描述符。Shell创建的每个进程都有三个打开的文件:标准输入(描述符为0),标准输出(描述符为1),标准错误(描述符为2)。

2.文件位置:每个打开的文件,内核保持着一个文件位置k,表示从文件开头起始的字节偏移量,应用程序能够通过执行seek,显式地将改变当前文件位置k。

3.读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。

4.关闭文件:释放相应数据结构,文件的描述符恢复到可用的描述符池中。

函数:

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

将filename转换为文件描述符,返回描述符数字

int close(fd)

fd是需要关闭的文件的描述符,close返回操作结果

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

read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf

ssize_t wirte(int fd,const void *buf,size_tn)

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

8.3 printf的实现分析

printf程序按照格式fmt结合参数arg生成格式化之后的字符串,并返回字串的长度。printf函数调用了vsprintf函数和write函数。 

 vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。

write(buf,i)将长度为i的buf输出,期间调用了syscall。 

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等。syscall将字符串中的字节从寄存器复制到显存中,显存中存储的是字符的ASCII码。字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

异步异常-键盘中断的处理:当用户按键时,会产生一个中断请求,操作系统将控制转移到键盘中断处理子程序,中断处理程序执行,接受键盘的按键扫描码将其转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。程序会等待用户按键,用户输入的字符都被存放在键盘缓冲区中直到用户按下回车,回车也会被放入缓冲区。输入回车之后,getchar才会开始从stdio流中读入一个字符。getchar函数的返回值是用户输入的第一个字符的ascii码(出错返回-1),且将用户输入的字符回显到屏幕。如果用户在回车前输入了不止一个字符,剩下的字符会保留在键盘缓存区中,等待getchar后续调用。后续的getchar调用不会像第一次等待用户按键,而是会直接读取缓冲区中剩下的字符,直到缓冲区中的字符全部读完,再等待用户按键。

8.5本章小结

本节介绍了Linux的IO设备管理方法以及Unix IO接口及其函数,并分析了printf和getchar函数是如何实现的。

结论

hello所经历的过程:

  1. 用c语言编写出hello.c
  2. 经过预处理器(cpp)生成hello.i文件
  3. 经过编译器(ccl)对hello.i文件进行编译,生成hello.s文件
  4. 汇编器(as)将汇编程序hello.s中的汇编语言转换成机器语言,生成一个可重定位目标文件hello.o
  5. 链接器(ld)将hello.o与可重定位目标文件和动态链接库链接成为可执行目标程序hello
  6. 在shell命令行输入./hello,shell调用fork函数创建一个子进程
  7. 加载器调用execve函数,运行hello程序
  8. 运行hello时,内存管理单元MMU、翻译后备缓冲器TLB、多级页表机制、三级cache等等分工合作,完成对内存地址的访问
  9. 运行过程中,可能遇到各种异常或者信号,可能需要进入内核调用异常处理程序。
  10. hello的输入输出与外界交互,与linux I/O息息相关
  11. hello最终运行结束,被父进程回收,内核回收为其创建的所有信息

大一阶段的我,只是堪堪在vs code中敲代码,跑结果,一遍又一遍苦恼于de不出来的bug,也许是结果跃上屏幕的速度太过于迅速,我从未想过一个小小的hello.c背后,竟然蕴含着如此庞大又精密的计算机系统体系。经过这门课的学习,我对计算机系统有了一定的深入认识,那些汇编、链接那些从未接触过的概念也在我的脑海中留下了深刻的印象。老师曾说过,对于一个学习计算机的学生,学习计算机的底层知识是极其重要的,在以后的时间里,我也将积极自学,丰富自己的知识。计算机系统是一门有着美妙的实验魅力的课程,每一次实验都会有新的收获。实践才是获得知识的最有效途径。

非常感谢老师和助教们开课以来的认真的教学和尽心的帮助!您们辛苦了!

附件

文件名称

文件功能

hello.c

源程序

hello.i

预处理后的文件

hello.s

编译后的汇编文件

hello.o

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

hello

链接后的可执行目标文件

hello_o.txt

hello.o反汇编后的文本文件

hello.txt

hello反汇编后的文本文件

hello_o_elf.txt

hello.o的ELF格式

hello_elf.txt

hello的ELF格式

参考文献

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

[1]  https://blog.csdn.net/hfut_zhanghu/article/details/122340261

[2]  https://blog.csdn.net/weixin_38314865/article/details/118277390

[3]  https://blog.csdn.net/forcj/article/details/117967945

[4]  https://www.csdn.net/tags/NtjaAgxsMjE2NzktYmxvZwO0O0OO0O0O.html

[5]  https://blog.csdn.net/qq_45656248/article/details/117898467

[6]  深入理解计算机系统原书第3版-文字版.pdf

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

               

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值