我们从单片机上知道,在上电的瞬间,会把单片机的程序指针初始化到上电复位时的地址,并从该地址读取要执行的指令,这样程序就在单片机上开始执行了(当然在调用程序的main之前,还有一系列其他的初始化要做,比如堆栈初始化,不过我们很少去修改这些)。PC机上电的时候和单片机类似,但是会读取BIOS,由BIOS完成很多初始化操作,最后调用系统初始化函数把控制权交给操作系统,于是我们就看到了Windows、Linux系统的启动。
如果把操作系统想象成一个运行在处理器上的大型裸机程序(也就是直接运行在硬件上的程序,因为操作系统直接运行在CPU上,这样想也是可以的,但是这个裸机程序有很多强大的功能),那么操作系统的启动和单片机程序的启动非常类似,前者有一个大型的初始化程序来完成非常复杂的初始化,后者则有一个很短的汇编代码来完成一些简单的初始化,从这个方面来看,他们的流程非常类似。
如果是系统上的程序启动会怎么样?都是由系统决定的。在Linux的shell中输入./p后,首先检查它是否是shell内置的命令;如果不是,shell就认为它是一个可执行文件(在Linux上一般是elf格式),然后调用一些相关函数把硬盘上的p文件的内容复制到内存(DDR RAM)中,并建立它的运行环境(当然还有一些其他的比如内存映射、虚拟内存、连接和加载等),准备执行。
从上面我们可以看出单片机上的程序和系统启动时运行的程序有很大的区别(如果把程序调用main之前的动作抽象为初始化,程序的启动可以简化为:建立运行环境+调用main函数,因此程序的执行区别并不大)。因为单片机上运行的程序(裸机程序)和操作系统一样都是运行在硬件上的,所以属于同一层次。以前之所以没有区分单片机上的程序和PC上的程序的一些区别,就是因为没有认识到这一点。
这样,之前的一些疑惑就解开了,为什么MCU上的程序很少使用malloc,而在PC上却经常使用呢?因为MCU上没有预先写好的内存管理算法的代码,而PC上操作系统中运行的程序,libc已经把这些都做好了,只需要调用它就可以了。如果要在MCU上使用动态内存,是可以的,但是得自己实现这些代码,定义一个对应的malloc。有时候有些公司会提供一些库函数,这些库函数可能实现了malloc,但是因为MCU上的RAM内存非常有限,如果不了解它是怎么工作的,很可能就会非常危险。同样,由于PC系统上运行的程序和裸机程序不同,裸机程序不会有动态链接,而只有静态链接。
程序执行的时候,从哪里读指令,从哪里读数据,我困惑了好久,因为没搞清楚系统上的程序和裸机程序的区别。虽然在《微机原理》课上知道程序运行时是从内存中读取指令和数据执行,再写回。但是单片机上的RAM只有几K,而Flash一般都是几十K甚至1M,这时候指令和数据是不是都在内存里呢?(这里的内存只指RAM,因为我们在PC上经常讲的内存就是DDR RAM内存,而我们先入为主的认为单片机也是这样,也没搞明白RAM和Flash都是内存)?这不可能,因为老师上课只讲内存,但是PC上的内存一般都是DDR RAM,而不是硬盘,硬盘才是存放数据的地方;打这个比方的时候,我自己都搞混了。单片机的RAM对应的是DDR RAM,那么Flash是不是就对应的是硬盘呢? 在CSAPP中我了解到PC上所有东西都存在DDR RAM中的原因在于速度因素。硬盘太慢,甚至即将问世的SSD也比DDR RAM慢几个数量级,所以就复制到DDR RAM中。此时一个程序的代码和数据是连续存放的,其中代码段是只读区域,数据段是可读写区域(这是操作系统的内存管理机制决定的)。运行时,它们被复制到速度更快的SRAM中,以获得更快的执行速度。
对于单片机来说,工作频率只有几M或者几十M,从Flash中读取和从RAM中读取差别可能并不明显,不会成为程序执行的瓶颈(而对于PC来说,Flash太慢,DDR RAM也很慢,甚至SRAM更是慢很多,所以提高工作频率并不能提高程序的执行速度,于是就出现了瓶颈。为了提高CPU的利用率,换个角度想,既然程序的执行时间无法缩短,那么就可以在同样的时间内执行更多的程序,一个核执行一个程序,两个核就可以执行两个程序,所以现在多核CPU已经成为主流)。所以裸机程序指令都存放在Flash(闪存)中,数据则放在RAM中(Flash的写入次数有限,其速度还是远远落后于RAM的)。更通俗的讲,在单片机上,RAM存放的是数据段、bss段、堆栈段; ROM(EPROM、EEPROM、Flash等非易失性存储设备)存储的是代码和只读数据段。本质上,这和PC上把程序全部放在RAM里是一样的。PC上,操作系统定义了哪些是可读的,哪些是可写的;而单片机上,则用不同的存储设备来区分哪些是可读的,哪些是可写的。(当然,现在的Flash是可以读写的,如果Flash没有写入次数限制,速度跟RAM差不多,那单片机是不是只需要Flash(直接相当于PC上的DDRRAM)?这样也比用一块RAM一块Flash便宜,对厂家来说,可以节省更多的成本,更划算。)
单片机程序执行过程中指令和数据的存储和读取理解如下:
单片机编程完成后,程序的代码段、数据段、bss段、rodata段等都是存放在Flash中的。单片机上电时,初始化汇编代码将数据段和bss段复制到RAM中,设置堆栈,开始调用程序的主函数。此后,就有了程序存储器和数据存储器的区分。运行时,从Flash(即指令存储器、代码存储器)读取指令,从RAM读写数据。RAM的意义在于速度更快。
不管是单片机还是PC,现有的内存金字塔都是一样。速度因素和成本制约导致每一级更快的内存速度越快,成本也就越高。应该说,理解程序执行就是理解内存金字塔。
笔记:
那么,RAM、ROM 和 Flash 是什么?虽然它们都是计算机内存的形式,但 RAM、ROM 和 FLASH 都以自己的方式与它们存储的数据交互。以下是对每种内存类型的简要描述。
RAM:代表随机存取存储器:微处理器可以读取和写入的存储器。当我们创建某些东西时,它是在内存中完成的。RAM 就是内存,反之亦然。
ROM:代表只读存储器:微处理器可以读取 ROM,但不能写入或修改它。ROM 是永久性的。ROM 芯片通常保存重要的、永不改变的特殊计算机指令。微处理器可以随时访问存储在 ROM 中的信息。由于这些指令无法擦除,因此它们存储在 ROM 中。
闪存:一种兼具 RAM 和 ROM 特性的特殊存储器。我们可以像使用 RAM 一样将数据写入闪存,但与 ROM 一样,断电时数据不会丢失。遗憾的是,闪存的速度不如 RAM,因此不要指望它随时可以取代标准计算机内存。