程序人生-Hello‘s P2P From Program to Progress

 

计算机科学与技术学院

2021年5月

摘  要

摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

    该篇论文以P2P,020两个角度为引,以hello.c为对象介绍了高级程序语言程序在运行过程中的各个阶段,并采用多种方法多角度、多层次地分析了隶属各个阶段的中间文件,研究了每一步的作用和不同步骤之间的相似点、不同点,同时也提供了一个程序转化为进程、再被执行的大框架。对于计算机系统理论的入门者的知识总结、查缺补漏来说有很大的帮助。

关键词:计算机系统,程序人生,hello.c;                           

(摘要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 -

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

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

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

第1章 概述

1.1 Hello简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。

Hello的P2P过程:Hello的P2P过程是指程序员通过I/O设备(如键盘)编写hello.c,之后经过预处理器(cpp),编译器(ccl),汇编器(as)与链接器(ld)的一系列操作之后,最后形成可执行目标程序hello的过程。简单来说,就是由名为hello.c的程序变成了名为hello的进程的过程。

需要注意的是,这里的P2P是from program to process的意思,与常见的peer to peer 是截然不同的。

Hello的020过程:Hello的020过程是指shell运行hello程序,首先通过系统函数fork产生子进程,再通过execve、waitpid等一系列的函数,完成该进程的执行并进行相关进程回收的过程。在运行进程之前,与结束进程处理残余之后,正常状态的shell中都几乎没有相关内存,这就是所谓的“从0到0”。

需要注意的是,这里的020也不是最常见的那种意思。这里的020是from zero to zero的意思,而不是常见的online to offline的意思。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

X64 CPU;8G RAM;256GHD Disk ;

Windows11 64位; VirtualBox-6.1.32-149290-Win;Ubuntu 20.04.4 LTS 64位;

      CodeBlocks 64位;vim; gcc

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

hello.i

hello.c预处理后的文件

hello.s

hello.i编译后的文件

hello.o

hello.s汇编后的文件

hello.asm

hello.o反汇编的文件

hello

hello.o与其他文件链接形成的文件

hello2.asm

hello反汇编的文件

1.4 本章小结

       本章从P2P和020两个角度简单介绍了hello.c程序的编译过程和运行过程,从总体上描述了hello.c程序的一生。同时提供了实验的环境与中间文件表,以便于和下面章节中出现的特殊用语,如文件名等作对照。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理就是根据程序内部的一些命令(如某些以‘#’符号开头的命令),对c程序的内容进行修改的过程。如读取#include<stdio.h>命令时,会在文件里添加stdio.h库的内容。同时,预处理也会删除程序内所有的注释。

作用:预处理虽然并不会对程序文本进行直接编译,但会对程序文本进行一些标记与修改,对一个资源进行宏替换等价替换,将其转化为易于编译的形式,为后续的步骤打下基础。

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

程序文本量大幅度增加,从hello.c的24行变为了hello.i的3060行。但在main函数的长度上,hello.c和hello.i之间却几乎没有区别。hello.i中大部分的内容都是类型定义函数typedef、外部类函数extern与_CS_等系统函数,显然,这是hello.c总stdio.h,stdlib.h等库的展开。

2.4 本章小结

本章主要介绍了预处理的概念及作用,并以hello.c转化为hello.i的过程和结果为例,对预处理前后文本的变化做出了简单的分析。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:编译就是将程序由C语言转为汇编语言的过程。在本实验中,hello.i被编译为hello.s,前者是C语言文件,而后者是汇编语言文件。

作用:计算机无法识别C语言等高级程序语言,却可以识别由01串组成的机器语言。而汇编语言和机器语言是等价的。将程序转化为汇编语言版本,既有便于机器识别,也能通过词法分析和句法分析查找程序可能的错误。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

与hello.i相比,hello.s的行数严重缩水,从3060行减少到了80行。

3.3.1常量分析

该程序涉及的常量仅有字符串常量"用法: Hello 学号 姓名 秒数!\n"与"Hello %s %s\n",两者都储存在.LC0一段中,分别位于第6行与第8行。其中,第一个字符串"用法: Hello 学号 姓名 秒数!\n"的所有汉字(包括中文感叹号!)均占三个字节。

3.3.2局部变量分析

该程序涉及的局部变量有i,argc与argv[],由于argv[]为数组形式,所以留到第3.3.3节叙述。

对于变量i,可以观察到它储存在-4(%rbp)的位置,分别在第31行、第51行、第53行进行了赋初值、自增与比较操作。与hello.c文本的判断条件i<8不同,hello.s中的i是与立即数7进行比较,而比较条件也从原先的“小于”变成了“小于等于”。

而变量argc则存储在-20(%rbp)的位置,在第24行与立即数4进行比较,并在第25行判断比较结果是否相等,根据情况决定是否跳转到L2。

3.3.3 数组分析

该程序涉及到的数组仅有字符指针数组argv[],它被储存在了-32(%rbp)的位置(第23行)。而在.L4节中,对argv[]地址的多次引用,是为了调用argv[1],argv[2]和argv[3]三个元素,三次在-32(%rbp)处增加的立即数分别是16,8,24。之所以每次增加的立即数都是8的倍数,是因为argv[]是字符指针数组,在64位系统下每个元素占用8个字节。知道了这点,可以很简单的得出,argv[]中元素被调用的先后顺序是argv[2],argv[1],argv[3]。

3.3.4 变量类型分析

该程序涉及到的变量类型有int 类型,字符串类型(使用字符型指针char*表示)。其中,int型变量argc被储存在了32位寄存器%edi中(见第22行),而int型变量i却储存在了栈中,虽然是整型变量却相当于占用了8字节空间。至于字符指针型变量argv[],则是直接被以栈的形式存储。

3.3.5 赋值操作分析

本程序的赋值操作仅有一处,即在进入for循环体时将0赋给变量i。

3.3.6 类型转换分析

本程序的类型转换操作仅有一处,即在48行,调用atoi函数进行强转。本程序没有隐式转换。

3.3.7 算术操作分析

本程序的算术操作有两处。一处是变量i的自增操作,使用addl来对整型变量i进行加一。另一处是取出argv[]中元素时的地址偏移操作,与第一处不同,本次使用的是addq。

3.3.8 函数引用分析

本程序涉及的函数共有6个,分别是puts, exit, sleep, printf, atoi以及主函数main,均使用call函数进行引用。它们所含的参量数由0个到2个不等。在有第一个参量的函数中,第一个参量被存储在寄存器%rdi里,而在有第二个参量的函数中,第二个参量被存储在寄存器%rsi里。如在main函数里,%rdi被用来存储argc,%rsi被用来存储argv[](见第22和第23行)。

3.3.9 条件跳转/关系操作分析

本程序的条件跳转共有3处,分别在第25行,第32行,以及第54行。3处跳转均使用j系列汇编代码,其中第一次对应hello.c文本中的if语句,而后两个则被用到了for循环中。

3.4 本章小结

本章主要介绍了编译的概念及其作用,并以hello.i转化为hello.s的过程为例,较为详细地介绍了hello.s中各个部分的情况。通过这些情况我们可以发现,程序在转化到汇编代码后会进行一些隐式的优化,从而使得程序结构更加合理。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编是将程序由汇编语言变成机器语言的过程。汇编器将翻译的指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件中。在本次试验中,hello.s被编译成hello.o。

注意,与前两个中间文件不同,本次的文件无法直接打开。

作用:汇编这一步骤真正实现了将程序由高级程序设计语言转化为机器可以读懂的机器语言的过程。在完成了这一步之后,机器终于可以开始执行输入的程序了。

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

在Linux终端输入指令readelf -a hello.o来查看相关信息,可以发现,所得到的信息大致可以分为四部分。

ELF头:ELF头由Magic序列开始,列出了有关程序以及机器的许多信息。阅读ELF头部分,我们可以得出程序的进入地址、版本,机器的端序,以及ELF文件后续部分的信息(如节头的字符串表格参数)。

节头:节头囊括了程序中所有节的信息,包括每个节的名称、大小、偏移地址等。如.test节,大小为0x92。

重定位部分:本节主要介绍了文件重定位部分的相关信息,如偏移量、类型、系统名称等等。

符号表:本节展示了程序中可能会使用的符号,包括函数名称符号,以及一些系统内部设定的符号。

4.4 Hello.o的结果解析

objdump -d -r hello.o  分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

hello.o的反汇编文件hello.asm和hello.s均为汇编语言文件,二者主体也都是main函数,只不过前者只有45行,后者却有80行。而除了这个不同点,在语句的使用与选择方面,二者还有更大的不同。

  1. 语句的分段:hello.s的文件中存在.L2,.LC0等的分段这些分段的名称作为跳转标志存在在文件里。而hello.asm直接以main函数为头,使用语句的相对地址进行跳转。
  2. 函数的调用:在hello.s文件中,每次函数调用都会写明调用的函数名称(如printf,sleep),而在hello.asm中却没有这些名称,取而代之的是下一条指令的地址,在实质上并没有起到调用函数的作用。这是因为hello.c调用的函数均为C语言共享库中的函数,在实际操作时要根据机器存储函数的实际地址进行调整。而调整的这一步要在之后的链接环节完成,所以在汇编环节中暂且放置。
  3. 字符串访问。在访问字符串时,hello.s会访问特定代码段,而hello.asm的对应代码则直接访问0x0,这自然也是因为还没有进行重定位。
  4. 数的进制:hello.s使用十进制数,而hello.asm使用十六进制数。

4.5 本章小结

本章阐述了汇编的概念与定义,并以hello.s被汇编为hello.o的结果,以及ELF格式和反汇编文件为例说明了汇编环节的特点。在hello.asm和hello.s的比较中,我们逐渐掌握了汇编与编译的相似点与不同点,明确了两步各自的任务以及作用。

(第41分)

5章 链接

5.1 链接的概念与作用

概念:链接是将系统一系列克重定位目标文件进行有机组合,最终形成一个有机整体——可执行目标文件的过程。在本次试验中,hello.o,以及其他一些.o文件,被链接器(ld)链接成了可执行的二进制目标文件hello。

作用:通过链接步骤,程序变为了最终的可执行进程状态,完成了P2P的过程。而链接的步骤也为模块化编程提供了可能。

5.2 在Ubuntu下链接的命令

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

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

在Linux终端输入指令readelf -a hello来查看相关信息,可以发现,所得到的信息大致可以分为四部分。

ELF头:ELF头由Magic序列开始,列出了有关程序以及机器的许多信息。阅读ELF头部分,我们可以得出程序的进入地址、版本,机器的端序,以及ELF文件后续部分的信息(如节头的字符串表格参数)。

与之前4.3节内容对比,除去文件类型改变之外,hello的ELF格式还添加了程序头的分段,而节头部分的内容也有增加。

节头:节头囊括了程序中所有节的信息,包括每个节的名称、大小、偏移地址等。然而,与hello.oELF程序相比,helloELF程序在许多方面有所不同,包括但不限于节的个数,节的大小,以及地址的偏移值。这三种现象的前两种可以用不同可重定位文件的合并来解释,而最后一种可以用重定位本身来解释。

 

       程序头:这一部分在hello.o的ELF文件中未出现过,其主要作用是给出hello这个可执行文件中存在的程序信息,譬如程雪的偏移地址、文件的大小和内存的大小等等。

       重定位部分:本节主要介绍了文件重定位部分的相关信息,如偏移量、类型、系统名称等等。可以看到,原先的rela.text消失了。取而代之的是rela.plt和rela.dyn。

       符号表:helloELF格式的符号个数也相比之前有显著的增加,而每个符号也有了对应的值(Value),这无疑是重定位的效果。

5.4 hello的虚拟地址空间

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

通过查看edb的data dump,可知hello的虚拟空间地址从0x401000开始,到0x402000结束,其中各节头中节的位置与5.3中的相同。

5.5 链接的重定位过程分析

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

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

hello.o的反汇编文件hello2.asm和hello.asm均为汇编语言文件,二者主体也都是main函数,只不过前者有192行,后者只有45行。而除了这个不同点,二者的成分也有很大差异。也许是因为在链接这一步骤,参与者并不只有hello.o一个,在hello2.asm中有除了main函数之外的函数,如.plt函数,_init函数等等。这些函数数量众多,但大部分只有两三行的长度,一部分是系统级函数,另一部分是基础的操作函数。

此外,它们还在以下的几个方面有区别:

  1. 函数的调用:如之前所述,在hello.asm里,函数的调用实际上只是走个形式,直接跳转到下一行,事实上并没有调用。而在hello2.asm中,函数的调用形式又恢复成了之前hello.s里的样子,直接call对应的函数(如sleep@plt, exit@pit)。

  1. 地址的值:在hello.asm中,由于尚未进行链接,程序中只有main函数的相对地址而没有绝对地址。而在hellasm里,经过了链接步骤,绝对地址已经被计算出,在跳转时也就同时书写相对地址与绝对地址。
  2. 函数数量:在hello.asm中仅有hello.c的main函数一个函数,而hello2.asm中添加了许多系统函数与操作函数。

5.6 hello的执行流程

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

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

_init

0x7ffff7e21cc0

_init_misc

0x7ffff7edefb0

__GI___ctype_init

0x7ffff7df4530

check_stdfiles_vtables

0x7ffff7de3a20

_init_cacheinfo

0x7ffff7de3a80

handle_intel

0x7ffff7e7c5a0

intel_check_word

0x7ffff7e7c270

_start

0x4010f0

libc_start_main

0x7ffff7de3fc0

libc_csu_init

0x4011c0

main

0x401125

puts@plt

0x401090

printf@plt

0x4010a0

getchar@plt

0x4010b0

atoi@plt

0x4010c0

exit@plt

0x4010d0

sleep@plt

0x4010e0

__GI_exit

0x7ffff7e06a70

__run_exit_handlers

0x7ffff7e067e0

IO_cleanup

0x7ffff7e52d90

IO_flush_all_lockp

0x7ffff7e52a60

_IO_default_setbuf

0x7ffff7e52510

__GI__exit

0x7ffff7fe7b6e

5.7 Hello的动态链接分析

       早动态链接分析中,我们需要密切关注两个表:

  1. 全局偏移表(Global Offset Table),简称GOT表,这个表用于定位全局变量与函数,在链接这一步骤中起到重要作用。
  2. 过程链接表(Procedure Linkage Table),简称PLT表,这个表用于进行函数的延迟绑定。像之前的章节中,函数名称后面一致出现地“@plt”字符,就是过程链接表功能的体现。

在helloelf.txt中查找到GOT和PLT的位置,分别是0x403ff0和0x404000。

打开相应的edb页面,找到对应的地址。可以发现,在dl_init前,0x403ff0处为00串,在dl_init之后则有了数据,可以发现在dl_init前后这两处发生了变化。

5.8 本章小结

本节主要介绍了链接的概念与作用,并以hello.和其他.o文件一起链接为hello的过程为例详尽地解释了链接的全过程,其中edb,gdb等工具的使用使得整个解释更加清晰易懂。

(第51分)

6章 hello进程管理

6.1 进程的概念与作用

概念:进程,通俗地来讲就是一段程序的执行过程,是计算机中的程序有关某数据集合的一次活动。进程是程序的实体。

作用:进程的存在给应用程序提供了两个假象:其一是我们的程序好像独占地使用处理器,其二是我们的程序好像独占地使用内存系统。

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

作用:shell是命令解释器,提供了用户和操作系统之间交互的方式,相当于用户和操作系统之间的中介,代表用户运行其他程序。而bash则是命令处理器。

处理流程:

  1. 读取用户从键盘等输入设备输入的命令行。
  2. 分析读入的命令行,获取命令行参数并对其进行预处理(如对后台符号$做处理)。
  3. 构造未来会传给execve函数的argv变量。
  4. 判断输入的命令是否为内部指令(如fg,bg),如果是则直接调用内置命令处理函数执行,反之则为其分配子进程(利用fork函数)再进行。
  5. 如果还有命令行,要在子进程中继续分析。
  6. 根据用户是否要求后台运行决定是否调用waitpid等函数等待特定进程终止。

6.3 Hello的fork进程创建过程

Hello的fork进程创建过程是shell处理命令行中的一个环节。如果要让shell创建fork,就要输入正确的外部指令。以hello程序为例,用户要输入自己的学号姓名,以及每循环一次的暂停时间(如 1203201320 李某人 1),之后shell会处理分析这个命令行,在判断其为外部指令后,使用fork函数为其创建进程。反之则不会为其创建子进程。这就是Hello的fork进程创建全过程。

6.4 Hello的execve过程

execve函数会在当前进程的上下文中加载一个新程序,不是像fork函数那样开辟一个新进程。以hello函数为例,在确认命令行无语法错误之后,execve函数会在hello子进程处创建一个内存映像,并在之后将代码段、数据段转送到这个内存映像所在处,再设置程序计数器(PC)从程序的入口开始执行hello程序。

6.5 Hello的进程执行

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

进程上下文信息:系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

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

调度:内核会根据程序的预计耗时、占用处理器数量等因素选择是否继续执行某个进程,是否运行新的进程。当内核选择一个新的进程运行时,则称内核调度了这个进程。

用户态与核心态转换:处理器用一个控制寄存器的一个位来表示进程是运行在用户态还是核心态,即用户模式还是核心模式。设置位时,进程就在核心模式,反之进程就在用户模式。用户模式的进程不能够执行一部分指令,如printf等,也只能访问与调用函数相同的栈。

hello的进程执行:hello一开始在用户模式进行,在碰到puts,printf等需要在内核模式下才能运行的函数就切换到内核模式,运行完相应函数之后又回到用户模式继续运行。而当进程运行到sleep函数时,该程序被挂起,内核调度其他程序执行,直到休眠时间结束,系统再次进行上下文切换,hello进程才继续进行(当然,还是以用户模式)。

6.6 hello的异常与信号处理

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

异常类型:

  1. 中断(interrupt):来自I/O设备的信号,而并不是来自与程序指令,因此是异步的。
  2. 陷阱(trap)和系统调用:是执行一条指令的结果,譬如创建一个进程、加载一个程序、读一个文件等等。
  3. 故障(fault)由错误情况引起,但是可以被故障处理程序修正,如缺页异常,即需要从磁盘而不是从内存中取出需要的信息。
  4. 终止(abort)是不可恢复的致命错误所造成的结果,唯一的解决策略就是终止这个程序。

hello.c产生的信号:STGINT,SIGSTP,

 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

脸滚键盘,随意输入字母、数字、键入回车,程序仍正常进行。

输入Ctrl+C,程序终止。

输入Ctrl+z,程序停止。

6.7本章小结

本章主要介绍了进程的概念和作用,以及shell进行进程处理的大致流程。并且以hello为例展示了一个进程的生态。通过套用课本的知识与案例,实验人员能够更生动更深层地理解进程的相关概念。

(第61分)

7章 hello的存储管理

7.1 hello的存储器地址空间

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

逻辑地址:在带有地址变换功能的计算机中,当用户查询地址时,机器给出的地址就是逻辑地址。逻辑地址不一定对应着存储信息的真实地址,可能还需要经过一定的转换。

线性地址:线性地址是逻辑地址转变成物理地址的中间层,通过逻辑地址变换产生。在没有采取分页机制的系统中,线性地址即为物理地址。

虚拟地址:虚拟地址是带虚拟内存的计算机中虚拟空间所对应的地址。在某些情况下虚拟地址就是线性地址。

物理地址:物理地址又称实际地址、真实地址,是计算机真实储存文件的地址,是逻辑地址经过一系列变换之后得出的。

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

段式管理,即把一个程序分成若干段,其中每一段都是一个逻辑实体,这有点类似于程序的模块化。每个段地址都又两部分组成:其一是段名(也是地址形式的),其二是段偏移量。而段名又分为两部分:索引号以及种类标识符。索引号就是单纯的地址,而种类标识符标识的是寄存器存储数据的种类。于是,就可以根据这些信息确定地址。其缺点是可能有碎片化残余。

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

页式管理与段式管理相似,就是把一个程序分成若干个页进行管理。页式管理把内存空间按页的大小划分成片或者页面,然后把其虚拟地址与真实存储信息的物理地址建立一一对应关系。通过这样一一对应的映射,系统便可由虚拟地址找到物理地址。页式管理具有无外碎片、可跳跃存放等优点,但对系统的内存提出了一定要求。

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

VA,即虚拟地址;PA,即物理地址。这部分的转换通常会设计到虚拟内存到物理内存的映射。

在页式管理的方法中,每次查询内存管理单元(MMU)都会不可避免的查询一次页表条目(PTE),这样的查询有可能会不命中,从而访问磁盘,这无疑很耗时间。因此为了缩短其耗时,一些机器设置了一块区域用来存储页表条目的缓存,即TLB。

而在页表的级数方面,也存在降低系统效率的隐患:以一级页表为例,在一级页表中,系统会给每一个程序都分配一个大小相同的页,这就意味着有相当数量的程序不能完全占用整张页表,造成空间的浪费。将页表分级,在所需空间较小时只分配初级的页表,只有在内存需求足够大时才分配高级的页表。

而结合TLB和四级页表,系统就可以根据缓存的是否命中,有效率、空间利用率高地完成虚拟地址向物理地址的转化。

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

在得到物理地址之后,程序如果要取出相应地址内存储的内容,还要有一个访存的过程。由于是访存,所以通常会涉及到在磁盘里查找内容,效率会有些低,因此这一部分也采用了缓存的方法:用缓存存储一部分地址对应内容,如果访问的地址正好是存储的那部分内容,则可以不用访问磁盘直接获得内容。而三级缓存的存在,也同样是为了防止内存空间的浪费。

7.6 hello进程fork时的内存映射

当hello进程被fork时,fork会创建当前进程的内存副本和地址副本,两个副本的标识符分别写作mm_struct和xm_area_struct,这两个标识符的存在保证了父进程和子进程共用相同的地址空间和存储结构。而为了保证父进程和子进程不受干扰,这两个进程的xm_area_struct都会被设置成私有(private),进程中的每个页面也会被标记成只读。

7.7 hello进程execve时的内存映射

如上一章所述,execve函数的主要功能是在当前的进程中重新运行一个程序,在hello的例子中,就是删除分配给子进程那部分地址的程序,并运行hello程序,再先后映射到私有区域(譬如相应的栈和堆)和共享区域(譬如hello和共享对象的动态链接)。在execve这一步的最后,要设置程序计数器(PC),调整进程下一步的入口点,从hello程序开始的位置执行程序。

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

缺页故障,就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问(在虚拟内存的相关领域中,DRAM的不命中称为缺页)。在没有缺页故障时,内核只需要在DRAM中寻找地址,而出现缺页故障时,内核收到惩罚,只能在比DRAM慢10000倍左右的磁盘中寻找地址。

缺页故障的处理模式遵循如下流程:

缺页异常的处理过程中可能出现牺牲页,即被新的缓存顶掉的缓存单元。

7.9动态存储分配管理

动态存储分配管理的操作函数其实在C语言课程中就有所提及,如常见的malloc函数,calloc函数,在shell调用动态内存分配器时,后者就会使用这些函数来开辟、维护相应的虚拟内存空间,又称为堆。每个空间块或者是已分配的,或者是空闲的。前者为应用程序保留,直到它被释放成为后者,而后者有待接下来的分配。

动态内存器分为显示分配器和隐式分配器两种:要求应用显示的释放任何已分配的块的显示分配器,比如前文提到的malloc函数;而通过检测已分配块是否还被程序使用来判断是否释放该块的分配器是隐式分配器。隐式分配器具有进程自动回收功能。

不论是显示内存分配,还是隐式内存分配,如何进行虚拟空间的分配管理,即堆的分配管理都是很重要的。隐式空间链表就是一种常见的简单堆算法。隐式空闲链表由三部分组成:头、有效载荷和填充,其中填充为可选项。其寻找空闲块的步骤共有三步——首次适配、再次适配和最佳适配,其中再次适配最快、最佳适配最好。之后再进行内存块的分割、释放与可能的合并。

7.10本章小结

本章主要介绍了hello程序的020过程中遇到的有关地址转换和内存映射的问题,例如段式管理、页式管理、三级缓存管理、fork和execve时的内存映射等等。既是程序运行分析的重点,也是程序运行分析的难点。

(第7 2分)

8章 hello的IO管理

8.1 Linux的IO设备管理方法

设备的模型化:文件

设备管理:unix io接口

在Linux系统中,所有的I/O设备都被转化成文件,而所有的输入和输出就都被当做对对应文件的读和写,被统一到了一个名为Unix的I/O接口中。这样的操作无疑增加了Linux处理I/O设备的模式性和一致性。

8.2 简述Unix IO接口及其函数

Unix I/O接口是Linux内核引出的一个简单、低级的接口,能够进行文件的读写、系统调用等操作。以下列出其部分功能。

  1. 改变当前的文件位置:对于每个打开的文件,内核保持着一个文件位置k,初始为0.这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek操作,显示地设置文件的当前位置为k。
  2. 读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个EOF的条件(这个应该不需要列出全称),应用程序能够检测到这个条件,虽然在文件结尾处并没有明确的EOF符。
  3. 关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因而终止,内核都会关闭所有打开的文件并释放它们的内存资源。
  4. 打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作符中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需要记住这个描述符。

下面列出一部分Unix I/O 的函数:

  1. open函数:

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

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

  1. close函数

int close(int fd);

进程通过调用close关闭一个打开的文件fd,返回器操作结果(是否正常关闭)。

  1. read函数

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

read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf,在文件中字节数少于n时,直接复制到文件末尾。返回值-1表示出现错误,而返回值0表示EOF。在其他情况下,返回值表示的则是实际传送的字节数量。因此在这个函数里,非0的返回值并不代表程序出错。

  1. write函数

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

write函数与read函数正好相反,是从内存位置buf复制之多n个字节到描述符fd的当前文件位置。

8.3 printf的实现分析

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

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

从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.

字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。

显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

以下为Linux系统下printf函数的函数体

可以看到,printf函数中含有另外的两个函数:vsprintf函数和wirte函数,代码分别如图所示。其中vsprintf返回的是要打印的字符串的长度,而write是把buf中第i个元素写到终端。而在write函数的最后,有一句汇编代码int INT_VECTOR_SYS_CALL,这表明了printf函数的实现还和syscall函数有关。而syscall的功能,自然就是直接打印字符,直到遇到’\0’。

8.4 getchar的实现分析

getchar有一个int型的返回值。当程序调用getchar时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车为止(回车字符也放在缓冲区中)。这显然是一个异步异常,即中断。当用户键入回车之后,getchar才开始从stdio流中每次读入字符,且每调用一次只读取一个字符。

getchar函数的返回值是用户输入的第一个字符的ascii码,如出错返回-1,且将用户输入的字符回显到屏幕。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。

getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章介绍了hello程序在运行时的I/O接口相关信息,譬如接口的特点、形式以及接口的操作函数等等。并且在这一部分还以printf函数和getcahr函数为例分析了函数实现过程的接口状态变动,用例详实,选题巧妙。

(第81分)

结论

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

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

正如第一章第一节所总结的那样,hello所经历的过程可以概括成P2P和020两个部分,首先从程序hello.c变成可执行文件hello,再从一开始被shell接收到最后被shell清除,这是一个完整的过程。详细来讲,每一个大步骤又可以分为数个小步。

  1. 用高级程序语言(本实验中为C语言)编写hello.c
  2. 用预处理器(cpp)处理hello.c得到C语言文件hello.i。
  3. 用编译器(ccl)处理hello.i得到汇编代码文件hello.s。
  4. 用汇编器(as)处理hello.s得到可重定位目标程序hello.o
  5. 用链接器(ld)链接hello.o和其他.o文件得到可执行目标程序hello
  6. 执行hello,shell判断为外部函数。
  7. fork函数创建子进程,execve函数加载并运行hello程序。
  8. 执行hello程序。
  9. hello程序执行完毕,子进程被父进程回收,hello的一生到此结束。

总结感想:CSAPP不愧是计算机基础书籍顶级作品,其中介绍的每一个章节都与程序的加载与运行过程一一对应。在一个个编译程序、壳与核、数据与算法的协作之下,hello.c的一生丰富而精彩,即便是在写完这份报告之后,我心中的震撼之情也久久不能平息。哎我学习计算机系统这门课之前,我是从来也从未想到过这一点的。而现在,当我真正了解到计算机系统的精确与神奇,知晓一代代计算机专家为其作出的贡献之后,只能感叹一声前人的伟大,与自己的渺小。

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

附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.i

hello.c预处理后的文件

hello.s

hello.i编译后的文件

hello.o

hello.s汇编后的文件

hello.asm

hello.o反汇编的文件

hello

hello.o与其他文件链接形成的文件

hello2.asm

hello反汇编的文件

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

参考文献

[1]  https://zhuanlan.zhihu.com/p/376217387内存管理:隐式空闲链表

[2] https://baike.baidu.com/item/%E7%BC%BA%E9%A1%B5%E4%B8%AD%E6% 96%AD/5029040?fr=Aladdin 缺页中断

[3]  https://www.kafan.cn/A/43pqr2xonm.html 压缩包中没保存的word怎么恢复

[4]  https://blog.csdn.net/weixin_42695485/article/details/110248969 UNIX IO 简介

[5]  https://blog.csdn.net/qq_45656248/article/details/117898044 动态内存分配--隐式空闲链表

[6]  https://www.it1352.com/1949402.html endbr64指令实际上是做什么的?

[7]  https://blog.csdn.net/u010164190/article/details/104716110/got、plt表介绍

[8]  https://blog.csdn.net/zsj100213/article/details/85083922 内存回收的关键函数以及其功能

[9]  兰德尔E布莱恩特. 《深入理解计算机系统》. 机械工业出版社,2017:7-1.

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值