择要:以hello.c程序为例,文章探究了从C语言代码文件到进程接纳这一过程。基于Linux操纵系统,运用gcc,edb等工具,详细介绍了程序编译的流程,分析了运行可执行文件时系统的进程管理与存储管理原理与机制,到末了hello被接纳的整个过程。文章按照程序天生与执行的次序,逐步分析计算机系统系统使用了什么原理,以及是如何实现这些过程。
关键词:计算机系统;编译;存储器层次;进程;捏造内存;
目次
第一章 概述
1.1 Hello简介
1.3 中心效果
1.4 本章小结
第二章 预处理
2.1 预处理的概念与作用
2.2 在Ubuntu下预处理的命令
2.3 Hello的预处理效果剖析
2.4 本章小结
第3章 编译
3.1 编译的概念与作用
3.2 在Ubuntu下编译的命令
3.3 Hello的编译效果剖析
3.3.1 数据类型处理
3.3.2 操纵处理
3.4 本章小结
第4章 汇编
4.1 汇编的概念与作用
4.2 在Ubuntu下汇编的命令
4.3 可重定位目的elf格式
4.3.1 ELF头
4.3.2 节头表
4.3.3 重定位节
4.3.4 符号表
4.4 Hello.o的效果剖析
4.5 本章小结
第5章 链接
5.1 链接的概念与作用
5.2 在Ubuntu下链接的命令
5.3 可执行目的文件hello的格式
5.4 hello的捏造地址空间
5.5 链接的重定位过程分析
5.5.1 链接的过程
5.5.2 重定位过程
5.6 hello的执行流程
5.7 Hello的动态链接分析
5.8 本章小结
第6章 hello进程管理
6.1 进程的概念与作用
6.2 简述壳Shell-bash的作用与处理流程
6.3 Hello的fork进程创建过程
6.4 Hello的execve过程
6.5 Hello的进程执行
6.6 hello的异常与信号处理
6.7本章小结
第7章 hello的存储管理
7.1 hello的存储器地址空间
7.2 Intel逻辑地址到线性地址的变换-段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
7.4 TLB与四级页表支持下的VA到PA的变换
7.5 三级Cache支持下的物理内存访问
7.6 hello进程fork时的内存映射
7.7 hello进程execve时的内存映射
7.8 缺页故障与缺页中断处理
7.9 动态存储分配管理
7.10 本章小结
结论
参考文献
第一章 概述
1.1 Hello简介
Hello的P2P,即from Program to Process,指的是从C语言代码文件hello.c到Linux内核为可执行文件开辟进程的过程。C语言源文件颠末预处理、编译、汇编、链接四大步调,最终形成可执行目的文件hello,存储在磁盘中。执行该文件时,shell为其创建子进程,并且在子进程中加载该程序。操纵系统提供异常控制流,由缺页故障处理程序将其载入物理物理内存。
Hello的020,指的是当hello程序终止,父进程将其接纳,操纵系统内核删除相关数据布局,释放其占据的资源,hello的一生就此结束。
1.2 情况与工具
本台机器CPU为x86-64架构,主频2.5GHz。主存巨细8GB。操纵系统为Windows 10, 64位。捏造机平台使用VMware Workstation 16 Player,捏造机操纵系统Ubuntu 18.04,64位。开辟工具使用gcc+gedit。
1.3 中心效果
- hello.i 预处理程序。
- hello.s 汇编程序。
- hello.o 可重定位目的程序。
- hello 可执行目的程序。
1.4 本章小结
本章简要描述了hello.c的一生,介绍机器硬件与软件配置,并展示中心效果文件,为后续探究打下底子。
第二章 预处理
2.1 预处理的概念与作用
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比方,程序hello.c中第一行#include <stdio.h>命令告诉预处理器读取头文件stdio.h的内容,并直接插入程序文本中。此外,对于hello.c文件前四行解释语句,预处理器也会在hello.i文件中将其删除。
2.2 在Ubuntu下预处理的命令
在Linux Terminal中输入预处理语句:
- gcc hello.c -E -o hello.i
复制代码 天生的文件hello.i部分如图1所示:
图 1 预处理文件与天生过程 2.3 Hello的预处理效果剖析
颠末预处理后,程序文本由原先的23行扩展到3105行。
在hello.i文件的尾部,能发现hello.c文件的程序语句,如图2所示。证明预处理后的.i文件仍保留了原先大部分代码。
同时,预处理器会将#define语句后宏定义进行更换。比方,将ITER更换为相应的8。
图 2 预处理文件与原文件对比 此外,分别在预处理文件hello.i与被引用的头文件stdio.h搜索printf()函数,如图3所示。发现hello.i拷贝了stdio.h中函数的定义,也就证明了预处理器会将头文件插入文本。
图 3 预处理文件与头文件对比 2.4 本章小结
本部分阐述了预处理的概念、作用、Linux预处理指令。并且颠末程序hello.c的预处理文件hello.i与原文件、头文件的对比,验证了预处理器的几大功能:#define定义符号的更换;解释的删除以及头文件文本拷贝等等。
第3章 编译
3.1 编译的概念与作用
编译阶段是编译器(ccl)将文本文件hello.i翻译成文本文件hello.s。hello.s是汇编语言程序,主要包罗低级机器语言指令。这一步调主要有四个任务:语法分析,词法分析,符号汇总与语义分析。
3.2 在Ubuntu下编译的命令
在Linux Terminal中输入预处理语句:
- gcc -m64 -Og -no-pie -fno-PIC hello.i -S -o hello.s
复制代码 天生的hello.s文件部分如图4所示。
图 4 hello.s文件与天生过程 3.3 Hello的编译效果剖析
3.3.1 数据类型处理
对于hello.c中的整数常量,编译器将其转换为立刻数,与代码一起保存在.text段中。比方,原文件中表达式i = 0的编译效果为movl $0 %ebx(%ebx为存放变量i的寄存器)。原文件中argc与4进行判断,对应的编译效果也是相应的寄存器与立刻数比较。注意,编译器并不一定直接拷贝原文件中的立刻数。比方,对于循环条件i<8,对应的机器语言为cmpl $7, %ebx。如图5所示。
图 5 .s文件的立刻数与原文件的整数常量 对于局部变量,编译器将其存储在栈或寄存器中。比方原程序定义变量的语句int i,编译器将其转换为subq $8, %rsp,也就是在栈中开辟8Byte的空间。其中低4Byte用于存放整型变量i。其中立刻数$8是由于SSE指令集要求数据地址16字节对齐(.cfi_def_cfa_offset 32意味着此时栈顶指针相对于调用main前栈顶指针偏置32Byte)。固然该程序中变量i始终保存在寄存器中,栈仍然为其开辟内存空间,如图6所示。
图 6 编译器为局部变量在栈中开辟空间 对于字符串常量,hello.s文件将其保存在.rodata段中。其中,汉字使用UTF-8编码,一个汉字占用3~4字节。如图7所示。
图 7 编译器保存字符串常量 当printf()函数将字符串常量作为参数时,指令movl $.LC1, %edi表示将相应字符串的地址$.LC1存入对于的寄存器,如图8所示。
图 8 函数调用字符串常量 对于数组,比方将argv[0]与argv[1]作为函数参数,由于寄存器%rbp存储数组argv首地址,因此(%rbp)+8与(%rbp)+16分别为对应的函数参数。如图9所示。在这个过程中,编译器需要确定数组第i个元素地址相对首地址的偏移量。偏移量取决于索引i与数组元素的数据类型巨细。
图 9 编译器取数组元素 3.3.2 操纵处理
对于赋值操纵,通过movx Src, Dst的数据传送指令完成。其中,x为操纵数指示符,Src为源操纵数,Dst为目的操纵数。比方,如图5所示,对于i = 0语句,编译器天生的汇编指令为movl $0 %ebx。
对于关系操纵与控制转移操纵,通过cmpx S1, S2指令完成S1与S2的判断,用S2-S1置标志寄存器中的标志位。同时,用jc Label指令完成条件跳转。其中,c表示跳转条件,根据标志位判断是否跳转。Label为跳转目的地的标号。比方,对于原程序判断语句if(argc != 4),先将立刻数$4与寄存器%edi中的值进行比较。此时,若argc == 4,则argc – 4 = 0,即标志位ZF=1。汇编语句jne .L6的跳转条件为~ZF,即ZF=0时跳转。因此,当argc=4时,继续次序执行,否则跳转到.L6处执行。如图10所示。
图 10 控制转移操纵的汇编指令 对于算数操纵,比方原程序中的自增操纵i++,编译器使用双字整数加法指令addl $1, %ebx,即%ebx + 1,再将运算效果送回%ebx。
对于函数操纵,包罗参数传递、函数调用、局部变量与函数返回等题目。调用函数时,参数1~n起首依次存放在%rdi,%rsi,%rdx,%rcx,%r8,%r9寄存器中,剩余的参数从后往前依次入栈。比方,向printf()函数传递参数时,%rdi存字符串常量地址,%rsi存放argv[1],%rdx存放argv[2],如图11所示。
图 11 汇编函数参数传递 调用函数时,编译器使用指令call Label,其中Label为被调用过程的起始地址。该指令把当前PC入栈,再设置PC为Label。当过程调用结束时,对应汇编指令ret,从栈中弹出返回地址,并设置PC,回到调用过程的下一条语句。
在3.3.1中已经分析局部变量存储在栈中。此外,函数返回值存储在寄存器%rax中。
3.4 本章小结
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |