本论文从一个程序文件hello.c出发,通过讲述该文件实现P2P,O2O的过程,简单地展现了计算机系统的工作原理,并且回顾了许多实用的工具。
关键词:P2P,O2O,计算机系统,程序;
1.编译过程
2. 创建并运行进程 通过给予上下文形成进程,并放在系统中运行 。
shell执行hello进程,为其映射出虚拟内存,然后在开始运行进程的时候分配并载入物理内存,开始执行hello的程序,将其output的东西显示到屏幕,然后hello进程结束,shell回收内存空间。
硬件环境: X64CPU; 8GHz; 8GRAM; 1TB HD;
软件环境: Windows10 64位;VMware14.12; Ubuntu 16.04 LTS 64位;
工具:gcc,gdb,cpp,ccl,as,ld,readelf等。
hello.i
hello.c文件通过预处理后得到的新的程序文本文件,对于包含文件,宏定义以及条件编译进行了处理;
hello.s
hello.i文件通过编译后得到的由汇编语言编写的文本文件,将高级语言转换成了对应的汇编语言;
hello.o
hello.s文佳汇编后得到的二进制文件,可用于与其他可重定位目标文件链接形成可执行文件;
hello
可执行目标文件,在主机端输入./hello后可执行程序,完成程序的预期任务。
关于hello.c P2P,O2O的粗略介绍。
预处理器(cpp)根据义字符#开头的命令,修改开始的c程序。比如hello.c中第一行的#include
hello.i可作为文本文件打开,该文本的最后末端的内容即是hello.c的主体内容,而hello.i前面还有大量的文本,包括各种各样的定义,系统内的文件引用,可见是将hello.c内预处理部分(主要是三个头文件)经过了预处理,将内容插入到了程序中,成为了一个新的程序。
预处理的主要三个功能就是处理宏定义,插入包含文件的内容,以及依据条件编译。
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,包含一个汇编语言程序。
gcc -S hello.c -o hello.s
3.3.1数据传送指令mov、movw,movsbw
一般用于赋初值,数据更换寄存器(具体目的包括输入、输出等)等
赋初值,利用了栈空间,l(movl)表示按4字节传送
3.3.2压入和弹出栈数据push、pop
利用栈空间 将寄存器%rbp中的数据压入栈中(一般是(%rsp-8)所示内存地址)
3.3.3算数和逻辑运算leaq、add、not、and、sal、shr等
包括加载有效地址、加减乘除运算、逻辑运算、移位运算
此外还有mul,imul乘法操作;div,idiv除法操作;and,or,xor,not逻辑运算;sal,shl,sar,shr移位运算 。
3.3.4控制操作cmp、test、set、jmp
比较(设置条件码)、跳转、由此可做到条件语句、循环语句
判断栈中数据与9的大小,决定是否跳转其中.L3 .L4(按顺序,L4在L3前)形成了循环语句,对应于hello.c中的for语句;
这是一个条件跳转语句,对应于hello.c中的if语句
3.3.5函数调用call、ret
也可由此实现递归
3.3.6全局变量
对应于hello.c中的int sleepsecs =2.5,由于int定义为整形数,所以此处给的空间为4个字节
汇编语言相较于一般的高级语言,更加展现了机器实现操作的步骤和思路,也给出了许多高级语言无法给出的信息(寄存器中的数据,栈的空间使用情况),而且可读性很高。
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。hello.o是一个二进制文件(0/1)。
可见其包含elf头表,节头表,重定位节和一个符号表重定位项目中给出了偏移量、信息、类型、符号值、符号名称等基本信息,如.rela.text中:
机器语言的一条指令一般是由若干个字节组成,包括指令、常数。
如b8 00 00 00 00前一个b8表示mov到%eax的指令,后面的八个零表示常数0x0(4字节,小端表示)
b8 00 00 00 00 mov 0x0 , %eax
在跳转时,反汇编文件跳转命令后直接接表示偏移量的数字
如:jmp 6fje 2bcallq 21
而机器语句中则是依据当前语句与要跳转到的语句偏移量的差,
如:26:e8 00 00 00 00 call 2b
由于0x26(当前PC相对偏移量)+0x5(5个字节) = 0x2b,所以按顺序结构中间相差0个字节,即00 00 00 00
汇编主要表现了机器语句的构造(章4 Y86-64),通过汇编,得到.o文件(二进制),机器才可以处理该文件。通过反汇编与.s文件的比较可以发现真正的机器指令思路上大体与汇编相同,但数的处理上会稍有不同。
hello程序调用了printf函数,printf函数存在于一个名为printf.o的单独地预编译好了的目标文件中,链接器(ld)就负责处理这种合并,结果就得到hello文件,一个可执行目标文件。
ld hello.o -lc -o hello.out 或gcc hello.o -o hello.out
最简单的生成可执行文件的方法: gcc -o hello hello.c
可见包含一个elf头表,29个节头,1个程序头,2个重定位节,2个符号表等,相比之前多了很多内容。图中含有地址,大小等信息
虚拟地址从0x00400000开始,到0x004001000
与5.3对照
可见若干程序头,包括:PHDR INTER PLOAD NOTEGNU_EH_FRAME GNU_STACK GNU_RELRO
1.hello的内容较多,多出来很多函数puts,printf,sleep等,还有节.init .plt等
2.对函数引用的差别比较明显
hello.o
可见hello主要是跳转向
3. 从节头数量看,hello也会比较多 hello.o的节头有13个,和hello的节头有29个
hello.o
载入:_dl_start _dl_init
开始执行:_start _libc_start_main _init
执行main:_main _printf _exit _sleep _getchar _dl_runtime_resolve_xsave _dl_fixup _dl_lookup_symbol_x
退出:exit
在edb调试之后,原先0x00600a10开始的global_offset表全部成0,在执行_dl_init之后被赋上了相应的偏移量。说明_dl_init操作是给程序赋上当前执行的内存地址偏移量。
通过与外部函数的链接,形成一个可以在机器上执行的文件,在链接的过程中,主要处理了符号解析和重定位的问题,最终使得我们编写成的简单的程序文件成为一个可以在机器环境中调用空间资源并处理问题的有效代码。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
shell是一个应用程序,操作系统中提供了一个用户与系统内核进行交互的界面。
处理流程:1.读取用户的输入 ;2.分析输入内容,获得输入参数; 3.如果是内核命令则直接执行,否则调用相应的程序执行命令; 4.在程序运行期间,shell需要监视键盘的输入内容,并且做出相应的反应.
execve运行时,先是从可执行文件中加载的内容,然后是运行时的堆栈和共享库的存储器映射区域。
新的程序典型的栈帧:
输入Ctrl+C,进程直接结束,该操作是向进程传输sigint信号,终止前台作业
输入Ctrl+Z, 进程停止 ,该操作是传输sigtstp信号,停止前台作业,不结束进程,输入ps后可见hello仍在。
乱按回车,除了换行,不受影响
Ctrl-z后可以运行jobs命令,可返回上一条指令结果的内容
Ctrl-z后可以运行pstree命令,可显示进程树
kill命令可想制定进程传送制定信号,向PID为2789的进程hello传送序号为2的信号(sigint,等同于Ctrl+C),发现进程终止。
进程在shell的环境下用fork,wait创建并回收子程序,用execve加载新的程序,用键盘、函数反复地发送、捕获、接收、处理/放弃信号,一会运行程序一会处理信号。
逻辑地址:其实指的是偏移量,如21
线性地址(虚拟地址):虚拟地址空间中的地址,可以保证每个进程拥有相同的地址空间,而且简化了内存管理。0x00400000,几乎所有的进程的用户空间都由此开始。
物理地址:内存地址,是真实且与内存空间一一对应的地址
先将逻辑地址分成段选择符+段描述符的判别符(TI)+地址偏移量的形式,然后先判断TI字段,看看这个段描述符究竟是局部段描述符(ldt)还是全局段描述符(gdt),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了
先将虚拟地址分为虚拟页号(VPN),虚拟页偏移量(VPO),依据VPN(TLBT+TLBI)先在TLB中寻找,若找不到,则在高速缓存/内存中寻找,若找到对应的物理页号(PPN),再将PPN与VPO组合成物理地址,若还是查找不到,则需要缺页处理,在磁盘中查找,并将新的页更新入内存中。
36位的VPN被分成4个9位的片,每个片被用做到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。VPN1提供一个L1 PET的偏移量,这个PTE包含L2页表的基地址,VPN2再提供一个偏移量,以此类推。
多级页表的地址翻译(这里借用一下老师的图)
物理地址拆分为CT(标记),CI(组索引),CO(块偏移),依据组索引,找到对应的组,在依据标记查找是否存在且是否可访问,最后根据块偏移找到块,若没有找到则向下一级高速缓存中查找
创建当前进程的mm_struct,vm_area_struct和页表的原样副本
两个进程的每个页面都标记为只读页面
两个进程的每个vm_area_struct都标记为私有,这样就只能在写入时复制
删除已存在的用户区域
创建新的私有区域(.malloc,.data,.bss,.text)
创建新的共享区域(libc.so.data,libc.so.text)
设置PC,指向代码的入口点
1.第一种情况:缺失发生时,相关的页已经被加载进内存,但是没有向MMU注册的情况。操作系统只需要在MMU中注册相关页对应的物理地址即可。
2.第二种情况:指相关的页在页缺失发生时未被加载进内存的情况。这时操作系统需要:寻找到一个空闲的页。或者把另外一个使用中的页写到磁盘上(如果其在最后一次写入后发生了变化的话),并注销在MMU内的记录;将数据读入被选定的页;向MMU注册该页。
动态内存分配器维护这一个进程虚拟内存区域,称为堆
(感谢老师)
显示分配器主要方法有隐式空闲链表,显示空闲链表
(再次感谢老师)
由于显示空闲链表可利用空闲块记录数据结构,所以更加通用
主要表述了一个地址的产生,经过各种翻译,最后返回需要的数据,包括从虚拟地址到物理地址,再从物理地址找到数据,以及翻译查找过程出现缺页的处理办法,和维护进程虚拟空间的办法
设备的模型化:文件
文件的类型:
普通文件(regular file):包含任意数据的文件。
目录(directory):包含一组链接的文件,每个链接都将一个文件名映射到一个文件(他还有另一个名字叫做“文件夹”)。
套接字(socket):用来与另一个进程进行跨网络通信的文件
命名通道
符号链接
字符和块设备
设备管理:unix io接口
打开和关闭文件
读取和写入文件
改变当前文件的位置
打开文件:open函数,打开一个已存在的文件或者创建一个新文件
int open(char *filename,int flags,mode_t mode);
关闭文件:close函数,关闭一个已打开的文件
int close(int fd);
读:read函数,执行输入
写:write函数,执行输出
ssize_t read(inf fd,void *buf,size_t n );
ssize_t write(inf fd,const void *buf,size_t n );
其中调用的vsprintf函数的作用是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。Write则将vsprintf的输出逐个的写到终端。
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall。
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
getchar有一个int型的返回值.当程序调用getchar时.程序就等着用户按键.用户输入的字符被存放在键盘缓冲区中.直到用户按回车为止(回车字符也放在缓冲区中).当用户键入回车之后,getchar才开始从stdin流中每次读入一个字符.getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕.如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取.也就是说,后续的getchar调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完为后,才等待用户按键。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
表述了Unix IO接口及其4个函数,简单了解了一下printf,getchar的实现
hello.c一开始说白了就是一堆文本文字,直至经过了预处理、编译、汇编、与若干其他文件的链接后终于成为了一个可执行目标文件hello,此时他已经可以走向linux,在系统中正式开始自己的程序生涯,他被给予上下文成为了进程,在系统中运行并处理信号,一些有它产生或者他需要的信息、空间则通过与存储结构合作,调用空间,存储信息。而且还会通过I/O设备与其他程序在进行交互与通信。
在完成大作业的过程中,回顾了这一学期以来csapp课程许多重要的概念、理解和操作,发现相比于半年前刚刚接触到深入C程序时对于C有了很多理解,一边是敬佩和感叹前人对于计算机系统设计的精妙与复杂,一遍又深觉自己的不足,虽然所谓的深入理解依旧是比较浅的,但是对于编写程序而言,提供的帮助是显而易见的。
hello.i hello.c文件通过预处理后得到的新的程序文本文件,对于包含文件,宏定义以及条件编译进行了处理;
hello.s hello.i文件通过编译后得到的由汇编语言编写的文本文件,将高级语言转换成了对应的汇编语言;
hello.o hello.s文佳汇编后得到的二进制文件,可用于与其他可重定位目标文件链接形成可执行文件;
hello 可执行目标文件,在主机端输入./hello后可执行程序,完成程序的预期任务;
[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/dxyinme/article/details/85322271
[8]https://www.cnblogs.com/pianist/p/3315801.html
[9]深入理解计算机系统原书第三版. 机械工业出版社