CSAPP大作业 程序人生-Hello's P2P

摘 要

本文通过对一个示例程序的介绍,解释了计算机系统各个模块之间的联系与功能,完成了搭建知识体系的目标。本文包括对示例程序的预处理、编译、汇编、链接、进程管理、存储管理、IO管理。

关键词:操作系统;进程;汇编;

第1章 概述

1.1 Hello简介

一、用高级编程语言(C语言)编写hello.c文件。
二、预处理器预处理hello.c得到hello.i文本文件。
三、编译器编译hello.i得到hello.s汇编语言文件。
四、汇编器处理hello.s得到hello.o可重定位目标二进制文件。
五、链接器处理hello.o,将其与函数库中需要的二进制文件进行链接,得到hello可执行目标二进制文件。
六、执行hello,操作系统调用fork函数创建子进程,并分配相应的内存资源。
七、使用execve函数加载进程。到这里完成了P2P。
八、程序执行结束后,操作系统进行进程回收,完成020。

1.2 环境与工具

硬件环境:x86-64CPU,2.20GHz;16.0GB RAM。
软件环境:Windows10 专业版,Ubuntu 18.04.1LTE
开发环境:gcc,gdb,objdump,edb,readelf

1.3 中间结果

中间结果文件名作用
hello.i预处理后的中间结果
hello.s汇编语言文件
hello.o可重定位EFL文件
hello.out可执行ELF文件

1.4 本章小结

hello.c程序从编写、预处理、编译、汇编、链接再到执行,体现了计算机系统系统各部分的具体功能,以及它们之间的的协同合作。

第2章 预处理

2.1 预处理的概念与作用

概念:是指在程序源代码被编译之前,由预处理器对程序源代码进行的处理。这个过程并不对程序的源代码进行解析,但它把源代码分割或处理成为特定的符号用来支持宏调用。
作用:通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。

2.2在Ubuntu下预处理的命令

gcc hello.c -E -o hello.i

hello.i:在这里插入图片描述

2.3 Hello的预处理结果解析

经过预处理之后,hello.c文件转化为hello.i文件。原文件中的宏进行了宏展开,头文件中的内容被包含进该文件中。打开该文件可以发现,文件长度变为3113行。文件的内容增加,且仍为可以阅读的C语言程序文本文件。

2.4 本章小结

本阶段将hello.c预处理为hello.i,为接下来的编译阶段做准备。

第3章 编译

3.1 编译的概念与作用

概念:利用编译程序从源语言编写的源程序产生目标程序的过程。编译程序也称为编译器,是指把用高级程序设计语言书写的源程序,翻译成等价的机器语言格式目标程序的翻译程序。
作用:将高级编程语言翻译为汇编语言。

3.2 在Ubuntu下编译的命令

gcc hello.i -S -o hello.s

hello.s:
在这里插入图片描述

3.3 Hello的编译结果解析

3.3.1 全局变量及全局函数


hello.c中只有全局函数int main(int argc,char *argv[])。经过编译后,main函数中使用的字符串常量存放在数据区。

3.3.2、main的参数

main的参数有int argc和char *argv[]两个,在汇编代码中被存于%rbp指向地址-20和-32处。
在这里插入图片描述
其中%edi存放argc,%rsi存放argv[]。

3.3.3、条件判断语句

在这里插入图片描述
在main函数中使用if进行了条件判断,cmpl语句判断条件是否满足(argc==4),如果满足则跳转到.L2,否则调用puts()输出指定字符串,然后调用exit退出程序。

3.3.4、循环语句及函数结尾部分

在这里插入图片描述
该阶段定义了一个局部变量i,存放在%rbp-4的位置,然后跳转到.L3进行判断,若i<7,则进入.L4执行循环体。在.L4中访问了argv[1] 与argv[2],因argv是指针数组,采用了二次寻址。将argv[2]放入%rdx,argv[1]放入%rax,将格式字符串放入%rdi,然后调用printf输出;然后将argv[3]放入%rax,调用atoi函数;最后将%rax的值放入%edi中并调用sleep函数;最后将i加1,结束循环部分。
退出循环后,调用getchar函数并给%eax赋0,结束程序。

3.4 本章小结

本阶段将高级编程代码转为汇编代码,汇编代码是机器仍然不能直接运行的代码,所以需要接下来的阶段进一步处理。

第4章 汇编

4.1 汇编的概念与作用

概念:汇编就是将汇编代码程序转变成等价的机器语言程序的过程,汇编通常由汇编程序执行。
作用:将汇编代码转变成机器可以识别的二进制格式。

4.2 在Ubuntu下汇编的命令

gcc hello.s –c –o hello.o

4.3 可重定位目标elf格式

readelf -h hello.o查看文件头信息:
在这里插入图片描述

如图可知该文件是可重定位文件,有13个节。
使用readelf -S hello.o查看节头表:
在这里插入图片描述

可知各节大小及其可进行的操作。
使用readelf -s hello.o查看符号表信息:
在这里插入图片描述

4.4 Hello.o的结果解析

使用objdump -d -r hello.o显示hello.o反汇编的结果:
在这里插入图片描述
机器语言由操作码和操作数构成。如:

38:   48 83 c0 10             add    $0x10,%rax

这句指令中48 83是操作码,表示指令add;c0 10是操作数,分别表示$0x10和%rax。
汇编语言中函数调用的形式为call <函数名>,机器语言中函数调用的形式为call <地址>,因为机器语言中每个函数的地址已经分配完毕,所以可以直接call一个地址。同理,je等条件跳转指令在机器语言中后面跟的也是地址。

4.5 本章小结

本阶段将hello.s汇编代码翻译为hello.o可重定向文件,虽然电脑可以识别该文件,但是文件中的符号的地址并不是真实地址,还需要进行链接和重定位才能运行。

第5章 链接

5.1 链接的概念与作用

概念:链接是将一组可重定位目标文件组合成一个可执行文件的过程。
作用:将可重定位文件中

5.2 在Ubuntu下链接的命令

ld -o hello -dynamic-linker /lib64ld-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.3 可执行目标文件hello的格式

readelf -h hello查看文件头信息:在这里插入图片描述
使用readelf -S hello查看节头表:在这里插入图片描述
在这里插入图片描述
使用readelf -s hello查看符号表信息:在这里插入图片描述

5.4 hello的虚拟地址空间

使用edb打开hello程序,hello的虚拟地址从0x400000开始,在下面可以查看内存,具体情况如下图所示:
在这里插入图片描述

5.5 链接的重定位过程分析

在这里插入图片描述
可以看出,hello中main函数的首地址和其后的指令地址是实际可运行地址(虚拟地址);而hello.o中main函数的首地址为0,其后的指令地址为偏移量。
链接的过程就是链接器根据符号表建立符号的引用和定义之间的关联。
链接器在重定位时有针对PC相对引用和绝对引用的一套算法。hello.o为每个需要重定位的项目定义了重定位方式,链接器在重定位时会自动选择相应的重定位算法。

5.6 hello的执行流程

名称地址
ld-2.27.so! dl_start0x7ffee5aca680
ld-2.27.so! dl_init0x7f9f48629630
hello!_start0x0x400500
ld-2.27.so!_libc_start_main0x7f9f48249ab0
libc-2.27.so! cxa_atexit0x7f4523fd6af7
libc-2.27.so! lll_look_wait_private0x7f4523ff8471
libc-2.27.so!_new_exitfn0x7f87ff534220
hello!_libc_csu_init0x7f87ff512b26
libc-2.27.so!_setjmp0x7f87ff512b4a
libc-2.27.so!_sigsetjmp0x7f87ff52fc12
libc-2.27.so!__sigjmp_save0x7f87ff52fbc3
hello_main0x400532
hello!puts@plt0x4004b0
hello!exit@plt0x4004e0
hello!printf@plt0x400587
hello!sleep@plt0x400594
hello!getchar@plt0x4005a3
dl_runtime_resolve0x7f169ad84750
libc-2.27.so!exit0x7fce8c889128

5.7 Hello的动态链接分析

5.7.1 概念

GOT表:
概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。
作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld。
PLT表:
过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目
当main()函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()函数,rtld得以调用就可以定位printf的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。
动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址.
在这里插入图片描述

5.7.2 分析

对于动态共享链接库中PIC函数,编译器没有办法预测函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,为避免运行时修改调用模块的代码段,链接器采用延迟绑定的策略。动态链接器使用过程链接表PLT+全局偏移量表GOT实现函数的动态链接,GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。
在dl_init调用之前,对于每一条PIC函数调用,调用的目标地址都实际指向PLT中的代码逻辑,GOT存放的是PLT中函数调用指令的下一条指令地址。
在这里插入图片描述

在dl_init调用之后, 0x601008和0x601010处的两个8B数据分别发生改变为0x7fd9 d3925170和0x7fd9 d3713680,其中GOT[1]指向重定位表(依次为.plt节需要重定位的函数的运行时地址)用来确定调用的函数地址, GOT[2]指向动态链接器ld-linux.so运行时地址。
在这里插入图片描述

在之后的函数调用时,首先跳转到PLT执行.plt中逻辑,第一次访问跳转时GOT地址为下一条指令,将函数序号压栈,然后跳转到PLT[0],在PLT[0]中将重定位表地址压栈,然后访问动态链接器,在动态链接器中使用函数序号和重定位表确定函数运行时地址,重写GOT,再将控制传递给目标函数。之后如果对同样函数调用,第一次访问跳转直接跳转到目标函数。

因为在PLT中使用的jmp,所以执行完目标函数之后的返回地址为最近call指令下一条指令地址,即在main中的调用完成地址。

5.8 本章小结

本章主要介绍了链接器如何将hello.o可重定向文件与动态库函数链接起来,其中用到了rodata中的重定位条目,最终分析了程序如何实现的动态库链接。

第6章 hello进程管理

6.1 进程的概念与作用

概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
作用:从理论角度看,是对正在运行的程序过程的抽象;从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。

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

作用:shell提供了用户与操作系统之间通讯的方式。这种通讯可以以交互方式(从键盘输入,并且可以立即得到响应),或者以shell script(非交互)方式执行。shell script是放在文件中的一串shell和操作系统命令,它们可以被重复使用。本质上,shell script是命令行命令简单的组合到一个文件里面。Shell基本上是一个命令解释器,类似于DOS下的command。它接收用户命令(如ls等)。
处理流程:

  1. 将命令行分成由 元字符(meta character) 分隔的 记号(token):
    元字符包括 SPACE, TAB, NEWLINE, ; , (, ), <, >, |, &
    记号 的类型包括 单词,关键字,I/O重定向符和分号。
  2. 检测每个命令的第一个记号,看是否为不带引号或反斜线的关键字。如果是一个 开放的关键字,如if和其他控制结构起始字符串,function,{或(,则命令实际上为一复合命令。shell在内部对复合命令进行处理,读取下一个命 令,并重复这一过程。如果关键字不是复合命令起始字符串,而是如then等一个控制结构中间出现的关键字,则给出语法错误信号。
  3. 依据别名列表检查每个命令的第一个关键字。如果找到相应匹配,则替换其别名定义,并退回第一步;否则进入第4步。
  4. 执行大括号扩展,例如a{b,c}变成ab ac
  5. 如果~位于单词开头,用$HOME替换~。使用usr的主目录替换~user。
  6. 对任何以符号$开头的表达式执行参数(变量)替换
  7. 对形如$(string)或者`string` 的表达式进行命令替换
    这里是嵌套的命令行处理。
  8. 计算形式为$((string))的算术表达式
  9. 把行的参数替换,命令替换和算术替换 的结果部分再次分成单词,这次它使用$IFS中的字符做分割符而不是步骤1的元字符集。
  10. 对出现*, ?, [ ]对执行路径名扩展,也称为通配符扩展
  11. 按命令优先级表(跳过别名),进行命令查寻。
    先作为一个特殊的内建命令,接着是作为函数,然后作为一般的内建命令,最后作为查找$PATH找到的第一个文件。
  12. 设置完I/O重定向和其他操作后执行该命令。

6.3 Hello的fork进程创建过程

shell调用fork向内核申请创建子进程,获得一段虚拟地址空间,将父进程的虚拟地址空间的一个副本放进该段空间,子进程同时也获得父进程所有打开文件描述符的副本。

6.4 Hello的execve过程

execve在shell创建的子进程的上下文中加载并运行hello,覆盖了原本子进程的地址空间,并继承所有在调用execve时已打开的文件描述符。

6.5 Hello的进程执行

hello在运行中在每个循环中会调用sleep(argv[3]),此时hello进程被挂起,系统执行其他进程,直到sleep结束,hello进程恢复工作。hello在除了调用printf等系统级函数时处于用户模式,当需要调用系统级函数时切换至内核模式。

6.6 hello的异常与信号处理

hello运行时可出现中断,陷阱,故障,终止所有四类异常,可能产生信号:SIGALRM、SIGSEGV、SIGCHLD、SIGCONT等。
不停乱按、回车:有输入,程序无反应,会继续正常进行。
在这里插入图片描述
Ctrl+Z:程序被停止,但未终止,可通过命令fg恢复、kill终止该进程。
在这里插入图片描述
在这里插入图片描述

Ctrl+C:终止该进程
在这里插入图片描述

6.7本章小结

本阶段通过在hello.out运行过程中执行各种操作,了解了与系统相关的若干概念、函数和功能。分析了在程序运行过程中,计算机硬件、软件和操作系统之间的配合和协作的方式。

第7章 hello的存储管理

7.1 hello的存储器地址空间

物理地址是用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。现代操作系统都提供了一种内存管理的抽像,即虚拟内存。进程使用虚拟内存中的地址,即虚拟地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。hello.s中使用的就是虚拟空间的虚拟地址。线性地址指虚拟地址到物理地址变换的中间层,是处理器可寻址的内存空间(称为线性地址空间)中的地址。程序代码会产生逻辑地址,或者说段中的偏移地址,加上相应段基址就成了一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址。而逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。它是Intel为了兼容,而将段式内存管理方式保留下来的产物。
逻辑(虚拟)地址经过分段(查询段表)转化为线性地址。线性地址经过分页(查询页表)转为物理地址。

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

一个逻辑地址由两部分组成,段标识符和段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。

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

CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有2的20个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。另一类“页”,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。

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

Core i7 MMU 使用四级的页表将虚拟地址翻译成物理地址。36位VPN 被划分成四个9 位VPN,分别用于一个页表的偏移量。详情见图。
在这里插入图片描述

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

首先CPU发出一个虚拟地址,在TLB里面寻找。如果命中,那么将PTE发送给L1Cache,否则先在页表中更新PTE。然后再进行L1根据PTE寻找物理地址,检测是否命中的工作。这样就能完成Cache和TLB的配合工作。详情见图。
在这里插入图片描述

7.6 hello进程fork时的内存映射

在这里插入图片描述

7.7 hello进程execve时的内存映射

在这里插入图片描述

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

DRAM 缓存不命中称为缺页,即虚拟内存中的字不在物理内存中。缺页导致页面出错,产生缺页异常。缺页异常处理程序选择一个牺牲页,然后将目标页加载到物理内存中。最后让导致缺页的指令重新启动,页面命中。

7.9动态存储分配管理

在程序运行时程序员使用动态内存分配器(如malloc)获得虚拟内存。动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块要么是已分配的,要么是空闲的。分配器的类型包括显式分配器和隐式分配器。前者要求应用显式地释放任何已分配的块,后者在检测到已分配块不再被程序所使用时,就释放这个块。

动态内存管理的策略包括首次适配、下一次适配和最佳适配。首次适配会从头开始搜索空闲链表,选择第一个合适的空闲块。搜索时间与总块数(包括已分配和空闲块)成线性关系。会在靠近链表起始处留下小空闲块的“碎片”。下一次适配和首次适配相似,只是从链表中上一次查询结束的地方开始。比首次适应更快,避免重复扫描那些无用块。最佳适配会查询链表,选择一个最好的空闲块,满足适配,且剩余最少空闲空间。它可以保证碎片最小,提高内存利用率。

7.10本章小结

本章通过hello的内存管理,复习了与内存管理相关的重要的概念和方法。加深了对动态内存分配的认识和了解。

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

首先是设备的模型化。在设备模型中,所有的设备都通过总线相连。每一个设备都是一个文件。设备模型展示了总线和它们所控制的设备之间的实际连接。在最底层,Linux 系统中的每个设备由一个 struct device 代表,而Linux统一设备模型就是在kobject kset ktype的基础之上逐层封装起来的。设备管理则是通过unix io接口实现的。

8.2 简述Unix IO接口及其函数

linux 提供如下 IO 接口函数:
read 和 write – 最简单的读写函数;
readn 和 writen – 原子性读写操作;
recvfrom 和 sendto – 增加了目标地址和地址结构长度的参数;
recv 和 send – 允许从进程到内核传递标志;
readv 和 writev – 允许指定往其中输入数据或从其中输出数据的缓冲区;
recvmsg 和 sendmsg – 结合了其他IO函数的所有特性,并具备接受和发送辅助数据的能力。

8.3 printf的实现分析

https://blog.csdn.net/zhengqijun_/article/details/72454714
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。

8.5本章小结

本章通过介绍hello中包含的函数所对应的unix I/O,大致了解了I/O接口及其工作方式,同时也了解了硬件设备的使用和管理的技术方法。

结论

一、用高级编程语言(C语言)编写hello.c文件。
二、预处理器预处理hello.c得到hello.i文本文件。
三、编译器编译hello.i得到hello.s汇编语言文件。
四、汇编器处理hello.s得到hello.o可重定位目标二进制文件。
五、链接器处理hello.o,将其与函数库中需要的二进制文件进行链接,得到hello可执行目标二进制文件。
六、执行hello,shell调用fork函数创建子进程,并分配相应的内存资源。
七、使用execve函数加载进程。到这里完成了P2P。
八、程序执行结束后,操作系统进行进程回收,完成020。
计算机系统是对计算机各软硬件的集大成者,其作用相当于首领之于团队的作用。计算机系统为用户提供了一个大而统一的抽象,让用户不必考虑系统内部的实现过程,保证的系统内核的安全性。同时,其对于编程人员也提供了抽象,如内存和文件的读写,这些内容都是由系统来完成的。缓存也是当代计算机的一个成功概念,其在高速存储设备与低速存储设备中提供了一层缓冲,使计算机运行中高速缓存的性能不会过分依赖低速缓冲的性能。

附件

中间结果文件名作用
hello.i预处理后的中间结果
hello.s汇编语言文件
hello.o可重定位EFL文件
hello.out可执行ELF文件

参考文献

[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.
[7] https://blog.csdn.net/weixin_34148340/article/details/91912492 Bash命令行处理流程详解
[8] https://blog.csdn.net/SmallCongc/article/details/85316619
[9] https://www.cnblogs.com/RogerLi/p/10226027.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值