【EmbeddedLinuxPrimer翻译】第二章:你的第一次嵌入式体验(一)

2019-07-13 07:16发布

第二章:你的第一次嵌入式体验
本章包含以下子章节:
  • 是否选择嵌入式系统?
  • 嵌入式系统剖析
  • 存储考虑
  • 嵌入式Linux发行版
  • 本章小结
通常,理解一项工作任务最好的途径是把握大局,对于嵌入式系统的初学者来说,一些基础概念是一个很大的挑战。本章给读者展示了一个典型的嵌入式系统以及其开发环境,尤其注重概念和各组成部分的介绍,往往正是由于这些组成部分,使得开发嵌入式系统的经历更加特别、更具挑战性。
  • 2.1 是否选择嵌入式系统?
嵌入式系统有一些关键属性,比如台式电脑系统我们一般不会叫它嵌入式系统,但是会考虑将一台台式电脑的硬件平台放在远程数据中心中用来执行关键监控和报警任务。假设这个数据中心通常是无人操作自动运行的,这就对该硬件平台有不同的要求,例如,如果电源中断后又恢复,我们希望系统能自动恢复任务而无需人工干预。
嵌入式系统会有各种各样的形状和尺寸,从最大的多机架数据存储系统或网络系统,到MP3播放器或手机系统,都属于嵌入式系统。归纳下来,嵌入式系统通常具有下列特征:
    • 包含一个处理引擎,例如通用微处理器
    • 通常为专用的应用程序或目的而设计
    • 包含一个简单的用户界面(也可以没有),例如汽车发动机点火控制器
    • 一般都配置很有限的系统资源,比如很小的内存或者没有硬盘
    • 可能有电源限制,比如系统是由电池供电
    • 通常不作为通用计算机平台
    • 通常预装好应用程序,而不是由用户选择
    • 上市之前已经集成好所有的硬件和软件模块
    • 应用程序自动执行,一般无需人工干预
一般来说,比起台式电脑,嵌入式系统资源相对有限,嵌入式系统一般内存都不大,硬盘也很小,甚至没有,有时都不连接网络,唯一的用户界面是一个串口和一些LED灯。这些特征都为嵌入式系统的开发者带来了挑战。
  • 2.2 嵌入式系统剖析
图2-1展示了一种典型嵌入式系统的结构图,这是一种很简单的高级硬件架构示例,一些无线接入设备(WAP)采用的就是这种架构,它以一颗32位的精简指令集CPU为中心,Flash存储器用来存储非易失性的程序和数据;主存储器用的是同步动态随机访问存储器(synchronous dynamic random-access memory,简称SDRAM),存储空间大小从几兆到几百兆不等,依赖于需要存放的应用程序的大小;实时时钟模块,通过电池来保持时间同步;图中还列举了以太网和USB接口,以及通过串口线连接的串口终端;802.11芯片组用来实现无线调试解调功能。
图2-1 嵌入式系统示例

在嵌入式系统中,处理器行使的功能要比传统意义上的CPU多,图2-1范例中的处理器包含了一个集成的UART模块用来处理串行数据,还有集成的USB控制器和以太网控制器,很多处理器都包含集成的外设控制器模块,第三章 处理器基础一章中会通过范例介绍这种集成性的处理器。
  • 2.2.1 典型的嵌入式Linux开发环境
摆在初学者面前的第一个问题往往是我第一步要做什么?为了回答这个问题,我们先看一个典型的嵌入式Linux开发环境是怎么样的。(参看图2-2)
图2-2 嵌入式Linux开发环境

这是一种很常见的布置。运行Linux(可以是你喜欢的任意发行版,如Red Hat、SuSE或Debian Linux)的开发主机(host development system),通过串口与嵌入式Linux开发板(又称目标板,target board)相连,我们把目标板的以太网口与一台本地的以太网集线器或交换机相连,这样的话开发主机还可以通过网口登录到目标板上。开发主机中有开发工具和实用工具集,可以从嵌入式Linux发行版中获得。
在本例中,开发主机通过串口与目标板连接,图2-2中的串口终端程序(Serial terminal)用来与目标板通信。最常用的串口终端程序是minicom,基本上所有的桌面Linux发行版中都可找到该程序包。
  • 2.2.2 开始目标板的工作
目标板电源上电后,CPU首先运行bootloader程序,执行低级的硬件初始化操作,包括处理器和内存的配置,UART控制器的初始化,以及以太网控制器的初始化。清单2-1列出了目标板加电后串口接收到的字符信息,在本例中,我们选用的是AMCC公司生产PowerPC 440EP评估板,它有一个很优雅的名字,叫“优山美地”(Yosemite)。这个板子总的来说是包含AMCC 440EP嵌入式处理器的一个参考设计板,预装的bootloader程序是U-Boot。
清单1-1 Bootloader初始串口输出 U-Boot 1.1.4 (Mar 18 2006 - 20:36:11) AMCC PowerPC 440EP Rev. B Board: Yosemite - AMCC PPC440EP Evaluation Board VCO: 1066 MHz CPU: 533 MHz PLB: 133 MHz OPB: 66 MHz EPB: 66 MHz PCI: 66 MHz I2C: ready DRAM: 256 MB FLASH: 64 MB PCI: Bus Dev VenId DevId Class Int In: serial Out: serial Err: serial Net: ppc_4xx_eth0, ppc_4xx_eth1 => “优山美地”加电后,U-Boot程序执行低级的硬件初始化,其中就包括了配置串口,并打印出了一行信息(参考清单2-1):U-Boot 1.1.4 (Mar 18 2006 - 20:36:11),紧接着是打印出处理器的名称和版本信息,再然后是版型信息。这些信息只是该开发板的通用信息,是在U-Boot源代码中由开发人员硬编码进去的字符串。(译者注:要想打印这些信息,U-Boot要支持该目标板,可以是U-Boot官方本身的支持,也可以是第三方的支持)
下面的信息则是针对该目标板特有的一些信息:包括内部时钟的配置(注意,要在串口输出任何信息之前配置好时钟,否则串口无法正常工作),完成后,U-Boot程序根据各硬件子系统的静态配置逐个配下去并对配置结果信息进行打印。这里我们可以看到I2C总线,DRAM,FLASH,PCI总线和网络子系统的配置结果信息。最后,U-Boot停在命令提示符“=>”,等待用户通过串口输入命令。
  • 2.2.3 启动Linux kernel
现在U-Boot程序已经完成了硬件、串口和以太网口的初始化,在它短暂却非常重要的生命周期中(译者注:U-Boot的生命周期是指,从目标板加电CPU开始执行Bootloader,到Bootloader执行结束CPU的指令地址跳转到Linux Kernel的这一过程,在该过程中,我们称为U-Boot接管对处理器的控制权),只剩下最后一项工作:加载并启动Linux kernel。所有的bootloader程序都有一个命令来加载并运行操作系统image,清单2-2列出了在U-Boot下手动启动Linux kernel的过程。 清单2-2 加载Linux kernel => tftpboot 200000 uImage-440ep ENET Speed is 100 Mbps - FULL duplex connection Using ppc_4xx_eth0 device TFTP from server 192.168.1.10; our IP address is 192.168.1.139 Filename 'uImage-amcc'. Load address: 0x200000 Loading: #################################################### ###################################### done Bytes transferred = 962773 (eb0d5 hex) => bootm 200000 ## Booting image at 00200000 ... Image Name: Linux-2.6.13 Image Type: PowerPC Linux Kernel Image (gzip compressed) Data Size: 962709 Bytes = 940.1 kB Load Address: 00000000 Entry Point: 00000000 Verifying Checksum ... OK Uncompressing Kernel Image ... OK Linux version 2.6.13 (chris@junior) (gcc version 4.0.0 (DENX ELDK 4.0 4.0.0)) #2 Thu Feb 16 19:30:13 EST 2006 AMCC PowerPC 440EP Yosemite Platform ... < Lots of Linux kernel boot messages, removed for clarity > ... amcc login: <<< This is a Linux kernel console command prompt
tftpboot命令告诉U-Boot通过tftp协议(注2)从远端网络中把kernel image uImage-440ep拷贝到内存的0x200000地址处,在本例中,kernel image放置在开发工作站中(通常是用开发主机当工作站),tftpboot命令传递了的地址是目标板内存中加载kernel image的物理地址。现在可以不用了解太多U-Boot的细节,后面我们有专门的一章介绍:第七章 Bootloaders
    • 注2:tftp服务器以及其他一些服务器的配置我们会在第十二章中讲:第十二章 嵌入式开发环境
bootm命令(即从内存中启动image的意思:boot from memory image)的参数是是一个地址,该命令告诉U-Boot从bootm指定的地址处启动我们刚刚加载的kernel image,这条命令把处理器的控制权交给了Linux kernel,假设你的kernel已经正确配置了,那么这条命令的执行结果是目标板进到Linux系统的命令提示符下,即用户登录提示符。
注意bootm命令执行完后就意味着U-Boot完成了使命,结束了生命周期,这是一个很重要的概念,这一点和台式电脑的BIOS有很大不同,大部分嵌入式系统都是按照这种架构设计,当Linux kernel开始启动时,bootloader程序就已经完成退出了。Linux kernel会把先前bootloader占用的内存和系统资源全部回收并重新使用,只有重启系统才你能再次执行bootloader。
最后,还有一点值得注意。清单2-2列出的串口输出信息中,从第一行到
Uncompressing Kernel Image ... OK这一行(包含这一行),都是U-Boot程序的输出,剩下的启动信息则是由Linux kernel打印的,这一行刚好是U-Boot退出、Linux kernel开始执行的分界线,后面的章节我们还会详细介绍。
  • 2.2.4 Kernel初始化概览
Linux kernel在启动过程中会打印出大量的状态信息,在我们讨论的范例中,系统在进到登录提示符之前打印了超过100行的的信息。(在上面的清单中,我们省略了中间的打印,以便更清晰的显示出我们要讨论的内容),清单2-3列出了进入到登录提示符之前的几行信息。注意,我们现在讲这些并不是为了深入到kernel初始化的细节中去(这些内容在第五章 Kernel初始化中详细讲解),我们只是为了从全局角度来了解嵌入式系统在Linux kernel启动过程中都发生了什么,以及那些模块是必需的。 清单2-3 Linux最后的几行启动信息 ... Looking up port of RPC 100003/2 on 192.168.0.9 Looking up port of RPC 100005/1 on 192.168.0.9 VFS: Mounted root (nfs filesystem). Freeing init memory: 232K INIT: version 2.78 booting ... coyote login: 在我们的串口终端上,进入到登录提示符之前,系统先执行挂载root文件系统(rootfs)的操作,在清单2-3中,Linux系统通过以太网将一台IP地址是192.168.0.9的NFS服务器(注3)挂载到本地,通常,这台服务器就是你的开发工作站,挂载到本地的目录中(也就是rootfs中)包含在开发工作站上交叉编译出来的应用程序,以及其他一些系统库,实用程序等,从而构成一个完整的GNU/Linux系统。
    • 注3:我们将在第十二章讲解NFS服务器和其他所需的服务器
这里有一点一定要明确,就是:Linux系统需要有文件系统的存在。很多老的嵌入式系统不需要文件系统,开发人员如果先前用的是老的嵌入式系统的话,刚转到Linux系统时往往都很意外。文件系统是指:在硬盘或其他存储介质中,一些预先准备好的目录和文件集按照特定的次序排列,然后kernel将其挂载系统中作为root文件系统。
Linux还可以从其他设备挂载root文件系统,最常见的就是将硬盘的某个分区挂载成root文件系统(译者注:事实上很多嵌入式设备并没有硬盘,那么这里讲的也可以扩展为将本地存储介质,如Nand Flash的某个分区挂载成root文件系统),事实上读者的台式电脑上运行的Linux就是这么做的。尽管当目标板远离开发主机所在的网络环境时NFS就不可用了,但是对于开发人员来讲,通过NFS挂载文件系统的方式会更加强大和灵活,后面章节的叙述读者会更加深刻地体会到这一点。
  • 2.2.5 第一个用户空间进程:init
在进行下面的学习之前,我们先看清单2-3中的这一行信息:
INIT: version 2.78 booting.
系统运行到上面这行文字之前,kernel本身都在执行代码,在“kernel上下文环境”中执行一步步的初始化操作,这个过程中,kernel拥有所有的系统内存,对所有的系统资源都全权管控,并且可以访问所有的物理内存和I/O子系统。(译者注:这个阶段,系统所有的软硬件资源都由kernel直接管理)
当Linux kernel执行完内部的初始化,并已挂载root文件系统后,默认会开启一个用户程序:init,kernel开启init进程后就切换到用户空间了。在这种操作模式下,用户空间进程只会访问受限的系统资源,如果用户空间的进程需要使用kernel提供的服务,例如设备和文件的I/O操作,必须通过系统调用实现。相对于kernel直接访问物理内存,用户空间的进程或程序运行在一块虚拟内存空间中,这块内存由kernel随机分配(注4)和管理。在处理器中专门的内存管理模块的支持下,kernel为用户空间进程维护了虚拟内存到物理内存的地址转换。这种架构最大的好处就在于,单一进程出错不会影响到其他进程的内存空间。而一些旧的嵌入式系统不具备这样的内存管理架构的话,就会存在致命的缺陷,一旦出现内存管理的bug几乎很难定位。
    • 注4:事实上并不是随机分配,我们权且先这样认为,后续章节我们再深入讨论。
不要被这些陌生的概念吓倒,本章节只是为了让读者在深入学习本书之前先对嵌入式系统有个宏观上的了解,后续章节会逐个对其进行深入介绍。  
(未完待续)