不了解土壤的组成成分,就很难种出可口的蔬菜;不了解操作系统,也不太可能写出高效优雅的程序。
我们先编写一段程序:
#include<stdio.h>
int main()
{
printf("hello world");
}
保存为hello.c,然后我们编译、链接生成可执行的目标程序。当我们执行这个目标程序时,控制台会输出“hello world”。想不想知道程序运行时操作系统发生了什么?以及为什么会这样?
先别急,我们先搞清楚一个概念,“什么是文本文件,什么是二进制文件”?
我们知道,计算机的存储和计算都是基于0和1,如果我们想存储"#",该怎么办呢?毕竟计算机只能识别0和1,那我们能不能用不同的01组合代表这个字符呢?当然可以,这种组合方式我们就称它为"编码"。举个例子,我们用"100011"(十进制35)表示"#"
通过对比编码表,我们的hello.c源程序露出了它的本来面目
我们就把这种由ASCII字符构成的文件称为文本文件,所有其他文件都称为二进制文件。还有个简单的方法来判断,打开全是乱码的就是二进制文件,不是乱码的就是文本文件。
言归正传,我们编写的hello.c文件属于源文件,并不能做什么事情,需要经过以下几个步骤将其变为可执行的程序:
1、预处理
#gcc -E hello.c -o pre_hello.i
(1)将所有的#define删除,并且展开所有的宏定义。说白了就是字符替换
(2)处理所有的条件编译指令,#ifdef #ifndef #endif等,就是带#的那些
(3)处理#include,将#include指向的文件插入到该行处
(4)删除所有注释
(5)添加行号和文件标示,这样的在调试和编译出错的时候才知道是是哪个文件 的哪一行
(6)保留#pragma编译器指令,因为编译器需要使用它们
vim pre_hello.i
没有乱码,所以它是文本文件。
2、编译
#gcc -S pre_hello.i -o com_hello.s
(1)词法分析
(2)语法分析
(3)语义分析
(4)优化后生成相应的汇编代码
vim com_hello.s
这里还是文本文件。
3、链接
#gcc com_hello.s -o hello
生成可执行的程序hello
vim hello
已经是乱码了,这里就是二进制文件。
我们来运行下这个可执行程序hello
#./hello
前面扯了那么多,那么可执行程序hello执行的时候,后台发生了什么?讲到这儿我们就需要画一个系统的硬件组成图了,一图胜千言。
I/O总线:传递字节的管道,计算机的数据都是由0和1构成,这个总线就负责传输01字节。如同一箱一箱的传送货物,I/O总线传输的也是定长的字节块,有的系统是4字节,32位,有的系统是8字节,64位(一个字节8bit)。
I/O设备:作为用户输入的鼠标和键盘,作为用户输出的显示器,以及磁盘都属于I/O设备,每个I/O设备都通过控制器或者适配器和I/O总线相连。这里插一个知识点,控制器和适配器有什么不同?控制器是置于I/O设备本身或者主板上的芯片组,而适配器是一块插在主板插槽上的卡,适配器可以很方便的插拔替换。它们的功能都相同,就是一个中介,在I/O总线和I/O设备之间传递信息。
主存储器:就是我们通常说的内存。
处理器:就是CPU中央处理单元。PC程序计数器,相当于一个指针,指向某条指令。每种CPU都有自己的指令集,操作都是在指令集的范围内操作。ALU算术逻辑单元,简单理解就是做算术逻辑运算的。工作的大致流程,先把内存的数据读取到寄存器,这一步称为加载,相当于把切好的菜放到炒锅里;然后寄存器把数据复制到ALU进行运算,置于是加法还是乘法或者其他,决定于从PC中读取的指令,并且运算后,将数据再复制到寄存器,这一步相当于炒菜添加油盐酱醋,俗称操作;最后是将数据由寄存器复制到内存,俗称存储,相当于把菜盛到盘子里。
概念讲完了,下面来看下程序运行时,计算机内部都发生了什么?
#./hello
在键盘上输入./hello后,shell会将hello这几个字符逐一读入CPU内部的寄存器,然后再把它存到内存(主存储器)。
之后shell执行指令将hello文件的内容加载到内存,因为文件是存储在磁盘上的,这一步操作属于从磁盘读取数据加载到内存。这里讲一个概念DMA(直接存储器存储技术)。上一步中,在控制台输入hello字符后,数据是先被读取到CPU的寄存器中,之后再由寄存器复制到内存。直接存储技术 就是省略数据加载到寄存器那一步,直接由磁盘传输到内存。
现在hello程序已经有磁盘传输到内存了,之后CPU将开始执行hello的二进制指令,这些指令将"hello world\n"中的字节由内存复制到寄存器,再从寄存器复制到显示设备,最终显示在屏幕上。
至此程序执行完成!