计算机系统

大作业

题     目 程序人生-Hellos P2P

专       业 计算学部

学     号 120L022417

班   级 2003008

学       生 董亦轩

指 导 教 师 吴锐

计算机科学与技术学院

2021年5月

摘  要

本文介绍了一个普通的程序——hello从C语言代码文件到可执行文件的全过程,以及hello运行时的流程。从C语言代码文件,再到预处理生成hello.i,编译生成hello.s,汇编生成hello.o,链接生成hello.out可执行文件,再到hello程序执行时的fork,execve,访问内存时的虚拟地址映射,收到信号时的信号处理,申请内存空间时的动态内存分配......直到最后的终止。本文将跟随hello的“一生”,阐释上述流程的详细过程。

关键词:编译;汇编;虚拟内存;动态内存分配;                            

(摘要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.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:如果Hello使用C语言编写,采用gcc一步步生成为hello.out文件,其实现从编程到进程的过程如下:Hello被编写成hello.c,存储于磁盘,再由IO总线加载入内存,被gcc预处理器读取,由gcc预处理器生成hello.i;hello.i由gcc编译器生成hello.s;hello.s由gcc汇编器生成hello.o文件;gcc链接器将hello.o与程序内调用的C库函数链接,生成hello.out文件。在shell中运行./hello.out命令,shell为hello.out开辟一个子进程,子进程被分配一个虚拟内存空间,由加载器跳转到_start,最终跳转到main所生成机器码所在地址,开始执行hello的功能。

020:From Zero-0 to Zero-0:shell调用hello.out之前,我们称其未调用hello的状态为Zero-0;hello调用结束后,以僵尸程序的形式保持终止状态,直到shell为其开辟的子进程被回收后,hello完全退出,shell状态回到未调用hello的Zero-0。

1.2 环境与工具

硬件环境:intel i7 Core 1065G7 1.30GHz,16G内存,475G磁盘

软件环境:win10操作系统,Orcal VM Virtual Box下Unbuntu20.04

开发与调试工具:Visual Studio 2019,CodeBlocks IDE ,edB, gcc

1.3 中间结果

hello.c:C语言源代码,hello.i:预处理后的文件,替换了宏定义,包含了头文件,

hello.s:由编译器生成的汇编代码,hello.o:由编译器生成的可重定位代码;

hello.out:由链接器生成的可执行文件。

1.4 本章小结

本章简述了hello从编写出来到预处理,编译,汇编,链接,执行,退出的全过程。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理器处理C语言代码,将宏定义实现,处理条件编译,引入头文件,删除注释,添加行号,生成.i文件。

作用:删除注释,简化代码;实现宏定义,引入头文件,添加行号,是为编译器生成汇编代码做的准备;行号方便汇编代码生成以及可能出现的错误信息的显示。

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i,执行结果如下:

图2.2.1 Ubuntu下预处理命令

2.3 Hello的预处理结果解析

首先我们打开hello.i,查看其内容,此处仅展示其开头结尾:

图2.3.1 Hello的预处理结果

可以发现,hello.i文件内容十分繁多,左侧行号清晰可见,从第3行到第3041行充满了对于头文件位置的描述。除此之外,还含有对于头文件中定义函数的引用以及一些对编译起到帮助作用的结构体与共用体的定义。程序的源代码在文件结尾得到了完整保留,如红色方框所示。

                                                图2.3.2 hello的ls-l结果

使用ls -l查看文件大小,发现hello.i文件确实占用了庞大的空间。

附:hello.c:

图2.3.3 hello.c源代码

2.4 本章小结

本章介绍了linux下hello生成预处理hello.i文件的方法,并对hello.i内容做了简要分析。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编译器通过词法分析和语法分析,进行一定程度的优化,将.i文件翻译为.s文件,.s文件为反应了原始.c文件内容的汇编代码。

作用:生成和机器码一一对应的汇编代码,将初始化后C语言代码翻译为汇编代码,向生成可执行文件更进一步。

3.2 在Ubuntu下编译的命令

gcc -S hello.i -o hello.s

图3.2.1 hello的预编译指令

3.3 Hello的编译结果解析

首先来查看hello.s的内容:

图3.3 hello的汇编结果

3.31:字符串常量的存储与使用:.LC0处存储了hello.c第14行的printf中字符串常量,printf显示字符串时,将.LC0中数据存入%rsi,作为参数,调用puts@callPLT,字符串常量显示在命令行中。

同理,.LC1处存储了hello.c第18行printf的模式字符串常量,其依然作为参数被传入%rdi中,以供调用printf@PLT所用

3.32:参数int argc的存储与使用:命令行调用函数时,参数数目argc存储在%rdi,再将%rdi存入-20(%rbp)中,后续cmpl $4,-20(%rbp)实现了由argc是否=4作为条件的的函数分支。

3.33:参数char* argv[]的存储与使用(数组访问操作):命令行调用函数时,参数字符数组首地址argv存储在%rsi,后存入-32(%rbp)中。.L4中,argv被从-32(%rbp)取出,存入%rax,通过访问argv,argv是指针数组,每个元素长度为8字节,高16字节地址中数据,即argv[2]被存入%rdx。高8字节地址中数据,即argv[1]被存入%rsi。之后调用printf@PLT,将argv[1],argv[2]作为填入模式串的参数输出。高24字节地址中数据,即argv[3]被存入%rdi,以供atoi@PLT解析使用。

3.34函数puts@PLT的使用:不带填入参数的printf函数被解析为了puts@PLT。

其第一个参数%rdi是输出字符串的地址。判断参数argv!=4后,call puts@PLT输出了存储在.LC0中字符串 "用法: Hello 学号 姓名 秒数!\n" 作为提示信息。

3.35函数printf@PLT的使用:带填入参数的printf函数被解析为了printf@PLT。其中第二个参数%rsi是输出字符串模式串的地址。其余参数从%rdx,%rcx依次类推为填入模式串的数值。在读取了argv[1],argv[2]之后,printf@PLT替换模式串中%s的值,将argv[1],argv[2]地址下字符串嵌入模式串,输出显示内容。

3.36函数atoi@PLT的使用:atoi解析一个字符串内容,返回其对应的整数,在汇编语言中映射为atoi@PLT。在读取了argv[3]之后,atoi将存储在%rdi的argv[3]解析为整数存入%rax,又被转存为%rdi,以供sleep@PLT使用。

3.37函数sleep@PLT的使用:sleep(atoi(argv[3]))使得程序等待atoi(argv[3])秒,sleep函数在汇编中映射为sleep@PLT,其参数%rdi传入atoi(argv[3]),以实现sleep的功能。

3.38函数getchar@PLT的使用:.L3中,如果-4(%rbp)的值>7,执行函数getchar,

其在汇编中映射为getchar@PLT,没有参数,返回一个字符

3.39条件分支的实现:条件分支由各种jmp语句配合test或cmp语句实现。hello程序中,cmpl $4, -20(%rbp)以及je .L2为判断argc!=4的if语句翻译为的条件跳转。

3.310循环的实现:循环同样由各种jmp语句配合test或cmp语句实现。hello程序中,.L2中movl $0 ,-4(%rbp)为循环变量初始化;.L4最后的addl $1 ,-4(%rbp)为循环变量值的改变;.L3中cmpl $7,-4(%rbp)为循环终止条件的检查,jle .L4负责实现循环条件满足时的循环体重复执行。

3.311程序终止、退出的实现:通过调用exit@PLT函数和ret语句实现。

3.312关系操作的实现:通过各种cmp语句实现

3.313加法运算的实现:通过add语句实现

3.4 本章小结

本章概述了Linux下hello.i编译生成hello.s的方法,具体分析了hello.s中对于hello.c中每部分内容的具体实现方式。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器将hello.s翻译为一条条机器语言指令,并且用这些机器语言指令生成可重定位目标程序。

作用:将汇编代码一一对应翻译为机器语言片段,完成了从代码从机器不可理解到机器可理解的最后一步转化。

4.2 在Ubuntu下汇编的命令

gcc -c hello.s hello.o

图4.2 hello的汇编指令

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。readelf -a hello.o > hello.o.elf可以获得hello的ELF文件格式,objdump -d -r hello.o > hello.o.txt可以获得hello.o的反汇编代码。

4.3.1ELF头:ELF头中16Magic序列描述了生成该文件的系统的字的大小和字节顺序,剩下的部分是对目标文件的信息的标识以及帮助链接器工作的内容。ELF头显示ELF文件类型为ELF64;数据表示类型是补码表示,小端序;ELF头的大小是64字节,文件类型是可重定位文件;操作系统是unix,系统架构是x86-64;节头部表中条目数量,即节数量是14;以及字符串表的开始位置是第13个节。

图4.3 hello.o的可重定位文件elf头

4.3.2节头:

图4.3.2 hello.o的节头

4.3.3:重定位节(.rela.text):重定位节存储需要修改地址的外部符号引用,以供链接器会通过正确计算出可重定位文件中缺省的函数调用跳转地址。

hello.o中需要重定位的内容有:.rodata中位于汇编代码.LC0中的模式串,puts,exit,.rodata中位于汇编代码.LC1中的模式串,printf,sleep,getchar,.text等符号。信息一项中,前4位是.symbol节中的偏移量,后四位是type中的偏移量。偏移量一项表示可重定位内容在.o文件中相对main函数起始位置的偏移位置,即可重定位内容在.o文件中的位置。

图4.3.3 hello的重定位节内容

4.4 Hello.o的结果解析

图4.4 hello.o的反汇编

查看hello.o文件反汇编,反汇编代码是机器语言一一逆映射生成的汇编代码。先观察每行右侧的翻译内容,发现与汇编代码基本一致,第三章中框出的几大部分完全在反汇编代码中中清晰可见。

不同之处在于:

  1. 实际机器语言中对于可重定位节中存储符号的表示方法:每个可重定位符号都被替换为表示缺省的4字节0:00 00 00 00;并且在其后附加1字节的重定位标记。同时,汇编语言中的指令全部替换为了16进制表示。
  2. 汇编代码中立即数表示为10进制,机器语言中为16进制。
  3. 汇编代码中分支跳转处衔接符号,机器语言中衔接相对地址偏移。
  4. 汇编代码中函数调用处衔接函数名符号,机器语言中衔接缺省值00 00 00 00,直接结果为跳转到下一条指令的位置。

4.5 本章小结

本章主要介绍了Linux下由hello.s生成hello.o的具体过程,具体分析了hello.o文件中ELF头的内容,节头的内容以及其机器语言反汇编代码与hello.s中汇编代码的异同。

(第4章1分)

5 链接

5.1 链接的概念与作用

概念:链接是将各种不同文件(主要是可重定位目标文件,即.o文件)的代码和数据综合在一起,通过符号解析和重定位等过程,最终组合成一个可以在程序中加载和运行的单一的可执行目标文件即.out文件的过程。

作用:链接器综合一个或多个可重定位文件与静态库等其他资源,生成可执行文件。链接这一过程实现了对于将代码分为多个文件书写,以及引用其他文件中函数的支持,让程序可以分为更小,更好管理模块独立修改,独立编译。

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.2 hello的链接指令

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

readelf -a hello> hello.elf可以获得hello的ELF文件格式,objdump -d -r hello> hello.txt可以获得hello的反汇编代码。

5.3.1ELF头:

图5.3.1 hello的elf头

5.3.2节头:

图5.3.2 hello的节头

5.4 hello的虚拟地址空间

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

图5.4 hello的虚拟空间内容

查看hello的虚拟空间地址,发现内存中段与段之间存在较大空隙。与5.3中各个段的虚拟空间地址进行对比,发现.data段内容相对较多,节头中每个内存中位置不为缺省值0的段都可在虚拟内存相应位置找到;.rodata段中,可以找到hello.c中定义的两个字符串常量。

5.5 链接的重定位过程分析

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

结合hello.o的重定位项目,分析hello中对其怎么重定位的。

图5.5 hello的反汇编

与第四章4.4中hello.o文件内容对比,hello中main中机器语言代码基本相同,但其余内容有所不同:

  1. hello增加了对hello.o重定位节中引用函数的机器语言定义;
  2. hello增加了一些新的段:.init段,.plt段;
  3. .text段中增加了_start;
  4. hello.o文件中函数调用跳转地址的缺省值(尤其是main函数中对其他函数的调用)被替换为确定的虚拟内存地址。
  5. hello文件中控制流跳转不再是相对地址偏移,而是绝对的虚拟内存空间地址。

hello将hello.o中重定位项目的对应函数的机器代码定义添加在了文件内;在hello.o文件内容中,对这些函数的call 机器语言指令衔接的缺省值被替换为相应函数机器代码在文件内的起始位置,即函数的具体地址。hello.o文件中的控制流跳转也被重新修改为绝对的虚拟内存空间地址。最后删除重定位段,添加一些运行必要的段和内容,例如.init,.plt,_start等内容。链接器通过查看重定位段内容和各个链接资源中对于符号的定义内容,不断向未知符号表添加删减元素,合并多个.o文件中的段,检查是否有符号重复定义,区分强弱符号,修改合并后的.o文件内容来生成hello文件。

5.6 hello的执行流程

图5.6 hello的执行流程追踪

首先使用edb ->plugins->BreakpointManger,把5.5中可见的程序函数定义起始地址全部打上断点;顺序执行文件,我们得到了hello的执行流程:

程序名称                                 程序地址

hello!_start                        0x0000000000400100

hello! __libc_csu_init        0x00000000004011c0

hello!_init                           0x0000000000401000

hello!main                         0x0000000000401125

hello!.plt+70                      0x0000000000401090

hello!_put@plt                  0x0000000000401030

hello!exit@plt                   0x0000000000401070

hello! _fini ()                      0x0000000000401238

5.7 Hello的动态链接分析

图5.7.1hello的动态链接条目

objdump -R查看hello GOT位置:

dl_init前:

dl_init后:

图5.7.2 hello的GOT内容

通常来说,程序链接动态库之后,采用GOT和PLT协同配合动态调用动态链接器,在第一次调用函数时GOT中的指向动态链接器,将函数重定位,并把重定位结果写入GOT中。GOT的值被修改后,再次调用函数,GOT中的值指向动态库函数首地址,直接完成调用。

5.8 本章小结

本章主要介绍了linux下链接的过程,分析了hello文件的具体内容,运行流程,以及程序如何在运行时动态链接。

(第5章1分)

6 hello进程管理

6.1 进程的概念与作用

概念:程序是指令、数据及其组织形式的描述,进程是程序的实体,程序运行在进程的上下文中;进程还是系统进行资源分配和调度的基本单位,是操作系统结构的基础。父进程可以通过fork()来创建一个子进程,通过execve()在进程中运行一个程序。

作用:进程给程序独占整个CPU以及地址空间的错觉,方便了代码编写以及错误诊断;对于进程的调度也很好实现了系统资源给不同程序的分配。

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

Shell是一个交互型的应用级程序,代表用户运行其他程序。Shell的作用是:通过分析用户在的输入,根据特定输入来执行不同的操作,实现了用户通过控制命令行的输入要执行需要的操作。

处理流程:以Shell命令行输入./hello 120L d 2为例:

  1. Shell命令行读入用户输入的字符串,解析为命令行命令,发现不是内部指令,解析为对hello的运行命令。
  2. hello的运行命令被解析为一个对函数evecve(const char *filename, const char *argv[],const char *envp)的调用,hello被解析为参数filename;hello,120L,d,2分别作为参数argv的argv[0],argv[1],argv[2],argv[3];当前环境变量被填入envp。
  3. Shell调用fork()新建一个子进程,子进程中虚拟地址空间为父进程的拷贝。
  4. Shell根据2中解析结果在子进程中调用evecve()函数,在子进程上下文中运行程序hello。加载器将hello的5.32中节头定义的节的信息加载入子进程的虚拟地址空间,如5.4中图片所示。
  5. 控制经过5.6中的跳转流程,最终跳转到hello的main,执行hello程序的功能。
  6. hello程序终止后,子进程僵死,父进程将其回收后,子进程终止。Shell命令行回到初始状态。

6.3 Hello的fork进程创建过程

Shell命令行调用函数fork(),新建一个子进程,子进程与父进程并行运行,拥有与父进程不同的PID,以及与父进程相同的虚拟地址空间拷贝,代码,数据段,共享库,堆和用户栈。完成这些任务后,fork()函数在在父、子进程中分别返回:在父进程中返回子进程的PID,在子进程中返回0。

6.4 Hello的execve过程

子进程被fork()创建以后,子进程调用execve函数来在自己的上下文中加载、运行hello。将参数filename,argv,envp传入之后,execve函数首先检查当前目录下是否存在hello文件。不存在时,execve返回到fork(),命令行显示错误信息,fork()终止后退出;hello存在时,execve将hello加载到子进程中,并且调用启动代码。启动代码设置栈,并且将控制交给hello的main。

6.5 Hello的进程执行

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

Hello在自己的上下文中运行,操作系统为每个进程维护一个上下文,确保控制转移时不会发生信息丢失。当Hello运行一段较长时间以后,或是调用函数sleep以后,CUP调度将控制权由Hello转到其他程序,这一过程通过以下过程实现:

  1. CPU触发系统调用,控制由用户模式运行Hello先转到内核模式,
  2. 控制从内核模式切换到用户模式,运行其他进程。

当其他进程运行一段时间后,或sleep函数休眠周期结束后,CUP调度将控制转换回Hello,其实现过程如下:

  1. CPU触发系统调用,控制由用户模式运行Hello先转到内核模式,

2. 控制从内核模式切换到用户模式,运行其他进程。

6.6 hello的异常与信号处理

6.61正常运行:

图6.6.1 hello正常运行

6.62运行后按下Ctrl+C 6.63运行后按下Ctrl+Z

图6.6.2 hello接收来自键盘的信号

按下Ctrl+C后,hello收到SIGINT信号,引起终止异常。

按下Ctrl+Z后,hello收到SIGTSTP信号,hello被挂起。

6.631输入ps: 6.632输入pstree:

图6.6.3 hello的ps指令

执行ps命令,得知hello的PID为31893

6.633输入jobs:

图6.6.4 hello的jobs指令

执行jobs,的值hello jobs号为1

6.634输入fg 1,hello继续执行: 6.64中途乱按:

图6.6.5 hello的前后台切换与乱按测试

中途乱按时,字符串被载入缓冲区;由于hello只执行一次getchar(),所以hello运行结束后,剩余的字符串被当作位置指令。命令行输出错误提示信息。

6.65Ctrl+Z后kill -9 hello的PID:

图6.6.6 使用kill向hello发送信号

hello被杀死,进程终止。

6.7本章小结

本章给出了进程的定义,阐述了进程和程序的关系,介绍了Linux下hello运行时fork创建子进程、evecve加载的流程与hello进程执行过程,分析了hello收到不同信号发生的异常,以及进程作出的回应。

(第6章1分)

7 hello的存储管理

7.1 hello的存储器地址空间

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

7.11逻辑地址:程序会编译后出现在汇编语言代码中的地址。用来在代码中以相对偏移的方式,指定一个操作数位置或者一个指令在虚拟内存中的位置。逻辑地址是虚拟空间中相对的地址。逻辑地址可分为两部分:段选择符和段偏移量。汇编语言代码经过汇编生成课重定位文件,以及链接重定位,逻辑地址转化为线性地址。

7.12线性地址:逻辑地址加上一个所在段中偏移量得到的地址。用来在代码中以虚拟空间绝对地址的方式,指定一个操作数或者一个指令在虚拟内存中的位置。线性地址是虚拟空间中绝对的地址。

7.13虚拟地址:虚拟地址是我们学习的课程中对于线性地址的称呼。广义来说,逻辑地址和线性地址所描述的都是虚拟空间中地址,都是虚拟空间中的地址。虚拟地址和物理地址可以相互转化。

7.14物理地址:物理地址是在物理内存中,也就是实际硬件中,代码中数据、指令存储的地址。以绝对地址的方式表式硬件中数据、指令的位置。物理地址是物理存储空间的绝对的地址。

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

Intel的逻辑地址长48位,前16位为段选择符,32位长为段偏移量。

段偏移量就是我们在汇编语言代码中可以看到的语句标号

图7.2.1 段选择符

在16位段选择符中,高13位是段索引,表示段描述符在存储其的描述表中的位置。TI为0时,表示段描述符存储在全局描述符表(GDT)中;TI=1时,表示段描述符存储在局部描述表(LDT)中。GDT和LDT的首地址存储在用户不可见的寄存器中。CDT和LDT存储在内存中。RPL描述段的级别。Linux中,00表示内核段,11表示系统段。

根据段选择符在描述符表中找到段描述符。段描述符长度为8字节,这八个字节具体存储位置如下图所示:

图7.2.2 段描述符

具体说明每一项的含义:

B: 段的基地址。                 L:段的最大页号。

G: 内存每页的大小。         G=1,每页4KB; G=0,每页1KB

D: 段偏移量宽度。             D=1,32位; D=0,16位

S: 段的种类                        S=0,系统控制段; S=1,data段,text段

A: 段的访问历史                 A=0,未被访问; A=1,已被访问过

PDL: 段的访问权限限制。 PDL=0,仅内核可访问;PDL=3,不限制权限。

P: 段是否存在。                 P总是等于1

TYPE: 段的种类信息

通过检查段选择符指向的段描述符的段描述符内容,排除越级的段访问以及一系列非法的访问,取出段的基地址,加上已有的段偏移量,得到逻辑地址对应的虚拟地址。

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

线性地址又叫虚拟地址。方便起见,下文均以虚拟地址代替之。

物理内存(PM)作为虚拟内存(VM)的全相联cache,称作DRAM。虚拟内存中每个虚拟页(PA)被映射到一个物理内存中的物理页(PA)中。虚拟地址翻译即为翻译虚拟地址到其映射到的物理页对应地址。

虚拟地址分为两部分:虚拟页号(VPN)和虚拟页面偏移量(VPO)。物理地址同样具有两部分,物理页号(PPN)和物理页偏移量(PPO)。由于物理页大小和虚拟页大小一致,所以地址映射时,VPO和PPO始终保持一致,由虚拟地址翻译物理地址时,同样有PPO=VPO。因此,翻译的重点在于将VPN翻译为PPN。

图7.3 PTE与PA/VA的关系

将翻译具体实现简化来看,物理内存中存在页表(PT),页表中每个页表条目(PTE)存储着每个虚拟页对印的物理页地址。

VPO到PPO的变换过程由页表参与完成:

  1. 处理器(CPU)生成一个虚拟地址(VA),将其传递给MMU,
  2. MMU从TLB查找VPN
  3. 3.1VPN对应标志位不为0时,直接返回PTE;

3.2否则MMU生成一个PTE地址(PTEA),

向内存(或缓存)中请求得到PTE

  1. 4.1若PTE标志位不为0时,MMU由PTE中内容构建PA,完成翻译。

4.2否则表示页不再物理页内,引发缺页异常。内核加载页后,重新执行 引发异常的指令。

  1. 翻译出的PA会作为请求传递给内存(或缓存),完成应有的工作。

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

虚拟内存到物理内存的翻译过程中,页表的引入也带来了一些问题。

页表的访问造成了一些时间开销。如果页表存储在物理内存中,访问速度较慢,而恰巧每次访存都需要页参与,因此TLB作为页表的cache出现了。TLB每个块分为两部分:TLB标记(TLBT)和TLBT索引(TLBI)。

由于虚拟内存空间很大,在Corei7中,甚至大过物理内存,对每个VPN都建立一个PTE会产生很大的空间开销。为此设立了多级页表。在Corei7中,为四级页表。第一级页表PTE指向一个二级页表基地址,二级页表PTE指向一个三级页表基地址,三级页表PTE指向一个四级页表基地址,四级页表PTE中才包含着虚拟页VPN对印的的PPN。

当上级页表PTE为空,其对应的下级页表不会存在。多级页表第二级往往保存于磁盘,常用的二级页表才会被缓存到主存中。具体过程如图所示。

图7.4 四级页表下TLB参与的地址翻译示意图

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

已经得到了PA,下一步就是根据PA去寻找是数据或指令。Corei7处理器有三级cache,尝试访问每级cache时,物理地址被拆分为三部分:

  1. 组标记(CT):cache的每组映射的地址不连续,CT表示为地址位于映射的所有地址中的第几段。Corei7中,L1cache的CT长度和PPN等长,十分方便。
  2. 组索引(CI):cahe一般为组相联。CI表示位于cache中第几组。
  3. 块偏移(CO):位于某一组下的第几块。

由CI首先找到某一组,再由CO找到某一块,若块有效位为0或标记不等于CT,cache不命中,访问下一级cache或者直接由PPN,PPO访问物理内存;否则cache命中,直接访问这一块中的数据即可。

图7.5 三级cache支持下的物理内存访问示意图

7.6 hello进程fork时的内存映射

hello进程的fork函数被调用时,内核为新进程创建各种数据结构,并且为新进程分配一个唯一的PID。为了给新进程创建虚拟内存,内核将当前进程的mm_struct、区域结构和页表复制给子进程。

父子进程每个虚拟页面都为只读,虚拟页面的每个区域结构都是私有写时复制,在任一一个进程写某个区域结构时,都会创建一个新物理页面,将改写的区域结构存入这个页面,并且修改相应PTE。

图7.6 私有写时复制机制示意图

7.7 hello进程execve时的内存映射

hello进程执行execve语句,主要经历以下几个步骤:

  1. 删除已存在的用户区域。
  2. 映射私有区域:

新程序的代码、数据、bss和栈区域都被创建为私有写时复制。

代码区域映射到.text,数据区域映射到.text,bss区域映射为请求二进制0 的匿名文件,栈、堆区域也映射为请求二进制0的匿名文件。

  1. 映射共享区域:

hello链接的共享库之类的共享对象作为共享对象映射到共享区域。

  1. 设计程序计数器(PC):

设置PC到程序代码区域入口。

图7.7 hello的内存映射

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

实际计算机系统中的页表带有控制程序对页访问、读、写权限的许可位。程序运行时请求访问一个地址时,发现页表中地址所在页对应PTE有效位为0,请求的虚拟地址所在页不在物理内存中;或请求不符合PTE的许可限制,引发缺页中断。

图7.8 带权限位的PTE

程序运行过程中发生缺页故障时,内核接管控制进行缺页中断处理。

可能有以下几种情况:

  1. 如果中断由访问未定义的页引发,内核触发一般保护异常,抛出“段错误”;
  2. 如果中断由违反PTE许可位引发,内核触发一般保护异常,抛出“段错误”;
  3. 如果中断由PTE有效位为0产生,将页面从磁盘中加载入物理内存。如果 物理内存已满,则选择一个一个牺牲页,检查牺牲页是否配修改,如果是, 换出之前将其写回磁盘;将其换出。

7.9动态存储分配管理

7.9.1动态内存分配器:

动态内存分配由动态内存分配器完成。动态内存分配器维护这一个动态内存区域,称作堆。堆顶由指针brk指定。

动态内存分配器分为隐式和显式两种。隐式分配器也叫垃圾回收器,不要求写明何时释放已分配的块。由于C语言不区分数据与地址,所以不产生碎片的C语言隐式分配器是不存在的。

显式分配器要求应用指明何时释放分配的块。

7.9.1显示分配器:

显式分配器有一些严格的约束条件:

  1. 处理任意请求序列 4.对齐块
  2. 立即响应请求 5.不修改已分配的块
  3. 只使用堆

一种用来实现显式动态分配器的策略是使用隐式空闲链表。每个块的头部用来存储空闲链表的一个节点,分配器返回的地址是之后可使用的的有效载荷。

7.10本章小结

本章介绍了hello的虚拟内存映射全过程,hello在虚拟内存中fork和execve的过程以及hello的动态内存管理。

(第7章 2分)

8 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

Linux中所有的IO设备都被抽象为文件。所有的输入操作和输出操作都被抽象为模型的读和写操作来执行。

设备管理:unix io接口

8.2 简述Unix IO接口及其函数

Unix IO接口

每个文件的操作通过unix io接口实现。操作主要分为以下几种:

  1. 打开文件:应用向内核请求打开文件,内核返回一个一个非负整数,称为文件描述符。内核记录有关文件的信息,应用程序只需要使用描述符表示文件。
  2. 每个进程开始时有三个文件:标注输入(描述符0)、标准输出(描述符1)、标准错误(描述符2)。
  3. 文件打开时当前位置为距起始0字节偏移。使用seek函数可以显式设置文件当前位置。
  4. 读文件:从文件当前位置复制n字节(n>0)到内存,文件当前位置+n,如果当前位置超出文件末尾,触发end-of-file(EOF)条件。文件末尾没有EOF符号。
  5. 写文件:与读文件相似,从内存复制n字节(n>0)到内存,文件当前位置+n,文件大小会因为写操作改变。
  6. 关闭文件:应用完成文件操作后,通知内核关闭文件。内核释放相关数据结构,恢复该文件占用的描述符为可用。一个进程终止时,其所有打开的文件都会被关闭。

UnixIO函数

  1. int open(char *filename,int flags,mode_t mode):

open函数打开地址为filename的一个文件,若不存在,则创建一个新文件。然后返回描述符。描述符总是当前可用描述符池中最小的那个。

flags为O_RDONLY:只读 为O_WRONLY:只写

为 O_RDWR:可读可写

mode指定了创建新文件时新文件的访问权限。

  1. int close(int fd):

close函数关闭一个文件,fd是文件的描述符。

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

read函数通过文件读操作从描述符为fd的文件当前位置复制n字节到内存地址buf。返回-1表示发生错误,返回0表示发生EOF,否则返回传送字节数。

  1. ssize_t write(int fd,const void *buf,size_t n):

write函数通过文件写操作从内存位置buf复制n字节到描述符为fd的文件当前位置。发生错误时返回-1,否则返回传送字节数。

  1. lseek函数可以显式改变文件当前位置。

8.3 printf的实现分析

参考[转]printf 函数实现的深入剖析 - Pianistx - 博客园

首先查看printf源码:发现调用了vsprintf函数和write函数。

图 8.3.1  printf源代码

首先分析printf函数,va_list即为char* ,arg指向了缺省的第一个参数。

从vsprintf函数处理完buf返回后,调用系统函数write将buf的前i个字节写到标准输出。

图8.3.2 vsprintf源代码

vsprintf解析输入的模式串,依次解析参数列表args的每一位,将其替换入模式串。最后返回模式串的长度。

回到printf函数,在执行write后,标准输出文件中存储着待输出字符的ascii码。字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。显示芯片会按照一定的刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量),至此字符串就显示在了屏幕上。

8.4 getchar的实现分析

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

调用getchar时,系统先检查键盘缓冲区中是否有字符,有则读一个字符返回;

否则挂起。用户按下的每一个按键组合,如果有对应的字符,就会在键盘缓冲区中增加一个对应字符。等待用户按下键,getchar再调用系统函数read读取键盘缓冲区中第一个字符。

8.5本章小结

本章介绍了Linux管理IO的方法,以及使用printf和getchar函数举例,分别阐释了Linux系统向IO设备输出数据,从IO设备读入数据的一般过程。

(第8章1分)

结论

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

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

本文介绍了一个普通的程序,hello从无到有的全过程。具体流程如下:

  1. hello.c的诞生:hello的C语言代码被编写出来。
  2. hello的预处理:预处理器将hello调用的外部库展开合并入hello,添加行号,实现宏定义,删除注释,增设一些数据结构,方便编译,生成hello.i文件。
  3. hello的编译:编译器将hello.i编译为汇编语言代码hello.s
  4. hello的汇编:汇编器将hello.i翻译为机器语言指令,生成可重定位文件hello.o。
  5. hello的链接:链接器将hello与动态链接库链接,生成hello.out可执行文件。
  6. 运行:hello在Shell被作为一个外部调用执行。在运行的过程中,hello第一次调用动态链接库中定义的函数和数据结构时,GOT和PLT的协同操作会使控制跳转到动态链接器对hello进行动态链接。
    1. hello进程的fork:Shell为hello开辟一个子进程。
    2. hello进程的execve:Shell调用execve,启动加载器在子进程上下文中加载hello的代码段、数据段,最后控制跳转到hello的main函数处,执行hello功能。
    3. 访问内存:每个访问虚拟地址的请求被MMU翻译为一个物理地址。如果物理内存中不存在这个虚拟地址,触发缺页异常,页被加载后重新执行访问指令。
    4. 动态申请内存:hello中的printf函数调用显示内存分配函数malloc在用户堆中申请内存。
    5. 信号管理:hello函数正常终止时,父进程终端收到SIGCHLD信号;按下Ctrl+C时,终端转发SIGINT信号给hello,hello终止。按下Ctrl+Z时,终端转发SIGTSTP信号给hello,hello停止。
  1. 终止:hello终止后,Shell收到SIGCHLD信号,内核删除为hello维持的所有数据结构。Shell回到未执行hello的状态。

经历这样一番漫长的过程,我的感悟颇多:

  1. cache是一个十分重要的思想,缓存极大的提高了数据的访问速度。缓存的思想也可活用于生活之中。
  2. 抽象是一个十分重要的概念。虚拟内存是对物理内存的抽象,文件是对IO设备的抽象,抽象让我们可以用泛化的方法高效处理不同的问题,大大提高了生产效率。抽象的思想也可广泛运用于生活之中。
  3. 在hello.c直到hello.out非一蹴而就,分解细化,逐层实现的分步骤思想也可活用于生活之中。

在知识的泥土中埋下种子,用实践的汗水灌溉,创新的理念会随实践发芽。

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

附件

中间产物文件名 功能

hello.c 源程序

hello.i 预处理后文件

hello.s 编译后的汇编文件

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

hello 链接后的可执行文件

hello.o.elf hello.o的ELF格式

hello.o.txt hello.o的反汇编

hello.txt hello的反汇编代码

hello.elf hello的ELF格式

(附件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] CSDN博客 段页式访存——逻辑地址到线性地址的转换 - 简书 作者madao756

[8]  [转]printf 函数实现的深入剖析 - Pianistx - 博客园 博客园

[9] 深入理解计算机系统 Randal E.Bryant, David R.OHallaron卡内尔-梅隆大学

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

计算机系统-大作业-hello的一生-哈尔滨工业大学2020级相关推荐

  1. 2021春深入理解计算机系统大作业---hello的一生

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算学部 学    号 120L021725 班    级 2003006 学       生 杨楠 指 导 ...

  2. 2022秋计算机系统大作业-hello的一生

    计算机系统 计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机科学与技术 计算机科学与技术学院 2022年5月 摘  要 本文通过详细分析hello从预处 ...

  3. 哈工大计算机系统大作业 HELLO的一生

    程序人生 HELLO's P2P 计算机科学与技术 吴嘉阳 2021113679 摘  要 本文串联计算机系统所学知识,以hello.c程序为例,阐述它在linux系统x86-64环境下从编写到运行终 ...

  4. 哈工程转专业计算机,哈尔滨工业大学2020级转专业情况看冷热

    哈工大各学院都先后公布了2019级本科生入学一年后转专业的结果,其中计算机学部.航天学院等都是转入人数比较香的院系,而土木工程学院则比较惨,申请转入土木工程学院的仅有1名建筑学院风景园林专业的学生申请 ...

  5. 哈尔滨工业大学 计算机系统 大作业 22春

    计算机系统大作业 计算机科学与技术学院 2022年5月 摘 要 摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息.摘要应包括本论文的目的.主要内容.方法.成果及其 ...

  6. 【程序人生-Hello‘s P2P】哈尔滨工业大学深入理解计算机系统大作业

    计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 xxxx 学 号 2021xxxx 班 级 210xxxx 学 生 xx 指 导 教 师 xxx 计算机科学与技术学院 2023年 ...

  7. 哈尔滨工业大学 计算机系统大作业

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机类 学   号 120L022010 班   级 2003007 学       生 窦馨宇 指 导 教 ...

  8. 哈尔滨工业大学计算机系统大作业2022春

    计算机系统 大作业 题     目 程序人生-Hello's P2P 专       业 计算机 学   号 120L02**** 班   级 2003005 学       生 无敌飞龙 指 导 教 ...

  9. 哈尔滨工业大学计算机系统大作业--程序人生

    计算机系统   大作业 题     目  程序人生-Hello's P2P      专       业   计算机科学与技术        学    号        2021110xxx      ...

最新文章

  1. 浏览器本地mysql_IndexedDB:浏览器里的本地数据库
  2. ALGO-117_蓝桥杯_算法训练_友好数
  3. Java 理论与实践: 线程池与工作队列
  4. JavaScript性能优化【上】-- 内存管理、垃圾回收
  5. 从 CentOS 5.5 中精简出属于自己的专属Linux(二)
  6. 使用剪切板[4]: 如果把子控件一起复制? - 同时回复 ghd2004 的问题
  7. 第二章 第七课 Scratch作品:神奇的小猫(脚本块:外观)
  8. python中index什么意思_index在python中是什么意思
  9. windows系统c++/VS2019编译gRPC
  10. arcgis的python安装包_用Python使用arcgis中站点包arcpy
  11. CentOS 命令使用
  12. 2022年流动式起重机司机考题及模拟考试
  13. LDPC码简介【定义、特点、算法、Tanner】
  14. 企业wifi安全管家,贴身又暖心的wifi管理系统
  15. 前端 学习笔记 【持更】
  16. 2022.12.9 英语背诵
  17. 【2021-09-15】封装、继承、多态(作业)
  18. 第二周总结(2018-03-05~2018-03-09)
  19. Eclipse 字体推荐
  20. unity2d游戏开发系列教程:二、新建工程并熟悉Unity编辑器常用功能

热门文章

  1. 计算机专业高数学科难度,大学里极其有难度的4个专业,挂科率很高,不是学霸学不明白...
  2. c++算法的上界下界
  3. Elasticsearch:使用 Filebeat 从 Node.js Web 应用程序提取日志
  4. 上界通配符、下界通配符
  5. mac环境下搭建frida环境并连接网易mumu模拟器
  6. html页面上传文件mui,mui 文件上传注意问题
  7. 学习篇-Activiti-29-流程定义存储表
  8. 工具--Typora详解
  9. OpenVINO常用PythonAPI详解与演示
  10. lly的瞬移方块(并查集)