计算机系统

大作业

题 目程序人生-Hellos P2P

专 业 计算机科学与技术

学   号

班 级

学 生

指 导 教 师刘宏伟

计算机科学与技术学院

2022年5月

摘 要

本文通过对几乎所有程序员的入门程序hello的一生做了介绍,逐步分析了其经历预处理,编译,汇编,链接,执行的过程,以及shell对其进行的操作,总结所学过的知识,加深了自己对计算机系统的理解。

关键词:预处理,编译,汇编,链接,执行,计算机系统,ubuntu;

目 录

第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:在shell命令行输入gcc hello.c -o hello后经过预处理器cpp的预处理,生成hello.i文件,再经过编译器ccl的编译,生成hello.s文件,再经过汇编器as汇编,生成hello.o文件,最后通过链接器ld的链接,生成最后的可执行文件。

020:shell命令行输入命令./hello。Shell调用fork()函数创建子进程,再调用evecue程序来加载并运行hello程序。在程序运行结束时,会向父进程发送SLGCHLD信号,等待父进程对其进行回收。当hello被回收时,它的生命周期就结束了。

图 1

1.2 环境与工具

软件环境ubuntu16.04;

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

开发工具gcc; vim; edb;objdump.

1.3 中间结果

hello.c 源文件

hello.i 经过预处理的源程序

hello.s 经过编译以后的程序文件

hello.o 汇编器汇编后的可重定位文件

hello 程序文件

hello.elf 可重定位目标文件的ELF文件格式

hello.elf 可执行文件的ELF文件格式

1.4 本章小结

本章对hello程序的一生做了总体性的概括,同时介绍了本论文所用到的实验环境,包括软件环境,硬件环境,开发工具等。最后列出了本文用到的一系列中间结果。


第2章 预处理

2.1预处理的概念与作用

预处理的概念:

预处理就是在编译器对程序进行编译之前,对程序进行初步处理的过程。预处理器(CPP)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第1行的#include命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中。结果就得到了另一个C程序,通常是以.i作为文件扩展名。

预处理的作用:

1,头文件的包含将包含的文件插入到程序指定位置。

2,#define定义的符号与宏替换用实际值替换宏。

3,删除程序的注释

4,处理#if等预编译指令。

2.2在Ubuntu下预处理的命令

gcc -E hello.c -o hello.i

图 2-2

2.3 Hello的预处理结果解析

我发现,预处理之后的程序多达3060行,除此以外,在hello.i的最后部分我们可以看到C语言源程序,这说明之前的三千多行是对hello.c中的头文件stdio.h,unistd.h,stdlib.h文件的展开。最后的hello.i程序中是没有宏的

图2-3-1

图2-3-2

2.4 本章小结

本章主要是对预处理部分进行了一定的阐述,在本章中,我们生成了预处理后的文件hello.i,这个文件比源文件要大很多,通过观察,我们发现在该文件的末尾出现了编写main函数体,说明了之前的许多行都是对头文件的展开。
第3章 编译

3.1 编译的概念与作用

编译的概念:

编译器将文本文件hello.i翻译成文本文件hello.s,通过词法分析和语法分析,确认正确之后生成汇编语言程序。

编译的作用:

  1. 检查程序是否有语法错误等错误
  2. 对程序进行一定程度的优化。
  3. 生成汇编语言程序

3.2 在Ubuntu下编译的命令

gcc hello.i -S -o hello.s

图3-2

3.3 Hello的编译结果解析

图3-3-1图3-3-2

在解析之前,先介绍汇编语言开头的一些符号

.file 程序源文件

.text 代码段,立即数也存放在这里

.section 到节头部表的索引

string 字符串

.rodata 存放只读变量,例如格式串等

.align 对齐

.type 类型,要么是数据,要么是函数

.global 表明全局变量

3.3.1数据——常量

此处”Hello %s %s\n”即为常量,存放在.rodata段

此处的8为常量,是一个立即数。存放在.text段中。

3.3.2数据——变量

-32(%rbp)中存放的是变量,这个操作将其赋值到%rax寄存器中

3.3.3赋值操作

该部分同上,上图操作即为一个赋值操作。

3.3.4算术运算

图3-3-3

以上这几条指令均为算术加法运算指令,将结果存在%rax,-4(%rbp)中。

像这样的指令还有很多图3-3-4

3.3.5 关系操作

图3-3-5

这里将-4(%rbp)中的值与8进行比较。

3.3.6 函数操作

图3-3-6

以该指令为例,该指令调用了getchar()函数。在函数结束的时候通过ret指令进行返回。

此外该汇编程序还调用了printf,sleep等函数。

3.3.7 跳转

图3-3-7

该指令为条件跳转,当-4(%rbp)中的值小于等于8时,就跳转到.L4的位置。

3.3.8 leaq操作

leaq指令通常用来加载地址,但是也可以用来算数操作:

图3-3-8

此处该指令即为加载地址。

3.4 本章小结

本章主要对编译进行了分析,首先是介绍了编译的概念和作用,之后结合hello.s对汇编指令进行了一定的分析,分析了关系操作,算术操作,跳转操作,地址加载操作等,理解汇编语言对于编程人员来说具有重要意义。

第4章 汇编

4.1 汇编的概念与作用

汇编的概念:

汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。汇编器将输入的.s文件输出成可重定位文件。

汇编的作用:

汇编的主要作用就是把汇编语言翻译为机器语言,生成.o文件。

4.2 在Ubuntu下汇编的命令

gcc hello.s -c -o hello.o

图4-2

4.3 可重定位目标elf格式

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

Elf头:ELF (ELF header)以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。

图4-3-2

节头:节头部表描述了不同节的位置和大小

图4-3-3

本文件没有程序头

重定位节:描述一些在重定位过程中需要用到的一些信息。

图4-3-4

符号表:描述了符号的一些重要信息

图4-3-5

4.4 Hello.o的结果解析

主要有两个方面发生了变化,首先是函数调用方面,.s文件中call后面跟着的是函数名@PLT,而反汇编的文件中跟着的是call的下一条指令。

图4-4-1

在指令跳转方面:原来跟着的是标签,现在跟着的是实际地址(重定位之前):

图4-4-2

图4-4-3

图4-4-4

4.5 本章小结

本章对汇编做了一定的的介绍,介绍了汇编的概念和作用,同时也通过elf展示了生成的汇编文件。并且通过反汇编与上一章节的汇编文件作比较,分析了差异。

第5章 链接

5.1 链接的概念与作用

链接的概念:

链接是指从 hello.o 到hello生成过程。链接器将多个可重定位文件,静态库,动态库等链接起来,生成可执行文件。

链接的作用:

让把大型程序文件分成不同的模块成为了可能。

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

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

ELF头:文件的类型发生了改变,为可执行程序类型,程序入口不再是0x0

图5-3-1

节头,节的数目变多了,里面有一些是可执行文件特有的,比如.init

图5-3-2

程序头图5-3-4

段节:

图5-3-5

Dynamic section

图5-3-6

重定位节:

图5-3-7

符号表:可执行文件多了许多符号

图5-3-8

5.4 hello的虚拟地址空间

图5-4-1

图5-4-2

程序的内存是0x0040100开始的,恰好与init节,rodata节等相近。这些节都是储存在低地址处的。并且每个节都可以找到对应的位置。

  1. .init PROGBITS 0000000000401000 00001000

[17] .rodata PROGBITS 0000000000402000 00002000

5.5 链接的重定位过程分析

图5-5

二者的不同主要体现在以下几个方面:

  1. 反汇编call后面跟着的成了实际的地址;
  2. 反汇编多了许多新的节和函数,可以推测这些节是链接之后加入的

重定位:

重定位节和符号定:在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输人模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data 节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输人模块定义的每个节,以及赋给输人模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。

重定位节中的符号引用:在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目的数据结构。

5.6 hello的执行流程

401000

401020

401090

4010a0

4010b0

4010c0

4010d0

4010e0

4010f0

401120

401125

4011c0

401230

401238

5.7 Hello的动态链接分析

[13] .plt PROGBITS 0000000000401020 00001020

0000000000000070 0000000000000010 AX 0 0 16

[20] .got PROGBITS 0000000000403ff0 00002ff0

0000000000000010 0000000000000008 WA 0 0 8

开始和结束时,这些条目的位置发生了变化。PLT是个数组,其中每个条目都是16字节代码,PLT[0]是一个跳转到动态链接器里面的条目。每个条目都负责一个具体的函数。

GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[0]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。

计算机系统通过利用PLT和GOT这两个数据结构来实现延迟绑定,进行动态库的链接。

图5-7-1

这是执行之后的

图5-7-2

5.8 本章小结

本章我对链接的概念和作用做了简要的介绍,同时也以elf的格式观察了hello文件,并与之前的可重定位文件进行了比较,进一步理解了虚拟地址空间等,了解了hello的执行流程,对动态链接也有了进一步的认识。

6hello进程管理

6.1 进程的概念与作用

进程的概念:

进程的一个经典定义就是一个执行中的程序实例。进程不仅包括代码,还包括当前的活动。

进程的zuoy:

进程提供给了我们假象,好像我们的程序是系统当前运行的唯一的程序一样,我们的程序好像是独占地使用内存和处理器,处理器就好像是无间断地一条一条执行我们的指令,我们的代码和数据好像是系统内存中唯一的对象。

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

Shell是一个交互级的应用程序,它代表用户执行其它程序。

首先是解析以空格分隔的命令行参数,并构造最终会传递给execve的argv向量。第一个参数被假设为要么是一个内置的shell命令名,马上就会解释这个命令,要么是一个可执行目标文件,会在一个新的子进程的上下文中加载并运行这个文件。如果最后一个参数是一个“&”字符,表示应该在后台执行该程序(shell不会等待它完成)。否则,应该在前台执行这个程序(shell会;等待它完成)。在解析了命令行之后,检查第一个命令行参数是否是一个内置的shell命令。如果是,它就立即解释这个命令,并返回值1。否则返回0。简单的shell只有一个内置命令一quit命令,该命令会终止shell。

6.3 Hello的fork进程创建过程

新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。

6.4 Hello的execve过程

调用函数fork创建新的子进程之后,子进程会调用execve函数,在当前进程的上下文中加载并运行一个新程序hello。execve 函数一般不返回,除非运行错误,它将删除该进程的代码和地址空间内的内容并将其初始化,然后通过跳转到程序的第一条指令或入口点来运行该程序。它调用启动代码。启动代码设置

栈,并将控制传递给新程序的主函数。

5 Hello的进程执行

逻辑控制流,并发流:

即使在系统中通常有许多其他程序在运行,进程也可以向每个程序提供一种假象,好像它在独占地使用处理器。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称逻辑流。 一个逻辑流的执行在时间与另一个流重叠,称为并发流, 这两个流被称为并发地运行。

用户模式和内核模式的切换:

运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷人系统调用这样的异常。当异常发生时,控制传递到异常处理程序,处理器将模式从用户模式变为内核模式。处理程序运行在内核模式中,当它返回到应用程序代码时,处理器就把模式从内核模式改回到用户模式。

进程时间片:

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

进程上下文:

内核为每个进程维持一个上下文。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。

6.6 hello的异常与信号处理

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

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

图6-6-1

中断是异步发生的,是来自处理器外部的I/O 设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。

图6-6-2

陷阱是有意的异常,是执行一条指令的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。

图6-6-3

故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。

图6-6-4

终止是不可恢复的致命错误造成的结果,通常是一些硬件错误

图6-6-5

信号

图6-6-6

程序运行:

正常运行:

图6-6-7

按下ctrl+z

图6-6-8

输入ps:

图6-6-9

输入pstree:

图6-6-10

输入jobs:

图6-6-11

输入fg:

图6-6-12

输入kill:

图6-6-13

Ctrl+c+ps

图6-6-14

6.7本章小结

本章我介绍了进程相关的概念和作用,介绍了一些进程相关函数。总结了一部分异常和信号。同时在程序运行的时候,也试验了一部分指令,探究了其结果。

总结

一个hello程序的一生大概经历了如下几个阶段:在其还未出生之时,shell程序便通过对命令行的解析,调用fork()函数创建了一个子进程,调用execve函数来加载hello,开始了hello一生的序幕。一开始经过预处理器的编译,让它得到了扩展,充实了自己,成为了经过预处理的源程序hello.i,然后经过编译器对其进行汇编,生成了hello.s文件,之后经过汇编器的汇编,生成了可重定位文件hello.o,此时hello终于可以找到和自己的朋友们,它们一起被链接器链接成可执行文件,就像在生活中我们不能一个人解决所有问题,所以我们需要队友,朋友,团队,集体。在执行了之后,它会被父进程回收。结束其短暂却又不凡的一生。这既是刚入门的编程人员对编程世界的问好,也是一种积极的态度。

计算机系统是一门十分复杂但是极其有用的一门课程,通过这门课,我不仅对计算机的原理有了更深的了解,为历史上计算机科学家们的巧妙构思感到惊叹,同时生活中的一些现象也可以用计算思维来解释,总而言之,这门课对我的影响很大。关于新的构思,也许可以用三进制计算机?不过随着量子计算机的发展,不知道未来的计算世界会是怎样崭新的模样。

参考文献

【1】深入理解计算机系统