程序保存在存储设备之中,通过有序的读取来实现运行,这称为存储程序方式,现在看来很理所当然,但是在过去确是一个里程碑的意义,因为以前改变程序是改变布线的(想想自己学的数字电路)。但是程序是如何启动运行的呢?
单片机
我们可以通过串口或者JLINK来讲编译好的bin文件或者hex文件烧录到单片机里面,我们都知道代码是烧录在flash中的,程序运行时会将堆栈分配到RAM中,程序的变量,结果都存在堆栈中,CPU运算完之后会将结果保存在RAM中,可是单片机上电后是如何知道我们的程序在哪里,并且有条不紊的执行呢?这个就是硬件工程的设计了,在单片机中有两个寄存器十分重要,一个是PC,一个是SP。
PC是16位程序计数器(Program Counter),它不属于特殊功能寄存器范畴,程序员不以像访问特殊功能寄存器那样来访问PC。PC是专门用于在CPU取指令期间寻址程序存储器。PC总是保存着下一条要执行的指令的16位地址。通常程序是顺序执行的,在一般情况下,当取出一个指令(更确切地说为一个指令字节)字节后,PC自动加1。如果在执行转移指令、子程序调用/返回指令或中断时,要把转向的地址赋给PC。具体就是
1.PC的现行值,即下一条将要执行的指令的地址,自动压入堆栈(这里就是SP的作用了,不累述),保护起来。
2.将子程序的入口地址或中断向量的地址送入PC,程序流向发生变化,去执行子程序或中断服务子程序。
3.遇到返回RET或RETI指令时,将栈顶的断点值弹回PC中,程序的流向又返回到断点处,从断点处继续执行程序。
我们的STM32单片机可以通过设置BOOT0和BOOT1来实现从RAM启动还是从flash启动。这两种有什么区别呢?flash是掉电保存的,即没有电程序也是存在里面的,但是RAM不是的,RAM一旦断电其中的程序就会消失。所以,一般来说我们将程序都是烧录在flash中的,这样下次我们就不需要烧录了,但是有时候我们需要频繁的烧录程序,而flash的烧录是十分缓慢的,并且不易调试,这时我们就可以将程序烧到RAM中。等到所有都调试完毕再烧到flash中的。当然,我们都知道RAM的运行程序快于flash,所以直接烧到RAM中运行也会加快的(我是没有发现)。
上面只是简单的说了一下程序是如何有序执行的,但是还有很多东西都被省略了,比如在程序开始之前是如何找到main函数在哪里的,中断时是如何找到中断函数的,堆栈是如何分配的等等,这个其实就是启动文件。启动文件是帮助我们如何来分配堆栈(有了堆栈函数才能运行),如何定义中断函数位置,如何配置时钟。每一个芯片都有自己的启动文件,但是我们在编写51的时候好像没有看到过,其实开发软件已经帮我们内化了,所以不需要我们来参与。总的来说,对单片机编程后,程序的代码段,data段,bss段,rodata段等都存放在Flash中。当单片机上电后,初始化汇编代码将data段,bss段,复制到RAM中,并建立好堆栈,开始调用程序的main函数。以后,便有了程序存储器,和数据存储器之分,运行时从Flash(即指令存储器,代码存储器)中读取指令 ,从RAM中读取与写入数据。RAM存在的意义就在于速度更快。
PC
让我有一点很不明白的是,为什么PC的程序必须要在内存中运行,而不是磁盘运行。计算机中存储程序的部件是磁盘和内存,但是磁盘中存储的程序必须要加载到内存中才可以运行。在磁盘中保存的程序是无法直接运行的。这是因为负责解析和运行程序内容的的CPU,需要通过内部程序计数器来指定内存地址,然后才能读出程序。即使CPU可以直接读取并运行磁盘中保存的程序,但是磁盘的读取速度是非常缓慢的,程序的运行也是非常缓慢的。但是对于单片机来说,运行的速度也就是几十上百兆,相对来说是比较慢的,所以程序在RAM中和flash中运行的差异不是十分明显。所以PC在内存运行程序而不是磁盘主要的原因就是速度。
可是我们的内存都是几个G的,但是有时候我们运行十几个G的程序内存还是吃的消,这是为什么呢?这个就要说到虚拟内存了。虚拟内存是指把磁盘的一部分作为假象的内存。所以当我们运行十几个G的程序时,其实内存运行了一部分,磁盘“运行”了一部分。当然这里程序是不会在磁盘中运行的。CPU只能运行内存中的程序,所以当要运行磁盘上的程序时,系统会把磁盘上的程序和内存中的程序进行部分置换,这样看起来就是在运行十几个G的程序,其实运行的只是一部分而已。虽然程序是运行起来了,可是既然要置换必然就会减慢程序执行的速度。
嵌入式
为什么说嵌入式呢?因为我在学习嵌入式开发板时才想到了这个问题。开发板上有NAND FLASH和NOR FLASH两种flash,当时就觉得很奇怪,为什么要两个flash,而且还有两种启动方式。然后找了一些资料,有了点儿明白。
先来看看NAND FLASH 和 NOR FLASH的区别。
NORFlash的读取和我们常见的SDRAM的读取是一样,用户可以直接运行装载在NORFLASH里面的代码,不必再把代码读到系统RAM中,这样可以减少SRAM的容量从而节约了成本。NANDFlash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的而且是序列性写入,通常是一次读取512个字节,抹除动作只能以“区块”为单位偏移量进行,采用这种技术的Flash比较廉价。用户不能直接运行NANDFlash上的代码,因此好多使用NANDFlash的开发板除了使用NANDFlash以外,还加上了一块小的NORFlash来运行启动代码。
总的来说就是
NOR FLASH地址线和数据线是分开的,可以采用内存的随机读取方式,用户可以直接运行其中的代码。
NAND FLASH地址线和数据线是共用的,需要程序来控制,才成读写数据,所以不可以片上运行。这样做的好处就是可以减少NAND FLASH的引脚。
mini2440(我买的是友善之臂)的NAND FLASH有128M,而NOR FLASH 只有2M,系统的bootloader一般很小,只有200多KB,而作为一个完整的系统,需要bootloader(200K),kernel(2M),rootfs(100M),这些加起来就有100多M,这三个部分不管有没有电都要保存在单片机中,所以肯定不能烧到内存(SDRAM)中,需要存在flash中。所以mini2440的NOR FLASH中存储了bootloader,而NAND FLASH中存储了NAND FLASH。但是如果我们要运行系统,那么必须是从NAND FLASH中启动(开关拨到NOR启动,显示屏什么都没有,因为NOR FLASH中只存储了bootloader,只能检测有没有硬件损坏,没有显示屏的驱动,所以不会显示),从NAND FLASH启动时也并不是整个系统都是从NAND FLASH运行,而是映射到内存(SDRAM)中运行。
看完这些大致也就知道为什么NAND FLASH不能执行程序了,首先NAND FLASH是地址和数据在一起的,并且是连接到了控制器上而不是总线上,我们的CPU是直接寻址读取指令并执行的,NAND FLASH有自己时序,这样CPU就不能取得执行的代码,也就不能加载系统了。其次,NAND FLASH是块读取的,这个我们想要访问个变量就必须要读取一个块,我们不能随机来访问,程序也无法跳转。那么为什么我们从NAND FLASH中还是能够启动系统运行程序呢?
这是因为NAND FLASH内部有一个RAM,叫做stepping stone(垫脚石),如果S3C2440被配置成从Nand Flash启动(配置由硬件工程师在电路板设置), Nand Flash控制器会自动的把Nand Flash上的前4K数据搬移到4K内部RAM中,并把0x00000000设置内部RAM的起始地址,CPU从内部RAM的0x00000000位置开 始启动。这个过程不需要程序干涉。 程序员需要完成的工作,是把最核心的启动程序放在Nand Flash的前4K中。由于内部RAM只有4KB,所以我们必须要完成2440的核心配置(初始化SDRAM(程序最终需要拷贝到SDRAM中去运行)和 将NAND flash中的剩余的程序拷贝到SDRAM中去,然后跳转到SDRAM中执行)。
总结
CPU的直接取址方式和flash是否可以随机访问(独立的地址线和数据线)决定了flash是否可以运行程序(NAND FLASH不是直接取址的需要控制器和自己的时序,SDRAM和NOR FLASH是随机访问存储,可以直接让CPU取址并执行)