Android 嵌入式编程

978-7-115-49380-4
作者: [美] 罗杰·叶(Roger Ye)
译者: 师蓉
编辑: 吴晋瑜
分类: Android

图书目录:

详情

本书展示了如何使用 C 来创建硬件接口与受欢迎的启动引导 Linux 内核程序。引导读者完成使用文件系统映像,学习打造自定义 Rom,支持任何新的安卓设备。读者可以建立一个完整的虚拟化的环境程序串行端口、获得用于移植 U-boot 到新环境的深入知识。

图书摘要

版权信息

书名:Android 嵌入式编程

ISBN:978-7-115-49380-4

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

著    [美] 罗杰•叶(Roger Ye)

译    师 蓉

责任编辑 吴晋瑜

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315


Authorized translation from the English language edition, entitled EMBEDDED PROGRAMMING WITH ANDROID: BRINGING UP AN ANDROID SYSTEMFROM SCRATCH,1E,byYE, ROGER, published by Pearson Education,Inc.,Copyright ©2016.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD., and POSTS & TELECOMMUNICATIONS PRESS Copyright©2018.

本书中文简体版由Pearson Education,Inc授权人民邮电出版社出版。未经出版者书面许可,不得以任何方式或任何手段复制和抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。


本书主要介绍Android嵌入式系统编程的相关内容,通过讲解裸机编程、启动加载程序、构建Android系统等知识点,旨在帮助读者夯实编程基础,掌握Android嵌入式系统的开发、编译及调试技巧。

本书包括三部分内容。第一部分重点介绍裸机编程,主要介绍底层开发和Android系统编程的基本原理,涵盖使用U-Boot启动Linux内核所必需的硬件接口,裸机编程环境中串口的硬件接口编程、实时时钟、NAND闪存控制器等内容。第二部分介绍将U-Boot移植到Goldfish平台的方法。第三部分则完成使用虚拟设备为Android设备构建定制的ROM的实现。本书适合有一定经验的从事Android系统开发的开发人员参考,也适合想要探索Android底层开发知识的计算机专业学生阅读。


计算机正变得越来越普及。计算设备逐渐从传统的台式计算机发展到平板电脑和移动设备上。与传统的大型计算机和台式计算机相比,嵌入式计算机在新的平台上发挥着越来越重要的作用。嵌入式系统编程在不同的使用场景中显得非常不同。在某些情况下,它由使用汇编语言和C语言直接在硬件上编程的应用程序组成。在其他情况下,它发生在实时操作系统(RTOS)上。在复杂情况下,它可以使用现代操作系统(例如Linux或Windows)的桌面系统。

由于可能存在多种不同的使用场景和硬件架构,因此在学校或者大学中用标准的教学方式讲授嵌入式编程是非常困难的——存在太多基于大量不同架构的硬件平台。处理器或者微处理器可以是简单的8位模式,也可以是复杂的32位甚至是64位设备。但在大多数情况下,学生会根据主板使用某个公司的编译器和调试器学习在专用硬件上嵌入式编程。显然,这种开发环境是独特的,是很难复制的。为了克服这些挑战,本书使用虚拟技术和开源工具来提供任何程序员都可以轻易从互联网上获取的开发环境。

如果读者想学习嵌入式系统编程,尤其是Android嵌入式系统编程,一定要阅读本书。首先,本书包含许多可供读者尝试的示例,可供初学者从中获得一些实际经验。其次,读者不需要担心硬件平台或开发工具——书中所有示例都是在从互联网下载的开源工具的基础上构建的,并且这些示例都可以在Android模拟器上进行测试。本书的源代码托管在GitHub上。我们在附录A中介绍了配置编译环境的方法,并解释了使用GitHub上源代码的方法。

注意

Git是很多开源项目使用的版本控制工具。如果你还没有听说过它,可以在互联网上搜索“git”或“GitHub”查找相应的使用教程。

GitHub是互联网上的免费git仓库,可以用于托管开源项目。

如果你刚开始作为嵌入式系统软件工程师的职业生涯,所做的第一个项目有可能就是将U-Boot移植到新硬件系统中。本书将详细介绍将U-Boot移植到Android模拟器上的方法。

如果你是有经验的软件开发人员,你可能知道很难在自己的项目中调试复杂的设备驱动程序。我们在本书中将探索一种将硬件接口调试与设备驱动程序开发分开的方式,阐释在裸机环境中调试串口、中断控制器、定时器、实时时钟和NAND闪存等的方法。然后,我们将阐释如何将这些示例与U-Boot驱动程序整合起来。相同的方法还可以用于Linux或Windows驱动程序开发。

要想让本书物尽其用,读者必须熟悉C语言、基本的操作系统概念和ARM汇编语言。本书适合想要探索底层开发知识的计算机科学专业毕业生或者经验丰富的开发人员阅读,也可供想要从事Android系统开发的专业人士参考。

我们在本书中会讨论嵌入式系统编程的全部内容——从基本的裸机编程到启动加载程序,再到Android系统的启动。本书的重点是向读者灌输普通的编程知识以及开发编译器和调试的技巧,旨在提供嵌入式系统编程基础知识,为读者提供一条进入嵌入式系统编程领域更好的路径。

本书是以一种面向过程的方式组织的。读者可以决定如何根据自身情况阅读本书,也就是说,以什么顺序阅读本书中的各个章节和探讨内容。

本书由三部分组成。第一部分重点介绍裸机编程。其中包含底层开发和Android系统编程的基本原理。第1章~第4章介绍与裸机编程相关的基础知识,包括使用汇编语言代码直接在硬件上运行程序的方法。第5章的重点将转移到C编程语言。第一部分的剩余章节将探讨使用U-Boot启动Linux内核所必需的最小硬件接口集。第5~8章的重点是在裸机编程环境中串口的硬件接口编程、中断控制器、实时时钟和NAND闪存控制器。

第二部分从第9章开始,介绍了将U-Boot移植到Goldfish平台的方法。正如我们将在第10章所介绍的,使用U-Boot可以启动Linux内核和Android系统。第5~8章完成的工作有助于U-Boot移植——将硬件复杂度从U-Boot中的驱动程序框架中分离出来。同样的技术也可以用于Linux驱动程序开发。在这一部分,我们还会使用Android SDK中的文件系统映像启动Android系统。要支持两种不同的启动过程(NOR和NAND闪存),我们必须从Android SDK中定制文件系统。由于这项工作发生在二进制级别,因此我们仅限于在文件级别执行定制,也就是说,我们不能修改任何文件的内容。定制文件系统的策略将在本书第三部分中介绍。

第三部分会从启动加载程序转移到内核,再到文件系统。我们使用虚拟设备演示如何为Android设备构建定制的ROM。我们会探讨支持新设备以及将启动加载程序和Linux内核整合到Android源代码树中的方式。在第11章中,我们将深入探讨Android模拟器的环境设置过程和标准构建过程。在第12章中,我们会为包含U-Boot和Linux内核整合的虚拟设备创建定制的ROM。在学完所有内容后,读者可以对Android系统开发人员在移动设备制造级别所做的工作有全面的了解。

各章节的主要内容如下。第一部分由第1~8章组成,主要介绍裸机编程。

第二部分由第9章和第10章组成,介绍了U-Boot移植和调试的过程。有了可工作的U-Boot映像后,我们就可以用它启动自己的Goldfish内核和Android映像。

第三部分探讨将U-Boot和Linux内核集成到Android开源项目(AOSP)和CyanogenMod源树中的方法。

本书给出了很多可用于测试各章内容的示例。我们建议读者在阅读本书时输入并运行这些示例代码。这样做可以为读者提供良好的实践经验和有价值的见解,以便读者能更好地理解各章所涵盖的主题。

对于第3~8章来说,目录结构是按章节来组织代码的。有些文件夹在所有示例中都很常见,例如包含include和驱动程序文件的那些文件夹。所有其他文件夹都是与章节相关的,例如c03、c04和c05,这些文件夹包含对应章节的示例代码。

常见的生成文件是位于顶层目录中的makedefs.arm。本书给出了每个示例的生成文件。示例代码的一个生成文件模板如下所示。PROJECTNAME被定义为一个示例代码的文件夹。这个生成文件模板可用于第3~8章的项目。

#
# The base directory relative to this folder
#
ROOT=../..
PROJECTNAME=
#
# Include the common make definitions.
#
include ${ROOT}/makedefs.arm
#
# The default rule, which causes the ${PROJECTNAME} example to be built.
#
all: ${COMPILER}
all: ${COMPILER}/${PROJECTNAME}.axf
#
# The rule to debug the target using Android emulator.
#
debug:
         @ddd --debugger arm-none-eabi-gdb ${COMPILER}/${PROJECTNAME}.axf &
         @emulator -verbose -show-kernel -netfast -avd hd2 -shell -qemu -monitor
telnet::6666,server -s -S -kernel ${COMPILER}/${PROJECTNAME}.axf
#
# The rule to clean out all the build products.
#
clean:
         @rm -rf ${COMPILER} ${wildcard *~}
#
# The rule to create the target directory.
#
${COMPILER}:
         @mkdir -p ${COMPILER}

#
# Rules for building the ${PROJECTNAME} example.
#

${COMPILER}/${PROJECTNAME}.axf: ${COMPILER}/${PROJECTNAME}.o
${COMPILER}/${PROJECTNAME}.axf: ${PROJECTNAME}.ld
SCATTERgcc_${PROJECTNAME}=${PROJECTNAME}.ld
ENTRY_${PROJECTNAME}=ResetISR
#
# Include the automatically generated dependency files.
#
ifneq (${MAKECMDGOALS},clean)
-include ${wildcard ${COMPILER}/*.d} __dummy__
endif

本书中的源代码可在GitHub上找到,要了解更多细节,请参阅附录A。


感谢培生技术出版集团的主编Laura Lewin和Bernard Goodwin,感谢他们给我在Addison-Wesley出版这本书的机会。感谢Addison-Wesley出版社团队的全体成员。感谢策划编辑Michael Thurston,他审查了所有章节并提出了关于内容介绍的宝贵意见;感谢Olivia Basegio和Michelle Housley帮助我与Addison-Wesley的团队协作;感谢执行编辑Elizabeth Ryan确保这个项目按照计划进行;感谢文字编辑Jill Hobbs为提高本书可读性所做的努力。

如果没有经过技术审查,本书就不可能出版。在此感谢所有指出本书错误并提供宝贵意见的审稿人。尤其要感谢Android专家、《Enterprise Android》和《Programming Android》的合著者:Zigurd Mednieks和G. Blake Meike。

还要感谢我所有的朋友以及摩托罗拉公司和埃莫森公司的所有同事。我们一起开发了很多促成过去十年来技术繁荣的伟大产品。我们还一起见证了改变了我们今天生活的高科技产品的推出。

最后,我要感谢我的爱人和女儿,谢谢她们在我写这本书时给我的支持和鼓励。


Roger Ye是一名对嵌入式系统及其相关技术有着极大兴趣的嵌入式系统程序员。他曾在摩托罗拉公司、埃莫森公司和英特尔公司担任技术经理。在摩托罗拉公司和埃莫森公司工作时,他参与了移动设备和电信基础设施的嵌入式系统项目。目前他是英特尔公司安全支持部门的技术经理,领导着开发Android应用程序的团队。


第1章 嵌入式系统编程简介

第2章 Android模拟器内里

第3章 设置开发环境

第4章 链接器脚本和内存映射

第5章 使用C语言

第6章 使用C库

第7章 异常处理和定时器

第8章 Goldfish平台中的NAND闪存支持


在阅读第一本教科书时,我以为它肯定是在告诉我这个世界的真相。很多年后的今天,回首过去,我才明白每本书只是从作者的角度讲述这个世界的真相。

同样的想法也适用于嵌入式系统编程。市面上有很多与嵌入式系统编程相关的书籍,每位作者都不可避免地从自己的角度分享经验。在本书中,我也从这个角度来介绍嵌入式系统编程。

嵌入式系统是为最终用户提供专用功能的计算设备或组件。它可以是一个大系统的一部分,也可以是一个独立的设备。我们生活中的很多应用程序和其他设备都被称为嵌入式系统。有些是我们每天都会直接使用的,例如DVD播放器、扫描仪、打印机、开关和路由器等。其他则是隐藏在大系统中的,例如基站、卫星、电梯控制装置、汽车发动机控制装置、医院设备控制装置和成像系统等。

嵌入式系统既可以是一个简单的设备,也可以是一个复杂的系统。它们包含低成本的设备和复杂的高成本系统。这些设备和组件可以使用任何满足设计目标的硬件架构。

显然,要为这个宽泛的主题提供完整的介绍是很难的。我们在本书中将通过一个典型的示例探索嵌入式系统的世界以及嵌入式系统编程的发展概况。

裸机编程意味着直接将代码写在硬件上,也就是说,在程序下面没有其他软件层。这种做法在对微处理器(MCU)编程时很常见。

很多书籍都侧重于嵌入式编程,却很少详细讲解裸机编程。然而,如果你在互联网上搜索裸机编程,就会发现很多关于这个主题的文章和讨论。很多书籍通常不讲解裸机编程的原因是这种编程严重依赖于硬件,因此很难做到让关于这个主题的书籍适用于所有读者,让他们都能从中受益。我们谈论裸机编程时,必然会涉及设计特定的硬件参考板。当然,并不是所有读者手里都有参考板。本书将用虚拟环境解决这个硬件依赖问题,具体来说,是用Android模拟器作为硬件参考板。

想要进行裸机编程的原因有很多,但最简单的系统中的硬件限制是主要动机之一。在最简单的嵌入式系统中,可能会使用微处理器。这种系统中的硬件资源可能非常有限,以至于它不能运行自己的操作系统。在这种情况下,直接在硬件上运行的小程序是唯一的选择。

我们有时也会在高级或者复杂的系统中进行裸机编程。在带有微处理器的复杂系统中,最终用户环境中可能会有操作系统。然而,对于研究实验室中的芯片级硬件验证来说,可能很难使用整个软件堆栈在操作系统中验证初始芯片。最简单的方法是直接在硬件上创建一个简单的环境,以便芯片设计师和验证团队可以专注于硬件验证本身。如果你在硬件参考板开发团队工作过,就会知道很多硬件模块的初始代码是由验证团队或者硬件设计师提供的。他们提供直接在硬件上运行的代码片段,而不是依赖于一个操作系统。实际上,操作系统的设备驱动程序开发人员在使用硬件规范作为参考的同时,可能会根据测试代码开发硬件驱动程序。

裸机编程主要是使用C语言和汇编语言编写的,因为这两种语言都可以在没有(或者只有最少的)运行时库支持的情况下使用。这意味着可以在内存的任何位置加载程序。我们可以通过执行复位向量运行程序,让硬件首先取指令。随后,添加C运行时库做一些简单但非常有用的事情,例如用printf函数提供错误消息。

为了让读者能跟紧我们的步伐,我们将使用汇编语言、C语言和你可以在计算机上下载并运行的虚拟硬件——即Android模拟器或Goldfish模拟器。我们将从头开始编写程序。最初,程序是以汇编语言编写的,但我们会尝试尽快将其转换成C语言。在这个过程中,我们将探讨硬件接口并直接在虚拟硬件上创建实验代码。稍后我们会在U-Boot移植中使用这个实验代码。

注意 

Goldfish虚拟硬件板是在Android模拟器中定义的虚拟硬件,我们将在第2章详细讨论它。

在完成系统构建后,我们就会尽可能地整合现有的技术,以减少用于构建最终系统的时间。我们会整合C运行时库的代码和重用硬件外设的代码,并逐步构建启动加载程序(U-Boot)、Goldfish内核和文件系统。在本书结束时,读者应该能对嵌入式系统是如何构建的以及开发环境是什么样的有一个清楚的了解。

本书侧重于动手实践。此外,读者可能需要自己探索与ARM架构、汇编语言或C语言以及Android系统相关的细节。一些有用的资源如下。

本书同时使用了裸机编程和嵌入式系统编程这两个术语。裸机编程是一种发生在硬件上的非常简单的编程,其中并未涉及很多资源管理活动——需要与硬件相关的资源,例如CPU、内存、中断和存储。相比较而言,嵌入式系统编程是一个比较广义的术语,它指的是在硬件上的任何编程——包括裸机编程和实时操作系统(RTOS)编程。实际上,裸机编程和RTOS编程之间并没有明确的区别,因此在某些情况下使用“嵌入式系统编程”可能是合理的。

当程序变得越来越复杂时,你往往会从裸机编程转向RTOS编程。简单的资源管理(例如中断处理和内存管理)会存在于裸机编程中。例如,在开始使用C库并调用malloc函数时,你就在管理内存分配了;在无限循环中添加多个任务来处理不同的函数时,你就在处理调度了;在让裸机编程变得像启动加载程序那样复杂时,你就在启用嵌入式操作系统了。

要学习嵌入式系统编程,你需要了解电子学、数字电子学、微控制器、汇编语言和C语言等知识。

了解了这些基础知识之后,你首先要做的是开始一个真正的项目并学习基本的开发过程。从这个真正的项目开始,你可以获得设置开发环境、构建和调试阶段的实践经验,因为它们都与硬件板有关。在做这个项目时,你一定会遇到各种各样的挑战。逐一解决这些挑战后,你的分析能力也一定会提高,并且可以将自己现有的硬件和软件知识联系起来。这是一个艰难且痛苦的循环过程,是你很难在课堂上学到的。

缩短这个过程的一种好方法是先在虚拟环境中实践。与在真正的硬件板上所做的相比,你可以在虚拟环境中更自由地使用硬件。此外,在纯软件环境中,通常有更好的方式来调试和分析目标,例如,在QEMU或Android模拟器的虚拟硬件板上更容易进行源代码调试。在这种环境中,你可以轻松地将调试过程从一层移到另一层,这在真实的硬件板上很难做到。我们在第9章用源代码调试来分析从U-Boot到Linux内核的启动过程时将会用到这种方法。

在通过本书的项目获得足够多的经验后,你就可以在真正的硬件板上开始一个项目了。你会发现,在真正的硬件板上编程和在虚拟硬件上编程有很多相似之处——但也会注意到两者之间的一些差异。在真实的项目中,你可能会发现硬件问题与软件问题是混在一起的。尤其是,在硬件板首个版本的初始启动过程中工作是非常困难的。硬件和软件工程师必须共同努力克服初始启动阶段的挑战。你还需要在真实的硬件环境中使用更多的调试和分析工具,而这些工具大多是针对某个硬件平台的。如果移动到其他项目时,还必须学习使用新工具。但是,使用的工具越多,你就会越有信心学习和使用新工具。理想情况下,你应该试着把自己在编程中遇到的问题或挑战分开来看,每次只需要处理其中的一两个问题,这样就可以简化复杂的问题,然后再一一攻克它们。

成功地完成一些项目后,你可能会开始进行嵌入式系统开发领域中更具挑战的任务。你想要探索的可能是操作系统(尤其是RTOS)知识,也可能是探索更复杂的硬件接口(例如USB或以太网)。这些分层软件堆栈是从硬件层开始的。例如,你可能需要先熟悉以太网协议、IP、TCP以及应用层协议,然后才能进行与网络接口相关的嵌入式项目。要应对这些挑战,除了要有直接从本书获得的知识,你可能还需要查阅很多操作系统和专有硬件接口的相关书籍。

简单的嵌入式系统架构如图1-1所示。

图1-1 简单的嵌入式系统架构

虽然不同嵌入式系统编程项目的范围和性质可以是多种多样的,但是用于攻克每个项目的基础知识是相似的。本书旨在通过基本知识和示例帮助读者攻克项目。

应用程序的类型决定着嵌入式系统的软件架构。嵌入式系统的软件架构可以像图1-1所示的基于微控制器的应用程序那样简单。这种类型的应用程序可以在温度计或者电烤箱这些设备中找到。

这类系统中只有一个软件层,即应用程序本身。应用程序直接控制硬件。它通常先初始化硬件,并运行一个执行专门功能的无限循环。例如,温度计应用程序可能会先初始化传感器,然后运行一个反复读取温度的无限循环。

更复杂的嵌入式系统可能包含通用计算机中所有的层,如图1-2所示。这种系统通常包含可以支持一个完整操作系统的处理器。该系统从用于初始化硬件外设并加载操作系统的启动加载程序开始。操作系统准备好后,在操作系统上运行的一些应用程序就会执行多个功能。典型的示例包括手机、GPS导航仪和基站。要注意的是,系统中软件堆栈上的层实际上依赖于特定的应用程序,也就是说,图1-2中的层是被高度泛化的。例如,系统可以用固件设计,也可以不用固件设计。固件可以是启动加载程序、BIOS或DSP的编码解码器。启动加载程序通常会在系统启动后消失。在某些特殊的情况下,固件可能会留在内存中,并在系统开始运行后提供一些运行时服务。例如,最新的基于统一的可扩展固件接口(Unified Extensible Firmware Interface,UEFI)的BIOS包含UEFI运行时服务——由BIOS而不是操作系统提供的一组服务。任何在带有UEFI BIOS的硬件上运行的操作系统都可以初始化UEFI运行时服务。

图1-2 带有完整软件堆栈的嵌入式系统

为了满足不同嵌入式应用程序的需求,厂商通常会提供不同的微处理器模型。我们可以以流行的ARM处理器为例,来看看如何使用不同的处理器系列来满足不同的要求。为了处理各种应用程序的动态需求,ARM架构已经演变成一组产品系列。目前,有4个系列的ARM处理器可供不同的应用程序选择。

SecurCore和FPGA核心通常可以设计为一个特定的硬件组件,并作为一个大型系统的一部分。这种处理器中的软件通常是固件,它依赖于硬件设计师(而不是软件编程人员)使用的编程语言,例如Verilog或HDL。

CORTEX-M系列包含微控制器。这种情况下的系统架构与图1-1所示的类似。软件应用程序通常直接在硬件上运行,并执行专有函数。这种应用程序软件又称为固件,因为它是在设备的只读存储器(ROM)中烧录的。应用程序被投放到市场中后,固件就不会改变了。

CORTEX-R或者CORTEX-A系列的系统架构与图1-2所示的类似。两者之间的区别在于可以支持的操作系统类型。CORTEX-R系列更适合实时操作系统,而CORTEX-A系列可以支持完整的操作系统。由于硬件限制,CORTEX-R系列无法支持完整操作系统,而CORTEX-A系列运行实时操作系统不会有任何问题。实时操作系统和完整的操作系统之间的区别是:在实时操作系统中,任务数量和内存使用是在系统启动前预先确定的。这使得实时操作系统中的调度程序比完整操作系统中的要简单得多。

注意 

Android模拟器模拟的ARM处理器是ARM926EJ-S,它是可以支持完整操作系统的入口点处理器。

本书第一部分将讨论嵌入式系统开发中使用的典型硬件外设的编程语言和硬件接口。系统架构如图1-3所示。第5章将讨论Goldfish平台支持的串口;第7章讨论中断处理、定时器和RTC;第8章将查看Goldfish平台中使用的NAND闪存编程。

图1-3 本书第一部分的系统示意图

本书第二部分从第9章开始,我们会将U-Boot移植到Goldfish平台。U-Boot支持本书第一部分支持的所有硬件外设。与其相关的系统示意图如图1-4所示。

图1-4 第9章的系统示意图

第10章将构建Goldfish Linux内核,并说明如何使用U-Boot和Goldfish内核启动Android才会有一个完整的系统。Android SDK中原始的Android文件系统用于说明最终系统。本书的Android系统示意图如图1-5所示。

图1-5 本书的Android系统示意图

本书第三部分会继续使用图1-5所示的系统示意图。不同之处在于要从头开始构建包括Android文件系统在内的所有内容(例如Android框架、运行时和系统应用程序)。

GNU工具链越来越多地用于嵌入式系统开发中。编程社区做出了很多贡献,使得GUN工具链可以支持大多数硬件架构。它已经成为嵌入式系统开发人员的首选。

虚拟技术的成熟,让使用虚拟硬件代替真正的硬件参考板进行嵌入式编程成为可能。从教育的角度来看,学习在虚拟硬件上使用GNU工具链进行嵌入式编程是非常有价值的。使用这种方法可以显著缩小时间和空间的限制。与众多虚拟技术相比,QEMU绝对是进行嵌入式系统编程的良好选择。它可以支持多种硬件架构和指令集,并可以支持很多硬件参考板的内置实现,包括TI、Freescale、Intel和其他厂商。

本书将使用QEMU模拟硬件参考板。然而,QEMU是可以模拟很多硬件板的通用模拟器,并且这些硬件板的成熟度可能是完全不同的。考虑到上述因素,Android模拟器将是更好的选择。Android模拟器是基于QEMU的。谷歌根据特定的QEMU版本进行了定制,并在Android SDK中包含了这些功能。其主要的修改包括虚拟硬件(Goldfish)支持,并增加了支持Android虚拟设备管理器的层。Android模拟器提供非常有用的显示支持、键盘和电源管理模拟功能。正如前面所提到的,它是由谷歌开发并在QEMU上运行的特殊虚拟硬件平台Goldfish构建的。虽然谷歌Goldfish平台没有正式的硬件规范,但我们可以通过研究Goldfish内核代码查找必要的硬件信息来探索其细节。

通过比较发现,QEMU更适合各种虚拟技术的开发。它更先进,并且被其用户或开发者社区快速修改。同时,Android模拟器适合作为开发Android应用程序或者学习编程的基础。这两种情况下,模拟器本身的可靠性都是主要的考虑因素。

虽然使用硬件模拟器学习嵌入式编程非常方便,但是你必须对真实的硬件平台和虚拟环境之间的区别有明确的认识。模拟器的优点是不必担心犯错时会损坏硬件,还可以轻松地进行源代码级调试。而在真实的硬件平台上,初始硬件启动时执行源代码级调试是非常有挑战性的。例如,JTAG调试器和Flashing工具必须用于初始调试工作。需要考虑的是,虚拟硬件永远不可能与真实硬件完全相同。某个硬件板的QEMU中可能会缺少某项功能,或者某些功能在虚拟版本和真实版本之间会有所不同。例如,QEMU支持ARM Versatile Express板,并且ARM Versatile Express板的大部分功能都对QEMU有效。然而,虚拟硬件和真实硬件之间仍然存在一些差异。在第9章中,我们将U-Boot移植到Goldfish平台时,会提到Versatile Express代码库。到时我们会更深入地探讨Versatile Express QEMU和真实Versatile Express板之间的区别。

幸运的是,通过使用Android模拟器,我们就不必担心虚拟硬件和真实硬件之间的区别。Goldfish平台是来自谷歌的纯虚拟硬件平台。硬件规范就是谷歌在Android模拟器源代码中所定义的。

本章概述了嵌入式系统编程,尤其是裸机编程。本章讨论了嵌入式系统应用程序的不同系统架构,并将它们与书中的不同章节一一对应起来,最后还简要介绍了本书中使用的开发工具和硬件平台。


本章将从硬件角度来探讨Android模拟器。Android模拟器是由Google开发的,用于让Android应用程序开发人员在不依赖于真实硬件的情况下测试Android应用程序。该模拟器建立在开源虚拟机QEMU上。QEMU是由开源社区开发的一个项目,可以支持多种硬件架构,包括ARM、x86和MIPS等。Android模拟器支持3种硬件架构——ARM、x86和MIPS。由于目前市场上大多数可用的移动设备都是为ARM架构设计的,因此我们在本书中选用基于ARM实例的Android模拟器。

在深入学习编程之前,我们先来了解本书中使用的虚拟硬件。由于我们没有关于真实硬件平台的硬件规范,因此必须研究Goldfish内核源代码来找出其细节。由于Android模拟器和Goldfish Linux内核都是开源项目,因此我们可以很容易地获取源代码并对它们进行研究。Android模拟器的用户界面如图2-1所示。

图2-1 Android模拟器的用户界面

如前所述,Android模拟器可以支持多种处理器架构,例如ARM、x86和MIPS等。我们将在本书中探讨ARM架构。Android模拟器支持的很多硬件功能都可以在移动设备上找到,具体如下。

图2-2所示的是Android模拟器硬件的简图。对于不同的硬件架构(例如x86或MIPS),除了CPU外,其所有硬件外设都相同。

图2-2 Goldfish平台硬件简图

注意 

这里讨论的Android平台包括Android的target版本和CPU架构。在图2-3中,我们创建了Android 4.0.3的AVD和一个ARM处理器。要为一个特定的Android版本创建AVD,用户必须使用Android SDK管理器下载该版本的SDK平台和系统映像。如果用户正在使用Linux,可以从命令行启动Android SDK管理器:

每个Android虚拟设备都可以视为带有特定软件和硬件配置的虚拟硬件设备。硬件配置会指定处理器架构(例如x86或ARM)和外设(例如内存、屏幕大小或相机)。软件配置会指定有(或没有)Google服务的Android版本。在本书第一部分和第二部分(第1~10章)中,我们会使用图2-3中定义的配置。在本书第三部分中,我们会用带有Android源代码的最新Android版本讨论U-Boot和Linux内核的整合。

要使用模拟器,必须先创建一个或者多个Android虚拟设备(AVD)配置。在每个配置中,我们指定了在模拟器中运行的Android平台以及想要使用的硬件选项集和模拟器皮肤。然后,登录模拟器时,我们会指定想要加载的AVD配置。

$ android sdk

每项AVD功能都可以作为一个独立设备——有自己的用户数据存储器和SD卡等。当启动带有AVD配置的模拟器时,它会自动从AVD目录中加载用户数据和SD卡数据。默认情况下,模拟器会存储用户数据、SD卡数据和AVD目录中的缓存。

要创建并管理AVD,我们可以使用AVD管理器用户界面(User Interface,UI)或者SDK中自带的Android工具,如图2-3所示。

图2-3 Android虚拟设备管理器

下载并安装Android SDK后,就可以使用下面的命令从命令行启动AVD管理器了。Android命令可以在“${SDK ROOT}/tools”文件夹中找到。

$ android avd

在图2-3中,我们创建了一个AVD名为hd2的配置。这个配置包括512MB RAM、4英寸(即10.16cm)的WVGA屏幕、200MB的内部闪存和100MB的SD卡存储器。

一个模拟器实例创建好后,我们就可以使用下面的命令启动该实例了:

emulator -avd <avd_name> [<options>]

由于本书的主题是嵌入式系统编程,因此我们对Android模拟器的硬件编程接口更感兴趣。下面将探讨一些会在本书中使用的硬件接口。在Linux内核源代码中定义的大多数Goldfish特定的硬件接口都在arch/arm/mach-goldfish/include/mach/hardware.h中。内存映射输入/输出(I/O)被Goldfish硬件外设使用,这种类型的I/O使用同一个地址空间来处理内存和I/O设备。I/O设备的内存和寄存器被映射到处理器地址空间。在Goldfish平台上,所有硬件接口都共享同一个基址,即0xff000000。虚拟地址的基址是0xfe000000。

注意 

只有本书第一部分和第二部分的分析需要用到内核源。我们会在第三部分探讨Android源代码树。我们将在本书中使用两个版本的Goldfish内核。用户可以检查android-Goldfish-2.6.29版本,来作为第一部分和第二部分的参考。在本书第三部分中,我们会使用android-Goldfish-3.4版本,因为我们将编译Android 4.4.x源树。本书中所用的源代码详细信息请参见附录B。

通过查看Goldfish内核的启动日志,我们可以快速得到支持Android模拟器硬件接口的概述。Goldfish硬件编程接口见表2-1。

表2-1 硬件寄存器和中断

硬件

C语言定义

基址偏移

中断

串口1

GOLDFISH_TTY_BASE

(0x2000)

4

定时器

GOLDFISH_TIMER_BASE

(0x3000)

3

音频

GOLDFISH_AUDIO_BASE

(0x4000)

15

Memlog

GOLDFISH_MEMLOG_BASE

(0x6000)

RTC

GOLDFISH_RTC_BASE

(0x10000)

10

串口2

GOLDFISH_TTY1_BASE

(0x11000)

11

串口3

GOLDFISH_TTY2_BASE

(0x12000)

12

以太网

GOLDFISH_smc91x_BASE

(0x13000)

13

帧缓存

GOLDFISH_FB_BASE

(0x14000)

14

事件

GOLDFISH_EVENTS_BASE

(0x16000)

18

NAND闪存

GOLDFISH_NAND_BASE

(0x17000)

管道

GOLDFISH_PIPE_BASE

(0x18000)

19

开关0

GOLDFISH_SWITCH0_BASE

(0x19000)

20

开关1

GOLDFISH_SWITCH1_BASE

(0x1a000)

21

后续几节提供串口和定时器接口的示例。我们将在本书中介绍移植到U-Boot和逻辑环境中的硬件接口。

Goldfish平台包含3个串口。其基址见表2-2。

表2-2 串行硬件

串行硬件

基址

中断

串口1

0xff002000

4

串口2

0xff011000

11

串口3

0xff012000

12

下面列出了5个可用于处理串口数据通信的32位寄存器。用户可以在drivers/char/goldfish_tty.c文件的Goldfish内核源中找到这些寄存器的定义。

定时器控制器的偏移量在0x3000位置。下面列出了arch/arm/mach-goldfish/include/mach/timer.h中定义的6个32位寄存器。时间由一个流动的64位计数器表示。

例2-1所示的是Goldfish内核(Android SDK附带的)启动日志。稍后调试启动过程时会涉及它。

例2-1 Goldfish内核启动日志

Uncompressing
Linux ...........................................................................
........................... done, booting the kernel.
goldfish_fb_get_pixel_format:167: display surface,pixel format:
  bits/pixel: 16
  bytes/pixel: 2
  depth:      16
  red:        bits=5 mask=0xf800 shift=11 max=0x1f
  green:      bits=6 mask=0x7e0 shift=5 max=0x3f
  blue:       bits=5 mask=0x1f shift=0 max=0x1f
  alpha:      bits=0 mask=0x0 shift=0 max=0x0
Initializing cgroup subsys cpu
Linux version 2.6.29-gea477bb (kroot@kennyroot.mtv.corp.google.com) (gcc version
4.6.x-google 20120106 (prerelease) (GCC) ) #1 Wed Sep 26 11:04:45 PDT 2012

可以看到,例2-1中的内核版本为2.6.29。这个版本是在Android 4.0.3 SDK中构建的。

CPU: ARMv7 Processor [410fc080] revision 0 (ARMv7), cr=10c5387f
CPU: VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
Machine: Goldfish
Memory policy: ECC disabled, Data cache writeback
Built 1 zonelists in Zone order, mobility grouping on. Total pages: 130048
Kernel command line: qemu.gles=1 qemu=1 console=ttyS0 android.qemud=ttyS1
androidboot.console=ttyS2 android.checkjni=1 ndns=1
Unknown boot option 'qemu.gles=1': ignoring
Unknown boot option 'android.qemud=ttyS1': ignoring
Unknown boot option 'androidboot.console=ttyS2': ignoring
Unknown boot option 'android.checkjni=1': ignoring
PID hash table entries: 2048 (order: 11, 8192 bytes)
Console: colour dummy device 80x30
Dentry cache hash table entries: 65536 (order: 6, 262144 bytes)
Inode-cache hash table entries: 32768 (order: 5, 131072 bytes)
Memory: 512MB = 512MB total
Memory: 515584KB available (2900K code, 707K data, 124K init)

内存大小被初始化为512MB,即图2-3中定义的大小。

Calibrating delay loop... 235.11 BogoMIPS (lpj=1175552)
Mount-cache hash table entries: 512
Initializing cgroup subsys debug
Initializing cgroup subsys cpuacct
Initializing cgroup subsys freezer
CPU: Testing write buffer coherency: ok
net_namespace: 936 bytes
NET: Registered protocol family 16
bio: create slab <bio-0> at 0
NET: Registered protocol family 2
IP route cache hash table entries: 16384 (order: 4, 65536 bytes)
TCP established hash table entries: 65536 (order: 7, 524288 bytes)
TCP bind hash table entries: 65536 (order: 6, 262144 bytes)
TCP: Hash tables configured (established 65536 bind 65536)
TCP reno registered
NET: Registered protocol family 1
checking if image is initramfs... it is
Freeing initrd memory: 176K

下面列出了由内核初始化的硬件接口以及对应的设备名、基址和中断号。例如,串口1是由设备goldfish_tty初始化的,其基址为0xff002000,中断号为4。

goldfish_new_pdev goldfish_interrupt_controller at ff000000 irq -1
goldfish_new_pdev goldfish_device_bus at ff001000 irq 1
goldfish_new_pdev goldfish_timer at ff003000 irq 3
goldfish_new_pdev goldfish_rtc at ff010000 irq 10
goldfish_new_pdev goldfish_tty at ff002000 irq 4
goldfish_new_pdev goldfish_tty at ff011000 irq 11
goldfish_new_pdev goldfish_tty at ff012000 irq 12
goldfish_new_pdev smc91x at ff013000 irq 13
goldfish_new_pdev goldfish_fb at ff014000 irq 14
goldfish_new_pdev goldfish_audio at ff004000 irq 15
goldfish_new_pdev goldfish_mmc at ff005000 irq 16
goldfish_new_pdev goldfish_memlog at ff006000 irq -1
goldfish_new_pdev goldfish-battery at ff015000 irq 17
goldfish_new_pdev goldfish_events at ff016000 irq 18
goldfish_new_pdev goldfish_nand at ff017000 irq -1
goldfish_new_pdev qemu_pipe at ff018000 irq 19
goldfish_new_pdev goldfish-switch at ff01a000 irq 20
goldfish_new_pdev goldfish-switch at ff01b000 irq 21
goldfish_pdev_worker registered goldfish_interrupt_controller
goldfish_pdev_worker registered goldfish_device_bus
goldfish_pdev_worker registered goldfish_timer
goldfish_pdev_worker registered goldfish_rtc
goldfish_pdev_worker registered goldfish_tty
goldfish_pdev_worker registered goldfish_tty
goldfish_pdev_worker registered goldfish_tty
goldfish_pdev_worker registered smc91x
goldfish_pdev_worker registered goldfish_fb
goldfish_pdev_worker registered goldfish_audio
goldfish_pdev_worker registered goldfish_mmc
goldfish_pdev_worker registered goldfish_memlog
goldfish_pdev_worker registered goldfish-battery
goldfish_pdev_worker registered goldfish_events
goldfish_pdev_worker registered goldfish_nand
goldfish_pdev_worker registered qemu_pipe
goldfish_pdev_worker registered goldfish-switch
goldfish_pdev_worker registered goldfish-switch
ashmem: initialized
Installing knfsd (copyright (C) 1996 okir@monad.swb.de).
yaffs Sep 26 2012 11:04:43 Installing.
msgmni has been set to 1007
alg: No test for stdrng (krng)
io scheduler noop registered
io scheduler anticipatory registered (default)
io scheduler deadline registered
io scheduler cfq registered
allocating frame buffer 480 * 800, got ffa00000
console [ttyS0] enabled
loop: module loaded
nbd: registered device at major 43
goldfish_audio_probe
tun: Universal TUN/TAP device driver, 1.6
tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
smc91x.c: v1.1, sep 22 2004 by Nicolas Pitre <nico@cam.org>
eth0 (smc91x): not using net_device_ops yet
eth0: SMC91C11xFD (rev 1) at e080c000 IRQ 13 [nowait]
eth0: Ethernet addr: 52:54:00:12:34:56

接下来要说的是,NAND闪存是由NAND闪存大小、页大小、空闲区大小和擦除块大小初始化组成的。其中有3个NAND闪存设备,分别作为系统、数据和缓存安装。

goldfish nand dev0: size c5e0000, page 2048, extra 64, erase 131072
goldfish nand dev1: size c200000, page 2048, extra 64, erase 131072
goldfish nand dev2: size 4000000, page 2048, extra 64, erase 131072
mice: PS/2 mouse device common for all mice
*** events probe ***
events_probe() addr=0xe0814000 irq=18
events_probe() keymap=qwerty2
input: qwerty2 as /devices/virtual/input/input0
goldfish_rtc goldfish_rtc: rtc core: registered goldfish_rtc as rtc0
device-mapper: uevent: version 1.0.3
device-mapper: ioctl: 4.14.0-ioctl (2008-04-23) initialised: dm-devel@redhat.com
logger: created 64K log 'log_main'
logger: created 256K log 'log_events'
logger: created 64K log 'log_radio'
Netfilter messages via NETLINK v0.30.
nf_conntrack version 0.5.0 (8192 buckets, 32768 max)
CONFIG_NF_CT_ACCT is deprecated and will be removed soon. Please use
nf_conntrack.acct=1 kernel paramater, acct=1 nf_conntrack module option or
sysctl net.netfilter.nf_conntrack_acct=1 to enable it.
ctnetlink v0.93: registering with nfnetlink.
NF_TPROXY: Transparent proxy support initialized, version 4.1.0
NF_TPROXY: Copyright (c) 2006-2007 BalaBit IT Ltd.
xt_time: kernel timezone is -0000
ip_tables: (C) 2000-2006 Netfilter Core Team
arp_tables: (C) 2002 David S. Miller
TCP cubic registered
NET: Registered protocol family 10
ip6_tables: (C) 2000-2006 Netfilter Core Team
IPv6 over IPv4 tunneling driver
NET: Registered protocol family 17
NET: Registered protocol family 15
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
802.1Q VLAN Support v1.8 Ben Greear <greearb@candelatech.com>
All bugs added by David S. Miller <davem@redhat.com>
VFP support v0.3: implementor 41 architecture 3 part 30 variant c rev 0
goldfish_rtc goldfish_rtc: setting system clock to 2013-05-20 13:29:09 UTC
(1369056549)
Freeing init memory: 124K
mmc0: new SD card at address e118
mmcblk0: mmc0:e118 SU02G 100 MiB
 mmcblk0:
init: cannot open '/initlogo.rle'

在日志的余下部分,NAND闪存分区作为日志块设备安装。

yaffs: dev is 32505856 name is "mtdblock0"
yaffs: passed flags ""
yaffs: Attempting MTD mount on 31.0, "mtdblock0"
yaffs_read_super: isCheckpointed 0
save exit: isCheckpointed 1
yaffs: dev is 32505857 name is "mtdblock1"
yaffs: passed flags ""
yaffs: Attempting MTD mount on 31.1, "mtdblock1"
yaffs_read_super: isCheckpointed 0
yaffs: dev is 32505858 name is "mtdblock2"
yaffs: passed flags ""
yaffs: Attempting MTD mount on 31.2, "mtdblock2"
yaffs_read_super: isCheckpointed 0
init: cannot find '/system/etc/install-recovery.sh', disabling 'flash_recovery'
init: untracked pid 47 exited
eth0: link up

本章介绍了Android模拟器的内里,给出了使用Android虚拟设备管理器配置Android模拟器的实例,然后详细讨论了两个常见的硬件接口——串行接口和定时器接口。了解硬件是进行任何嵌入式系统编程过程的首个工作。


相关图书

Android Gradle权威指南
Android Gradle权威指南
爱上Android
爱上Android
深入解析Android 虚拟机
深入解析Android 虚拟机
Android系统安全和反编译实战
Android系统安全和反编译实战

相关文章

相关课程