哈工大计算机系统大作业——程序人生
Posted chjmlo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哈工大计算机系统大作业——程序人生相关的知识,希望对你有一定的参考价值。
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 航天学院人工智能
学 号 7203610228
班 级 2036015
学 生 韩雨萌
指 导 教 师 史先俊
计算机科学与技术学院
2022年5月
本文介绍hello从hello.c程序编写完成开始到hello最终被回收结束,介绍了hello从预处理、编译、汇编、链接直到被回收的整个过程及原理,同时介绍了linux下的内存管理、进程管理、I/O管理、虚拟内存、异常信号的相关内容,勾勒了hello完整坎坷却不失华丽的一生。
关键词:预处理,编译,汇编,链接,linux,P2P
目 录
第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 -
参考文献........................................ - -
第1章 概述
1.1 Hello简介
Hello最初是一个.c文件,经过预处理得到hello.i文件,编译器编译得到hello.s文件,汇编器汇编得到hello.o目标文件,链接器与库函数链接生成一个可执行程序,shell为其fork产生子进程的过程即为P2P(From Program to Process )具体过程如下图:
图1.1p2p过程
O2O(From Zero to Zero):操作系统调用execve执行这个Process,映射虚拟内存,先删除当前虚拟地址的数据结构并为hello创建新的区域结构,进入程序入口后载入物理内存,进入main函数执行目标代码,程序结束后,shell父进程回收子进程hello,内核删除相关信息。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1.2.1 硬件环境
64位操作系统x64CPU;处理器AMD Ryzen 7 4800U with Radeon Graphics 1.80 GHz;机带RAM16.0GB
1.2.2 软件环境
Windows 10 64位;Vmware16;Ubuntu20.04。
1.2.3 开发工具
codeblocks64位20.04;Visual Studio2019;vi/vim/gedit+gcc;EDB
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名称 | 文件作用 |
hello.c | Hello源程序 |
hello.i | 预处理生成文件 |
hello.s | 汇编程序(文本) |
hello.o | 可重定位目标程序(二进制) |
hello.elf | Hello.o的ELF格式可执行文件 |
hello | 链接器生成的可执行目标文件 |
hello.asm | Hello.o的反汇编文件 |
hello1.asm | Hello的反汇编文件 |
hello1.elf | Hello的ELF格式可执行文件 |
表1 中间文件
1.4 本章小结
本章简单介绍了hello程序,以及hello的P2P、O2O过程,介绍了完成本论文的硬件环境和软件环境以及开发工具,最后列出了编写本论文生成的中间结果的文件以及作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
2.1.1预处理的概念
预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。比如#include <stdio.h>命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中。结果就得到了另一个 C 程序,通常是以 .i 作为文件扩展名。
2.2.1预处理的作用
1.将源文件中以“include”格式包含的文件复制到编译的源文件中;
2.用实际值替换用“#define”定义的字符串;
3.根据“#if”后面的条件决定需要编译的代码。
2.2在Ubuntu下预处理的命令
对hello.c文件进行预处理的命令是:gcc -E -o hello.i hello.c
图2.1预处理命令及结果
预处理后生成一个hello.i的文件。
2.3 Hello的预处理结果解析
经过预处理之后,生成hello.i文件,打开该文件可以发现,文件的内容明显增加,该文件主要为为对原文件中的头文件进行展开,例如声明函数、定义结构体、定义变量、定义宏等内容。另外,如果代码中有#define命令还会对相应的符号进行替换。但是函数主体部分并未发生改变。
图2.2hello.i文件内容
2.4 本章小结
本章主要介绍了预处理的概念及其作用,同时给出了在Linux下预处理的指令,并对生成的hello.i文件进行解析。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译器将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序。
作用:将输入的高级程序设计语言翻译成以汇编语言或机器语言表示的目标程序作为输出,为不同高级语言的不同编译器提供了通用的输出语言。
3.2 在Ubuntu下编译的命令
对hello.i进行编译的命令是:gcc -S -o hello.s hello.i
图3.1编译命令及结果
3.3 Hello的编译结果解析
在hello.s中用到的数据类型有整数,数组,字符串。
进行的操作有赋值、类型转换、算术操作、关系操作、数组/指针/结构操作、控制转移(分支、循环)、函数操作。
下面首先对程序开始部分进行解析。
3.3.1汇编指令介绍
图3.2汇编指令
.file:声明源文件
.text:代码节
.section:指示把代码划分成若干个段(Section)
.rodata:只读代码段
.align:数据或者指令的地址对其方式
.string:声明一个字符串(.LC0,.LC1)
.global:声明全局变量(main)
.type:声明一个符号是数据类型还是函数类型
在hello.s中用到的数据类型有整数,数组,字符串。
3.3.2整数
在hello.c程序中一共有两个变量为int,分别是i和argc。
- 对于i,i为局部变量,而局部变量通常是会被保存在栈或者寄存器中,在本程序中,我们可以看到i被存储在栈 %rbp处,由于是int类型,故占据4个字节。
图3.3定义i 图3.4对应i的循环加1操作
- 对于变量argc,这个变量为main的第一个形参,由寄存器%edi保存。
图3.5变量argc
(3)除了int型变量外还有一些常数,他们是作为立即数直接出现的,形式为$+常数。如下图的$32.
图3.6常数表示形式
3.3.3数组
程序中定义的数组char *argv[]为main函数的第二个形参,是一个字符型指针数组,保存在寄存器%rsi中。随后保存到寄存器%rbp中,同时申请32字节的空间。argv数组中一个元素的大小为8个字节,要想访问数组中的数据就需要加上相应的偏移量。
图3.7数组表示形式
3.3.4字符串
在本程序中的字符串为用法: Hello 学号 姓名 秒数!和Hello %s %s,对应存储在.rodata节中,其中汉字被编码成UTF-8格式,一个汉字占据3个字节,每个字节用\\分割。
图3.8字符串表示形式
3.3.5赋值操作
在本程序中的赋值操作仅有i=0这一条,在汇编中用mov实现,在hello.s中对应代码如下。
图3.9赋值操作
3.3.6 类型转换
在本程序中利用atoi函数将argv[3]由字符串转换为了整型。对应操作如下:
图3.10类型转换操作
3.3.7算术操作
(1)在本程序中可以看到使用了i++的算术操作,通过addl $1,-4(%rbp)这条命令实现。
(2)同时,在为数组argv开辟栈空间时的subq $32, %rsp也为算术操作
(3)以及在取出argv数组中的对应内容时进行的偏移量计算也是算术操作。如addq $24, %rax等。
3.3.8关系操作
本程序中用到的关系操作有两处,分别为if(argc!=4)和for(i=0;i<8;i++)
(1)其中 if(argc!=4)对应的汇编语言为cmpl $4, -20(%rbp)。进行比较,然后设置条件码,根据条件码结果判断是否跳转。
图3.11 if操作
(2)for(i=0;i<8;i++)对应的汇编语言为cmpl $7, -4(%rbp)。进行比较后,根据条件码结果判断是否跳转。
图3.12 for循环操作
3.3.9数组/指针/结构操作
在本程序中访问argv为指针型数组,对此数组的操作通常由mov指令实现。取到argv数组的首地址,然后对首地址加相应字节得到对应的地址,然后再通过地址中的内容找到对应的字符串,存储在寄存器。
图3.13指针操作
3.3.10控制转移
(1)分支(if)
判断argc是否等于4, cmpl比较了argc与4的大小之后设置条件码,然后再判断是否跳转到L2,这个判断是基于条件码ZF位,如果为0则跳转,不为0则继续往下执行。
图3.14分支
(2)循环(for)
判断变量i是否满足循环条件i<8,首先在L2中将i赋值为0,跳转到L3,与7进行比较判定是进入循环还是继续执行,如果i<=7则跳转到L4进入循环,如果i>7则继续执行下一行,而不进入循环。
图3.15循环
3.3.11函数操作
本程序涉及到的函数操作包括参数传递、函数调用和函数返回
- 参数传递(main函数)
main函数被存储在.text节中,有两个参数,分别为命令行传入的argc和argv[],开始被保存在寄存器%rdi和%rsi中。
图3.16参数传递
- 函数调用(exit,printf, atoi,sleep,getchar)
在我们的程序中有两处调用了printf函数。第一处调用由于只是输入一串字符串,所以被优化成puts函数,之后通过call来调用puts,而第二处调用printf,有三个参数,因此我们需要取出参数,将参数分别保存在%rdx,%rsi和%edi寄存器中,之后通过call调用printf。
图3.17函数调用
对于exit()函数如果我们输入的参数不是4个,程序就会调用exit函数结束程序,先将1传给%edi,然后调用exit函数退出。
图3.18 exit()函数
对于atoi函数它是用来将我们输入的第四个参数从字符串转化为整型,将第四个参数存储在%rdi中,作为atoi函数的参数,然后调用atoi函数。
图3.19 atoi()函数
对于sleep函数,它的参数就是atoi的返回值,所以在atoi被调用完后,会将其返回值从%eax传给%edi作为sleep函数的参数。
图3.20 sleep()函数
对于getchar()函数,不需要传递参数,直接调用即可。
图3.21getchar()函数
- 函数返回(return)
函数的返回值一般在寄存器%eax中,如果有返回值,则要先把返回值存到%eax中,再用return返回。源程序中有主函数的return 0;就是先把返回值立即数0存到%eax中,再用return返回。
图3.22return()函数
3.4 本章小结
概括了编译的概念和作用,通过对hello编译结果的分析重点分析了c程序的数据与操作翻译成汇编语言时的表示和处理方法。此时的hello程序以及变成了更底层的语言。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as)将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序(relocatable object program)的格式,并将结果保存在.o 目标文件中,.o 文件是一个二进制文件,它包含程序的指令编码。
作用:将汇编代码转换为机器指令,使其在链接后能被机器识别并执行。
4.2 在Ubuntu下汇编的命令
gcc -c -o hello.o hello.s
图4.1生成hello.o命令及结果
4.3 可重定位目标elf格式
在linux下生成.elf格式文件的命令为:readelf -a hello.o > hello.elf,如下图所示:
图4.2生成hello.elf
图4.3 elf文件内容
在linux命令行输入命令readelf -h hello.o即可看到hello.elf中ELF Header的具体信息,结果如下:
图4.4ELF Header内容
ELF头(ELF header)以一个16B的序列Magic开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
在linux命令行输入命令readelf -S hello.o即可看到hello.elf中Section Headers的具体信息,结果如下:
图4.5 Section Headers内容
Section Headers:节头部表,记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。 由于是可重定位目标文件,所以每个节都从0开始,用于重定位。
在linux命令行输入命令readelf -s hello.o即可看到hello.elf中Symbol table的具体信息,结果如下:
图4.6 Symbol table内容
Symbol table:符号表,存放程序中定义和引用的函数和全局变量的信息。每个可重定位目标文件在.symtab中都有一张符号表,name是符号名称, value是符号相对于目标节的起始位置偏移,对于可执行目标文件,该值是一个绝对运行的地址。size是目标的大小,type数据或者函数。Bind字段表明符号是本地的还是全局的。
查看重定位节,具体信息如下:
图4.7 .rela.text内容
.rela.text:重定位节,保存的是.text节中需要被修正进行重定位的信息;任何调用外部函数或者引用全局变量的指令都需要被修正;调用外部函数的指令需要重定位;引用全局变量的指令需要重定位; 调用局部函数的指令不需要重定位;在可执行目标文件中不存在重定位信息。包括以下几个部分:Offset:需要被修改的引用节的偏移;Info:包括symbol和type两个部分,symbol在前面四个字节,type在后面四个字节;symbol:标识被修改引用应该指向的符号;Type:告知链接器应该如何修改新的应用;Attend:一个有符号常数,一些重定位要使用它对被修改引用的值做偏移调整Name:重定向到的目标的名称。
4.4 Hello.o的结果解析
通过objdump -d -r hello.o 生成hello.o的反汇编,下面与第3章的 hello.s进行对照分析。如图为反汇编结果,同时objdump -d -r hello.o > hello.asm将结果保存在hello.asm中。
图4.8hello.o反汇编结果
机器语言的构成:机器语言是机器能直接识别的程序语言或指令代码,无需经过翻译,每一操作码在计算机内部都有相应的电路来完成它,或指不经翻译即可为机器直接理解和接受的程序语言或指令代码。机器语言使用绝对地址和绝对操作码。不同的计算机都有各自的机器语言,即指令系统。从使用的角度看,机器语言是最低级的语言。
机器语言与汇编语言的映射关系:机器语言是用于控制计算机中处理器的实际位,通常被视为十六进制数字序列(通常为字节).处理器从程序存储器中读取这些位,这些位表示下一步操作的"指令".因此,机器语言提供了一种将指令输入计算机的方式(无论是通过交换机,穿孔带还是二进制文件).
汇编语言是一种更易读的机器语言视图.指令和寄存器不是将机器语言表示为数字,而是给出名称(通常是缩写词或助记符,例如ld表示"加载").与高级语言不同,汇编程序非常接近机器语言.主要的抽象(除了助记符)是使用标签而不是固定的内存地址和注释.
汇编语言程序(即文本文件)由汇编程序转换为机器语言.反汇编程序执行反向功能(尽管标签的注释和名称将在汇编程序进程中被丢弃).
与hello.s对比的差别如下:
(1)分支转移:在汇编代码中,分支跳转是直接以.L0等助记符表示,但在反汇编代码中,分支转移不在依靠段名称,而是表示为主函数+段内偏移量。因段名称在汇编语言中为便于编写的助记符,所以在汇编成机器语言之后就不存在了,而是确定的地址。
(2)函数调用:汇编代码中函数调用时直接使用函数名称,而在反汇编的文件中call之后定位到call的下一条指令,即用具体的地址表示。在.rela.text节中为其添加重定位条目等待链接。
(3)访问全局变量:汇编代码中使用.LC0(%rip),反汇编代码中为0x0(%rip),因为访问时需要重定位,所以初始化为0并添加重定位条目等链接之后再确定。
4.5 本章小结
本章主要介绍汇编的概念与作用,如何得到汇编文件hello.o的操作命令。同时利用readelf对elf文件做了详细的分析,最后比较hello.o的反汇编文件与之前得到的hello.s文件,比较了机器语言和反汇编文件的差别。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接(linking)是将各种代码和数据片段收集并合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到内存并执行时;甚至执行于运行时(run time),也就是在由应用程序来执行。
作用;链接器在软件开发过程中扮演着一个关键的角色,负责处理不同函数预编译好的目标文件,因为它们使得分离编译(separate compilation)成为可能。
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.1生成hello可执行文件
执行结束后生成hello可执行文件。
5.3 可执行目标文件hello的格式
在linux命令行输入命令readelf -h hello即可看到ELF Header的具体信息,结果如下:
图5.2 hello的ELF Header内容
Type类型为EXEC表明hello是一个可执行目标文件,由之前的14个节变为27个节。
在linux命令行输入命令readelf -S hello即可看到Section Headers的具体信息,结果如下:
图5.3 hello的Section Headers内容
Section Headers:节头部表,记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。 因此根据 Section Headers 中的信息我们就可以用 HexEdit 定位各个节所占的区间(起始位置,大小)。其中 Address 是程序被载入到虚拟地址的起始地址。
在linux命令行输入命令readelf -s hello即可看到hello.elf中Symbol table的具体信息,结果如下:
图5.4 hello的Symbol table内容
Symbol table:符号表,存放程序中定义和引用的函数和全局变量的信息。
查看重定位节,具体信息如下:
图5.5 hello的重定位节内容
5.4 hello的虚拟地址空间
使用edb加载hello,详细信息如下:
图5.5利用edb反汇编结果
观察Data Dump窗口,发现虚拟地址从0x400000开始到0x401ff0结束。根据的节头部表,可以通过edb找到各个节的信息
图5.6 Data Dump信息
5.5 链接的重定位过程分析
在linux命令行输入objdump -d -r hello 得到hello的反汇编文件,与hello.o的反汇编文件相比节的数目和文件内容有一些差异。
图5.7利用odjdump得到反汇编文件
- 节的数目不同
Hello生成的.asm文件中节的个数多余hello.o生成的.asm文件中节的个数,多了比如.init和.plt等等.
图5.8hello生成的asm文件与hello.o生成asm文件对比
- 文件内容差异
hello反汇编的代码有确定的虚拟地址,也就是说已经完成了重定位,而hello.o反汇编代码中代码的虚拟地址均为0,未完成可重定位的过程。
图5.9 Hello反汇编结果
图5.10 Hello.o反汇编结果
重定位就是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程。它是实现多道程序在内存中同时运行的基础。重定位有两种,分别是动态重定位与静态重定位。
hello重定位的过程:
(1)重定位节和符号定义链接器将所有类型相同的节合并在一起后,这个节就作为可执行目标文件的节。然后链接器把运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号,当这一步完成时,程序中每条指令和全局变量都有唯一运行时的地址。
(2)重定位节中的符号引用这一步中,连接器修改代码节和数据节中对每个符号的引用,使他们指向正确的运行时地址。执行这一步,链接器依赖于可重定位目标模块中称为的重定位条目的数据结构。
(3)重定位条目当编译器遇到对最终位置未知的目标引用时,它就会生成一个重定位条目。利用在.rela.data和.rela.txt节中保存的重定位信息,来修改对各个符号的引用,即修改他们的地址。
5.6 hello的执行流程
使用edb执行hello,下面展示从加载hello到_start,到call main,以及程序终止的所有过程并列出其调用与跳转的各个子程序名或程序地址。
表2过程及对应地址:
ld-2.31.so!_dl_start | 0x00007f807a1180b3 |
ld-2.31.so!_dl_init | 0x00007f5a70281c10 |
Hello!_start | 0x000000000040111e |
libc-2.31.so!__libc_start_main | 0x00007fd1c3e7a550 |
Hello!main | 0x00000000004010d0 |
Hello!printf@plt | 0x00000000004010a0 |
hello!atoi@plt | 0x00000000004010c0 |
Hello!sleep@plt | 0x00000000004010e0 |
hello!getchar@plt | 0x00000000004010b0 |
libc-2.31.so!exit | 0x00007d1c3e6f460 |
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。
在进行动态链接前,首先要进行静态链接,生成部分链接的可执行目标文件hello。动态链接采用了延迟加载的策略,即在调用函数时才进行符号的映射。使用偏移量表got+过程链接表plt实现函数的动态链接。got中存放函数目标地址,为每个全局函数创建一个副本函数,并将对函数的调用转换成对副本函数调用。.got全局偏移表 .plt程序链接表
查看dl_init函数调用前后.got.plt节的变化。
根据.elf文件可知,got起始表位置为0x403ff0,
图5.11 got起始位置
调用前:.got表位置在调用dl_init之前0x403ff0后的16个字节均为0.
图5.12 .got初始状态
调用后:.got位置发生变化,存入地址。
图5.13 .got结束状态
5.8 本章小结
本章主要介绍了链接的概念与作用,并介绍了hello.o通过链接生成可执行文件的过程,同时详细介绍了hello的ELF格式和各个节的含义并且与上一章中hello.o生成的ELF文件进行对比,并且分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是操作系统对一个正在运行的程序的一种抽象,是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
作用:它提供一个假象,好像我们的程序独占地使用内存系统,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。多进程可以完成多任务,每个进程就好比一家独立的公司,每个公司都各自在运营,每个进程也各自在运行,执行各自的任务。
图6.1进程
6.2 简述壳Shell-bash的作用与处理流程
作用:交互性命令解释器,把用户输入的命令翻译给操作系统。提供了一个界面,用户可以通过这界面访问操作系统内核。
处理流程:
(1)从终端读取用户输入的命令行,并将命令行进行切分得到参数;
(2)分析命令行字符串,若是内置命令,则立即执行;
(3)如果不是内置命令,则调用fork()创建新子进程,再调用execve()执行指定程序。
6.3 Hello的fork进程创建过程
哈工大2022计算机系统大作业---程序人生
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机类
学 号 120L021923
班 级 2003006
学 生 甄自镜
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
本文对hello.c在linux下的运行的整个生命周期进行分析,从编写完成开始到程序回收结束,介绍了hello从预处理、编译、汇编、链接直到被回收的整个过程及原理,同时介绍了与运行相关的进程管理、存储管理、I/O管理的内容,描绘了hello波澜壮阔的一生
关键词:预处理;编译;汇编;链接;进程管理;存储管理;linux;I/O
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
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 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
1.建立hello.c文件
2. 预处理器将其进行预处理生成hello.i文件。
3. 编译器编译得到hello.s文件
4. 汇编器汇编得到hello.o
5. 链接器与库函数链接生成一个可执行程序
6.通过shell输入./shell,shell通过fork函数创建了一个新的进程
7. 调用execve映射虚拟内存,通过mmap为hello程序开创了一片空间。
8. CPU从虚拟内存中的.text,.data节取代码和数据,调度器为进程规划时间片,有异常时触发异常处理子程序。
9. .程序运行结束,父进程回收hello进程和它创建的子进程,内核删除相关数据结构。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
1.2.1 硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 软件环境
Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上;
1.2.3 开发工具
Visual Studio 2010 64位以上;CodeBlocks 64位;vi/vim/gedit+gcc
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
文件名 | 功能 |
hello.c | 源程序 |
hello.i | 预处理后的文件 |
hello.s | 汇编文件 |
hello.o | 可重定位目标执行文件 |
hello | 可执行文件 |
hello.elf | hello.o的ELF格式 |
hello.txt | hello.o的反汇编语言 |
hello1.txt | hello的反汇编语言 |
hello1.elf | hello的ELF格式 |
1.4 本章小结
本章对hello的一生进行了简要的介绍和描述,介绍了P2P,O2O的整个过程,介绍了计算机硬件环境、软件环境、开发与调试工具,介绍了中间文件的名称即其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码之前的过程。
作用:预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。
2.2在Ubuntu下预处理的命令
图2.2.1
在linux 系统中用gcc -E -o hello.i hello.c进行预处理的命令
2.3 Hello的预处理结果解析
图2.3.1 Hello.i文件截图
分析:hello.c预处理后变为hello.i文件,文件内容如上图所示,文件扩展为三千多行,将原程序中的宏进行了宏展开,对函数的声明,变量定义等信息放在中间,程序的源代码被放到了最后。
2.4 本章小结
本章介绍了预处理的概念与作用,学习了用gcc对hello.c文件进行预处理,将其重定向到hello.i中,并且分析了hello.i文件中的内容
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
作用:
1.语法分析:编译程序的语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位。
2.中间代码:源程序的一种内部表示,或称中间语言。中间代码的作用是可使编译程序的结构在逻辑上更为简单明确,特别是可使目标代码的优化比较容易实现中间代码。
3.代码优化:指对程序进行多种等价变换,使得从变换后的程序出发,能生成更有效的目标代码。
4.目标代码:生成是编译的最后一个阶段。目标代码生成器把语法分析后或优化后的中间代码变换成目标代码。此处指汇编语言代码,须经过汇编程序汇编后,成为可执行的机器语言代码。
3.2 在Ubuntu下编译的命令
图3.2.1
在linux 系统中用gcc -S hello.i -o hello.s进行编译的命令
3.3 Hello的编译结果解析
3.3.1汇编第一部分:
图3.3.1
file:声明源文件
text:代码节
section:指示把代码划分成若干个段(Section)
rodata:只读代码段,后面是printf()与scanf()中存储的字符串。
align:数据或者指令的地址对其方式
string:声明一个字符串LC0,LC1
global:声明全局变量(main)
type:声明一个符号是数据类型还是函数类型
3.3.2数据
1.字符串:即只读代码段中的两个字符串LC0,LC1。二者作为printf(),scanf()的参数,如图:
图3.3.2
、
2.整数:
在程序中一共有两个int变量,分别为i与argc
i是一个局部变量,编译器进行编译的时候将局部变量i会放在堆栈中。如图所示,局部变量i放在栈上-4(%rbp)的位置。
Argc是传给main的参数,进入了堆栈
3.立即数,形式为$+常数
3.3.3赋值操作:
利用mov实现,类如:
PS:movb:一个字节
movw:“字”
movl:“双字”
movq:“四字”
3.3.4关系操作和控制转移
Jle:判断cmpl产生的条件码,小于等于7跳转L4
Je:判断cmpl产生的条件码,不等于4则跳转.L2、
3.3.5算术操作:
Add 即i++,图片对应解释为:对i自加,栈上存储变量i的值加1
3.3.6数组/指针/结构操作
主函数main的参数中有指针型数组char *argv[],对此数组的操作通常由mov指令实现。取到argv数组的首地址,然后对首地址加相应字节得到对应的地址,argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别表示两个字符串。char* 数据类型占8个字节
3.3.7函数操作:
Main函数;传入参数argc和argv[],分别用寄存器%rdi和%rsi存储。被系统启动函数调用。设置%eax为0并且返回,对应return 0 。
Printf:第一处调用由于只是输入一串字符串,所以被优化成puts函数,之后通过call来调用puts,而第二处调用printf,有三个参数,因此我们需要取出参数
Exit:传入参数1后调用函数退出。
Atoi:将字符串类型转换为整型。
Sleep:atoi被调用完后,会将其返回值作为sleep函数的参数,调用sleep。
Getchar:直接调用
3.4 本章小结
介绍了编译的概念以及过程。通过hello函数分析了c语言如何转换成为汇编代码。介绍了汇编代码如何实现变量、常量、传递参数以及分支和循环。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器将汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中。
作用:将汇编代码转换为机器指令,使其在链接后能被机器识别并执行。
4.2 在Ubuntu下汇编的命令
图4.2.1
在linux 系统中用gcc -c -o hello.o hello.s进行汇编的命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
典型ELF可重定位目标文件:
图4.3.1
图4.3.2
在linux 系统中用readelf -a hello.o > hello.elf进行生成elf格式文件的命令
ELF文件格式分析:
1.ELF头:ELF头(ELF header)以一个16B的序列Magic开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含了帮助链接器语法分析和解释目标文件的信息,其中包括ELF头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如x86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是有节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)。
Hello.elf文件中ELF头:
图4.3.3
2.节头目表:(节头):记录各节名称、类型、地址、偏移量、大小、全体大小、旗标、链接、信息、对齐。 因为是可重定位目标文件,所以每个节都从0开始,用于重定位。
图4.3.4
3.重定位节:各个段引用的外部符号等在链接时需要通过重定位对这些位置的地址进行修改。链接器会通过重定位节的重定位条目计算出正确的地址。
需要重定位有:.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等符号。
图4.3.5
4.符号表:存放在程序中定义和引用的函数和全局变量的信息。编译器中的符号表不同,.symtab符号表不包含局部变量的条目。
图4.3.6
4.4 Hello.o的结果解析
图4.4.1
利用objdump -d -r hello.o >hello.txt 进行反汇编。
机器语言的构成:机器语言是机器能直接识别的程序语言或指令代码,无需经过翻译,每一操作码在计算机内部都有相应的电路来完成它,或指不经翻译即可为机器直接理解和接受的程序语言或指令代码。机器语言使用绝对地址和绝对操作码。不同的计算机都有各自的机器语言,即指令系统。从使用的角度看,机器语言是最低级的语言。
与汇编语言的映射关系:机器语言是用于控制计算机中处理器的实际位,通常被视为十六进制数字序列(通常为字节).处理器从程序存储器中读取这些位,这些位表示下一步操作的"指令".因此,机器语言提供了一种将指令输入计算机的方式
与hello.s的不同:
1.控制转移:
Hello.s
Hello.txt
Hello.s中使用L2这样的段名称进行跳转,而在反汇编中,跳转到函数+偏移量这样的形式。因为段名称在汇编语言中为便于编写的助记符,所以在汇编成机器语言之后就不存在了,而是确定的地址。
2.函数调用
汇编代码中函数调用时直接使用函数名称,而在反汇编的文件中call之后定位到call的下一条指令,即用具体的地址表示。例如:
3.数的表示
hello.s中的操作数表现为十进制,而hello.o反汇编代码中的操作数为十六进制。
4.5 本章小结
本章介绍了汇编。经过汇编器,将汇编语言转化为机器语言,hello.s文件转化为hello.o可重定位目标文件。分析了ELF的文件格式,了解了ELF头等相关概念,分析了汇编语言与机器语言的不同。
(第4章1分)
第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.1
5.3 可执行目标文件hello的格式
利用readelf -a hello > hello1.elf看ELF Header的具体信息
图5.3.1
1.ELF头
图5.3.2
2.节头
图5.3.3
3.重定位节
图5.3.4
4.符号表
图5.3.5
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
从data dump中发现虚拟地址从0x401000开始到0x401ff0结束,根据5.3节头部表,可以找到各个节的信息
图5.4.1
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
通过objdump -d -r hello >hello1.txt得到hello的反汇编文件hello1.txt
二者的不同:
1.hello1.txt中多了许多节
比如下图中的.int .plt等
图5.5.1
2.文件内容有所不同
Hello已经完成了重定位,因此在调用函数时使用的地址已经是函数的虚拟地址,同时在跳转时也是使用虚拟地址。
图5.5.2
5.6 hello的执行流程
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
过程:
0x4010f0<_start>
0x4011f1 <in __libc_csu_init>
0x401000<_init>
0x401125 <main>
0x4010a0 printf@pl
0x4010c0 atoi@plt
0x4010e0 sleep@plt
0x4010b0 getchar@plt
0x7ffff7e06a70 <exit>
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
延迟绑定是通过GOT和PLT实现的
由ELF文件可知,got起始表位置为0x403ff0
图5.7.1
调用dl_init前0x403ff0为空
图5.7.2
调用后:
图5.7.3
我们可以发现,0x403ff0出现了变化,存入地址。
5.8 本章小结
本章研究了链接的过程。通过edb查看hello的虚拟地址空间,对比hello与hello.o的反汇编代码,深入研究了链接的过程中重定位的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:一个执行程序中的实例,系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
作用:进程提供给应用程序的关键抽象:一个独立的逻辑控制流,如同程序独占处理器;一个私有的地址空间,如同程序独占内存系统。它提供了一个假象,好像我们的程序独占地使用内存系统,处理器好像是无间断的执行我们程序中的指令,我们程序中的代码和数据好像是系统内存中唯一的对象。多进程可以完成多任务,每个进程就好比一家独立的公司,每个公司都各自在运营,每个进程也各自在运行,执行各自的任务。
6.2 简述壳Shell-bash的作用与处理流程
作用:shell是一个交互型应用程序,把用户输入的命令翻译给操作系统。提供了一个界面,用户可以通过这界面访问操作系统内核。
处理流程:shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序(这里的应用程序可以是Linux本身的实用程序,如ls和rm,也可以是购买的商业程序,如xv,或者是自由软件,如emacs)。然后shell在搜索路径里寻找这些应用程序(搜索路径就是一个能找到可执行程序的目录列表)。如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。
6.3 Hello的fork进程创建过程
在我们输入./hello 120L021923 甄自镜 1后,shell将会对我们的命令进行解析,由于我们输入的不是一个内置命令,shell会调用fork(),创建一个子进程,子进程得到与父进程用户级虚拟地址空间相同的但是独立的一份副本,然后开始执行程序。
6.4 Hello的execve过程
子进程创建完毕后,会去调用execve()来加载可执行文件到当前进程。然后删除已存在的用户区域。删除之前进程在用户部分中已存在的结构。创建新的区域结构:通过虚拟内存机制将可执行文件hello中的各个段映射到对应的代码段、数据段等地址空间。然后通过跳转到hello程序的第一条指令或入口点来运行该程序,由此将控制传递给新程序的主函数。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
进程上下文信息:当进程调度一个新的进程运行后,会使用上下文切换来将控制转移到新的进程。上下文切换会:1.保存当前进程的上下文。2.恢复某个先前被抢占进程的被保存的上下文。3.将控制传递给新进程。系统调用、中断可能引起上下文切换。
进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。
进程调度的过程:在对进程进行调度的过程,操作系统主要做两件事:加载保存的寄存器,切换虚拟地址空间。
用户态与核心态转换:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中, 用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。
具体过程如下:输入命令./hello 120L021923 甄自镜 ,然后调用sleep()函数进入内核模式,进行信号处理,然后返回用户模式,运行过程中,cpu不断切换上下文,使运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。
图6.5.1
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
1.异常
异常和信号异常可以分为四类:中断、陷阱、故障、终止
中断:处理器外部I/O设备引起,异步异常,例如:时钟中断,键盘上敲击Ctrl-C.
陷阱:有意的异常,执行指令产生的结果,发生时间可预知,同步异常,例如:系统调用。
故障:不是有意的,但可能被修复,同步异常,例如:缺页故障,保护故障。
终止:非故意,不可恢复非致命错误造成,例如:非法指令,奇偶校验错误。
2.各种键盘操作导致的异常:
正常:
图6.6.1
回车
图6.6.2
Ctrl Z 默认结果是挂起前台的作业,hello进程并没有回收,而是运行在后台下
图6.6.3
Ps
图6.6.4
Jobs
图6.6.5
Pstree
图6.6.6
Fg
图6.6.7
Kill 挂起的进程被终止,在ps中无法查到到其PID。
图6.6.8
Ctrl C
图6.6.9
PS:运行了两个hello,因此显示有两个
6.7本章小结
本章介绍了进程的概念和作用,以及壳Shell-bash的作用与处理流程,调用 fork 创建新进程,调用 execve函数执行< 以上是关于哈工大计算机系统大作业——程序人生的主要内容,如果未能解决你的问题,请参考以下文章