计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 人工智能领域方向(2+X模式)
学 号 2022110864
班 级 22WL022
学 生 宋雨桐
指 导 教 师 刘宏伟
计算机科学与技术学院
2024年5月
这门课通过其独特的程序员视角,以生动的方式揭示了计算机系统的内在工作原理,从数据表示到机器级编程,再到处理器架构、内存层次、系统级I/O和网络编程等方面,全面覆盖了计算机系统的核心概念。它不仅提供了深刻的理论讲解,还通过丰富的实验和编程练习,将理论与实践紧密结合,使我们在实际操作中理解和掌握复杂的系统行为。课程结构精妙,内容深入浅出,真正实现了帮助程序员从系统层面理解和优化程序的目的。
关键词:计算机系统;工作原理;机器语言
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第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 -
参考文献....................................................................................... - 16 -
第1章 概述
- P2P:From Program to Process。P2P是Linux中,hello.c经过预处理器的预处理、编译器的编译、汇编器的汇编、链接器的链接,最终成为可执行目标程序hello,在Shell中键入启动命令后,Shell为其fork产生子进程的过程。
- 020:execve在当前进程中载入并运行一个新程序,映射虚拟内存,程序开始载入物理内存,进入 main函数执行目标代码,CPU为运行的程序分配时间片执行逻辑控制流,等待程序运行结束后,Shell父进程负责回收hello进程,内核删除相关数据结构的过程。
1.2 环境与工具
硬件:Intel i7 CPU
软件:WSL2
工具:gdb,edb,readelf
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello | 链接之后的可执行目标文件 |
hello.c | hello.c |
hello.elf | hello的ELF格式 |
hello.i | 预处理 |
hello.o | 汇编 |
hello.objdmp | 反汇编 |
hello.s | 编译产生的汇编 |
hello.txt | hello.o的ELF格式 |
1.4 本章小结
本章主要简单介绍了hello的P2P,020过程。在本章的最后,按照要求列出了本次实验信息、环境、中间结果,和该篇论文完成所需要生成的一些中间文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理器(cpp)根据以字符#开头的文件hello.c,修改原始的C程序。比如hello.c中第一行的#include <stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入到程序文本中。结果就得到了另一个C程序,通常是以.i作为文件扩展名。
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
生成了hello.i文件,如下图所示:
2.4 本章小结
本章主要介绍了预处理的定义与作用,并结合预处理之后的程序hello.i对预处理的过程和结果进行了解析。预处理是将程序在编译之前进行的一步处理,使得程序中的宏引用被替换掉。最后我们可以看到,程序从hello.c到hello.i。生成的程序hello.i还是一个文本 文件。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译器(cc1)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。汇编语言是非常有用的,因为它为不同高级语言的不同编译器提供了通用的输出语言。例如,C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1 数据类型
表1:x86-64中C语言数据类型的大小
C声明 | Intel数据类型 | 汇编代码后缀 | 大小(字节) |
char | Byte | b | 1 |
short | Word | w | 2 |
int | Double word | l | 4 |
long | Quad word | q | 8 |
char * | Quad word | q | 8 |
float | Single precision | s | 4 |
double | Double precision | l | 8 |
3.3.2 操作
- mov操作:可将一个寄存器中的值复制给另一个寄存器,或寄存器中的值存入主存,或取出主存中的值并赋值 给寄存器。
- push和pop操作:push指令将寄存器中的值压入栈中,pop将栈顶的值弹出并存入寄存器。
- 逻辑运算操作:
- leaq: mov操作的变种;
- INC, DEC, NEG, NOT;
- ADD, SUB, IMUL, XOR, OR, AND;
- SAL, SHL, SAR, SHR:注意右移分为算术右移和逻辑右移;
- CMP:比较;
- TEST:与AND指令一致,除了test要设置状态码;
- SET:基于状态码的组合来设置单个bit;
- JMP:根据状态码跳转至某个标签处;
- CMOV:根据状态码决定是否进行mov操作。
四种状态码为:CF(unsigned overflow), ZF(Zero), SF(Negative), OF(Signed Overflow)。
3.4 本章小结
本章主要说明了编译器是如何处理C语言的各个数据类型以及各类操作的,通过结合hello.c到hello.s汇编代码之间的映射关系来说明这个编译的过程,并详细解析汇编代码的逻辑关系。可以看到,我们的编译器ccl将hello.i这个文本文件编译成了hello.s里的汇编代码。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
- 概念:将用文本表示、给出单个指令的汇编语言转换成计算机能识别的二进制(汇编语言)。
- 作用:这一过程根据计算机的不同,产生的汇编语言文件也不同,这样就生成了可重定位文件,最后通过链接这一步骤,转换成可执行的程序。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.4 Hello.o的结果解析
- 机器语言的构成:地址、指令和可重定位索引;
- 与汇编函数映射关系:
- 第三章中的.s文件没有地址;
- 两者指令类型一致;
- 第三章中的.s文件的mov指令使用了十进制数;
- 第三章中的JMP指令的操作数不是十六进制的地址,而是条目。
4.5 本章小结
本章节我们观察了hello.o的elf文件格式的信息。通过查看反汇编代码,对比hello.s,看到了在函数调用、条件转移等过程中的区别。可以发现,编译之后的汇编将我们的程序向能够在计算机上运行的方向又推进了一大步。在汇编这步中,我们使其变成一个relocatable file,这时候就可以进行下一步的链接,最终生成excutable file。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
- 概念:将各个代码文件和数据组合成一个文件,这个文件可以被加载到内存、可以被电脑执行。
- 作用:允许多个源代码文件单独编译,利于代码修改,大大降低编译时间。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
5.4 hello的虚拟地址空间
使用gdb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
可见ELF被映射到了0x400000 。
5.5 链接的重定位过程分析
- 增加的节:增加了.init和.plt节,和一些节中定义的函数。
- 函数调用:hello中无hello.o中的重定位条目,并且跳转和函数调用的地址在hello中都变成了虚拟内存地址。对于hello.o的反汇编代码,函数只有在链接之后才能确定运行执行的地址,因此在.rela.text节中为其添加了重定位条目。
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
ld-2.31.so!_dl_start | 0x7ffff7fe3293 |
ld-2.31.so!_dl_init | 0x7f9f48629630 |
hello!_start | 0x0x4010f0 |
ld-2.31.so!_libc_start_main | 0x7f9f48249ab0 |
hello!printf@plt | 0x401040 |
hello!sleep@plt | 0x401080 |
hello!getchar@plt | 0x401050 |
libc-2.31.so!exit | 0x7fd6cd96fa40 |
5.7 Hello的动态链接分析
在函数调用时,首先跳转到PLT执行.plt中操作,第一次访问跳转时GOT地址为下一条指令,将函数序号入栈,然后跳转到PLT[0],之后将重定位表地址入栈,访问动态链接器,在动态链接器中使用在栈里保存的函数序号和重定位表计算函数运行时的地址,重写GOT,返回调用函数.之后如果还有对该函数的访问,就不用执行第二次跳转,直接参看GOT信息。
5.8 本章小结
本章主要介绍了链接的概念与作用、hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。ld使得分离编译成为可能。当分离编译时,我们可以考虑把程序分解为更小、更好管理的模块,可以独立地修改和编译。这有助于我们解耦合的编程思想的思想。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
- 进程就是一个执行中的程序实例。每次用户通过向Shell 输入一个可执行目标文件的名字,并运行程序时,Shell 就会创建一个新的进程。
- 进程为用户提供以下假象:程序好像是系统中当前运行的唯一程序一样,独占地使用处理器和内存,CPU仿佛无间断的执行程序中的指令,程序中的代码和数据仿佛是系统内存中唯一的对象。
6.2 简述壳Shell-bash的作用与处理流程
Shell是一个应用程序,在操作系统中提供了一个用户与系统内核进行交互的界面。处理过程一般是这样的:
- 从终端读入输入的命令。
- 将输入字符串切分获得所有的参数
- 如果是内置命令则立即执行
- 否则调用相应的程序为其分配子进程并运行
- Shell应该接受键盘输入信号(ctrl+c,ctrl+z),并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
Shell(父进程)通过fork 函数创建一个新的运行的子进程。新的子进程与父进程十分相似(最大区别是PID),子进程有父进程虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程进程还获得与父进程任何打开文件描述符相同的副本
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个程序。加载并运行可执行目标文件,且带参数列表argv (char *)和环境变量列表envp (char *)。只有当出现错误时,例如找不到filename时, execve 才会返回到调用程序。fork 一次调用返回两次不同, 而execve 调用一次并从不返回
6.5 Hello的进程执行
hello初始运行在用户模式,在hello进程调用sleep之后陷入内核模式;内核处理休眠请求主动释放当前进程,并将hello进程从运行队列中移出加入等待队列,定时器开始计时,内核进行上下文切换将当前进程的控制权交给其他进程,当定时器到时时发送一个中断信号;此时进入内核状态执行中断处理,将hello进程从等待队列中移出重新加入到运行队列,成为就绪状态,hello进程就可以继续进行自己的控制逻辑流了。
6.6 hello的异常与信号处理
- 异常分类:中断、陷阱、故障、终止。
- 信号:
- 中断处理:中断是异步发生的,是来自处理器外部的 I/O 设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。硬件中断的异常处理程序常常称为中断处理程序。
- 陷阱处理:陷阱是有意的,是执行一条指令后的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
- 故障 (Faults):不是有意的,但可能被修复。Examples: 缺页(可恢复),保护故障(protection faults,不可恢复), 浮点异常(floating point exceptions,不恢复)。处理程序要么重新执行引起故障的指令(已修复),要么终止。
- 终止处理:终止是不可恢复的致命错误造成的结果,Examples: 非法指令,奇偶校验错误(parity error),机器检查(machine check)。会中止当前程序
6.7本章小结
本章介绍了进程管理的一些简要信息,比如进程的概念作用,Shell的基本原理,Shell调用fork和execve函数,进程在执行时收到不同信号,运行ps jobs pstree fg kill 等命令的处理)。还介绍了一些常见异常和其信号处理方法。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
(以下格式自行编排,编辑时删除)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
- 逻辑地址:程序代码经过编译后出现在汇编程序中地址。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。
- 线性地址(虚拟地址):逻辑地址经过段机制后转化为线性地址(虚拟地址),是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。如果启用了分页机制,那么线性地址可以再经过变换以产生一个物理地址;如果没有启用分页机制,那么线性地址直接就是物理地址。
- 物理地址:CPU地址总线传来的地址,由硬件电路控制(硬件是可编程的)一个使用物理寻址的系统:在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写;一个使用虚拟寻址的系统:在使用了虚拟存储器的情况下,虚拟地址送到存储器管理单元MMU,把虚拟地址映射为物理地址。在hello程序中,就表示为这个程序运行时的一条确切的指令在内存地址上的具体哪一块进行执行。
- 首先,给定一个完整的逻辑地址 [段选择符:段内偏移地址]:
- 先看段选择符的T1=0还是1,就知道当前要转换是GDT中的段,还是LDT中的段。根据相应寄存器,得到其地址和大小。
- 再拿出段选择符中前13位,查找到对应的段描述符,得到基地址Base
- Base + offset=要转换的线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
Linux将虚拟内存组织成一些段的集合。
- task_struct是内核为hello进程维护一个段的任务结构,其中条目mm指向一个mm_struct.
- mm_struct.描述了虚拟内存的当前状态;pgd指向第一级页表的基地址(结合一个进程一串页表);
- mmap指向一个vm_area_struct的链表。
- vm_area_struct一个条目对应一个段,链表相连指出了hello进程虚拟内存中的所有段。
7.4 TLB与四级页表支持下的VA到PA的变换
CPU每传来一个虚拟地址,MMU就需要查询一次PTE(页表)如果没有命中,就需要再从内存中取得,这需要花费很多时间,通过TLB(翻译后备缓冲器)能够消除这些开销。TLB是一个小的,虚拟寻址的缓存,在MMU里,其每一行都保存着一个单个PTE组成的块,TLB通常具有高度相联度。
压缩页表的常见方式是使用层次结构的页表。二级页表中, 第一级页表的每个PTE负责一个4MB的块,每个块由1024个连续的页面组成。二级页表每一个PTE负责一个4KB的虚拟地址页面
7.5 三级Cache支持下的物理内存访问
在获得了物理地址VA之后,会使用CI进行组索引,每组8路,对8路的块分别匹配CT(前40位)。如果匹配成功且块的valid标志位为1,则命中,根据数据偏移量CO(后6位)取出数据返回。如果没有匹配成功或者匹配成功但是标志位是1,则不命中。向下一级缓存中查询数据(L2 CacheèL3 Cacheè主存)。
查询到数据之后,一种简单的放置策略如下:如果映射到的组内有空闲块,则直接放置,否则组内都是有效块,产生冲突,采用最近最少使用的策略(Least frequently used)进行替换,也就是替换掉最不经常访问的一次数据
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程分配唯一的PID。
为了给这个新进程创建虚拟内存,创建了当前进程的mm_struct、区域结构和页表的原样副本。当fork函数在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。
当两个进程中的任一个来进行写操作时,写时复制就会创建新页面。由此,也就为每个进程保持了私有地址空间的概念。
7.7 hello进程execve时的内存映射
当我们用./hello运行可执行文件hello时,Shell会先fork创建虚拟内存区域,当这个区域和父进程还是完全一样的,会调用execve(“hello”,NULL,NULL),加载并运行可执行文件hello,有效替换了当前程序,加载并运行hello的步骤如下:
- 删除已存在的用户区域
- 映射私有区域(为hello程序的代码,数据,.bss,栈区域创建新的区域结构),都是私有,写时复制的;
- 映射共享区域,如我们的hello需要与libc.so动态链接,那么这些对象动态链接到这些程序,然后再映射到用户虚拟地址空间中的共享区域.
7.8 缺页故障与缺页中断处理
- 确认是不是一个合法的地址——不断将这个地址与每个区域的vm_start&vm_end进行比对。
- 确认访问权限是不是正确的——如果这一页是只读页,但是却要做出写这个动作,那明显是不行的。
- 确认是合法地址并且是符合权限的访问,那么就用某个特定算法选出一个牺牲页。如果该页被修改了,就将此页调出并且调入那个被访问的页,并将控制传递到触发缺页中断的那条指令,这条指令继续执行的时候就不会触发缺页中断,这样就可以继续执行下去。找到了要存储的页后,内核会从磁盘中将需要访问的内存VP3放入到之前已经操作过的PP3中,并且将PTE中的信息更新,这样就成功的将一个物理地址缓存在了页表中。当异常处理返回的时候,CPU会重新执行访问虚拟内存的操作,这个时候就可以正常的访问,不会发生缺页现象了。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。堆在未初始化的数据区域后开始,并向更高的地址生长。对于每个进程,内核维护着一个变量brk, 它指向堆的顶部。
分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用,空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
7.10本章小结
本章主要介绍了hello的存储器地址空间、intel的段式管理、页式管理,TLB与四级页表支持下的VA到PA的变换、三级cache支持下物理内存访问, hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理、动态存储分配管理等内容。
(第7章 2分)
附件
hello | 链接之后的可执行目标文件 |
hello.c | hello.c |
hello.elf | hello的ELF格式 |
hello.i | 预处理 |
hello.o | 汇编 |
hello.objdmp | 反汇编 |
hello.s | 编译产生的汇编 |
hello.txt | hello.o的ELF格式 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 《深入理解计算机系统》Randal E. Bryant David R.O`Hallaron
[2] http://blog.csdn.net/xiaoguaihai/article/details/8705992
(参考文献0分,缺失 -1分)