一个应用软件工程师对嵌入式系统的理解

Published: by Creative Commons Licence

  • Categories:

嵌入式系统

嵌入式系统有很多种类型, 但在消费电子领域,最常用的就是基于ARM芯片的嵌入式系统,最常见的嵌入式设备就是智能手机了。运行在智能手机中的操作系统,不管是Android还是iOS都属于嵌入式操作系统,一般来讲,只要是运行在ARM平台上的操作系统都可以称之为嵌入式操作系统,主要是为了和运行在x86/amd64等平台上的操作系统区分开。本质上,运行在ARM平台上的操作系统和运行在x86平台上的操作系统并无本质的区别。

嵌入式软件工程师

同样,嵌入式软件工程师也有很多类型, 但在消费电子领域,最常见的就是在ARM平台上做开发的嵌入式软件工程师,目前我们公司(就我所知)的所有嵌入式软件工程师都是在ARM平台下做开发的。嵌入式软件工程师大体上有以下几种类型,嵌入式驱动开发工程师,他们经常和各种硬件打交道,看得懂硬件的说明文档,懂得什么叫IO口,什么叫控制口,然后主要用C语言(大牛用ASM?)编写和硬件打交道的软件代码;嵌入式系统移植工程师,他们非常清楚linux系统从上电到完整启动的各个方面,因为他们的主要工作就是要让系统在不同的硬件平台上跑起来,他们非常清楚Bootloader&BSP,操作系统内核的构成,硬件驱动的原理及作用机制,ramfs 和 rootfs 的区别及作用,高级一点的,要会写bootloader&bsp,写裸机代码,写驱动,精通linux内核的开发、配置、编译及打patch,熟悉rootfs的制作及移植。由于这些知识的更新都是相对较慢的,因此你有可能可以跟得上它们发展的脚步,但事实上依旧非常难,主要是因为量太大。

另一方面,由于在实际开发中,很多时候你并不需要从头开始开发“新东西”,因此,嵌入式软件工程师特别重要的一个能力就是读C代码的能力(Java/C#/js 软件开发工程师不要笑,进入C的世界,没有各种漂亮的IDE帮你导航,只有有限的导航,这对阅读代码产生了非常大的挑战),读原厂的bootloader&bsp的代码,读linux内核的代码,读各种patch代码,等等等等……

虽然嵌入式的开发需要对Linux的内核及文件系统非常了解,但并不是所有研究Linux内核的人都属于嵌入式软件工程师,嵌入式主要研究Linux内核中和硬件打交道的那层,而应用软件工程师主要研究Linux内核中的架构无关的部分,比如namespaces,cgroups,kvm等。

ARM & Linux

之前提到,ARM芯片和x86芯片并无本质的区别,那为什么嵌入式软件开发经常和ARM,linux联系在一起,而没有x86,windows(请暂时忽略windows的嵌入式版本)什么事呢?最主要的原因是x86平台早已被标准化,其中最重要的是硬件对外的接口已经标准化,因此,操作系统就比较容易适配不同厂家生产的硬件,由于目前使用最广的操作系统windows和linux都早已支持x86,因此只要你的硬件支持x86,那么你就可以直接安装这些操作系统,不需要复杂的系统移植。而ARM就不同了,ARM至今也没有完全标准化, 目前它支持高度的定制化,比如定制运行在火星车上的ARM系统和运行在手机上的系统就很不一样,正因为这样,才会出现大量的不同的基于ARM的系统,它们的外在表现可能非常不一样。由于linux本身也拥有高度的可定制性,因此,ARM和linux就不谋而合了。所以你现在看到的各种嵌入式设备大多是基于ARM+Linux开发的。

嵌入式系统镜像

嵌入式软件工程师的最终产出一般是一个嵌入式Linux系统镜像,比如手机的ROM。它至少包含Bootloader,定制的 Linux Kernel,DTB,rootfs,通常也包含Device Driver,Device Firmware,initramfs,recovery等。嵌入式系统开发的主要工作就是围绕着这些内容展开的。有些部分只需要做些移植就可以,有些部分是需要自己开发的。一般情况下Bootloader,Linux Kernel只需要移植即可,不会有很大的改动,DTB,Device Driver,Device Firmware 这些,设备厂家一般会提供,不需要做修改,rootfs,initramfs这些可以自己制作,但一般情况下也是在原有的基础上进行裁剪的,比如网上就有很多的所谓精简版的ROM,其实大多只是修改了rootfs。

现在很多ARM平台已经支持UEFI,或者应该反过来说,UEFI的标准制定时已经有考虑ARM平台了,UEFI一般被用于让一个平台标准化,如果以后ARM想要在服务器市场上推广,那UEFI就是非常有必要的了。如果手机也都支持UEFI,那么我们就可以像安装PC操作系统那样给手机安装各种不同的操作系统了。

小白福利:

  • Bootloader: 用于引导操作系统内核的程序,常见的为uboot。
  • Linux Kernel: linux操作系统内核,一般为vmlinux,Image等文件。
  • DTB: device tree blob,设备树。
  • Device Driver: 设备驱动,运行在操作系统之上的用于控制硬件的驱动程序。
  • Device Firmware: 设备固件,运行在相应硬件上的程序。
  • initramfs: 内存文件系统,被内核装载并且用于引导rootfs的cpio压缩包。
  • rootfs: 用于存放用户态应用程序及数据的磁盘分区。
  • recovery: 特定嵌入式系统才会有,并不是技术层面的东西,而是业务层面的东西,用于系统的还原及升级。

嵌入式系统的开发流程

介绍了嵌入式系统的镜像之后,再来梳理嵌入式系统的开发流程就比较简单了:

  1. 确定目标板卡;

  2. 搭建针对目标板卡的开发环境;

    由于嵌入式系统软件是运行在ARM平台下的,而我们的开发机一般是x86/amd64平台的,因此需要交叉编译,用来在x86平台下编译生成可以运行在ARM平台下的程序。网上搜索ARM开发工具链即可找到相关交叉编译工具链,也可以去ARM官网上下载。有没有觉得很神奇,如果是的话,那就去学习编译原理的知识吧,学过之后你会更加理解编译器的作用。

    另外,还有一点你可能会有疑问,如何调试程序?因为交叉编译生成的代码是运行在ARM平台下的,那我如何调试呢?确实有办法可以进行双击联调,也就是把程序放到目标板卡上进行调试,但更常用的方法是使用模拟器,也就是强大的qemu套件。他甚至可以让你在x86平台上调试ARM版本的linux内核。除此之外,它还是一个非常通用的基于KVM技术实现的虚拟机,和virtualbox/vmware类似。探索吧,少年!

  3. 移植bootloader;

    一般情况下你选用的板卡厂家会提供bootloader,你不需要做什么事情,顶多是学习一下厂家提供的bootloader如何使用,从而进行一些配置而已。

  4. 移植kernel;

    linux kernel原生已经提供了大量的对不同板卡的支持,你只需要去官网下载后按需进行配置、编译即可。另外,有些厂家会对kernel进行定制,并且会开放出来给你使用,这个时候你就需要使用厂家提供的kernel而不是从linux官网下载的kernel了。

    在这个过程中,你至少需要对kernel比较熟悉,要达到这一步,你必须能读懂Makefile文件,而且要了解Makefile的一些高级用法。

    这里需要注意,虽然kernel是被bootloader引导的,但不是说bootloader可以“直接引导”kernel,有时候需要对kernel做些处理才行,具体要根据不同的bootloader来确定。

  5. (可选)制作initramfs;

    initramfs 可以理解为一个精简的 rootfs,除此之外他和rootfs并无本质的区别。

  6. 制作rootfs;

    这个根据产品需求,可以是Andorid系统,chromium系统,ubuntu系统等。这些是理论上的,具体操作起来它们可能会对kernel有一些依赖。

  7. 驱动开发;

    驱动可以是嵌入到kernel中的,也可以是放到磁盘上的,根据不同的需求做决定。

  8. (可选)将Firmware放到相应位置;

    Firmware 一般是由厂家提供的,把他放到合适的位置……这个位置可以咨询厂家,也可以从kernel中找到。