计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 电子信息科学与技术
学 号 1182100521
班 级 1821203
学 生 唐小龙
指 导 教 师 史先俊
计算机科学与技术学院
2020年3月
摘 要
本文主要介绍了Linux系统下一个普通C语言源程序hello.c从出生到消失的一生。文中按顺序详细介绍了预处理、编译、汇编、链接等过程,同时在介绍hello.c一生的同时,介绍了Linux系统内存管理、IO设备管理等方面内容。
关键词:预处理;编译;汇编;IO设备 ;虚拟地址
(摘要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、P2P: From Program to Process
计算机不能直接执行hello.c。首先人工的键入hello.c源文件,经过编译器预处理得到.i文件,再编译获得.s汇编语言程序,后经过汇编得到可重定位目标程序.o,它是一个二进制文件,该文件再通过链接得到可执行目标程序hello。最后操作系统加载进程,至此P2P结束。。
2、020:From Zero to Zero
shell为其execve,mmap将其的代码段,数据段等加入到虚拟内存中,然后进行内存访问,进入main函数执代码。程序完成后,父进程收回hello,删除相关数据结构,至此202结束。
1、硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk
2、软件环境:Windows10 64位;VirtualBox/Vmware 11;Ubuntu 16.04 LTS 64位
3、开发与调试工具:Visual Studio 2019 64位;CodeBlocks;vi/vim/gpedit+gcc
hello.c:源代码;
hello.i:预处理后的文本文件;
hello.s:编译之后的汇编文件;
hello.o:汇编之后的可重定位目标执行文件;
hello:链接之后的可执行文件;
hello.elf:hello的ELF格式;
本章主要简要介绍了程序执行过程中P2P和020两个过程,同时指出了本次实验所处的硬件环境、软件环境及一些开发调试工具。最后列出了本实验所出现的所有文件,方便后续实验进行。
(第1章0.5分)
C语言与其他高级语言的一个重要区别是可以使用预处理命令和具有预处理的功能。ANSI C标准规定可以在C源程序中加入一些预处理命令,以改进程序设计环境,提高编程效率。这些预处理命令是由ANSI C统一规定的,但是他不是C语言本身的组成部分,不能直接对它进行编译,必须在对程序进行通常的编译之前,先对程序中这些特殊的命令进行“预处理”。经过预处理后程序可有编译程序对预处理后的源程序进行通常的编译处理,得到可供执行的目标代码。C提供的预处理功能主要有以下三种:
这些功能分别用宏定义命令、文件包含命令、条件编译命令来实现,他们以符号“#”开头。
预处理有许多作用,可以完成很多功能。例如:例如我们在程序里面写#include
可以看到预处理就是将hello.c中include里包含的头文件内容进行了扩充替换,同时还进行了宏替换。也因此原本程序只有数十行,打开hello.i后可以发现现在共有3042行,其中主函数main出现在3029行。
主函数main部分。
本章主要介绍了预处理的概念及作用,介绍了一些常用的预处理命令,同时演示了Ubuntu下的预处理命令,观察了预处理得到的hello.i文件。
(第2章0.5分)
编译指的是编译器预处理文件(.i)到汇编程序(.s)的过程。
编译的作用是将高级语言源程序翻译成汇编语言,方便生成可以立即执行的机器级指令。
3.3.1 Hello.s 中有以下数据类型:
3.3.1.1 字符串
源代码中,整形 i作为局部变量出现main中,汇编语言”movl $o,-4(%rbp)”表明,局部变量i被储存在一个内存空间中(%rbp)。
3.3.2 赋值
将i的值赋值为0,并存储与于%rbp中。
3.3.3算术操作
源代码中涉及的算术操作只有for 循环中的i++ 语句,通过以上汇编语言实现,其中%rbp中储存的是局部变量 i。
3.3.4关系操作
源代码中有两处关系操作分别是argc!=4以及i<8
3.3.5数组/结构/指针操作
源代码中有一个指针数组* argv[ ]
for(i=0;i<8;i++){
printf("Hello %s %s\n",argv[1],argv[2]);
sleep(atoi(argv[3]));
}
其中argv[1]作为printf函数的一个参数,观察汇编语言可以得到其位于%rax+16储存的地址中,同理argv[2]在%rax+8储存的地址中,argv[3]在%rax+24储存的地址中 。
3.3.6控制转移
由源代码可知hello.c中存在的控制转移指令主要有if语句及for循环中存在的跳转。
cmpl $7, -4(%rbp) 语句是跳转语句,i大于7时跳出循环,否则进入.L4部分。
cmpl $4, -20(%rbp) 语句,argc!=4 进入语句,否则跳转。。
3.3.7 函数操作
Hello.c 中有多个函数,下面一一介绍。
3.3.7.1 int main(int argc, char *argv[])
函数参数:整型argc,及指针数组*argv[]
函数作用:程序执行的起点,本程序中,exi(1)或者return 0 结束本函数。
3.3.7.2 printf()
函数参数:有两处,传入要输出的值或者地址。
函数作用:在屏幕上打印所需信息。
3.3.7.2 exit()
函数参数:exit(0)表示正常退出;exit(1)表示异常退出。
函数作用:用于终结程序。
3.3.7.4 getchar()
函数参数:无。
函数作用:本程序中用于读取一个字符。
3.3.7.5 sleep()
函数参数:atoi(argv[3])
函数作用:延时,程序暂停若干时间。
3.3.7.6 atoi()
函数参数:argv[3]
函数作用:将字符参数转化为整型。
本章主要介绍了编译的概念及作用,同时演示了使用编译命令对Hello.i文件的处理。另外详细的对本程序从数据、复制、函数操纵等方面进行了剖析。
(第3章2分)
汇编指的是把汇编语言书写的程序翻译成与之等价的机器语言程序的过程。汇编程序输入的是用汇编语言书写的源程序,输出的是用机器语言表示的目标程序。
汇编的作用是将.s 文件翻译成机器语言指令,生成可重定位目标程序,保存在二进制目标文件.o 中。
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
如图所示:
其中重定位节如下:
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
1)操作数:hello.s 中为十进制 ,hello.o的反汇编中操作数是十六进制。
2)分支转移:hello.s 中跳转指令后是 .L1 .L2等段名称,而hello.o 的反汇编中是相对于跳转指令的相对偏移地址。
3)函数调用:hello.s 函数调用call指令后接具体的函数名称,同分支转移一样hello.o 的反汇编中call后接相对偏移地址。
本章主要讲了汇编的概念及其作用,演示了Ubuntu下汇编命令,观察了重定向目标文件,比较了hello.s和hello.o 的反汇编的差别。
(第4章1分)
链接指的是将各种代码和数据片段收集并组合成为一个单一文件的过程
,这个文件可被加载(复制)到内存并执行。
链接的作用是将单独编译的文件通过链接和并得到hello 文件,生成一个可执行文件加载到内存中并被执行。
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
使用readelf -a hello>hello.elf 指令获得hello 的ELF格式文件如下。
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
分析对比hello 与hello.o 可以得到hello相比较hello.o 增加了 _init .plt 等部分。在hello.o 中跳转指令以及函数调用皆使用的相对偏移地址,hello.o中使用的虚拟内存地址。
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
程序名 |
程序地址 |
ld-2.27.so!_dl |
0x7f5d6118fea0 |
hello!_start |
0x400500 |
ld-2.27.so!_dl_init |
0x7f5d6119e630 |
hello!exit@plt |
0x4004e0 |
hello!puts@plt |
0x4004b0 |
|
|
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
dl_init 前后global_offset表增加了相对偏移量。
本章主要介绍了链接的概念及作用,同时分析查看了hello 的ELF文件,然后使用了edb工具深入了解了hello 的虚拟地址空间,更加详细了解了连接这一过程,特别是重定位过程。
(第5章1分)
进程是允许某个并发执行的程序在某个数据集合上的运行过程。进程是由正文段、用户数据段及进程控制块共同组成的执行环境。正文段存放被执行的机器指令,用户数据段存放进程在执行时直接进行操作的用户数据。进程控制块存放程序的运行环境,操作系统通过这些数据描述和管理进程。
进程的作用是可以执行一个单独的任务。
作用:为使用者提供操作界面”的软件(命令解析器)。它类似DOS下的command.com和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。
运行流程:
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
1. 陷入内核
2. 加载新的可执行文件并进行可执行性检查
3. 将新的可执行文件映射到当前运行进程的进程空间中,并覆盖原来的进程数据
4. 将EIP的值设置为新的可执行程序的入口地址。如果可执行程序是静态链接的程序,或不需要其他的动态链接库,则新的入口地址就是新的可执行文件的main函数地址;如果可执行程序还需要其他的动态链接库,则入口地址是加载器ld的入口地址
5. 返回用户态,程序从新的EIP出开始继续往下执行。至此,老进程的上下文已经被新的进程完全替代了,但是进程的PID还是原来的。从这个角度来看,新的运行进程中已经找不到原来的对execve调用的代码了,所以execve函数的一个特别之处是他从来不会成功返回,而总是实现了一次完全的变身。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
进程调度的过程是这样的,起初hello 运行在用户模式,进程调用sleep()后进入内核模式,计时器开始计时,内核通过上下文切换将当前进程的控制权交给其他进程。当倒计时结束时内核将进程还给hello进程,hello继续自己的进程。
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
1、正常情况下输出八次,输入回车,结束进程。
2、运行过程中随意按键,没有什么影响,最后输入回车结束进程。
3、运行过程中键入ctrl+c 提前结束进程
4、运行过程中键入ctrl+z,然后使用ps命令查看后台进程,发现hello处于后台,使用kill命令可以杀死(结束)指定进程。
5、运行过程中键入ctrl+z,然后使用ps命令查看后台进程,发现hello处于后台,使用fg命令将hello命令调出后台继续运行,发现总共也有八次输出。
本章主要了解了进程的概念及作用,同时介绍了shell-bash的作用及处理流程,fork进程的创建过程,以及execve过程,最后测试了异常与信号处理。
(第6章1分)
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:在机器语言中指向一个操作数或者指令,由段和偏移量组成,就是hello.o 中的相对偏移地址。
线性地址:逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。就是hello中虚拟内存地址。
虚拟地址:CPU生成的一个虚拟地址。
物理地址:指出目前CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,最后两位涉及权限检查。索引号就是“段描述符(segment descriptor)”。段描述符描述了一个段。这样,很多个段描述符,就组了一个数组,叫“段描述符表”。然后可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。每一个段描述符由8个字节组成,这里我们只关心Base字段,它描述了一个段的开始位置的线性地址。
Intel设计的本意是,一些全局的段描述符,就放在“全局段描述符表(GDT)”中。一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT。
GDT在内存中的地址和大小存放在CPU的gdtr控制寄存器中,而LDT则在ldtr寄存器中。举个例子:
1、首先,给定一个完整的逻辑地址[段选择符:段内偏移地址]
2、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
3、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
把Base + offset,就是要转换的线性地址了。
将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存储存器的统一管理。
CPU发出一个虚拟地址在TLB里面搜索,如果命中就将PTE发给L1Cache,否则先在页表中更新PTE在发送。
当fork被shell调用时候,内核为hello进程创建各种数据结构,并给它匹配唯一的PID,为了给hello进程创建虚拟内存,创建mm—struct,区域结构和页表原样副本,将两个进程中的每个页面都有标记为只读页面,并将每个区域结构都标记为私有的写时复制。当fork在进程中返回时现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当需要进行写操作的时候,写时复制机制会创建新的页面,为每个进程保持了私有地址空间的抽象概念。
当调用函数execve函数加载hello程序时经历以下过程:首先删除已存在的用户区域,再将用户区域映射私有区域为Hello的代码、数据、bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时复制的。再映射共享区域。比如Hello程序与标准C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户虚拟地址空间中的共享区域内。最后设置程序计数器(PC)。exceve做的最后一件事就是设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
1.基本概念:
(1)在程序运行时程序员使用动态内存分配器获得虚拟内存。(数据结构的大小只有运行时才知道)。
(2)动态内存分配器维护着进程的一个虚拟内存区域,称为堆。
(3)分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的。
(4)分配器的类型
①显式分配器:要求应用显式地释放任何已分配的块。例如,C语言中的malloc和free。
②隐式分配器:应用检测到已分配块不再被程序所使用,就释放这个块
2.记录空闲块的方法:
(1)隐式空闲链表:通过头部中的长度字段一隐含地链接所有块
(2)显式空闲链表:在空闲块中使用指针
(3)分离的空闲列表:按照大小分类,构成不同大小的空闲链表
(4)块按大小排序:使用平衡树(如红黑树),在每个空闲块中保存指针,并用长度作为key值。
本章主要讲述了hello的内存管理,介绍了几个地址的概念,介绍了动态存储分配管理。
(第7章 2分)
设备的模型化:文件
设备管理:unix io接口
在Linux系统中,所有IO设备均被模型化为文件,所有的输入输出均被当作文件的读写操作。一个linux文件就是一个m个字节的序列,所有的I/ O 设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux 内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
1.open():
open函数将file那么转换为一个文件描述符并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。
2.close():
当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
3.read():
read函数从描述符为fd 的当前文件位置复制最多n个字节到内存位置buf。返回值一1表示一个错误,而返回值0表示EOF。否则,返回值表示的是实际传送的字节数量。
4.write():
write函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
(以下格式自行编排,编辑时删除)
本章主要介绍了Linux系统下IO设备的管理,介绍了一些Unix IO接口及其函数,详细分析了printf和getchar。
(第8章1分)
1、hello.c经过预处理得到hello.i。
2、hello.i经过编译生成hello.s。
3、hello.s经过汇编生成可重定位目标文件hello.o。
4、hello.o链接生成可执行目标文件hello。
5、shell调用fork函数为hello程序创建子进程。
6、shell调用execve函数,为其创建虚拟内存映像,进入程序入口后开始载入物理内存,进入main函数。
7、MMU通过页表将虚拟地址映射到对应的物理地址完成访存。
8、printf 会调用 malloc ,内核通过动态内存分配器为其分配内存。
9、hello程序可接受一些信号并进行信号处理。
10、 shell父进程回收hello,内核删除hello进程的所有痕迹。
(结论0分,缺失 -1分,根据内容酌情加分)
hello.c:源代码;
hello.i:预处理后的文本文件;
hello.s:编译之后的汇编文件;
hello.o:汇编之后的可重定位目标执行文件;
hello:链接之后的可执行文件;
hello.elf:hello的ELF格式;
(附件0分,缺失 -1分)
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
(参考文献0分,缺失 -1分)