当一个微处理器最初启动时,它首先执行在一个预定地址处的指令.通常这个位置是只读内存,其中存放着系统初始化或引导程序.在PC中,它就是BIOS.这些程序要执行低级的CPU初始化并配置其他硬件.BIOS接着判断出哪一个磁盘包含有操作系统,再把OS拷贝到RAM中,并把控制权交给OS.实际上,整个过程远非这么简单,不过对我们的理解已经足够.运行在PC上的Linux系统依赖于该PC的BIOS来提供这些配置和OS加载功能.
在一个嵌入式系统里,常常没有上述的BIOS;这样,你就需要去提供等价的启动代码.还好,一个嵌入式系统的BIOS并不需要像PC BIOS引导程序那样有那么多的灵活性,因为它通常仅需处理一种硬件配置方案.这些代码比较简单但也另人厌烦.它是一些把特定的数写入指定硬件寄存器的指令序列.不过这是很关键的代码,因为这些数值必须要符合你的硬件并且要按特定顺序来完成.在大多数情况下,(这些代码中)有一个最小化的加电自检模块用以检查内存,让一些LED闪现一下,也可能探测一些其它让Linux OS启动和运行的必要硬件.这些启动代码是高度硬件专用性的,因而,不具移植性.
有幸的是,大多数系统为核心微处理器和内存使用了食谱式的硬件设计.典型地,芯片制造商有一个可供设计参考的演示板--新设计多少可以从中直接拷贝一些.对这些食谱式的设计,经常有现成的启动代码可用,而且可以很容易地被修改以适应你的需要.很少(会遇到)有需要从头开始编写的启动代码.
为了测试你的(启动)代码,你可以使用一个包含它自己的仿真内存的电子仿真器(in-circuit emulator),这里的仿真内存用以替换目标内存.你把待测代码加载到仿真器中并通过仿真器调试它.如果没有可用的仿真器,也可以跳过这一步,不过需要一个较长的调试周期.
这些代码最终要从非易失性存储器中运行,通常是用闪存或EPROM芯片,你要想办法把这些代码放进前述芯片中,放入的具体方法依赖于"目标"硬件和工具.一个常见的方法是把闪存或EPROM芯片插入到一个EPROM或闪存"烧炉"中.这种方法将把你的程序"烧入"(存入)芯片中.然后,把芯片插入到你目标板上的一个插槽中,打开电源.这种方法要求在板子上具有插槽化(socketed)部分;然而,有些设备包格式(结构)不允许被插槽化(socketed).
另一种办法是通过一个JTAG接口.一些芯片包含一个JTAG接口,从而可以对芯片编程.这是一种最简便的办法.芯片可以被永久地焊接到板子上.一段电缆从板子上的JTAG连接器(通常是一个PC卡)连接着一个JTAG接口.接下来要求在操纵JTAG接口的PC上做一些用户定制性编程.在仅需较少运行量的产品中,也可以使用这种方法.
健壮性---比政客的承诺还可靠
这是显而易见的,作为一种选择,Linux已被普遍地认为能够在PC平台上可靠地,稳定地运行。嵌入式内核自身有多稳定?对于大多数微处理器来说,Linux是很好用的。将Linux移植到新的微处理器体系也是非常迅捷的。一般是将其移植到一种新型的目标板,这种新型的目标板包含有独特的外设,当然还有CPU.
幸运的是,大部分的内核代码都是相同的,因为它们与微处理器无关,所以,移植的工作都集中在那些不同的部分,通常是一些存储器管理及中断处理程序。一旦完成,它们往往是非常稳定。如同前面谈到的,引导的过程非常依赖于硬件的变化而变化,所以,一定要周密地计划一番。
设备驱动程序虽然变化多端,但其中一些已相当稳定。同时,你的选择也不算太多,一旦离开PC平台,那就只有自己去写了。所幸的是,在我们周围已有许多既有的设备驱动程序,你总能找到一个近似的,而去修改它。驱动程序的接口是明确定义的。大多数驱动程序之间都是相似的,所以,移植一个磁盘,网络,串行口驱动程序,从一个设备到另一种,经常不是太难。我发现大多数的驱动程序都写的很好,并很好理解,而难题反而是在我手上的那本讲解内核结构的书。
就我个人的经验而言,Linux与我曾经使用过的那些顶顶大名的商业操作系统一样稳定。总的来说,关于这些操作系统(包括Linux)的问题都源于对系统工作策略的误解,而不是纯代码bug或基本设计错误。大量的操作系统的bug故事,在这里没有必要再重提。而Linux的优点,就在于其源码是公开的,并有很好的注释和完整的文档说明,从而,你也就拥有了控制与解决一切问题的能力基础。