计算机系统
大作业
题 目 程序人生-Hello’s
P2P
专 业 计算机类
学 号 1180300412
班 级 1803004
学 生 yiguanghui
指 导 教 师
计算机科学与技术学院
2019年12月
摘 要
关键词:计算机系统、编译链接、异常控制流、虚拟内存
摘要:本文较详细地跟踪介绍了hello.c在Linux下的生命周期,从被程序员创建,到在系统上运行,然后输出简单的消息,最后终止。本文通过计算机系统课程中相关的知识来分析hello.c在Linux开发工具下经历预处理、编译、汇编、链接、加载、执行、终止、回收等过程和结果,跟踪程序的链接、进程创建及加载、虚拟内存转换、高速缓存访问、异常控制流、I/O管理等过程。
目 录
第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章 概述
1.1 Hello简介
Hello程序的生命周期是从一个高级C语言程序开始的。在linux系统上,GCC编译器驱动程序读取源程序文件hello.c,通过执行预处理阶段、编译阶段、汇编阶段和链接阶段这四个阶段翻译成一个可执行目标文件hello。
P2P:From Program to
Process:gcc编译器驱动程序读取程序文件hello.c,然后由预处理器(cpp)根据以#字符开头的命令,修改该程序获得hello.i文件,通过编译器(cll)将文本文件hello.i翻译成文本文件形式的汇编程序hello.s,再通过汇编器(cs)将hello.s翻译成机器语言指令,将指令打包成可重定位的目标文件hello.o,然后通过链接器(ld)合并获得可执行目标文件hello。Linux系统中通过内置命令行解释器shell加载运行hello程序,为hello程序fork进程,此时,hello.c就完成了P2P的过程。
O2O:From Zero-0 to
Zero -0:shell为子进程通过execve,mmap等一系列指令来加载可执行目标文件hello。这些指令将hello目标文件中的代码和数据从磁盘复制到主存,一旦目标文件hello中的代码和数据被加载到主存,处理器就开始执行hello程序的main程序中的机器语言指令,这些指令将它的消息字符串字节从主存复制到寄存器文件,再从寄存器文件复制到显示设备,最终显示在屏幕上,hello程序在屏幕上输出它的消息,然后终止。hello进程终止后,操作系统恢复shell进程中的上下文,并将控制权传回给它,shell进程等待下一个命令行的输入。hello执行完后shell为其回收子进程。
1.2 环境与工具
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
软件环境:Windows7 64位以上;VirtualBox/Vmware
11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上
开发调试工具:CodeBlocks
64位;vi/vim/gedit+gcc
1.3 中间结果
hello.c C语言源程序
hello.i hello.c预处理后生成的文本文件
hello.s
hello.i经编译器翻译后生成的文本文件
hello.o hello.s经过汇编器翻译后生成的可重定位目标文件
hello hello.o链接后的可执行文件
hello.elf
hello.o的elf格式文件
1.4 本章小结
本章整体性地概括了hello在计算机系统中的生命过程,介绍了大作业需要使用的软硬件环境和开发工具以及作业过程中产生的文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:在编译执之前进行的处理。包括三个方面:宏定义、文件包含、条件编译。预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如第六行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入到程序文本中。结果就得到了另一个C程序,通常是以.i作为文件扩展名。
2.2在Ubuntu下预处理的命令
gcc -E
hello.c -o hello.i
-E选项保留预处理器的输出文件,使用-o选项将它导入到hello.i
图1.1预处理hello.c
2.3 Hello的预处理结果解析
图1.2.hello.i的部分代码
hello.c结果预处理之后,最初的23行代码变成了hello.i中的3113行代码,而main()函数处在hello.i中末尾且内容并没有变化,即程序开头的#include<stdio.h>,#include<unistd.h>,#include<stdlib.h>被预处理器读取,并且直接插入到了hello.i中,因此程序变成了三千多行。
2.4 本章小结
本章介绍了预处理器对C程序进行的预处理操作规则和结果文件hello.i的解析,练习了Linux预处理的指令,掌握了预处理的概念与其在hello.c程序处理过程中的作用。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译的概念:用编译程序来产生目标程序的动作,它包含五个阶段:词法分析、语法分析、语义检查和中间代码生成、代码优化、目标代码生成。主要进行词法分析和语法分析,分析过程中若发现有语法错误,则给出提示信息。编译器将文本文件预处理产生的hello.i翻译成文本文件hello.s,其包含一个汇编语言程序。
3.2 在Ubuntu下编译的命令
gcc -S
hello.i -o hello.s
图3.1编译命令
3.3 Hello的编译结果解析
3.3.1:结构信息
图3.2.hello.s的结构信息
.file:源文件名
.text:代码段
.section,
.rodata:只读数据段
.align:对齐方式
.string:字符串常量
.globl main:主函数,可以在其他的模块中被引用的全局函数
.type
main,@function:指明main是函数类型
3.3.2:数据类型
(1)字符串常量:
输出字符串保存在.rodata节中,一共有两个字符串
a.”用法:Hello 学号 姓名 秒数!\n”,该字符串使用UTF-8编码,“用法”两个汉字占开始的六个字节,一个汉字占三字节。
b.”Hello %s
%s\n”,此字符串直接正常保存
图3.3字符串
(2)整型:
a.int i:局部变量i存放在栈中,位于-0x4(%rbp)的空间中,大小为四个字节
b:int argc: main函数的第一个参数,存放在寄存器%edi,之后传入栈中-0x20(%rbp)的位置,大小为四个字节
c.立即数常量如3,8等,直接存放在代码段
(3)数组:
程序中只有一个数组char* argv[],它是main()函数的第二个参数,每个元素大小为8个字节,数组占据了连续的栈空间,可以使用相对寻址利用数组的首地址与每个元素的大小来寻址。argv的首地址为-32(%rbp),argv[1]地址为-24(%rbp),argv[2]位于-16(%rbp).
3.3.3:汇编语言操作:
(1)赋值操作:
Mov S,D,效果:D<—S
指令
描述
movb
传送字节
movw
传送字
movl
传送双字
movq
传送四字
程序中使用下面这条指令来为i赋初值0
图3.4为i赋值为1
(2)算术操作:
在for循环中,每经一次循环,i++,使用立即数直接加到i所在的空间,编译产生的指令为:
图3.5 i+1
(3)类型转换:argv[3]是字符型,使用atoi将其转换为整型变量。
图3.6 将第四个命令行参数转换为整型数
(4)关系操作:
判断argc!=4,使用立即数4与argc比较,由于argc是main()函数的第一个参数,其存放在寄存器%edi中,之后又将%edi存放的值传送给-20(%rbp),故编译生成指令为: