计算机系统大作业程序人生-Hello’s P2P From Program to Process ...

打印 上一主题 下一主题

主题 823|帖子 823|积分 2469



计算机系统

大作业

题     目  程序人生-Hellos P2P 
专       业     工科试验班(****)  
学     号          202211****       
班   级          22*****          
学       生          才**        
指 导 教 师           史**          

计算机科学与技术学院

20245

摘  要

本文以hello.c程序开始,介绍了一个程序在Linux下运行的完整生命周期,包括预处理、编译、汇编、链接、历程管理、存储管理、I/O管理,具体介绍了程序从被键盘输入、生存到磁盘,直到最后程序运行结束,程序变为僵尸历程的全过程。清晰地观察hello.c的完整周期,直观地体现其生命历程。
关键词:Liunx;P2P;O2O;计算机系统;hello             
目  录


第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,从程序到历程。这个看似简朴的过程需要颠末预处理、编译、汇编、链接等一系列的复杂操作才能生成一个可执行目标文件。在运行时,我们打开Shell,等待我们输入指令。通过输入./hello,Shell创建新的历程来执行hello。操作系统会使用fork产生子历程,然后通过execve将其加载,不断举行访存、内存申请等操作。最后,在程序结束返回后,由父历程或先人历程举行回收,程序结束。具体而言,Hello始于hello.c,经历预处理、编译、汇编、链接四个阶段转变为可执行程序。在预处理阶段,hello.c通过预处理器生成修改后的C程序hello.i;在编译阶段,编译器将hello.i翻译为汇编语言程序hello.s;在汇编阶段,汇编器将hello.s翻译成机器语言指令,生成可重定位目标程序hello.o;在链接阶段,链接器将库函数所在的目标文件与hello.o合并,生成可执行文件hello。终极,Shell调用fork和execve函数创建历程,在历程上下文中运行程序,完成加载入内存并运行的使命。
O2O全称为From Zero to Zero,从无到终。Hello的出生是由操作系统举行存储管理、地址翻译、内存访问,通过按需页面调理开始这段生命。程序在运行过程中经历大量异常和信号处理,对存储器举行读写访问,与外设举行交互,程序运行结束后,父历程回收子历程,释放虚拟地址空间,删除相关内容,实现从有到终的过程。具体而言,Shell起首调用fork函数为程序创建历程,随后调用execve函数在历程上下文中加载运行hello程序,从而获得虚拟地址空间,存储通用寄存器、程序计数器、用户栈、内核栈及各种内核数据结构等信息,实现了从无到有的过程。程序运行结束后,父历程回收子历程,释放虚拟地址空间,删除相关内容,实现从有到终的过程。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows11 64位;VMware Workstation Pro15.5.1;Ubuntu 20.04.4
开发和调试工具:gdb;edb;readelf;objdump;Code::Blocks20.03
1.3 中心效果

hello.c:储存hello程序源代码
hello.i:源代码颠末预处理产生的文件(包含头文件等工作)
hello.s:hello程序对应的汇编语言文件
hello.o:可重定位目标文件
hello_o.s:hello.o的反汇编语言文件
hello.elf:hello.o的ELF文件格式
Hello:二进制可执行文件
hello.elf:可执行文件的ELF文件格式
hello.s:可执行文件的汇编语言文件
1.4 本章小结

对hello程序的P2P和020过程过程举行了简要的介绍,说明了作业时用到的实验环境与工具,列出了中心产生的文件。

第2章 预处理

2.1 预处理的概念与作用

预处理是C程序编译过程中的第一个阶段,主要用于处理预处理指令。预处理器负责处理源代码中的各种指令和宏定义,生成一个中心文件,通常以`.i`为扩展名。预处理器是一个文本替换工具,用于处理源代码中的预处理指令,如#include、#define、#if、#endif`等。
作用:
1. 头文件包含 (#include):将头文件的内容插入到源文件中,头文件一般包含函数原型、宏定义和类型定义
2. 宏定义 (#define):根据宏定义替换代码中的宏,宏用来定义常量或简朴的代码替换
3. 条件编译 (#if, #ifdef, #ifndef, #else, #elif, #endif):条件编译答应根据条件选择性编译代码片断。
4. 宏展开:展开宏定义,将宏替换为其定义的内容,使代码更易读和维护
5. 去除注释:去除源代码中的注释,使得编译器在后续阶段处理更简朴的代码。
2.2在Ubuntu下预处理的命令

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

2.3 Hello的预处理效果解析

颠末预处理后,文件显着变长,原代码位于预处理文件的末端。

·预处理文件中的原代码


·对stdio.h的包含(例)


·部门(例)

本来代码文件中的注释被删去了,预处理会忽略C代码文件当中的注释。
2.4 本章小结

本章介绍了预处理的概念和作用,实验对代码举行了预处理,分析了代码预处理的效果,对预处理过程有了更加深入的认识。


第3章 编译

3.1 编译的概念与作用

编译是将预处理后的C源文件转换成汇编语言程序的过程。这个阶段由编译器负责,通过语法分析和语义分析检查代码的正确性,然后生成中心代码并举行优化,最后生成特定于目标处理器的汇编代码。
3.2 在Ubuntu下编译的命令

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

3.3 Hello的编译效果解析

3.3.1文件信息与初始设置

这一行表示源文件的名称是hello.c

3.3.2文本段与只读数据段

.text声明了以下是程序的可执行代码
.section .rodata声明了一个只读数据段,存储只读数据
.align 8对齐段中的数据,使其地址是8的倍数
3.3.3字符串常量定义

 .LC0是第一个字符串常量的标签,包含中笔墨符串
.LC1是第二个字符串常量的标签,包含带格式化字符串
3.3.4主函数声明与栈帧设置

.globl main:声明main函数为全局可见
.type main, @function:声明main是一个函数
pushq %rbp:生存旧的基指针值
movq %rsp, %rbp:将栈指针值生存到基指针寄存器
subq $32, %rsp:分配32字节的栈空间
3.3.5函数参数处理

movl %edi, -20(%rbp):将edi寄存器的值(第一个参数)生存到栈帧中
movq %rsi, -32(%rbp):将rsi寄存器的值(第二个参数)生存到栈帧中
3.3.6条件判定与输出

cmpl $5, -20(%rbp):比力第一个参数是否即是5
je .L2:如果相当则跳转到标签.L2
leaq .LC0(%rip), %rax:将.LC0的地址加载到rax寄存器
movq %rax, %rdi:将rax的值移动到rdi寄存器(用于puts函数的参数)
call puts@PLT:调用puts函数输出字符串
movl $1, %edi:将1移动到edi寄存器(用于exit函数的参数)
call exit@PLT:调用exit函数退出程序
3.3.7循环初始化与跳转

movl $0, -4(%rbp):将循环控制变量初始化为0
jmp .L3:跳转到标签.L3,开始循环
3.3.8循环体与函数调用

这些指令执行了一个循环,处理指针解引用、字符串打印、字符串转换为整数和休眠操作。
movq和addq指令用于操作指针地址
call指令用于调用外部函数如printf、atoi和sleep
addl $1, -4(%rbp):增加循环控制变量
cmpl $9, -4(%rbp):比力循环控制变量是否小于即是9
jle .L4:如果小于即是9则跳转到.L4继续循环
3.3.9函数结束与返回

call getchar@PLT:调用getchar函数等待用户输入
movl $0, %eax:将返回值设置为0
leave:清算栈帧,恢复之前的基指针值
ret:返回到调用者
.size main, .-main:计算并设置main函数的大小
3.4 本章小结

本章起首介绍了编译的概念和作用,然后在Ubuntu下以hello.s为例,通太过析其汇编程序,理解编译器是怎样处理各种数据类型和各类操作的。编译是从高级语言程序生成可执行文件的过程中的关键一步。


第4章 汇编

4.1 汇编的概念与作用


汇编器(as)将汇编程序翻译为机器语言指令,然后把这些指令打包成可重定位目标程序的格式,并将效果生存在目标文件中(该文件是个二进制文件,文本编译器打开会乱码)。或指汇编器将以.s末端的汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式,终极效果生存在.o目标文件中的过程。可以将汇编语言翻译为机器语言,并将相关指令以可重定位目标程序格式生存在.o文件中。
4.2 在Ubuntu下汇编的命令

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

生成机器指令,方便机器直接分析。
4.3 可重定位目标elf格式

    命令:readelf -a hello.o > hello.elf
    




1、ELF头以16字节的序列开始,描述了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部门包含资助两届其语法分析息争释目标文件的信息,包括ELF头的大小、目标文件的类型(如可执行、可重定位大概共享的)、机器类型、节头部表的文件偏移以及节头部表中条目的大小和数目。
2、.text:已编译程序的机器代码
3、.rodata:只读数据
4、.data:已初始化的全局变量和局部静态变量
5、.bss:未初始化的全局变量和局部静态变量,仅是占位符,不占据任何实际磁盘空间
6、.symtab:符号表,存放函数和全局变量(符号表)信息,不包括局部变量
7、.rel.text:.text节的重定位信息,用于重新修改代码段的指令中的地址信息
8、.rel.data:.data节的重定位信息,用于对被模块使用或定义的全局变量重定位的信息
9、.debug:调试符号表,只有以-g方式调用编译器驱动程序时,才会得到这张表
10、.line:原始C源程序中的行号和.text节中机器指令之间的映射
11、.strtab节:字符串表,包括.symtab和.debug节中的符号表

重定位节信息:包括信息、类型、符号值、符号名称+加数


符号表信息

4.4 Hello.o的效果解析

在终端输入objdump -d -r hello.o查看hello.o的反汇编


在hello.s文件中,call后面紧随着函数名,hello.o中,call后面要跟着指令的地址。在数据格式上,hello.s中是十进制数字,而hello.o为十六进制。分支转移函数上,hello.s显示了跳转位置,hello.o指明了函数的偏移量位置。
通过将反汇编与hello.s比力发现,汇编指令代码几乎相同,反汇编代码除了汇编代码之外,还显示了机器代码,在左侧用16进制表示。机器指令有操作码和操作数构成,和汇编指令逐一对应。最左侧为相对地址。此中跳转指令和函数调用等指令,在反汇编代码中表示为对应地址的偏移,而在hello.s中直接表示为函数名或定义的符号。在反汇编代码中,立即数是16进制显示的,而在hello.s中立即数是以十进制显示的。
4.5 本章小结

本章节我们了解到汇编的概念及其作用,知道了汇编与反汇编的指令操作,在ubuntu中举行汇编与反汇编操作得到了hello.o和hello.elf文件,并对效果举行了分析。


5链接


5.1 链接的概念与作用

链接(Linking)是将编译后的目标文件(.o文件或称为对象文件)合并为可执行文件或库文件的过程。链接过程将多个目标文件中的代码和数据整合到一个文件中,并解决它们之间的符号引用。
链接的作用
符号解析:链接器负责解析目标文件中的符号引用
地址重定位:链接器将目标文件中的相对地址转换为绝对地址。每个目标文件中的代码和数据通常都有本身的地址空间,链接器将它们合并到一个统一的地址空间中。
段合并:链接器将各个目标文件中的代码段、数据段等合并成单个可执行文件。
生成可执行文件:链接器将处理后的代码和数据写入终极的可执行文件,使得该文件可以直接运行。
5.2 在Ubuntu下链接的命令

命令:gcc hello.o -o hello

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

    可执行目标文件与可重定位文件稍有差别,ELF头中字段e_entry给出执行程序时的第一条指令的地址,而在可重定位文件中,此字段为0。可执行目标文件多了一个程序头表,也成为段头表,是一个结构数组。还多了一个.init节,用于定义init函数,该函数用来执行可执行目标文件开始执行时的初始化工作。因为可执行目标文件不需要重定位,以是比可重定位目标文件少了两个.rel节。


查看hello的ELF头:发现hello的ELF头中类别处显示的是DYN,表示时可执行目标文件,这与hello.o差别,hello中的节的数目为31个。




查看hello的节头表,具体信息在节头表中都有显示,包括大小、类型、偏移量、地址是程序被载入虚址地址的起始地址。





查看hello的程序头表,起首显示这是一格可执行目标文件,共有12个表项,此中有4个可装入段(Type=LOAD),VirtAddr和PhysAddr分别是虚拟地址和物理地址,值相同。Align是对齐方式,这里4个可装入段都是4K字节对齐


5.4 hello的虚拟地址空间  

起始地址


使用edb加载hello,查看本历程的虚拟地址空间各段信息。


5.5 链接的重定位过程分析

使用objdump -d -r hello对hello举行反汇编








Hello是一个可执行文件,可以直接在操作系统中运行。hello.o是一个目标文件,它包含了编译后的机器代码和符号表信息,但还没有颠末链接器的处理,因此无法直接执行。两者的主要区别在于是否颠末了完整的链接过程。
链接过程是指将目标文件转换为可执行文件的过程中,需要颠末链接器的处理。链接过程主要包括两个步骤:符号解析和重定位。
符号解析
符号解析是指链接器解析目标文件中使用的符号,即变量名、函数名等,并找到它们对应的地址或符号表项。链接器通过查找和匹配这些符号,使得各个目标文件中的符号可以正确引用其他文件中的定义。
重定位
重定位是指链接器将解析得到的符号引用替换为实际的地址,并调整代码中的跳转目标地址,以确保程序能够正确执行。hello.o中的重定位条目描述了目标文件中的代码和数据与其他目标文件或库文件中的符号之间的关系。这些重定位条目包括了需要在链接时处理的符号引用及其位置信息。
链接过程中的处理
在链接过程中,链接器会根据hello.o中的重定位条目,将此中引用的符号与其他目标文件或库文件中的定义举行匹配,并举行相应的地址替换和调整。这样,终极生成的可执行文件hello中的代码和数据就可以正确地与其他模块举行连接和执行。
通过符号解析和重定位,链接器将各个目标文件中的代码和数据整合在一起,生成一个可以在操作系统中直接运行的可执行文件。因此,hello是一个完整的可执行文件,而hello.o只是一个中心产物,必须颠末链接过程才能成为可执行文件。
5.6 hello的执行流程

调用的子程序名和程序地址如下:
init                     0x401000
.plt                     0x401020
puts@plt                0x401090
strtol@plt               0x4010a0
__printf_chk@plt         0x4010b0
exit@plt                0x4010c0
sleep@plt               0x4010d0
getc@plt                0x4010e0
_start                   0x4010f0
_dl_relocate_static_pie     0x401120
deregister_tm_clones      0x401130
register_tm_colnes        0x401160
__do_global_dtors_aux    0x4011a0
frame_dummy           0x4011d0
main                  0x4011d6
__libc_csu_init         0x401260
__libc_csu_fini         0x4012d0
__fini                 0x4012d8
5.7 Hello的动态链接分析

动态链接的实现方式
动态链接是现代操作系统中一种高效且灵活的库管理方式。它答应多个程序共享相同的库文件,从而减少内存占用和磁盘空间。动态链接的实现通常涉及以下几个步骤:
1. 编译
在编译源代码时,编译器将动态链接库的调用信息嵌入到可执行文件中,而不是将实际的库函数代码复制到可执行文件中。这样,生成的可执行文件中包含了对动态链接库的引用信息(例如库的名称、版本和所需的符号),而不是具体的库代码。这种方式使得可执行文件更小,并且能够共享库文件。
2. 加载
当程序启动时,操作系统会根据可执行文件中的动态链接信息,将所需的动态链接库加载到内存中。加载器(loader)负责在程序的地址空间中创建库文件的映射关系。操作系统通过搜索预定义的路径(例如 /usr/lib、/usr/local/lib 等)来查找并加载这些库文件。
3. 符号解析
在运行时,程序需要调用动态链接库中的函数大概引用动态链接库中的变量。当程序举行这些调用时,操作系统会根据程序中的符号引用去动态链接库中举行符号解析,找到相应的函数或变量地址。符号解析器(dynamic linker/loader)使用符号表来查找库文件中的符号定义,并将它们与程序中的引用举行匹配。
4. 重定位
符号解析完成后,操作系统会根据解析得到的地址信息,对程序举行重定位。重定位过程包括调整程序中所有对库函数和变量的引用,使它们指向动态链接库中的实际地址。这一步骤确保程序能够正确地调用动态链接库中的函数大概引用动态链接库中的变量。重定位信息通常包含在重定位表中,指示需要修改的内存地址和具体的修改方式。
5. 运行
颠末上述步骤后,程序便可以正常运行。程序在运行时可以动态调用动态链接库中的函数大概引用动态链接库中的变量,实现所需的功能。由于动态链接库是共享的,多个程序可以同时使用同一个库文件,从而节省系统资源。此外,动态链接还答应在程序运行时加载和卸载库文件,实现插件式的扩展和动态功能更新。


.plt和.plt.got在elf文件中


使用edb查看,在动态链接加载前后hello重定位是不一样的



5.8 本章小结

本章节了解链接过程得到了hello.o文件,通过链接器查看是怎样转变为hello可运行文件的。同时还查看了hello的虚拟地址空间。并且分析了反汇编指令和hello.o的差别之处。最后对hello的动态链接举行了分析。


6hello历程管理

6.1 历程的概念与作用

历程是指计算机中已运行的程序, 是系统举行资源分配和调理的基本单位, 是操作系统结构的基础。
历程是操作系统的核心概念,通过有用管理资源、提供隔离和掩护、支持并发执行、实现历程间通讯、隔离故障和用户交互,确保计算机系统的高效运行和稳定性。历程机制的计划和实现对于操作系统的性能和可靠性具有关键作用。
6.2 简述壳Shell-bash的作用与处理流程

Shell是用户与操作系统之间的接口,负责解析和执行用户输入的命令,将其转换为操作系统可以理解的操作。它提供编写Shell脚本的能力,主动化执行使命,提高工作效率。Shell还能启动和管理其他程序或历程,通过Shell执行应用程序、运行脚本或启动服务。它管理用户的工作环境,包括环境变量和路径设置,用户可以通过Shell配置和调整系统环境Shell还提供操作文件系统的命令,如创建、删除、移动和修改文件及目次,方便用户管理系统中的文件和目次。
Bash(Bourne Again Shell)是UNIX Shell的一种,除了实现基本的Shell功能外,还提供了更多扩展特性,是许多Linux发行版的默认Shell。用户登录或打开终端时,Bash启动并读取启动文件(如`/etc/profile`、`~/.bashrc`)以配置用户环境。用户在提示符下输入命令,Bash将用户输入的一行命令读入内存。Bash对输入的命令行举行解析,辨认命令和参数,并举行变量替换、通配符展开和命令替换等预处理操作。
根据解析效果,Bash查找命令,若是内建命令则直接执行,若是外部命令则通过路径查找找到相应的可执行文件,并启动一个子历程来执行。Bash创建和管理命令的子历程,等待子历程完成并捕获其退出状态。命令执行完成后,Bash显示命令输出效果或错误信息,将命令的标准输出和标准错误重定向到得当的位置。当用户运行一个Bash脚本时,Bash依次读取和执行脚本中的每一行命令,脚本可以包含条件语句、循环、函数和变量等,实现复杂逻辑。Bash处理完当前命令后,等待用户输入新的命令,重复上述过程。通过这些步骤,Bash提供了一个命令行界面,使用户能够有用地与操作系统交互,执行各种使命和管理系统。
6.3 Hello的fork历程创建过程

1.父历程在某个地方调用fork系统,该系统调用会复制父历程的内存空间,包括代码段、数据段、堆栈等。
2.内核会创建一个新的历程,这个新历程是父历程的副本,但有本身独立的历程 ID。
3.子历程会继承父历程的内存空间的副本,但是子历程的内存空间是独立的,对此中的修改不会影响父历程。
4.fork调用在父历程中返回子历程的ID,而在子历程中返回0,这样可以通过返回值来区分父历程和子历程。
5.在fork调用之后,父历程和子历程会并发执行,它们各自独立地执行差别的代码段,但共享相同的程序代码和数据。通过fork创建的子历程是父历程的副本,但是它们是独立的历程,各自有本身的地址空间,可以独立运行和执行差别的操作。
6.4 Hello的execve过程

int execve(char *filename, char *argv[], char *envp[]);

execve函数在当前历程的上下文中加载并运行一个程序。加载并运行可执行目标文件filename(char *), 且带参数列表argv(char *)和环境变量列表envp (char *)。只有当出现错误时,例如找不到filename时, execve才会返回到调用程序。fork一次调用返回两次差别,而execve调用一次并从不返回。
execve会删除已存在的用户地区–>映射私有地区–>映射共享地区–>设置程序计数器。
1.调用驻留在内存中的被称为启动加载器的操作系统代码来执行程序,加载器删除子历程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。
2.新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。
3.最后加载器设置PC指向_start地址,_start终极调用main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。
4.直到CPU引用一个被映射的虚拟页时才会举行复制,这时,操作系统利用它的页面调理机制主动将页面从磁盘传送到内存。

6.5 Hello的历程执行

1、上下文信息:操作系统使用一种称为上下文切换的较高层次的异常控制流来实现多使命。其实上下文就是历程自身的虚拟地址空间,分为用户级上下文和系统及上下文。每个历程的虚拟地址空间和历程本身逐一对应(因此和PID逐一对应)。由于每个CPU只能同时处理一个历程,而许多时间系统中有许多历程都要去运行,因此处理器只能一段时间就要切换新的历程去运行,而实现差别历程中指令交替执行的机制称为历程的上下文切换。
2、历程时间片:一个历程执行它的控制流的一部门的每一时间段叫做时间片。

如上图所示,为历程A与历程B之间的相互切换。处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了历程当前享有的特权,当没有设置模式位时,为用户模式;设置模式为为内核模式。用户模式就是运行相应历程的代码段的内容,此时历程不答应运行特权指令,也不答应直接引用地址空间中内核区内的代码和数据;而内核模式中,历程可以运行任何指令。
6.6 hello的异常与信号处理

1.异常的分类

2、hello执行中可能出现的异常:
(1)停止:异步发生的。在执行hello程序的时间,由处理器外部的I/O设备的信号引起的。I/O设备通过像处理器芯片上的一个引脚发信号,并将异常号放到系统总线上,来触发停止。这个异常号标识了引起停止的设备。在当前指令完成执行后,处理器留意到停止引脚的电压变高了,就从系统总线读取异常号,然后调用得当的停止处理程序。在处理程序返回前,将控制返回给下一条指令。效果就像没有发生过停止一样。

(2)陷阱:陷阱是故意的异常,是执行一条指令的效果,hello执行sleep函数的时间会出现这个异常。

(3)故障:由错误引起,可能被故障处理程序修正。在执行hello时,可能出现缺页故障。

(4)终止:不可恢复的致命错误造成的效果,通常是一些硬件错误,好比DRAM大概 SRAM位被损坏时发生的奇偶错误。

运行实例:
(1)运行时输入回车

(2)运行时输入Ctrl+C



(3)运行时输入Ctrl+Z

(4)输入ps,监视后台程序

(5)输入jobs,显示当前停息的历程

(6)输入pstree,以树状图情势显示所有历程




(7)输入fg,使停止的历程收到SIGCONT信号,重新在前台运行。

(8)输入kill,-9表示给历程9554发送9号信号,即SIGKILL,杀死历程,直接退出。

6.7本章小结

本章主要介绍了程序怎样从可执行文件到历程的过程。介绍了shell的处理流程和作用。也介绍了fork函数和execve函数,及上下文切换机制等。

7hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:由段标志符与段内偏移量构成,是一种相对位置。例如hello.s中的相对偏移地址。
线性地址:如果地址空间的整数是连续的,那么即为线性地址空间。段地址+偏移地址得到线性地址,线性地址空间黑白负整数地址的有序集合。
虚拟地址:虚拟地址与线性地址是相似的;虚拟地址空间是一个拥有N=2^n个地址的有序集合。
物理地址:每个物理地址与系统物理内存的一个字节相对应,对应物理内存的M个字节。
7.2 Intel逻辑地址到线性地址的变换-段式管理

一个逻辑地址由两部门构成:段标识符:段内偏移量。段标识符是一个16位长的字段(段选择符)。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
索引号就是“段描述符”的索引,段描述符具体地址描述了一个段。许多个段描述符,就组了一个数组,叫“段描述符表”,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符。每一个段描述符由8个字节构成。全局的段描述符,放在“全局段描述符表(GDT)”中,一些局部的段描述符,放在“局部段描述符表(LDT)”中。
Linux通太过段机制,将逻辑地址转化为线性地址。给定一个完整的逻辑地址[段选择符:段内偏移地址]。起首,看段选择符的T1=0照旧1,知道当前要转换是GDT中的段,照旧LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。然后,拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,基地址就知道了。最后,把基地址 + 偏移量,就是要转换的线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理

正犹如在cache中探求内容也需要索引,从虚拟内存到物理内存也需要索引。因此在内存中,我们额外存储一个叫做页表的数据结构,作为对应的索引。因此,我们可以让每个历程都有一个页表,页表中的每一项都记录着该历程中对应的一页所投影到的物理地址、是否有用、尚有一些其他信息等。
然而由于页的大小为212个字节,而虚拟内存有232个字节,导致页表项会有2^20项,占用空间确实太大了,而且许多页表项应该其实都是空的,毕竟历程广泛没有占用很大的地址空间。因此系统接纳了多级页表的结构来举行索引。
系统将虚拟页作为举行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
每次将虚拟地址转换为物理地址,都会查询页表来判定一个虚拟页是否缓存在DRAM的某个地方,如果不在DRAM的某个地方,通过查询页表条目可以知道虚拟页在磁盘的位置。页表将虚拟页映射到物理页。页表就是一个页表条目的数组,每一个页表条目是由一个有用位和一个n为地址字段构成。有用位表明虚拟页是否缓存在DRAM中,n位地址字段是物理页的起始地址大概虚拟页在磁盘的起始地址。
n位的虚拟地址包含两个部门:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择得当的PTE,例如VPN 0选择PTE 0。根据PTE,我们知道虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。这里的VPO和PPO是相同的。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。

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

页表技术虽然能让我们再给出虚拟地址的时间,很大概率通过查找页表来找到内存地址,但是查页表也是访问内存的过程,也很浪费时间。利用局部性原理,像缓存一样,将最近使用过的页表项专门缓存起来。因此出现了TLB(后备转换缓冲器,也叫快表),之后找页表项的时间,先从快表找,找不到在访问内存中的页表项。同理,四级页表能包管页表项的数目少一些。



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

现代计算机系统中通常包含三级缓存:L1缓存、L2缓存和L3缓存。这些缓存层次结构的目的是提供快速访问数据的能力,以减少对慢速主存的访问次数。下面是访问的流程:
1.当一个处理器修改了缓存中的数据时,必须确保其他处理器的缓存中的相同数据是最新的。这通常通过缓存一致性协议来实现,如MESI协议。
2.当处理器需要访问物理内存时,它会将逻辑地址转换为物理地址。这个过程涉及到页表,用于将逻辑地址映射到物理地址。
3.如果所需数据在某个缓存层次中已经存在(缓存命中),处理器将直接从缓存中获取数据,而无需访问主存。如果所需数据不在任何缓存层次中(缓存未命中),处理器将从主存中获取数据,并将数据加载到得当的缓存层次中,以便将来的访问可以从缓存中获取。当缓存已满且需要为新数据腾出空间时,需要使用缓存替换策略。常见的替换策略包括最近最少使用和随机替换。当然,尽管缓存可以加速数据访问,但仍然存在内存访问耽误。当数据不在任何缓存中时,处理器必须从主存中获取数据,这会导致较长的访问耽误。
7.6 hello历程fork时的内存映射

在shell中输入命令./hello后,内核调用fork函数创建子历程,为hello程序的运行创建上下文,并分配一个与父历程差别的唯一的PID。为了给子历程创建虚拟内存,创建了当前历程的 mm_struct、地区结构和页表的原样副本。将这两个历程的每个页面都标记为只 读,并将两个历程中的每个地区结构都标记为私有的写时复制。

7.7 hello历程execve时的内存映射

execve函数调用驻留在内核地区的启动加载器代码,在当前历程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有用地替代了当前程序。
加载并运行hello需要以下几个步骤:
·删除当前历程虚拟地址中已存在的用户地区
·映射私有地区,为新程序的代码、数据、bss和栈创建新的地区结构,所有这些新的地区都是私有的、写时复制的。
·映射共享地区,将hello与libc.so动态链接,然后再映射到虚拟地址空间中的共享地区。
·设置当前历程上下文程序计数器(PC),使之指向代码地区的入口点。
7.8 缺页故障与缺页停止处理

缺页其实就是DRAM缓存未命中。当我们的指令中对取出一个虚拟地址时,若我们发现对该页的内存访问是合法的,而找对应的页表项式发现有用位为0,则说明该页并没有生存在主存中,出现了缺页故障。
此时历程停息执行,内核会选择一个主存中的一个牺牲页面,如果该页面是其他历程大概这个历程本身页表项,则将这个页表对应的有用位改为0,同时把需要的页存入主存中的一个位置,并在该页表项储存相应的信息,将有用位置为1。然后历程重新执行这条语句,此时MMU就可以正常翻译这个虚拟地址了。
7.9动态存储分配管理

动态内存分配器维护着一个历程的虚拟内存地区,称为堆。系统之间细节差别,但是不失通用性,假设堆是一个哀求二进制零的地区,它紧接在未初始化的数据地区后开始,并向上生长(向更高地址)。对于每个历程,内核维护着一个变量brk(读作“break”),它指向堆的顶部。
分配器有两种基本风格:
(1)显式分配器:要求应用显式地释放任何分配的块,例如C标准库提供的malloc程序包。
(2)隐式分配器:要求分配器检测一个已分配块何时不再被程序所使用,那么就是放这个块,也被称为垃圾收集器。
分配器简朴来说有以下几种实现方式:

7.10本章小结

本章主要介绍了hello的存储器地址空间,逻辑地址到线性地址、线性地址到物理地址的变换,接着介绍了四级页表下的线性地址到物理地址的变换,分析了hello的内存映射,及缺页故障与缺页停止处理和动态存储分配管理。

8hello的IO管理

8.1 Linux的IO设备管理方法

设备的模子化:文件
在 Linux 系统中,每个文件都有一个类型,用以表明它在系统中的脚色。文件类型包括:
1. 普通文件:包含任意数据的文件,可以是文本文件或二进制文件。文本文件只包含ASCII或Unicode字符,而二进制文件包含所有其他类型的数据。
2. 目次1:包含一组链接的文件,此中每个链接将一个文件名映射到一个文件,该文件可能是另一个目次。
3. 套接字:用于与另一个历程举行跨网络通讯的文件。
4. 其他文件类型:包括命名通道、符号链接、字符设备和块设备等。
设备管理:UNIX I/O接口
打开文件
一个应用程序通过哀求内核打开相应的文件来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,称为描述符,用于在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息,应用程序只需记住这个描述符即可。
初始文件描述符
Linux shell创建的每个历程在开始时都有三个打开的文件:
标准输入(描述符为 0)
标准输出(描述符为 1)
标准错误(描述符为 2)
头文件<unistd.h>定义了常量STDIN_FILENO、STDOUT_FILENO和 STDERR_FILENO,它们可以用来取代显式的描述符值。
改变当前的文件位置
对于每个打开的文件,内核保持一个文件位置k,初始值为0。这个文件位置是从文件开头起始的字节偏移量。应用程序可以通过执行seek操作,显式地设置文件的当前位置为k。
读写文件
读操作:从当前文件位置k开始,将n > 0个字节从文件复制到内存,然后将 k增加到k + n。当k凌驾文件大小m时,执行读操作会触发一个称为"end-of-file" (EOF)的条件,应用程序可以检测到这个条件。在文件末端处并没有明确的"EOF符号"。
写操作:从内存复制n > 0个字节到文件,从当前文件位置k开始更新 k。
关闭文件
当应用程序完成了对文件的访问后,它关照内核关闭这个文件。内核响应后释放文件打开时创建的数据结构,并将描述符返回到可用的描述符池中。无论一个历程因何种原因终止,内核都会关闭所有打开的文件并释放其内存资源。
8.2 简述Unix IO接口及其函数

8.2.1 Unix I/O 接口
(1)打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想 要访问一个 I/O 设备,内核返回一个小的非负整数,叫做描述符,它在 后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
(2)Linux Shell创建的每个历程都有三个打开的文件:标准输入、标准输出、标准错误。
(3)改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位 置 k,初始为 0,这个文件位置是从文件开头起始的字节偏移量,应用 程序能够通过执行 seek,显式地将改变当前文件位置 k。
(4)读写文件。一个读操作就是从文件复制 n > 0 个字节到内存,从当前文件位置 k 开始,然后将 k 增加到 k + n。给定一个大小为 m 字节的文件,当 k >= m 时,执行读操作会触发 EOF,应用程序能检测到它。雷同地,写操作就是从内存中复制 n > 0 个字节到一个文件,从当前文件位置 k 开始,然后更新 k。
(5)关闭文件。内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中去。
8.2.2函数
(1) open函数:int open(char *filename,int flags,mode_t node);
将filename转换为一个文件描述符,并且返回描述符数字。返回的描述符总是在历程中当前没有打开的最小描述符。flags 参数指明了历程计划怎样访问这个文件,mode参数指定了新文件的访问权限位。
(2) close函数:int close(int fd);
关闭一个打开的文件,当关闭已关闭的描述符会出错。
(3) read函数:ssize_t read(int fd,void *buf,size_t n);
从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。返回值-1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数目。
(4) write函数:ssize_t write(int fd,const void *buf,size_t n);
从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
(5) lseek函数:off_t lseek(int fd, off_t offset, int whence);
应用程序显示地修改当前文件的位置。
(6) stat函数:int stat(const char *filename,struct stat *buf);
以文件名作为输入,并填入一个stat数据结构的各个成员。
8.3 printf的实现分析


参数接纳了可变参数的定义, *fmt是一个char 类型的指针,指向字符串的起始位置。这个指针指向第一个const参数(const char *fmt)中的第一个元素。fmt也是个变量,它的位置,是在栈上分配的,它也有地址。
(1)printf调用的外部函数vsprintf。

这个函数担当一个格式化的命令,并把指定的匹配的参数格式化输出。i = vsprintf(buf,fmt arg);由此句可知,vsprintf返回的是一个长度,就是要打印出来的字符串的长度。write(buf, i);由此句可知,write即写操作,会把buf中的i个元素的值写到终端。vsprintf的作用就是格式化,担当确定输特别式的格式字符串fmt。用格式字符串对个数变化的参数举行格式化,产生格式化输出。
(2)write函数

在write函数中,将栈中参数放入寄存器,ecx是字符个数,ebx存放第一个字符地址,int INT_VECTOR_SYS_CALLA代表通过系统调用syscall。
(3)syscall函数

ecx中是要打印出的元素个数 ,ebx中的是要打印的buf字符数组中的第一个元素,这个函数的功能就是不断的打印出字符,直到遇到:’\0’ 停止.
(4)综合分析,printf函数实现过程:

a)vsprintf的作用是格式化,担当确定输特别式的格式字符串fmt,用格式字符串对个数变化的参数举行格式化,产生格式化输出。
b)vsprintf的输出到write函数中。在Linux下,write通过执行syscall指令实现了对系统服务的调用,从而使内核执行打印操作。内核会通过字符显示子程序,根据传入的ASCII码到字模库读取字符对应的点阵,然后通过vram(显存)对字符串举行输出。
c)显示芯片将按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量),终极实现printf中字符串在屏幕上的输出。
8.4 getchar的实现分析

键盘停止处理:操作系统通常会设置一个键盘停止处理程序。当用户按下键盘时,会触发一个硬件停止,操作系统的停止服务程序会被调用。停止服务程序会读取键盘控制器的状态,并获取键盘扫描码。扫描码可以被转换成相应的ASCII码,这样操作系统就能理解用户按下了哪个键。

键盘缓冲区:操作系统会有一个键盘缓冲区,用于存储从键盘中读取的字符。当键盘停止服务程序获取到一个字符后,它会将该字符存储到键盘缓冲区中。

getchar的实现:getchar函数通常会调用系统调用来读取键盘输入。在Unix/Linux系统中,通常会调用read系统调用来从标准输入读取字符。read系统调用会阻塞程序,直到有数据可读取为止。getchar会反复调用read系统调用,直到接收到回车符,表示用户输入结束。getchar会将每个接收到的字符从键盘缓冲区中读取,并返回给调用者。

返回字符:当getchar函数从read系统调用中接收到一个字符后,它会将该字符返回给调用者。如果getchar函数接收到回车符,则会结束,并返回该字符。

8.5本章小结

本章主要介绍了Linux的I/O设备管理方法、Unix IO接口及其函数,并分了printf函数和getchar函数的实现。

结论

一开始,程序员将hello.c一字一键地敲入电脑,轻松所在击运行后,“Hello, World!” 就出现在了屏幕上。虽然看起来简朴,但深入了解后才发现,这一切并没有那么简朴。
最初,hello.c程序安静地呆在磁盘里,等待着被执行。终于,在程序员的指令下,它经历了一系列复杂的处理步骤。起首,hello.c颠末预处理,头文件被引入、宏被展开等,酿成了hello.i文件。这个阶段的主要使命是展开所有的宏定义,并包含所有必要的头文件,使得代码预备好举行下一步处理。
接着,编译器将hello.i文件转换为汇编代码文件hello.s。这个过程涉及将高级语言的代码转化为低级的汇编语言代码,每一条指令都被具体化,接近机器能够理解的层次。然而hello.s还不能被直接执行,还需要进一步处理。
汇编器将hello.s文件转换成机器代码文件hello.o。在这个阶段,代码被翻译成机器可以直接理解的二进制格式,包含了具体的指令和数据。然而,hello.o文件仍然只是一个目标文件,不能直接执行。它需要颠末链接过程,将程序所需的库函数和其他模块连接起来,终极生成可执行文件hello。
可执行文件生成后,为了执行它,程序员在终端输入./hello。虽然效果瞬间就出来了,但是中心经历了许多复杂的步骤。
Shell 解析命令行输入的命令,然后调用fork创建子历程,并用execve将程序映射到虚拟内存中。当CPU执行到hello时,它开始读取对应的虚拟内存地址,通过缺页异常将hello放入主存中。CPU的内存管理单元通过多级页表机制,将虚拟地址转换为物理地址,并将所需的页面加载到物理内存中。
在执行过程中,指令和数据通过一级、二级、三级和四级缓存逐层读取和缓存,加快了访问速度,终极将hello加载到了处理器内部。处理器执行程序中的每条指令,依次完成各项操作。
通过I/O包装I/O函数,终极“Hello, World!”的效果输出到终端。I/O统调用将程序的输出重定向到标准输出设备,通常是屏幕,程序员便可以看到预期的输出。
在程序运行结束后,系统回收了hello程序所占用的资源。程序的代码和数据被从内存中移除,文件描述符被关闭,所有相关的数据结构被释放。终极,hello.c程序重新回到了硬盘,静静地等待下一次的执行。
尽管hello的一生云云短暂,但它的经历却崎岖而出色,从静静地躺在磁盘中到光荣地在屏幕上显示效果,这一过程展示了计算机系统的复杂性和高效性。
计算机系统这门课团体来说,知识点繁多,难度也不小,但却非常有趣。计算机系统远比我想象中的更复杂得多,隐藏着许多奥秘。通过学习这门课,通过实际做实验、上网查资料等,我了解到了许多新知识,提高了学习能力。总之,计算机系统这门课让我受益匪浅,我将继续深入学习相关知识,探索计算机系统的更多奥秘。
附件

hello.c:hello程序的源代码(文本文件)
hello.i:颠末预处理修改了的源程序(文本文件)
hello.s:编译器翻译成的汇编程序(文本文件)
hello.o:汇编器翻译汇编程序为二进制机器语言指令,这个文件是可重定位的目标程序
hello:调用printf.o函数,颠末链接器后得到的可执行文件
hello.elf:hello.o的elf格式文件

hello1.elf:hello的elf格式文件

hello.asm:hello的反汇编文件

hello.o.asm:hello.o的反汇编文件



参考文献


[1]  《深入理解计算机系统》Randal E.Bryant David R.O’Hallaron 机械工业出版社
[2] 袁春风. 计算机系统基础. 北京:机械工业出版社,2018.7(2019.8重印)
[3] Randal E. Bryant;David R. O’Hallaron. 深入理解计算机系统. 北京:机械工业出版社,2016.7(2019.3重印)
[4]  C汇编Linux手册 http://docs.huihoo.com/c/linux-c-programming
[5]  CMU的实验参考 http://csapp.cs.cmu.edu/3e/labs.html 



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

怀念夏天

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表