计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 120L22127
班 级 2003007
学 生 吕伟光
指 导 教 师 吴锐
计算机科学与技术学院
2021年5月
本文通过对一个hello程序进行深刻处理,并全程分析。来实现一个程序在系统中运行的全过程。揭示程序的底层原理,一步步解析成为程序代码的过程,分析程序的一生。
关键词:hello;预处理;编译;汇编;链接;进程;内存;I/O管理;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:程序员编写hello的代码,经过预处理得到hello.i,编译得到hello.s,汇编得到hello.o,链接得到可执行文件hello。shell启动程序,fork创建子进程。
020:shell为hello的execve映射虚拟内存,进入程序的入口,载入物理内存,执行main函数,通过I/O输出,shell父进程回收hello进程,去除痕迹。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:X64 CPU;2.60GHz;2G RAM;256GHD Disk
软件环境:windos10 64位
开发工具:Visual Studio,VirtualBox 11;Ubuntu 16.04
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.i: hello.c经过预处理后的文件。
hello.s: hello.i经过编译得到的文件。
hello.o: hello.s经过汇编得到的文件。
hello: hello.o经过链接后得到的可执行文件。
hello.elf: hello.o的ELF格式。
hello.txt: hello.o的反汇编。
newhello.elf: hello的ELF格式。
newhello.txt: hello的反汇编。
1.4 本章小结
本章简要介绍了hello的P2P,020过程,并列出了环境及开发工具,还列出了操作过程中产生的中间结果。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:在编译之前进行的处理,以字符#开头的命令,修改原始的C程序。
作用:将源文件中以"include"格式包含的文件复制到编译的源文件中。用实际值替换用"#define"定义的字符串。根据"#if"后面的条件决定需要编译的代码。
2.2在Ubuntu下预处理的命令
应截图,展示预处理过程!
命令:gcc -E hello.c -o hello.i
2.3 Hello的预处理结果解析
查看hello.i文件,原函数在最后,其中三个#include已被替换为库在计算机中的位置。
Hello.i包含3060行代码。
2.4 本章小结
本章主要介绍了预处理的概念和应用功能,同时将hello.c文件进行预处理生成hello.i文件,并对生成的hello.i文件进行分析。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
概念:编译器(ccl)将文本文件hello.i翻译成文本文件 hello.s,它包含一个汇编语言程序
作用: 把用高级程序设计语言书写的源程序,翻译成等价的计算机汇编或机器语言书写的目标程序,编译程序以高级程序设计语言书写的源程序作为输入,而以汇编或机器语言表示的目标程序作为输出。
3.2 在Ubuntu下编译的命令
应截图,展示编译过程!
命令:gcc -S hello.i -o hello.s
3.3 Hello的编译结果解析
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
原代码
3.3.1常量
字符串被存储在特定的内存空间。
3.3.2变量
全局变量
局部变量int
3.3.3 if操作
利用cmpl进行比较,在利用je相等则跳转。
3.3.4 for操作
运用了数组argv[]
3.3.5 处理函数
Printf函数 sleep函数 exit函数都是利用call调用。
3.4 本章小结
本章主要介绍了编译的概念以及过程,对hello进行编译转换为汇编代码。分析编译得到的文件,介绍汇编代码如何实现变量、常量、判断以及分支和循环。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
概念:汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式。
作用:通过汇编过程把汇编代码转换成计算机能直接识别的机器代码,把指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中。
4.2 在Ubuntu下汇编的命令
应截图,展示汇编过程!
命令:gcc -c hello.s -o hello.o
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
命令:readelf -a hello.o > hello.elf
ELF文件头
包含了系统信息,编码方式,ELF头大小,节的大小和数量等等一系列信息。
节头部表
描述了.o文件中出现的各个节的类型、位置、所占空间大小等信息。
重定位节
real.txt保存连接时需要修改的信息。
符号表
它存放在程序中定义和引用的函数和全局变量的信息
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
命令:objdump -d -r hello.o > hello.txt
与hello.s对比差异如下:
访问全局变量:汇编码中用.LC0(%rip),反汇编码中为0x0(%rip)
代码左边多了机器码
4.5 本章小结
了解汇编的概念和作用,汇编得到.o文件,然后分析了可重定位目标elf格式,然后使用objdump进行反汇编,更加深入地理解机器语言与汇编语言的关系。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
注意:这儿的链接是指从 hello.o 到hello生成过程。
概念:调用链接器(ld)处理一些合并,得到一个可执行文件,可以被加载到内存中,有系统执行。
作用:将源程序与为了节省空间而未编入的常用函数文件进行合并,生成可以正常工作的可执行文件。令分离编译成为可能,节省了大量的工作空间。
5.2 在Ubuntu下链接的命令
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
命令:
ld -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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
命令:readelf -a hello > newhello.elf
ELF文件头:
节头部表:
程序头:
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
PHDR:保存程序头表
INTERP:动态链接器的路径
LOAD:可加载的程序段
DYNAMIN:保存了由动态链接器使用的信息
NOTE保存辅助信息
GNU_STACK:标志栈是否可执行
GNU_RELRO:指定重定位后需被设置成只读的内存区域
使用edb打开hello从Data Dump窗口观察hello加载到虚拟地址的状况,并查看各段信息。
5.5 链接的重定位过程分析
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
命令:objdump -d -f hello > newhello.txt
在hello.o中,地址为相对偏移地址;在hello中,地址为可以由CPU直接访问的虚拟内存地址;
hello的反汇编文件比hello.o的反汇编文件多了_init,.plt,puts@plt,printf@plt,getchar@plt,exit@plt,sleep@plt,_start,_dl_relocate_static_pie,__libc_csu_init,__libc_csu_fini,_fini等节和需要用到的库函数;
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
(1) 载入:_dl_start、_dl_init
(2)开始执行:_start、_libc_start_main
(3)执行main:_main、_printf、_exit、_sleep、
_getchar、_dl_runtime_resolve_xsave、_dl_fixup、_dl_lookup_symbol_x
- 退出:exit
各个子程序名:init,start,libc_csu_init,puts@plt,exit@plt,fini,sleep,exit,print。
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
一开始地址的字节都为0,调用_init函数之后GOT内容产生变化,指向正确的内存地址,下一次调用跳转时可以跳转到正确位置。
经历init前:
经历init后:
5.8 本章小结
本章了解了链接的作用,对hello.o进行链接得到hello文件,并对hello进行分析,与hello.o的反汇编代码进行比较,更好的掌握重定位过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是程序的一次执行,进程是程序及其数据在CPU下顺序执行时所发生的活动,进程是具有独立功能的程序在数据集上运行的过程,它是系统进行资源分配和调度的一个独立单位。
作用:它是操作系统动态执行的基本单元
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是一种交互型应用级程序,它可以代表用户运行其它程序。
处理流程:Shell不断读入用户输入的命令行,然后解析并处理,从终端读入输入的程序,将输入的字符串切分获得的所有参数,如果内置命令则立刻执行,否则调用相应的程序为其分配子进程并运行,Shell应该接受键盘的信号并进行处理
6.3 Hello的fork进程创建过程
在终端输入命令行后,shell会处理该命令,如果不是内置命令,则调用fork创建子进程。子进程得到一份与父进程用户级虚拟地址空间相同的副本。fork被调用一次,但返回两次,父进程中返回子进程的PID,子进程返回0。
6.4 Hello的execve过程
execve会在父进程中创建一个子进程,在子进程被创建后,子进程会调用新的程序,也就是说在出现错误前都不会返回。调用一次不返回。
6.5 Hello的进程执行
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
进程正常运行是依赖于其上下文的,上下文是由程序正确运行的状态组成的。内核调用hello的进程开始进行,输出Hello与之前输入的内容,然后执行sleep函数,这个函数是系统调用,它请求让调用进程休眠。操作系统会给每个进程分配时间片,它决定了当前进程能够执行它的控制流的连续一段时间。继续执行hello进程,重复这个过程。循环结束后,后面执行到getchar函数,这时读取数据需要很长的时间,所以将会发生上下文切换转而执行其他进程,当数据被读取到缓存区后,会发生中断,使内核发生上下文切换,重新执行hello进程。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。
回车:无事发生
Ctrl-C:进程结束
Ctrl-Z:挂起
ps:查看
pstree:查看(未全部截取)
jobs:
fg :恢复
kill:杀死进程
6.7本章小结
本章概括了进程的概念及作用,shell-bash,fork和execve的运行过程以及hello的执行过程中异常处理。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
逻辑地址:逻辑地址是指在计算机体系结构中是指应用程序角度看到的内存单元(memory cell)、存储单元(storage element)、网络主机(network host)的地址。
线性地址:线性地址(Linear Address)是 逻辑地址 到 物理地址 变换之间的中间层。
虚拟地址:虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。
物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
对于一个以“段:偏移地址”形式给出的逻辑地址,CPU将会通过其中的16位段选择子定位到GDT/LDT中的段描述符,通过这个段描述符得到段的基址,与段内偏移地址相加得到的64位整数就是线性地址。这就是CPU的段式管理机制每个段有寄存器对应,段寄存器有一个栈,一个代码,两个数据寄存器。
7.3 Hello的线性地址到物理地址的变换-页式管理
系统将虚拟页作为数据传输的单位。Linux下每个虚拟页大小为4kB。CPU对于一个线性地址会取它的高若干位,通过它们去存储在内存中的页表里查询对应的页表条目,得到这个线性地址对应的物理页起始地址,然后与线性地址的低位(页中的偏移)相加就是物理地址。
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
得到物理地址PA之后,根据cache大小组数的要求,将PA拆分成CT(标记)、CI(索引)、CO(偏移量),用CI位进行索引,如果匹配成功且valid值为1,则为命中,根据偏移量在L1cache中取数;如果未命中就去二级和三级cache中重复以上步骤,命中后返回结果。
7.6 hello进程fork时的内存映射
当shell使用fork创建子进程时,内核为新的子进程创建各种数据结构,并分配给子进程一个唯一的PID,为了给它创建虚拟内存空间,内核创建了当前进程的mm_struct、区域结构和页表的原样副本,将两个进程的页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同,当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间。
7.7 hello进程execve时的内存映射
删除已存在的用户区域。创建新的私有区域(.malloc,.data,.bss,.text)。创建新的共享区域(libc.so.data,libc.so.text)。设置程序计数器PC,指向代码的入口点。
7.8 缺页故障与缺页中断处理
先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)。接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。若都不是,就是正常缺页,则选择一个页面牺牲然后换入新的页面并更新到页表。
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
在hello程序中使用的printf,而printf会使用由动态内存分配器动态内存分配机制。动态内存分配器维护进程虚拟地址空间中的的堆区域,它将堆视作一组不同大小的块的集合来维护,每个块是一段连续的虚拟内存碎片,要么是已分配的,要么是空闲的。空闲块保持空闲直至被应用程序分配,以已分配块保持已分配状态直至被释放。
7.10本章小结
本章介绍了hello 的存储器地址空间、 intel 的段式管理、 hello 的页式管理,在指定环境下介绍了 VA 到 PA 的变换,以及进程fork和execve内存映射的内容,还有缺页问题和动态存储分配管理的问题。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:在Linux中,所有的IO设备(网络、磁盘、终端等)都被模型化为文件,所有的输入和输出都被当作对相应文件的读和写来执行。
设备管理:unix io接口,这种将设备映射为文件的方式,允许linux内核引出一个简单的低级的应用接口,称为Unix I/O,这使得所有的输入输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
打开文件:open()函数
打开一个已经存在的文件,若文件不存在则创建一个新的文件。
关闭文件:close()函数
通知内核关闭这个文件,内核会释放文件打开时创建的数据结构,将描述符恢复到描述符池中。
读取文件:read()函数
从当前文件位置复制字节到内存位置,如果返回值<0则说明出现错误。
写入文件:write()函数
从内存复制字节到当前文件位置,如果返回值<0则说明出现错误。
改变文件位置:lseek()函数
文件开始位置为文件偏移量,应用程序通过seek操作,可设置文件的当前位置为k。
8.3 printf的实现分析
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
syscall将字符串中的字节从寄存器中通过总线复制到显卡的显存中,显存中存储的是字符的ASCII码。字符显示驱动子程序将通过ASCII码在字模库中找到点阵信息将点阵信息存储到vram中。
显示芯片会按照一定的刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。然后打印。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
异步异常-键盘中断的处理:当用户按键时,键盘接口会得到一个代表该按键的键盘扫描码,同时产生一个中断请求,终端请求抢占当前进程运行键盘中断子程序,键盘中断子程序先从键盘接口取得该按键的扫描码,然后转化为ASCII码,保存在系统的键盘缓冲区之中。getchar函数落实到底层调用了系统的read函数,通过系统调用read读取在键盘缓冲区ASCII码,直到读到回车符,然后返回整个字串,getchar进行封装,大体逻辑时读取字符串的第一个字符然后返回。
8.5本章小结
通过以上分析,我们从底层的输入输出机理揭示了hello是如何在屏幕上打印出信息的,又是如何接受键盘输入的,它们背后的机制是软件通过底层IO端口或中断与外部硬件的交互。
(第8章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
经历的过程:
跟着hello的脚步,一点一带你前进探索。首先是预处理,将hello.c文 件边为hello.i,再将生成的hello.i通过编译生成汇编文件hello.s,将 hello.s汇编成为二进制可重定位目标文件hello.o,将hello.o与可重定位 目标文件和动态链接库链接成为可执行目标程序hello。shell中运行hello,
shell进程调用fork函数创建子进程,shell调用execve函数,execve调用 启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进 入 main函数,CPU为其分配时间片,MMU将程序中使用的虚拟内存地址通过 页表映射成物理地址,如果运行途中键入Ctrl-C、Ctrl-Z则调用shell的信 号处理函数分别对其操作,结束进程。
感悟:
一定要更加深刻的理解计算机系统工作的基本原理,这样才能成为一名更加优秀的程序编译者。
创新理念:
在别人的基础上设计自己的想法,且加入自己创新。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.i: hello.c经过预处理后的文件。
hello.s: hello.i经过编译得到的文件。
hello.o: hello.s经过汇编得到的文件。
hello: hello.o经过链接后得到的可执行文件。
hello.elf: hello.o的ELF格式。
hello.txt: hello.o的反汇编。
newhello.elf: hello的ELF格式。
newhello.txt: hello的反汇编。
(附件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分)