北京android培训

更新时间:2022-12-30 12:20:11 阅读: 评论:0


2022年12月30日发(作者:老罗英语)x0c目 录 前言 第1章 Android底层开发基础 1.1 什么是驱动 1.1.1 驱动程序的魅力 1.1.2 电脑中的驱动 1.1.3 手机中的驱动程序 1.2 开源还是不开源的问题 1.2.1 雾里看花的开源 1.2.2 从为什么选择Java谈为什么不开源驱动程序 1.2.3 对驱动开发者来说是一把双刃剑 1.3 Android和Linux 1.3.1 Linux简介 1.3.2 Android和Linux的关系 1.4 简析Linux内核 1.4.1 内核的体系结构 1.4.2 和Android密切相关的Linux内核知识 1.5 分析Linux内核源代码很有必要 1.5.1 源代码目录结构 1.5.2 浏览源代码的工具 1.5.3 为什么用汇编语言编写内核代码 1.5.4 Linux内核的显著特性 1.5.5 学习Linux内核的方法 第2章 分析Android源代码 2.1 搭建Linux开发环境和工具 2.1.1 搭建Linux开发环境 2.1.2 设置环境变量 2.1.3 安装编译工具 2.2 获取Android源代码 2.3 分析并编译Android源代码 2.3.1 Android源代码的结构 2.3.2 编译Android源代码 2.3.3 运行Android源代码 2.3.4 实践演练——演示编译Android程序的两种方法 2.4 编译Android Kernel 2.4.1 获取Goldfish内核代码 2.4.2 获取MSM内核代码 2.4.3 获取OMAP内核代码 2.4.4 编译Android的Linux内核 2.5 运行模拟器

x0c2.5.1 Linux环境下运行模拟器的方法 2.5.2 模拟器辅助工具——adb 第3章 驱动需要移植 3.1 驱动开发需要做的工作 3.2 Android移植 3.2.1 移植的任务 3.2.2 移植的内容 3.2.3 驱动开发的任务 3.3 Android对Linux的改造 3.3.1 Android对Linux内核文件的改动 3.3.2 为Android构建Linux的操作系统 3.4 内核空间和用户空间接口是一个媒介 3.4.1 内核空间和用户空间的相互作用 3.4.2 系统和硬件之间的交互 3.4.3 使用Relay实现内核到用户空间的数据传输 3.5 三类驱动程序 3.5.1 字符设备驱动程序 3.5.2 块设备驱动程序 3.5.3 网络设备驱动程序 第4章 HAL层深入分析 4.1 认识HAL层 4.1.1 HAL层的发展 4.1.2 过去和现在的区别 4.2 分析HAL层源代码 4.2.1 分析HAL moudle 4.2.2 分析mokoid工程 4.3 总结HAL层的使用方法 4.4 传感器在HAL层的表现 4.4.1 HAL层的Sensor代码 4.4.2 总结Sensor编程的流程 4.4.3 分析Sensor源代码看Android API与硬件平台的衔接 4.5 移植总结 4.5.1 移植各个Android部件的方式 4.5.2 移植技巧之一——不得不说的辅助工作 第5章 Goldfish下的驱动解析 5.1 staging驱动 5.1.1 staging驱动概述 5.1.2 Binder驱动程序 5.1.3 Logger驱动程序 5.1.4 Lowmemorykiller组件

x0c5.1.5 Timed Output驱动程序 5.1.6 Timed Gpio驱动程序 5.1.7 Ram Console驱动程序 5.2 wakelock和early_suspend 5.2.1 wakelock和early_suspend的原理 5.2.2 Android休眠 5.2.3 Android唤醒 5.3 Ashmem驱动程序 5.4 Pmem驱动程序 5.5 Alarm驱动程序 5.5.1

Alarm简析 5.5.2 Alarm驱动程序的实现 5.6 USB Gadget驱动程序 5.7 Android Paranoid驱动程序 5.8 Goldfish设备驱动 5.8.1 FrameBuffer驱动 5.8.2 键盘驱动 5.8.3 实时时钟驱动程序 5.8.4 TTY终端驱动程序 5.8.5 NandFlash驱动程序 5.8.6 MMC驱动程序 5.8.7 电池驱动程序 第6章 MSM内核和驱动解析 6.1 MSM基础 6.1.1 常见MSM处理器产品 6.1.2 Snapdragon内核介绍 6.2 移植MSM内核简介 6.3 移植MSM 6.3.1 Makefile文件 6.3.2 驱动和组件 6.3.3 设备驱动 6.3.4 高通特有的组件 第7章 OMAP内核和驱动解析 7.1 OMAP基础 7.1.1 OMAP简析 7.1.2 常见OMAP处理器产品 7.1.3 开发平台 7.2 OMAP内核 7.3 移植OMAP体系结构 7.3.1 移植OMAP平台

x0c7.3.2 移植OMAP处理器 7.4 移植Android专用驱动和组件 7.5 OMAP的设备驱动 第8章 显示系统驱动应用 8.1 显示系统介绍 8.1.1 Android的版本 8.1.2 不同版本的显示系统 8.2 移植和调试前的准备 8.2.1 FrameBuffer驱动程序 8.2.2 硬件抽象层 8.3 实现显示系统的驱动程序 8.3.1 Goldfish中的FrameBuffer驱动程序 8.3.2 使用Gralloc模块的驱动程序 8.4 MSM高通处理器中的显示驱动实现 8.4.1 MSM中的FrameBuffer驱动程序 8.4.2 MSM中的Gralloc驱动程序 8.5 OMAP处理器中的显示驱动实现 第9章 输入系统驱动应用 9.1 输入系统介绍 9.1.1 Android输入系统结构元素介绍 9.1.2 移植Android输入系统时的工作 9.2 Input(输入)驱动 9.3 模拟器的输入驱动 9.4 MSM高通处理器中的输入驱动实现 9.4.1 触摸屏驱动 9.4.2 按键和轨迹球驱动 9.5 OMAP处理器平台中的输入驱动实现 9.5.1 触摸屏驱动 9.5.2 键盘驱动 第10章 振动器系统驱动 10.1 振动器系统结构 10.1.1 硬件抽象层 10.1.2 JNI框架部分 10.2 开始移植 10.2.1 移植振动器驱动程序 10.2.2 实现硬件抽象层 10.3 在MSM平台实现振动器驱动 第11章 音频系统驱动 11.1 音频系统结构 11.2 分析音频系统的层次

x0c11.2.1 层次说明 11.2.2 Media库中的Audio框架 11.2.3 本地代码 11.2.4 JNI代码 11.2.5 Java代码 11.3 移植Audio系统的必备技术 11.3.1 移植Audio系统所要做的工作 11.3.2 分析硬件抽象层 11.3.3 分析AudioFlinger中的Audio硬件抽象层的实现 11.4 真正实现Audio硬件抽象层 11.5 MSM平台实现Audio驱动系统 11.5.1 实现Audio驱动程序 11.5.2 实现硬件抽象层 11.6 OSS平台实现Audio驱动系统 11.6.1 OSS驱动程序介绍 11.6.2 mixer 11.7 ALSA平台实现Audio系统 11.7.1 注册音频设备和音频驱动 11.7.2 在Android中使用ALSA声卡 11.7.3 在OMAP平台移植Android的ALSA声卡驱动 第12章 视频输出系统驱动 12.1 视频输出系统结构 12.2 需要移植的部分 12.3 分析硬件抽象层 12.3.1 Overlay系统硬件抽象

层的接口 12.3.2 实现Overlay系统的硬件抽象层 12.3.3 实现接口 12.4 实现Overlay硬件抽象层 12.5 在OMAP平台实现Overlay系统 12.5.1 实现输出视频驱动程序 12.5.2 实现Overlay硬件抽象层 12.6 系统层调用Overlay HAL的架构 12.6.1 调用Overlay HAL的架构的流程 12.6.2 S3C6410 Android Overlay的测试代码 第13章 OpenMax多媒体框架 13.1 OpenMax基本层次结构 13.2 分析OpenMax框架构成 13.2.1 OpenMax总体层次结构 13.2.2 OpenMax IL层的结构 13.2.3 Android中的OpenMax

x0c13.3 实现OpenMax IL层接口 13.3.1 OpenMax IL层的接口 13.3.2 在OpenMax IL层中需要做什么 13.3.3 研究Android中的OpenMax适配层 13.4 在OMAP平台实现OpenMax IL 13.4.1 实现文件 13.4.2 分析TI OpenMax IL的核心 13.4.3 实现TI OpenMax IL组件实例 第14章 多媒体插件框架 14.1 Android多媒体插件 14.2 需要移植的内容 14.3 OpenCore引擎 14.3.1 OpenCore层次结构 14.3.2 OpenCore代码结构 14.3.3 OpenCore编译结构 14.3.4 OpenCore OSCL 14.3.5 实现OpenCore中的OpenMax部分 14.3.6 OpenCore的扩展 14.4 Stagefright引擎 14.4.1 Stagefright代码结构 14.4.2 Stagefright实现OpenMax接口 14.4.3 Video Buffer传输流程 第15章 传感器系统 15.1 传感器系统的结构 15.2 需要移植的内容 15.2.1 移植驱动程序 15.2.2 移植硬件抽象层 15.2.3 实现上层部分 15.3 在模拟器中实现传感器 第16章 照相机系统 16.1 Camera系统的结构 16.2 需要移植的内容 16.3 移植和调试 16.3.1 V4L2驱动程序 16.3.2 硬件抽象层 16.4 实现Camera系统的硬件抽象层 16.4.1 Java程序部分 16.4.2 Camera的Java本地调用部分 16.4.3 Camera的本地库 16.4.4 Camera服务

x0c16.5 MSM平台实现Camera系统 16.6 OMAP平台实现Camera系统 第17章 Wi-Fi系统、蓝牙系统和GPS系统 17.1 Wi-Fi系统 17.1.1 Wi-Fi系统的结构 17.1.2 需要移植的内容 17.1.3 移植和调试 17.1.4 OMAP平台实现Wi-Fi 17.1.5 配置Wi-Fi的流程 17.1.6 具体演练——在Android下实现Ethernet 17.2 蓝牙系统 17.2.1 蓝牙系统的结构 17.2.2 需要移植的内容 17.2.3 具体移植 17.2.4 MSM平台的蓝牙驱动 17.3 定位系统 17.3.1 定位系统的结构 17.3.2 需要移植的内容 17.3.3 移植和调试 第18章 电话系统 18.1 电话系统基础 18.1.1 电话系统简介 18.1.2 电话系统结构 18.2 需要移植的内容 18.3 移植和调试 18.3.1 驱动程序 18.3.2 RIL接口 18.4 电话系统实现流程分析 18.4.1 初始启动流程 18.4.2 request流程 18.4.3 respon流程 第19章 其他系统 19.1 Alarm警报器系统 19.1.1 Alarm系统的结构 19.1.2 需要移植的内容 19.1.3 移植和调试 19.1.4 模拟器环境的具体实现 19.1.5 MSM平台实现Alarm 19.2 Lights光系统 19.2.1 Lights

光系统的结构

x0c19.2.2 需要移植的内容 19.2.3 移植和调试 19.2.4 MSM平台实现光系统 19.3 Battery电池系统 19.3.1 Battery系统的结构 19.3.2 需要移植的内容 19.3.3 移植和调试 19.3.4 在模拟器中实现电池系统 Android移动开发技术丛书

Android底层开发技术实战详解 ——内核、移植和驱动

王振丽 编著

x0c電子工業出版社 Publishing Hou of Electronics Industry北京·BEIJING 内容简介 本书从底层原理开始讲起,结合真实的案例向读者详细介绍了Android内核、移植和驱动开发的整个 流程。全书分为19章,依次讲解驱动移植的必要性,何为HAL层深入分析,Goldfish、MSM、MAP内核 和驱动解析,显示系统、输入系统、振动器系统、音频系统、视频输出系统的驱动,OpenMax多媒体 、多媒体插件框架,传感器、照相机、Wi-Fi、蓝牙、GPS和电话系统等。在每一章中,重点介绍了与 Android驱动开发相关的底层知识,并对Android源码进行了剖析。 本书适合Android研发人员及Android爱好者学习,也可以作为相关培训学校和大专院校相关专业的教 学用书。 未经许可,不得以任何方式复制或抄袭本书之部分或全部内容。 版权所有,侵权必究。 图书在版编目(CIP)数据 Android底层开发技术实战详解:内核、移植和驱动/王振丽编著.—北京:电子工业出版社 ,2012.8(Android移动开发技术丛书) ISBN 978-7-121-17593-0 Ⅰ.①A… Ⅱ.①王… Ⅲ.①移动终端-应用程序-程序设计 Ⅳ.①TN929.53 中国版本图书馆CIP数据核字(2012)第157997号

策划编辑:张月萍 责任编辑:高洪霞 印 刷: 装 订: 出版发行:电子工业出版社 北京市海淀区万寿路173信箱 邮 编:100036 开 本:787×1092 1/16 印 张:33.75 字 数:864千字 印 次:2012年8月第1次印刷 印 数:3500册 定 价:69.00元 凡所购买电子工业出版社图书有缺损问题,请向购买书店调换。若书店售缺,请与本社发行部联系 ,联系及邮购电话:(010)88254888。 质量投诉请发邮件至zlts@,盗版侵权举报请发邮件到dbqq@。

x0c服务热线:(010)88258888。 前 言 随着3G的到来,无线带宽越来越高,使得更多内容丰富的应用程序装入手机成为可能,如视频通话、 视频点播、移动互联网冲浪和内容分享等。为了承载这些数据应用及快速部署,手机功能将会越来越 智能,越来越开放。为了实现这些需求,必须有一个好的开发平台来支持,在此由Google公司发起的 OHA联盟走在了业界的前列,2007年11月推出了开放的Android平台,任何公司及个人都可以免费获取 源代码及开发SDK。由于其开放性和优异性,Android平台得到了业

界广泛的支持,其中包括各大手机 厂商和著名的移动运营商等。继2008年9月第一款基于Android平台的手机G1发布之后,三星、摩托罗 拉、索爱、LG等主流手机制造商都推出了自己的Android平台手机。在2011年底,Android超越了塞班 和iOS,雄踞智能手机市场占有率榜首的位置。 毕竟Android平台被推出的时间才短短5年,了解Android平台软件开发技术的程序员还不多,如何迅 速地推广和普及Android平台软件开发技术,让越来越多的人参与到Android应用的开发中,是整个产 业链都在关注的一个话题。为了帮助开发者更快地进入Android开发行列,笔者特意精心编写了本书 。本书系统讲解了Android底层驱动开发和移植的基本知识,图文并茂地帮助读者学习和掌握各种驱 动的开发常识,详细讲解了Android源代码的方方面面。 从技术角度而言,Android是一种融入了全部Web应用的平台。随着版本的更新,从最初的触屏到现在 的多点触摸,从普通的联系人到现在的数据同步,从简单的Google Map到现在的导航系统,从基本的 网页浏览到现在的HTML 5,这都说明Android已经逐渐稳定,而且功能越来越强大。此外,Android平 台不仅支持Java、C、C++等主流的编程语言,还支持Ruby、Python等脚本语言,甚至Google专为 Android的应用开发推出了Simple语言,这使得Android有着非常广泛的开发群体。 本书的内容 在本书的内容中,详细讲解了Android底层技术和驱动开发的基本知识。本书内容新颖、知识全面、 讲解详细,全书分为19章,具体内容分布如下:

x0c全书内容都采用了理论加实践的教学方法,每个实例先提出制作思路及包含知识点,在实例最后补充 总结知识点并出题让读者举一反三。 本书特色 本书内容相当丰富,实例内容覆盖全面,满足Android技术人员成长道路上的方方面面。我们的目标 是通过一本图书,提供多本图书的价值,读者可以根据自己的需要有选择地阅读,以完善本人的知识 和技能结构。在内容的编写上,本书具有以下特色: (1)结构合理 从用户的实际需要出发,科学安排知识结构,内容由浅入深,叙述清楚,具有很强的知识性和实用性 ,反映了当前Android技术的发展和应用水平。同时全书精心筛选的最具代表性、读者最关心典型知 识点,几乎包括Android底层和驱动技术的各个方面。 (2)易学易懂 本书条理清晰、语言简洁,可帮助读者快速掌握每个知识点;每个部分既相互连贯又自成体系,使读 者既可以按照本书编排的章节顺序进行学习,也可以根据自己的需求对某一章节进行针对性的学习。 (3)实用性强 本书彻底摒弃枯燥的理论和简单的操作,注重实用性和可操作性

,详细讲解了各个部分的源代码知识 ,使用户在掌握相关操作技能的同时,还能学到相应的基础知识。 参加本书编写的人员有:王振丽、王东华、熊斌、朱桂英、周秀、邓才兵、罗红仙、王石磊、孙宇、 程娟、王文忠、王梦、陈强、于洋、管西京。本团队由于时间和水平所限,书中难免有不足之处。如 有纰漏和不尽如人意之处,诚请读者提出意见或建议,以便修订并使之更臻完善。另外,为了更好地 为读者服务,我们专门提供了技术支持网站,欢迎读者光临论坛,无论是书中的 疑问,还是学习过程中的疑惑,本团队将尽力为大家解答。 编 者 2012年5月 第1章 Android底层开发基础 Android是一种手机开发平台,它是建立在Linux基础之上的、能够迅速建立手机软件的解决方案。 Android外形比较简单,但功能十分强大,已经成为当前一个新兴的热点,并且是软件行业的一股新 兴力量。本章将简单介绍Android的发展历程和背景,讲解在进行驱动开发之前所要做的工作,为读 者学习后面的高级知识打下基础。

x0c1.1 什么是驱动 生活中总会遇到这样的场景:买了一个新的USB鼠标,插在电脑上后会提示安装新的驱动;买了一台 新的打印机,也提示需要安装驱动后才能使用。驱动含有“推动”和“发动”之意(计算机领域中的 驱动也含有推动之意)。本节将简要讲解计算机领域中驱动的基本知识。

x0c1.1.1 驱动程序的魅力 人们在安装新硬件时,总会被要求放入“这种硬件的驱动程序”。这是很令初学者头痛的事情,他们 往往不是找不到驱动程序的盘片,就是找不到文件的位置,或是根本不知道什么是驱动程序。比如安 装打印机这种普通的硬件设备,并不是把连接线接上就能完成的。如果将数据线连接到电脑后就开始 使用,系统会提示找不到驱动程序,这时应该怎么办呢?对于菜鸟来说,即使参考说明书也未必能顺 利安装。在不能成功使用硬件的原因中,70%以上是因为没有安装驱动程序。 其实在Windows系统中,安装主板、光驱、显卡、声卡这些硬件产品都对应一套完整的驱动程序。如 果需要外接别的硬件设备,还需要安装相应的驱动程序,例如外接游戏硬件要安装手柄、方向盘、摇 杆、跳舞毯等的驱动程序,外接打印机要安装打印机驱动程序,上网或接入局域网要安装网卡、 Modem甚至ISDN、ADSL的驱动程序。 和Windows系统一样,在Android手机中也经常需要使用一些外部硬件设备,例如蓝牙耳机、外部存储 卡和摄像头等。要想使用这些外部辅助设备,需要安装对应的驱动程序。驱动程序是添加在操作系统 中的一段代码,虽然这段代码

比较简短,但是其中包含了和硬件相关的设备信息。有了这些信息,计 算机就可以与设备进行通信,从而可以使用这些硬件。驱动程序是硬件厂商根据操作系统编写的配置 文件,可以说没有驱动程序,计算机中的硬件就无法工作。操作系统不同,对应的硬件驱动程序也不 同。硬件厂商为了保证硬件的兼容性及增强硬件的功能,会不断更新、升级驱动程序,例如显卡芯片 公司Nvidia平均每个月会升级驱动程序2到3次。 驱动程序是硬件的一个构成部分,当安装新的硬件时,必须安装对应的驱动程序。凡是安装一个原本 不属于我们电脑中或手机中的硬件设备时,系统就会要求安装驱动程序,将新的硬件与电脑或手机系 统连接起来。驱动程序在此扮演了一个沟通的角色,负责把硬件的功能告诉电脑系统,并且将系统的 指令传达给硬件,让它开始工作。

x0c1.1.2 电脑中的驱动 在Windows系统中,驱动程序按照其提供的硬件支持可以分为声卡驱动程序、显卡驱动程序、鼠标驱 动程序、主板驱动程序、网络设备驱动程序、打印机驱动程序、扫描仪驱动程序等。注意在电脑中没 有CPU和内存驱动程序,这是因为CPU和内存无须驱动程序便可使用。如果需要在Windows系统中的 DOS模式下使用光驱,那么还需要在DOS模式下安装光驱驱动程序。在当前市面上,显卡、声卡、网卡 等内置扩展卡和打印机、扫描仪、外置Modem等外部设备,多数都需要安装与设备型号相符的驱动程 序,否则无法发挥其部分或全部功能。 一般可以通过如下三种途径得到驱动程序。 ● 购买的硬件附带有驱动程序; ● Windows系统自带有大量驱动程序; ● 从Internet下载驱动程序,此途径往往能够得到最新的驱动程序。 可能有读者禁不住问:Windows如何知道安装的是什么设备,以及要复制哪些文件呢?答案是 “.inf”文件。“.inf”是从Windows 95时代开始引入的一种描述设备安装信息的文件,它使用特定 的语法文字来说明要安装的设备类型、生产厂商、型号、要复制的文件、复制到的目标路径,以及要 添加到注册表中的信息。通过读取和解释这些文字,Windows可以知道应该如何安装驱动程序。目前 几乎所有硬件厂商提供的用于Windows 9x的驱动程序都带有安装信息文件。其实“.inf”文件不仅可 以安装驱动程序,还可以安装与硬件并没有什么关系的软件,例如Windows系统支持的“Windows更新 ”功能,在更新时下载的系统部件就是使用“.inf”文件来说明如何安装该部件的。

x0c1.1.3 手机中的驱动程序 有的手机和电脑不能直接连接,必须用手机自带的磁盘驱动一下,其实就是安装了一个读取手机内存

信息的程序。我们可以去网络中寻找安装驱动,只需要在网上搜索机型和驱动就可以。或者把手机用 USB线连到电脑上,会弹出一个对话框,此时选择让电脑自己在互联网上搜索驱动程序即可。 如果在手机中使用数据线、蓝牙、红外等连接方式连接电脑,一般情况下需要驱动程序。而且在一部 分手机中,通过数据线、蓝牙、红外方式连接电脑后还需要软件才能传输数据到电脑,或者传输数据 到手机。此时可以使用购买手机时的随机光盘中的驱动程序解决问题,也可以在手机网站或论坛下载 。 如果是通过串口连接电脑的,则一般不需要驱动程序,但是此时会需要用软件来实现和手机的连接。 在手机附赠光盘中通常会有这样的软件,或者可以去网站、论坛下载。

x0c1.2 开源还是不开源的问题 本节将简单谈一谈开源和不开源的问题。我们都知道Android是基于Linux的,因为Linux是开源的 ,Android也号称开源,所以一经推出后就受到了广大程序员和手机厂商的青睐。但是本节的题目为 什么是“开源还是不开源的问题”呢?这需要从Android的发展历史谈起。

x0c1.2.1 雾里看花的开源 在Android刚被推出的时候,只能用Java语言开发应用程序,这就需要所有的应用程序都运行在一个 巨大的虚拟机上。2009年6月,Android发布了NDK工具包,这样就可以支持C/C++语言编程,但是性能 不如SKD工具包中的Java语言。 2010年2月,在开源界发生了一件大事。Linux Kernel的维护者Greg Kroah-Hartman宣布,将 Android代码从Linux Kernel代码库中删除。此事对于普通用户可能并没有什么影响,但对于开发者 来说,尤其是对于开源社区的开发者来说,算是一颗重磅炸弹。消息公布以后,外界普遍觉得惊讶和 可惜。好不容易才有了一个如此受欢迎的开源手机系统,应该齐心协力、共同开发才对,为什么要 “窝里斗”呢?到底是什么矛盾,使得Linux Kernel小组剔除Android代码呢? 从Linux 2.6.33版本开始,Google智能手机操作系统Android核心代码全部被删除。这是因为提倡开 源的Android在Linux面前使用了雾里看花的把戏,它修改了Kernel内核,但是又不提供修改的细节 ,这相当于自己搞了一个封闭的系统。尽管Android取得了空前的成功,但是Google也放弃了构建一 个真正开源的手机系统的机会,从而不能获得由全世界程序员提供智慧、分享代码和推动创新的好处 。由此可见,是因为Android不真正开源,所以才被从Linux体系中删除。 Android与Ubuntu、Debian、Red Hat等传统的Linux发行版相比,只有系统的底层结构是一样的,而 其他东西在Android中都不一样,尤其是程序员的编程接口是完全不同的。所以必须重新写Android应

用程序后才能使用,现存的Linux程序无法移植上去。由此可见,Android是一种全新的系统,它与 Linux距离很远。

x0c1.2.2 从为什么选择Java谈为什么不开源驱动程序 1.Java的好处 Android很好地解决了长期令手机制造商头痛不已的问题:在业界缺乏一个开源的Java虚拟机和统一 的应用程序接口。使用Android后,程序员只要编写一次程序就可以用在各种手机硬件平台之上。这 就是Android应用程序使用Java语言开发的原因,因为如果不这样做,就无法让程序实现和硬件无关 。 可能很多熟知Linux的读者会反问:传统的Linux系统也不依赖特定的硬件,只要把源代码根据不同的 平台分别编译,同一个程序就可以在不同的硬件架构、不同的Linux发行版中使用。那么Android只采 用Kernel、只允许用Java编程的真正原因到底是什么呢? 2.为什么驱动不开源 Linux Kernel的版权是GPL。在此版本下,硬件厂商都希望自己的硬件能在Linux Kernel下运行,此 时就必须使用驱动程序。但是如果把驱动程序的源代码公开,就等于公开硬件规格,这是广大硬件厂 商所不能接受的。所以硬件厂商只提供编好的驱动程序,而不提供原始代码。 Android的重点是商业应用,为了解决上述驱动开源的问题,Google采用了自己的方法来绕过这个问 题。Google把驱动程序移到“urspace”中,即让驱动程序在Linux Kernel上面运行,而不是一起 运行,这样就可以避过GPL规则。然后在Kernel上开一个小门,让本来不能直接控制到硬件的 “urspace”程序也可以碰得到,此时只需公布这个开的“小门”程序源代码即可。由此可见 ,Google在Kernel和应用程序之间设计了一个中间层,这样既不违反GPL许可,又能不让外界看到厂 商的硬件驱动和应用程序的源代码。 3.带来的问题 但是Google的上述做法随之带来了一个问题,Kernel和Android采取不同的许可证,Kernel采用GPL许 可证,而Android采用Apache Software Licen(简称ASL)许可证。在GPL许可证中规定,对源代码 的任何修改都必须开源,所以Android需要开源,因为它修改了Kernel。而在ASL许可证中规定,用户 可以随意使用源代码而不必开源,所以建立在Android之上的硬件驱动和应用程序都可以保持封闭。 这种封闭得到了更多硬件厂商的支持,Google特意修改了Kernel,使得原本应该包括在Kernel中的某 些功能都被转移到“urspace”中,所以就避开了开源。 4.影响 Google的上述行为有利于推广Android,并且可以吸引更多厂商和软件开发商的加入,但是同时也宣 布放弃了构建一个真正开源的手机系统的机会。所有为Android写的硬件驱动都不能合并到Kernel中 ,因为它们只在Google的代码里才有效,而在Ke

rnel里根本没法用。

x0c1.2.3 对驱动开发者来说是一把双刃剑 正因为所有为Android写的硬件驱动都不能合并到Kernel中,这些驱动程序只能在Google代码中有效 ,而在Kernel中根本没法用,所以Google从不向Kernel提交大量的硬件驱动程序和平台源代码。 硬件厂商都不开源驱动代码,为生存在Android底层的开发人员,特别是从事驱动开发的成员,带来 了巨大的就业机会。开发人员可以为硬件厂商开发不开源的驱动程序从而获得报酬。随着Android的 异常火爆,市面上有很多企业在招聘Android驱动开发人员。由此可见,驱动的不开源既为我们的学 习带来了难题,也为就业机会增加了砝码,是一把双刃剑!

x0c1.3 Android和Linux Linux是一类UNIX计算机操作系统的统称。Linux操作系统的内核名字也是“Linux”。Linux操作系统 是自由软件和开放源代码发展中最著名的例子。2007年,基于Linux的Android系统横空出世 ,Android将是未来智能手机发展的趋势。自从2008年9月22日,美国运营商T-Mobile USA在纽约正式 发布了第一款基于Android的手机后,更多的移动设备厂商看到了Android的光明前景,并纷纷加入其 中,Android甚至已经涉足上网本市场了。随着Android手机的普及,Android应用的需求势必会越来 越大,这是一个潜力巨大的市场,会吸引无数软件开发厂商和开发者投身其中。本节将简要讲解 Android和Linux之间的关系,为读者学习本书后面的知识打下基础。

x0c1.3.1 Linux简介 众所周知,在计算机操作系统领域,Windows、Netware和UNIX一直占据主导地位。但是最近几年发生 了一些变化,以自由标榜自己的Linux越来越显示出其咄咄逼人的气势,日益成为一个令人生畏的对 手。Linux正在发起一场产业革命,逐渐吞噬着传统操作系统的市场份额。请回头看一看我们平常使 用的技术:嵌入式系统、移动计算、移动互联网工具、服务器、超级计算。在几乎每个技术领域 ,Linux都展现出作为未来主导平台的势头。随着SaaS、云计算、虚拟化、移动平台、企业2.0等新兴 技术的发展,Linux事业正面临着巨大发展机遇。主要体现在如下三个方面。 1.向企业级核心应用迈进 Linux的采用已由网络领域逐步转向了关键业务应用,企业关键任务成为IBM的增长领域,比如ERP软 件。同时,随着IT决策逐步从IT主管下放给IT管理人员,这些管理者对Linux显示了强烈的支持,但 同时对安全、可用性与服务提出了更高的需求。尽管很多用户仍将UNIX视为关键任务的平台,但随着 Linux开发者逐步缩小两者的功能性差距,越来越多的用户开始将关键业务部署在Linux之上。 2.Linux将主导移动平台 Linux进入移动终端操作系统后,很快就以其

开放源代码的优势吸引了越来越多的终端厂商和运营商 ,关注它的包括摩托罗拉和NTT DoCoMo等知名的厂商。与其他操作系统相比,Linux是个后来者,但 Linux具有其他操作系统无法比拟的优势。其一,Linux具有开放的源代码,能够大大降低成本。其二 ,既满足了手机制造商根据实际情况有针对性地开发自己的Linux手机操作系统的要求,又吸引了众 多软件开发商对内容应用软件的开发,丰富了第三方应用。 3.新技术为Linux加速 在目前的企业级计算领域,云计算、SaaS、虚拟化是热门技术话题。在这些领域,Linux同样大有可 为。云计算将全部使用Linux,Linux也是一款未来运行数据中心虚拟机的理想操作系统。

x0c1.3.2 Android和Linux的关系 在了解Linux和Android的关系之前,需要先明确如下三点。 ● Android采用Linux作为内核。 ● Android对Linux内核做了修改,目的是适应在移动设备上的使用。 ● Android开始是作为Linux的一个分支,后来由于无法并入Linux的主开发树,已被Linux内核组从 开发树中删除。 1.Android是继承于Linux的 Android是在Linux 2.6的内核基础之上运行的,提供的核心系统服务包括安全、内存管理、进程管理 、网络组和驱动模型等内容。内核部分还相当于一个介于硬件层和系统中其他软件组之间的一个抽象 层次。但是严格来说它不算是Linux操作系统。 因为Android内核是由标准的Linux内核修改而来的,所以继承了Linux内核的诸多优点,保留了 Linux内核的主题架构。同时Android按照移动设备的需求,在文件系统、内存管理、进程间通信机制 和电源管理方面进行了修改,添加了相关的驱动程序和必要的新功能。但是和其他精简的Linux系统 相比(比如uClinux),Android在很大程度上保留了Linux的基本架构,因此Android的应用性和扩展 性更强。当前Android版本对应的Linux内核版本如下所示。 ● Android 1.5:Linux-2.6.27 ● Android 1.6:Linux-2.6.29 ● Android 2.0, 2.1:Linux-2.6.29 ● Android 2.2:Linux-2.6.32.9 2.Android和Linux内核的区别 Android系统的系统层面的底层是Linux,中间加上了一个叫做Dalvik的Java虚拟机,表面层上面是 Android运行库。每个Android应用都运行在自己的进程上,享有Dalvik虚拟机为它分配的专有实例。 为了支持多个虚拟机在同一个设备上高效运行,Dalvik被改写过。 Dalvik虚拟机执行的是Dalvik格式的可执行文件(.dex)。该格式经过优化,将内存耗用降到最低。 Java编译器将Java源文件转为.class文件,.class文件又被内置的dx工具转化为.dex格式文件,这种 文件在Dalvik虚拟机上注册并运行。 Android系统的应用软件都是运行在Dalvik之上的Java软件,而Dalvik是运行在Linux中的,在

一些底 层功能——比如线程和低内存管理方面,Dalvik虚拟机是依赖Linux内核的。由此可见,Android是运 行在Linux之上的操作系统,但是它本身不能算是Linux的某个版本。 Android内核和Linux内核的差别主要体现在11个方面,接下来将一一简要介绍。 (1)Android Binder Android Binder是基于OpenBinder框架的一个驱动,用于提供Android平台的进程间通信 (IPC,Inter-Process Communication)。原来的Linux系统上层应用的进程间通信主要是DBus(Desktop Bus),采用消息总线的方式来进行IPC。 其源代码位于:drivers/staging/android/binder.c。 (2)Android电源管理(PM) Android电源管理是一个基于标准Linux电源管理系统的轻量级的Android电源管理驱动,针对嵌入式 设备做了很多优化。利用锁和定时器来切换系统状态,控制设备在不同状态下的功耗,以达到节能的 目的。 其源代码分别位于: kernel/power/earlysuspend.c; kernel/power/consoleearlysuspend.c; kernel/power/fbearlysuspend.c; kernel/power/wakelock.c;

x0ckernel/power/urwakelock.c。 (3)低内存管理器(Low Memory Killer) Android中的低内存管理器和Linux标准的OOM(Out Of Memory)相比,其机制更加灵活,它可以根据 需要杀死进程来释放需要的内存。Low Memory Killer的代码非常简单,里面的关键是函数 lowmem_shrinker(),作为一个模块在初始化时调用register_shrinke注册一个lowmem_shrinker,它 会被vm在内存紧张的情况下调用。lowmem_shrinker完成具体操作。简单说就是寻找一个最合适的进 程杀死,从而释放它占用的内存。 其源代码位于:drivers/staging/android/lowmemorykiller.c。 (4)匿名共享内存(Ashmem) 匿名共享内存为进程间提供大块共享内存,同时为内核提供回收和管理这个内存的机制。如果一个程 序尝试访问Kernel释放的一个共享内存块,它将会收到一个错误提示,然后重新分配内存并重载数据 。 其源代码位于:mm/ashmem.c。 (5)Android PMEM(Physical) PMEM用于向用户空间提供连续的物理内存区域,DSP和某些设备只能工作在连续的物理内存上。驱动 中提供了mmap、open、relea和ioctl等接口。 源代码位于:drivers/misc/pmem.c。 (6)Android Logger Android Logger是一个轻量级的日志设备,用于抓取Android系统的各种日志,是Linux所没有的。 其源代码位于:drivers/staging/android/logger.c。 (7)Android Alarm Android Alarm提供了一个定时器,用于把设备从睡眠状态唤醒,同时它还提供了一个即使在设备睡 眠时也会运行的时钟基准。 其源代码位于: drivers/rtc/alarm.c; drivers/rtc/alarm-dev.c。 (8)USB Gadget驱动 此驱动是一个基于标准Linux USB gadget驱动框架的设备驱动,Android的USB驱动是基于gadget框架 的。 其源代码位于: dri

vers/usb/gadget/android.c; drivers/usb/gadget/f_adb.c; drivers/usb/gadget/f_mass_storage.c。 (9)Android RAM Console 为了提供调试功能,Android允许将调试日志信息写入一个被称为RAM Console的设备里,它是一个基 于RAM的Buffer。 其源代码位于:drivers/staging/android/ram_console.c。 (10)Android timed device Android timed device提供了对设备进行定时控制的功能,目前仅支持vibrator和LED设备。 其源代码位于:drivers/staging/android/timed_output.c(timed_gpio.c)。 (11)Yaffs2文件系统 在Android系统中,采用Yaffs2作为MTD nand flash文件系统。Yaffs2是一个快速稳定的应用于 NAND和NOR Flash的跨平台的嵌入式设备文件系统。同其他Flash文件系统相比,Yaffs2使用更小的内

x0c存来保存运行状态,因此它占用内存小;Yaffs2的垃圾回收非常简单且快速,因此能达到更好的性能 ;Yaffs2在大容量的NAND Flash上性能表现尤为明显,非常适合大容量的Flash存储。 其源代码位于“fs/yaffs2/”目录下。

x0c1.4 简析Linux内核 由于Android内核和Linux内核有密切的联系,因此在学习Android底层驱动开发之前必须先了解 Linux内核的基本知识。本节将简要介绍Linux内核的基本应用知识,为读者学习本书后面的高级知识 打下基础。

x0c1.4.1 内核的体系结构 图1-1所示是一个完整操作系统最基本的视图,由此可见内核的作用就是将应用程序和硬件分离开来 。

图1-1 操作系统的基本视图 内核的主要任务是负责与计算机硬件进行交互,实现对硬件的编程控制和接口操作,调度对硬件资源 的访问。除此之外,内核为用户应用程序提供一个高级的执行环境和访问硬件的虚拟接口。提供硬件 的兼容性是内核的设计目标之一,几乎所有的硬件都可以得到Linux的支持,只要不是为其他操作系 统所定制的。 与硬件兼容性相关的是可移植性,即在不同的硬件平台上运行Linux的能力。内核从最初只支持标准 IBM兼容机上的Intel X86架构到现在可以支持Alpha、ARM、MIPS、PowerPC等几乎所有硬件平台,如 此广泛的平台支持之所以能够成功,部分原因在于内核清晰地划分为了体系相关部分和体系无关部分 。 如图1-2所示是Linux操作系统的基本视图。由此可见,Linux内核分为如下两部分。

x0c图1-2 Linux操作系统的基本视图 ● 体系相关部分:这部分内核为体系结构和硬件所特有。 ● 体系无关部分:这部分内核是可移植的。体系无关部分通常会定义与体系相关部分的接口,这样 ,内核向新的体系结构移植的过程就变成确认这些接口的特性并将它们加以实现的过程。 用户应用程序和内核之间的联系是通过它和内核的中间层——标准C库来实现的。标准C库函数是建立 在内核提供的系

统调用基础之上的。通过标准C库及内核体系无关部分与体系相关部分的接口,用户 应用程序和部分内核都成为可移植的。根据上面的描述,给出Linux操作系统的标准视图,如图1-3所 示。

x0c图1-3 Linux系统的标准视图 图1-3中标准视图主要构成模块的具体说明如下所示。 (1)系统调用接口 为了与用户应用程序进行交互,内核提供了一组系统调用接口,通过这组接口,应用程序可以访问系 统硬件和各种操作系统资源。系统调用接口层在用户应用程序和内核之间添加了一个中间层,形象地 说,它扮演了一个函数调用多路复用和多路分解器的角色。 (2)进程管理 进程管理负责创建和销毁进程,并处理它们之间的互相联系(进程间通信),同时负责安排调度它们 去分享CPU。 进程管理部分实现了一个进程世界的抽象,这个进程世界类似于我们人类世界,只不过我们人类世界 里的个体是人,而在进程世界里则是一个一个的进程;人与人之间通过书信、手机、网络等进行交互 ,而各个进程之间则通过不同方式的进程间通信进行交互;我们所有人都在分享同一个地球,而所有 进程都在分享一个或多个CPU。 (3)内存管理 在进程世界里,内存是重要的资源之一,就好比我们的土地一样。因此,管理内存的策略与方式是决 定系统性能的一个关键因素。内核的内存管理部分根据不同的需要,提供了包括malloc/free在内的 许多简单或者复杂的接口,并为每个进程都提供了一个虚拟的地址空间,基本上实现了虚拟内存对进 程的按需分配。 (4)虚拟文件系统 虚拟文件系统为用户空间提供了文件系统接口,同时又为各个具体的文件系统提供了通用的接口抽象 。在VFS上面是对诸如open、clo、read和write等函数的一个通用API抽象,在VFS下面则是具体的 文件系统,它们定义了上层函数的实现方式。 通过虚拟文件系统,我们可以利用标准的Linux文件系统调用对不同介质上的不同文件系统进行操作 。VFS是内核在各种具体的文件系统上建立的一个抽象层,它提供了一个通用的文件系统模型,而该 模型囊括了我们所能想到的所有文件系统的行为。

x0c(5)网络功能 网络子系统处理数据包的收集、标识、分发,以及路由和地址的解析等所有网络有关的操作。 socket层是网络子系统的标准API,它为各种网络协议提供了一个用户接口。 (6)设备驱动程序 操作系统的目的是为用户提供一种方便访问硬件的途径,因此,几乎每一个系统操作最终都会映射到 物理的硬件设备上。除了CPU、内存等有限的几个对象外,所有设备的访问控制操作都要由相关的代 码来完成,这些代码就是所

谓的设备驱动程序。 (7)代码 这里的代码需要依赖体系结构,因为部分内核代码是体系相关的,在“linux/arch”子目录中定义了 内核源代码中依赖体系结构的部分,其中包含了对应各种特定体系结构的子目录。比如,对于一个典 型的桌面系统来说,使用的是i386目录。 每个特定体系结构对应的子目录又包含了很多下级子目录,分别关注内核中的一个特定方面,比如引 导、内核、内存管理等。

x0c1.4.2 和Android密切相关的Linux内核知识 我们知道,Android是在Linux 2.6的内核基础之上运行的,提供的核心系统服务包括安全、内存管理 、进程管理、网络组和驱动模型等内容。在接下来的内容中,将简要讲解上述核心系统服务的基本知 识。 1.安全 关于Linux安全的知识主要涉及用户权限问题和目录权限问题。 (1)Linux系统中的用户和权限 Linux系统中的每个文件和目录都有访问权限,用它来确定谁可以通过何种方式对文件和目录进行访 问和操作。Linux系统中规定了文件有如下三种不同类型的用户。 ● ur:文件拥有者。 ● group:同组用户,和网上邻居中的group组类似。 ● others:可以访问系统的其他用户。 (2)文件及目录权限的功能 Linux系统还规定了访问上述三种文件或目录的方式:读(r)、写(w)、可执行或查找(x)。 ● 读权限(r):表示只允许指定用户读取相应文件的内容,禁止对它做任何的更改操作,如目录读 权限表示可以列出存储在该目录下的文件,即读目录内容。 ● 写权限(w):表示允许指定用户打开并修改文件,如目录写表示允许你从目录中删除或创建新的 文件或目录。 ● 执行权限(x):表示允许将该文件作为一个程序执行。文件被创建时,文件所有者自动拥有对该 文件的读、写和可执行权限,以便于对文件的阅读和修改。 Linux系统在创建文件时会自动把该文件的读写权限分配给其属主,使用户能够显示和修改该文件。 也可以将这些权限改变为其他的组合形式。一个文件若有执行权限,则允许它作为一个程序被执行。 2.内存管理 内存管理是计算机编程最为基本的领域之一。虽然在很多脚本语言中,作为开发人员来说无须担心内 存是如何管理的,但是这并不能降低内存管理的重要性。对实际编程来说,理解内存管理器的能力与 局限性至关重要。在大部分系统语言中必须进行内存管理,例如C和C++。 其实在运行整个系统时,系统有多少内存,我们就有多少内存。我们无须花费心思和精力去弄清楚它 到底有多少内存,因为每一台机器的内存数量都是相同的。所以,如果内存需要非常固定,那么只需 要选择一个内存范围并使用它即

可。 但是即使是在这样一个简单的计算机中也会有问题,尤其是当我们不知道程序的每个部分将需要多少 内存时。如果空间有限,而内存需求是变化的,那么需要用一些方法来满足下面的需求。 ● 确定是否有足够的内存来处理数据。 ● 从可用的内存中获取一部分内存。 ● 向可用内存池(pool)中返回部分内存,以使其可以由程序的其他部分或者其他程序使用。 实现上述需求的程序库称为分配程序(allocator),因为它们负责分配和回收内存。程序的动态性 越强,内存管理就越重要,你的内存分配程序的选择也就更重要。我们要了解可用于内存管理的不同 方法,它们的好处与不足,以及它们最适用的情形。 3.进程管理 在Linux操作系统中包括如下三种不同类型的进程。 ● 交互进程:由一个Shell启动的进程。交互进程既可以在前台运行,也可以在后台运行。 ● 批处理进程:批处理进程和终端没有联系,是一个进程序列。 ● 守护进程:系统守护进程是Linux系统启动时启动的进程,在后台运行。 Linux管理进程的最好方法就是使用命令行下的系统命令。Linux下面的进程涉及的命令有ps、kill、 pgrep等。

x0c(1)父进程和子进程 父进程和子进程的关系是管理和被管理的关系,当父进程终止时,子进程也随之被终止。但如果子进 程被终止,父进程并不一定终止。比如当httpd服务器运行时,我们可以杀掉其子进程,父进程并不 会因为子进程的终止而终止。在进程管理中,当我们发现占用资源过多或存在无法控制的进程时,应 该杀死它,以保护系统的稳定安全运行。 (2)进程命令 在Linux中,通过命令来管理和操作进程。常用的命令可以分为如下几类。 ①监视进程命令 ● ps(process status命令)用于显示瞬间进程的动态,其使用方式如下所示。

ps的参数非常多,常用参数的具体说明如下所示。 ○ -a:显示所有终端机下执行的进程,除了阶段作业领导者之外。 ○ -w:采用宽阔的格式显示进程状况。 ○ -au:显示较详细的进程状况。 ○ -aux:显示所有包含其他用户的进程。 ● pstree命令:功能是以树状图显示运行的程序。pstree指令用ASCII字符显示树状结构,清楚地 表达程序间的相互关系。如果不指定程序识别码或用户名称,则会把系统启动时的第一个程序视为基 层,并显示之后的所有程序。若指定用户名称,便会以隶属该用户的第一个程序当做基层,然后显示 该用户的所有程序。其使用方式如下所示。

常用参数的具体说明如下所示。 ○ -a:显示每个程序的完整指令,包含路径、参数或常驻服务的标识。 ○ -c:如果有重复的进

程名,则分开列出(预设值会在前面加上*)。 ● top命令:Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于 Windows的任务管理器。其使用方式如下所示。

常用参数的具体说明如下所示。 ○ d:指定每两次屏幕信息刷新之间的时间间隔,可以使用s交互命令来改变。 ○ q:该选项将使top没有任何延迟地进行刷新。如果调用程序有超级用户权限,那么top将以尽可 能高的优先级运行。 ○ c:显示整个命令行,而不只是显示命令名。 ○ S:切换到累计模式。 ○ s:安全模式,避免潜在的危机。 ○ i:不显示任何闲置(idle)或无用(zombie)的进程。 ○ n:更新的次数,完成后将会退出top。 ②控制进程命令 向Linux系统的内核发送一个系统操作信号和某个程序的进程标识号,系统内核就可以操作进程标识 号指定的进程。例如在top命令中会看到系统运行的许多进程,有时就需要使用kill中止某些进程来 增加系统资源。在安装和登录命令中使用多个虚拟控制台的作用是当一个程序出错造成系统死锁时可 以切换到其他虚拟控制台工作关闭这个程序。此时使用的命令就是kill,因为kill是大多数Shell内 部命令可以直接调用的。

x0c在Linux系统中,使用kill命令来控制进程。kill可以删除执行中的程序或工作,可以将指定的信息 送至程序,预设的信息为SIGTERM(15),可将指定程序中止。如果仍然无法中止该程序,可以使用 SIGKILL(9)信息尝试强制删除程序。程序或工作的编号可利用ps指令或jobs指令查看。 使用kill命令的方式有如下两种。

各个参数的具体说明如下所示。 ● -l <信息编号>:如果不加<信息编号>选项,则-l参数会列出全部的信息名称。 ● -s <信息名称或编号>:指定要送出的信息。 注意: 进程是Linux系统中一个非常重要的概念。Linux是一个多任务的操作系统,系统中经常同时 运行着多个进程。我们不关心这些进程究竟是如何分配的,或者内核是如何管理分配时间片的,所要 关心的是如何去控制这些进程,让它们能够很好地为用户服务。 ③进程优先级设定(nice命令) 在当前程序运行优先级基础之上调整指定值得到新的程序运行优先级,用新的程序运行优先级运行命 令行“command[arguments...]”。优先级的范围为-20~19共40个等级,其中数值越小优先级越高 ,数值越大优先级越低,即-20的优先级最高,19的优先级最低。若调整后的程序运行优先级高于20,则以优先级-20来运行命令行;若调整后的程序运行优先级低于19,则以优先级19来运行命令行 。若nice命令未指定优先级的调整值,则以默认值10来调整程序运行优先

级,即在当前程序运行优先 级基础之上增加10。若不带任何参数运行命令nice,则显示出当前的程序运行优先级。 使用nice命令的方式如下所示。

各个参数的具体说明如下所示。 ● -n adjustment,-adjustment,--adjustment=adjustment:都将该原有优先级增加 adjustment。 ● --help:显示帮助信息。 ● --version:显示版本信息。 4.设备驱动程序 既然是一本讲解内核和驱动开发的书籍,就不可避免地要讲解设备驱动程序的知识。设备驱动程序用 于与系统连接的输入/输出设备通信,如硬盘、软驱、各种接口、声卡等。按照经典的UNIX箴言“万 物皆文件”(everything is a file)这一说法,对外设的访问可利用“/dev”目录下的设备文件来 完成,程序对设备的处理完全类似于常规的文件。设备驱动程序的任务在于支持应用程序通过设备文 件与设备通信。 通常可以将外设分为以下两类。 ● 字符设备:提供连续的数据流,应用程序可以顺序地读取,通常不支持随机存取。相反,此类设 备支持按字节/字符来读写数据。例如,调制解调器是典型的字符设备。 ● 块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘是典型的块设 备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是 512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。 编写块设备的驱动程序比字符设备要复杂得多,因为内核为提高系统性能广泛地使用了缓存机制。 5.网络 网卡也可以通过设备驱动程序控制,但在内核中属于特殊状况,因为网卡不能利用设备文件访问。原 因在于在网络通信期间,数据打包到了各种协议层中。在接收到数据时,内核必须针对各协议层的处 理,对数据进行拆包与分析,然后才能将有效数据传递给应用程序。在发送数据时,内核必须首先根

x0c据各个协议层的要求打包数据,然后才能发送。 为支持通过文件接口处理网络连接(按照应用程序的观点),Linux使用了源于BSD的套接字抽象。套 接字可以看做应用程序、文件接口、内核的网络实现之间的代理。

x0c1.5 分析Linux内核源代码很有必要 长期以来,学习内核的最好方法就是分析内核代码。内核代码本身就是最好的参考资料,其他任何经 典或非经典的书籍都只是起辅助作用,不能、也不应该取代内核代码在我们学习过程中的主导地位。 学习内核是一项浩大的工程,在学习之前需要首先做到以下三个方面。 (1)熟练使用Linux操作系统 Linux操作系统是Linux内核在用户层面的具体体现,只有熟练掌握Linux的基本操作,才能在内核

学 习的过程中达到事半功倍的效果。 (2)掌握操作系统理论基础 要掌握操作系统中比较基础的理论,比如分时(time-shared)和实时(real-time)的区别、进程的 概念、CPU和系统总线、内存的关系等。 (3)掌握C语言基础 不需要很精通C语言,但必须能够理解链表、散列表等数据结构的C语言实现,并熟练运用GCC编译器 。总之对C语言越熟悉,对我们的内核学习越有帮助。

x0c1.5.1 源代码目录结构 下载Linux内核源代码的官方地址为/,如图1-4所示。

图1-4 Linux内核官方下载界面 当下载内核代码后,很有必要知道内核源代码的整体分布情况。通常其内核代码保存在 “/usr/src/linux”目录下,该目录下的每一个子目录都代表了一个特定的内核功能性子集,接下来 将针对2.6.23版本进行简单描述。 (1)目录“Documentation” 此目录下面没有内核代码,只有很多质量参差不齐的文档,但往往能够为我们提供很多帮助。 (2)目录“arch” 所有与体系结构相关的代码都在此目录及“include/asm-*/”目录中,Linux支持的每种体系结构在 arch目录下都有对应的子目录,而在每个体系结构特有的子目录下又至少包含如下三个子目录。 ● kernel:存放支持体系结构特有的诸如信号量处理和SMP之类特征的实现。 ● lib:存放体系结构特有的对诸如strlen和memcpy之类的通用函数的实现。 ● mm:存放体系结构特有的内存管理程序的实现。 除了上述这三个子目录之外,大多数体系结构在必要的情况下还有一个boot子目录,其中包含了硬件 平台上启动内核所使用的部分或全部平台特有代码。另外在大部分体系结构所特有的子目录中,还应 该根据需要包含供附加特性使用的其他子目录。例如在i386目录中包含一个“math-emu”子目录,在 里面包含了在缺少数学协处理器(FPU)的CPU上运行模拟FPU的代码。 (3)目录“drivers” 此目录是内核中最庞大的目录,可以在其中找到显卡、网卡、SCSI适配器、PCI总线、USB总线和其他 任何Linux支持的外围设备或总线的驱动程序。 (4)目录“fs” 在此目录中保存了虚拟文件系统(VFS,Virtual File System)的代码,还有各个不同文件系统的代 码。Linux支持的所有文件系统在fs目录下面都有一个对应的子目录。例如ext2文件系统对应的是 fs/ext2目录。 文件系统是存储设备和需要访问存储设备的进程之间的媒介。存储设备可能是本地的物理上可访问的 ,比如硬盘或CD-ROM驱动器,它们分别使用ext2/ext3和isofs文件系统。也可能是通过网络访问的 ,使用NFS文件系统。 还有一些虚拟文件系统,例如proc是以一个标准文件系统出现的,然而它其中的文件只存

在于内存中 ,并不占用磁盘空间。 (5)目录“include” 在此目录中包含了内核中的大部分头文件,它们按照“include/asm-*/”的子目录格式进行分组。这 种格式的子目录有多个,每一个都对应着一个arch的子目录,例如“include/asm-alpha”、

x0c“include/asm-arm”和“include/asm-i386”等。在每个子目录下的文件中,都定义了支持给定体 系结构所必需的预处理器宏和内联函数,这些内联函数多数都是全部或部分使用汇编语言实现的。 在编译内核时,系统会建立一个从“include/asm”目录到目标体系结构特有的目录的符号链接。比 如对于arm平台,就是“include/asm-arm”到“include/asm”的符号链接。因此,体系结构无关部 分的内核代码可以使用如下形式包含体系相关部分的头文件。

(6)目录“init” 在此目录中保存了内核的初始化代码,包括文件main.c、创建早期用户空间的代码,以及其他初始化 代码。 (7)目录“ipc” IPC即进程间通信(Inter-Process Communication),在此目录中包含了共享内存、信号量以及其他 形式IPC的代码。 (8)目录“kernel” 此目录是内核中最核心的部分,包括进程的调度(kernel/sched.c),以及进程的创建和撤销 (kernel/fork.c和kernel/exit.c)等,和平台相关的另外一部分核心的代码在arch/*/kernel目录中 。 (9)目录“lib” 此目录中保存了库代码,这些代码实现了一个标准C库的通用子集,包括字符串和内存操作的函数 (strlen、mmcpy和其他类似的函数),以及有关sprintf和atoi的系列函数。与arch/lib下的代码不 同,这里的库代码都是使用C语言编写的,在内核新的移植版本中可以直接使用。 (10)目录“mm” 在此目录中包含了体系结构无关部分的内存管理代码,体系相关的部分位于arch/*/mm目录下。 (11)目录“net” 在此目录中保存了和网络相关的代码,实现了各种常见的网络协议,如TCP/IP、IPX等。 (12)目录“scripts” 在该目录中没有内核代码,只包含了用来配置内核的脚本文件。当运行make menuconfig或者make xconfig之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。 (13)目录“block” 在此目录中保存了block层的实现代码。最初block层的代码一部分位于drivers目录,一部分位于 fs目录,从2.6.15开始,block层的核心代码被提取出来放在了顶层的block目录中。 (14)目录“crypto” 在此目录中保存了内核本身所用的加密API信息,实现了常用的加密和散列算法,还有一些压缩和 CRC校验算法。 (15)目录“curity” 在此目录中包括不同的Linux安全模型的代码,比如NSA Security-Enhanced Linux。 (16)目录“sound” 在此目录中保存了声卡驱动及其他声

音相关的代码。 (17)目录“usr” 此目录实现了用于打包和压缩的cpio等。

x0c1.5.2 浏览源代码的工具 俗话说“工欲善其事,必先利其器”。一个功能强大并方便的代码浏览工具,会有助于我们学习内核 代码这项“工程”。接下来将简单介绍浏览Linux内核源代码的常用工具。 1.Source Insight 在Windows下,最为方便快捷的代码浏览工具是Source Insight,这是一款商业软件。Source Insight是一个面向项目开发的程序编辑器和代码浏览器,它拥有内置的对C/C++、C#和Java等程序的 分析。Source Insight可以分析源代码并在工作的同时动态维护它自己的符号数据库,并自动为我们 显示有用的上下文信息。Source Insight不仅仅是一个强大的程序编辑器,还能显示reference trees、class inheritance diagrams和call trees等信息。Source Insight提供了最快速的对源代 码的导航和任何程序编辑器的源信息。Source Insight提供了快速和革新的访问源代码和源信息的能 力。与众多其他编辑器产品不同,Source Insight能在你编辑的同时分析源代码,提供实用的信息并 立即进行分析。 安装Source Insight后,需要先打开Source Insight并创建一个工程,然后将内核代码加入到该工程 中,并进行文件同步,这样就可以在代码之间进行关联阅读了。 Source Insight的缺点是并没有对应Linux的版本。因此对于很多Linux初学者来说,在一个完全的 Linux环境下进行学习,需要寻找一个可以取代Source Insight的代码浏览工具。 2.Vim+Cscope Linux环境下的最佳浏览工具是Vim,各种Linux发行版都会默认进行安装。虽然Vim默认的编辑界面很 普通,甚至丑陋,但是可以通过配置文件“.vimrc”添加不同的界面效果。同时还可以配合 TagList、WinManager等很多好用的插件或工具,将Vim打造成一个不次于Source Insight的代码浏览 编辑工具。 3.LXR LXR(Linux Cross Reference)也是一款比较流行的Linux内核源代码浏览工具,其下载地址为 /。 如果我们的目的只是浏览Linux内核代码,那么并不需要去安装LXR。因为在网站 /上已经提供了几乎所有版本的Linux内核在线阅读代码,我们只需要登录该网 站,选择某一特定的内核版本后,就可以在内核代码之间进行关联阅读。 当登录网站并选择内核版本后,在查找框内输入要查找的内核代码符号名称,就可以搜索到所有以超 链接形式给出的对该符号定义和引用的确切位置。

x0c1.5.3 为什么用汇编语言编写内核代码 很多读者可能禁不住要问,Java、C++和C#功能强大,Visual Basic易于使用,为什么还要使用古老 的汇编语言来编写内核代码呢?这是因为出于以下三个方面的考虑。 ● Linux内核中的底层代

码直接和硬件打交道,需要一些专用的指令,而这些指令在C语言中并无对 应的语言成分。 ● 内核中实现某些操作的过程、代码段或函数,在运行时会被很频繁地调用,这时用汇编语言编写 ,其时间效率会有大幅度提高。 ● 在某些特别的场合,一段代码的空间效率也很重要,比如操作系统的引导程序一定要容纳在磁盘 的第一个扇区中,多一个字节都不行。这时只能用汇编语言编写。 在Linux内核代码中,以汇编语言编写的代码有如下两种不同的形式。 ● 完全的汇编代码,这样的代码采用“.s”作为文件名的后缀。 ● 嵌入在C代码中的汇编语言片段。 对于新接触Linux内核源代码的读者,即使比较熟悉i386汇编语言,在理解内核中的汇编代码时都会 感到困难。原因是在内核的汇编代码中,采用的是不同于常用Intel i386汇编语言的AT&T格式的汇编 语言,而在嵌入C代码的汇编语言片段中,更是增加了一些指导汇编工具如何分配使用寄存器、如何 与C代码中定义的变量相结合的语言成分。这些成分使得嵌入C代码的汇编语言片段实际上变成了一种 介于汇编和C之间的中间语言。

x0c1.5.4 Linux内核的显著特性 在下载Linux内核代码并安装浏览工具后,就可以浏览并分析Linux内核的源代码了。接下来将简要讲 解Linux内核的显著特性,为读者学习本书后面的知识打下基础。 1.GCC特性 Linux内核使用GNU Compiler Collection(GCC)套件的几个特殊功能。这些功能包括提供快捷方式 和简化以及向编译器提供优化提示等。GCC和Linux是出色的组合。尽管它们是独立的软件,但是 Linux完全依靠GCC在新的体系结构上运行。Linux还利用GCC中的特性(称为扩展)实现更多功能和优 化。 (1)基本功能 概括来说,GCC具有如下两大功能。 ● 功能性:扩展提供新功能。 ● 优化:扩展帮助生成更高效的代码。 GCC允许通过变量的引用识别类型,这种操作支持泛型编程。在C++、Ada和Java语言等许多现代编程 语言中都可以找到相似的功能。例如Linux使用typeof构建min和max等依赖于类型的操作。使用 typeof构建一个泛型宏的代码如下所示。

GCC还支持范围,在C语言的许多方面都可以使用范围。最常见的是在switch/ca块中的ca语句中 使用。使用switch/ca也可以通过使用跳转表实现进行编译器优化。在复杂的条件结构中,通常依 靠嵌套的if语句实现与下面代码相同的结果,但是下面的代码更简洁。

(2)属性 GCC允许声明函数、变量和类型的特殊属性,以便指示编译器进行特定方面的优化和更仔细的代码检 查。使用方式非常简单,只需在声明后面加上如下代码即可(代码中的__是两个英文下画线

)。

其中ATTRIBUTE是属性的说明,多个说明之间以逗号分隔。GCC可以支持十几个属性,接下来将介绍一 些比较常用的属性。 ● 属性noreturn 属性noreturn用在函数中,表示该函数从不返回。它能够让编译器生成较为优化的代码,消除不必要 的警告信息。

x0c● 属性format(archetype, string-index, first-to-check) 属性format用在函数中,表示该函数使用printf、scanf、strftime或strfmon风格的参数,并可以让 编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。 ● archetype:指定是哪种风格。 ● string-index:指定传入函数的第几个参数是格式化字符串。 ● first-to-check:指定从函数的第几个参数开始按照上述规则进行检查。 例如下面的内核代码:

表示printk的第一个参数是格式化字符串,从第二个参数开始根据格式化字符串检查参数。 ● 属性unud 属性unud用于函数和变量,表示该函数或变量可能并不使用,这个属性能够避免编译器产生警告信 息。 ● 属性aligned(ALIGNMENT) 属性aligned常用在变量、结构或联合中,用于设定一个指定大小的对齐格式,以字节为单位,例如 下面的内核代码。

在上述代码中,表示结构体ohci_hcca的成员以256字节对齐。如果aligned后面不紧跟一个指定的数 字值,那么编译器将依据目标机器情况使用最大、最有益的对齐方式。 需要注意的是,属性attribute的效果与我们的连接器也有关,如果连接器最大只支持16字节对齐 ,那么此时定义32字节对齐也是无济于事的。 ● 属性packed 属性packed用在变量和类型中,当用于变量或结构体成员时表示使用最小可能的对齐,当用于枚举、 结构体或联合类型时表示该类型使用最小的内存。属性packed多用于定义硬件相关的结构时,使元素 之间不会因对齐产生问题。例如下面的内核代码。

x0c在上述代码中,__attribute__ ((packed))告诉编译器usb_interface_descriptor的元素为1字节对 齐,不要再添加填充位。因为定义此结构的代码和usb spec中的是完全一样的。如果不给编译器这个 暗示,则编译器会依据平台的类型在结构的每个元素之间添加一定的填充位,使用这个结构时就不能 达到预期的结果。 (3)内建函数 在GCC中提供了大量的内建函数,其中有很多是标准C库函数的内建版本,例如memcpy(),它们的功能 与对应的C库函数的功能相同,在此不再进行讲解。 在内建函数中,还有很多函数的名字是以__builtin开始的。接下来将对__builtin_expect()进行详 细分析,其他__builtin_xxx()函数的原理和此函数类似,本书中不再一一介绍。 函数__builtin_expect()的格式如下所示。

为什么Linux会推出__builtin_xxx()函数呢

?这是因为大部分代码在分支预测方面做得比较糟糕,所 以GCC提供了此内建函数来帮助处理分支预测并优化程序。 ● 第一个参数exp:是一个整型的表达式,返回值也是此exp。 ● 第二个参数c:其值必须是一个编译期的常量。 由此可见,此内建函数的意思就是exp的预期值为c,编译器可以根据这个信息适当地重排条件语句块 的顺序,将符合这个条件的分支放在合适的地方。 我们看此函数在Linux内核中的应用,具体代码如下所示。

代码unlikely(x)用于告诉编译器条件x发生的可能性不大,likely(x)用于告诉编译器条件x发生的可 能性很大。它们一般用在条件语句里,if语句不变,当if条件为1的可能性非常小的时候,可以在条 件表达式外面包装一个unlikely(),那么这个条件块里语句的目标码可能就会被放在一个比较远的位 置,以保证经常执行的目标码更紧凑。如果可能性非常大,则使用likely()包装。 2.链表的重要性 链表和本书中讲解的驱动密切相关,例如USB驱动。鉴于链表在内核中的特殊地位,有必要在此对其 做一番陈述。内核中链表的实现位于“include/linux/list.h”文件中,链表数据结构的定义也很简 单。具体代码如下所示。

x0c结构list_head包含两个指向list_head结构的指针prev和next,由此可见,内核中的链表实际上都是 双链表。学过C语言的读者应该知道,链表的定义结构如下所示。

以上述格式使用链表时,对于每一种数据类型都要定义它们各自的链表结构。而内核中的链表却与此 不同,它并没有数据域,不是在链表结构中包含数据,而是在描述数据类型的结构中包含链表。 如果在hub驱动中使用struct usb_hub来描述hub设备,hub需要处理一系列的事件,比如当探测到一 个设备连进来时,就会执行一些代码去初始化该设备,所以hub就创建了一个链表来处理各种事件 ,这个链表的结构如图1-5所示。

图1-5 hub链表的结构 在Linux代码中完整展示了链表的操作过程,接下来将简单剖析对应的Linux代码。 (1)声明与初始化 可以使用如下两种方式来声明链表。 ● 使用LIST_HEAD宏在编译时静态初始化。 ● 使用INIT_LIST_HEAD()在运行时进行初始化。 对应的Linux代码如下所示。

x0c无论采用哪种方式,新生成的链表头的两个指针next、prev都初始化为指向自己。 (2)判断链表是否为空 对应的Linux代码如下所示。

(3)插入操作 建立链表后,就不可避免地要对其进行操作,例如向其中添加数据。使用函数list_add()和 list_add_tail()可以完成添加数据的工作。对应的Linux代码如下所示。

其中,函数list_add()将数据插入head之后,函数list_add_tail()将数据插入head->prev之后。对 于

循环链表来说,因为表头的next、prev分别指向链表中的第一个和最后一个节点,所以函数 list_add()和list_add_tail()的区别并不大。 (4)删除操作 可以使用函数list_replace_init()从链表中删除一个元素,并将其初始化。对应的Linux代码如下所 示。

(5)遍历操作 在内核中的链表仅保存了list_head结构的地址,我们应该如何通过它获取一个链表节点真正的数据 项呢?此时需要使用list_entry宏,通过它可以很容易地获得一个链表节点的数据。对应的Linux代

x0c码如下所示。

以hub驱动为例,当要处理hub的事件的时候,需要知道具体是哪个hub触发了这起事件。而 list_entry的作用是从struct list_head event_list中得到它所对应的struct usb_hub结构体变量 。例如下面的代码。

通过上述代码,从全局链表hub_event_list中取出了结构体变量tmp,然后通过tmp获得它所对应的 struct usb_hub。 3.Kconfig和Makefile Kconfig和Makefile是浏览内核代码时最为倚仗的两个文件之一。几乎Linux内核中的每一个目录下边 都有一个Kconfig文件和一个Makefile文件。通过Kconfig和Makefile,我们可以了解一个内核目录下 面的结构。在研究内核的某个子系统、某个驱动或其他某个部分之前,需要仔细阅读目录下对应的 Kconfig和Makefile文件。 (1)Kconfig结构 每种平台对应的目录下面都有一个Kconfig文件,例如“arch/i386/Kconfig”。Kconfig文件通过 source语句可以构建一个Kconfig树。文件“arch/i386/Kconfig”的代码片段如下所示。

Kconfig的详细语法规则可以参看内核文档Documentation/kbuild/,对其有简 单介绍。 ①菜单项

x0c关键字config可以定义一个新的菜单项,比如下面的代码。

后面的代码定义了该菜单项的属性,包括类型、依赖关系、选择提示、帮助信息和默认值等。常用的 类型有bool、tristate、string、hex和int。类型为bool的只能被选中或不选中,类型tristate的菜 单项多了编译成内核模块的选项。 依赖关系是通过“depends on”或“requires”定义的,指出此菜单项是否依赖于另外一个菜单项。 帮助信息需要使用“help”或“---help---”来指出。 ②菜单组织结构 菜单选项通过如下两种方式来组成树状结构。 ● 第一种方式:使用关键字“menu”显式声明为菜单,例如下面的代码。

● 第二种方式:也可以使用依赖关系确定菜单结构,例如下面的代码。

因为菜单项MODVERSIONS依赖于MODULES,所以它就是一个子菜单项。这要求菜单项和它的子菜单项同 步显示或不显示。 ③关键字Kconfig Kconfig文件描述了一系列的菜单选项,除帮助信息外,文件中的每一行都以一个关键字开始,主要 有config、menuconfig、choice/endchoice、comments、menu/endmenu

、if/endif、source等,只有 前5个可以用在菜单项定义的开始,它们都可以结束一个菜单项。 (2)Makefile Linux内核的Makefile分为如下5个组成部分。 ● Makefile:顶层的Makefile。 ● .config:内核的当前配置文档,编译时成为顶层Makefile的一部分。 ● arch/$(ARCH)/Makefile:和体系结构相关的Makefile。 ● Makefile.*:一些特定Makefile的规则。 ● kbuild级别Makefile:各级目录下的大约500个文档,编译时根据上层Makefile传下来的宏定义 和其他编译规则,将源代码编译成模块或编入内核。顶层的Makefile文档读取.config文档的内容 ,并总体上负责build内核和模块。Arch Makefile则提供补充体系结构相关的信息。其中.config的 内容是在运行make menuconfig的时候,通过Kconfig文档配置的结果。

x0c假如想把自己写的一个Flash驱动程序加载到工程中,并且能够在通过menuconfig配置内核时选择该 驱动,此时该怎么办呢?方法是借助Makefile实现,解决流程如下所示。 ● 将编写的flashtest.c文档添加到“/driver/mtd/maps/”目录下。 ● 修改“/driver/mtd/maps”目录下的kconfig文档,修改代码如下所示。

这样当运行make menuconfig时会出现“ap71 flash”选项。 ● 修改该目录下makefile文档,添加下面的代码内容。

此时当运行make menucofnig时会发现“ap71 flash”选项,假如选择了此选项,该选择就会保存在 “.config”文档中。当编译内核时会读取“.config”文档,当发现“ap71 flash”选项为yes时 ,系统在调用“/driver/mtd/maps/”下的makefile时,会把flashtest.o加入内核中。

x0c1.5.5 学习Linux内核的方法 学习Linux内核的最大工作就是对内核代码进行分析,如果抱着走马观花、得过且过的态度,最终结 果很可能是没有多大的收获。学习内核应该遵循科学、严谨的态度,要做到真正理解每一段代码的实 现,并且在学习过程中多问、多想、多记。 上述学习Linux内核的方法非常重要。接下来将通过两个具体的应用来演示学习Linux内核的过程。 1.分析USB子系统的代码 Linux内核中USB子系统的代码位于“drivers/usb”目录下,进入该目录,执行命令ls后将会显示如 下结果。

目录“drivers/usb”一共包含10个子目录和4个文件,为了理解每个子目录的作用,有必要首先阅读 README文件。根据README文件的描述,得知“drivers/usb”目录下各个子目录的作用,具体说明如 下所示。 (1)core 这是内核开发者针对部分核心的功能特意编写的代码,用于为其他的设备驱动程序提供服务,比如申 请内存,实现所有的设备都会需要的一些公共函数,并命名为USB core。 (2)host 早期的内核结构并不像现在这般富有层次感,几乎所有的文件都直接堆砌在“d

rivers/usb/”目录下 ,这其中包括“usb core”和其他各种设备驱动程序的代码。 后来在“drivers/usb/”目录下面单独列出了“core”子目录,用于存放一些比较核心的代码,比如 整个USB子系统的初始化、root hub的初始化、host controller的初始化代码。 后来随着技术的发展,出现了多种USB host controller,于是内核开发者把host controller有关的 公共代码保留在“core”目录下,而其他各种host controller对应的特定代码则移到“host”目录 下,让相应的负责人去维护。为此针对host controller单独创建了子目录“host”,用于存放与其 相关的代码。 (3)gadget 用于存放USB gadget的驱动程序,控制外围设备如何作为一个USB设备和主机通信。比如,嵌入式开 发板通常会支持SD卡,使用USB连接线将开发板连接到PC时,通过USB gadget架构的驱动,可以将该 SD卡模拟成U盘。 除core、host和gadget之外,其他几个目录分门别类存放了各种USB设备的驱动,例如U盘的驱动位于 “storage”子目录,触摸屏和USB键盘鼠标的驱动位于“input”子目录。 因为我们的目的是研究内核对USB子系统的实现,而不是特定设备或host controller的驱动,所以通 过对README文件的分析,应该进一步关注“core”子目录。 2.分析USB系统的初始化代码 分析Kconfig和Makefile文件可以帮助我们在庞大复杂的内核代码中定位及缩小目标代码的范围。为 了研究内核对USB子系统的实现,需要在目标代码中找到USB子系统的初始化代码。 Linux内核针对某个子系统或某个驱动,使用subsys_initcall或module_init宏来指定初始化函数。 在内核文件“drivers/usb/core/usb.c”中,可以发现下面的代码。

在上述代码中,可以将subsys_initcall理解为module_init,只不过因为该部分代码比较核心,开发 者们把它看做一个子系统,而不仅仅是一个模块。在Linux中,类似此类别的设备驱动被归结为一个 子系统,例如PCI子系统和SCSI子系统。通常“drivers/”目录下第一层的每个子目录代表一个子系 统,因为它们分别代表了一类设备。

x0csubsys_initcall(usb_init)表示函数usb_init()是USB子系统的初始化函数,而module_exit则表示 usb_exit函数是USB子系统结束时的清理函数。为了研究USB子系统在内核中的实现,需要从函数 usb_init()开始分析,对应的内核代码如下所示。

x0c接下来分析上述代码。 (1)标记__init 关于usb_init,第一个问题是上述第一行代码中的__init标记有什么意义?在前面讲解GCC扩展的特 殊属性ction时曾经提到,__init修饰的所有代码都会被放在.节,当初始化结束后就可 以释放这部分内存。但是内核是如何调用到__init所修饰的这些初始化函数的呢?要回答这个问题 ,

需要用到subsys_initcall宏的知识,它在文件“include/linux/init.h”中的定义格式如下所示 。

x0c此时出现了一个新的宏__define_initcall,它用来将指定的函数指针fn存放到 “.”节。subsys_initcall宏,则表示把fn存放到“.”的子节 “.”。 要理解“.”、“.”和“.”之类的符号,还需要了解和内 核可执行文件相关的概念。内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节 ,如文本、数据、init数据、bass等。这些对象文件都是由一个称为“链接器脚本”的文件链接并装 入的,这个文件的功能是将输入对象文件的各节映射到输出文件中。换句话说,它将所有输入对象文 件都链接到单一的可执行文件中,将该可执行文件的各节装入指定地址处。是保存在 “arch//”目录中的内核链接器脚本,它负责链接内核的各个节并将它们装入内存中特定偏 移量处。 打开文件“arch/i386/kernel/”,搜索关键字“”后便会看到如下结果 。

其中“__initcall_start”指向“.”节的开始,“__initcall_end”指向 “.”节的结尾。而“.”节又被分为如下7个子节。

宏subsys_initcall将指定的函数指针放在了“.”子节,至于其他宏,功能也类似 ,例如core_initcall将函数指针放在了“.”子节,device_initcall将函数指针放 在了“.”子节等。 各个子节的顺序是确定的,即先调用“.”中的函数指针,然后调用 “.”中的函数指针。__init修饰的初始化函数在内核初始化过程中调用的顺序和 .节里函数指针的顺序有关,不同的初始化函数被放在不同的子节中,因此就决定了它 们的调用顺序。 (2)模块参数 在前面usb_init()函数代码中,代码nousb在文件“drivers/usb/core/usb.c”中定义为如下格式。

x0c从中可知nousb是个模块参数,用于在内核启动的时候禁止USB子系统。关于模块参数,可以在加载模 块的时候指定,但是如何在内核启动的时候指定?打开系统的grub文件,找到kernel行,例如下面的 代码。

其中的root、splash、vga等都表示内核参数。当某一模块被编译进内核的时候,它的模块参数便需 要在kernel行中指定,其格式为:

例如下面的代码。

对应到kernel行的代码如下所示:

通过命令“modinfo -p ${modulename}”可以得知一个模块有哪些参数可以使用。而对于已经加载到 内核里的模块,其模块参数会列举在“/sys/module/$ {modulename}/parameters/”目录下面,可以 使用如下命令去修改。

关于函数usb_init(),除了上面介绍的代

码外,余下的代码分别完成usb各部分的初始化,其他代码 的具体分析工作可以参阅下载的Linux内核代码,具体含义可以参阅相关的书籍和资料。因为篇幅限 制,在此不再进行详细介绍。 第2章 分析Android源代码 通过前面的介绍,我们学习了Linux内核的基本知识,并了解了学习Linux内核的方法。本章将简要分 析Android源代码的基本知识,介绍各个目录中主要文件的功能和架构原理,为读者学习本书后面的 高级知识打下基础。

x0c2.1 搭建Linux开发环境和工具 因为Android是基于Linux的,所以作为底层应用的Android驱动开发需要在Linux环境下进行。在进行 开发之前很有必要讲解搭建开发环境的知识,接下来将简单介绍搭建Linux开发环境的方法,其中包 括安装开发工具的知识。

x0c2.1.1 搭建Linux开发环境 在Linux环境下开发Android程序时需安装相关的包并配置Java环境,具体流程如下所示。 1.安装包 如果是在Ubuntu主机上,则需要安装下面的包。

2.安装Java环境JDK

Android的官方文档说,如果使用sun-java6-jdk可能会出问题,需要用sun-java5-jdk。笔者经过测 试发现,如果仅仅用make(make不包括make sdk),使用sun-java6-jdk是没有问题的。但是如果运 行make sdk就会发生问题,也就是说在使用“make doc”命令这一步骤时出问题,它需要的 javadoc版本为1.5。 所以在此建议读者,在安装sun-java6-jdk后最好再安装sun-java5-jdk,或者只安装sun-java5jdk。笔者的计算机中,sun-java6-jdk和sun-java5-jdk都安装了,并且只修改了和 javadoc。因为只有这两者是make sdk用到的。这样除了javadoc工具使用1.5版本外,其他的都使用 1.6版本。命令如下:

然后还需要修改javadoc的link。

x0c2.1.2 设置环境变量 在Linux环境下开发Android时需要设置环境变量,命令如下所示。

然后需要在.bashrc中新增或整合PATH变量,例如和Java程序开发/运行相关的一些环境变量。

最后不要忘记同步上述变化,命令如下所示。

x0c2.1.3 安装编译工具 在Linux环境下开发Android程序时必须安装编译工具,例如git和repo。其中repo的作用十分重要 ,功能是更新Android源代码,是一种能够调用git的封装工具。 安装repo的流程如下所示。 step1 创建~/bin目录,用来存放repo程序。

step2 将目录添加到环境变量PATH中,然后下载repo脚本并使其可执行。

另外在编译Android系统时,需要使用编译主机的工具GCC。对于编译目标主机文件,Android在 prebuilt目录中集成了GCC交叉编译工具链。

x0c2.2 获取Android源代码 可以登录/获取Android的源代码,在如下网页上详细介绍了获取 Android源代码的方法,如图2-1所示。

图2-1 Linux下获取Android源代码

的方法 在下载源代码时,需要使用repo或git工具来实现。接下来将详细介绍使用工具获取Android源代码的 流程。 step1 创建源代码下载目录,命令如下所示。

step2 用repo工具初始化一个版本,假如是Android 2.2r2,则命令如下所示。

在初始化过程中会显示相关版本的TAG信息,同时会提示我们输入用户名和邮箱地址,上面的命令初 始化的是Android 2.2 froyo的最新版本。 step3 Android 2.2有很多个版本,这些版本信息可以从TAG信息中看出来。当前froyo的所有版本 信息如下所示。

每次下载的都是最新的版本,当然也可以根据TAG信息下载某一特定的版本。例如下面的命令:

step4 开始下载源代码,命令如下所示。

x0cfroyo版本的代码很大,超过2GB,下载过程非常漫长,需要耐心等待。 step5 最后一步是编译代码,命令如下所示。

上述以repo获取的方式获取源代码的速度非常慢,我们可以通过网页浏览的方式来访问Android代码 库,其浏览路径是/,界面如图2-2所示。

图2-2 通过页面浏览方式访问Android代码库 注意: 因为上述获取Android源代码的过程非常缓慢,所以一般建议不要使用repo来下载Android源 代码,建议直接登录/bbs/pub/来下载,解压出来的 “cupcake”中也有“.repo”文件夹,此时可以通过repo sync来更新cupcake代码。命令如下所示。

x0c2.3 分析并编译Android源代码 获取Android源代码之后,接下来就可以分析其具体结构并进行编译工作了。本节将简要分析 Android源代码的结构并介绍编译Android源代码的方法。

x0c2.3.1 Android源代码的结构 可以将Android源代码的全部工程分为如下三个部分。 ● Core Project:核心工程部分,这是建立Android系统的基础,保存在根目录的各个文件夹中。 ● External Project:扩展工程部分,可以使其他开源项目具有扩展功能,保存在“external”文 件夹中。 ● Package:包部分,提供了Android的应用程序、内容提供者、输入法和服务,保存在 “package”文件夹中。 无论是Android 1.5还是Android 2.2和Android 2.3,各个版本的源代码目录基本类似。在里面包含 了原始Android的目标机代码、主机编译工具和仿真环境。在接下来的内容中,将简单介绍Android源 代码的目录结构,并注释常用目录的含义。 (1)第一级目录 解压缩代码包后,第一级别的目录和文件的结构如下所示。 |– Makefile:全局的Makefile; |– bionic:bionic含义为仿生,这里面是一些基础的库的源代码; |– bootloader:引导加载器; |– build:里面的内容不是目标所用的代码,而是编译和配置所需要的脚本和工具; |– dalvik:Java虚拟机; |– d

evelopment:程序开发所需要的模板和工具; |– external:目标机器使用的一些库; |– frameworks:应用程序的框架层; |– hardware:与硬件相关的库; |– kernel:Linux 2.6的源代码; |– packages:Android的各种应用程序; |– prebuilt:Android在各种平台下编译的预置脚本; |– recovery:与目标的恢复功能相关; `– system:Android的底层的一些库。 ● Makefile:整个Android编译所需要的真正的Makefile,它被顶层目录的Makefile引用。 ● Makefile目录下的:在使用仿真器运行的时候,用于设置环境的脚本。 ● dalvik目录:提供Android Java应用程序运行的基础——Java虚拟机。 (2)development目录 展开development目录后,里面的同一个级别的目录结构如下所示。 development/ |– apps:Android应用程序的模板; |– build:编译脚本模板; |– cmds; |– data; |– docs; |– emulator:仿真相关; |– host:包含Windows平台的一些工具; |– ide; |– pdk;

x0c|– samples:一些示例程序; |– simulator:大多是目标机器的一些工具; `– tools。 ● 目录“emulator”:里面的“qemud”是使用QEMU仿真时目标机器运行的后台程序,“skins”是 仿真时手机的界面。 ● 目录“samples”:在里面包含了很多Android简单工程,这些工程为开发者学习开发Android程 序提供了很大便利,可以作为模板使用。 (3)external目录 展开external目录后,里面的同一个级别的目录结构如下所示。 external/ |– aes |– apache-http |– bluez |– clearsilver |– dbus |– dhcpcd |– dropbear |– elfcopy |– elfutils |– emma |– esd |– expat |– fdlibm |– freetype |– gdata |– giflib |– googleclient |– icu4c |– iptables |– jdiff |– jhead |– jpeg |– libffi |– libpcap |– libpng |– libxml2 |– netcat |– netperf |– neven |– opencore

x0c|– openssl |– oprofile |– ping |– ppp |– protobuf |– qemu |– safe-iop |– skia |– sonivox |– sqlite |– srec |– strace |– tagsoup |– tcpdump |– tinyxml |– tremor |– webkit |– wpa_supplicant |– yaffs2 `– zlib 在上述external目录中,每个目录表示Android目标系统中的一个模块,这些模块可能由一个或者多 个库构成。其中常用目录的具体说明如下所示。 ● opencore:Android多媒体框架的核心。 ● webkit:Android网络浏览器的核心。 ● sqlite:Android数据库系统的核心。 ● openssl:Secure Socket Layer,表示一个网络协议层,用于为数据通信提供安全支持。 (4)frameworks目录 展开frameworks目录后,里面的同一个级别的目录如下所示。 frameworks/ |– ba |– opt `– policies ● frameworks:Android应用程序的框架。 (5)packages目录 展开packages目录后,发现里面包含了两个目录,其中在

目录“apps”中保存了Android的各种应用 程序,在目录“providers”中保存了内容提供者的信息,即在Android中的一个数据源。展开 “packages”目录后的具体结构如下所示。 packages/ |– apps | |– AlarmClock | |– Browr

x0c| |– Calculator | |– Calendar | |– Camera | |– Contacts | |– Email | |– GoogleSearch | |– HTMLViewer | |– IM | |– Launcher | |– Mms | |– Music | |– PackageInstaller | |– Phone | |– Settings | |– SoundRecorder | |– Stk | |– Sync | |– Updater | `– VoiceDialer `– providers |– CalendarProvider |– ContactsProvider |– DownloadProvider |– DrmProvider |– GoogleContactsProvider |– GoogleSubscribedFeedsProvider |– ImProvider |– MediaProvider `– TelephonyProvider 目录“packages”中两个子目录的内容大多数都是使用Java编写的程序,各个文件夹的层次结构是类 似的。 (6)prebuilt目录 展开prebuilt目录后的同级别目录结构如下所示。 prebuilt/ |– |– android-arm |– common |– darwin-x86 |– linux-x86 `– windows

x0c(7)system目录 展开system目录后,两个级别的目录结构如下所示。 system/ |– bluetooth | |– bluedroid | `– brfpatch |– core | |– | |– README | |– adb | |– cpio | |– debuggerd | |– fastboot | |– include:各个库接口的头文件 | |– init | |– libctest | |– libcutils | |– liblog | |– libmincrypt | |– libnetutils | |– libpixelflinger | |– libzipfile | |– logcat | |– logwrapper | |– mkbootimg | |– mountd | |– netcfg | |– rootdir | |– sh | `– toolbox |– extras | |– | |– latencytop | |– libpagemap | |– librank | |– procmem | |– procrank | |– showmap | |– showslab | |– sound

x0c| |– su | |– tests | `– timeinfo `– wlan `– ti

x0c2.3.2 编译Android源代码 编译Android源代码的方法非常简单,只需使用Android源代码根目录下的Makefile执行make命令即可 轻松实现。当然在编译Android源代码之前,首先要确定已经完成同步工作。进入Android源代码目录 ,使用make命令进行编译。

编译Android源代码可以得到“~/project/android/cupcake/out”目录,整个编译过程需要很久,需 要耐心等待。 虽然编译方法非常简单,但是初学者很容易出错。接下来将简单介绍在编译过程中的常见错误,并给 出解决这些问题的方法。 (1)缺少必要的软件 进入Android目录下,使用make命令编译,可能会发现出现如下错误提示。

上述错误是因为缺少zlib1g-dev,需要使用apt-get命令从软件仓库中安装zlib1g-dev,具体命令如 下所示。

同理,我们必须安装

下面的软件,否则也会出现类似的错误。

(2)没有安装Java环境JDK 当安装上述所有软件后,运行make命令再次编译Android源代码。如果在之前忘记安装Java环境 JDK,则此时会出现很多Java文件无法编译的错误,如果打开Android的源代码,可以在如下目录中下 发现有很多Java源文件。

这充分说明在编译Android之前必须先安装Java环境JDK,并且是Linux版本的。安装流程如下所示。 step1 从Oracle官方网站下载文件并安装它。 在Ubuntu 8.04中,文件“/etc/profile”是全局的环境变量配置文件,它适用于所有的shell。在登 录Linux系统时应该先启动“/etc/profile”文件,再启动用户目录下的“~/.bash_profile”、 “~/.bash_login”或“~/.profile”文件中的一个,执行的顺序和上面的排序一样。如果存在 “~/.bash_profile”文件,还会执行“~/.bashrc”文件。在此只需要把JDK的目录放到 “/etc/profile”目录下即可,命令如下所示。

step2 重新启动机器,输入“java–version”命令,如果输出如下信息,则表示配置成功。

x0c注意: 当编译Android源代码成功后,在终端将会输出如下提示。

x0c2.3.3 运行Android源代码 当编译完整个项目后,如果需要查看编译后的运行效果,那么需要在系统中安装模拟器。当前最新模 拟器SDK的下载地址为/sdk/,界面效果如图2-3所示。

图2-3 最新SDK的下载页面 解压后需要把“/usr/local/src/android-sdk-linux_x86-1.1_r12/tools”目录加入到系统环境变量 “/etc/profile”中。然后找到编译后Android的目录文件out,此时会发现在 “android/out/host/linux-x86/bin”目录下多了很多应用程序,这些应用程序就是Android得以运 行的基础。我们需要把这个目录也添加到系统PATH下,并在“$HOME/.profile”文件中加入如下内容 。

接下来需要把Android的镜像文件加载到emulator中,使emulator可以看到Android运行的实际效果 ,然后在“$HOME/.profile”文件中加入如下内容。

重新启动机器,此时就可以进入到模拟器目录中并启动模拟器。

上述流程是在Linux下运行Android程序的方法,和本书第1章介绍的在Windows下搭建Android开发环 境的方法完全不同。

x0c2.3.4 实践演练——演示编译Android程序的两种方法 Android编译环境本身比较复杂,并且不像普通的编译环境那样只有顶层目录下才有Makefile文件 ,而其他的每个component都使用统一标准的文件。不过这并不是我们熟悉的 Makefile,而是经过了Android自身编译系统的很多处理。所以要真正理清楚其中的联系还比较复杂 。不过这种方式的好处在于,编写一个新的给Android增加一个新的Component变得比较简 单。为了使读者

更加深入地理解在Linux环境下编译Android程序的方法,接下来将分别演示编译 Android程序的两种方法。 1.编译Native C的helloworld模块 可以直接采用Eclip的集成环境来编译Java程序。具体实现方法非常简单,在这里就不再重复了。 接下来主要针对C/C++,通过一个例子来说明如何在Android中增加一个C程序的Hello World。 step1 在“$(YOUR_ANDROID)/development”目录下创建一个名为“hello”的目录,并用 “$(YOUR_ANDROID)”指向Android源代码所在的目录。

step2 在目录“$(YOUR_ANDROID)/development/hello/”下编写一个名为“hello.c”的C语言文件 。文件hello.c的代码如下所示。

step3 在目录“$(YOUR_ANDROID)/development/hello/”下编写文件。这是Android Makefile的标准命名,不能更改。文件的格式和内容可以参考其他已有的文件 的写法,针对helloworld程序的文件内容如下所示。

● LOCAL_SRC_FILES:用来指定源文件。 ● LOCAL_MODULE:指定要编译的模块的名字,在下一步骤进行编译时将会用到。 ● include $(BUILD_EXECUTABLE):表示要编译成一个可执行文件。如果想编译成动态库,则可用 BUILD_SHARED_LIBRARY,这些具体用法可以在“$(YOUR_ ANDROID)/build/core/”中查到 。 step4 回到Android源代码顶层目录进行编译。

在此需要注意,make helloworld中的目标名helloworld就是上面文件中由 LOCAL_MODULE指定的模块名。最终的编译结果如下所示。

x0cstep5 如果和上述编译结果相同,则编译后的可执行文件存放在如下目录:

这样通过“adb push”将它传送到模拟器上,再通过“adb shell”登录到模拟器终端后就可以执行 了。 2.手工编译C模块 前面讲解了通过标准的文件来编译C模块的具体流程,其实我们可以直接运用gcc命令行来 编译C程序,这样可以更好地了解Android编译环境的细节。具体流程如下所示。 step1 在Android编译环境中,提供了“showcommands”选项来显示编译命令行。我们可以通过打 开这个选项来查看编译时的一些细节。 step2 在具体操作之前需要使用如下命令清除前面的helloworld模块。

上面的“make clean-$(LOCAL_MODULE)”命令是Android编译环境提供的make clean的方式。 step3 使用showcommands选项重新编译helloworld,具体命令如下所示。

x0c从上述命令行可以看到,Android编译环境所用的交叉编译工具链如下:

其中参数“-I”和“-L”分别指定了所有C库头文件和动态库文件的路径,路径分别是 “bionic/libc/include”和“out/target/product/generic/obj/lib”,此外还包括很多编译选项 及-D所定义的预编译宏。 step4 此时就可以利用上面的编译命令来手工编译helloworld程序了,首先手工删

除上次编译得到 的helloworld程序。

再用gcc编译以生成目标文件。

x0c如果此时与编译参数进行比较,会发现上面主要减少了不必要的参数“-I”。 step5 接下来开始生成可执行文件。

在此需要特别注意的是参数“-Wl,-dynamic-linker,/system/bin/linker”,它指定了Android专用 的动态链接器是“/system/bin/linker”,而不是平常使用的。 step6 最后可以使用命令file和readelf来查看生成的可执行程序。

这就是ARM格式的动态链接可执行文件,在运行时需要和。当提示“not stripped”时表示它还没被STRIP(剥离)。嵌入式系统中为节省空间通常将编译完成的可执行文件 或动态库进行剥离,即去掉其中多余的符号表信息。在前面“make helloworld showcommands”命令 的最后我们也可以看到,Android编译环境中使用了“out/host/linux-x86/bin/soslim”工具进行 STRIP。

x0c2.4 编译Android Kernel 编译Android Kernel代码就是编译Android内核代码,在编译之前需要先了解在Android开源系统中主 要包括如下三部分代码。 ● 仿真器公共代码:对应的工程名是kernel/。 ● MSM平台的内核代码:对应的工程名是kernel/。 ● OMAP平台的内核代码:对应的工程名是kernel/。

x0c2.4.1 获取Goldfish内核代码 Goldfish是一种虚拟的ARM处理器,通常在Android的仿真环境中使用。在Linux的内核中 ,Goldfish作为ARM体系结构的一种“机器”。在Android的发展过程中,Goldfish内核的版本也从 Linux 2.6.25升级到了Linux 2.6.29。此处理器的Linux内核和标准的Linux内核的差别有以下三个方 面。 ● Goldfish机器的移植。 ● Goldfish一些虚拟设备的驱动程序。 ● Android中特有的驱动程序和组件。 Goldfish处理器有两个版本,分别是ARMv5和ARMv7,在一般情况下,只需使用ARMv5版本即可。在 Android开源工程的代码仓库中,使用git工具得到Goldfish内核代码的命令如下所示。

在其Linux源代码的根目录中,配置和编译Goldfish内核的过程如下所示。

其中,在“CROSS_COMPILE=”指定交叉编译工具的路径。 编译结果如下所示。

● vmlinux:Linux进行编译和连接之后生成的ELF格式的文件。 ● Image:未经过压缩的二进制文件。 ● piggy:一个解压缩程序。 ● zImage:解压缩程序和压缩内核的组合。 在Android源代码的根目录中,vmlinux和zImage分别对应Android代码prebuilt中的预编译的arm内核 。使用zImage可以替换prebuilt中的“prebuilt/android-arm/”目录下的goldfish_defconfig,此 文件的主要片段如下所示。

x0c因为Goldfish是ARM处理器,所以需要使用CONFIG_ARM宏。CONFIG_ARCH_GOLDFISH和 CONFIG_MACH_GOLDFISH宏是Goldfish处理器这类机器使用的配置宏。 在gildfish_defconfig

中,与Android系统相关的宏如下所示。

在goldfish_defconfig配置文件中,另外有一个宏是处理器虚拟设备的“驱动程序”,其内容如下:

在Goldfish处理器的各个配置选项中,体系结构和Goldfish的虚拟驱动程序基于标准Linux的内容的 驱动程序框架,但是这些设备在不同的硬件平台的移植方式不同。Android专用的驱动程序是 Android中特有的内容,非Linux标准,但是和硬件平台无关。 和原Linux内核相比,Android内核增加了Android的相关Driver驱动,对应的目录如下所示。

x0c相关Driver驱动主要分为以下几类。 ● Android IPC系统:Binder (binder.c)。 ● Android日志系统:Logger (logger.c)。 ● Android电源管理:Power (power.c)。 ● Android闹钟管理:Alarm (alarm.c)。 ● Android内存控制台:Ram_console (ram_console.c)。 ● Android时钟控制的gpio:Timed_gpio (timed_gpio.c)。 对于本书讲解的驱动程序开发来说,我们比较关心的是Goldfish平台下相关的驱动文件,具体说明如 下所示。 (1)字符输出设备 kernel/drivers/char/goldfish_tty.c (2)图像显示设备(Frame Buffer) kernel/drivers/video/goldfishfb.c (3)键盘输入设备文件 kernel/drivers/input/keyboard/goldfish_events.c (4)RTC(Real Time Clock)设备文件 kernel/drivers/rtc/rtc-goldfish.c (5)USB Device设备文件 kernel/drivers/usb/gadget/android_adb.c (6)SD卡设备文件 kernel/drivers/mmc/host/goldfish.c (7)FLASH设备文件 kernel/drivers/mtd/devices/goldfish_nand.c kernel/drivers/mtd/devices/goldfish_nand_reg.h (8)LED设备文件 kernel/drivers/leds/ledtrig-sleep.c (9)电源设备 kernel/drivers/power/goldfish_battery.c (10)音频设备 kernel/arch/arm/mach-goldfish/audio.c (11)电源管理 kernel/arch/arm/mach-goldfish/pm.c (12)时钟管理 kernel/arch/arm/mach-goldfish/timer.c

x0c2.4.2 获取MSM内核代码 在当前市面上谷歌的手机产品G1是基于MSM内核的,MSM是高通公司的应用处理器,在Android代码库 中公开了对应的MSM的源代码。在Android开源工程的代码仓库中,使用git工具得到MSM内核代码的命 令如下所示。

x0c2.4.3 获取OMAP内核代码 OMAP是德州仪器公司的应用处理器,为Android使用的是OMAP3系列的处理器。在Android代码库中公 开了对应的MSM的源代码,使用git工具得到MSM内核代码的命令如下所示。

x0c2.4.4 编译Android的Linux内核 了解了上述三类Android内核后,下面开始讲解编译Android的Linux内核的方法,假设以Ubuntu 8.10为例,完整编译Android的Linux内核的流程如下所示。 (1)构建交叉编译环境 Android的默认硬件处理器是ARM,因此我们需要在自己的机器上构建交叉编译环境。交叉编译器GNU Toolchain for ARM Processors下载地址如下:

单击GNU/Linux对应的链接,再单击IA32 GNU/Linux Installer链接后直接

下载,如图2-4所示。

图2-4 下载交叉编译器 把下载到的文件“arm-2008q3-72-arm-none-linux-gnueabi-i686-pc-linux- 2”解压到 一个目录下,例如“~/programes/”,并加入PATH环境变量:

然后添加:

保存后并运行source ~/.bashrc。 (2)获取内核源代码 源代码地址如下:

选择的内核版本要与选用的模拟器版本尽量一致,下载后并解压后得到文件夹。

(3)获取内核编译配置信息文件 编译内核时需要使用configure。configure通常有很多选项,我们往往不知道需要哪些选项。在运行 Android模拟器时,有一个文件“/proc/”,这是当前内核的配置信息文件,把 获取并解压放到“/”下,然后改名为.config。命令如下所示。

x0c(4)修改Makefile 修改195行的代码:

将CROSS_COMPILE值改为arm-none-linux-gnueabi-,这是我们安装的交叉编译工具链的前缀,修改此 处的目的是告诉make在编译的时候要使用该工具链。然后注释562和563行的如下代码:

必须将上述代码中的build id值注释掉,因为目前版本的Android内核不支持该选项。 (5)编译 使用make进行编译,同时生成zImage。

这样生成zImage的大小为1.23MB,android- sdk-linux_x86-1.0_r2/tools/lib/images/kernelqemu的大小是1.24MB。 (6)使用模拟器加载内核测试 命令如下所示:

到此为止,模拟器就加载成功了。

x0c2.5 运行模拟器 我们都知道程序开发需要调试,只有经过调试之后才能知道我们的程序是否正确运行。对于一款手机 系统,我们怎样在电脑平台之上调试Android程序呢?不用担心,谷歌提供了模拟器来解决我们担心 的问题。所谓模拟器,就是指在电脑上模拟Android系统,可以用这个模拟器来调试并运行开发的 Android程序。开发人员不需要真实的Android手机,只通过电脑即可模拟运行一个手机,即可开发出 应用在手机上面的程序。因为Android底层开发是基于Linux环境下进行的,所以很有必要讲解在 Linux下运行模拟器的知识。本节将介绍这方面的知识。

x0c2.5.1 Linux环境下运行模拟器的方法 下面以Linux Ubuntu 8.10为平台,介绍搭建Android开发环境的具体方法。具体流程如下所示。 step1 安装虚拟光驱。 step2 在Windows下用虚拟光驱安装Ubuntu 8.10,ISO文件是“”。 step3 用dpkg命令打开patch。首先进入Ubuntu系统,解压“ubuntu_package_”。

然后打开patch:

如果没有成功,则需要执行下面的命令:

注意: 可能有时需要一起运行dpkg,形式如下:

另外,还需要重新用Java 5执行dpkg命令(因为用java6会有问题)。 step4 编译源代码和Android sdk。 开始编译源代码,解压源代码到本地,进入源代码目

录,执行下面的命令。

开始编译sdk:在make完成后,直接运行make sdk,会在out/host/linux-x86/sdk下面生成mdk文件及 文件夹,例如android-sdk__linux-x86。 step5 安装Eclip。 只需直接解压即可完成Eclip的安装,命令如下所示 。

step6 在Eclip下配置Android。在此需要注意对 应的ADT要用,而不是0.8.1版本。 step7 测试刚才编译好的SDK,分为如下三个步骤。 ①在Eclip中将android SDK目录设置成自己编译生成的SDK目录,例如out/host/linuxx86/sdk/android-sdk__linux-x86。选择【Window】︱【Preferences】︱【Android】中的 SDK Location,进行设置。 ②创建AVD:在Eclip中,选择【Window】︱【Android AVD Manager】,将name、target、 sdcard、skin都填选好后,单击“Create AVD”即可。 ③在cmd窗口中,进入到目录下,执行下面的命令。

经过上述操作后,模拟器就运行起来了。 注意: 如果没有需要的JDK、Eclip和Android SDK。在Linux下也需要分别下载它们,在下载时选 择Linux的资源即可,整个安装顺序和Windows下的大同小异。

x0c2.5.2 模拟器辅助工具——adb ADB(Android Debug Bridge)是Android提供的一个通用的调试工具,借助这个工具,我们可以管理 设备或手机模拟器的状态,还可以进行以下操作。 ● 快速更新设备或手机模拟器中的代码,如应用或Android系统升级。 ● 在设备上运行shell命令。 ● 管理设备或手机模拟器上的预定端口。 ● 在设备或手机模拟器上复制或粘贴文件。 接下来将一一列出一些常用的操作命令。 step1 进入Shell的命令如下:

通过上面的命令,就可以进入设备或模拟器的shell环境中。在这个Linux Shell中,你可以执行各种 Linux的命令。另外,如果只想执行一条shell命令,可以采用以下的方式:

例如:adb shell dmesg会打印出内核的调试信息。 Android的Linux shell做了大量精简,很多Linux常用指令都不支持。 step2 上传文件:

step3 下载文件:

step4 安装程序:

step5 卸载软件:

补充一点,通过adb安装的软件(*.apk)都在/data/app/目录下,所以安装时不必指定路径,卸载时只 需要简单地执行rm就行。 step6 结束adb:

step7 显示Android模拟器状态:

step8 等待正在运行的设备:

step9 端口转发:

step10 查看bug报告:

x0cstep11 访问数据库SQLite3:

step12 记录无线通信日志:

一般来说,无线通信中的日志非常多,在运行时没必要去记录。我们可以通过命令设置记录应用程序 配置文件,在文件中编写如下代码:

这样就可以设置应用程序是否显示在Panel上。 step13 在shell内可以使用am

指令来加载Android应用。

step14 启动浏览器指令:

step15 拨打电话指令:

step16 启动map直接定位到北京指令:

step17 为模拟器加上SD卡指令:

下面介绍如何创建“”文件。用它来创建一个“SD卡”的命令如下所示。

这样就创建了一个容量为1GB的SD卡。 step18 用Android模拟器打电话发短信。 GPhone的模拟器有个特有的号码:155********,这就类似于我们实体手机的SIM卡号码。需要如下三 个步骤实现拨号操作。 ①打开终端。 ②连接:

③命令:

x0c由此看出,接听/挂断过程和实体手机完全一样。发送短信也一样简单,只需重复上面前两步,第 ③步命令如下所示。

第3章 驱动需要移植 我们开发的Android驱动程序是基于Linux内核的,这些驱动程序需要在Android系统中使用,上述工 作需要系统移植的知识。本章将详细介绍移植Android系统的基本知识,以及系统移植的基本原理 ,为后面学习驱动开发和移植打下坚实的基础。

x0c3.1 驱动开发需要做的工作 Android作为当前最流行的手机操作系统之一,受到了广大开发人员和商家的青睐。Android正在逐渐 形成一个蓬勃发展的产业,带来了无限商机。既然Android这么火爆,我们程序员可以学习它的哪一 方面的内容呢?本书的驱动开发又属于哪一领域呢?接下来将为读者奉上这两个问题的答案。 Android是一个开放的系统,这个系统的体积非常庞大,开发人员无须掌握整个Android体系中的开发 知识,只需熟悉其中某一个部分即可收获自己的未来。 从具体功能上划分,Android开发主要分为如下三个领域。 1.移植开发移动电话系统 移植开发的目的是构建硬件系统,并且移植Android的软件系统,最终形成手机产品。 2.Android应用程序开发 应用程序开发的目的是开发出各种Android应用程序,然后将这些应用程序投入Android市场,进行交 易。 Android的应用程序开发是Android开发的另一个方面。从开发的角度来看,这种形式的开发可以基于 某个硬件系统,在没有硬件系统的情况下也可以基于Linux或者Windows下的Android模拟器来开发。 这种类型的开发工作在Android系统的上层。 事实上,在Android软件系统中,第3个层次(Java框架)和第4个层次(Java应用)之间的接口也就 是Android的系统接口(系统API)。这个层次是标准的接口,所有的Android应用程序都是基于这个 层次的接口开发出来的。Android系统的第4个层次就是一组内置的Android应用程序。 Android应用程序开发者开发的应用程序和Android系统的第4个层次的应用程序其实是一个层次的内 容。例如,Android系统提供了基本的桌面程序,开发者可以根据Android的系统接口,实现另外

一个 桌面程序,提供给用户安装使用。根据Android系统的接口开发游戏,也是Android应用程序开发的一 个重要方向。 上述两种类型的开发结构如图3-1所示。

x0c图3-1 Android开发的领域 3.Android系统开发 系统开发的目的是升级或改造Android中已经存在的应用和架构,开发出有自己特色的手机系统。例 如联想手机乐Phone就是在Android基础上打造的一款适合国人使用习惯的手机系统,如图3-2所示。

图3-2 乐Phone Android系统开发的一个比较典型的示例就是当系统需要某种功能时,为了给Java层次的应用程序提 供调用的接口,需要从底层到上层的整体开发,具体步骤如下所示。 step1 增加C或者C++和本地库。 step2 定义Java层所需要的类(系统API)。 step3 将所需要的代码封装成JNI。 step4 结合Java类和JNI。 step5 应用程序调用Java类。 一定要慎重对待对Android系统API的改动工作,因为系统API的稍微变动就可能会涉及Android应用程 序的兼容问题。 Android系统本身的功能也处于增加和完善的过程中,因此Android系统的开发也是一个重要的方面。 这种类型的开发涉及Android软件系统的各个层次。在更多的时候,Android系统开发只是在不改变系 统API的情况下修正系统的缺陷,增加系统的稳定性。 从商业模式的角度来看,第一种类型的开发和第二种类型的开发是Android开发的主流。事实上,移 动电话的制造者主要进行第一种类型的开发,产品是Android实体手机;公司、个人和团体都可以进 行第二种类型的开发,其产品是不同的Android应用程序。 在Android的开发过程中,每一种类型的开发都只涉及整个Android系统的一个子集。在Android系统 中有着众多开发点,这些开发点相互独立,又有内在联系。在开发的过程中,需要重点掌握目前开发 点涉及的部分。

x0c背景说明: Android API的接口是用Java语言编写的,通常更改接口函数的格式(参数、返回值)、 常量的值等内容就相当于更改系统API。Android是一个开放的系统,适用于从最低端直到最高端的智 能手机。核心的Android API在每部手机上都可使用,但仍然有一些API接口有一些特别的适用范围 :这就是所谓的“可选API”。 在为某手机编写Android应用程序时,需要多少地对Android API进行修改,然后实现我们需要的功能 。例如使用Android API添加蓝牙程序和Wi-Fi程序。在更改Android API时,通常更改其接口函数的 格式(参数、返回值)和常量值等内容。但是Android API毕竟是谷歌推出的一系列标准,为了方便 以后系统的升级,建议大家不改变Android API的格式,而是只改变Android API的具体行为,也就是 说为这些固定的Android API编写各种各样的应

用程序。

x0c3.2 Android移植 本书讲解的是Android驱动方面的开发知识,由图3-1可知,驱动开发是底层的应用,属于Linux内核 层的工作。因为驱动是系统和硬件之间的载体,涉及不同硬件的应用问题,所以需要做系统移植工作 。本节将简要介绍系统移植方面的有关问题。

x0c3.2.1 移植的任务 Android移植开发的最终目的是开发手机产品。从开发者的角度来看,这种类型的开发以具有硬件系 统为前提,在硬件系统的基础上构建Android软件系统。这种类型的开发工作在Android系统的底层。 在软件系统方面,主要的工作集中在以下两个方面。 (1)Linux中的相关设备驱动程序 驱动程序是硬件和上层软件的接口。在Android手机系统中,需要基本的屏幕、触摸屏、键盘等驱动 程序,以及音频、摄像头、电话的Modem、Wi-Fi、蓝牙等多种设备驱动程序。 (2)Android本地框架中的硬件抽象层 在Android中硬件抽象层工作在用户空间,介于驱动程序和Android系统之间。Android系统对硬件抽 象层通常都有标准的接口定义,在开发过程中,实现这些接口也就给Android系统提供了硬件抽象层 。 上述两个部分综合起来相互结合,共同完成了Android系统的软件移植。移植成功与否取决于驱动程 序的品质和对Android硬件抽象层接口的理解程度。Android移植开发的工作由核心库、Dalvik虚拟机 、硬件抽象层、Linux内核层和硬件系统协同完成,具体结构如图3-3所示。

图3-3 Android移植结构

x0c3.2.2 移植的内容 在Android系统中,在移植过程中主要移植驱动方面的内容。Android移植主要分为如下几个类型。 ● 基本图形用户界面(GUI)部分:包括显示部分、用户输入部分和硬件相关的加速部分,还包括媒 体编解码和OpenGL等。 ● 音视频输入输出部分:包括音频、视频输出和摄像头等。 ● 连接部分:包括无线局域网、蓝牙、GPS等。 ● 电话部分:包括通话、GSM等。 ● 附属部件:包括传感器、背光、振动器等。 具体来说主要移植下面的内容。 ● Display显示部分:包括FrameBuffer驱动和Gralloc模块。 ● Input用户输入部分:包括Event驱动和EventHub。 ● Codec多媒体编解码:包括硬件Codec驱动和Codec插件,例如OpenMax。 ● 3D Accelerator(3D加速器)部分:包括硬件OpenGL驱动和OpenGL插件。 ● Audio音频部分:包括Audio驱动和Audio硬件抽象层。 ● Video Out视频输出部分:包括视频显示驱动和Overlay硬件抽象层。 ● Camera摄像头部分:包括Camera驱动(通常是v4l2)和Camera硬件抽象层。 ● Phone电话部分:包括Modem驱动程序和RIL库。 ● GPS全球定位系统部分:包括GPS驱动(例如串口)和GPS硬件抽象层。 ● Wi-Fi无线局

域网部分:包括Wlan驱动和协议和Wi-Fi的适配层。 ● Blue Tooth蓝牙部分:包括BT驱动和协议及BT的适配层。 ● Sensor传感器部分:包括Sensor驱动和Sensor硬件抽象层。 ● Vibrator振动器部分:包括Vibrator驱动和Vibrator硬件抽象层。 ● Light背光部分:包括Light驱动和Light硬件抽象层。 ● Alarm警告器部分:包括Alarm驱动和RTC系统和用户空间调用。 ● Battery电池部分:包括电池部分驱动和电池的硬件抽象层。 注意: 在Android系统中有很多组件,但并不是每一个组件都需要移植,例如那些纯软的组件就不需 要移植。像浏览器引擎虽然需要下层的网络支持,但是实际上并不需要直接为其移植网络接口,而是 通过无线局域网或者电话系统数据连接来完成标准的网络接口。

x0c3.2.3 驱动开发的任务 前面介绍了Android系统的基本知识和移植内容,那么究竟在驱动开发领域需要做什么工作呢?我们 的任务就是为某一个将要在Android系统上使用的硬件开发一个驱动程序。因为Android是基于 Linux的,所以开发Android驱动其实就是开发Linux驱动。 对于大部分子系统来说,硬件抽象层和驱动程序都需要根据实际系统的情况来实现,例如传感器部分 、音频部分、视频部分、摄像头部分和电话部分。另外也有一些子系统的硬件抽象层是标准的,只需 实现Linux内核中的驱动程序即可,例如输入部分、振动器部分、无线局域网部分和蓝牙部分等。对 于有标准的硬件抽象层的系统,有的时候通常也需要做一些配置工作。 随着Android系统的更新和发展,它已经不仅仅是一个移动设备的平台,也可以用于消费类电子和智 能家电,例如3.0以后的版本主要是针对平板电脑的,另外电子书、数字电视、机顶盒、固定电话等 都逐渐使用Android系统。在这些平台上,通常需要实现比移动设备更少的部件。一般来说,包括显 示和用户输入的基本用户界面部分是需要移植的,其他部分是可选的。例如电话系统、振动器、背光 、传感器等一般不需要在非移动设备系统上实现,一些固定位置设备通常不需要实现GPS系统。

x0c3.3 Android对Linux的改造 Android内核是基于Linux 2.6内核的,这是一个增强内核版本,除了修改部分Bug外,还提供了用于 支持Android平台的设备驱动。Android不但使用了Linux内核的基本功能,而且对Linux进行了改造 ,目的是实现更为强大的通信功能。

x0c3.3.1 Android对Linux内核文件的改动 在本书第1章的内容中已经讲解过Linux内核的基本知识,其实Android对Linux内核也进行了改动,这 些改动保存在下面的文件中。

x0cx0c3.3.2 为Android构建Linux的操作系统 如果我们以一个原始的Linux操作系统为基础,改

造成一个适合于Android的系统,所要做的工作其实 非常简单,仅仅是增加适用于Android的驱动程序。在Android中有很多Linux系统的驱动程序,将这 些驱动程序移植到新系统的步骤非常简单,具体来说有以下三个步骤。 step1 编写新的源代码。 step2 在KConfig配置文件中增加新内容。 step3 在Makefile中增加新内容。 在Android系统中,通常会使用FrameBuffer驱动、Event驱动、Flash MTD驱动、Wi-Fi驱动、蓝牙驱 动和串口等驱动程序。并且还需要音频、视频、传感器等驱动和sysfs接口。移植的过程就是移植上 述驱动的过程,我们的工作是在Linux下开发适用于Android的驱动程序,并移植到Android系统。 在Android中添加扩展驱动程序的基本步骤如下所示。 step1 在Linux内核中移植硬件驱动程序,实现系统调用接口。 step2 在HAL中把硬件驱动程序的调用封装成Stub。 step3 为上层应用的服务实现本地库,由Dalvik虚拟机调用本地库来完成上层Java代码的实现。 step4 编写Android应用程序,提供Android应用服务和用户操作界面。

x0c3.4 内核空间和用户空间接口是一个媒介 驱动程序是供系统使用硬件的,也就是说,驱动程序是介于系统和硬件之间的桥梁。在Linux下开发 这些中间桥梁的驱动程序时,需要用到内核空间和用户空间之间的接口。

x0c3.4.1 内核空间和用户空间的相互作用 现在,越来越多的应用程序需要编写内核级和用户级的程序来一起完成具体的任务。通常采用以下模 式:首先,编写内核服务程序利用内核空间提供的权限和服务来接收、处理和缓存数据;然后,编写 用户程序和先前完成的内核服务程序进行交互。具体来说,可以利用用户程序来配置内核服务程序的 参数,提取内核服务程序提供的数据,当然也可以向内核服务程序输入待处理数据。 比较典型的应用包括Netfilter(内核服务程序:防火墙);Iptable(用户级程序:规则设置程序 );IPSEC(内核服务程序:VPN协议部分);IKE(用户级程序:VPN密钥协商处理);当然还包括大 量的设备驱动程序及相应的应用软件。这些应用都是由内核级和用户级程序通过相互交换信息来一起 完成特定任务的。

x0c3.4.2 系统和硬件之间的交互 实现硬件和系统的交互是底层开发的主要任务之一。在Linux平台下有如下5种实现此功能的方式。 1.编写自己的系统调用 系统调用是用户级程序访问内核最基本的方法。目前Linux大致提供了二百多个标准的系统调用(具 体请参考内核代码树中的“include/asm-i386/unistd.h”和“arch/i386/kernel/entry.S”文件 ),并且允许我们添加自己的系统调用来实现和内核的信息交换。假如我们想建立一个系统

调用日志 系统,将所有的系统调用动作记录下来,以便进行入侵检测,此时可以编写一个内核服务程序,该程 序负责收集所有的系统调用请求,并将这些调用信息记录到在内核中自建的缓冲里。我们无法在内核 里实现复杂的入侵检测程序,因此必须将该缓冲里的记录提取到用户空间。最直截了当的方法是自己 编写一个新系统调用实现这种提取缓冲数据的功能。当内核服务程序和新系统调用都实现后,就可以 在用户空间里编写用户程序执行入侵检测任务了,入侵检测程序可以定时、轮询或在需要的时候调用 新系统调用从内核提取数据,然后进行入侵检测。 2.编写驱动程序 Linux/UNIX的一个特点就是把所有的东西都看做文件(every thing is a file)。系统定义了简洁 完善的驱动程序界面,客户程序可以用统一的方法通过这个界面和内核驱动程序交互。而大部分系统 的使用者和开发者已经非常熟悉这种界面及相应的开发流程了。 驱动程序运行于内核空间,用户空间的应用程序通过文件系统中“/dev/”目录下的一个文件来和它 交互。这就是我们熟悉的文件操作流程:open()→read()→write()→ioctl()→clo()。 注意: 并不是所有的内核驱动程序都是这个界面,网络驱动程序和各种协议栈的使用就不大一致 ,比如套接口编程虽然也有open(),clo()等概念,但它的内核实现及外部使用方式都和普通驱动 程序有很大差异。 这里先不谈设备驱动程序在内核中要做的中断响应、设备管理、数据处理等工作,在此先把注意力集 中在它与用户级程序交互这一部分。操作系统为此定义了一种统一的交互界面,就是前面所说的 open()、read()、write()、ioctl()和clo()等。每个驱动程序按照自己的需要做独立实现,把自 己提供的功能和服务隐藏在这个统一界面下。客户级程序选择需要的驱动程序或服务(其实就是选择 “/dev/”目录下的文件),按照上述界面和文件操作流程,就可以跟内核中的驱动交互了。用面向 对象的概念更容易解释,系统定义了一个抽象的界面(Abstract Interface),每个具体的驱动程序 都是这个界面的实现(Implementation)。 由此可见,驱动程序也是用户空间和内核信息交互的重要方式之一。从本质上来说,ioctl、read、 和write也是通过系统调用去完成的,只是这些调用已被内核进行了标准封装和统一定义。因此用户 不必像填加新系统调用那样必须修改内核代码,重新编译新内核,使用虚拟设备只需要通过模块方法 将新的虚拟设备安装到内核中(insmod上)就能方便使用。 大致可以将Linux中的设备分为如下三类。 ● 字符设备:包括那些必须以顺序方式,

像字节流一样被访问的设备。 ● 块设备:指那些可以用随机方式,以整块数据为单位来访问的设备,如硬盘等。 ● 网络接口:通常指网卡和协议栈等复杂的网络输入输出服务。 如果将我们的系统调用日志系统用字符型驱动程序的方式实现,整个过程就非常简单了。可以将内核 中收集和记录信息的那一部分编写成一个字符设备驱动程序。虽然没有实际对应的物理设备,但是 Linux的设备驱动程序本来就是一个软件抽象,它可以结合硬件提供服务,也完全可以作为纯软件提 供服务。在驱动程序中,可以使用open()来启动服务,用read()返回处理好的记录,用ioctl()设置 记录格式等,用clo()停止服务,write()没有用到,那么我们可以不去实现它。然后在 “/dev/”目录下建立一个设备文件,对应我们新加入内核的系统调用日志系统驱动程序。 3.使用proc文件系统 proc是Linux提供的一种特殊的文件系统,使用它的目的就是提供一种便捷的用户和内核间的交互方 式。proc以文件系统作为使用界面,使应用程序可以以文件操作的方式安全、方便地获取系统当前运 行的状态和其他一些内核数据信息。

x0cproc文件系统多用于监视、管理和调试系统,平常使用的ps和top等管理工具就是利用proc来读取内 核信息的。除了读取内核信息外,proc文件系统还提供了写入功能,所以我们可以利用它来向内核输 入信息。比如通过修改proc文件系统下的系统参数配置文件“/proc/sys”,可以直接在运行时动态 更改内核参数。 除了系统已经提供的文件条目,通过proc为我们留的接口,允许在内核中创建新的条目从而与用户程 序共享信息数据。比如可以为系统调用日志程序(无论是作为驱动程序还是作为单纯的内核模块)在 proc文件系统中创建新的文件条目,在此条目中显示系统调用的使用次数,每个单独系统调用的使用 频率等。也可以增加另外的条目用于设置日志记录规则。 4.使用虚拟文件系统(VFS) 很多内核开发者认为利用ioctl()系统调用往往会使得系统调用意义不明确,而且难控制。而将信息 放入到proc文件系统中会使信息组织混乱,所以不赞成过多地使用此系统。他们的建议是实现一种孤 立的虚拟文件系统来代替ioctl()和proc。这是因为文件系统接口清楚,而且便于用户空间访问,同 时利用虚拟文件系统使得利用脚本执行系统管理任务更加方便、有效。 下面举例来说如何通过虚拟文件系统修改内核信息。假设我们可以实现一个名为“sagafs”的虚拟文 件系统,其中文件log对应内核存储的系统调用日志。此时就可以通过文件访问的普遍方法获得日志 信息,命令如下所示。



使用虚拟文件系统可以更加方便、清晰地实现信息交互。但是很多程序员认为VFS的API接口十分复杂 。其实读者无须担心,因为从Linux 2.5内核开始就提供了一种叫做libfs的例子程序,它可以帮助不 熟悉文件系统的用户封装实现了VFS的通用操作。 5.使用内存映像 Linux通过内存映像机制来提供用户程序对内存直接访问的能力。内存映像的意思是把内核中特定部 分的内存空间映射到用户级程序的内存空间去。也就是说,用户空间和内核空间共享一块相同的内存 。这样做有如下影响。 内核在这块地址内存储变更的任何数据,用户可以立即发现和使用,根本无须进行数据复制。 在使用系统调用交互信息时,在整个操作过程中必须有一步数据复制的工作,或者是把内核数据复制 到用户缓冲区,或者是把用户数据复制到内核缓冲区。这样对于许多数据传输量大、时间要求高的应 用来说很不科学,因为许多应用根本就无法忍受数据复制所耗费的时间和资源。

x0c3.4.3 使用Relay实现内核到用户空间的数据传输 Relay是一种从Linux内核到用户空间的高效数据传输技术。通过用户定义的Relay通道,内核空间的 程序能够高效、可靠、便捷地将数据传输到用户空间。Relay特别适用于内核空间有大量数据需要传 输到用户空间的情形,目前已经广泛应用在内核调试工具如SystemTap中。 1.Relay的原理 Relay提供了一种机制,使得内核空间的程序能够通过用户定义的Relay通道(channel)将大量数据 高效地传输到用户空间。一个Relay通道由一组和CPU一一对应的内核缓冲区组成。这些缓冲区又被称 为Relay缓冲区(buffer),其中的每一个在用户空间都用一个常规文件来表示,叫做Relay文件 (file)。内核空间的用户可以利用Relay提供的API接口来写入数据,这些数据会被自动写入当前的 CPU ID对应的那个Relay缓冲区;同时,这些缓冲区从用户空间看来,是一组普通文件,可以直接使 用read()进行读取,也可以使用mmap()进行映射。Relay并不关心数据的格式和内容,这些完全依赖 于使用Relay的用户程序。Relay的目的是提供一个足够简单的接口,从而使得基本操作尽可能高效。 Relay实现了对数据读和写的分离,使得大量突发性数据写入的时候,不需要受限于用户空间相对较 慢的读取速度,从而大大提高了效率。Relay作为写入和读取的桥梁,也就是将内核用户写入的数据 缓存并转发给用户空间的程序。这种转发机制正是Relay这个名称的由来。 2.Relay的API 在Relay中提供了许多API来支持用户程序完整地使用Relay。这些API主要分为两大类,分别是面向用 户空间和面向内核空间的,具体说明如下所示。 (1

)面向用户空间的API 此类API编程接口向用户空间程序提供了访问Relay通道缓冲区数据的基本操作的入口,主要包括如下 方法。 ● open():允许用户打开一个已经存在的通道缓冲区。 ● mmap():使通道缓冲区被映射到位于用户空间的调用者的地址空间。要特别注意的是,我们不能 仅对局部区域进行映射。也就是说,必须映射整个缓冲区文件,其大小是CPU的个数和单个CPU缓冲区 大小的乘积。 ● read():读取通道缓冲区的内容。这些数据一旦被读出,就意味着它们被用户空间的程序消费掉 了,不能被之后的读操作看到。 ● ndfile():将数据从通道缓冲区传输到一个输出文件描述符。其中可能的填充字符会被自动去 掉,不会被用户看到。 ● poll():支持POLLIN/POLLRDNORM/POLLERR信号。每次子缓冲区的边界被越过时,等待着的用户 空间程序会得到通知。 ● clo():将通道缓冲区的引用数减1。当引用数减为0时,表明没有进程或者内核用户需要打开 它,从而这个通道缓冲区被释放。 (2)面向内核空间的API 此类API接口向位于内核空间的用户提供了管理Relay通道、数据写入等功能,其中最为常用的如下所 示。 ● relay_open():创建一个Relay通道,包括创建每个CPU对应的Relay缓冲区。 ● relay_clo():关闭一个Relay通道,包括释放所有的Relay缓冲区,在此之前会调用 relay_switch()来处理这些Relay缓冲区以保证已读取但是未满的数据不会丢失。 ● relay_write():将数据写入到当前CPU对应的Relay缓冲区内。由于它使用了local_irqsave()保 护,因此也可以在中断上下文中使用。 ● relay_rerve():在Relay通道中保留一块连续的区域来留给未来的写入操作。通常用于那些希 望直接写入到Relay缓冲区的用户。考虑到性能或者其他因素,这些用户不希望先把数据写到一个临 时缓冲区中,然后再通过relay_write()进行写入。 3.使用Relay

x0c在下面的内容中,将通过一个最简单的例子来介绍使用Relay的方法。本实例由如下两部分组成。 ● 位于内核空间将数据写入Relay文件的程序,使用时需要作为一个内核模块被加载。 ● 位于用户空间从Relay文件中读取数据的程序,使用时作为普通用户态程序运行。 (1)实现内核空间 内核空间程序的主要操作如下所示。 ● 当加载模块时,打开一个Relay通道,并且往打开的Relay通道中写入消息。 ● 当卸载模块时,关闭Relay通道。 文件hello-mod.c的具体实现代码如下所示。

(2)实现用户空间 用户空间的函数主要操作过程如下所示。 如果relayfs文件系统还没有被umount(是一个命令)处理,则将其umount到“/mnt/relay”目录上 。首先遍历每一个CPU对应

的缓冲文件,然后打开文件,接着读取所有文件内容,然后关闭文件,最 后,umount掉Relay文件系统。 实现文件audience.c的具体实现代码如下所示。

x0cx0c上述实例演示了使用Relay的过程。虽然上述代码并没有实际用处,但是形象地描述了从用户空间和 内核空间两个方面使用Relay的基本流程。实际应用中对Relay的使用当然比这复杂得多,有关更多用 法的实例请参考Relay的主页。

x0c3.5 三类驱动程序 在Linux系统中主要有三类设备驱动程序,分别是字符设备驱动程序、块设备驱动程序和网络设备驱 动程序。本节将简要讲解上述三类设备驱动程序的基本知识。

x0c3.5.1 字符设备驱动程序 字符设备是指在I/O传输过程中以字符为单位进行传输的设备,例如键盘、打印机等。请注意,以字 符为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1个字符占16比特,合2个字节。 字符设备驱动程序的结构如图3-4所示。

图3-4 字符设备驱动程序的结构 在Lniux系统中,字符设备以特别文件方式在文件目录树中占据位置并拥有相应的i节点。在i节点中 的文件类型指明该文件是字符设备文件。可以使用与普通文件相同的文件操作命令对字符设备文件进 行操作,例如打开、关闭、读、写等。概括来说,字符设备驱动程序主要做如下三件事。 ● 定义一个结构体static struct file_operations变量,在其中定义一些设备的打开、关闭、读 、写、控制函数。 ● 在结构体外分别实现结构体中定义的这些函数。 ● 向内核中注册或删除驱动模块。 由此可见,实现字符设备驱动程序的首要任务是定义一个结构体。字符设备提供给应用程序流控制接 口有open、clo、read、write和ioctl。添加一个字符设备驱动程序的过程,实际上是给上述操作 添加对应的代码的过程,Linux对这些操作统一做了抽象。 定义结构体file_operations的格式如下所示。

在此结构体中规定了驱动程序向应用程序提供的操作接口,主要有实现以下几个功能的接口。 ● 实现write操作,就是从应用程序接收数据送到硬件。例如下面的代码。

x0c在上述代码中,函数u_long copy_from_ur(void *to, const void *from, u_long len)用于把用 户态的数据复制到内核态,实现数据的传送。 ● 实现read操作,即从硬件读取数据并交给应用程序。例如下面的代码。

在上述代码中,函数u_long copy_to_ur(void * to, const void *from, u_long len)用于实现把 内核态的数据复制到用户态。 ● 实现ioctl操作,即为应用程序提供对硬件行为的控制。例如下面的代码。

x0c● 实现open操作。 当应用程序打开设备时对设备进行初始化,使用MOD_INC_USE_COUNT增加驱动程

序的使用次数。例如 下面的代码。

● 实现relea操作。 当应用程序关闭设备时处理设备的关闭操作。使用MOD_DEC_USE_COUNT来增加驱动程序的使用次数。 例如下面的代码。

● 驱动程序初始化函数。 Linux在加载内核模块时会调用初始化函数,初始化驱动程序本身使用register_chrdev向内核注册驱 动程序,该函数的第三个参数是指向包含有驱动程序接口函数信息的file_operations结构体。例如

x0c下面的代码。

在上述代码中,函数module_init()的功能是向内核声明当前模块的初始化函数。 ● 驱动程序退出函数。 Linux在卸载内核模块时会调用退出函数释放驱动程序使用的资源,使用unregister_chrdev从内核中 卸载驱动程序。将驱动程序模块注册到内核,内核需要知道模块的初始化函数和退出函数,才能将模 块放入自己的管理队列中。例如下面的代码。

在上述代码中,函数module_exit()的功能是向内核声明当前模块的退出函数。

x0c经过上述分析,可以总结出开发字符设备驱动程序的基本步骤如下所示。 step1 确定主设备号和次设备号。 step2 实现字符驱动程序,先实现file_operations结构体,然后实现初始化函数并注册字符设备 ,接下来实现销毁函数并释放字符设备。 step3 创建设备文件节点。 接下来给出一个通用的字符设备驱动程序。此程序由如下两个文件构成。其中文件tst-driver.h的实 现代码如下所示。

另一个构成文件tst-driver.c的实现代码如下所示。

x0cx0cx0cx0cx0c3.5.2 块设备驱动程序 块设备I/O与字符设备操做的主要区别如下所示。 ● 块设备只能以块为单位接收输入、返回输出,而字符设备则以Byte为单位。大多数设备是字符设 备,它们不需要缓冲并且不以固定块大小进行操作。 ● 块设备对于I/O请求有对应的缓冲区,所以它们可以选择以什么顺序进行响应。字符设备无须缓 冲且被直接读写。 ● 字符设备只能被顺序读写,块设备可以随机访问。 1.结构体block_device_operations 在文件“include/linux/fs.h”中定义了结构体block_device_operations,此结构体描述了对块设 备的操作的集合,具体代码如下所示。

2.结构体gendisk 结构体gendisk的功能是描述一个独立的磁盘设备或分区,具体代码如下所示。

3.结构体request和bio (1)请求request 结构体request和request_queue在Linux块设备驱动中,使用request结构体来表征(显示出来的现象 ;表现出来的特征)等待进行的I/O请求,用request_queue来表征一个块I/O请求队列。这两个结构 体的定义代码如下所示。

x0c(2)请求队列request_queue 请求队列跟踪等候块I/O的请求,功能是描述这个设备能够支持的请求的类型信息。请求队列还要实 现

一个插入接口,这个接口允许使用多个I/O调度器,I/O调度器以最优性能的方式向驱动提交I/O请 求。大部分I/O调度器是积累批量的I/O请求,并将其排列为递增/递减的块索引顺序后提交给驱动。 另外I/O调度器还负责合并邻近的请求,当一个新的I/O请求被提交给调度器后,它会在队列里搜寻包 含邻近的扇区的请求。如果找到一个并且此请求合理,则调度器会将这两个请求合并。 定义结构体request_queue的代码如下所示。

x0c注意: 在块设备模块中,还可以使用函数实现块设备驱动的模块卸载、加载、打开与释放操作,相 关知识请参阅相关资料。 Android的块设备驱动在目录“/dev/block”中,其主要内容如下所示。

在上述内容中,主设备号为1的是各个内存块设备,主设备号为7的是各个回环块设备,主设备号为 31的是mtd设备中的块设备,mmcblk0表示SD卡的块设备。 在Android系统中,可以使用mount命令来查看系统中被挂起的文件系统。使用mount命令的格式如下 所示。

上述命令中的主要参数的具体说明如下所示。 ①-t vfstype:用于指定文件系统的类型,通常不必指定。mount会自动选择正确的类型。常用类型 有如下6类。

x0c● 光盘或光盘镜像:iso9660。 ● DOS fat16文件系统:msdos。 ● Windows 9x fat32文件系统:vfat。 ● Windows NT ntfs文件系统:ntfs。 ● Mount Windows文件网络共享:smbfs。 ● UNIX(LINUX)文件网络共享:nfs。 ②-o options:主要用来描述设备或档案的挂接方式。常用的参数有如下4类。 ● loop:用来把一个文件当成硬盘分区挂接上系统。 ● ro:采用只读方式挂接设备。 ● rw:采用读写方式挂接设备。 ● iochart:指定访问文件系统所用字符集。 ③device:要挂接(mount)的设备。 ④dir:设备在系统上的挂接点(mount point)。 在Android系统中,可以使用df命令来查看系统中各个盘的使用情况。使用df命令的格式如下所示。

参数options常用取值的具体说明如下所示。 ● -s:对每个Names参数只给出占用的数据块总数。 ● -a:递归地显示指定目录中各文件及子目录中各文件占用的数据块数。若既不指定-s,也不指定 -a,则只显示Names中的每一个目录及其中的各子目录所占的磁盘块数。 ● -k:以1024字节为单位列出磁盘空间使用情况。 ● -x:跳过在不同文件系统上的目录,不予统计。 ● -l:计算所有的文件大小,对硬链接文件则计算多次。 ● -i:显示inode信息而非块使用量。 ● -h:以容易理解的格式输出文件系统大小,例如136KB、254MB、21GB。 ● -P:使用POSIX输出格式。 ● -T:显示文件系统类型。

x0c3.5.3 网络设备驱动程序 Linux网络设备驱动程

序由4部分组成,分别是网络设备媒介层、网络设备驱动层、网络设备接口层及 网络协议接口层。网络设备媒介层包括各种物理网络设备和传输媒介。对于网络设备接口层 ,Linux系统用Net_device结构表示网络设备接口。Net_device结构保存所有与硬件有关的接口信息 ,各协议软件主要通过Net_device结构来完成与硬件的交互。网络设备驱动层主要包括网络设备的初 始化、数据包的发送和接收。网络协议接口层提供网络接口驱动程序的抽象接口。 Linux网络驱动程序中的常用方法如下所示。 ● 初始化(initialize):检测设备;配置和初始化硬件;初始化net_device结构;注册设备。 ● 打开(open):这个方法在网络设备被激活的时候调用,进行资源的申请和硬件的激活等。 open方法另一个作用是如果驱动程序作为一个模块被装入,则要防止模块卸载时设备处于打开状态。 在open方法里要调用MOD_INC_USE_COUNT宏。 ● 关闭(clo):释放某些系统资源。如果是作为模块装入的驱动程序,clo里应该调用 MOD_DEC_USE_COUNT,减少设备被引用的次数,以使驱动程序可以被卸载。 ● 发送(hard_start_xmit):网络设备驱动程序发送数据时,系统调用dev_queue_xmit函数,发 送的数据放在一个sk_buff结构中。一般的驱动程序将数据传输到硬件发出去,特殊的设备如 loopback把数据组成一个接收数据再回送给系统,或如dummy设备直接丢弃数据。如果发送成功,则 在hard_start_xmit方法里释放sk_buff,返回0,否则返回1。 ● 接收(reception):驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统 的。一般设备收到数据后都会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff,从硬件 读出数据放置到申请好的缓冲区里。接下来填充sk_buff中的一些信息。最后调用netif_rx()把数据 传送给上层协议层处理。 在Android系统中,可以使用命令ifconfig来查询系统中的网络设备,另外使用此命令也可以获取WiFi网络和电话网络的信息。 第4章 HAL层深入分析 HAL层又叫硬件抽象层。它在Android体系中有着深远的意义,因为Android究竟是完全开源的还是完 全不开源的秘密就在这一层。Google将硬件厂商的驱动程序放在这一层。正是因为这一层的代码没有 开源,所以Android才被Linux家族删除。本章将详细介绍HAL层的基本知识,为学习本书后面的驱动 开发和移植打下坚实的基础。

x0c4.1 认识HAL层 HAL层(硬件抽象层)是位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽象化。它 隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,这样就可以

在多种平台上进行移植。从软硬件测试的角度来看,软硬件测试工作都可分别基于硬件抽象层来完成 ,从而使软硬件测试工作的并行进行成为可能。HAL层的结构如图4-1所示。

图4-1 HAL层的结构 由图4-1所示的结构图可知,HAL层的目的是为了把Android Framework(Android框架)与Linux Lernel(Linux内核)隔离。让Android不至于过度依赖Linux Kernel,从而可以在不考虑驱动程序的 前提下进行Android Framework开发。在HAL层主要包含了GPS、Vibrator、Wi-Fi、Copybit、Audio、 Camera、Lights、Ril、Overlay等模块。

x0c4.1.1 HAL层的发展 硬件抽象层可以分为以下6种HAL。 ● 上层软件。 ● 虚拟驱动,设置管理模块。 ● 内部通信SERVER。 ● 内部以太网。 ● 内部通信CLIENT。 ● 用户接入口。 定义硬件抽象层接口的代码具有以下5个特点。 ● 硬件抽象层具有与硬件的密切相关性。 ● 硬件抽象层具有与操作系统无关性。 ● 接口定义的功能应包含硬件或系统所需硬件支持的所有功能。 ● 接口定义简单明了,太多接口函数会增加软件模拟的复杂性。 ● 具有可测性的接口设计有利于系统的软硬件测试和集成。 在Android源代码中,HAL主要被保存在下面的目录中。 ● libhardware_legacy:过去的目录,采取了链接库模块观念来架构。 ● libhardware:新版的目录,被调整为用HAL stub观念来架构。 ● ril:是Radio接口层。 ● msm7k:和QUAL平台相关的信息。 接下来将对目前HAL的现况做一个简单的分析。另外,目前Android的HAL层仍旧散布在不同的地方 ,例如分为Camera、Wi-Fi等,因此上述的目录并不包含所有的HAL程序代码。HAL架构成熟前的结构 如图4-2所示,现在HAL层的结构如图4-3所示。

图4-2 成熟前的HAL层架构

x0c图4-3 现在的HAL层架构 从现在HAL层的结构可以看出,当前的HAL stub模式是一种代理人(proxy)的概念,虽然stub仍以 *.so文件的形式存在,但是HAL已经将*.so文件隐藏了。stub向HAL提供了功能强大的操作函数 (Operations),而runtime则从HAL获取特定模块(stub)的函数,然后再回调这些操作函数。这种 以Indirect Function Call模式的架构,让HAL stub变成了一种“包含”关系,也就是说在HAL里面 包含了许多stub(代理人)。Runtime只要说明module ID(类型)就可以取得操作函数。在当前的 HAL模式中,Android定义了HAL层结构框架,这样通过接口访问硬件时就形成了统一的调用方式。

x0c4.1.2 过去和现在的区别 为了使读者明白过去结构和现在结构的差别,接下来将对HAL_legacy和HAL做一个对比。 ● HAL_legacy:这是过去HAL的模块,采用共享库形式,在编译时会调用到。由于采用function call形式调用,因此可

被多个进程使用,会被映射到多个进程空间中,造成浪费,同时需要考虑代码 能否安全重入的问题(thread safe)。 ● HAL:这是新式的HAL,采用了HAL module和HAL stub结合形式。HAL stub不是一个共享库,在编 译时上层只拥有访问HAL stub的函数指针,并不需要HAL stub。在上层通过HAL module提供的统一接 口获取并操作HAL stub,所以文件只会被映射到一个进程,而不会存在重复映射和重入问题。

x0c4.2 分析HAL层源代码 通过上一节内容的学习,了解了在Android中为了给用户提供统一的硬件接口和硬件形态,在Linux Kernel和用户空间之间提供了一个HAL层,即硬件抽象层。这使得Android中的Framework只需要关心 HAL中的内容即可,而不用关心具体的硬件实现。本节将简单分析HAL层的代码,当然不是具体驱动程 序的代码,而是HAL层中的各个接口的代码。

x0c4.2.1 分析HAL moudle 在HAL moudle中主要分为如下三个结构。 ● struct hw_module_t ● struct hw_module_methods_t ● struct hw_device_t 上述三个结构的继承关系如图4-4所示。

图4-4 Android HAL结构的继承关系 上述三个抽象概念在文件hardware.c中进行了详细描述。HAL模块的源代码保存在“harware”目录中 ,对于不同的hardware的HAL,其对应的lib命名规则是,比如,表 示其ID是gralloc,msm7k是variant。variant的取值范围是在该文件中定义的variant_keys对应的值 。 接下来开始分析文件hardware.c的源代码。 (1)函数hw_get_module()。 此函数的功能是根据模块ID寻找硬件模块动态链接库的地址,然后调用load打开动态链接库,并从中 获取硬件模块结构体地址。执行后首先得到根据固定的符号HAL_MODULE_INFO_ SYM寻找到的 hw_module_t结构体,然后在hw_moule_t中的hw_module_methods_t结构体成员函数提供的open结构打 开相应的模块,并进行初始化。因为用户在调用open()时通常都会传入一个指向hw_device_t指针的 指针。这样函数open()就将对模块的操作函数结构保存到hw_device_t结构体里面,用户通过它可以 和模块进行交互。

x0c函数hw_get_module()的具体实现代码如下所示。

(2)数组variant_keys。 在上述函数中,需要用到数组variant_keys,因为HAL_VARIANT_KEYS_COUNT就是数组variant_keys的 大小。定义此数组的代码如下所示。

然后通过此数组,并使用如下代码得到操作权限。

在此variant_keys[i]对应有三个值,分别是trout、msm7k和ARMV6。 (3)将路径和文件名保存到path。 接下来将通过如下代码将路径和文件名保存到path。

通过上述代码,把HAL_LIBRARY_PATH/id.***.so保存到path中,其中“***”就是上面 variant_keys中各个元素所对应的值。 (4)载入相应的库,并把它们的HMI保存到module中。

具体代码如下所示。

x0c(5)打开相应的库并获得hw_module_t结构体,具体代码如下所示。

x0cx0c4.2.2 分析mokoid工程 在mokoid工程中,为我们提供了一个LedTest示例程序,此示例是中国台湾Jollen的培训教程。了解 这个工程源代码,对于理解Android层次结构、HAL编程方法都非常有意义。此工程文件源代码可以从 网络中获取,在Linux中的下载命令如下所示。

下载mokoid工程文件后,目录结构如图4-5所示。

图4-5 mokoid工程的目录结构 需要通过JNI(Java Native Interface)来实现Android的HAL,JNI就是Java程序可以调用C/C++写的 动态链接库,所以HAL可以使用C/C++语言编写,这样效率更高。在Android下有以下两种访问HAL的方 式。 ● Android的app直接通过rvice调用.so格式的JNI:此方法比较简单高效,但是不正规。 ● 经过Manager调用Service:此方法实现起来比较复杂,但更符合目前的Android框架。在此方法 中,在进程LegManager和LedService(Java)中需要通过进程通信的方式实现通信。 在mokoid工程中分别实现了上述两种方法,下面将详细介绍这两种方法的实现原理。 1.直接调用rvice方法的实现代码 (1)HAL层的实现代码

x0c文件hardware/modules/led/led.c的实现代码如下所示。

x0c(2)JNI层的实现代码 文件frameworks/ba/rvice/jni/com_mokoid_rver_的实现代码如下所示。

x0cx0c(3)rvice的实现代码 这里的Service属于Framework层,文件 的实现代码如下所示。

x0c(4)APP测试程序的实现代码 这里的测试程序属于APP层,文件apps/LedClient/src/com/mokoid/LedClient /的实 现代码如下所示。

x0c2.通过Manager调用rvice的实现代码 (1)Manager的实现代码 APP通过此Manager和rvice实现通信功能,实现文件frameworks/ba/core/java/mokoid/ hardware/的实现代码如下所示。

x0c因为LedService和LedManager分别属于不同的进程,所以在此需要考虑不同进程之间的通信问题。此 时在Manager中可以增加一个aidl文件来描述通信接口。文件 frameworks/ba/core/java/mokoid/hardware/的实现代码如下所示。

(2)SystemServer的实现代码

x0c此处的SystemServer属于APP层,实现文件apps/LedTest/src/com/mokoid/LedTest/ 的代码如下所示。

(3)APP测试程序 这里的测试程序属于APP层,文件mokoid-readonly/apps/LedTest/src/com/mokoid/LedTest/的实现代码如下所示。

x0cx0c4.3 总结HAL层的使用方法 经过4.2节内容的学习,想必读者已经初步掌握了使用HAL层的方法。本节将根据源代码分析来总结使 用HAL层接口的方法。 1.使用HAL的方法 下面以Sensor传感器为例介绍使用HAL的方法,具体流程如下所示。 step1

Native code通过hw_get_module调用获取HAL stub。

step2 通过继承hw_module_methods_t的callback来打开设备。

step3 通过继承hw_device_t的callback(回访)来控制设备。

2.编写HAL stub的方法 编写HAL stub的基本流程如下所示。 step1 自定义HAL结构体,编写头文件led.h和hardware/hardware.h,主要代码如下所示。

step2 编写文件led.c实现HAL stub注册功能。 step3 设置led_module_methods继承于hw_module_methods_t,并实现对open()方法的回访。

step4 使用HAL_MODULE_INFO_SYM实例化led_module_t,注意这个名称不可以修改。

x0c对上述代码的说明如下。 ● tag:表示需要指定为HARDWARE_MODULE_TAG。 ● id:表示指定为HAL Stub的module ID。 ● methods:为HAL所定义的方法。 step5 open()是一个必须实现的回调API,用于负责申请结构体空间并填充信息,并且可以注册具 体操作API接口,并打开Linux驱动。但是因为存在多重继承关系,所以只需对子结构体 hw_device_t对象申请空间即可。

step6 填充具体API操作,具体代码如下所示。

x0cx0c4.4 传感器在HAL层的表现 人们在日常的生活中经常会用到传感器,例如楼宇的楼梯灯、马路上的路灯等。那么我们手机中的传 感器又可以起到什么作用呢?在Android手机中提供了加速度传感器、磁场、方向、陀螺仪、光线、 压力、温度等传感器。在Android系统中,传感器的代码分布信息如下所示。 ● 传感器系统的Java部分,实现文件为Sensor*.java。 代码路径:frameworks/ba/include/core/java/android/hardware。 ● 传感器系统的JNI部分,此部分演示了r类的本质支持。 代码路径:frameworks/ba/core/jni/android_hardware_。 ● 传感器系统HAL层,演示了传感器系统的硬件抽象层需要具体的实现。 头文件路径:hardware/libhardware/include/hardware/nsors.h。 ● 驱动层。 代码路径:kernel/driver/hwmon/$(PROJECT)/nsor。 本节将主要分析HAL的代码。

x0c4.4.1 HAL层的Sensor代码 1.文件 HAL层的代码都是c/cpp格式,其一般保存路径是hardware/$(PROJECT)/nsor/。其中文件 的实现代码如下所示。

在这里要注意LOCAL_MODULE的赋值,这里的模块名字都是预先定义好的,具体可以参考 hardware/libhardware/hardware.c。 在此也可以看到加载的顺序,在加载Sensor的时候,需要依次去查找是否存在不同的so,然后根据不 同的so来加载对应的适配驱动。 2.填充的结构体 在HAL层,需要特别注意下面几个填充结构体。 (1)定义nsor模块的代码如下所示。

其中的get_nsors_list()表示用来获得传感器列表。 (2)用nsor_t表示一个传感器的描述,具体代码如下所示。

(3)定义了结构体和联合,具体代码如下

所示。

x0c3.适配层函数接口 在HAL层中需要注意下面的s_device_open函数。

还要特别注意下面的赋值。

在上述函数中,可以根据名字知道JNI应该是采用poll方式来获取数据。也就是说,在驱动中提供的 代码要实现file_operation。 注意: 在驱动代码中获得的Sensor寄存器中的值并不一定是我们实际要上报给应用的值,比如gnsor,则各个方向不应该大于10,一定注意也要考虑其他的。

x0c4.4.2 总结Sensor编程的流程 step1 获取系统服务(SENSOR_SERVICE)返回一个SensorManager对象。

step2 通过SensorManager对象获取相应的Sensor类型的对象。

step3 声明一个SensorEventListener对象用于侦听Sensor事件,并重载onSensorChanged方法。

step4 注册相应的SensorService。

step5 销毁相应的SensorService。

此处的SensorListener接口是整个传感器应用程序的核心,它包括如下两个必需的方法。 ● onSensorChanged(int nsor,float values[]):此方法在传感器值更改时调用,只对受此应用 程序监视的传感器调用(更多内容见下文)。该方法包括如下两个参数。 ○ 一个整数:指示更改的传感器。 ○ 一个浮点值数组:表示传感器数据本身。 注意: 有些传感器只提供一个数据值,另一些则提供三个浮点值。方向和加速表传感器都提供三个 数据值。 ● onAccuracyChanged(int nsor,int accuracy):当传感器的准确性更改时将调用此方法,此 方法的参数包括两个整数,一个表示传感器,另一个表示该传感器新的准确值。

x0c4.4.3 分析Sensor源代码看Android API与硬件平台的衔接 接下来将以重力感应器Sensor为例,看重力感应器如何和Applications、Application Framework实 现衔接。 step1 首先在文件platform/hardware/Libardware/Include/Sensors.h中查看定义重力感应器对驱 动程序的操作,对应代码如下所示。

step2 接下来看文件framework/Jni/,此文件用于加载该驱动的访问程序,具体代码如 下所示。

x0cstep3 看文件framework/Jni/com_android_rver_,此文件用于向 Application Framework提供接口。具体代码如下所示。

x0cx0cx0cx0cx0c到此为止,完成了文件系统中的底层部分功能。由此可以看出,对于下层类库来说,可以通过HAL的 方式建立Android API和硬件设备驱动连接的桥梁。针对不同的硬件平台,我们需要编写上述函数的 实现方式,并且通过Android Kernel里的驱动来控制硬件行为。对于上层来说,可以看做是给顶层 Java实现Android API提供一个访问接口。因为该文件是编译成系统的*.so库文件,这与NDK中为系统 加载一个*.so极其相似。 step4 监听Sensor的物理数据。 接下来开始看Application Framework层是怎样监听Sensor物理数据的。此时可以通过 system.l

oad("*.so")来获得对某个库的访问,并使用里面的函数进行我们想要的操作。Google为了 方便程序员操作,使用Java语言提供了一些便捷访问的Application Framework,他们将底层的 C/C++实现的驱动或者其他细节封装起来,其实这就是API的原型。 回到Android API层,文件 framework/ba/rvice/java/com/android/rver/的实现代码如下所示。

x0cx0cx0cx0cx0cx0c最后的代码就是Core提供给我们使用的API了,为节省篇幅,在此将不再详细介绍。 注意: 其实在Android中还可以直接调用一个驱动,当然这针对的是比较简单的子系统,这些系统并 没有存在硬件抽象层,也就是说实现硬件抽象的功能部分不在单独的代码当中。例如由JNI代码直接 调用的驱动程序的设备节点或者使用SYS文件系统。

x0c4.5 移植总结 介绍了硬件抽象层的内容之后,整个移植内容已经讲解得差不多了。本部分的核心是系统移植,学习 驱动开发离不开移植,移植技术和Linux内核技术是Android驱动开发的最核心基础。本节将对 Android移植的基本知识进行简要介绍。

x0c4.5.1 移植各个Android部件的方式 在Android系统中,不同子系统的移植方法不同。不同部件的移植方式如下所示。 ● 显示系统:使用Framebuffer标准或其他驱动程序,对应的硬件抽象层是Gralloc。 ● 用户输入系统:使用Event设备的驱动程序,对应的硬件抽象层是EventHub。 ● 3D加速系统:使用非标准的驱动程序,对应的硬件抽象层是OpenGL。 ● 音频系统:使用非标准的驱动程序,对应的是C++继承的硬件抽象层。 ● 视频输出系统:使用非标准的驱动程序,对应的硬件抽象层是overlay模块。 ● 摄像头系统:使用非标准的驱动程序,对应的是C++继承的硬件抽象层。 ● 多媒体解码系统:使用非标准的驱动程序,对应的硬件抽象层是Skia和OpenMax插件。 ● 电话系统:使用非标准的驱动程序,对应的硬件抽象层是动态开发插件库。 ● GPS定位系统:使用非标准的驱动程序,对应的硬件抽象层通常是直接接口。 ● 无线局域网:使用Wlan驱动程序,对应的硬件抽象层分别是Linux下的Wpa和Android下的Wi-Fi。 ● 蓝牙系统:使用Bluetooth驱动程序,对应的硬件抽象层分别是Linux下的Bluez和Android下的 Bluedroid。 ● 传感器系统:使用非标准的驱动程序,对应的硬件抽象层是Sensor硬件模块。 ● 振动器系统:使用SYS文件系统中固定位置的驱动程序,对应的硬件抽象层是Android标准的直接 接口。 ● 背光和指示灯系统:使用非标准的驱动程序,对应的硬件抽象层是Light硬件模块。 ● 警告器系统:使用Misc驱动程序,对应的硬件抽象层是Android标准的JNI层。 ● 电池管

理系统:使用SYS文件系统中固定位置的驱动程序,对应的硬件抽象层是Android标准的直 接接口。

x0c4.5.2 移植技巧之一——不得不说的辅助工作 在Android移植过程中,除了移植驱动程序和硬件抽象层之外,还需要实现一些辅助工作。本节就讲 解这些辅助性的工作。 1.设置设备权限 当Android系统启动时,在内核引导参数上一般都会设置init=/init,此时如果内核成功挂载了这个 文件系统,首先运行的就是这个根目录下的init程序。这个init程序是Android系统运行后的第一个 用户空间的程序,它以守护进程的方式运行。 当我们需要增加驱动程序的设备节点时,需要随之更改这些设备节点的属性,这些更改内容保存在文 件system/core/init/devices.c中。此文件代码比较冗长,接下来将只对和权限有关的代码进行讲解 。 (1)定义perms_表示设备的类型,具体代码如下所示。

(2)定义数组devperms表示系统中的设备,具体代码如下所示。

x0c在上述数组中,分别设置了设备的权限、所属用户和所属组。具体权限值的含义和Linux中的完全一 致,其中三个数组分别表示所属用户、所属组和其他人的权限,4表示可读,2表示可写,1表示可执 行。例如数组内的如下首行代码:

/dev/null是一个标准的设备,赋予它的权限是0666,表示任何用户都可以对其进行读写操作。如果 需要增加一个新的设备节点文件,则需要在数组devperms中新增加一行内容。 (3)两个函数。 在文件中有两个比较重要的函数,分别是handle_device_event()和make_device(),具体实现代码如 下所示。

x0cx0c函数get_device_perm()会比较path路径是否和devperms[]数组中的inode路径相同,如果相同则返回 devperms[]数组中指定的uid、gid和mode数值。这样make_device就会在/dev中创建inode节点,同时 改变该inode的uid和gid.[tt]。 (4)和用户名相关的名称。 和用户名相关的名称被定义在文件system/core/include/private/android_filesystem_config.h中 ,其中用android_id_info来表示用户名id的属性,定义代码如下所示。

各个用户名id被定义在数组android_ids[]中,此数组表示了一个映射关系,能够将字符串和整数值 对应起来。定义代码如下所示。

x0c2.实现初始化操作 文件system/core/rootdir/可以实现一些简单的初始化操作,Android中的是启动脚 本。脚本被直接安装到目标系统的根目录下,并被init可执行的程序解析。 是在init启动后被执行的启动脚本,其语法主要包含了下面的内容。 ● Commands:命令 ● Actions:动作 ● Triggers:触发条件 ● Services:服务 ● Options:选项 ● Properti:属性 Commands是一些基本的操作命令,例如:

x0c

这些命令在init可执行程序中被解析,然后可以调用相关的函数来实现。 ● Actions(动作)是表示一系列动作的命令,通常在Triggers(触发条件)中被调用,动作和触 发条件的形式如下所示。

例如下面是使用动作的示例代码。

init表示一个触发条件,当这个触发事件发生后会设置环境变量并建立一个目录,上述操作被称为一 个“动作”。 ● Services(服务):通常表示启动一个可执行程序,用Options(选项)来表示服务的附加内容 以配合服务使用。具体代码如下所示。

其中vold和bootsound分别表示为两个服务的名称,/system/bin/vold和/system/bin/playmp3分别是 它们所对应的可执行程序。 socket、ur、group和oneshot是配合服务使用的选项,其中oneshot选项表示该服务只启动一次 ;如果没有oneshot选项,则这个可执行程序会一直存在;如果可执行程序被杀死,则会重新启动。 3.更改配置文件 在Android硬件抽象层的移植过程中,经常需要向系统中加入运行时的配置文件以配置系统的功能。 etc文件即运行时的配置文件,例如配置信息通常被保存在如下文件中。 ● /system/etc/ APN:接入点配置文件 ● /system/etc/:音频过滤器配置文件 ● /system/etc/:书签数据库 ● /system/etc/:总线监视配置文件 ● /system/etc/:收藏夹 ● /system/etc/firmware:固件信息 ● /system/etc/ GPS:设置文件 ● /system/etc/:内核HCID配置文件 ● /system/etc/hosts:网络DNS缓存 4.文件系统的属性 在文件system/core/include/private/android_filesystem_config.h中定义了各个文件的属性,其 中fs_path_config表示文件系统路径的属性,定义代码如下所示。

x0c在数组android_dirs[]中定义了子目录的属性,定义代码如下所示。

然后在数组android_files[]中定义了默认文件的属性,定义代码如下所示。

x0c第5章 Goldfish下的驱动解析 在本书第2章的内容中,已经讲解过Goldfish处理器的基本知识,并剖析了其内核代码。从本章开始 分析常用内核中的标准驱动程序,为自行开发驱动做好准备。本章将介绍Goldfish内核下驱动程序的 基本知识,为读者学习本书后面的知识打下坚实的基础。

x0c5.1 staging驱动 Android驱动分为专用驱动和设备驱动两大类,其中Android的专用驱动不是Linux中的标准内容,而 是与体系结构和硬件平台无关的纯软件。Android的专用驱动和Linux中的内存驱动类似,主要被保存 在drivers/staging/android目录中,只有极少数的驱动被保存在其他目录中。在移植Android专用驱 动时,无须做出任何更改即可进行配置,并可以灵活选择是否使用驱动程序。 其中Android中专用驱动

的具体类别结构如图5-1所示。

x0c图5-1 Android专用驱动的类别结构 本节将首先讲解Android专用驱动中staging驱动的基本知识。

x0c5.1.1 staging驱动概述 Android的专用驱动程序主要保存在drivers/staging/android目录中,此目录是Android系统特有的 目录,其中还包含了常用的Kconfig文件和Makefile文件。其中Makefile的内容如下所示。

对于上述内容的具体说明如下所示。 ● binder和logger:两个普通的misc驱动程序。 ● timed_output:一种android特有的驱动程序框架。 ● timed_gpio:基于timed_output的一个驱动程序。 ● lowmemorykiller:一个内存管理的组件。 ● ram_console:一个利用控制台驱动的框架。 接下来将简单讲解Makefile内容中上述驱动程序的基本知识。

x0c5.1.2 Binder驱动程序 虽然应用程序是以独立的进程来运行的,但是相互之间还需要通信。例如在多进程的环境下,应用程 序和后台服务通常会运行在不同的进程中,有着独立的地址空间。但是因为需要相互协作,彼此间又 必须进行通信和数据共享,因此需要进程通信来完成。在Linux系统中,进程间通信的方式有很多 ,对应Android可以选择的进程间通信的方式也很多,主要包括以下三种方式。 ● 标准Linux Kernel IPC接口 ● 标准D-BUS接口 ● Binder接口 在上述三种方式中,Android使用得最多也最被认可的是Binder机制。这是因为Binder更加简洁和快 速,并且消耗的内存资源更少。另外Binder还解决和避免了传统的进程间通信可能会增加进程开销的 问题,也避免了进程过载和安全漏洞等方面的风险。Binder主要实现以下功能。 ● 用驱动程序来推进进程间的通信。 ● 通过共享内存来提高性能。 ● 为进程请求分配每个进程的线程池。 ● 针对系统中的对象引入了引用计数和跨进程的对象引用映射。 ● 进程间同步调用。 1.Binder驱动的原理 为了完成进程间通信,Binder采用了AIDL(Android Interface Definition Language)来描述进程 间的接口。在实际的实现中,Binder是作为一个特殊的字符型设备而存在的,其设备节点为 /dev/binder。Binder驱动程序的实现代码由以下两个文件实现。

在其驱动的实现过程中,使用binder_ioctl()函数与用户空间进程交换数据。BINDER_WRITE_READ用 来读写数据,数据包中的cmd域用于区分不同的请求。使用binder_thread_write()函数来发送请求或 返回结果,使用binder_thread_read()函数来读取结果。在binder_thread_write()函数中,通过调 用binder_transaction()函数来转发请求并返回结果。当收到请求时,binder_transaction()函数会 通过对象的handle找到对象所在的进程。如果handle结果为空,则认为此对象是context_mgr,然后 把请求发给context_mg

r所在的进程。并将请求中所有的Binder对象全部放到一个RB树中,最后把请 求放到目标进程的队列中以等待目标进程读取。在函数binder_par()中实现数据解析工作。 2.Binder的工作流程 作为Android系统的核心机制,Binder几乎贯穿整个Android系统,Binder的工作流程如下所示。 step1 客户端首先获得服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端 的“引用”,该代理对象具有服务器端的功能,使其在客户端访问服务器端的方法就像访问本地方法 一样。 step2 客户端通过调用服务器代理对象的方式向服务器端发送请求。 step3 代理对象将用户请求通过Binder驱动发送到服务器进程。 step4 服务器进程处理用户请求,并通过Binder驱动返回处理结果给客户端的服务器代理对象。 step5 客户端收到服务器端的返回结果。 Binder经过上述流程处理实现了一次通信处理。 3.实现Binder驱动 Binder的实质就是要把对象从一个进程映射到另一个进程中,而不管这个对象是本地的还是远程的。 接下来我们先介绍Binder机制中所使用的数据结构体,然后再分析整个流程。 (1)binder_work

x0c结构体binder_work是一个最简单也最基础的结构体,其定义代码如下所示。

在上述代码中,entry被定义为list_head,用来实现一个双向链表,用于存储所有binder_work的队 列;enum类型的type用于表示binder_work的类型。 (2)Binder类型 在头文件binder.h中用enum来表示Binder类型的定义代码,具体代码如下所示。

在上述代码中,Binder被分成了三个不同的大类,分别是本地对象(BINDER_TYPE_BINDER、 BINDER_TYPE_WEAK_BINDER)、远程对象的“引用”(BINDER_TYPE_HANDLE、 BINDER_TYPE_WEAK_HANDLE)和文件(BINDER_TYPE_FD)。在此要特别注意最后一种类型——文件 (BINDER_TYPE_FD)。如果传递的是BINDER_TYPE_FD类型,其实还是会将文件映射到句柄上,根据此 fd找到对应的文件,然后在目标进程中分配一个fd,最后把这个fd赋值给返回的句柄。 (3)Binder对象 通常把进程之间传递的数据称为Binder对象(Binder Object),在源代码中使用 flat_binder_object结构体(位于binder.h文件中)来表示对象,定义代码如下所示。

(4)binder_transaction_data 因为Binder对象所传递的实际内容是通过另外一个结构体binder_transaction_data来表示的,所以 从结构体flat_binder_object中看不到Binder对象所传递的实际内容,定义

x0cbinder_transaction_data的代码如下所示。

此结构体是Binder驱动的核心,各个字段的具体说明如下所示。 ● target字段:是一个复合联合体对象,target字段中的handle是要处理此事件的目标对象的句柄 。Binder驱动根据此handle可以找到应该

由哪个进程处理此事件,并且把此事件的任务分发给一个线 程,而那个线程也正在执行ioctl的BINDER_WRITE_READ操作,即正在等待一个请求。target的ptr字 段与handle对应,请求方使用handle来指出远程对象;响应方使用ptr来寻址,这样可以找到需要处 理此事件的对象。由此可见handle和ptr是一个事物的两种表达形式,handle和ptr之间的解析关系就 是Binder驱动需要维护的任务。 ● cookie字段:表示target对象所附加的额外数据。 ● code:一个命令,用于描述请求Binder对象执行的操作。 ● flags字段:用于描述传输方式与flat_binder_object中的flags字段对应。 ● nder_pid和nder_euid:表示该进程的pid和uid。 ● data_size:表示数据的大小字节数。 ● offts_size:表示数据的偏移量字节数。 ● union数据data:表示真正的数据,其中ptr表示与target->ptr对应的对象的数据,buf表示与 handle对象对应的数据,data中的ptr中的buffer表示实际的数据,而offts则表示其偏移量。 (5)binder_proc 结构体binder_proc用于保存调用Binder的各个进程或线程的信息,例如线程ID、进程ID、Binder状 态信息等,定义代码如下所示。

x0c(6)binder_node 结构体binder_node表示一个Binder节点,定义代码如下所示。

x0c(7)binder_thread 结构体binder_thread用于存储每一个单独的线程的信息,其定义代码如下所示。

● proc字段:表示当前线程属于哪一个Binder进程(binder_proc指针)。 ● rb_node:表示一个红黑树节点。 ● pid:表示线程的pid。 ● looper:表示线程的状态信息。 ● transaction_stack:定义了要接收和发送的进程和线程信息,其结构体为 binder_transaction。 ● todo:用于创建一个双向链表。 ● return_error和return_error2:返回的错误信息代码。

x0c● wait:一个等待队列头。 ● stats:用于表示Binder状态信息。 (8)binder_transaction 结构体binder_transaction用于中转请求和返回结果,并保存接收和要发送的进程信息。定义代码如 下所示。

● work:一个binder_work。 ● from和to_thread:都是一个binder_thread对象,用于表示接收和要发送的进程信息,另外还包 括了接收和发送进程信息的父节点from_parent和to_thread。 ● to_proc:是一个binder_proc类型的结构体。其中还包括flags、need_reply、优先级 (priority)等数据。 ● buffer:用来表示binder的缓冲区信息。 (9)初始化函数binder_init() 驱动的操作从初始化操作开始,在文件binder.c中可以找到该初始化函数,具体实现代码如下所示。

x0cbinder_init()是Binder驱动的初始化函数,初始化函数一般需要设备驱动接口来调用,Android Binder设备驱动接口函数是device_initcall()。初始化函数使用proc_m

kdir创建了一个Binder的 proc文件系统的根节点(binder_proc_dir_entry_root,/proc/binder)。如果创建根节点成功,则 接着为binder创建binder proc节点(binder_proc_dir_entry_proc,/proc/binder/proc);然后 ,驱动Binder使用misc_register把自己注册为一个Misc设备,其设备节点位于/dev/binder,该节点 由init进程在handle_device_fd(device_fd)函数中调用handle_device_event(&uevent)函数执行 ,其中uevent-netlink事件在"/dev/"目录下创建。最后,调用create_proc_read_entry创建以下只 读proc文件:

在注册Binder驱动为Misc设备时,需要指定Binder驱动的miscdevice,具体实现代码如下所示。

x0cBinder设备的主设备号为10,这个设备号需要动态获得,.minor被设置为动态获得设备号 MISC_DYNAMIC_MINOR,用.name来表示设备名称。在最后需要指定该设备的file_operations结构体 ,定义代码如下所示。

任何驱动程序都有向用户空间的程序提供操作接口这一主要功能,这个接口是标准的。 (10)函数binder_open() 函数binder_open()用于打开Binder设备文件/dev/binder,在Android驱动中任何一个进程及其内的 所有线程都可以打开一个Binder设备,其打开过程的实现代码如下所示。

x0c(11)函数binder_relea() 函数binder_relea()与函数binder_open()的功能相反,当Binder驱动退出时,需要使用它来释放 在打开及其他操作过程中分配的空间并清理相关的数据信息。 (12)binder_flush 在关闭一个设备文件描述符复制时调用flush操作接口。通过调用一个workqueue来执行 BINDER_DEFERRED_FLUSH操作以完成该flush操作,将最终处理交给binder_defer_work()函数。 (13)binder_poll 函数poll()是非阻塞型I/O的内核驱动实现,所有支持非阻塞I/O操作的设备驱动都需要实现poll()函 数。Binder的poll()函数仅支持设备是否可以非阻塞地读(POLLIN),在此有proc_work和 thread_work两种等待任务。其他驱动的poll实现一样,也需要通过调用poll_wait()函数来实现。

x0c(14)函数binder_get_thread() 该函数用于在threads队列中查找当前的进程信息。 (15)binder_mmap 用于把设备内存映射到用户进程地址空间中,这样就可以像操作用户内存那样操作设备内存。 (16)binder_ioctl 此部分是Binder的最核心内容,Binder的功能就是通过ioctl命令来实现的。Binder一共有7个 ioctl命令,被定义在ioctl.h头文件中,具体代码如下所示。

命令ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。I/O通道管理是指对设备的一些特性 进行控制,例如串口的传输波特率、马达的转速等。其调用函数是int ioctl(int fd, ind cmd, ...);,其中,fd就是用户程序打开设备时使用open函数返回的文件标识符;cmd就是用户程序对设 备的控制命令;至于后面的省略号,

那是一些补充参数,一般最多一个,有或没有与cmd的意义相关 。ioctl函数是文件结构中的一个属性分量,也就是说,如果你的驱动程序提供了对ioctl的支持,用 户就可以在用户程序中使用ioctl函数控制设备的I/O通道。 注意: 不用ioctl命令也可以实现对设备的I/O通道的控制,但是会十分复杂。例如可以在驱动程序 中实现write的时候检查一下是否有特殊约定的数据流通过,如果有则在后面附加控制命令(一般在 socket编程中常常这样做)。此时会导致代码分工不明确,引发程序结构混乱。所以最好还是使用 ioctl来实现控制的功能。用户程序所做的只是通过命令码告诉驱动程序它想做什么,至于怎么解释 这些命令和怎么实现这些命令,都是驱动程序要做的事情。

x0c5.1.3 Logger驱动程序 Logger驱动是一个轻量级的log驱动,功能是为用户层程序提供Log支持,此驱动通常作为一个工具来 使用。Logger有如下三个设备节点。 ● /dev/log/main:主要的log。 ● /dev/log/event:事件的log。 ● /dev/log/radio:Modem部分的log。 Logger驱动为用户空间提供了ioctl接口、read接口和异步write接口,其主设备号为10(Misc Driver),实现源代码位于如下源文件中。

对于非本用户或本组而言,Logger驱动程序的设备节点是可写而不可读的。在Android用户空间中 ,使用liblog库封装了Logger驱动程序,其路径为system/core/liblog。logcat程序负责调用 Logger驱动,此程序是一个可知性程序,是当用户取出系统log信息后在系统中使用的一个辅助工具 ,logcat程序的代码路径为system/core/logcat。

x0c5.1.4 Lowmemorykiller组件 组件Lowmemorykiller是一个内存管理组件,它可以根据需要“杀死”进程来释放需要的内存。和 Linux标准OOM(Out Of Memory)机制相比,Lowmemorykiller更加灵活。当内存不够的时候,该策略 会试图结束一个进程。组件Lowmemorykiller通过调用Linux内存管理系统的接口来注册一个 shrinker,此处的shrinker是通过Lowmemorykiller实现的。 组件Lowmemorykiller的源代码位于drivers/staging/android/lowmemorykiller.c,其中核心部分代 码如下所示。

和组件Lowmemorykiller密切相关的配置文件有如下两个,其中设置了配置系统的相关参数。

注意: 标准Linux内核OOM Killer在mm/oom_kill.c中实现,在 mm/page_alloc.a_alloc_pages_may_oom中被调用。文件oom_kill.c最主要的函数是 out_of_memory(),它选择一个bad进程杀死,通过发送SIGKILL信号来杀死进程。 在out_of_memory中通过调用lect_bad_process选择杀死一个进程,选择的依据在badness()函数中 实现,基于多个标准来给每个进程算分,分最高的被选中杀死。基本上是占用内存越多,oom_adj越 大越有可能被选中。 由此可以看出

,Android的Lowmemorykiller和标准的OOM Killer的很多思路是一致的,只不过 Lowmemorykiller作为一个shrinker实现;而OOM Killer则在分配内存时候被调用(如果内存资源很 紧张)。Android的low memory killer实现得较为简洁,这一点从代码尺寸就能看出,但并不觉得比 OOM Killer更为灵活。它只不过是另一种OOM Killer。

x0c5.1.5 Timed Output驱动程序 Timed Output驱动程序是Android中一个很重要的框架,例如经常通过Timed Output驱动程序框架来 实现Vibrator(振动)驱动程序。Timed Output驱动是基于SYS文件系统来完成的,能够对设备进行 定时控制功能,目前支持设备有Vibrator(振动)和LED(闪光灯)设备。 Timed Output驱动会注册sys/class/timed_output/目录,每一个注册实现的Timed Output设备(例 如Vibrator和LED)将会在sys/class/timed_output/目录中新建一个和设备同名的子目录。在子目录 中有一个enable文件,通过对此文件的读写实现对设备的控制和显示。 有如下两个实现Timed Output驱动的文件。

在文件timed_output.h中定义了结构体timed_output_dev,使设备设置定时器功能,并设置返回定时 器的剩余时间。文件timed_output.h的实现代码如下所示。

在文件timed_output.c中,定义了函数timed_output_dev_register()实现Timed Output设备的注册 ,此函数的实现代码如下所示。

x0c在文件timed_output.c中,定义了函数timed_output_dev_unregister()实现Timed Output设备的注 销,此函数的实现代码如下所示。

在文件timed_output.c中定义了函数enable_show()和enable_store(),这两个函数通过调用SYS文件 系统来实现驱动功能。文件enable是每个Timed Output设备中都有的文件,在写这个文件的时候表示 设置定时器时间并启动定时器,这两个函数的实现代码如下所示。

x0cx0c5.1.6 Timed Gpio驱动程序 Timed Gpio是基于Timed_Output的一个驱动程序,能够定时控制GPIO。Timed Gpio可以调用Timed Output框架注册一个驱动程序。Timed Gpio驱动程序保存在如下两个文件中。

在文件timed_gpio.h中定义了Timed Gpio驱动程序的名称,并设置结构体timed_gpio作为驱动程序的 私有结构体。文件timed_gpio.h的实现代码如下所示。

在文件timed_gpio.c中,通过下面的两个函数分别实现对驱动设备的注册和注销。

x0c5.1.7 Ram Console驱动程序 Ram Console驱动程序是一个控制台驱动的框架,它提供了一种可以辅助调试的内核机制。为了提供 调试功能,Android允许将调试日志信息写入一个被称为Ram Console的设备里,它是一个基于RAM的 Buffer。 Ram Console与用户空间之间的接口是proc文件系统,在proc中使用名为last_kmsg的文件来表示 kernel最后打出的信息。Ram Console驱动程序保存在如下文件中。

在文件ram_console.c中实现注册功能的函数

代码如下所示。

x0c5.2 wakelock和early_suspend wakelock和early_suspend是Android系统中的一种特殊机制,能够实现系统的“唤醒”和“休眠”功 能,获取系统资源的信息,例如电源信息和CPU信息等。

x0c5.2.1 wakelock和early_suspend的原理 1.wakelock wakelock在Android的电源管理系统中扮演一个核心的角色。wakelock是一种“锁”机制,只要有人 拿着这个锁,系统就无法进入休眠状态。这个锁可以是有超时的或没有超时的,超时的锁会在时间过 去以后自动解锁。如果没有锁了或者超时了,内核就会启动休眠的那套机制来进入休眠。 当系统在启动完毕后,会自己去加一把名为main的锁,而当系统有意愿去睡眠时则会先去释放这把 “main”锁。在Android中,在early_suspend的最后一步会去释放main锁(wake_unlock: main)。 释放完后则会去检查是否还有其他存在的锁,如果没有则直接进入睡眠过程。 它的缺点是,如果有某一应用获锁而不释放或者因一直在执行某种操作而没时间来释放的话,则会导 致系统一直进入不了睡眠状态,功耗过大。 在wakelock中有三种类型,最常用的是WAKE_LOCK_SUSPEND,作用是防止系统进入睡眠。wakelock的 接口定义在文件wakelock.c中,定义代码如下所示。

在wakelock中,有如下两个地方可以让系统从early_suspend进入suspend状态。 ● 在wake_unlock中,当解锁之,没有其他的wakelock时,则进入suspend。 ● 当超时锁的定时器超时后,定时器的回调函数会判断有没有其他的wakelock,若没有则进入 suspend。 2.early_suspend early_suspend在Linux内核的睡眠过程前被调用。因为背光需要的能耗过大,所以常采用此类方法在 手机系统的设计中操作背光。如一些在内核中预先进行处理的事件可以先注册上early_suspend函数 ,这样当系统进入睡眠之前会首先调用这些注册的函数。 和Android休眠唤醒相关的实现文件如下所示。

x0c5.2.2 Android休眠 当用户读写/sys/power/state时,文件linux_source/kernel/power/main.c中的state_store()函数 会被调用。其中,Android的early_suspend会执行:

标准的Linux休眠会执行:

函数state_store()的原型如下所示。

在函数request_suspend_state()中,会调用early_suspend_work的工作队列以进入 early_suspend()函数中。函数request_suspend_state()的原型如下所示。

在函数early_suspend()中,首先要判断当前请求的状态是否还是suspend,如果不是则直接退出;如 果是,则函数会调用已经注册的early_suspend的函数。然后同步文件系统,最后释放 main_wake_lock。函数early_suspend()的原型如下所示。

在函数wake_unlock()中删除链表中的wake_lock节点,目的是判断当前是否存在wake_lock。如果 wake_lock的数目为0,则调用工作队列suspen

d_work,然后进入suspend状态。函数wake_unlock()的 原型如下所示。

在函数suspend()中,首先判断当前是否有wake_lock,如果有则退出;然后同步文件系统,最后调用 pm_suspend()函数。函数suspend()的原型如下所示。

在函数pm_suspend()中调用enter_state()函数,就进入了标准linux的休眠过程。函数 pm_suspend()的原型如下所示。

在函数enter_state()中,首先检查一些状态参数,再同步文件系统,然后调用suspend_prepare()来 冻结进程,最后调用suspend_devices_and_enter()让外设进入休眠。函数enter_state()的原型如下 所示。

在函数suspend_prepare()中,先通过pm_prepare_console()给suspend分配一个虚拟终端来输出信息 ,再广播一个系统进入suspend的通报,关闭用户态的helper进程,然后调用函数 suspend_freeze_process()来冻结进程,最后会尝试释放一些内存。函数suspend_prepare()的原 型如下所示。

在函数suspend_freeze_process()中调用freeze_process()函数,而在freeze_process()函数 中又调用try_to_freeze_tasks()函数来完成冻结任务。在冻结过程中,会判断当前进程是否有 wake_lock,如果有则冻结失败,函数会放弃冻结。函数freeze_process()的原型如下所示。

x0c到此为止,所有的进程都已经停止了,内核态进程有可能在停止的时候握有一些信号量,如果这时在 外设里面去解锁这个信号量有可能会发生死锁,所以建议不要在外设的suspend()里面等待锁。而且 在suspend的过程中,很多log是无法输出的,所以一旦出现问题就非常难以调试。 接下来回到enter_state()函数中。当冻结进程完成后调用suspend_devices_and_enter()函数,目的 是让外设进入休眠。在该函数中,首先休眠串口,然后通过device_suspend()函数调用各驱动的 suspend函数。 当外设进入休眠后调用suspend_ops->prepare(),suspend_ops是板级的PM操作,假如是s3c6410,则 被注册在文件linux_source/arch/arm/plat-s3c64xx/pm.c中,在其中只定义了suspend_ops>enter()函数。

然后在多CPU中关闭非启动CPU,代码如下所示。

x0c接下来调用函数suspend_enter(),该函数将首先关闭IRQ,然后调用device_power_down(),此函数 会调用suspend_late()函数。这个函数是系统真正进入休眠最后调用的函数,通常会在这个函数中做 最后的检查,接下来休眠所有的系统设备和总线。最后调用suspend_pos->enter()使CPU进入省电状 态。此时整个休眠过程完成了。函数suspend_enter()的原型如下所示。

x0c5.2.3 Android唤醒 如果在休眠中系统被中断或者被其他事件唤醒,接下来的代码就从suspend完成的地方开始执行。以 s3c6410为例,即在文件pm.c中执行函数s3c6410_pm_enter()中的CPU初始化部分,然后执行 suspend_enter()的sysdev_resume()函数,唤醒系统设备和总线,

使能系统中断。 然后回到suspend_devices_and_enter()函数中,使能休眠时候停止的非启动CPU,并且继续唤醒每个 设备。当函数suspend_devices_and_enter()被执行完成后,系统外设已经唤醒,但进程依然是冻结 的状态,返回到enter_state函数中,调用suspend_finish()函数。 在函数suspend_finish()中解冻进程和任务,这样可以使用户空间帮助进程,从而广播一个系统从 suspend状态退出的notify。 注意: 当所有的唤醒已经结束以后,用户进程都已经开始运行了,但没点亮屏幕。唤醒通常会是以 下几种原因。 如果是来电,那么Modem会发送命令给rild,这样可以让rild通知WindowManager有来电响应,这样就 会远程调用PowerManagerService来写on到/sys/power/state来调用late resume(),执行点亮屏幕等 操作。 用户按键事件会送到WindowManager中,WindowManager会处理这些按键事件。按键分为几种情况,如 果按键不是唤醒键,那么WindowManager会主动放弃wakeLock来使系统进入再次休眠;如果按键是唤 醒键,那么WindowManger就会调用PowerManagerService中的接口来执行late Resume。 当将“on”标识写入/sys/power/state之后,同early_suspend的执行过程一样 ,request_suspend_state()会被调用,只是执行的工作队列变为late_resume_work。在 late_resume()函数中,唤醒调用了early_suspend的设备。

x0c5.3 Ashmem驱动程序 Ashmem是Android的“内存分配/共享”机制,经常被称为匿名共享内存。在dev目录下对应的设备是 /dev/ashmem。和传统的内存分配机制相比,例如malloc、anonymous/named mmap,Ashmem的好处是 提供了辅助内核内存回收算法的pin/unpin机制。 1.原理 Ashmem基于mmap系统调用,不同进程可以将同一段物理内存映射到各自的虚拟地址控制,从而实现共 享。假如A进程的内存空间范围0X0000~0XFFFF,B进程的内存空间范围0X0000~0XFFFF,它们两个进 程想共同共享一个文件或一段空间时,可以使用mmap(比如都想读取硬盘上的,txt内容为 “123”),首先另外开辟第三个内存空间(3个字节),将硬盘上的映射到此内存空间中,使 此内存空间拥有这个,再将A、B进程分别映射至这个内存空间,则现在A进程的内核空间范围为 0X0000~0XFFFF+4,B进程的内核空间范围为0X0000~0XFFFF+4。此时进程A和B都拥有了共同的内存 空间,即可以互相共享共同内存空间里的内容了。在创建mmap时还可以指定是否可读写,如果A或B改 变了共同内存空间的值,将内容改为了“234”,硬盘上的内容仍然为123,若想改变,则 需要调用msync实现硬盘和共享内存区的同步,而Ashmem与mmap稍有不同的是,Ashmem与cache shrinker关联起来,可以在适当时机去回收这些共享内存,这一点比较智能

,而mmap则不能。 2.分析Ashmem源代码 Ashmem的源代码保存在内存管理的mm目录中,实现文件如下:

Ashmem的头文件如下:

(1)文件ashmem.h 在文件ashmem.h中定义了ioctl命令,定义代码如下所示。

(2)文件ashmem.c 在文件ashmem.c中定义了Ashmem类,通过注册cache shrinker来实现内存回收,通过注册misc提供 mmap接口。Ashmem分别使用如下两个结构体来实现维护工作。 ● ashmem_area:代表共享内存的区域,其中有一个unpinned_list成员,挂在这个list上的 range可以被回收。 ● ashmem_range:将这段区域以页为单位分为多个range,其中有一个LRU链表,在cache shrink回 收一个ashmem_area的某段内存时,根据LRU的原则来选择哪些页面优先被回收。 在文件ashmem.c中需要用到两个数据结构,其中数据结构ashmem_area的代码如下所示。

x0c上述结构体是Android共享内存区,当父进程调用open()函数时有效,在调用relea()函数时消亡 ,通过ashmem_mutex来保护其互斥性。 数据结构struct ashmem_range代表unpinned页的区间,生命周期从unpin到pin,也需要用 ashmem_mutex来保证其互斥性。其定义代码如下所示。

接下来需要定义全局变量LRU链表,并定义LRU链表计数器。定义代码如下所示。

接下来看实现函数,在文件ashmem.c中定义的实现函数如下所示。

x0c然后定义数据结构file_operations ashmem_fops,代码如下所示。

和上述数据结构有关的成员函数如下所示。

3.Ashmem实现 Ashmme的典型用法是先打开设备文件,然后实现mmap映射。基本实现流程如下所示。 (1)调用ashmem_create_region()函数,此函数需要完成以下三件事。 ● fd = open("/dev/ashmem", O_RDWR); ● ioctl(fd, ASHMEM_SET_NAME, region_name); //这一步可选 ● ioctl(fd, ASHMEM_SET_SIZE, region_size); (2)应用程序调用mmap来把ashmem分配的空间映射到进程空间。

应用程序还可以通过ioctl来pin和unpin某一段映射的空间,以提示内核的page cache算法可以把哪 些页面回收,这是一般mmap做不到的。 由此可见,ashmem以较小的代价(用户需进行额外的ioctl调用来设置名字、大小、pin和unpin)获 得了一些内存使用的智能性。并且Ashmem借助了内核已经有的工具,所以本身实现很小巧,只有不到 700行代码。如果Ashmem不使用内核驱动实现,则pin/unpin的语义比较难以实现,即便实现了,效率 也不会很高。

x0c注意: 如果不使用ashmem驱动,并且舍弃pin/unpin语义,可以很简单地模拟Ashmem的语义。首先 ,ashmem_create_region可以为进程创建一个唯一的文件(如进程名+时戳),打开,然后返回这个 文件的fd;接着应用程序就可以进行一般的mmap操作了。如果不使用ashmem_create_region接口函数 ,那么使用anonymous的mmap就可以了,

但这种方式属于正在被丢弃的方式,而且并不是所有的系统 都支持,比如Macos就不支持。

x0c5.4 Pmem驱动程序 Pmem与Ashmem都是通过mmap实现共享的,区别是Pmem的共享区域是一段连续的物理内存,而Ashmem的 共享区域在虚拟空间是连续的,物理内存却不一定连续。 1.PMEM的原理 Pmem的源代码保存在drivers/misc/pmem.c中,Pmem驱动程序依赖于Linux的misc device和platform driver框架,一个系统可以有多个Pmem,默认为最多10个。Pmem暴露了如下4组操作。 ● platform driver的probe和remove操作; ● misc device的fops接口和vm_ops操作。 在初始化Pmem模块时会注册一个platform driver,在后面的probe时会创建misc设备文件,分配内存 ,完成初始化工作。 Pmem通过如下三个结构体来维护分配的共享内存。 ● pmem_info:代表一个Pmem设备分配的内存块。 ● pmem_data:代表该内存块的一个子块。 ● pmem_region:负责把每个子块分成多个区域。 其中pmem_data是分配的基本单位,即每次应用层要分配一块Pmem内存,就会有一个pmem_data来表示 这个被分配的内存块,实际上在open的时候,并不是open一个pmem_info表示的整个Pmem内存块,而 是创建一个pmem_data以备使用。一个应用可以通过ioctl来分配pmem_data中的一个区域,并可以把 它map到进程空间;并不一定每次都要分配和map整个pmem_data内存块 2.用户接口 一个进程首先打开Pmem设备,通过ioctl(PMEM_ALLOCATE)分配内存,当mmap这段内存到自己的进程空 间后,该进程成为master进程。其他进程可以重新打开这个Pmem设备,并且可以通过调用 ioctl(PMEM_CONNECT)将自己的pmem_data与master进程的pmem_data建立连接关系,此时这个进程就 成为client进程。client进程可以通过mmap将master Pmem中的一段或全部重新映射到自己的进程空 间,这样就实现了共享Pmem内存。如果是GPU或DSP,则可以通过ioctl(PMEM_GET_PHYS)获取物理地址 进行操作。

x0c5.5 Alarm驱动程序 Alarm是一个硬件时钟,前面我们已经知道它提供了一个定时器,用于把设备从睡眠状态唤醒,同时 它也提供了一个在设备睡眠时仍然会运行的时钟基准。在应用层上,有关时间的应用都需要Alarm的 支持,源代码位于drivers/rtc/alarm.c。

x0c5.5.1 Alarm简析 Alarm的设备名为/dev/alarm,此设备的实现非常简单,打开源代码后首先看到:

其中定义了一些和Alarm相关的信息,主要包括如下5种类型的Alarm。 ● _WAKEUP类型:表示在触发Alarm时需要唤醒设备,反之,不触发Alarm则不需要唤醒设备。 ● ANDROID_ALARM_RTC类型:表示在指定的某一时刻触发Alarm。 ● ANDROID_ALARM_ELAPSED_REALTIME:表示在设备启动后,流逝的时间达到总时间之后触发 Alarm。 ● ANDROID_ALARM_SYSTEMTIME

类型:表示系统时间。 ● ANDROID_ALARM_TYPE_COUN:表示Alram类型的计数。

x0c5.5.2 Alarm驱动程序的实现 Alarm返回标记随着Alarm的类型而改变。通过定义的宏实现禁用Alarm、Alarm等待、设置Alarm等功 能。下面分析Alarm驱动程序的具体实现。 1.alarm_init 在初始化过程中,首先需要初始化系统时间,通过platform_driver_register()函数来注册Alarm驱 动程序的相关参数,具体代码如下所示。

在上述代码中,指定了当系统挂起(suspend)和唤醒(Desume)时所需要的实现,分别是 alarm_suspend和alarm_resume,同时将Alarm设备驱动的名称设置为alarm。 2.alarm_late_init 当启动Alarm后需要读取当前的RCT和系统时间,由于需要确保在这个操作过程中不被中断,或者在中 断之后能告诉其他进程该过程没有读取完成,不能被请求,因此这里需要通过spin_lock_irqsave和 spin_unlock_irqrestore来对其执行锁定和解锁操作。对应代码如下所示。

3.alarm_exit 当Alarm退出时需要通过class_interface_unregiste()函数卸载在初始化时注册的Alarm接口,通过 wake_lock_destroy()函数销毁SUSPEND lock,并通过platform_driver_unregister()函数卸载 Alarm驱动。具体实现代码如下所示。

4.添加和移除设备 接下来讲解函数rtc_alarm_add_device()和函数rtc_alarm_remove_device()的实现。在添加设备时 ,首先将设备转换成rtc_device类型,然后通过misc_register()函数将自己注册成为一个Misc设备

x0c。在此功能阶段的主要对应代码如下所示。

其中alarm_device中的.name表示设备文件名称,alarm_fops定义了Alarm的常用操作,包括打开、释 放和I/O控制。另外还需要通过rtc_irq_register()函数注册一个rtc_task,用来处理Alarm触发的方 法。

x0c5.6 USB Gadget驱动程序 USB Gadget是Linux系统中的USB驱动程序,在Android系统中,新增了ADB Garget驱动程序来实现驱 动功能。当使用Garget驱动程序时,Android将作为一个USB设备而提供一个ADB接口。 在Linux系统中,ADB Garget的功能主要体现在设备端,并且每一个硬件只能选一个。在ADB Garget中包含了ADB的调试功能和大容量存储器的功能。 ADB Garget驱动程序的源代码保存在/drivers/usb/gadget目录下,具体来说由三个文件实现,分别 是android.c、f_adb.c和f_mass_storage.c。其中g_是由这三个文件编译而来的,文件 android.c依赖于f_adb.c和f_mass_storage.c(两个文件f_adb.c和f_mass_storage.c之间没有依赖 关系)。f_adb.c是实现ADB功能的文件,f_mass_storage.c是标准的文件,包含此文件的目的是为了 同时实现大容量存储器的功能。 在文件android.c中注册了一个MISC设备dev/android_adb_enable,当打开这个设备时表示用ADB Garget的功能。在文件android.c中分别注册adb和mass storage的代码如下所示。



在文件f_afb.c中也注册了一个MISC设备dev/android_adb,此设备支持读写功能。 注意: 在调试Android系统的gadget功能时,会通过make menuconfig发现gadget是被编译成 module模式的。当将Android编译成module(即g_)模式时,如果放入到rootfs中手动加 载会发现非常顺利。但是当关闭adb进程rmmod g_android的时候会出现死锁状态。我们打开代码查询 问题所在:

调试时发现一直死锁,打开kernel hacking中的spinlock debug,里面提示是自己把自己锁了。我们 接下来分析rmmod时调用函数的过程。当rmmod时会首先执行__exit cleanup(void)中的 usb_composite_unregister(&android_usb_driver),并且还调用composite.c中的 adb_function_unbind()函数。看此函数的定义代码:

x0c问题找到了,只需要重新编写一个不加锁的req_get_free_lock来替代while循环中的req_get()函数 即可解决。

x0c5.7 Android Paranoid驱动程序 Android Paranoid是一个网络驱动程序,Android对Linux内核的网络部分进行了改动,改动后增加了 网络认证机制。上述改动特性是通过宏ANDROID_PARANOID_NETWORK实现的。此修改涉及了Linux源代 码中的以下文件。 ● net/ipv4/af_inet.c:IPV4协议文件。 ● net/ipv6/af_inet5.c:IPV6协议文件。 ● net/bluetooth/af_bluetooth.c:蓝牙协议文件。 ● curity/commoncap.c:安全性文件。 上述文件中,前三个是三种不同网络协议中处理协议方面的文件,在逻辑上是并列的关系。有关网络 部分AID的定义是在文件include/linux/android_aid.h中实现的,对应代码如下所示。

在文件af_inet.c中会进一步检查AID,只有符合时才返回1,如果没有附加此特性,则直接返回1。在 文件commoncap.c中与之相关的代码如下所示。

通过上述代码实现了对AID的判断,如果AID符合要求则返回0,并不会再使用函数return cap_raid()进行处理。

x0c5.8 Goldfish设备驱动 经过前面内容的学习,Android专用驱动的内容已经讲解完毕。本节将介绍Goldfish处理平台中设备 驱动的基本知识。Goldfish处理平台中的设备驱动的类别结构如图5-2所示。

图5-2 设备驱动类别结构

x0c5.8.1 FrameBuffer驱动 在Android中使用SurfaceFlinger作为屏幕合成引擎,用它管理来自各个窗口的Surface objects,然 后将其写入到FrameBuffer去。每一个Surface都是双缓冲的,SurfaceFlinger使用前buffer负责合成 ,使用后buffer负责绘制。一旦绘制完成,Android通过页翻转操作,交换Y轴坐标的偏移量,选择不 同buffer。在EGL显示服务初始化时,如果虚拟Y轴分辨率大于实际Y轴分辨率,说明FrameBuffer可以 直接使用双缓冲。否则,后buffer要复制到前buffer,这样会导致页交换延迟。为了提高系统性能 ,FrameBuffer驱动最好提供双缓冲机制。 Goldfish的FrameBuffer驱动

中提供了双缓冲机制,用于Android SDK中基于QEMU的模拟器。 FrameBuffer对应的源文件保存在linux/drivers/video/目录下。总体抽象设备文件为fbcon.c,在此 目录下还有与各种显卡驱动相关的源文件。FrameBuffer设备驱动基于下面的文件。 ● linux/include/linux/fb.h:定义一些变量结构和宏。 ● linux/drivers/video/fbmem.c:实现设备入口和初始化。 ● xxxfb.c:自己添加的设备驱动文件,例如struct fb_info,有两个实现入口点函数,分别是 xxxfb_init()和xxxfb_tup()。 1.文件fb.h FrameBuffer设备在很大程度上依靠了下面的数据结构。 ● Struct fb_var_screeninfo:描述图形卡的特性。通常是被用户设置的。 ● Struct fb_fix_screeninfo:定义了图形卡的硬件特性,不能改变。 ● Struct fb_cmap:描述了和设备无关的颜色映射信息。可以通过FBIOGETCMAP和FBIOPUTCMAP对应 的ioctl操作设定或获取颜色映射信息。 ● Struct fb_info:定义了当前图形卡FrameBuffer设备状态,一个图形卡可能有两个 FrameBuffer,在这种情况下,就需要两个fb_info结构。这个结构是唯一在内核空间可见的。在这个 结构中有一个fb_ops指针,指向驱动设备工作所需的函数集。 ● Struct fb_ops:用户应用可以使用ioctl()系统调用来操作设备,这个结构就是用于支持 ioctl()的这些操作的。 2.文件fbmem.c 文件fbmem.c处于FrameBuffer设备驱动技术的中心位置,既为上层应用程序提供系统调用,也为下一 层的特定硬件驱动提供接口。底层硬件驱动需要用到此处的接口向系统内核注册自己。文件 fbmem.c为所有支持FrameBuffer的设备驱动提供了通用的接口。接下来简单分析文件fbmem.c的主要 内容。 (1)全局变量 通过两个变量记录了所有fb_info结构的实例,用fb_info结构描述了显卡的当前状态,所有设备对应 的fb_info结构都保存在这个数组中。当一个FrameBuffer设备驱动向系统注册自己时,其对应的 fb_info结构就会添加到这个结构中,同时num_registered_fb自动加1。对应代码如下所示。

(2)函数 文件fbmem.c主要实现了如下两个函数。

这两个函数是提供给下层FrameBuffer设备驱动的接口,设备驱动通过这两个函数向系统注册或注销 自己。几乎底层设备驱动所要做的任务就是填充fb_info结构,然后向系统注册或注销。 3.可以自行编写自己的驱动文件

x0c例如下面的代码。

4.操作FrameBuffer 操作FrameBuffer的基本步骤如下所示。 ● 打开一个可用的FrameBuffer设备。 ● 通过mmap调用把显卡的物理内存空间映射到用户空间。 ● 更改内存空间里的像素数据并显示。 ● 退出时关闭FrameBuffer设备。 例如在下面的代码中,使用FrameBuffer绘制了一个渐变的进度条,实现文件framebuf.c的

具体代码 如下所示。

x0cx0cx0cx0c5.8.2 键盘驱动 Goldfish平台中的键盘驱动是Goldfish_events,其源代码路径如下所示。

在文件goldfish_events.c中先定义枚举,具体代码如下所示。

然后定义数据结构event_dev,具体代码如下所示。

然后进行模块初始化,对应代码如下所示。

通过初始化实现注册功能,对应代码如下所示。

在函数platform_driver_register()中会执行下面的代码。

最后调用函数platform_drv_probe(),此函数的实现代码如下所示。

在上述函数代码中,to_platform_driver(_dev->driver)的作用是返回一个platform_driver型的指 针,而to_platform_device(_dev)的作用是返回一个platform_device型的指针。

x0c5.8.3 实时时钟驱动程序 Goldfish平台中的实时时钟驱动程序就是RTC设备,这也是Linux中的一种标准驱动程序,在用户空间 提供了设备节点,例如MISC和自定义字符设备。其源代码路径如下所示。

其中goldfish_rtc_read_time()是其读取时间的调用函数,定义代码如下所示。

函数int goldfish_rtc_probe()是一个探测函数,定义代码如下所示。

x0c由此可见,RTC驱动程序的主要功能是实现探测和读取时间,模拟实现时钟功能。

x0c5.8.4 TTY终端驱动程序 Goldfish平台中的TTY终端驱动提供了虚拟串口功能,其实现源代码被保存在如下文件中。

Goldfish的TTY终端驱动程序在用户空间有三个设备,对应的节点分别是dev/ttyS0、dev/ttyS1和 dev/ttyS2。TTY终端驱动程序只支持写操作,驱动程序写功能是通过文件goldfish_tty.c的 goldfish_tty_do_write()函数实现的,此函数的实现代码如下所示。

x0c5.8.5 NandFlash驱动程序 Goldfish平台中的NandFlash驱动程序提供了对Flash设备的支持,其实现源代码被保存在如下文件中 。

NandFlash驱动程序是标准的MTD驱动程序,所以Goldfish的Nand驱动程序将会为每一个分区构建字符 设备和块设备。在同一个分区中,可能会有两个字符设备分别用于读写操作和只读操作。

x0c5.8.6 MMC驱动程序 Goldfish平台中的MMC驱动程序是标准的MMC主机驱动程序,在手机应用中常用于实现SD卡驱动,有时 也被称为多媒体驱动程序。MMC驱动程序的标准实现源代码被保存在如下文件中。

当有MMC或者SD卡注册时,才会使用MMC驱动程序。

x0c5.8.7 电池驱动程序 Goldfish平台中的电池驱动程序的标准实现源代码被保存在如下文件中。

这里的电池驱动是一个power_supply驱动程序,能够读取电池设备的电量属性,例如剩余电量和总电 量等。获取属性功能是通过函数goldfish_ac_get_property()函数实现的,此函数的定义代码如下所 示。

第6章 MSM内核和驱动解析 在上一章的内容中,详细讲解了Goldfish内核移植和驱动的基本知识。本章将简要介绍MSM内核的基

本知识,并简要讲解内核移植和各种驱动的基本知识,为读者学习本书后面的知识打下坚实的基础。

x0c6.1 MSM基础 MSM是美国高通公司的应用处理器,是Android系统最常用的处理器产品之一。本节将简要介绍MSM处 理器的基本知识。

x0c6.1.1 常见MSM处理器产品 MSM处理器汇集了多款经典产品,在其发展过程中主要有如下几种处理器产品。 1.MSM7200 MSM7200解决方案支持上行密集型(uplink-intensive)服务,例如IP语音(VoIP)、3D多人无线游 戏,以及实时共享高质量视频和图像的一按式多媒体(push-to-multimedia)应用。此外 ,MSM7200芯片组还支持大容量附件电子邮件的发送和接收,从而进一步提高企业效率。 MSM7200芯片组支持的下行链路的数据传输速率高达7.2Mb/s,上行链路的数据传输速率高达 5.76Mb/s,这一速率高于有线宽带连接的速率。作为融合平台的一部分,MSM7200还支持第三方操作 系统,从而进一步将消费类电子产品功能和无线通信功能融合在一起。 高通MSM7200芯片的CPU部分主频高达400MHz。采用双核构架,有一个400MHz的Arm11核心负责程序部 分,一个频率为274MHz的Arm9核心负责通信,拥有高速的网络接口,可以支持GPRS、EDGE、WCDMA、 HSDPA、HSUPA等数据连接。另外MSM7200还可以提供Java硬件加速,拥有独立的音频处理模块,内建 Q3Dimension 3D渲染引擎,支持OpenGL ES 3D图形加速,拥有每秒4百万多边形计算、133万像素填充 能力。从硬件上支持H.263及H.264的视频解码。在摄像头方面给予最大的支持,并且具备内建GPS模 块的功能。可以说MSM是一块高度集成的处理器,而且性能非常强劲。 2.MSM7201A MSM7201A是单芯片、双核的解决方案,可以提供高速数据处理功能、硬件加速多媒体功能、3D图形及 嵌入式多模3G移动宽带连接以实现完美的无线体验。 MSM7201A芯片组内建3D图形处理模块,以及嵌入模式的3G连接。它还具备高速数据传输及处理功能 ,同时支持硬件加速技术。这样的硬件设计丰富了Android平台的功能,支持多样化的应用服务,为 用户带来新鲜的个性体验。不过话分两头说,这个主频为528MHz的MSM7201A芯片组已经在HTC Touch Diamond和Touch Pro这两款机器上使用了。 MSM7201A芯片组支持高分辨率的图像及视频播放,流媒体功能表现也很出色,支持包括YouTube在内 的服务。300万像素的摄像头可以有效地扫描条形码,用户可以在网上查找到相关物品的售价,这样 方便比较同样商品的售价。SM7201A芯片组支持GPS卫星定位功能。而且高通公司透露MSM7201A芯片组 将会在未来其他制造商的Android平台手机上使用。 3.QSD8250 QSD8250支持HSPA数据传输,下行速率可达7.2Mb/s,上行速率达5.76Mb/s,并提供全

向后兼容(Full Backward Compatibility)。双模的QSD8650支持HSPA及CDMA2000 1xEV-DO Rev.B,并提供全向后兼 容。此两款解决方案均含有1GHz的微处理器核心,搭配高通第六代以600MHz运作的DSP核心,可提供 随开即用(instant-on)及全时连线(always-connected)的使用者体验。 Snapdragon支持高传真影像解码、1200万像素的照相功能、GPS、移动电视(含MediaFLO、DVB-H及 /或ISDB-T标准)、Wi-Fi及蓝牙功能,可协助设备制造商设计即时、无缝连线的轻薄手机。 4.QSD8650 QSD8650和QSD8250是高通为不同网络用户而设计的两个芯片解决方案,其中,QSD8650是双模芯片解 决方案,它不仅可以像QSD8250那样支持HSPA数据传输,而且支持CDMA2000、CDMA1×网络以及EV-DO Rev.B数据传输。这两个解决方案都包含1GHz处理器核心。 5.高通MSM8255 MSM8255采用45纳米级单核心技术的CPU芯片,制程的提升有助于省电和缩小芯片尺寸,省电是比较重 要的提升。其次,GPU的提升也非常明显,QSD8250内建Adreno 200图形处理芯片,而MSM8255为 Adreno 205,虽然数字只差5,但是性能翻倍,对于Android这样吃GPU的系统来说,高性能GPU就更有 必要了。 MSM8255是世界首款1.4GHz单核,MSM8x55芯片组平台包括MSM8255和MSM8655,专为高性能智能手机和 平板电脑设计,以最新设计和优化的多媒体子系统及45nm处理技术为特色,在低能耗的同时提供一流 的单核处理性能。这一平台同时包括APQ8055处理器,专为平板电脑和大型展示设备设计,无须使用 WWAN调制解调器。

x0c6.MSM8260 MSM8260是世界首款1.5GHz移动异步双核,MSM8x60芯片组平台包括MSM8260和MSM8660,满足多任务、 高级游戏和娱乐需要,使用低电能45nm处理技术,具有更高的整合度和性能。这一平台同时包括 APQ8060处理器,专为平板电脑和大型展示设备设计,无须使用WWAN调制解调器。

x0c6.1.2 Snapdragon内核介绍 Snapdragon是高度集成的移动优化系统芯片(SoC),结合了业内领先的3G/4G移动宽带技术与高通公 司自有的基于ARM的微处理器内核、强大的多媒体功能、3D图形功能和GPS引擎。 Snapdragon芯片组系列定位IT与通信融合,由于具备极高的处理速度、极低的功耗、逼真的多媒体和 全面的连接性,推动了全新智能移动终端的涌现,因此可以使用户获得“永远在线、永远激活、永远 连接”的最佳体验,从而为世界各地的消费者重新定义移动性。Snapdragon旨在支持功能先进的智能 手机和智能本,并为消费者提供了异于市场上任何其他产品的独特体验。通过更好地优化定制CPU内 核,Snapdragon获得了出色的移动性,兼具前所未有的处理性能与低功耗,使制造商能够基于 Snapdragon推出具有全天电池使用时间的轻薄且功能强

大的终端产品。 以下为高通公司Snapdragon的主要技术优势与特点。 ● 具备出色移动计算性能的、增强的基于ARM的单核或双核CPU。 ● 集成3G/4G移动宽带。 ● 支持Wi-Fi和蓝牙连接。 ● 内置GPSOne引擎。 ● 高分辨率视频编解码支持。 ● 高性能的3D图像功能。 ● 高清显示屏支持。 ● 支持高达数百万像素的摄像头。 ● 支持包括MediaFLO在内的移动广播解决方案。 ● 支持Android、Brew MP、Chrome和Windows Phone等领先操作系统。

x0c6.2 移植MSM内核简介 在Android中使用的MSM处理器平台的Linux内核,与标准的Linux内核相比主要有以下几个方面的差别 。 ● MSM及其板级平台机器的移植。 ● MSM及其板级平台一些虚拟设备的驱动程序。 ● Android中特有的驱动程序和组件。 MSM及其板级平台机器的移植和MSM及其板级平台一些虚拟设备的驱动程序是硬件平台相关的内容。 Android中特有的驱动程序和组件是Android中特有的部分,这部分内容在Android平台的Linux内核中 是基本相同的。 在Android开源网站上,使用git工具可以得到MSM内核代码。操作命令如下所示。

在通常情况下,MSM内核git的代码仓库中有origin/android-msm-2.6.29和origin/androidmsm2.6.29-nexusone两个分支可以选择。 选择msm通用的2.6.29版本并进行编译的方式如下所示:

选择Nexus One中使用的MSM内核版本并进行编译的方式如下所示:

在当前应用中,使用MSM平台的Linux内核主要有如下两种版本。 ● 针对MSM7kxx系列的处理器:config文件的路径是arch/arm/configs/msm_defconfig。 ● 针对QSD8kxx系列的处理器(snapdragoh):config文件的路径为 arch/arm/configs/mahimahi_defconfig。 上述两个版本使用了不同的Linux代码和配置文件。 例如linux-2.6.36内核中的MSM源代码片段如下所示。

x0cx0c6.3 移植MSM MSM处理器的Linux移植部分的主要内容被保存在以下目录中。 ● arch/arm/mach-msm/:MSM平台部分移植的核心部分,其中包含了qdsp5和qdsp6这两个目录,它 们分别是5代DSP和6代DSP在应用处理器端的相关内核代码。 ● arch/arm/mach-msm/include/mach/:MSM平台头文件的目录,可在内核空间中被其他部分引用。

x0c6.3.1 Makefile文件 Makefile文件永远是移植的核心,MSM平台中的对应文件是arch/arm/machlmsm/Makefile,主要代码 如下所示。

x0c因为MSM处理器既有ARM11(属于ARMv6)体系结构的MSM7k,也有SCORPION体系结构(属于ARMv7)的 QSD8k,所以其不同的方面在Makefile中对此做出了区分。在为mahimahi板构建的系统中,CONFIG ARCH_MSM_SCORPION,CONFIG_MSM_QDSP6,CONFIG MACH_SWORDFISH和CONFIG-MACH_MAHIMAHI等几个宏 均为真。

x0c6.3.2 驱动和组件 在MSM平台中,几乎没有专门针对Android的驱动和组件,这是因为几乎都和

硬件无关。唯一的区别是 在配置文件中对Android专用驱动和组件的选择不同。文件MSM_defconfig的主要代码如下所示。

x0cx0cx0c6.3.3 设备驱动 1.显示驱动 在MSM平台中,显示驱动是FrameBuffer,另外还调用了一些内部独有的功能。在MSM中和显示相关的 头文件如下所示。 (1)文件arch/arm/mach-msm/include/mach/msm_fb.h:这是FrameBuffer驱动程序的头文件。 (2)文件include/linux/msm_mdp.h:显示模块头文件。 除了在“drivers/video/”中关于FrameBuffer驱动程序的通用代码之外,MSM显示部分的驱动程序主 要被保存在“drivers/video/msm/”目录中。其中gpu目录为图形处理单元(Graphic Process Unit)部分相关的内容。 文件msm_fb.c是FrameBuffer驱动程序的入口文件,另外有一些和mddi(Display Digital Interface,一种串行总线,用于连接LCD)、mdp(Display Processor,显示的主模块,为 FrameBuffer核心使用)实现相关的文件。 文件msm_mddi(mddi.c),msm_mdp(mdp.c)和msm_panel(msm fb.c)等几个platform_driver都是和显示 部分相关的。在文件“msm2/arch/arm/mach-msm/device.c”中定义了对应msm_mddi和msm_mdp的 platform_device。mddi_client-XXX中定义了对应msm_panel的platform_device。这三个平台驱动可 以在sys文件系统的目录“/sys/bus/platform/drivers/”中可以找到。 MDP还定义了一种名为msm_mdp的class。在SYS文件系统的“/sys/class/”中保存了其相关信息。 2.触摸屏驱动 MSM的mahimahip平台触摸屏的驱动程序保存在“drivers/input/touchscreen”目录中,实现文件是 synaptics_i2c-rmi.c和msm_ts.c。 文件synaptics_i2c_rmi.c中的驱动是一个i2c的触摸屏的驱动程序,其i2c_driver的名称为 synaptics-rmi-ts。在文件“arch/arm/mach-msm/board-mahimahi.c”中定义其对应的 i2c_device,这个驱动在SYS文件系统的“sys/bus/i2c/drivers/synaptics-rmi-ts”目录中,它在 i2c-0总线上的ID为0040。 文件synaptlcs_i2c_rmi.c对应的event设备是“/dev/inpuUevent2”。文件msmts.c是高通MSM/QSD触 摸屏的驱动程序,在SYS文件系统的目录“/sys/bus/platform/drivers/”中可以找到相关信息,文 件“msm2/arch/arm/mach-msm/device.c”定义了相对应的platform_device。 3.按键和轨迹球驱动 MSM的mahimahip平台系统包含了按键(有三个按键)和轨迹球的功能,具体功能是通过文件 “arch/arm/mach-msm/board-mahimahi-keypad.h”实现的,在此文件中注册了名为mahimahikeypad的键盘设备和名称为mahimahi-nav的轨迹球设备,对应的设备节点分别为 :/dev/input/event4和/dev/inpuUevent5。 4.时钟驱动 MSM的实时时钟的驱动程序在“drivers/rtc”目录的文件rtc-MSM7kOOa.c和hctosys.c中实现。文件 rtc-MSM7kOOa.c实现了标准的实时时钟的实现,驱动程序名称为rs30000048:00010000,SYS文件系统 中可以在“/sys/bus/platform/drivers/”目录

下找到。 在文件hctosys.c文件中提供了实时时钟的初始化函数。 5.摄像头驱动 MSM的摄像头系统构成的方式为经典的Camera驱动+Sensor驱动方式。其驱动程序是基于Video for Linux2的摄像头驱动程序。 除了v412的共用部分以外,MSM的主要文件保存在“drivers/media/video/msm/”目录中。其中包含 了msm_v412.c,msm_camera.c,s5k3e2fx.c,msm_vfe8x_proc.c等文件。 文件msm_camera.c是公用的库函数,创建了“/dev/msm_camera”中的各个设备文件。在此主要包含 了三个自定义的字符设备,其中frame0为帧数据设备,config0为配置设备,contro10为控制设备。 文件“include/media/msm_camera.h”是MSM摄像头相关的头文件,其中定义了各种额外的ioctl命令 。

x0c文件msm_v412.c是v412驱动程序的实现文件,实现了标准的Video for Linux 2的驱动程序,它实际 上是在调用msm_camera.c中内容的基础上实现的。 s5k3e2fx是摄像头传感器的驱动程序,platform_driver的名称为msm_camera_s5k3e2fx,此名称和文 件board-mahimahi.c中定义的platform_device相匹配。 s5k3e2fx是连接在i2c总线上的,其地址为0-0010,在SYS文件系统中。 6.无线局域网驱动 MSM平台包含了无线局域网,使用bcm4329集成了蓝牙、无线局域网和FM为一体的芯片。相关代码内容 保存在“drivers/net/wireless/bcm4329”目录中。其中在文件dhd_linux.c中定义了 platform_driver的名称为bcm4329_wlan,其名称和文件board-mahimahi-wifi.c中定义的 platform_device相匹配。 7.蓝牙驱动 MSM的mahimahip平台的蓝牙驱动使用标准的HCI驱动,保存路径是“drivers/bluetooth”,在里面包 含了文件hci11.c,hci_h4.c和hci_ldisc.c,编译后将生成hci uart.o文件。 8.DSP驱动 在MSM平台的DSP(数字信号处理器)具有比较高级的功能,主要被保存在如下的目录中。 ● arch/arm/mach-msm/qdsp5:MSM7k系列处理器使用的5代DSP。 ● arch/arm/mach-msm/qdsp6:QSD8k系列处理器使用的6代DSP。 其中,在“arch/arm/mach-msm/qdsp6”中包含了如下有用的文件。 ● dal.c:dal协议文件。 ● q6audio.c:Audio系统通用库文件。 ● audio_ctl.c:音频控制文件。 ● routing.c:音频路径控制。 ● pcm_in.c:PCM输入通道。 ● pcm_out.c:PCM输出通道。 ● mp3.c:MP3码流直接输出通道。 ● msm_q6vdec.c:视频解码。 ● msm_q6venc.c:视频编码。 Audio系统的头文件是“arch/arm/mach-msm/include/mach/msm_qdsp6_audio.h”。 MSM视频编解码的头文件在“include/linux/”目录中,由如下两个文件实现。 ● msm_q6vdec.h:视频解码器头文件。 ● msm_q6venc.h:视频编码器头文件。 q6venc是视频编码器在用户空间的节点,是一个MISC字符设备,vdec是视频解码器在用户空间的节点 ,是一个自定义的字符设备。

x0c6.3.4 高通特有的组

件 在MSM处理器中还包含了很多高通独有的组件驱动,这些驱动的实现文件被保存在arch/arm/machmsm/目录中,主要内容如下所示。 ● smd_private.h:共享内存相关的结构和内存区域等定义。 ● smd.c:共享内存的部分底层机制的实现。 ● proc_comm.c:处理器间简单远程命令接口实现。 ● smd_rpcrouter.c:ONCRPC实现部分。 ● smd_rpcrouter_device.c:ONCRPC实现部分。 ● smd_rpcrouter_rvers.c:ONCRPC实现部分。 1.SMEM SMEM(Shared Memory)用于管理共享内存的区域,有静态和动态两种区域。静态区域一般是定义好 的,可以由两个CPU分别直接访问。而动态区域一般通过smem的分配机制来分配。 SMEM是最基础的共享内存管理机制,所有使用共享内存的通信机制或协议都基于它来实现。这些区域 很多,有用于存放基本的版本等信息的,也有用于实现简单的RPC机制的,还有分配Buffer以用于大 量数据传输的。 SMEM的区域定义在arch/arm/mach-msm/目录的smd_private.h文件中,实现代码大多在该目录下的 smd.c文件中。 2.SMSM SMSM利用SMEM中的SMEM_SMSM_SHARED_STATE等区域,传送两个CPU的状态信息,诸如MODEM重启、休眠 等状态。当SMSM信息变化后,通常通过中断来通知另一处理器。 3.PROC COMM PROC COMM使用SMEM中的最前面一个区域:SMEM_PROC_COMM。它是一套应用处理器向MODEM发送简单命 令的接口。 PROC COMM能传递的信息非常有限,仅能传递两个uint32的数据作为参数,也只能接受两个uint32的 数据,加一个boolean作为返回值。但相对于后面提到的RPC,PROC COMM更轻量级。 PROC COMM定义在文件proc_comm.c中,通常应用处理器会使用msm_proc_comm接口函数来发送命令 ,并通过轮询进行等待返回。在此注意需要注册好支持的命令,也就是说在MODEM启动时,需要注册 好对应的处理程序。 常用的PROC COMM命令如下所示。 ● SMEM_PROC_COMM_GET_BAIT_LEVEL:获取电池电量级别。 ● SMEM_PROC_COMM_CHG_IS_CHARGING:判断是否在充电。 ● SMEM_PROC_COMM_POWER_DOWN:关机。 ● SMEM_PROC_COMM_RESET_MODEM:重启MODEM。 4.SMD SMD用在处理器之间,是一套通过共享内存同步大量数据的协议。目前SMD支持64个通道,其中36个已 经定义,分别用于蓝牙、RPC、modem数据链接等。为了防止冲突,每个通道使用两路连接,将发送和 接收分开。 SMD使用SMEM中的对应区域分配适当大小的缓冲,并定义了详细的协议,用于控制传输的开启、停止 等。控制的标记类似于RS-232,而且支持流控。 SMD支持stream模式和packet两种模式。后者会对数据进行封包,保证对端获取到的数据与传送时的 分块一致。 SMD主要是在文件smd.c中实现的,其中有一整套如下函数的接口。 ● smd_open:打开一个sm

d通道。

x0c● smd_clo:关闭一个smd通道。 ● smd_read:从一个通道中读取。 ● smd_write:写入到一个通道。 ● smd_alloc_channel:分配一个通道。 5.ONCRPC RPC的含义为Remote Procedure Calls(远程过程调用)。此处特指处理器间的远程过程调用。在高 通平台中,这一机制又叫ONCRPC(Open Network Computing Remote Procedure Call),以下提及 ONCRPC,都是特指高通平台上的具体实现。 ONCRPC基于共享内存上的SMD实现,使应用处理器端的应用程序可以直接访问MODEM端的服务,支持的 服务如下所示。 ● Call Manager (CM API) ● Wueless Messaging Service (WMS API) ● GSDI (Sm,USIM) ● GSTK (Toolkit) ● PDSM API (GPS) 另外,ONCRPC基于服务端、客户端的思想构建,代码分布在smd_rpcrouter开头的源代码文件中。服 务端实现到modem的具体服务访问,而客户端暴露透明的API给用户程序调用。用户程序如果需要使用 ONCRPC,则需要链接ONCRPC-shared,AMSS RPC exported等库。 第7章 OMAP内核和驱动解析 上一章详细讲解了MSM内核移植和驱动的基本知识。本章将简要介绍OMAP内核的基本知识,并简要讲 解内核移植和各种驱动的基本知识,为读者学习本书后面的知识打下坚实的基础。

x0c7.1 OMAP基础 OMAP是德州仪器公司的应用处理器,是Android系统最常用的处理器产品之一。本节将简要介绍 OMAP处理器的基本知识。

x0c7.1.1 OMAP简析 OMAP是一款面向多操作系统(包括PalmOS5.0,PocketPC2002和通信领域的Symbian)的高性能低功耗 处理器。它集成了包括一个数字协处理器在内的多媒体单元,并且加入GSM/GPRS接口和蓝牙无线协议 等一些当前的高级功能。由于其较低的主频150MHz和广泛的支持性能,而获得了Palm公司的认可,成 为其下Palm OS5产品的标准处理器。 OMAP处理器的优点是支持接口全面,并且具有较低的功耗和不错的性能表现。其在Palm OS5系统上的 运用很好地延续了Palm一向给人的省电、程序效率高的印象。其缺点是耗电基本和旧款的彩色机型持 平,但想要达到昔日的辉煌是不可能了。而且面对处理MPEG流和一些解码动作的“硬杀伤”应用的时 候,其绝对性能还是要逊于StrongARM的。

x0c7.1.2 常见OMAP处理器产品 OMAP处理器汇集了多款经典产品,在其发展过程中主要有如下几种处理器产品。 1.OMAP 3系列 进入2010年后,作为OMAP 34x0系列处理器的升级产品,OMAP35x0及36x0系列处理器已经成为新的标 杆产品。根据国外媒体报道,索尼爱立信U5i便采用了最新的OMAP 3630处理器,在45纳米制程下具备 更低的电力消耗及更强的性能。 2.OMAP 35xx系列 作为OMAP 34x0系列处理器的“正序”升级系列,德州仪器推出的OMAP 35x0系列中性能指标最为抢眼 的

莫过于OMAP 3530型处理器,运行主频达到720MHz(ARM Cortex-A8架构),而DSP处理器运行频率 则达到了520MHz。同系列中的OMAP 3525处理器的两项指标分别为600MHz及430MHz。而定位较为大众 化的OMAP 3515及3503两款处理器则省略了DSP处理器,核心运行频率为600MHz。 OMAP3530处理器(720MHz)的主要特性如下所示。 ● 720 MHz ARM Cortex-A8内核。 ● 支持1400 Dhrystone每秒百万条指令(MIPS)。 ● 520 MHz C64x+ DSP。 ● 用于加速3D图形的POWERVR SGX显示与游戏效果子系统支持。 3.OMAP 36xx系列 这是当前德州仪器的最高级产品,最先推出的4款OMAP 36xx系列处理器采用了全新的45nm生产工艺 ,能够在12mm×12mm或14mm×14mm的封装单位(BGA封装)内集成更多晶体管,在提供最高1GHz运行 主频的同时,能够带来最高75%的图形性能提升及25%的电力节省。

x0c7.1.3 开发平台 德州仪器(TI)为OMAP处理器推出了专门的移动开发平台——Zoom,该平台不仅可为移动应用开发人员 提供所需的所有工具,还有助于加速创新型应用的上市进程。这款由Logic公司设计、开发并制造的 新型平台性能可靠,能为智能电话与移动因特网设备(MID)应用开发人员提供无线连接技术、增强型 影像、视频、显示技术及软件,以确保可靠而小巧的手持设备特性,并提供3G调制解调器以及在手掌 中即可支持大屏幕体验的DLP Pico投影技术模块等选项,充分满足开发人员在领先的移动操作系统上 实现各种先进应用的需求。

x0c7.2 OMAP内核 OMAP是德州仪器公司的应用处理器,为Android使用的是OMAP3系列的处理器。在Android代码库中公 开了对应的MSM的源代码,使用git工具得到MSM内核代码的命令如下所示。

在OMAP处理器中,其Zoom平台的config路径为arch/arm/configs/zoom2_defconfig,此文件的主要内 容如下所示。

x0cx0cx0c7.3 移植OMAP体系结构 在OMAP处理器的Zoom平台中,Linux内核和标准Linux内核相比,主要差别体现在如下三个方面。 ● OMAP Zoom平台机器的移植。 ● OMAP Zoom平台的驱动程序。 ● Android中特有的驱动程序和组件。 基于上述三点差别,整个移植过程也是基于上述三个方面。在OMAP处理器中,在移植过程中主要涉及 如下两个目录。 ● arch/arm/plat-omap:移植OMAP平台部分。 ● arch/arm/mach-omap2:移植OMAP处理器部分。

x0c7.3.1 移植OMAP平台 OMAP平台需要移植的内容保存在arch/arm/plat-omap目录下,其中include目录为此平台的头文件。 接下来将简要介绍arch/arm/plat-omap目录下主要文件的内容。 1.文件Kconfig 在arch/arm/plat-omap目录下的Kconfig文件中,配置了OMAP平台的各种内容。主要内容如下所示。

2.Kconfig文件 在arch/arm/plat-omap目录下的Makefile文件中设置了如下通用

的支持内容。

x0c3.头文件 在arch/arm/plat-omap/include目录下保存了OMAP平台的头文件,其中有mach和dspbridge两个子目 录。其中子目录mach比较重要,在此目录中的很多文件的功能是这个硬件平台在Linux移植中约定的 名称,例如文件dma.h是表示DMA信息的头文件,文件irqs.h是和中断信息相关的头文件。子目录 mach下的文件结构如图7-1所示。

x0c图7-1 子目录mach下的文件结构 在子目录dspbridge中包含了控制DSP相关的头文件,因为OMAP包含了ARM和DSP下的双核处理器,为了 在ARM方面控制DSP,所以德州仪器采用了dspbridge的方式。子目录dspbridge下的文件结构如图72所示。

x0c图7-2 子目录dspbridge下的文件结构 因为OMAP处理器的复杂性,将很多功能做成了库。这些库文件也被保存在此目录下。例如在文件 arch/arm/plat-omap/include/plat/display.h中定义了和显示子系统相关的数据结构和接口,主要 代码内容如下所示。

x0cx0c7.3.2 移植OMAP处理器 目录arch/arm/mach-omap2是OMAP处理器的移植目录,接下来将简要介绍此目录下主要文件的内容。 1.文件Kconfig 在arch/arm/mach-omap2目录下的Kconfig文件中,配置了通用OMAP中的文件,主要内容如下所示。

x0c2.文件board-zoom2.c 文件board-zoom2.c是OMAP机器实现的核心文件,主要代码内容如下所示。

x0c3.文件devices.c 文件devices.c的功能和其他平台的类似,主要是向系统注册各种平台设备,主要代码内容如下所示 。

x0cx0cx0c7.4 移植Android专用驱动和组件 因为OMAP平台的专用Android驱动和组件与Android系统中的驱动和组件基本相同,所以整个移植工作 比较简单,唯一的区别是在配置文件中对Android专用驱动和组件的选择不同。OMAP平台的Zoom2的配 置文件是zoom2_defconfig,其中的选择内容如下所示。

x0cOMAP和Android系统实现了完美的融合,有关Linux、OMAP和Android之间的具体联系,读者可以登录 /Android_on_OMAP来了解更多信息。界面效果如图7-3所示。

x0c图7-3 Android on OMAP页面

x0c7.5 OMAP的设备驱动 了解了OMAP内核的移植知识后,从本节开始分析OMAP平台中的设备驱动知识,为读者学习本书后面的 知识打下坚实的基础。 1.显示驱动程序 OMAP处理器中的显示驱动就是SOC的DSS驱动程序,其中包含了一个主显示屏和两个视频叠加的显示层 。显示系统的库文件保存在drivers/video/omap2/omapfb/dss目录下,主要包含文件core.c、 manager.c、overlay.c、dss.c、omapdss.c、dpi.c和wenc.c,上述文件构成了OMAP显示驱动程序的 “库”。 文件core.c定义了platform_driver的名称omapdss,主显示驱动的FramBuffer驱动程序的内容保存在 drivers/video/omap2/omapfb/目录中的如下文件中。

其中文件omapfb-main.c比较重要,其中定义了platform_driver

的名称为omapfb。 Framebuffer的参数最终可以体现在overlay的信息中,在 /sys/devices/platform/omapdss/overlay0目录下有overlay0对应的framebuffer的输入图像分辨率 、输出图像分辨率、屏幕宽度、对应的输出通道名称、使用的管理器等。 2.I2C总线驱动程序 I2C core是用于维护Linux的I2C核心部分,提供了核心的数据结构、I2C适配器驱动和设备驱动的注 册、注销管理等API,同时还提供了I2C总线读写访问的一般接口(具体的实现在与I2C控制器相关的 I2C adapter中实现)。 I2C adapter driver层即I2C适配器驱动层,每种处理器平台都有自己的适配器驱动,属于平台移植 相关层。它的职责是为系统中每条I2C总线实现相应的读写方法。但是适配器驱动本身并不会进行任 何通信,而是等待设备驱动调用其函数。 在系统开机时,I2C适配器驱动被首先装载。一个适配器驱动用于支持一条特定的I2C总线的读写。一 个适配器驱动通常需要两个模块,一个struct i2c_adapter和一个struct i2c_algorithm来描述。 I2C adapter构造一个对I2C core层接口的数据结构,并通过相应的接口函数向I2C core注册一个适 配器。i2c_algorithm主要实现对I2C总线访问的算法,master_xfer和smbus_xfer即I2C adapter底层 对I2C总线读写方法的实现,相关的数据结构如下:

I2C的device driver层可以用两个模块来描述,分别是struct i2c_driver和struct i2c_client。 i2c_client和i2c_driver分别构造了I2C core层接口的数据结构,并且通过相关的接口函数向I2C Core注册I2C设备驱动。相关的数据结构是i2c_driver。 Driver是为device服务的,i2c_driver注册时会扫描i2c bus上的设备,进行驱动和设备的绑定。主 要有两种接口attach_adapter和probe,二者分别针对旧的和新式的驱动。通常来说i2c_client对应 着I2C总线上某个特定的slave或者是ur space的某个用户对应,而此时的slave可以动态变化。 I2C adapter驱动位于drivers/i2c/buss目录下,OMAP的I2C adapter驱动程序为i2c-omap.c。 Android中Platform device的注册代码位于内核的如下文件中。

x0cAndroid中Platform driver的注册代码位于内核的如下文件中。

该驱动的注册目的是初始化OMAP3630的I2C adapter,提供I2C总线传输的具体实现,并且向I2C core注册I2C adapter。 (1)定义Platform driver 在文件drivers/i2c/buss/ i2c-omap.c中,定义Platform driver的代码如下所示。

(2)注册Platform driver 在文件drivers/i2c/buss/ i2c-omap.c中,注册Platform driver的代码如下所示。

当通过函数platform_driver_register()注册Platform driver omap_i2c_driver时,会扫描 Platform bus上的所有设备,由于匹配因子是name(即i2c_omap),而之前已经将name为i2c_omap的 Platform device注册到platform bus上,所以会匹配成功,调用函数omap_i2c_probe(

)可以将设备 和驱动绑定起来。 在文件drivers/i2c/buss/ i2c-omap.c中会涉及数据结构omap_i2c_dev,在此结构中定义了OMAP的 I2C控制器,定义结构代码如下所示。

x0c3.摄像头和视频输出驱动程序 在OMAP3处理平台中,摄像头和视频输出分别属于两个不同的子系统,其中摄像头属于ISP子系统,连 接的硬件是摄像头传感器;而视频输出属于DSS系统,连接的硬件是屏幕。 OMAP平台的摄像头和视频输出都是基于video for Linux 2的驱动程序框架,摄像头调用显示相应的 库实现视频输入功能,而视频输出调用DSS相关的库实现视频输出功能。 ISP驱动程序由drivers/media/video目录中的如下文件组成。

OMAP平台摄像头驱动程序的v412驱动主文件是omap34xxcam.c。 视频输出驱动程序被保存在drivers/media/video/omap/omap-vout目录下的如下文件中。

x0c上述文件都是v412的驱动程序,设备节点分别是dev/video1和dev/video2。 4.触摸屏和键盘驱动程序 OMAP的Zoom平台的输入设备包含了触摸屏和Qwerty全键盘。OMAP的Zoom平台的触摸屏驱动程序被保存 在drivers/input/touchscreen目录下的如下文件中。

在文件drivers/input/keyboard/twl4030_keypad.c中,实现了OMAP的Zoom平台的键盘驱动程序。因 为twl4030使用的是I2C的接口,所以这个驱动程序本身是经过一次封装的。 文件twl4030_keypad.c中的核心内容是中断处理的相关内容,函数do_kp_irq()是一个标准的Linux中 断的处理函数,其代码如下所示。

函数twl4030_kp_scan()负责找到按键的行列,然后调用input_report_key()函数来汇报信息,其主 要代码如下所示。

x0c5.实时时钟驱动程序 OMAP处理器的Zoom平台的实时时钟是一个连接在I2C总线上的设备,通过tw14030来控制。OMAP的实时 时钟驱动程序在文件drivers/rtc/rtc-twl14030.c中实现。其平台驱动名称是tw14030_rtc,可以在 SYS文件系统的sys/bus/platform/drivers目录中找到。 6.音频驱动程序 OMAP音频驱动程序使用了标准的ALSA驱动程序框架,ALSA的核心是使用CONFIG_SND系列的配置宏。 OMAP的音频驱动程序保存在sound/soc/omap目录下,其中最主要的实现文件是omap-mcbsp.c和omappcm.c。 7.蓝牙驱动程序 OMAP平台中的蓝牙设备使用标准的HCI驱动程序,保存在drivers/bluetooth目录中,主要文件是 hci_ll.c、hci_ldisc.c和hci_h4.c,编译后将生成hci_uart.o文件。 8.DSP驱动程序 OMAP处理器包含了ARM处理器和DSP处理器,其中DSP处理器用于辅助实现加速功能,现在高级的处理 器一般都使用双处理器来架构。ARM和DSP之间用DspBrige桥来连接,DSP桥的内容保存在 drivers/dsp/bridge目录中,其中包含了如下目录。 ● dynload:实现动态加载功能。 ● gen:实现工具类函数。 ● hw:硬件核心功能。 ● pmgr:能源管理

器。 ● rmgr:资源管理器。 ● wmd:实现Bridg Mini Driver接口。 第8章 显示系统驱动应用 在本书前面的内容中,介绍了Linux内核和Android内核的基本知识,详细讲解了和驱动有关的内核和 驱动知识,并且简要讲解了三大主流处理器的内核、移植和驱动程序。从本章开始,将讲解 Android驱动的具体应用知识,介绍市面上常用驱动系统的移植方法和实现原理,为读者步入工作岗 位打下基础。

x0c8.1 显示系统介绍 Android显示系统的功能是操作显示设备并获得显示终端。显示系统对应硬件层中的LCD、LCD控制器 和VGA输出设备等显示设备。显示系统驱动应用和Android的Surface库有着千丝万缕的联系,在显示 系统的下层实现了对基本显示输出的封装,Surface中有一部分库提供了对多个图层的支持。本节将 简要介绍Android显示系统的基本知识。

x0c8.1.1 Android的版本 在Linux系统中,FrameBuffer驱动是标准的显示设备的驱动;对于PC系统来说,FrameBuffer驱动是 显卡的驱动;对于嵌入式系统SoC处理器来说,FrameBuffer通常作为其LCD控制器或者其他显示设备 的驱动。Donut和Eclair都是Android的名字,每当Android发布一个新版本的时候,都会以一种 Google员工们喜爱的食品(尤其是甜点)命名,例如1.5版Cupcake(纸杯蛋糕),1.6版Donut(甜甜 圈)和2.0版Eclair(法式奶油夹心甜点)。新版本虽然名字不相同,但是显示系统的结构差不多。 主要区别在Donut和Eclair这两个版本上,具体说明如下所示。 ● 在Donut及其以前的版本中,使用libui直接调用FrambeBuffer驱动程序来实现显示部分的接口。 ● 在Eclair及其后面的版本中增加了模块Gralloc,这是一个位于显示设备和libui库中间的一个硬 件模块。

x0c8.1.2 不同版本的显示系统 本节将简要讲解Donut及其以前版本和Eclair及其后面版本显示系统的基本知识。 1.Donut及其以前版本 此版本的系统头文件被保存在ui库的目录中,具体路径如下所示。

在此目录中主要包含了如下代码文件。 ● EGLNativeSurface.h:定义了类EGLNativeSurface,此类继承了类egl_native_window_t。 ● EGLDisplayInfoSurface.h:定义了类EGLDisplayInfoSurface,此类继承了类 EGLNativeSurface。 ● EGLNativeWindowsSurface.h:定义了类EGLNativeWindowsSurface,是EGLNativeSurface的另一 个实现者。 此版本的显示系统代码保存在frameworks/ba/libs/ui/目录下,其中文件EGLDisplaySurface.h是 系统和FrameBuffer设备的接口。 2.Eclair及其后面版本 在Android的Eclair及其后面版本中,显示系统的本地部分包含硬件抽象层和ui库中的部分,和以前 版本的区别是在实现层的上面增加了一个名为Gralloc(图形分配)的硬件设备,此硬件位于libui和 显示设

备的驱动程序之间,作为显示系统的硬件抽象层来使用。 Gralloc模块的实现文件保存在system/lib/hw目录的动态库中,在运行过程中使用dlopen和dlsym的 方式动态打开并取出符号来使用,而在系统的其他部分没有链接此动态库。 Gralloc模块比较灵活,是可以被移植的模块,是系统和显示之间的接口,是以硬件的形式存在的。 在Android系统中,既可以使用FrameBuffer作为Gralloc模块的驱动程序,也可以在此模块中不使用 FrameBuffer设备。

x0c8.2 移植和调试前的准备 在了解了不同Android版本的不同显示系统架构之后,本节将详细讲解移植Android显示系统的核心知 识,为真正的移植工作做好准备。

x0c8.2.1 FrameBuffer驱动程序 在Linux系统中,标准的设备显示驱动是FrameBuffer。直观地说,FrameBuffer驱动程序是PC系统中 的显卡驱动程序,是嵌入式系统中的SoC处理器,通常将FrameBuffer作为其LCD控制器或者其他显示 设备的驱动。 FrameBuffer驱动是一个字符设备,此驱动通常在文件系统的设备节点中,位置如下。

FrameBuffer驱动的主设备号是29,次设备号用递增数字生成。每个系统可以有多个显示设备,分别 使用/dev/fb0、/dev/fb1和/dev/fb2等来表示。 在用户空间中,通常FrameBuffer驱动使用ioctl和mmap等文件系统接口进行操作,其中ioctl用于获 得和设置信息,mmap可以将FrameBuffer的内存映射到用户空间。另外FrameBuffer驱动直接支持 Write操作,可以直接用写的方式输出显示内容。显示驱动FrameBuffer的架构图如图8-1所示。

图8-1 显示驱动FrameBuffer的架构图 FrameBuffer驱动功能主要涉及如下两个文件。 ● include/linux/fb.h:FrameBuffer驱动的头文件。 ● drivers/video/fbmem.c:FrameBuffer驱动的核心实现文件。 在文件fb.h中,定义了FrameBuffer驱动的核心数据接口fb_info。在文件fb.h中的对应代码如下所示 。

x0c在上述数据接口fb_info中,包含了FrameBuffer驱动的主要信息。具体说明如下所示。 ● struct fb_var_screeninfo和struct fb_fix_screeninfo:两个相关的数据结构,分别对应 FBIOGET_VSCREENINFO和FBIOGET_FSCREENINFO这两个ioctl,功能是从用户空间获得显示信息。 ● fb_ops:表示FrameBuffer驱动的操作。 在实现FrameBuffer驱动的过程中,通常使用如下两个函数分别实现注册和注销功能。

FrameBuffer驱动的ioctl命令也是在上述文件fb.h中定义的,对应代码如下所示。

在实现FrameBuffer驱动的过程中,需要定义一个实现fb_info结构和实现fb_ops中各个函数的指针。 从驱动程序的用户空间实现ioctl调用时,会转换成调用其中的函数。当注册具体的FrameBuffer驱动 后,将会自动递增一个次设备号。在配置Linux系统时,FrameBuffer驱动的配置选项如下所示。



应的配置文件如下所示。

其中包含了对文本模式、控制台和启动图标等子选项的支持。

x0c8.2.2 硬件抽象层 1.Donut及其以前版本 在Donut及其以前版本中,因为显示系统的硬件抽象层位于libui标准库,所以不需要移植这部分内容 ,整个移植的过程只需要移植FrameBuffer驱动程序即可。又因为libui对于FrameBuffer驱动程序使 用了标准的规则,所以只要在当前系统中实现Linux中的FrameBuffer驱动程序,就可以在Donut及其 以前版本的Android系统中实现显示,仅有的一点区别是驱动程序的设备节点的路径。 在Donut及以前版本中的硬件抽象层的核心文件是 frameworks/ba/libs/ui/,此文件的核心功能是通过函数 mapFrameBuffer()实现的,通过此函数实现了对驱动程序的操作。函数mapFrameBuffer()的实现代码 如下所示。

x0cx0cx0cx0c上述代码的实现流程如图8-2所示。

图8-2 函数mapFrameBuffer()的实现流程图 2.Eclair及其后面版本 在Eclair及其后面版本中,Gralloc模块用于显示部分硬件抽象层。因为Gralloc模块是灵活多变的 ,所以移植方式也多种多样,具体来说有如下两种情况。 ● 如果继续使用Android中已经实现的Gralloc模块,就可以继续使用标准的FrameBuffer驱动程序 ,此时只需要移植FrameBuffer的内容即可。 ● 如果想自己实现特定的Gralloc模块,则此模块就是当前系统的显示设备和Android接口,此时显 示设备可以是各种类型的驱动程序。 注意: 如果想对一个标准的FrameBuffer驱动程序实现优化改动,需要额外增加一些ioctl命令来获 取额外的控制,例如通过Android的pmem驱动程序可以实现获取加速效果。 (1)头文件 Gralloc模块的头文件在文件hardware/libhardware/include/hardware/gralloc.h中定义,接下来将 详细讲解此文件的实现流程。 step1 定义子设备和模块的名称,对应代码如下所示。

x0c其中gralloc是硬件模块的名称,fb0是FrameBuffer设备,gpu0是图形处理单元设备。 step2 通过扩展定义gralloc_module_t实现Gralloc硬件模块,对应代码如下所示。

上述gralloc_module_t是此头文件的核心,各个函数指针的具体说明如下所示。 ● registerBuffer:在alloc_device_t::alloc前调用。 ● lock:用于访问特定的缓冲区,在调用此接口时硬件设备需要结束渲染或完成同步处理。 ● unlock:在所有buffer改变之后被调用。 ● perform:用于未来某个用途。 step3 定义函数gralloc_open(),功能是打开gralloc接口,此函数的实现代码如下所示。

step4 定义函数framebuffer_open(),功能是打开FrameBuffer的接口,此函数的实现代码如下所 示。

step5 定义函数framebuffer_open(),功能是关闭FrameBuffer的接口,此函数的实现代码如下所 示。

step6

定义函数gralloc_clo(),功能是关闭gralloc的接口,此函数的实现代码如下所示。

x0cstep7 设备GRALLOC_HARDWARE_GPU0对应的设备是结构体alloc_device_t,设备 GRALLOC_HARDWARE_FB0对应的设备是结构体framebuffer_device_t,这两个结构体的具体定义代码如 下所示。

(2)文件 Gralloc模块是由gralloc_module_t模块、alloc_device_t设备和framebuffer_device_t设备这三个 结构体来描述的,里面的函数指针起了非常重要的作用。Gralloc模块是由ui库中的如下文件调用的 。

在文件中定义了类FramebufferNativeWindow,此类继承了 android_native_buffer_t,这是对上层的接口,表示这是一个本地窗口。接下来将详细讲解此文件 的实现流程。 step1 定义构造函数FramebufferNativeWindow(),实现代码如下所示。

x0cx0c在函数FramebufferNativeWindow()中使用了双显示区缓冲方式,初始化过程如图8-3所示。

图8-3 初始化流程图 图8-3所示流程的具体描述如下所示。 ● 打开Gralloc模块,然后打开设备framebuffer_device_t和alloc_device_t。 ● 从设备framebuffer_device_t中获取显示区的宽、高、颜色格式,构建NativeBuffer结构。 ● 从设备alloc_device_t中分配内存到NativeBuffer句柄。 ● 获取framebuffer_device_t设备中的其他信息。 ● 分别给指针dequeueBuffer、lockBuffer、queueBuffer、query和perform赋值。 (3)文件 文件frameworks/ba/libs/ui/通过调用Gralloc模块和gralloc_ module_t模块来显示缓冲区的分配,此文件的核心代码如下所示。

x0c其中mAllocDev是一个alloc_device_t设备类型,当调用者具有GRALLOC_USAGE_HW_ MASK标志时 ,alloc_device_t会调用分配一个内存,否则将从软件中分配一个内存。 (4)文件 文件frameworks/ba/libs/ui/通过调用Gralloc模块显示缓冲的映射,并 且在其中注册了显示的缓冲内容。在使用完毕后可以注销显示的缓冲内容。此文件的核心代码如下所 示。

x0cx0c其中mAllocDev是一个alloc_device_t设备类型,根据句柄的范围可以从Gralloc模块中注册 Buffer,也可以从软件中注册Buffer。 (5)文件 在管理库SurfaceFlinger中也调用了Gralloc模块,调用路径如下所示。

注意: SurfaceFlinger按英文翻译过来就是Surface投递者。SurfaceFlinger的构成并不是太复杂 ,复杂的是它的客户端架构。SurfaceFlinger的主要功能如下所示。 ● 将Layers(Surfaces)的内容刷新到屏幕上。 ● 维持Layer的Zorder序列,并对Layer最终输出做出裁剪计算。 ● 响应Client要求,创建Layer与客户端的Surface建立连接。 ● 接收Client要求,修改输出大小、Alpha等Layer属性。 对于投递者的实际意义,

我们首先需要知道的是如何投递、投递物、投递路线和投递目的地。 SurfaceFlinger的基本组成框架如图8-4所示。

x0c图8-4 SurfaceFlinger的基本组成框架 SurfaceFlinger的管理对象如下所示。 ● mClientsMap:管理客户端与服务端的连接。 ● ISurface,IsurfaceCompor:AIDL调用接口实例。 ● mLayerMap:服务端的Surface的管理对象。 ● SortedByZ:以Surface的Z-order序列排列的Layer数组。 ● graphicPlane:缓冲区输出管理。 ● OpenGL ES:图形计算、图像合成等图形库。 ● :这是个跟平台相关的图形缓冲区管理器。 ● pmem Device:提供共享内存,在这里只是在可见,在上层被抽 象了。 在文件中定义的类是一个Buffer类,并为其定义了构造函数,构造函数的实现代码 如下所示。

在上述代码中,调用了gralloc_module_t的可选实现的函数指针perform,如果在当前使用的 Gralloc模块中实现了这个函数指针,则在此调用函数。

x0c8.3 实现显示系统的驱动程序 在模拟器中使用的驱动程序是Goldfish和FrameBuffer驱动程序,使用的硬件抽象层是Gralloc模块。 Gralloc模块既可以被模拟器使用,也可以被实际硬件系统使用。本节将详细讲解实现Android显示驱 动程序的基本知识。

x0c8.3.1 Goldfish中的FrameBuffer驱动程序 Goldfish中的FrameBuffer驱动程序保存在文件drivers/video/goldfishfb.c中,此文件的主要实现 代码如下所示。

x0cx0cx0c上述代码通过FrameBuffer驱动程序实现了对RGB565颜色空间的支持,其中虚拟显示的y值是实际显示 的2倍,这样就实现了双缓存功能。

x0c8.3.2 使用Gralloc模块的驱动程序 不同的硬件有不同的硬件图形加速设备和缓冲内存实现方法。Android Gralloc动态库抽象的任务是 消除不同的设备之间的差别,在上层看来都是同样的方法和对象。在Moudle层隐藏缓冲区操作细节。 Android使用了动态链接库来封装底层细节。 默认Gralloc模块的实现源代码保存在hardware/libhardware/modules/gralloc/目录中,Android Gralloc主要有如下三个实现文件。 ● :在其中实现了gralloc_module_t模块和alloc_device_t设备。 ● :在其中实现了工具函数。 ● :在其中实现了alloc_device_t设备。 (1)文件 step1 定义函数gralloc_device_open(),这是一个模块打开函数,其实现代码如下所示。

step2 定义函数gralloc_alloc_framebuffer_locked(),此函数用于调用 gralloc_alloc_framebuffer_ locked(),主要代码如下所示。

x0cstep3 定义函数gralloc_alloc_framebuffer_locked(),功能是:如果当前没有Framebuffer--> mapFrameBufferLocked(m),如果不支持PAGE_FLIP,则通过软件方法分配gralloc_alloc

_buffer,并 且决定使用双FrameBuffer中的哪一个作为缓冲地址。此函数的实现代码如下所示。

step4 定义函数gralloc_alloc_buffer(),主要代码如下所示。

x0cx0cx0c上述代码的实现流程如下所示。 ● 如果系统没用PMEM,则直接赋值fd = ashmem_create_region("gralloc-buffer", size); ● 如果有PMEM,则init_pmem_area(m); ● 获取本次需要的size:offt = te(size); ● 建立PMEM region:struct pmem_region sub = { offt, size }; ● 重新打开:fd = open("/dev/pmem", O_RDWR, 0); ● 链接空间:err = ioctl(fd, PMEM_CONNECT, m->pmem_master); ● 获取句柄:private_handle_t* hnd = new private_handle_t(fd, size, flags); 在文件中,结构体类型private_module_t扩展了gralloc_module_t结构体,此结构体在 文件gralloc_priv.h中定义,实现代码如下所示。

x0c(2)文件 在文件中定义的函数是结构体gralloc_module_t的具体实现,此文件的具体实现流程如下 所示。 step1 定义函数gralloc_register_buffer(),功能是建立一个新的private_handle_t对象,如果 不是本进程对象则赋初值,其实现代码如下所示。

step2 定义函数gralloc_unregister_buffer(),功能是通过validate判断handle是否合法。对应 代码如下所示。

x0c(3)文件 文件用于实现设备framebuffer_device_t,其核心代码和Donut之前版本的 文件的实现类似,不同的是在文件中使用双缓冲的实现方式 。在接下来的内容中,将详细讲解此文件的具体实现流程。 step1 定义函数fb_device_open(),功能是初始化设备framebuffer_device_t,其实现代码如下所 示。

x0cstep2 定义函数mapFrameBufferLocked(),功能是实现打开FrameBuffer设备的真正功能,实现代 码如下所示。

x0cx0cx0c上述代码需要完成如下工作。 ● 打开FrameBuffer设备。 ● 判断是否支持PAGE_FLIP。 ● 计算刷新率。 ● 打印Gralloc信息。 ● 填充private_module_t。 step3 定义函数fb_post(),功能是将某个缓冲区显示在屏幕上。具体代码如下所示。

x0c上述代码首先检查buffer是否合法,然后进行了相应的类型转换处理。如果currentbuffer非空,则 执行unlock解锁。 注意: 上述代码非常科学,屏幕上显示的内容其实是通过硬件DMA读取显示缓冲区的数据,而在程序 中需要写入显示缓冲区的数据。为了避免上述两个步骤同时进行,Gralloc使用了如下科学合理的处 理方式。 ● 锁定其中的一个后再写内容,写完后解锁。 ● 在解锁期间,此显示缓冲区不能被硬件DMA获取,在此期间另一个缓冲区被解锁,此时可以显示 到屏幕上。

x0c8.4 MSM高通处理器中的显示驱动实现 经过前面内容的学习,读者已经了解了模拟器中Goldfish

和FrameBuffer驱动程序的基本知识。本节 将详细剖析当前主流处理器平台显示系统驱动的实现过程,为独立开发显示驱动程序打好基础。

x0c8.4.1 MSM中的FrameBuffer驱动程序 MSM处理器的入口源文件是drivers/staging/msm/msm_fb.c,从其中的实现代码看,这是一个标准的 FrameBuffer驱动程序,此驱动使用了RGB565颜色空间,使用2倍和实际显示区内存作为虚拟显示区。 接下来将详细介绍文件msm_fb.c的实现流程。 step1 定义接口fb_ops msm_fb_ops,这是高通msm fb设备的文件操作函数接口,具体代码如下所 示。

step2 定义接口platform_driver msm_fb_driver,这是高通msm fb的driver接口。具体代码如下 所示。

step3 和标准的FrameBuffer驱动程序相比,MSM在驱动中增加了特殊的iocttl,对应代码如下所示 。

x0cx0cx0c8.4.2 MSM中的Gralloc驱动程序 在MSM处理器中重新实现了Gralloc模块的架构,此Gralloc模块是基于FrameBuffer和Pmem驱动实现的 。在MSM平台中,和Gralloc模块相关的文件目录如下所示。 ● hardware/msm7k/libgralloc:MSM7系列的实现文件。 ● hardware/msm7k/libgralloc-qsd8k:QSD8K系列的实现文件。 在MSM中实现Gralloc模块的方法和在Android系统中的实现方法类似,主要变化在于使用了Pmem部分 。 (1)文件 在文件中,使用Gralloc中的结构体private_module_t来扩展里面的结构体 private_module_t。结构体private_module_t的实现文件是gralloc_priv.h,在其中包含了和上下文 有关的信息。在接下来的内容中,将简单分析文件的实现流程。 在文件中首先需要定义结构体HAL_MODULE_INFO_SYM,具体代码如下所示。

上述代码和Gralloc中的代码基本一致,只是增加了结构体private_module_t的perform()函数指针来 实现gralloc_perform()。函数gralloc_perform()在文件中定义,具体代码如下所示。

x0c在上述代码中,通过ca语句只实现了一个命令 GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER,此命令是在SurfaceFlinger中被调用的内容 ,这是一个可选的功能,在此通过调用Pmem获取了内存的大小。 (2)文件 在文件hardware/msm7k/libgralloc-qsd8k/中,实现了QSD8K的 framebuffer_device_t设备驱动,其实现源代码和标准的程序类似,区别是增加了更多颜色格式的支 持,并且用RGBA8888作为默认的颜色格式。 其中在post中的区别是不支持双缓冲需要的内存复制功能时,不会再调用memcopy来实现,而是调用 msm_copy_buffer来实现。Post实现函数的代码如下所示。

x0cx0cx0c(3)文件 在文件libgralloc-qsd8k/中定义了函数alloc()、free()和clo(),这些函数是MSM的 Galloc模块默认的实现函数,和标准函数是有一些区别的。函数gralloc_alloc是MSM中 gra

lloc_device_t设备的分配函数。如果其参数不具有GRALLOC_USAGE_HW_FB宏,则会调用 gralloc_alloc_buffer来分配内存。由此可见这部分的实现和默认Galloc模块中的是不一样的。函数 Gallocgralloc_alloc_buffer的实现代码如下所示。

x0cx0c在文件中,函数init_pmem_area_locked()比较重要,它能够从默认的内存中实现内存映 射功能,并且它是通过文件描述符实现的,这和mapBuffer()有很大的不同。函数 init_pmem_area_locked()的实现代码如下所示。

x0c上述函数的运行流程如下。 ● 通过open("/dev/pmem", O_RDWR, 0)打开Pmem。 ● 通过ioctl(master_fd, PMEM_GET_TOTAL_SIZE, ®ion)获取所有空间。 ● 分配e(size)指定数目的空间。 ● 完成映射功能。 再看函数gralloc_free(),此函数用于实现alloc_device_t中的释放功能,和默认的Gralloc相比 ,区别是在此不使用PRIV_FLAGS_FRAMEBUFFER标志实现。函数gralloc_free()的实现代码如下所示。

x0c由此可见,在MSM平台中提高了alloc_device_t设备的性能,使用Pmem驱动程序作为内存映射工具 ,将原来通过ashmem分配和管理的内存部分转移到了Pmem上面。

x0c8.5 OMAP处理器中的显示驱动实现 本节将详细剖析OMAP处理器平台显示系统驱动的实现过程。OMAP平台的驱动程序是由FrameBuffer驱 动和Gralloc模块构成的,其中的FrameBuffer是标准驱动,而Gralloc模块既可以使用默认的,也可 以使用自己自定义的。 (1)文件omapfb-main.c OMAP处理器中FrameBuffer驱动的主要实现文件是drivers/video/omap2/omapfb/omapfb-main.c,在 此文件中通过函数omapfb_create_framebuffers()来注册FrameBuffer驱动程序,此函数的实现代码 如下所示。

x0cx0c(2)文件omapfb.h 在文件include/linux/omapfb.h中定义了额外的ioctl命令号,具体代码如下所示。

x0c第9章 输入系统驱动应用 上一章介绍了Android显示系统驱动的基本知识,并讲解了在MSM和OMAP处理器中的显示系统驱动的实 现代码。从本章开始讲解Android输入系统驱动的基本知识,介绍这些源代码的实现原理,为读者学 习后面的知识打下基础。

x0c9.1 输入系统介绍 Android输入系统的结构比较简单,实现输入功能的硬件设备包括键盘、触摸屏和轨迹球等。在 Android的上层中,可以获得这些设备产生的事件,并对设备的事件做出响应。在Java框架中,通常 使用运动事件来获得触摸屏和轨迹球设备的信息,使用按键事件获得各种键盘的信息。 Android输入系统的基本框架结构如图9-1所示。

图9-1 Android输入系统的基本框架结构

x0c9.1.1 Android输入系统结构元素介绍 Android输入系统的结构比较简单,自下而上包含了驱动程序、本地库处理部分、Java类对输入事件 的处理和Java程序接口等,如图9-2所示。

图9-2

用户输入系统的结构 图9-2中从顶层到底层各个结构元素的具体说明如下所示。 ● Android应用程序层:通过重新实现onTouchEvent和onTrackballEvent等函数来接收运动事件 (MotionEvent),通过重新实现onKeyDown和onKeyUp等函数来接收按键事件(KeyEvent)。这些类 包含在包中。 ● Java框架层的处理:在Java框架层具有KeyInputDevice等类用于处理由EventHub传送上来的信息 ,通常信息由数据结构RawInputEvent和KeyEvent表示。在通常情况下,对于按键事件,则直接使用 KeyEvent来传送给应用程序层,对于触摸屏和轨迹球等事件,则由RawInputEvent经过转换后,形成 MotionEvent事件传送给应用程序层。 ● EventHub:本地框架层的EventHub是libui中的一部分,它实现了对驱动程序的控制,并从中获 得信息。定义按键布局和按键字符映射需要运行时配置文件的支持,它们的后缀名分别为kl和kcm。 ● 驱动程序:保存在/dev/input目录中,通常是Event类型的驱动程序。

x0c9.1.2 移植Android输入系统时的工作 在移植Android输入系统时,需要完成如下两个工作。 ● 移植输入(input)驱动程序。 ● 在用户空间中动态配置kl和kcm文件。 因为Android输入系统的硬件抽象层是libui库中的EventHub,此部分是系统的标准部分,所以在实现 特定硬件平台的Android系统的时候,通常改变输入系统硬件抽象层。 EventHub使用Linux标准的输入设备作为输入设备,并且大多数使用实用的Event设备。基于上述原因 ,为了实现Android系统的输入,必须使用Linux标准输入驱动程序作为标准的输入。 由此可见,输入系统的标准化程度较高,在用户空间实现时一般不需要更改代码。唯一的变化是使用 不同的kl和kcm文件,使用按键的布局和按键字符映射关系。

x0c9.2 Input(输入)驱动 Input(输入)驱动程序是Linux输入设备的驱动程序,可以进一步分成游戏杆(joystick)、鼠标 (mou和mice)和事件设备三种驱动程序。其中事件驱动程序是目前通用的驱动程序,可支持键盘 、鼠标、触摸屏等多种输入设备。 Input驱动程序的主设备号是13,每一种Input设备占用5位,因此每种设备包含的个数是32个。 Event设备在用户空间大多使用如下三种文件系统来操作接口。 ● Read:用于读取输入信息。 ● Ioctl:用于获得和设置信息。 ● Poll:调用可以进行用户空间的阻塞,当内核有按键等中断时,通过在中断中唤醒poll的内核实 现,这样在用户空间的poll调用也可以返回。 Event设备在文件系统中的设备节点为/dev/input/eventX目录。主设备号为13,此设备号按照递增顺 序生成,为64~95,各个具体的设备保存在misc、touchscreen和keyboard等目录中。 Androi

d输入设备驱动程序的头文件是include/linux/input.h,核心文件是 drivers/input/input.c,Event部分的代码文件是drivers/input/evdev.c。 (1)文件input.h 在手机系统中使用的键盘(keyboard)和小键盘(kaypad)属于按键设备EV_KEY,轨迹球属于相对设 备EV_REL,触摸屏属于绝对设备EV_ABS。在文件input.h中定义按键数值的代码如下所示。

然后定义结构struct input_dev,功能是表示Input驱动程序的各种信息,在里面定义并归纳了各种 设备的信息,例如按键、相对设备、绝对设备、杂项设备、LED、声音设备,强制反馈设备、开关设 备等。结构struct input_dev的定义代码如下所示。

x0cx0c在具体实现Event驱动程序时,可以用接口通过向上通知的方式得到按键的事件。在文件input.h中定 义的实现上述接口的代码如下所示。

x0c基于文件input.c的原理,编写的USB输入驱动程序如下所示。

x0c(2)文件KeycodeLabels.h 文件KeycodeLabels.h是本地框架层libui的头文件,用于实现用户空间处理功能。通常触摸屏和轨迹 球非常简单,只需要传递坐标、按下、抬起等信息即可。而按键处理的过程稍微复杂,键表示方式需 要先后经过按键布局转换和按键码转换。 注意: 键扫描码Scancode是由Linux的输入驱动框架定义的整数类型。键扫描码Scancode经过一次转 换后,形成按键标签KeycodeLabel,这是一个字符串的表示形式。按键标签KeycodeLabel经过转换后 ,再次形成整数型按键码keycode。在Android应用程序层,主要使用按键码keycode来区分。 在文件KeycodeLabels.h中,按键码是整数值的格式,在此文件中是使用枚举实现的,枚举KeyCode的 定义代码如下所示。

x0cx0cx0c定义数组KEYCODES[],功能是存储从字符串到整数的映射关系。左列的内容即表示按键标签 KeyCodeLabel,右列的内容为按键码KeyCode(与KeyCode的数值对应)。实际上,在按键信息第二次 转换的时候就是将字符串类型KeyCodeLabel转换成整数的KeyCode。定义代码如下所示。

x0cx0c在文件frameworks/ba/core/Java/android/view/中定义了类 nt,在其中定义整数类型的数值与KeycodeLabels.h中定义的KeyCode枚举值是 对应的。 (3)文件KeyCharacterMap.h 文件frameworks/ba/include/ui/KeyCharacterMap.h也是本地框架层libui的头文件,在其中定义 了按键的字符映射关系。其实KeyCharacterMap只是一个辅助的功能,因为按键码只是一个与UI无关 的整数,通常用程序对其进行捕获处理。如果将按键事件转换为用户可见的内容,需要经过这个层次 的转换。其中定义类KeyCharacterMap的实现代码如下所示。

x0c在上述代码中,使用KeyCharacterMap将按键码映射为文本可识别的字符串。 注意: KeyCharacterMap需要从本地层传送到Java

层,其中涉及的JNI代码的路径如下。

KeyCharacterMap Java框架层的代码如下。

类racterMap是Android平台的API,我们可以在应用程序中使用这个类。另外 ,在中有各种Linstener,相互之间可以监听KeyCharacterMap相关的信息。 上面关于按键码和按键字符映射的内容是在代码中实现的内容,还需要配合动态的配置文件来使用。 在实现Android系统的时候,很可能需要更改这两种文件。需要动态配置如下两个文件。 ● KL(Keycode Layout):后缀名为kl的配置文件。 ● KCM(KeyCharacterMap):后缀名为kcm的配置文件。 在Donut及其之前版本的配置文件路径为:

在Eclair及其之后版本的配置文件路径为:

当系统生成上述配置文件后,会被放置在目标文件系统的/system/usr/keylayout/目录中或 /system/usr/keychars/目录中。另外,kl文件将被直接复制到目标文件系统中。由于尺寸较大 ,kcm文件放置在目标文件系统中之前,需要经过压缩处理。负责解析处理kl文件 ,负责解析kcm文件。 (4)kl格式文件 kl格式文件是按键布局文件,通常以原始的文本文件的形式存在,被保存在目标文件系统的 /system/usr/keylayout/目录中或者/system/usr/keychars/目录中。Android默认提供的按键布局文 件有两个,分别是和。其中是全键盘的布局文件,是系统中主要按键使 用的布局文件。文件用于实现多媒体的控制。 文件的主要代码如下所示。

x0c在上述代码中,第1列为按键的扫描码,是一个整数值;第2列为按键的标签,是一个字符串。即完成 了按键信息的第1次转换,将整型的扫描码,转换成字符串类型的按键标签。第3列表示按键的 Flag,带有WAKE字符,表示此按键可以唤醒系统。 注意: 扫描码由驱动程序决定,不同的扫描码对应一个按键标签。两个手机的物理按键可以对应同 一个功能按键。假如当上面的扫描码为158时,对应的标签为BACK,经过第二次转换后,根据 KeycodeLabels.h的KEYCODES数组可得出其对应的按键码是4。 (5)kcm格式文件 kcm格式文件是按键字符映射文件,用于表示按键字符的映射关系,功能是将整数类型按键码 (keycode)转换成可以显示的字符。kcm文件将被makekcharmap工具转换成二进制的格式,放在目标 系统的/system/usr/keychars/目录中。 文件表示全键盘的字符映射关系,主要代码如下所示。

x0c在上述代码中,第一列表示转换之前的按键码,第二列后面的分别表示转换成的显示内容 (display)和数字(number)等内容。这些转换的内容和文件KeyCharacterMap.h相对应,具体内容 是在此文件的getDisplayLabel()和getNumber(

)等函数中定义的。 除了QWERTY映射类型,还可以映射Q14(单键多字符对应的键盘)和NUMERIC(12键的数字键盘)。 (6)文件 文件位于libui库中的frameworks/ba/libs/ui目录下,此文件是输入系统的核心控制 文件,整个输入系统的主要功能都是在此文件中实现的。例如当按下电源键后,系统把scanCode写入 对应的设备节点,文件会读这个设备节点,并把scanCode通过kl文件对应成keyCode发 送到上层。 在文件中需要定义设备节点所在的路径,定义代码如下所示。

在具体处理时,在函数openPlatformInput()中通过调用scan_dir()函数搜索路径下面所有Input驱动 的设备节点,函数scan_dir()会从目录中查找设备,找到后调用open_device()函数以打开查找到的 设备。其中函数openPlatformInput()的实现代码如下所示。

x0c再看函数getEvent(),功能是在一个无限循环中调用阻塞的函数等待事件到来。具体代码如下所示。

x0cx0c在上述代码中,通过poll()函数来阻塞程序的运行,此时为等待状态,不会有内存开销。当Input设 备的响应事件发生后会将poll()函数返回,然后通过read()函数读取Input设备发生的事件代码。 注意: 在Android系统中,可能有一些Input设备不需要经过EventHub处理,在这种情况下可以根据 EventHub中的open_device()函数进行处理。我们可以在驱动程序中设置一些标志来屏蔽一些设备。 在函数open_device()中实现了键盘、轨迹球和触摸屏等几种设备的处理功能,对其他设备可以忽略 。此外还有另外一种简单的方法实现设备忽略功能,即将不需要EventHub处理的设备节点不放置在 /dev/input目录中。 另外,函数open_device()还可以打开并处理system/usr/keylayout/目录中的kl文件,此函数对应此 功能的实现代码如下所示。

x0c在Android中已经定义了丰富、完整的标准按键。在一般情况下,只需要根据kl配置按键即可,不需 要再为Android系统增加按键。当在现实项目中需要比较奇特的按键的时候,需要更改Android系统的 框架层来实现更改按键功能。在Android中增加新按键时,需要更改下面的文件。 ● 文件KeycodeLabels.h:保存在frameworks/ba/include/ui/目录下,需要修改KeyCode枚举数 值和KeycodeLabel类型Code数组。 ● 文件:保存在frameworks/ba/core/Java/android/view/目录下,在此可以定义 整数值作为平台的API供Java应用程序使用。 ● 文件:保存在frameworks/ba/core/res/res/values/目录下,表示属性的资源文件 ,需要修改其中的name="keycode"的attr。框架层增加完成后,只需要更改KL文件,增加按键的映射 关系即可。 除此之外,还有一种更为简易的做法,就是使用Android中已经定义的

“特殊”按键码作为这个新增 按键的键码。使用这种方式Android的框架层不需要做任何改动。这种方式的潜在问题是当某些第三 方的应用可能已经使用那些特殊按键时,会意外激发系统的这种新增的按键。

x0c9.3 模拟器的输入驱动 在Goldfish虚拟处理器中,使用event驱动程序作为键盘输入功能的驱动程序,其驱动程序的相关文 件是drivers/input/keyboard/goldfish_events.c。此驱动程序是一个标准的event驱动程序,在用 户空间的设备节点为/dev/event/event0,其核心代码如下所示。

函数events_interrupt()是按键事件的中断处理函数,当中断发生后会读取虚拟寄存器的内容,并将 信息上报。模拟器根据主机环境键盘按下的情况可以得到虚拟寄存器中的内容。 在模拟器环境中,使用默认的所有的KL和KCM文件,由于模拟器环境支持全键盘,因此基本上包含了 大部分的功能。在模拟器环境中,实际上按键的扫描码对应的是桌面电脑的键盘(效果和鼠标点击模 拟器的控制面板类似)。当按下键盘的某些按键后会转换为驱动程序中的扫描码,然后再由上层的用 户空间处理。上述过程和实际系统中是类似的。通过更改默认KL文件的方式,又可以更改实际按键的 映射关系。

x0c9.4 MSM高通处理器中的输入驱动实现 在高通MSM的Mahimahi平台中,具有触摸屏、轨迹球和简单按键功能。这些功能是由Android系统中的 驱动程序实现的,并且需要用户空间的内容来协助实现。 在Mahimahi平台中,输入系统设备包括了以下Event设备。 ● /dev/input/event4:几个按键的设备。 ● /dev/input/event2:触摸屏设备。 ● /dev/input/event5:轨迹球设备。

x0c9.4.1 触摸屏驱动 高通Mahimahi平台的触摸屏驱动程序的实现文件是 drivers/input/touchscreen/synaptics_i2c_rmi.c,此文件的核心是函数 synaptics_ts_probe(),在该函数中需要进行触摸屏工作模式的初始化,对作为输入设备的触摸屏驱 动在Linux平台下的设备名注册,同时初始化触摸事件触发时引起的中断操作。函数 synaptics_ts_probe()的实现代码如下所示。

x0cx0cx0cx0cx0cx0c在上述代码中,通过i2c_smbus_read_byte_data()函数读取寄存器信息就可以获取其事件信息,也可 以通过i2c_transfer来批量读取其寄存器信息。

x0c9.4.2 按键和轨迹球驱动 MSM具有按键和轨迹球的功能,对应的驱动程序在arch/arm/mach-msm/board-mahimahikeypad.c文件 中实现,接下来介绍此文件的实现流程。 step1 在文件board-mahimahi-keypad.c中定义全局的代码如下所示。

因为按键和轨迹球是通过GPIO系统来实现的,所以在上面定义了一个gpio_event_info类型的数组。 mahimahi-keypad和mahimahi-nav分别表示两个设备的名称。在gpio_event_info指针数组 mahimahi_input

_info中包括4个成员,分别是mahimahi_ keypad_matrix_、 mahimahi_keypad_key_、jogball_x_和jogball_y_。 step2 使用gpio_event_matrix_info矩阵定义按键驱动,此驱动是利用GPIO矩阵实现的,在定义时 需要包含按键的GPIO矩阵和Input设备的信息,具体代码如下所示。

x0c在上述代码中,mahimahi_keypad_key_matrix_info和mahimahi_keypad_info是 gpio_event_matrix_info类型的结构体,分别实现了两个按键和一个按键的处理功能。

x0cstep3 使用GPIO驱动实现轨迹球部分驱动,在实现时由X方向和Y方向两部分组成。具体代码如下所 示。

在上述代码中,使用jog_axis_info类型的结构体定义了轨迹球,这种设备的类型(type)是相对设 备EV_REL。 注意: 除默认的和之外,在高通Mahimahi平台中新增了文件h2w_和 。

x0c9.5 OMAP处理器平台中的输入驱动实现 上一节介绍了高通Mahimahi平台中输入驱动的基本知识,本节将简要介绍OMAP处理器平台中输入驱动 的基本知识。

x0c9.5.1 触摸屏驱动 OMAP的Zoom平台的输入设备包括触摸屏和键盘(Qwerty全键盘)两种,其中触摸屏驱动程序保存在文 件drivers/input/touchscreen/synaptics_i2c_rmi.c中,这是一个i2c的触摸屏的驱动程序,和 MSM的完全相同,在此不再进行讲解。

x0c9.5.2 键盘驱动 Zoom平台的键盘驱动程序保存在文件drivers/input/keyboard/twl4030_keypad.c中,此驱动使用了 i2c的接口,驱动本身经过了一次封装处理。文件twl4030_keypad.c中的核心内容是中断处理相关的 内容,其中函数do_kp_irq()是标准Linux系统的中断的处理函数,其主要代码如下所示。

函数twl4030_kp_scan()负责实现核心处理功能,先找到按键的行和列,然后调用函数 input_report_key()来汇报结果。函数twl4030_kp_scan()的主要实现代码如下所示。

接下来使用函数twl4030_find_key()根据行列来扫描键盘信息,其实现代码如下所示。

x0c在此需要注意,上面的代码使用kp->keymap数组定义了按键的映射关系,此数组在文件 arch/arm/mach-omap2/board-zoom2.c中定义,并对应于数组zoom2_twl4030_keymap。此数组的定义 代码如下所示。

在OMAP的Zoom平台中,因为键盘基本上是全键盘,并且其数字键和字母键是共用的,所以使用全键盘 的配置文件基本上可以实现我们需要的功能。 第10章 振动器系统驱动 在手机系统中,振动器是比较常见的功能之一,例如可以将来电设置为振动模式,也可以将闹钟设置 为振动模式。在Android系统中有振动系统模块,也能够实现上述来电铃声和闹钟的振动设置。本章 将详细讲解Android振动器系统驱动的实现和移植内容。

x0c10.1 振动器系统结构 Android开发人员应该都知道振动器负

责控制引动电话的振动功能,Android中的振动器系统就是一个 专供实现这方面功能的小系统,能够根据时间和来电而实现振动功能。 Android振动系统包括驱动程序、硬件抽象层、JNI部分、Java框架类等几个部分,并且向Java应用程 序层提供了简单的API作为平台接口。Android振动器系统的基本层次结构如图10-1所示。

图10-1 Android振动器系统的框架结构 自下而上,Android振动器系统包括驱动程序、振动器系统硬件抽象层、振动器系统Java框架类、 Java框架中振动器系统使用等部分,其结构如图10-2所示。

x0c图10-2 振动器系统结构元素 在图10-2中,各个构成元素的具体说明如下所示。 (1)驱动程序 驱动程序是某特定硬件平台振动器的驱动程序,通常基于Android的Timed Output驱动框架来实现。 (2)硬件抽象层 光系统硬件抽象层接口路径如下:

振动系统的硬件抽象层在Android中的默认代码路径如下:

因为Android振动器的硬件抽象层是libhardware_的一部分,所以通常并不需要重新实现。 (3)JNI框架部分 此部分的代码路径如下:

在此文件中定义了振动器的JNI部分,通过调用硬件抽象层向上层提供接口。 (4)Java应用部分 此部分的代码路径如下:

通过调用VibratorService JNI来实现包中的 VibratorService类。VibratorService类不是平台的API,只被Android系统Java框架中的一小部分调 用。 在文件中实现了包中的Vibrator类,这是向Java层提供的API。

x0c10.1.1 硬件抽象层 光系统硬件抽象层接口的实现文件是vibrator.h,主要代码如下所示。

在Android系统中,振动系统硬件抽象层的默认代码路径如下所示。

文件vibrator.c的主要代码如下所示。

x0cx0c10.1.2 JNI框架部分 JNI框架部分的实现文件是com_android_rver_,主要代码如下所示。

在上述代码中,核心功能是通过JNINativeMethod method_table[]和 register_android_rver_VibratorService()实现的。其中使用如下文件通过调用VibratorService JNI来实现包中的VibratorService类。

通过文件frameworks/ba/core/java/android/os/实现了包中的 Vibrator类,然后获得名称为vibrator的服务,并配合同目录中的文件向应 用程序层提供Vibrator的相关API。

x0c10.2 开始移植 了解了和振动器系统相关的构成元素之后,本节开始讲解具体移植的方法。根据特定的硬件平台,有 如下两种移植振动器系统的方法。 ● 由于已经具有硬件抽象层,振动器系统的移植只需要实现驱动程序即可。这个驱动程序需要基于 Android内核中的Timed Output驱动框架。 ● 根据自己实现的驱动程序,在libhardware_le

库中重新实现振动器的硬件抽象层定义接 口。因为振动器硬件抽象层的接口非常简单,所以这种实现方式也非常简单。

x0c10.2.1 移植振动器驱动程序 要想实现Vibrator驱动程序,只需要实现振动的接口即可。因为这是一个输出设备,所以需要接受振 动时间作为参数。在Android中,可以使用多种方式来实现Vibrator驱动程序。在此推荐基于 Android内核定义的Timed Output驱动程序框架实现Vibrator的驱动程序。 Timed Output有“定时输出”之意,用于定时发出某个输出,这种驱动程序依然是基于SYS文件系统 来完成的。 在文件drivers/staging/android/timed_output.h中定义了一个名为timed_output_dev的结构体,其 中包含了enable和get_time这两个函数指针。当实现结构体后,使用函数 timed_output_dev_register()实现注册,使用函数timed_output_dev_unregister()实现注销。 Timed Output驱动程序框架将为每个设备在/sys/class/timed_output/目录中建立一个子目录,其中 设备子目录中的enable文件就是设备的控制文件。当读这个enable文件时表示获得剩余时间,当写这 个文件时表示根据时间振动。虽然Timed Output类型驱动本身有获得剩余时间的能力(读enable文件 ),但是在Android Vibrator硬件抽象层以上的各层接口都没有使用这个功能。 通过SYS文件系统可以调试Timed Output驱动设备,对于Vibrator设备来说,其实现的Timed Output驱动程序的名称是vibrator,所以Vibrator设备在SYS文件系统中的方法如下所示。

对于enable文件来说,“写”表示能指定的时间,“读”表示获取剩余时间。

x0c10.2.2 实现硬件抽象层 1.硬件抽象层的接口 由前面的知识可以了解到,Vibrator硬件抽象层的接口在如下文件中实现。

文件vibrator.h的核心代码如下所示。

在文件vibrator.h中定义了两个接口,分别表示振动和关闭,振动以毫秒(ms)作为时间单位。 2.实现标准硬件抽象层 Vibrator硬件抽象层是标准的实现代码,定义在如下文件中。

文件vibrator.c的核心内容是函数ndit(),此函数的实现代码如下所示。

在上述代码中,ndit()函数的功能是根据设置的时间进行“振动”,在真实的硬件中是通过SYS文 件系统的文件进行控制的,如果是模拟器环境则通过QEMU发送命令。其中vibrator_on()调用 ndit()以时间作为参数,vibrator_on()调用ndit()以0作为参数。

x0c10.3 在MSM平台实现振动器驱动 在MSM的Mahimahi平台中,因为是基于Timed Output驱动程序框架的驱动程序来实现Vibrator的,所 以不需要再实现硬件抽象层。 在Mahimahi平台中,Vibrator驱动程序在如下内核文件中实现。

文件msm_vibrator.c的实现代码如下所示。

x0cx0cx0c在上述实现代码中,核心功能是通过函数t_pmic_vibrator

()实现的,此函数通过MSM系统的远程过 程调用(RPC)实现了具体的功能,调用的指令由HTC_PROCEDURE_SET_VIB_ON_OFF指定。 MSM驱动程序的初始化处理功能是通过上述代码中的函数msm_init_pmic_vibrator(void)实现的,其 中vibrator_work为work_struct类型,功能是在队列的执行函数update_vibrator中调用 t_pmic_vibrator()函数。 pmic_vibrator是一个timed_output_dev类型的设备。在实现enable()函数指针时 ,vibrator_enable根据输入的数值开始定时,并向调度队列进行输出操作。在实现get_time()函数 指针时,vibrator_get_time只是从定时器中获取剩余时间。 注意: 此处之所以使用定时器加队列的方式,是因为enable的调用将形成一个持续时间的效果,但 是调用本身并不宜阻塞,所以就在函数vibrator_enable()退出后通过定时器实现效果。 第11章 音频系统驱动 在手机系统中,声音应用比较重要。无论是手机铃声、闹铃声音,还是播放音乐和视频,都需要声音 系统。本章将详细讲解Android音频系统驱动的实现和移植内容,为读者学习本书后面的知识打下基 础。

x0c11.1 音频系统结构 Android音频系统对应的硬件设备有音频输入部分和音频输出两部分,手机中的输入设备通常是话筒 ,输出设备通常是耳机和扬声器。Android音频系统的核心是Audio系统,它在Android中负责音频方 面的数据流传输和控制功能,也负责音频设备的管理。Audio部分作为Android的Audio系统的输入 /输出层次,一般负责播放PCM声音输出和从外部获取PCM声音,以及管理声音设备和设置。 Audio系统主要分成如下几个层次。 ● Media库提供的Audio系统本地部分接口。 ● AudioFlinger作为Audio系统的中间层。 ● Audio的硬件抽象层提供底层支持。 ● Audio接口通过JNI和Java框架提供给上层。 Android音频系统的基本层次结构如图11-1所示。

图11-1 Android音频系统的框架结构 图11-1中各个构成部分的具体说明如下所示。 (1)Audio的Java部分 Java部分的代码路径是frameworks/ba/media/java/android/media。 与Audio相关的Java包是,其中主要包含了和AudioManager、Audio等系统相关的类。 (2)Audio的JNI部分 JNI部分的代码路径是:frameworks/ba/core/jni。 生成库是libandroid_,Audio的JNI是其中的一个部分。 (3)Audio的框架部分

x0c框架部分的头文件路径是frameworks/ba/include/media/。 源代码路径是frameworks/ba/media/libmedia/。 Audio本地框架是Media库的一部分,本部分内容被编译成库,提供Audio部分的接口(包 括基于Binder的IPC机制)。 (4)Audio Flinger Flinger部分的代码路径是frameworks/ba/libs/audioflinger,此部分内容被编译成库 ,这是Audio系统的本地服务部分

。 (5)Audio的硬件抽象层接口 硬件抽象层接口的头文件路径是hardware/libhardware_legacy/include/hardware/ Audio硬件抽象层的实现在各个系统中可能是不同的,需要使用代码去继承相应的类并实现它们,作 为Android系统本地框架层和驱动程序接口。

x0c11.2 分析音频系统的层次 在Android中,Audio系统从上到下分别由Java的Audio类、Audio本地框架类、AudioFlinger和 Audio的硬件抽象层几个部分组成。本节将简要介绍这几个层次的基本知识。

x0c11.2.1 层次说明 ● Audio本地框架类:是的一个部分,这些Audio接口对上层提供接口,由下层的本地 代码去实现。 ● AudioFlinger:继承了libmeida里面的接口,提供实现库。这部分内容没有 自己的对外头文件,上层调用的只是libmedia本部分的接口,但实际调用的内容是 。 ● JNI:在Audio系统中,使用JNI和Java对上层提供接口,JNI部分通过调用libmedia库提供的接口 来实现。 ● Audio硬件抽象层:提供到硬件的接口,供AudioFlinger调用。Audio的硬件抽象层实际上是各个 平台开发过程中需要主要关注和独立完成的部分。 因为Android中的Audio系统不涉及编解码环节,只负责上层系统和底层Audio硬件的交互,所以通常 以PCM作为输入/输出格式。 在Android的Audio系统中,无论上层还是下层,都使用一个管理类和输出/输入两个类来表示整个 Audio系统,输出/输入两个类负责数据通道。在各个层次之间具有对应关系,如表11-1所示。 表11.1 Android各个层次的对应关系

x0c11.2.2 Media库中的Audio框架 在Media库中提供了Android的Audio系统的核心框架,在库中实现了AudioSystem、AudioTrack和 AudioRecorder三个类。另外还提供了IAudioFlinger类接口,通过此类可以获得IAudioTrack和 IAudioRecorder两个接口,分别用于声音的播放和录制。AudioTrack和AudioRecorder分别通过调用 IAudioTrack和IAudioRecorder来实现。 Audio系统的头文件被保存在frameworks/ba/include/media/目录中,其中包含了如下头文件。 ● AudioSystem.h:media库的Audio部分对上层的总管接口。 ● IAudioFlinger.h:需要下层实现的总管接口。 ● AudioTrack.h:放音部分对上接口。 ● IAudioTrack.h:放音部分需要下层实现的接口。 ● AudioRecorder.h:录音部分对上接口。 ● IAudioRecorder.h:录音部分需要下层实现的接口。 其中文件IAudioFlinger.h、IAudioTrack.h和IAudioRecorder.h的接口是通过下层的继承来实现的。 文件AudioFlinger.h、AudioTrack.h和AudioRecorder.h是对上层提供的接口,它们既供本地程序调 用(例如声音的播放器、录制器等),也可以通过JNI向Java层提供接口。 从具体功能上看,AudioSystem用于综合管理Audio系统,

而AudioTrack和AudioRecorder分别负责输 出和输入音频数据,即分别实现播放和录制功能。 在文件AudioSystem.h中定义了枚举值和t/get等一系列接口,主要代码如下所示。

x0cx0c在上述枚举值中,是用单独的位来表示audio_routes的,而不是用顺序的枚举值来表示的,所以在使 用这个值的过程中可以使用“或”的方式。例如表示声音既可以从耳机(EARPIECE)输出,也可以从 扬声器(SPEAKER)输出。上述功能是否能够实现,是由下层提供支持的。在这个类中,t/get等接 口控制的也是相关的内容,例如Audio声音大小、Audio模式和路径等。 AudioTrack是Audio输出环节的类,其中包含了最重要的接口write(),主要代码如下所示。

x0c类AudioRecord用于实现和Audio输入相关的功能,其中最重要的功能是通过接口函数read()实现的 ,主要代码如下所示。

在类AudioTrack和AudioRecord中,函数read()和write()的参数都是内存的指针及其大小,内存中的 内容一般表示的是Audio的原始数据(PCM数据)。这两个类还涉及Auido数据格式、通道数、帧数目 等参数,不但可以在建立时指定,也可以在建立之后使用t()函数进行设置。 另外,在libmedia库中提供的只是一个Audio系统框架,其中类AudioSystem、AudioTrack和 AudioRecord分别调用下层的接口IAudioFlinger、IAudioTrack和IAudioRecord来实现。另外的一个 接口是IAudioFlingerClient,它作为向IAudioFlinger中注册的监听器,相当于使用回调函数获取 IAudioFlinger运行时信息。

x0c11.2.3 本地代码 在Android系统中,AudioFlinger是Audio音频系统的中间层,能够作为libmedia提供的Audio部分接 口的实现。这部分本地代码的路径如下:

文件AudioFlinger.h和是实现AudioFlinger的核心文件,在其中提供了类 AudioFlinger,此类是一个IAudioFlinger的实现,其接口代码如下所示。

由上述代码可以看出,AudioFlinger使用函数createTrack()来创建音频的输出设备IAudioTrack,使 用函数openRecord()来创建音频的输入设备IAudioRecord。并且还使用接口get/t来实现控制功能

x0c。 构造函数AudioFlinger()的代码如下所示。

x0c由上述代码可以看出,在初始化AudioFlinger之后,会首先获得放音设备,然后为混音器 (Mixer)建立线程并建立放音设备线程,最后在线程中获得放音设备。 在文件AudioResampler.h中定义了类AudioResampler,此类是一个音频重取样器的工具类,定义代码 如下所示。

在上述音频重取样工具类中,包含了如下三种质量。 ● 低等质量(LOW_QUALITY):使用线性差值算法实现。 ● 中等质量(MED_QUALITY):使用立方差值算法实现。 ● 高等质量(HIGH_QUALITY):使用FIR(有限阶滤波器)实现。 在AudioResampler中,AudioResamplerOrder

1是线性实现,AudioResamplerCubic.*文件提供立方实 现方式,AudioResamplerSinc.*提供FIR实现。 通过文件AudioMixer.h和实现了一个Audio系统混音器,它被AudioFlinger调用,一 般用于在声音输出之前的处理,提供多通道处理、声音缩放、重取样。AudioMixer调用了 AudioResampler。

x0c11.2.4 JNI代码 在Android中的Audio系统中,通过JNI向Java层提供功能强大的接口,这样就可以在Java层通过JNI接 口完成Audio系统的大部分操作。 Audio JNI的实现代码保存在frameworks/ba/core/jni目录下,在该目录下主要有三个核心文件 ,这三个文件分别对应了Android Java框架中的三个类的支持。这三个文件的具体说明如下所示。 ● ystem:负责Audio系统的总体控制。 ● rack:负责Audio系统的输出环节。 ● ecorder:负责Audio系统的输入环节。 在Android的Java层中,可以对Audio系统进行控制和数据流操作,其中控制操作和底层的处理基本一 致。对于数据流操作来说,由于Java不支持指针,因此接口被封装成了另外的形式。例如在音频输出 功能中,通过文件android_media_提供了写字节和写短整型的接口类型。对应代码如 下所示。

x0c11.2.5 Java代码 在Android的Audio系统中,和Java相关的类定义在包中,Java部分的代码保存在 frameworks/ba/media/java/android/media目录中,在其中主要实现了如下类: ● ystem ● . AudioTrack ● ecorder ● ormat 其中前三个类和本地代码是对应的,在AudioFormat中提供了一些和Audio相关的枚举值。在此需要注 意的是在Audio系统的Java代码中,虽然可以通过AudioTrack和AudioRecorder的write()和read()接 口在Java层对Audio的数据流进行操作,但是,更多的时候并不需要这样做,而是在本地代码中直接 调用接口进行数据流的输入/输出,而Java层只进行控制类操作,不处理数据流。

x0c11.3 移植Audio系统的必备技术 在Android中,Audio的标准化部分是硬件抽象层的接口,所以针对不同的特定平台,需要移植 Audio驱动程序和Audio硬件抽象层,开发人员的任务就是移植这两方面的内容。

x0c11.3.1 移植Audio系统所要做的工作 ● Audio驱动程序:此部分需要在Linux内核中实现,虽然有很多实现方式,但是大多数Audio驱动 程序都需要提供用于音量控制等功能的控制类接口,通过这些接口实现PCM输入、输出的数据类接口 。 ● Audio硬件抽象层:这是Audio驱动程序和Audio本地框架类AudioFlinger的接口。根据Android系 统对接口的定义,Audio硬件抽象层是C++类的接口,需要在继承接口中定义三个类来实现Audio硬件 抽

象层,这三个类分别实现总控、输入和输出功能。 要想实现一个Android的硬件抽象层,需要实现AudioHardwareInterface、AudioStreamOut和 AudioStreamIn这三个类,并将代码编译成动态库。AudioFlinger会链接这个动态库,并 调用其中的createAudioHardware()函数来获取接口。 在文件AudioHardwareBa.h中定义了类AudioHardwareBa,此类继承了Audio HardwareInterface,通过继承此接口也可以实现Audio的硬件抽象层。 Android系统的Audio硬件抽象层可以通过继承类AudioHardwareInterface来实现,其中分为控制部分 和“输入/输出”处理部分。

x0c11.3.2 分析硬件抽象层 Audio系统的硬件抽象层是AudioFlinger和Audio硬件之间的接口,在不同系统的移植过程中可以有不 同的实现方式。其中Audio硬件抽象层的接口路径如下所示。

在上述路径下的核心文件是AudioHardwareBa.h和AudioHardwareInterface.h。 作为Android系统的Audio硬件抽象层,既可以基于Linux标准的ALSA或OSS音频驱动来实现,也可以基 于私有的Audio驱动接口来实现。 在文件AudioHardwareInterface.h中分别定义了类AudioStreamOut、AudioStreamIn和 AudioHardwareInterface。类AudioStreamOut和AudioStreamIn分别描述了音频输出设备和音频输入 设备,其中负责数据流的接口分别是函数write()和read(),参数是一块内存的指针和长度;另外还 有一些设置和获取接口。类AudioStreamOut和AudioStreamIn的实现代码如下所示。

由此可见,类AudioStreamOut和AudioStreamIn是两个对应的接口类,分别实现了输出和输入环节。 类AudioStreamOut和AudioStreamIn都需要通过Audio硬件抽象层的核心AudioHardwareInterface接口 类来获取。接口类AudioHardwareInterface的实现代码如下所示。

x0c在上述AudioHardwareInterface接口中,分别使用函数openOutputStream()和openInputStream()来 获取AudioStreamOut和AudioStreamIn这两个类,将它们分别作为音频输入设备和输出设备来使用。 除此之外,在文件AudioHardwareInterface.h中还定义了C语言的接口来获取一个AudioHardware Interface类型的指针。定义代码如下所示。

x0c11.3.3 分析AudioFlinger中的Audio硬件抽象层的实现 在Android的AudioFlinger中,可以通过编译宏的方式来选择Audio硬件抽象层。可选择的Audio硬件 抽象层既可以作为参考设计,也可以在没有实际的Audio硬件抽象层时使用,目的是保证系统的正常 运行。 1.编译文件 文件是AudioFlinger的编译文件,定义代码如下所示。

在上述代码中,当BOARD_USES_GENERIC_AUDIO为True时链接libaudiointer face.a静态库;当 BOARD_USES_GENERIC_AUDIO为Fal时链接动态库,在大多数的情况下使用后 者。 另外,在中也生成了libaudiointerface.a,具体代码如下所示。

在上述代码中,分别

编译如下4个源文件来生成libaudiointerface.a静态库。 ● AudioHard :用于实现基础类和管理。 ● :实现基于特定驱动的通用Audio硬件抽象层。 ● :实现Audio硬件抽象层的一个桩。 ● :实现输出到文件的Audio硬件抽象层。 在文件中定义了AudioHardwareInterface::create(),此函数是 Audio硬件抽象层的创建函数,主要代码如下所示。

x0c2.桩方式实现 在文件AudioHardwareStub.h和中,通过桩方式实现了一个Android硬件抽象 层。桩方式不操作实际的硬件和文件,只是进行了空操作。在系统没有实际的Audio设备时才使用桩 方式实现,目的是保证系统的正常工作。如果使用这个硬件抽象层,实际上Audio系统的输入和输出 都将为空。 在文件AudioHardwareStub.h中定义了类AudioStreamOutStub和AudioStreamInStub,分别实现输入和 输出功能。主要代码如下所示。

x0c在上述代码中,只用缓冲区大小、采样率和通道数这三个固定的参数将一些函数直接无错误返回。 然后需要使用AudioHardwareStub类来继承AudioHardwareBa,也就是继承 AudioHardwareInterface。主要代码如下所示。

x0c为了保证可以输入和输出声音,桩实现的主要内容是实现AudioStreamOutStub和 AudioStreamInStub类的读/写函数。实现代码如下所示。

当使用这个接口来输入和输出音频时,和真实的设备并没有任何关系,输出和输入都使用延时来完成 。在输出时不会播出声音,但是返回值表示全部内容已经输出完成;在输入时会返回全部为0的数据 。 3.通用Audio硬件抽象层

x0c在Android系统中,文件AudioHardwareGeneric.h和实现了通用的Audio硬 件抽象层。与前面介绍的桩实现方式不同,这是一个真正能够使用的Audio硬件抽象层,但是它需要 Android的一种特殊的声音驱动程序的支持。 在通用硬件抽象层中,类AudioStreamOutGeneric、AudioStreamInGeneric和 AudioHardwareGeneric分别继承Audio硬件抽象层的三个接口。对应代码如下所示。

在文件中使用的驱动程序是/dev/eac,这是一个非标准程序,定义设备路 径的代码如下所示。

注意: eac是Linux中的一个misc驱动程序,作为Android的通用音频驱动,写设备表示放音,读设备 表示录音。 在Linux操作系统中,/dev/eac驱动程序在文件系统中的节点主设备号为10,是次设备号自动生成的 。通过构造函数AudioHardwareGeneric()可以打开这个驱动程序的设备节点。对应代码如下所示。

此音频设备是一个比较简单的驱动程序,在其中并没有很多设置接口,只是用写设备来表示录音,用 读设备来表示放音。放音和录

音支持的都是16位的PCM。对应代码如下所示。

尽管AudioHardwareGeneric是一个可以真正工作的Audio硬件抽象层,但是这种实现方式非常简单 ,不支持各种设置,参数也只能使用默认的。而且这种驱动程序需要在Linux核心加入eac驱动程序的 支持。

x0c4.具备Dump功能的Audio硬件抽象层 在文件AudioDumpInterface.h和中提供了Dump功能的Audio硬件抽象层,目 的是将输出的Audio数据写入到文件中。 其实AudioDumpInterface本身支持Audio输出功能,但是不支持输入功能。在文件 AudioDumpInterface.h中定义类的代码如下所示。

在上述代码中,只实现了AudioStreamOut输出,而没有实现AudioStreamIn输入。由此可见,此 Audio硬件抽象层只支持输出功能,不支持输入功能。其中输出文件的名称被定义为如下格式。

在文件中,通过函数AudioStreamOut实现写操作,写入的对象就是这个文件 。对应代码如下所示。

x0c如果文件是打开的,则可以使用追加方式写入。所以当使用这个Audio硬件抽象层时,播放的内容 (PCM)将全部被写入文件。而且这个类支持各种格式的输出,这取决于调用者的设置。 使用AudioDumpInterface的目的并不是为了实际的应用,而是为了调试我们使用的类。当使用播放器 调试音频时,有时无法确认是解码器的问题还是Audio输出单元的问题,这时就可以用这个类来替换 实际的Audio硬件抽象层,将解码器输出的Audio的PCM数据写入文件中,由此可以判断解码器的输出 是否正确。

x0c11.4 真正实现Audio硬件抽象层 要想实现一个真正的Audio硬件抽象层,需要完成和11.2节中实现硬件抽象层类似的工作。例如可以 基于Linux标准的音频驱动OSS(Open Sound System)或ALSA(Advanced Linux Sound Architecture)驱动程序来实现。 (1)基于OSS驱动程序实现 对于OSS驱动程序来说,实现方式和前面的AudioHardwareGeneric方式类似,数据流的读/写操作通 过对/dev/dsp设备的“读/写”来完成。区别在于OSS支持了更多的ioctl来进行设置,还涉及通过 /dev/mixer设备进行控制,并支持更多不同的参数。 (2)基于ALSA驱动程序实现 对于ALSA驱动程序来说,实现方式一般不是直接调用驱动程序的设备节点,而是先实现用户空间的 alsa-lib,然后Audio硬件抽象层通过调用alsa-lib来实现。 在实现Audio硬件抽象层时,如果系统中有多个Audio设备,此时可由硬件抽象层自行处理 tRouting()函数设定。例如可以选择支持多个设备的同时输出,或者有优先级输出。对于这种情况 ,数据流一般来自函数AudioStreamOut::write(),可由硬件抽象层确定输出方法。对于某种特殊的 情况,也有可能采用硬件直接连接的方式,此时数据流可

能并不来自上面的write(),这样就没有数 据通道,只有控制接口。Audio硬件抽象层也是可以处理这种情况的。

x0c11.5 MSM平台实现Audio驱动系统 了解了Audio驱动程序的基本知识后,在本节内容中将讲解MSM平台中Audio系统的实现方法,为读者 学习本书后面的知识打下基础。

x0c11.5.1 实现Audio驱动程序 在MSM平台中,Audio驱动程序被保存在arch/arm/mach-msm/qdspX目录中。如果是版本为5的DSP处理 器,其驱动目录为qdsp5;如果是版本为6的DSP处理器,其驱动目录为qdsp6。Audio驱动和MSM处理器 的DSP系统是密切相关的。 通常Audio驱动程序的头文件是/include/linux/msm_mdp.h,而qdsp6的特定头文件是arch/arm/machmsm/include/mach/msm_qdsp6_audio.h。在Audio系统中涉及的头文件如下所示。 ● audio_ctl.c:音频控制文件,生成设备的节点是dev/msm_audio_ctl。 ● routing.c:控制音频路径,生成设备的节点是dev/msm_audio_route。 ● pcm_in.c:PCM输入通道,生成设备的节点是dev/msm_pcm_out。 ● mp3.c:MP3码流直接输出通道,生成设备的节点是dev/msm_mp3。 在MSM平台中,Audio驱动程序并不是主流的标准驱动程序,在用户空间中包括了两个控制节点和三个 数据节点。其中两个控制节点用于控制Audio的基本内容和路径,而三个数据节点包括PCM输出、 PCM输入和MP3码流输出。 在文件include/linux/msm_audio.h中定义了Audio系统的ioctl控制命令,具体代码如下所示。

x0c11.5.2 实现硬件抽象层 在MSM平台中,硬件抽象层已经包含在Android的开放源代码中,这些都是通用的代码。这些代码被保 存在目录hardware/msm7k中,使用MSM7K处理器实现libaudio,通过QSD8K处理器实现libaudioqsd8k。其中在libaudio-qsd8k目录中主要包含如下文件。 ● :实现了Audio硬件抽象层。 ● AudioHardware.h:定义了Audio硬件抽象层的类。 ● :实现了Audio策略管理。 ● AudioPolicyManager.h:定义了Audio策略管理类。 ● msm_audio.h:实现了和内核相同的ioctl命令和数据结构的定义。 在文件AudioHardware.h中定义了三个类,分别是AudioHardware、class AudioStreamOutMSM72xx和 AudioStreamInMSM72xx,这三个类都继承自其他类,分别实现Audio系统的总控、输出和输入环节的 功能。具体代码如下所示。

x0c类AudioStreamOutMSM72xx的核心功能是通过函数write()实现的,此函数的实现代码如下所示。

x0cx0c上述函数的处理过程是先打开PCM输出设备来获取配置,然后设置配置,并通过ioctl命令开始 Audio处理流,并读、写操作文件。 类AudioStreamInMSM72xx的核心功能是通过函数read()实现的,此函数的实现代码如下所示。

x0c在上述代码中,通过mFd打开Audio输入设备的描述,输入设备是dev/msm/_pcm_in。其实

在函数 t()中,已经对设备dev/msm/_pcm_in进行了基本配置。函数t()的实现代码如下所示。

x0c11.6 OSS平台实现Audio驱动系统 OSS(Open Sound System)是UNIX平台上一个统一的音频接口。本节将简要介绍在OSS上实现Audio系 统的基本知识。

x0c11.6.1 OSS驱动程序介绍 OSS驱动是字符型设备,因为在UNIX系统中所有的设备都被统一成文件,通过对文件的访问方式(首 先open,然后read/write,同时可以使用ioctl读取/设置参数,最后clo)来访问设备。所以在 OSS中主要包含以下的几种设备文件。 ● /dev/mixer:访问声卡中内置的mixer,调整音量大小,选择音源。 ● /dev/sndstat:测试声卡,执行cat/dev/sndstat会显示声卡驱动的信息。 ● /dev/dsp、/dev/dspW和/dev/audio:读这个设备就相当于录音,写这个设备就相当于放音。 /dev/dsp与/dev/audio之间的区别在于采样的编码不同,/dev/audio使用μ律编码,/dev/dsp使用8bit(无符号)线性编码,/dev/dspW使用16-bit(有符号)线性编码。/dev/audio主要是为了与 SunOS兼容,所以尽量不要使用。 ● /dev/quencer:访问声卡内置的或者连接在MIDI接口的synthesizer。 在Linux系统中有如下三个和OSS相关的文件。

Linux音频的输入/输出功能是通过/dev/dsp设备实现的,但对于这些声音信号的处理则是通过 /dev/mixer设备来完成的。查看文件linux/soundcard.h可以获取对Mixer文件操作所需要的变量,在 此文件中列出了如下常用变量。

上述变量名都是在soundcard.h中可以查到的,通过名称即可看出其用途,后面的赋值在该头文件中 则并不是这样定义的,而是通过调用一些函数返回的,应该是声卡上对应的地址。在应用程序中可通 过ioctl(fd,cmd,arg)来对这些变量进行赋值。其中fd即为一个打开/dev/mixer的文件指针,cmd为上 面所列的这些变量,arg即是对这些变量进行操作所需赋给的结构体或变量。

x0c11.6.2 mixer 在OSS中,mixer()是核心。Audio Mixer函数是一组控制音频线到目标设备的函数,并且可以控制音 量和其他效果。在这组API中,尽管只有10个函数和2个消息,但使用起来还是比较难。在本节中,通 过应用这些函数编写成两个应用程序,来展示它们的使用方法。而且尽可能采用实际应用中的用户界 面,这样更有可能被读者直接利用。 1.程序1daima11MIXER_MUTE 此程序运行后能够等效于Windows Volume Control的Mute all复选框,核心功能是通过文件 实现的,主要代码如下所示。

x0cx0cx0c执行效果如图11-2所示。

图11-2 执行效果 2.程序2daima13MIXER_VOLUME 此程序运行后能够等效于Windows Volume Control的进度条,核心功能是通过文件实 现的,主要代码如下所示。

x0cx0cx0cx0c执行效果如图11-3

所示。

图11-3 执行效果

x0c11.7 ALSA平台实现Audio系统 ALSA是Advanced Linux Sound Architecture的缩写,是高级Linux声音架构的简称,它在Linux操作 系统上提供了音频和MIDI(Musical Instrument Digital Interface,音乐设备数字化接口)的支持 。在2.6系列内核中,ALSA已经成为默认的声音子系统,用来替换2.4系列内核中的OSS(Open Sound System,开放声音系统)。本节将简要介绍在ALSA上实现Audio系统的基本知识。

x0c11.7.1 注册音频设备和音频驱动 1.注册音频设备 设备本身非常简单,复杂的是这个设备的drvdata。drvdata中包含三部分,分别是关于machine的、 关于Platform的和关于codec的。大体上说machine主要是关于CPU级别的也可以说是关于SSP本身设置 的,而Platform是关于平台级别的,就是说与这个平台本身实现相关的,而codec就是与我们所用的 音频codec相关的。这里基本上就可以看出整个音频驱动的架构特点,就是从ALSA层进入内核ALSA层 接口core层,再调用上面说的三个方面的函数来处理,先是CPU级别的,然后是Platform的,再是 codec级别的,这几层做完了,工作也就做得差不多了,后面会详细介绍。当然这个执行顺序不是固 定的(不知道是不是marvel写代码不专业导致的),但多半都包括了这三部分的工作。 2.注册音频驱动 前面讲了设备的注册,其中设备的名字就是soc-audio,而这里的driver注册时名字也是socaudio,对于Platform的设备匹配的原则是根据名字的,所以将会匹配成功,成功后就会执行Audio驱 动提供的函数soc_probe()。 函数soc_probe()本身架构很简单,和前面说的逻辑一样,先调用了CPU级别的Probe,然后是codec级 别的,最后是Platform的(这三个的顺序就不一样),但是因为CPU级别的和Platform级别的都为空 ,最后都调用了codec级别的Probe函数,也就是micco_soc_probe,这个函数基本上就完成了所有应 该完成的音频驱动的初始化了。简单划分为两部分,对上和对下:对上主要是注册设备节点,以及这 些设备节点对应的流的创建;对下主要是读写函数的设置,codec本身的dai设置,初始化寄存器的设 置,最重要的就是后面的control的创建和门的创建了。

x0c11.7.2 在Android中使用ALSA声卡 1.使用过程 在Android系统中使用ALSA声卡的具体流程如下所示。 step1 使用下面的CD命令来到Android源代码的根目录,在此以Android 2.0为例。

step2 从Android主页下载ALSA声卡关源代码,下载命令如下所示。

step3 下载完成后修改板子的文件,确保板子可以使用ALSA声卡,修改代码如下所 示。

在Android 2.0版本中,文件位于如下目录中。

step4 重新编译一遍Android,编译完成后在根文件/system/e

tc/中添加配置声卡的工 作参数脚本,具体代码如下所示。

x0cx0cx0c2.几个需要注意的问题 (1)ALSA音频路径

x0c我们知道在sound/soc/codecs目录中有很多音频的Codec驱动,例如笔者使用的是wm9713,AP是 s3c6410,在此驱动文件中定义了很多widget和control,ALSA在PlayBack(回放)或Record(录制 )的时候,文件sound/soc/soc-dapm.c中的函数dapm_power_widgets()会根据“配置情况”来打开相 应的Widget,并搭建一个完整的音频路径。只要搭建该音频路径成功,就可以正常工作。 sound/soc/codecs/wm9711.c中的audio_map[]就是一个wm9713的路由表,根据wm9713手册中的Audio Paths Overview可以选择自己需要的音频路径,在audio_map[]中测试一下,看audio_map中是否支持 这种路径。 (2)配置ALSA 在ALSA中最主要的配置是ALSA音频调试,当ALSA使用amixer命令打开audio_map[]中的开关 (control/switch)和其他control(控制),并设置这些control后,在使用“aplay(播放 )/arecord(录音)”的时候即可搭建正确的路径,实现播放和录音功能。 例如在调试的时候,在不使用amixer控制的时候(这是默认状态),arecord可以正确录音,使用文 件sound/soc/soc-dapm.c中的函数dump_dapm()来Dump出的路径是正确的;当aplay的时候 ,dump_dapm出来的路径是错误的,原因是默认设置里没有打开playback的开关(switch)。当遇到 上述问题的时候,运行如下命令即可正确地打开playback。

由此可见,在打开playback路径时需要一个开关,dapm_power_widgets会自动把这些开关连接的 widget连接起来而构成一个完整的播放路径。 (3)在Android中配置ALSA 在Andriod中使用alsa-lib时也需要配置音频路径,具体来说有如下两个配置方法。 ● 在文件中,使用函数system()调用amixer来完成配置。具体代码如下所 示。

● 编写文件文件,在中的ALSAMixer::ALSAMixer对象初始化时 ,会通过alsa-lib的conf.c文件中的函数来读取文件/etc/以获取配置信息,并对 codec进行配置。 3.ASLA中的重要命令 (1)alsa_amixer命令 命令alsa_amixer用于配置音频codec的mixer开关、mux对路选择、volume值等,例如下面的代码。

x0cx0c(2)alsa_alsactl store命令 此命令用于生成文件/etc/,在显示当前codec的状态时可以根据该文件检查codec的状 态是否正确。例如下面的代码。

x0camixer命令的用法可以参照alsa_amixer contents中的内容;编写文件的方法可以参照 alsa_alsactl生成/etc/的过程。下面的代码是笔者编写的。

x0cx0c上述代码只是文件的一部分,其他dPlayback_xxx的写法都类似,唯一的区别 是hook_argsp[]中的内容需要根据自己的需要来设

置。

x0c11.7.3 在OMAP平台移植Android的ALSA声卡驱动 接下来讲解在OMAP3530平台上移植Android的ALSA声卡驱动的方法,我们以最难以进行移植操作的 Android 1.5为例进行讲解。 step1 使用GIT下载移植代码,读者需要注意的是,不同人在网上下载的移植代码可能会有区别 ,我们需要明确在AudioSystem类中是否定义了DEVICE_OUT_EARPIECE,需要选择使用没有定义的。 先运行如下命令:

将下载的内容复制到external目录下,并重命名为alsa-lib。 然后运行如下命令:

将下载的内容复制到hardware目录下,并重命名为libaudio-alsa。 再运行如下命令:

将下载的内容复制到external目录下,并重命名为alsa-utils。 然后通过以下命令下载DEVICE_OUT_EARPIECE的代码。

复制和重命名DEVICE_OUT_EARPIECE的方法和前面的方法相同。 step2 修改文件system/core/init/device.c,在其中加上如下代码以创建/dev/snd。

step3 修改文件system/core/init/devices.c,目的是增加设备节点及权限。

step4 修改文件build/target/board/generic/。

x0cstep5 修改文件hardware/alsa_sound/,此步骤很重要,否则编译不会通过。

x0cstep6 重建如下编译选项。 ● build/:不同下载的脚本名字可能有不同。 ● choocombo。 ● make clean:必须经过此过程,否则不能在Android系统中发声。 step7 编译make -j4 //core dual。 step8 制作文件系统。 在文件/system/etc/()需要注意如下几个特别的配置:

x0cstep9 在编译后修改文件,重新设置Audio驱动的设备节点的owner和访问属性。

第12章 视频输出系统驱动 在手机系统中,视频应用比较重要。我们通常在手机中播放影视视频,也通常用手机在线观看精美大 片。本章将详细讲解Android视频输出系统驱动的实现和移植内容,为读者学习本书后面的知识打下 基础。

x0c12.1 视频输出系统结构 在Android系统中,视频输出系统对应的是Overlay子系统,此系统是Android的一个可选系统,用于 加速显示输出视频数据。视频输出系统的硬件通常叠加在主显示区之上的额外的叠加显示区。这个额 外的叠加显示区和主显示区使用独立的显示内存。在通常情况下,主显示区用于输出图形系统,通常 是RGB颜色空间。额外显示区用于输出视频,通常是YUV颜色空间。主显示区和叠加显示区通过 Blending(硬件混淆)自动显示在屏幕上。在软件部分我们无须关心叠加的实现过程,但是可以控制 叠加的层次顺序和叠加层的大小等内容。 Overlay系统的基本层次结构如图12-1所示。

图12-1 Overlay的基本层次结构 Android中的Overlay系统没有Java部分,在其中只包含了视频输出的驱动程序、硬件抽象层和本地框 架等。Overla

y系统的结构如图12-2所示。

x0c图12-2 Overlay系统结构 在图12-2所示的系统结构中,各个构成部分的具体说明如下所示。 ● Overlay驱动程序:通常是基于FrameBuffer或V4L2的驱动程序。在此文件中主要定义了两个 struct,分别是data device和control device,这两个结构体分别针对data device和control device的函数open()和函数clo()。这两个函数是注册到device_module里面的函数。 ● Overlay硬件抽象层:代码路径如下所示。

Overlay硬件抽象层是Android中一个标准的硬件模块,其接口只有一个头文件。 ● Overlay服务部分:代码路径如下所示。

由此可见,Overlay系统的服务部分包含在SurfaceFlinger中,此层次的内容比较简单,主要功能是 通过类LayerBuffer实现的。首先要明确的是SurfaceFlinger只是负责控制merge Surface,例如计算 出两个Surface重叠的区域。至于Surface需要显示的内容,则通过Skia、Opengl和Pixflinger来计算 。所以我们在介绍SurfaceFlinger之前先忽略里面存储的内容究竟是什么,先弄清楚它对merge的一 系列控制的过程,然后再结合2D、3D引擎来看它的处理过程。 ● 本地框架代码。 本地框架的头文件路径如下所示。

源代码路径如下所示。

Overlay系统只是整个框架的一部分,主要功能是通过类Ioverlay和Overlay实现的,源代码被编译成 ,它提供的API主要在视频输出和照相机取景模块中使用。

x0c12.2 需要移植的部分 因为Overlay系统的底层和系统框架接口是硬件抽象层,所以要想实现Overlay系统,需要实现硬件抽 象层和下面的驱动程序。在Overlay系统的硬件抽象层中,使用了Android标准硬件模块的接口,此接 口是标准C语言接口,通过函数和指针来实现具体功能,其中包含了数据流接口和控制接口,需要根 据硬件平台的具体情况来实现。 Overlay系统的驱动程序通常是视频输出驱动程序,可以通过标准的FrameBuffer驱动程序或Video for Linux 2视频输出驱动程序来实现。因为系统不同,所以即使使用同一种驱动程序,也有不同的 实现方式。 1.FrameBuffer驱动程序方式 FrameBuffer驱动程序方式是最直接的方式,实现视频输出从驱动程序的角度和一般FrameBuffer驱动 程序类似。区别是视频输出使用YUV格式颜色空间,而用于图形界面的FrameBuffer使用RGB颜色和空 间。 2.Video for Linux 2方式 Video for Linux 2是Linux视频系统的一个标准框架,在其第一个版本中提供了摄像头视频输入框架 ,从Video for Linux 2.0版本开始提供视频输出接口。使用此视频输出接口,可以根据系统的性能 来调整队列的数目。

x0c12.3 分析硬件抽象层 Overlay系统的硬件抽象层是一个硬件模块,本节将简要介绍Overlay系统的硬件抽象层的基

本知识。

x0c12.3.1 Overlay系统硬件抽象层的接口 在如下文件中定义了Overlay系统硬件抽象层的接口。

在文件overlay.h中,主要定义了两个struct,分别是data device和control device。并提供了针对 data device和control device的函数open()和函数clo()。文件overlay.h的代码结构如图12-3所 示。

图12-3 文件overlay.h结构 ● 定义Overlay控制设备和Overlay数据设备,它们的名称被定义为如下两个字符串。

● 定义一个枚举enum,定义了所有支持的Format格式。FrameBuffer会根据Format和width、 height来决定Buffer(FrameBuffer里面用来显示的Buffer)的大小。定义enum的代码如下所示。

● 定义和Overlay系统相关结构体。

x0c在文件overlay.h中和Overlay系统相关的结构体是overlay_t和overlay_handle_t,主要代码如下所 示。

结构体overlay_handle_t是在内部使用的结构体,用于保存Overlay硬件设备的句柄。在使用的过程 中,需要从overlay_t获取overlay_handle_t。其中上一层的使用只实现结构体overlay_handle_t指 针的传递,具体的操作是在Overlay的硬件抽象层中完成的。 ● 定义结构体overlay_control_device_t,此结构体定义了一个control device。里面的成员除了 common都是函数,这些函数就是我们需要去实现的。在实现的时候我们会基于这个结构体扩展出一个 关于control device的context的结构体,context结构体内部会扩充一些信息并且包含control device。common的每一个device都必须有,而且必须被放到第一位,目的只是为了 overlay_control_device_t和hw_device_t匹配。overlay_control_device_t的定义代码如下所示。

● 定义结构overlay_data_device_t,此结构和overlay_control_device_t类似。在具体使用上 ,overlay_control_device_t负责初始化、销毁和控制类的操作,overlay_data_device_t用于显示 内存输出的数据操作。结构overlay_data_device_t的定义代码如下所示。

x0cx0c12.3.2 实现Overlay系统的硬件抽象层 在实现Overlay系统的硬件抽象层时,具体实现方法取决于硬件和驱动程序,根据设备需要进行处理 。具体分为如下两种情况。 (1)FrameBuffer驱动程序方式 在此方式下,需要先实现函数getBufferAddress(),然后返回通过mmap获得的FrameBuffer指针。如 果没有双缓冲的问题,不需要真正实现函数dequeueBuffer()和queueBuffer()。上述函数的实现文件 是,此文件被保存在如下目录中。

函数getBufferAddress()用于返回FrameBuffer内部显示的内存,通过mmap获取内存地址。函数代码 如下所示。

函数dequeueBuffer()和queueBuffer()的实现代码如下所示。

(2)Video for Linux 2方式 如果使用Video for Linux 2的输出驱动,函数dequeueBuffer()和queueBuffer()与调用驱动时的主 要ioctl是一致的,即分别调用VIDIOC_QBUF和VIDIOC_DQBU

F即可直接实现。至于其他的初始化工作 ,可以在initialize中进行处理。因为存在视频数据队列,所以此处处理的内容比一般的帧缓冲区复 杂,但是可以实现更高的性能。 由此可见,在某一个硬件系统中,Overlay的硬件层和Overlay系统的调用者都是特定实现的,只需匹 配上下层代码即可实现,并不要一一满足每一个要求,各个接口可以根据具体情况灵活使用。

x0c12.3.3 实现接口 在Android系统中,Overlay系统提供了接口overlay,此接口用于叠加在主显示层上面的另外一个显 示层。此叠加的显示层经常作为视频的输出或相机取景器的预览界面来使用。文件Overlay.h的主要 内部实现类是Overlay和OverlayRef。OverlayRef需要和surface配合来使用,通过Isurface可以创建 出OverlayRef。RefBa的主要代码如下所示。

在上述代码中,通过surface来控制Overlay,其实也可以在不使用Overlay的情况下统一进行管理。 此处是通过OverlayRef来创建Overlay的。一旦获取了Overlay,就可以通过这个Overlay获取到用来 显示的Address地址,向Address中写入数据后就可以显示我们的图像了。

x0c12.4 实现Overlay硬件抽象层 在Android系统中,提供了一个Overlay硬件抽象层的框架实现,其中有完整的实现代码,我们可以将 其作为使用Overlay硬件抽象层的方法。但是其中没有使用具体硬件,所以不会有实际的显示效果。 上述框架实现的源代码目录如下所示。

在上述目录中,主要包含了文件和,其中文件的主要代码如下所 示。

Overlay库是一个C语言库,没有被其他库链接,在使用时是被动打开的,所以必须放置在目标文件系 统的system/lib/hw目录中。 文件的主要代码如下所示。

x0cx0cx0c12.5 在OMAP平台实现Overlay系统 本节将简要介绍在OMAP平台中实现Overlay系统的基本知识。

x0c12.5.1 实现输出视频驱动程序 在OMAP平台中,实现视频输出驱动程序的代码保存在如下目录中。

在上述目录中,主要包含如下所示的文件。 ● 文件omapvout-dss.c和omapvout-dss.h:封装了DSS系统的功能。 ● 文件omapvout-mem.c和omapvout-mem.h:实现内存映射、释放和分配等功能。 ● 文件omapvout-vbq.c和omapvout-vbq.h:用于操作虚拟内存。 ● 文件omapvout.c和omapvout.h:OMAP平台的主框架,用于注册V412输出驱动的接口。 在文件omapvout.c中定义函数omapvout_probe(),在此函数中建立了多个Video输出设备。此函数的 主要实现代码如下所示。

x0c在文件omapvout.c中定义ideo_device类型的结构omapvout_devdata,此结构体是在函数 omapvout_probe_device中被注册的Video设备。结构omapvout_devdata的定义代码如下所示。

x0c12.5.2 实现Overlay硬件抽象层 在OMAP平台中,通过Android

系统实现了Overlay硬件抽象层,此硬件抽象层是基于v412视频驱动程序 实现的。OMAP平台的Overlay硬件抽象层在如下目录中实现。

上述目录中的构成文件如图12-4所示。

图12-4 构成文件 (1)文件 文件的主要代码如下所示。

通过上述代码,生成了名为3的动态库,被存放在目标系统的/system/lib/hw中,这是 Android标准的硬件模块。 (2)文件 在文件中提供了OMAP平台的Overlay硬件模块框架,其中函数overlay_ createOverlay()是Overlay控制设备的createOverlay()指针的实现。函数 overlay_createOverlay()的实现代码如下所示。

x0cx0cx0c(3)文件v4l2_utils.c 在文件v4l2_utils.c中,通过函数v4l2_overlay_open()打开Overlay设备,此函数的实现代码如下所 示。

在上述代码中,参数id是Overlay设备的编号。从上述代码可以看出,在Overlay设备中已经包含了 dev/video1和dev/video2两个设备。在实现硬件抽象层时,先打开第一个来解决问题,如果有需要再 打开第二个。 在文件v4l2_utils.c中需要封装v412驱动程序,函数v4l2_overlay_map_buf()在初始化阶段进行定义 ,用于从设备中获取内存大小。函数v4l2_overlay_map_buf()的实现代码如下所示。

还需要调用函数v4l2_overlay_query_buffer()来调用v412的ioctl命令来查询内存,此函数的实现代 码如下所示。

x0c还需要定义函数v4l2_overlay_stream_on()来打开数据流,定义函数v4l2_overlay_stream_off()关 闭数据流。这两个函数的实现代码如下所示。

还需要定义函数v4l2_overlay_q_buf()来对应Overlay数据设备的queueBuffer接口,定义函数 v4l2_overlay_dq_buf()来对应Overlay数据设备的dequeueBuffer接口。这两个函数的实现代码如下 所示。

x0cx0c12.6 系统层调用Overlay HAL的架构 本节将简要介绍系统层调用Overlay模块的基本知识,为读者学习本书后面的知识打下基础。

x0c12.6.1 调用Overlay HAL的架构的流程 1.测试文件 在如下文件中提供了简单调用Overlay的方法。

遗憾的是上述测试程序有错误,在编译时提示编译如下代码失败。

其实读者朋友们无须担心,造成上述错误的原因是申请Surface接口失败,和Overlay系统无关。

2.在Android系统中创建Overlay Overlay系统是一个功能强大的系统,不仅仅是简单地实现视频输出功能,而且实现了和摄像头、 GPS等有关的功能。Overlay的具体应用主要体现在如下几个方面。 (1)摄像头应用的实现文件是 (frameworksbacameralibcamerarvice),实现流程如下所示。

x0c相关内容将在本书后面的章节中进行介绍。 (2)界面相关应用文件(frameworksbalibsui)。 LayerBaClient::Surface::onTransact()函数位于中,好像是用于ibind进程通信的 函数。其

中函数BnSurface::onTransact()有5种方式,只有确定有overlay硬件支持时才会调用如下 ca CREATE_OVERLAY语句。

x0c(3)文件(frameworksbalibssurfaceflinger),此文件是createOverlay的实 现。实现流程如下所示。

x0c3.管理Overlay HAL模块 文件(frameworksbalibsui)的功能是管理Overlay HAL模块,并封装HAL的API。具 体实现流程如下所示。 step1 打开Overlay HAL模块,实现代码如下所示。

step2 初始化Overlay HAL,主要代码如下所示。

x0c其构造函数位于文件中,对应代码如下所示。

step3 需要封装很多需要的API,比如TI自己编写的函数opencore()用于负责视频输出。各个API的 实现和编码请读者参考开源文件,在此不再一一讲解。 由此可以看出,虽然Overlay的输出对象有两种,一种是视频(主要是YUV格式,调用系统的 V4L2),另外一种是ISurface的一些图像数据(RGB格式,直接写FrameBuffer),但是从代码实现角 度看,目前Android系统默认并没有使用Overlay功能。虽然提供了Skeleton的Overlay HAL,并对其 进行封装,但是上层几乎没有调用到封装的API。 如果想使用Overlay HAL,需要大量修改上层框架。这种做法对视屏播放比较重要,具体实现读者可 以参考用TI编写的实现文件Android_surface_output_。并且Surface实现的Overlay功 能和Copybit的功能有部分重复,从TI的代码看主要是实现V4L2的Overlay功能。

x0c12.6.2 S3C6410 Android Overlay的测试代码 笔者用S3C6410测试了其提供的开源代码,测试代码如下所示。

x0c运行后的效果如图12-5所示。

图12-5 跑动测试代码 第13章 OpenMax多媒体框架 在前面两章的内容中,分别讲解了Android系统中音频驱动和视频驱动的基本知识。在Android多媒体 领域中,除了这两章中讲解的知识外,还涉及其他的高级知识,例如本章将要讲解的OpenMax多媒体 框架。

x0c13.1 OpenMax基本层次结构 2006年,NVIDIA公司和Khronos联合推出OpenMax,这是多媒体应用程序的框架标准。OpenMax是无授 权费、跨平台的应用程序接口API。OpenMax通过使媒体加速组件能够在开发、集成和编程环节中实现 跨多操作系统和处理器硬件平台,提供全面的流媒体编码/解码器和应用程序便携化。 OpenMax的官方网站地址如下所示。

OpenMax是一个多媒体应用程序的框架标准。其中,OpenMax IL(集成层)技术规格定义了媒体组件 接口,以便在嵌入式器件的流媒体框架中快速集成加速编码/解码器。 在Android中,OpenMax IL层通常用在多媒体引擎插件中,Android的多媒体引擎OpenCore和 StageFright都可以使用OpenMax作为插件,主要用于编码和解码(Codec)处理。 在Android的框架层中定义了由Android封装的OpenMax接口,此

接口和标准的接口类似,但是使用的 是C++类型接口,并且使用了Android的Binder IPC机制。StageFright使用Android封装的OpenMax接 口,OpenCore没有使用此接口,而是使用其他形式对OpenMax IL层接口进行封装。 Android中OpenMax的基本层次结构如图13-1所示。

图13-1 OpenMax多媒体框架的层次结构

x0c13.2 分析OpenMax框架构成 本节将详细讲解OpenMax框架各个层次结构的基本知识。

x0c13.2.1 OpenMax总体层次结构 OpenMax分成三个层次,自上而下分别是OpenMax DL(开发层)、OpenMax IL(集成层)和OpenMax AL(应用层)。在实际的应用中,OpenMax的三个层次中使用较多的是OpenMax IL集成层,由于操作 系统到硬件的差异和多媒体应用的差异,OpenMax的DL和AL层使用相对较少。 接下来讲解上述三个层次结构的具体说明。 ● OpenMax DL(Development Layer,开发层) 在OpenMax DL中定义了一个API,这是音频、视频和图像功能的集合。硅供应商可以在一个新的处理 器上实现并优化,然后编码/解码供应商使用它来编写更广泛的编码/解码器功能。OpenMax DL具备 音频信号的处理功能,例如FFT和Filter,也具备图像原始处理功能,例如颜色空间转换、视频原始 处理,并且可以实现例如MPEG-4、H.264、MP3、AAC和JPEG等编码/解码器的优化。 ● OpenMax IL(Integration Layer,集成层) OpenMax IL是一种音频、视频和图像编码/解码器,能够实现和多媒体编码/解码器的交互,并以统 一的行为支持组件(例如资源和皮肤)。这些编码/解码器或许是软、硬件的混合体,其中可以将对 用户是透明的底层接口应用于嵌入式、移动设备,这些接口包括应用程序和媒体框架。S编码/解码 器供应商必须具备写私有的或者封闭的接口,这样就可以集成进移动设备。IL的主要目的是使用特征 集合为编码/解码器提供一个系统抽象,解决多个不同媒体系统之间的轻便性问题。 ● OpenMax AL(Application Layer,应用层) OpenMax AL API在应用程序和多媒体中间件之间提供了一个标准化接口,多媒体中间件提供服务以实 现被期待的API功能。OpenMax具有三个层次,具体如图13-2所示。

图13-2 OpenMax层次 OpenMax API将会与处理器一块被提供,目的是使库和编码/解码器开发者能够高速有效地利用新器 件的完整加速潜能,而无须担心其底层的硬件结构。该标准是针对嵌入式设备和移动设备的多媒体软 件架构。在架构底层上为多媒体的编码/解码器和数据处理定义了一套统一的编程接口,对多媒体数 据的处理功能进行系统级抽象,为用户屏蔽了底层的细节。因此,多媒体应用程序和多媒体框架通过 OpenMax IL可以用一种统一的方式来使用编码/解码器和其他

多媒体数据处理功能,具有跨越软硬件 平台的可移植性。

x0c13.2.2 OpenMax IL层的结构 在当前多媒体领域,OpenMax IL实际上已经成为多媒体框架标准。大多数嵌入式处理器或者多媒体编 码/解码器模块的硬件生产者,通常提供标准的OpenMax IL层的软件接口,这样软件的开发者就可以 基于此层次的标准化接口进行多媒体程序的开发。 OpenMax IL的接口层次结构比较科学,既不是硬件编码/解码器的接口,也不是应用程序层的接口 ,所以可以比较容易地实现标准化。OpenMax IL的层次结构如图13-3所示。

图13-3 OpenMax IL的层次结构 在如图13-3所示的层次结构中,虚线部分的内容是OpenMax IL层的内容,功能是实现了OpenMax IL中 的各个组件(Component)。对于下层而言,OpenMax IL既可以调用OpenMax DL层的接口,也可以直 接调用各种Codec实现。对于上层而言,OpenMax IL既可以被OpenMax AL层等框架层 (Middleware)调用,也可以被应用程序直接调用。 OpenMax IL层中包含的主要内容如下所示。 ● 客户端(Client):OpenMax IL的调用者。

x0c● 组件(Component):OpenMax IL的单元,每一个组件实现一种功能。 ● 端口(Port):组件的输入/输出接口。 ● 隧道化(Tunneled):让两个组件直接连接的方式。 OpenMax IL层的运作流程如图13-4所示。

图13-4 OpenMax IL层的运作流程 在图13-4中,OpenMAX IL层的客户端通过调用如下4个OpenMAX IL组件来实现同一个功能。 ● Source组件:只有一个输出端口。 ● Host组件:有一个输入端口和一个输出端口。 ● Accelerator组件:具有一个输入端口,调用了硬件的编码/解码器器,加速主要体现在此环节 上。 ● Sink组件:Accelerator组件和Sink组件通过私有通信方式在内部进行连接,没有经过明确的组 件端口。 在使用OpenMAX IL的时候,有好几种处理数据流的方式,既可以经由客户端,也可以不经由客户端。 在图13-4中,Source组件到Host组件的数据流就是经过客户端的;而Host组件到Accelerator组件的 数据流就没有经过客户端,使用了隧道化的方式;Accelerator组件和Sink组件甚至可以使用私有的 通信方式。 OpenMax Core是辅助组件正常运行的模块,它需要完成各个组件的初始化等工作,在真正运行过程中 ,重点的是各个OpenMax IL的组件,OpenMax Core不是重点,也不是标准。

x0c在OpenMax IL层中,真正的核心内容是OpenMAX IL组件,此组件以输入、输出端口为接口,端口可以 被连接到另一个组件上。外部对组件可以发送命令,还可以进行设置/获取参数、配置等内容。组件 的端口可以包含缓冲区(Buffer)的队列。 在OpenMax IL层中,组件的处理的核心内容是通过输入端口来消耗Buffer

,通过输出端口来填充 Buffer,由此多组件相连接就可以构成流式的处理。在OpenMAX IL层中,一个组件的基本结构如图 13-5所示。

图13-5 OpenMAX IL层中的组件结构 组件的功能和定义的端口类型有着千丝万缕的联系,在通常情况下,只有一个输出端口的是Source组 件;只有一个输入端口的是Sink组件;有多个输入端口、一个输出端口的是Mux组件;有一个输入端 口、多个输出端口的是DeMux组件。输入和输出端口各一个组件的为中间处理环节,这是最常见的组 件。 端口根据应用来支持不同的数据类型。例如存在输入、输出端口各一个组件,其输入端口使用MP3格 式的数据,输出端口使用PCM格式的数据,那么此组件就是一个MP3解码组件。 通过隧道化(Tunneled)可以将不同的组件的一个输入端口和一个输出端口连接到一起,在这种情况 下,两个组件的处理过程合并,共同处理。尤其对于单输入和单输出的组件,两个组件将作为类似一 个使用。 注意: 隧道化是一个关于组件连接方式的概念。

x0c13.2.3 Android中的OpenMax 在Android系统中,主要使用的是标准OpenMax IL层的接口,其中只是进行了简单的封装。标准的 OpenMax IL实现可以很容易地以插件的形式加入到Android系统中。 在Android多媒体引擎OpenCore和StageFright中,都可以使用OpenMax作为多媒体编码/解码器的插 件,但是并没有直接使用OpenMax IL层提供的纯C接口,而只是对其进行了一定的封装。 Android系统对OpenMax支持的力度逐渐扩大,在Android 2.x版本之后,Android的框架层开始封装定 义OpenMax IL层接口,甚至使用Android中的Binder IPC机制来调用。在Stagefright中使用了 OpenMax IL层接口,但是没有使用OpenCore。OpenCore使用在OpenMax IL层作为编码/解码器插件在 前,Android框架层封装OpenMax接口在后面的版本中才引入。 在Android系统中,主要使用了OpenMax的编码/解码器功能。虽然OpenMax也可以生成输入、输出、 文件解析/构建等组件,但是在Android中使用得最多的依旧是编码/解码器组件。媒体输入、输出 环节和系统有很大的关系,引入OpenMax标准会比较麻烦;文件解析/构建环节一般不需要使用硬件 加速。因为编码/解码器组件最能体现硬件加速环节,所以最常使用。 在Android系统中实现OpenMax IL层和标准的OpenMax IL层时,一般需要实现如下两个环节。 ● 编码/解码器驱动程序:位于Linux内核空间,需要通过Linux内核调用驱动程序,通常使用非标 准的驱动程序。 ● OpenMax IL层:根据OpenMax IL层的标准头文件实现不同功能的组件。 另外,在Android中还提供了OpenMax的适配层接口(对OpenMax IL的标准组件进行封装适配),它作 为Android本

地层的接口,可以被Android的多媒体引擎调用。

x0c13.3 实现OpenMax IL层接口 在Android系统中,主要使用了OpenMax的编码/解码器功能,这些功能主要是通过OpenMax IL层的接 口实现的。本节将详细讲解实现OpenMax IL层接口的基本知识。

x0c13.3.1 OpenMax IL层的接口 1.头文件 在OpenMax IL层的接口中定义了若干个头文件,在这些文件中定义了实现OpenMax IL层接口的内容。 这些头文件的具体说明如下所示。 ● OMX_Types.h:OpenMax IL的数据类型定义。 ● OMX_Core.h:OpenMax IL核心的API。 ● OMX_Component.h:OpenMax IL组件相关的API。 ● OMX_Audio.h:音频相关的常量和数据结构。 ● OMX_IVCommon.h:图像和视频公共的常量和数据结构。 ● OMX_Image.h:图像相关的常量和数据结构。 ● OMX_Video.h:视频相关的常量和数据结构。 ● OMX_Other.h:其他数据结构,包括A/V同步。 ● OMX_Index.h:OpenMax IL定义的数据结构索引。 ● OMX_ContentPipe.h:内容的管道定义。 在OpenMax标准中只有头文件,没有标准的库。 2.实现过程 在具体实现OpenMax IL层的接口时,程序员的主要任务是实现包含函数指针的结构体,下面看一下上 述头文件中的实现流程。 step1 在文件OMX_Component.h中定义的OMX_COMPONENTTYPE结构体,这是OpenMax IL层的核心内容 ,表示一个组件,其实现如下所示。

x0cx0c实现上述OMX_COMPONENTTYPE结构体后,其中的各个函数指针就是调用者可以使用的内容。各个函数 指针和文件OMX_core.h中定义的内容相对应。例如在文件OMX_core.h中定义OMX_FreeBuffer的代码如 下所示。

在文件OMX_core.h中定义OMX_FillThisBuffer的代码如下所示。

step2 接下来需要定义组件运行机制。其中EmptyThisBuffer和FillThisBuffer是驱动组件运行的 基本机制,前者表示让组件消耗缓冲区,表示对应组件输入的内容;后者表示让组件填充缓冲区,表 示对应组件输出的内容。其中定义OMX_EmptyThisBuffer的代码如下所示。

x0c其中定义FillThisBuffer的代码如下所示。

step3 接下来开始定义和端口相关的缓冲区管理函数,有UBuffer()、AllocateBuffer()、 FreeBuffer()等函数。有些组件的端口可以自己分配缓冲区,而有些可以使用外部的缓冲区,因此有 不同的接口对其进行操作。 step4 使用SendCommand向组件发送控制类的命令。接口GetParameter、SetParameter、 GetConfig、SetConfig用于辅助的参数和配置的设置和获取。具体代码如下所示。

step5 使用ComponentTunnelRequest实现组件之间的隧道化连接,在此需要指定两个组件及其相连 的端口。

x0cstep6 使用ComponentDeInit来反初始化组件。在文件OMX_Component.h中定义的端口类型为 OMX_PORTDOMAINTYPE枚举类型,此枚举的定义代码如下所示。



上述代码中,分别定义了音频类型、视频类型和图像类型,其他类型是OpenMax IL层次所定义的 4种端口的类型。 step7 使用OMX_PARAM_PORTDEFINITIONTYPE类(也在OMX_Component.h中定义)来定义端口的具体 内容,其实现代码如下所示。

对于上述代码的具体说明如下所示。 ● 端口的方向(OMX_DIRTYPE):包含OMX_DirInput(输入)和OMX_DirOutput(输出)两种。 ● 端口格式的数据结构:使用format联合体来表示,具体由如下4种不同类型来表示,与端口的类 型相对应。 ● OMX_AUDIO_PORTDEFINITIONTYPE ● OMX_VIDEO_PORTDEFINITIONTYPE ● OMX_IMAGE_PORTDEFINITIONTYPE ● OMX_OTHER_PORTDEFINITIONTYPE 上述类型分别在头文件OMX_Audio.h、OMX_Video.h、OMX_Image.h和OMX_Other.h中定义。

x0c● OMX_BUFFERHEADERTYPE:在OMX_Core.h中定义,表示一个缓冲区的头部结构。 step8 在文件OMX_Core.h中定义的枚举类型OMX_STATETYPE用于表示OpenMax的状态机,主要代码如 下所示。

step9 在文件OMX_Core.h中定义的枚举类型OMX_COMMANDTYPE,此枚举表示对组件的命令类型,主 要代码如下所示。

注意: 在OpenMax的函数参数中,经常包含OMX_IN和OMX_OUT等宏,它们的实际内容为空,只是为了 标记参数的方向是输入还是输出。

x0c13.3.2 在OpenMax IL层中需要做什么 在实现OpenMax IL层时一般不调用OpenMax DL层,具体实现的内容是各个不同的组件。通常通过以下 两个步骤来实现OpenMax IL组件。 (1)组件的初始化函数 包括硬件和OpenMax数据结构的初始化,一般分成函数指针初始化、私有数据结构的初始化、端口的 初始化等步骤,使用其中的pComponentPrivate成员可以保留本组件的私有数据为上下文,在最后获 得填充完成OMX_COMPONENTTYPE类型的结构体。 (2)OMX_COMPONENTTYPE类型结构体的各个指针 实现其中的各个函数指针,需要使用私有数据的时候,从其中的pComponentPrivate得到指针,转化 成实际的数据结构使用。 端口定义的是OpenMax IL组件对外部的接口。在OpenMax IL层中常用的组件大多数是一个输入和一个 输出端口。对于最常用的编解码(Codec)组件来说,通常需要在每个组件的实现过程中,调用硬件 的编解码接口来实现。在组件的内部处理中可以通过建立线程来处理。在OpenMax组件的端口中有默 认参数,但也可以在运行时设置,因此一个端口也可以支持不同的编码格式。音频编码组件的输出和 音频编码组件的输入通常是原始数据格式(PCM格式),视频编码组件的输出和视频编码组件的输入 通常是原始数据格式(YUV格式)。

x0c13.3.3 研究Android中的OpenMax适配层 在Android系统中,在如下文件中定义OpenMax适配层的接口。

文件IOMX.h的主要代码如下所示。

x0cx0c在IOMX中,只有

第一个函数createRenderer()是纯虚函数,第二个函数createRenderer()和 createRendererFromJavaSurface()通过调用createRenderer()函数实现。 类IOMXRenderer表示一个OpenMax的渲染器,定义此类的代码如下所示。

在类IOMXRenderer中包含了一个render接口,其参数类型IOMX::buffer_id其实是void*,可以根据不 同的渲染器使用不同的类型。 在文件IOMX.h中还存在一个观察器类IOMXObrver,此类表示OpenMax的观察者,其中包含了函数 onMessage(),其参数是omx_message结构体,其中包含Event事件类型、FillThisBuffer完成和 EmptyThisBuffer完成几种类型。

x0c13.4 在OMAP平台实现OpenMax IL 在Android的开源代码库中,已经包含了TI的OpenMax IL层的实现代码,其源代码路径如下所示。

x0c13.4.1 实现文件 在hardware/ti/omap3/omx/目录下主要包含如下目录。 ● system:OpenMax核心和公共部分。 ● audio:音频处理部分的OpenMax IL组件。 ● video:视频处理部分OpenMax IL组件。 ● image:图像处理部分OpenMax IL组件。 在TI OpenMax IL实现中,最上面的内容用于实现管理和初始化操作;中间层是各个编/解码单元的 OpenMax IL标准组件;最下层的LCML层供各个OpenMax IL标准组件调用。 实现TI OpenMax IL的公共部分文件保存在system/src/openmax_il/目录中,包含的主要内容如下所 示。 ● omx_core/src:OpenMax IL的核心,生成动态库libOMX_。 ● lcml/:LCML的工具库,生成动态库。 实现TI OpenMax IL的视频(Video)功能的相关组件被保存在video/src/openmax_il/目录中,包含 的主要内容如下所示。 ● prepost_processor:Video数据的前处理和后处理,生成动态库。 ● video_decode:Video解码器,生成动态库。 ● video_encode:Video编码器,生成动态库。 实现TI OpenMax IL的音频(Audio)功能的相关组件被保存在audio/src/openmax_il/目录中,包含 的主要内容如下所示。 ● g711_dec:G711解码器,生成动态库。 ● g711_enc:G711编码器,生成动态库。 ● g722_dec:G722解码器,生成动态库。 ● g722_enc:G722编码器,生成动态库。 ● g726_dec:G726解码器,生成动态库。 ● g726_enc:G726编码器,生成动态库。 ● g729_dec:G729解码器,生成动态库。 ● g729_enc:G720编码器,生成动态库。 ● nbamr_dec:AMR窄带解码器,生成动态库。 ● nbamr_enc:AMR窄带编码器,生成动态库。 ● wbamr_dec:AMR宽带解码器,生成动态库。 ● wbamr_enc:AM

R宽带编码器,生成动态库。 ● mp3_dec:MP3解码器,生成动态库。 ● aac_dec:AAC解码器,生成动态库。 ● aac_enc:AAC编码器,生成动态库。 ● wma_dec:WMA解码器,生成动态库。 实现TI OpenMax IL图像(Image)功能的相关组件保存在image/src/openmax_il/目录中,包含的主 要内容如下所示。 ● jpeg_enc:JPEG编码器,生成动态库。 ● jpeg_dec:JPEG解码器,生成动态库。

x0c13.4.2 分析TI OpenMax IL的核心 TI OpenMax IL的核心是LCML,其全称是“Linux Common Multimedia Layer”,是TI的Linux公共多 媒体层。在实现OpenMax IL时,LCML内容保存在system/src/openmax_il/lcml/目录中,核心文件是 src子目录中的文件LCML_DspCodec.c。通过调用DSPBridge的内容可以实现ARM和DSP的通信,让DSP进 行编/解码方面的处理。 注意: DSP的运行还需要固件的支持。 TI OpenMax IL的核心功能在system/src/openmax_il/omx_core/目录中定义,生成TI OpenMax IL的 核心库是libOMX_。其中如下文件是主要文件,其中定义了编/解码器的名称等内容。

文件OMX_Core.c的主要代码如下所示。

在上述tComponentName数组的各个项中,第一个表示编/解码库内容,第二个表示库所实现的功能。 使用函数TIOMX_GetHandle()来获得各个组件的句柄,其主要代码如下所示。

x0cx0cx0c在上述函数TIOMX_GetHandle()中,根据tComponentName数组中动态库的名称来动态打开各个编/解 码实现的动态库,并取出其中的OMX_ComponentInit符号来执行各个组件的初始化。

x0c13.4.3 实现TI OpenMax IL组件实例 在TI OpenMax IL中,通过调用LCML来实现各个组件,各种组件的实现方式基本类似。主要都实现了 名称为OMX_ComponentInit的初始化函数,都实现了OMX_COMPONENTTYPE类型的结构体中的各个成员。 各个组件的目录结构和文件结构也类似。接下来以MP3解码器的实现为例,介绍实现TI OpenMax IL组 件的方法。 在audio/src/openmax_il/mp3_dec/src目录中,主要包含以下文件。 ● OMX_Mp3Decoder.c:MP3解码器组件实现。 ● OMX_Mp3Dec_CompThread.c:MP3解码器组件的线程循环。 ● OMX_Mp3Dec_Utils.c:MP3解码器的相关工具,调用LCML实现真正的MP3解码的功能。 在文件OMX_Mp3Decoder.c中,函数OMX_ComponentInit()实现组件初始化功能,从参数中可以得到返 回的内容。函数OMX_ComponentInit()的主要代码如下所示。

x0cx0cx0cx0c上述组件是通过OpenMax的标准实现方式实现的。对外接口的内容只有一个初始化函数,实现了 OMX_COMPONENTTYPE类型的初始化功能。输入端口和输出端口的具体说明如下所示。 ● 输入端口:编号为MP3D_INPUT_PORT(==0

),类型为OMX_PortDomainAudio,格式为 OMX_AUDIO_CodingMP3。 ● 输出端口:编号是MP3D_OUTPUT_PORT(==1),类型为OMX_PortDomainAudio,格式为 OMX_AUDIO_CodingPCM。 在文件OMX_Mp3Dec_CompThread.c中,通过函数MP3DEC_ComponentThread()来创建MP3解码的线程的执 行函数。 在文件OMX_Mp3Dec_Utils.c中,通过函数Mp3Dec_StartCompThread()来调用POSIX的线程库以建立 MP3解码的线程,实现代码如下所示。

对于函数Mp3Dec_StartCompThread()来说,它在组件初始化函数OMX_ComponentInit()的最后被调用 。MP3线程的开始并不表示解码过程开始,线程需要通过pipe机制获得命令和数据(cmdPipe和 dataPipe),在合适的时候开始工作。此pipe在MP3解码组件的SendCommand中实现写操作,在线程中 读取其内容。 第14章 多媒体插件框架 前面三章分别讲解了Android音频系统驱动、视频系统驱动和OpenMax多媒体系统驱动的基本知识。在 Android多媒体领域中除了上述知识外,还涉及其他高级知识,例如本章将要讲解的多媒体插件引擎 。本章将详细讲解OpenCore引擎和Stagefright的基本知识和移植方法,为读者学习本书后面的知识 打下基础。

x0c14.1 Android多媒体插件 在Android的多媒体系统中,可以根据需要添加一些第三方插件,这样可以增强多媒体系统的功能。 在Android系统的本地多媒体引擎上面,是Android的多媒体本地框架,而在多媒体本地框架上面是多 媒体JNI和多媒体的Java框架部分。和多媒体相关的应用程序通过调用Android Java框架层,来提供 标准的多媒体API进行构建。本章将要讲解的OpenCore引擎和Stagefright引擎是Android本地框架中 定义接口的实现者,上层调用者不知道Android下层使用什么多媒体引擎。 Android多媒体引擎和插件的基本层次结构如图14-1所示。

图14-1 Android多媒体引擎和插件的基本层次 Android系统的多媒体框架系统如图14-2所示。

x0c图14-2 Android系统的多媒体框架结构 从多媒体应用的实现角度来看,多媒体系统主要包含如下两方面的内容。 ● 输入输出环节:音频、视频纯数据流的输入、输出系统。 ● 中间处理环节:包括文件格式处理和编码/解码环节处理。 假如想要处理一个MP3文件,媒体播放器的处理流程是:将一个MP3格式的文件作为播放器的输入,将 声音从播放器设备输出。在具体实现上,MP3播放器经过了MP3格式文件解析、MP3码流解码和PCM输出 播放的过程。整个过程如图14-3所示。

图14-3 MP3播放器结构

x0c14.2 需要移植的内容 在移植多媒体插件时,主要包含如下两方面的工作。 ● 输入/输出环节的工作:主要是基于Android硬件抽象层来实现的。 ● 编码/解码环节:通常是基于OpenMax IL层实现的。 在Android系统中

,有两个常用的本地多媒体引擎,分别是OpenCore引擎和Stagefright引擎。这两个 引擎都实现了Android本地框架的Media部分,这样可以定义媒体播放器和媒体录制器。在Android 2.2前的版本中,都是使用OpenCore引擎实现多媒体功能。而从Android 2.2开始,OpenCore引擎和 Stagefright引擎同时存在,并且主要通过Stagefright引擎实现媒体文件的播放。 OpenCore引擎和Stagefright引擎支持两种插件类型,一种是实现媒体输入/输出环节的插件,另外 一种是实现编码/解码环节的插件。 ● OpenCore引擎:使用MediaIO形式实现媒体播放器的视频输出功能,使用OpenMax实现编码和解码 插件。 ● Stagefright引擎:使用VideoRenderer形式实现媒体播放器的视频输出功能,使用Android封装 的OpenMax接口实现编码和解码插件。

x0c14.3 OpenCore引擎 本节将详细讲解OpenCore引擎的基本知识,分别介绍其结构和插件机制,为读者学习后面的知识打下 基础。

x0c14.3.1 OpenCore层次结构 OpenCore的另外一个常用的称呼是PacketVideo,它是Android的多媒体核心。其实PacketVideo是一 家公司的名称,而OpenCore是这套多媒体框架的软件层的名称。在Android开发者的眼中,二者的含 义基本相同。与其他Android程序库相比,OpenCore的代码非常庞大,是基于C++实现的,定义了全功 能的操作系统移植层,各种基本的功能均被封装成类的形式,各层次之间的接口多使用继承等方式。 OpenCore是一个多媒体的框架,从宏观上来看主要包含如下两方面的内容。 ● PVPlayer:提供了媒体播放器的功能,可以完成各种音频(Audio)、视频(Video)流的回放 (Playback)功能。 ● PVAuthor:提供媒体流记录的功能,可以完成各种音频(Audio)、视频(Video)流及静态图像 捕获功能。 PVPlayer和PVAuthor以SDK的形式提供给开发者,可以在这个SDK之上构建多种应用程序和服务。在移 动终端中常常使用的多媒体应用程序有媒体播放器、照相机、录像机、录音机等。 OpenCore的基本结构如图14-4所示。

图14-4 OpenCore的层次结构 在图14-4中的主要层次元素的具体说明如下所示。 ● OSCL是Operating System Compatibility Library的缩写,意为操作系统兼容库,其中包含了一 些操作系统底层的操作,功能是为了更好地在不同操作系统间移植。它包含了基本数据类型、配置、 字符串工具、I/O、错误处理、线程等内容,类似于一个基础的C++库。 ● PVMF:是PacketVideo Multimedia Framework的缩写,意为PV多媒体框架,可以在框架内实现一 个文件解析(parr)和组成(compor)、编/解码的NODE,也可以继承其通用的接口,在用户层 实现一些NODE。

x0c● PVPlayer Engine:PVPlayer引擎。 ● PVAuthor Engi

ne:PVAuthor引擎。 除了上述4个元素外,其实在OpenCore中还包含了很多内容。从播放的角度看,PVPlayer输入的 (Source)是文件或者网络媒体流,输出(Sink)的是音频、视频的输出设备,其基本功能包含了媒 体流控制、文件解析、音频、视频流的解码(Decode)等方面的内容。除了从文件中播放媒体文件之 外,还包含了与网络相关的RTSP流(Real Time Stream Protocol,实时流协议)。在媒体流记录方 面,PVAuthor输入的(Source)是照相机、麦克风等设备,输出(Sink)的是各种文件,包含流的同 步、音频、视频流的编码(Encode),以及文件的写入等功能。 在使用OpenCore SDK的时候,有可能需要在应用程序层实现一个适配器(Adaptor),然后在适配器 之上实现具体的功能,对于PVMF的NODE也可以基于通用的接口,在上层用插件的形式实现具体功能。

x0c14.3.2 OpenCore代码结构 在Android系统中,OpenCore的代码目录是external/opencore/,此目录是OpenCore的根目录,其中 包含的各个子目录的具体说明如下所示。 ● android:这是一个上层库,基于PVPlayer和PVAuthor的SDK实现了一个为Android使用的 Player和Author。 ● balibs:其中包含了数据结构和线程安全等内容的底层库。 ● codecs_v2:这是一个内容较多的库,主要包含了编/解码的实现和OpenMAX的实现。 ● engines:包含PVPlayer和PVAuthor引擎的实现。 ● extern_libs_v2:包含了khronos的OpenMAX的头文件。 ● fileformats:文件格式的解析(parr)工具。 ● nodes:其中提供了PVMF的NODE,主要是编/解码和文件解析方面的NODE。 ● oscl:操作系统兼容库。 ● pvmi:包含了输入/输出控制的抽象接口。 ● protocols:主要包含了和网络相关的RTSP、RTP、HTTP等协议的内容。 ● pvcommon:pvcommon库文件的文件,没有源文件。 ● pvplayer:pvplayer库文件的文件,没有源文件。 ● pvauthor:pvauthor库文件的文件,没有源文件。 ● tools_v2:其中包含了编译工具及一些可注册的模块。 另外,在external/opencore/目录中还包含了如下两个文件。 ● :全局的编译文件。 ● :配置文件。 在external/opencore/的各个子文件夹中还包含了很多个文件,在这些文件之间存在着递 归的关系。例如在根目录下的中包含了下面的内容片段。 ● include $(PV_TOP)/pvcommon/ ● include $(PV_TOP)/pvplayer/ ● include $(PV_TOP)/pvauthor/ 这表示要引用pvcommon、pvplayer和pvauthor等文件夹下面的文件。 external/opencore/文件夹中的各个文件可以按照排列组合来使用,将几个内 容合并在一个库里面。

x0c14.3.3

OpenCore编译结构 在Android开源版本中,通过OpenCore编译出来的各个库的具体说明如下所示。 ● :OpenCore的Author库。 ● :OpenCore底层的公共库。 ● :下载注册库。 ● :下载功能实现库。 ● :MP4注册库。 ● :MP4功能实现库。 ● libopencorenet_:网络支持库。 ● :OpenCore的Player库。 ● :RTSP注册库。 ● :RTSP功能实现库。 OpenCore中的各个库之间的关系如下所示。 ● :所有库的依赖库,提供了公共的功能。 ● 和:两个并立的库,分别用于回放和记录,而且这 两个库是OpenCore对外的接口库。 ● libopencorenet_:提供网络支持的功能。 除此之外,还有一些功能以插件(Plug-In)的方式放入Player中使用,每个功能使用两个库,一个 实现具体功能,一个用于注册。在接下来的内容中,将简要介绍OpenCore中各个库的基本结构。 1.库的结构 库是整个OpenCore的核心库,其编译控制的文件的路径如下所示。

上述文件使用递归的方式寻找子文件,其主要内容如下所示。

x0c这些被包含的文件真正指定需要编译的文件,这些文件在的目录及其子目录中 。事实上,在库中包含了以下内容。 ● OSCL的所有内容。 ● Pvmf框架部分的内容(pvmi/pvmf/)。 ● 基础库中的一些内容(balibs)。 ● 编/解码的一些内容。 ● 文件输出的node(nodes/pvfileoutputnode/)。 从库的结构可以看出,最终生成库的结构与OpenCore的层次关系并非完全重合 。在库中已经包含了底层的OSCL的内容、PVMF的框架及Node和编/解码的工具 。 2.库的结构 库是一个用于实现播放功能的库,其编译控制的文件的路径如下所示。

上述文件的主要代码如下所示。

x0cx0c在库中包含了如下内容。 ● 解码工具。 ● 文件的解析器(MP4)。 ● 解码工具对应的Node。 ● player的引擎部分(路径是engines/player/)。 ● 为Android的player适配器(路径是android/)。 ● 识别工具(路径是pvmi/recognizer)。 ● 编/解码工具中的OpenMax部分(路径是codecs_v2/omx)。 ● 对应几个插件Node的注册。 库中的内容比较多,其中主要为各个文件解析器和解码器。PVPlayer的核心功 能在文件engines/player/中实现,而文件android/Android

.mk的内容比较特殊,功能是 在PVPlayer上构建一个为Android使用的播放器。 3.库的结构 库是实现媒体流记录的功能库,其编译控制的文件的路径如下所示。

上述文件的主要代码如下所示。

x0c在库中包含了如下内容。 ● 编码工具,例如视频流H263、H264,音频流Amr。 ● 文件的组成器,例如MP4。 ● 编码工具对应的Node。 ● 用于媒体输入的Node(目录是nodes/pvmediainputnode/Android.m)。 ● author引擎(目录是engines/author/)。 ● Android的author适配器(目录是android/author/)。 库的内容主要由各个文件编码器和文件组成器构成,其中PVAuthor的核心功能 在engines/author/目录中,而文件android/author/是在PVAuthor之上构建的 一个为Android使用的媒体记录器。 4.其他库 除了前面介绍的4个库之外,在OpenCore中还有另外几个库,具体说明如下所示。 网络支持库libopencorenet_,对应的文件的路径如下所示。

MP4功能实现库和注册库,对应的文件的路径如 下所示。

RTSP功能实现库和注册库,对应的文件的路 径如下所示。

下载功能实现库和注册库,对应的 文件的路径如下所示。

x0c14.3.4 OpenCore OSCL OSCL是Operating System Compatibility Library(操作系统兼容库)的缩写,其中包含了一些不同 操作系统中移植层的功能,其代码结构如下所示。 oscl/oscl |-- config:配置的宏; |-- makefile; |-- ; |-- osclba:包含基本类型、宏以及一些STL容器类似的功能; |-- osclerror:错误处理的功能; |-- osclio:文件IO和Socket等功能; |-- oscllib:动态库接口等功能; |-- osclmemory:内存管理、自动指针等功能; |-- osclproc:线程、多任务通信等功能; |-- osclregcli:注册客户端的功能; |-- osclregrv:注册服务器的功能; `-- osclutil:字符串等基本功能。 在目录oscl中,通常用一个目录表示一个模块。OSCL对应的功能非常详细,几乎封装C语言中的每一 个细节功能,并且提供了C++接口供上层使用。其实在OperCore中的PVMF和Engine都在使用OSCL,整 个OperCore的调用者也需要使用OSCL。 在实现OSCL时,简单封装了很多典型的C语言函数,例如osclutil中与数学相关的功能在 oscl_中被定义为内嵌(inline)的函数,具体代码如下所示。

因为文件oscl_被oscl_math.h包含,所以其结果是oscl_log()等功能等价于原始的log()等 函数。 其实OSCL的实现比较复杂,很多C语言标准库的句柄都被定义成为C++类

的形式,实现起来会比较烦琐 。但是幸运的是复杂性不是很高。以oscllib为例,其代码结构如下所示。 oscl/oscl/oscllib/ |-- |-- build | `-- make | `-- makefile `-- src

x0c |-- oscl_library_common.h |-- oscl_library_ |-- oscl_library_list.h |-- oscl_shared_lib_interface.h |-- oscl_shared_ `-- oscl_shared_library.h 其中文件oscl_shared_library.h是提供给上层使用的动态库的接口功能,定义的接口代码如下所示 。

这些接口都与库的加载有关系,而在文件oscl_shared_中,其具体的功能通过使用函数 dlopen()等来实现。

x0c14.3.5 实现OpenCore中的OpenMax部分 在OpenCore中,OpenMax是作为插件来实现的,只要封装了OpenMax,就可以在OpenCore中使用标准的 OpenMax。 1.OpenMax结构 在OpenCore中,在如下目录的头文件中包含标准的OpenMax。

在文件build_config/opencore_dynamic/Android_omx_aacdec_中,主要在库 libomx_中声明了插件OpenMax,主要代码如下所示。

库libomx_是omx针对OpenCore的接口层库,也就是说在每个模拟器上 libomx_向外(即OpenCore)提供的接口是一致的。此库可以动态打开各个 OpenMax的编/解码模块,各个编/解码模块通过调用codecs_v2中audio和video目录中软件的编/解 码库来实现。 在opencore的根目录中有一个名为的文件,此文件用于实现OpenCore运行过程的动态配 置,此文件的主要代码如下所示。

2.OpenMax接口 在OpenCore中,OpenMax接口是通过封装标准的OpenMax IL层来构建的,这些接口的基本内容相同 ,但是不同于标准的OpenMax IL层的C语言接口。在OpenCore中和OpenMax接口相关的头文件如下所示 。 ● opencore/codecs_v2/omx/omx_mastercore/include/omx_interface.h:定义插件接口。 ● opencore/codecs_v2/omx/omx_common/include/pv_omxcore.h:核心定义。 ● opencore/codecs_v2/omx/omx_baclass/include/pv_omxcomponent.h:定义PV的OpenMax组件 。 文件omx_interface.h定义了OpenMax接口的核心功能,其中包含了各种函数指针的定义类型。

x0c上述函数指针是OpenMax的核心方法,这些指针类型需要使用继承来设置。 另外,在文件omx_interface.h中还定义了类OMXInterface,在类中包含了一系列函数,这些函数返 回的都是上面的类型的函数指针。类OMXInterface是OpenMax直接实现OpenCore的接口。

x0cx0cx0c3.OpenMax组织结构 在文件opencore/codecs_v2/omx/omx_sharedlibrary/interface/src/pv_omx_中实现 了前面介绍的类OMXInterface。具体是通过实现类里面的函数指针方式实现的。 在文件pv_omx_中,函数PVGetInterface()和PVReleaInterface()是使用C语言导出 的函数,这两个函数的实现代码如下所示。

在文件pv_omx_in

中,类PVOMXInterface继承了OMXInterface,在此类的构造函数中设置 了各个OMXInterface中的函数指针。构造函数PVOMXInterface()的主要代码如下所示。

x0c上面介绍的构造函数,都是在文件opencore/codecs_v2/omx/omx_common/src/pv_中实现 的,此文件实现了OpenMax的核心功能。 文件opencore/codecs_v2/omx/omx_common/src/pv_的功能是注册OpenMax模块,其 主要实现代码如下所示。

x0cx0c4.实现OpenMax编/解码组件 OpenMax的主要功能是通过编/解码组件实现的,各个组件的基本结构类似,它们的实现内容实际上 就是文件opencore/codecs_v2/omx/omx_baclass/include/pv_omxcomponent.h中定义的类 OmxComponentBa。假如要实现MP3格式文件的解码处理,则在如下目录中实现了MP3的解码功能。

在上述目录中,文件生成了名为libomx_mp3_component_的库,此静态库将被链接 生成动态库libomx_mp3dec_sharedlibrary_lib。此文件的主要代码如下所示。

x0c在目录opencore/codecs_v2/omx/omx_mp3/src/中保存了如下三个文件。 ● mp3_:能够调用MP3解码器组件。 ● mp3_:能够实现时间戳功能。 ● omx_mp3_:定义了MP3解码器组件。 在文件opencore/codecs_v2/omx/omx_mp3/include/omx_mp3_component.h中定义了类 OpenmaxMp3AO,此类继承了OmxComponentAudio,主要代码如下所示。

在文件omx_mp3_中定义了MP3解码器组件,通过函数ProcessData()实现MP3文件的解码 处理。函数ProcessData()的实现代码如下所示。

x0cx0cx0cx0cx0cx0cx0cx0c14.3.6 OpenCore的扩展 除了OpenCore本身提供的强大功能外,在使用过程中还可以对OpenCore进行扩展。 1.OpenCore Node 在扩展OpenCore时,一般是基于OpenCore的框架为其增加固定插件的,插件主要做成Node的形式。其 中和编/解码相关的Node如下所示。 pvomxbadecnode、pvomxaudiodecnode、pvomxvideod ecnode、pvomxencnode。 和文件格式相关的Node如下所示。 pvwavffparrnode、pvaacffparrnode、pvamrffparrnode、pvmp3ffparrn ode、 pvmp4ffparrnode、pvvideoparrnode、pvmp4f fcompornode。 和输入输出相关的Node如下所示。 pvmediainputnode、pvmediaoutputnode、pvdummyinput node、pvdummyoutputnode、 pvfileoutputnode、pvdownloadmanagernode。 除了上述Node外,还包括pvsocketnode和pvdownloadmanagernode等其他功能的Node。 2.MediaIO MediaIO的缩写是MIO,在opencore/pvmi/pvmf/include/目录中有如下头文件定义。 ● pvmiMIOControl.h ● pvmi_media_transfer.h 在实现的过程中只需要继承和构建其中的接口,然后由框架最终实现成为Node在OpenCore系统中使用 。其实MediaIO是对Node的一种封装,将其封装成多媒体的输入/输出环节。 在文件pvmi_mio_control.h中,定义类PvmiMIOControl来表示MIO的控制类接口,定义此类的代

码如 下所示。

x0c在上述代码中,有很多函数使用OsclAny类型的指针作为参数,这样做的好处是可以使用所有的数据 结构。其中接口Init()、Ret()、Start()、Pau()、Flush()和Stop()实现流控制,而函数 createMediaTransfer()用于得到类PvmiMediaTransfer。 而在文件pvmi_media_transfer.h中,定义类PvmiMediaTransfer来表示MIO的数据接口,定义此类的 代码如下所示。

x0c3.OpenCore Player OpenCore的Player的编译文件是pvplayer/,编译后将生成动态库文件 ,在此库中包含了如下两方面的内容。 ● Player的Engine(引擎)。 ● 为Android构建的Player,是一个适配器(Adapter),Engine的路径是 engine/player,Adapter的路径是android。 在库中包含了下面的内容。 ● 解码工具。 ● 文件的解析器。 ● 解码工具对应的Node。 ● Player的引擎部分,编译文件是engines/player/。 ● 为Android构建的Player适配器,编译文件是android/。 ● 识别工具,目录是pvmi/recognizer。 ● 编/解码工具中的OpenMAX部分,目录是codecs_v2/omx。 ● 对应插件Node的注册。 由此可见,库中的内容较多,其中主要功能为各个文件解析器和解码器。 PVPlayer的核心功能在文件engines/player/中。而文件android/的内容比较 特殊,功能是在PVPlayer之上构建的一个可以供Android使用的播放器。 库的具体结构如图14-5所示。

x0c图14-5 库的结构图 (1)Player Engine OpenCore中的Player Engine具有清晰明确的接口,不同的系统在此接口上可以根据具体情况实现不 同的Player。Player Engine位于OpenCore中的engines/player/目录下,其中在 engines/player/include目录中保存的是接口头文件,在engines/player/src目录中保存的是源文件 和私有头文件。Player Engine的类结构如图14-6所示。

x0c图14-6 Player Engine的类结构图 (2)PVPlayer PVPlayer的结构如图14-7所示。

图14-7 PVPlayer结构图 在图14-7中,Sink Node会接受上一个Node写的动作。 PVPlayer的类结构如图14-8所示。

x0c图14-8 PVPlayer的类结构图 (3)Author OpenCore的Author的编译文件是pvauthor/,编译后将生成动态库 ,此库和Player类似,包含了Author的Engine和为Android构建的Author两方 面内容。 OpenCore Author的基本结构如图14-9所示。

x0c图14-9 OpenCore Author的基本结构图 在图14-9所示的结构中,目录OpenCore/engines/author/是Author引擎目录,其中主要包含了 include和src两个目录。并且如下两个头文件中是接口。

OpenCore的Author主要功能文件是,类Author Engine的结构如图14-10所示。

x0c图14-10 类Author Engine的结构图 PVAuthor的结构如图14-

11所示。

图14-11 PVAuthor的结构图

x0c14.4 Stagefright引擎 在Android系统中,内置的多媒体框架是OpenCORE。OpenCORE的最大特点是跨平台,有较好的可移植 性,并且因为已经通过多方验证,所以相对来说比较稳定。但是其缺点也比较明显,例如庞大复杂 ,需要耗费较多的时间去维护。为了解决上述缺点,从Android 2.0开始,Android引入了架构稍微简 单的框架——Stagefright。随着Android后续新版本的陆续推出,Stagefright大有取代OpenCORE之 势。

x0c14.4.1 Stagefright代码结构 Stagefright是一个轻量级的多媒体框架,其主要功能是基于OpenMax实现的。在Stagefright中提供 了媒体播放等接口,这些接口可以为Android框架层所使用。 在Android开源代码中,Stagefright的头文件路径如下所示。

实现Stagefright功能的文件路径如下所示。

实现Stagefright播放器和录音器功能的文件路径如下所示。

测试Stagefrigh功能的代码路径如下所示。

x0c14.4.2 Stagefright实现OpenMax接口 Stagefright可以实现Android系统中的OpenMax接口,可以让Stagefright引擎内的OMXCode调用实现 的OpenMax接口,最终目的是使用OpenMax IL编/解码功能。 由此可见,在Android中是通过Stagefrigh来定义OpenMax接口的,具体实现内容保存在omx目录中。 在头文件media/libstagefright/include/OMX.h中实现了Android标准的IOMX类,此文件的主要代码 如下所示。

文件frameworks/ba/media/libstagefright/omx/是上述OMX.h的实现文件,定义函数 createRenderer()来创建映射,首先建立了一个hardware renderer——SharedVideoRenderer(),如果失败则建立software renderer——SoftwareRenderer (surface)。此函数的主要代码如下所示。

x0cx0c由此可见,OMXMaster是的真正实现者,并且能够管理OpenMax插件的类,这些功能是通过头 文件OMXMaster.h和源代码文件实现的。其中在文件 frameworks/ba/include/media/stagefright/OMXMaster.h中定义了类OMXMaster,主要代码如下所 示。

x0c在文件frameworks/ba/media/libstagefright/中,定义静态函数Create()将 MediaSource作为IOMX插件给OMXCode。函数Create()的主要代码如下所示。

x0cx0c14.4.3 Video Buffer传输流程 在Stagefright引擎中,播放视频的过程是处理Video Buffer的过程。在Stagefright中需要使用 VideoRenderer插件来处理Video Buffer。接下来将简要讲解使用插件来传输Video Buffer的流程。 step1 在一开始,OMXCodec会通过函数read()来传送未解码数据给decoder,并要求decoder回传解 码后的数据。对应的代码如下所示。

step2 Decoder从input port(输入点)获取资料,然后进行解码处理,并且通过回传 EmptyBufferDone的方式通知OMXCodec当前所要进行的工作。对应的代码如下所示。

x0cstep3 当OMXCodec接

收到EMPTY_BUFFER_DONE后,继续传送下一个未解码的资料给decoder。 Decoder解码后的资料送到output port(输出点),并且通过回传FillBufferDone的方式来通知 OMXCodec。对应的代码如下所示。

当OMXCodec收到FILL_BUFFER_DONE后,将解码后的资料放入mFilledBuffers,然后发出 mBufferFilled信号,并要求decoder继续发出资料。 step4 使用函数read()等待mBufferFilled信号,当mFilledBuffers被填入资料后,函数read()将 其指定给buffer,并回传给AwesomePlayer。对应的代码如下所示。

函数AwesomePlayer::onVideoEvent()除了通过OMXCodec::read取得解码后的资料外,还需要将这些 资料(mVideoBuffer)传给video renderer,以便在屏幕上显示。此功能的实现过程如下所示。 step1 在将mVideoBuffer中的资料输出之前,必须先建立mVideoRenderer。对应的代码如下所示。

x0cstep2 如果Video Decoder是OMX component,则需要建立一个AwesomeRemoteRenderer作为 mVideoRenderer。从上面步骤(1)中的代码来看,AwesomeRemoteRenderer的本质是由函数 OMX::createRenderer()创建的。函数createRenderer()先建立一个硬件渲染器 ——SharedVideoRenderer(在中定义),如果失败则建立软件渲染器 ——SoftwareRenderer(使用surface接口实现)。对应的代码如下所示。

step3 如果Video Decoder是软件组件,则需要建立一个AwesomeLocalRenderer来作为 mVideoRenderer。AwesomeLocalRenderer的建造者会呼叫本身的函数init(),其功能和函数 OMX::createRenderer()一样。对应的代码如下所示。

x0cstep4 建立mVideoRenderer后就可以开始将解码后的资料回传给它,对应的代码如下所示。

经过上述操作之后,Renderer的处理过程介绍完毕。在播放多媒体的时候,需要使用Audio来实现处 理功能。在Stagefright框架中,Audio的部分内容是由AudioPlayer来处理的,此功能在函数 AwesomePlayer::play_l()中被建立。接下来介绍使用Audio的基本流程。 step1 当要求播放影音时,会同时建立并启动AudioPlayer。对应的代码如下所示。

step2 在启动AudioPlayer的过程中会先读取第一笔解码后的资料,并开启Audio Output。对应的 代码如下所示。

x0c在上述代码中,AudioPlayer并没有将mFirstBuffer传给Audio Output。 step3 在开启Audio Output的同时,AudioPlayer将启用函数callback(),这样每当函数 callback()被呼叫AudioPlayer时,会从Audio Decoder(音频解码器)读取解码后的资料。对应的代 码如下所示。

由上述代码可以知道,读取解码后的音频资料工作是由函数callback()所驱动的,函数 fillBuffer()会将资料复制到数据中,复制后音频输出会回头取用这些数据。 第15章 传感器系统 在很多读者的眼中,传感器有点陌生。随着物联网概念的推出,传感器这一名词逐渐进入

了开发人员 的耳中。其实传感器在大家的日常生活中经常见到甚至用到,比如楼宇的楼梯灯,马路上的路灯等。 本章将详细讲解Android系统传感器系统的基本知识和移植方法,为学习本书后面的知识打下基础。

x0c15.1 传感器系统的结构 很多读者不明白手机里的传感器到底可以起到哪些作用。Android中提供了加速度传感器、磁场、方 向、陀螺仪、光线、压力、温度和接近等传感器。传感器系统会主动对上层报告传感器精度和数据的 变化,并且提供了设置传感器精度的接口,这些接口可以在Java应用和Java框架中使用。 Android传感器系统的基本层次结构如图15-1所示。

图15-1 传感器系统的层次结构 根据图15-1所示的结构,Android传感器系统从上到下分别是:Java应用层、Java框架对传感器的应 用、传感器类、传感器硬件抽象层、传感器驱动。传感器的系统结构如图15-2所示。

x0c图15-2 Android传感器的系统结构 图15-2中传感器结构的对应的代码结构如下所示。 (1)传感器系统的Java部分 代码路径是:frameworks/ba/include/core/java/android/hardware,对应的实现文件是 Sensor*.java。 (2)传感器系统的JNI部分 代码路径是:frameworks/ba/core/jni/android_hardware_,在此部分中提供 了对类的本地支持。 (3)传感器系统HAL层 头文件路径是:hardware/libhardware/include/hardware/nsors.h,传感器系统的硬件抽象层需 要具体的实现。 (4)驱动层 驱动层的代码路径是:kernel/driver/hwmon/$(PROJECT)/nsor。 在库中提供了以下8个API函数。 ● 控制方面:在结构体ensors_control_device_t中定义,包括如下函数。

x0c○ int (*open_data_source)(struct nsors_control_device_t *dev) ○ int (*activate)(struct nsors_control_device_t *dev, int handle, int enabled) ○ int (*t_delay)(struct nsors_control_device_t *dev, int32_t ms) ○ int (*wake)(struct nsors_control_device_t *dev) ● 数据方面:在结构体nsors_data_device_t中定义,包括如下函数。 ○ int (*data_open)(struct nsors_data_device_t *dev, int fd) ○ int (*data_clo)(struct nsors_data_device_t *dev) ○ int (*poll)(struct nsors_data_device_t *dev, nsors_data_t* data) ● 模块方面:在结构体nsors_module_t中定义,包括下面的一个函数。 ○ int (*get_nsors_list)(struct nsors_module_t* module, struct nsor_t const** list) 在Java层Sensor的状态控制是由SensorService来负责的,它的Java代码和JNI代码分别位于如下文件 中。

SensorManager负责在Java层Sensor的数据控制,它的Java代码和JNI代码分别位于如下文件中。

android framework中与nsor的通信是通过文件和实现的 。文件的具体通

信是通过JNI调用中的方法实现的。文件 的具体通信是通过JNI调用中的方法实现的。文件 和通过文件hardware.c与通信。其中文件 实现对nsor的状态控制,文件实现对nsor的数据控制。 库通过ioctl控制nsor driver的状态,通过打开nsor driver对应的设备文件读取Gnsor采集的数据。

x0c15.2 需要移植的内容 在Android传感器系统中,因为硬件抽象层接口之下都是非标准的,所以Android传感器系统的移植包 括移植传感器驱动程序和移植传感器硬件抽象层。Sensor硬件抽象层被Sensor JNI调用,而Sensor JNI被Java程序调用。由此可见,实现传感器系统的核心是实现硬件抽象层,Sensor的HAL必须满足硬 件抽象层的接口。在Sensor硬件抽象层中,使用Android的标准硬件模块接口,这是一种纯C语言接口 ,需要依靠函数和指针来实现。 Android系统中的Sensor驱动程序是非标准的,只是为了满足硬件抽象层而推出的。

x0c15.2.1 移植驱动程序 因为Android系统中的Sensor驱动程序是非标准的,所以在构建Sensor驱动程序时也是没有标准可以 遵循的。我们编写的Sensor驱动程序的目的是从硬件中获取传感器的信息,并通过接口将这些信息传 递给上层。在现实应用中,我们可以通过如下接口来实现Sensor驱动程序。 ● 使用Event设备:因为传感器本身就是一种获取信息的工具,所以使用Event设备非常自然。通过 使用Event设备,可以实现用于阻塞poll调用,在中断到来的时候将poll解除阻塞,然后通过red调用 将数据传递给用户空间。当使用Event设备时,可以使用input驱动框架中定义的数据类型。 ● 使用Misc杂项字符设备:和使用Event设备方式类似,可以直接实现file_operations中的read、 poll、和ioctl接口来实现对应的功能。 ● 实现一个字符设备的主设备:和上面的使用Misc杂项字符设备方式相同。 ● 使用SYS文件系统:可以实现基本的读、写功能,对应驱动中的show和store接口实现。虽然使用 SYS文件系统可以实现阻塞,但是通常不这样做。

x0c15.2.2 移植硬件抽象层 在Sensor传感器系统中,HAL层的实现文件目录如下所示。

其中的文件代码如下所示。

在此需要注意对LOCAL_MODULE赋值,这里的模块名字都是定义好了的,具体可以参考文件 hardware/libhardware/hardware.c。 在文件nsors.h中实现了Sensor传感器系统硬件层的接口,这是一个标准的Android硬件模块,其中 定义了如下几个重要的结构体。 (1)通过结构体nsor_module_t来定义nsor模块,代码如下所示。

其中get_nsors_list()用来获得传感器列表。 (2)定义

nsor_t,用于表示对一个传感器的描述,代码如下所示。

(3)定义结构体nsors_data_t来表示传感器的数据,代码如下所示。

x0c除了上述结构体外,还有两个十分重要的结构体,分别是nsors_control_device_t和 nsors_data_device_t,它们都实现了对函数指针的定义。这两个结构体的代码如下所示。

x0c15.2.3 实现上层部分 Sensor传感器系统的上层部分包含如下三个模块。 ● 传感器的JNI部分和Java框架。 ● 在Java Framework中调用传感器。 ● 在应用程序中调用传感器。 接下来将简要介绍实现上述三个模块的基本流程。 (1)实现传感器的JNI部分和Java框架部分 Android中Sensor传感器系统的JNI部分的实现文件如下所示。

上述文件提供了对类的本地支持。此文件是Sensor的Java部分和硬 件抽象层的接口,Sensor的JNI部分将直接调用硬件抽象层,其中包含了本地的 hardware/nsors.h。文件com_android_rver_和 android_hardware_SensorManager. cpp联合使用,通过文件 androidhardwarelibhardwarehardware.c与实现通信。 ● 文件android_hardware_ 首先实现方法注册,其中包含了文件中的功能方法。主要代码如下所示。

定义函数nsors_module_init(),功能是调用函数hw_get_module()打开Sensor的硬件模块。定义函 数nsors_module_init()的代码如下所示。

使用函数nativeClassInit()来初始化Java中的Sensor类,此函数的实现代码如下所示。

x0c定义函数nsors_data_poll(),此函数实现了Senso JNI的核心内容,主要代码如下所示。

在上述nsors_data_poll()函数中,向上层传递了传感器数据、精度和日期三个变量,这是三个浮 点型数据。例如在一个加速度传感器中,传递的三个参数是三个方向的加速度。而对于温度传感器来 说,第一个数据是温度信息,而第二个和第三个是无用的。 (2)在Java Framework中调用传感器的部分 Sensor传感器系统的Java部分在如下目录中定义。

在上述目录中主要包含如下文件。 ● :实现传感器系统的管理类SensorManager。 ● :描述了一个单一的传感器,此类是通过SensorManager实现的。类Sensor的初始化 工作是在SensorManager JNI代码中实现的,在中维护了一个Sensor列表。 ● :实现传感器系统的事件类SensorEvent。 ● :通过在中注册可以监听特定类型的nsor传来 的数据。 SensorManager、SensorEvent和Sensor是三个类,而nsorEventListener和nsorListener是两个 接口,上述文件都是Android系统的API接口。

x0c定义类Sensor的实现代码如下所示。

x0c在上述代码中,用数字1~8来表示TYPE_格式的常量,分别

表示在Android系统中可以支持的传感器类 型,而最后一行TYPE_ALL=-1表示支持所有的类型。 定义类SensorEvent的代码如下所示。

由此可见,类SensorEvent比Sensor增加了values、accuracy和timestamp。 类SensorManager是Sensor系统的核心,主要代码如下所示。

x0c(3)在应用程序中调用传感器 在Java应用层中可以调用SensorManager,通常通过SensorEventListener注册回调函数的方式实现对 Sensor系统的调试。

x0c15.3 在模拟器中实现传感器 本章前面内容讲解了Android传感器系统的基本知识,并且分析了主要源代码。本节将讲解在模拟器 中实现Sensor硬件抽象层的方法。 在Android系统中,为模拟器提供了一个Sensor硬件抽象层实例,此实例本身具有强大的功能,读者 在此实例的基础上进行简单修改即可实现自己需要的功能。 在Donut及其以前的版本中,Sensor硬件抽象层的代码路径如下所示。

在Eclair及其以后的版本中,Sensor硬件抽象层的代码路径如下所示。

无论是什么版本,在上述目录中都会包含如下两个文件。 ● ● nsors_qemu.c 经过编译以后会形成(goldfish表示产品名),这是一个单独的模块,将被放 在标准文件系统的system/lib/hw目录中,在运行时将作为一个硬件被加载。 在文件nsors_qemu.c中定义所需常量的代码如下所示。

定义传感器链表的代码如下所示。

x0c在上述代码中,SENSOR_(x,y)是一个宏,功能是创建一个传感器描述的数据结构,在上述代码中,分 别创建了加速度、磁场、方向和温度4个传感器。 在文件nsors_qemu.c中通过如下代码打开传感器硬件模块。

在上述代码中,函数open_nsor()是一个打开函数,用于构建Sensor控制设备和数据设备。定义函 数open_nsor()的代码如下所示。

定义结构体nsors_module_t,功能是增加这里的传感器的私有数据作为上下文。此结构体的代码如 下所示。

x0c定义数组sSensorListInit,此数组是一个nsor_t类型的数组,其中列出了系统可以支持的传感器 。定义此数组的代码如下所示。

x0c上述代码在硬件抽象层中定义了4个传感器,分别是加速度、磁场、方向和温度。 文件nsors_qemu.c的核心功能是通过函数data__poll()实现的,主要代码如下所示。

x0cx0cx0c在上述代码中,先使用if语句分类读取传感器中的数据,然后为SensorData结构赋值。读者需要注意 的是,上述代码实例只是仿真演示,所以读取信息的内容是来自软件的Buffer,设置结果需要通过数 据结构nsor_data_t来体现。 第16章 照相机系统 在当前手机(无论是智能手机还是普通的手机)系统中,基本上都支持手机拍照和录制视频功能,这 些功能都是通过手机上的摄像头实现的。在Android

系统中,上述照相机功能是通过Camera系统实现 的。本章将详细讲解Android平台中Camera系统的基本知识和移植方法,为学习本书后面的知识打下 基础。

x0c16.1 Camera系统的结构 Camera作为一个照相机系统,应该提供取景器、视频录制和拍摄相片等功能,并且还具有各种控制类 的接口。在Camera系统中提供了Java层的接口和本地接口。其中Java框架中的Camera类实现了Java层 相机接口,为照相机和扫描类使用。而Camera的本地接口可以被本地程序调用,作为视频输入环节应 用于摄像机和视频电话领域。 Android照相机系统的基本层次结构如图16-1所示。

图16-1 照相机系统的层次结构 Android的Camera系统包括了Camera驱动程序层、Camera硬件抽象层、AudioService、Camera本地库 、Camera的Java框架类和Java应用层对Camera系统的调用。Android中Camera的系统结构如图16-2所 示。

x0c图16-2 Camera的系统结构 图16-2中各个构成层次的具体说明如下所示。 (1)Camera的Java程序,代码路径如下所示。

其中文件是主要实现的文件,对应的Java层次的类是,这个类 和JNI中定义的类是同一个,有些方法通过JNI的方式调用本地代码得到,有些方法自己实现。 (2)Camera的Java本地调用部分(JNI),代码路径如下:

这部分内容编译成libandroid_,主要的头文件在以下的目录中:

(3)Camera本地框架,其中头文件路径如下:

或:

源代码路径如下:

或:

x0c这部分的内容被编译成库或libcamera_。 (4)Camera服务部分,代码路径如下:

这部分内容被编译成库。 为了实现一个具体功能的Camera,在底层还需要一个硬件相关的Camer库(例如通过调用video for linux驱动程序和JPEG编码程序实现)。这个库将被Camera的服务库调用。 (5)摄像头驱动程序。 此部分是基于Linux的Video for Linux视频驱动框架。 (6)硬件抽象层。 硬件抽象层中的接口代码路径如下:

或:

其中的核心文件是CameraHardwareInterface.h。 在Camera系统的各个库中,库位于核心的位置,它对上层提供的接口主要是Camera类,类 libandroid_通过调用Camera类提供对Java的接口,并且实现了re. camera类。 库是Camera的服务器程序,它通过继承的类实现服务器的功能,并且 与中的另外一部分内容通过进程间通信(即Binder机制)的方式进行通信。 库libandroid_和是公用库,其中除了Camera外还有其他方面的功能。 Camera部分的头文件被保存在frameworks/ba/include/ui/目录中,此目录和库的源文 件目录frameworks/ba/libs/ui/是相对应的。 在Camera中

主要包含如下头文件。 ● ICameraClient.h ● Camera.h ● ICamera.h ● ICameraService.h ● CameraHardwareInterface.h 文件Camera.h提供了对上层的接口,而其他的几个头文件都提供了一些接口类(即包含了纯虚函数的 类),这些接口类必须被实现类继承才能够使用。 当整个Camera在运行的时候,可以大致上分成Client和Server两个部分,分别在两个进程中运行,它 们之间使用Binder机制实现进程间通信。这样在客户端调用接口时,功能在服务器中实现。但是在客 户端中调用就好像直接调用服务器中的功能,进程间通信的部分对上层程序不可见。 从框架结构上来看,文件ICameraService.h、ICameraClient.h和ICamera.h中的三个类定义了 Camera的接口和架构,文件和用于实现Camera架构,Camera的具体功 能在下层调用硬件相关的接口来实现。

x0c16.2 需要移植的内容 在Android系统中,Camera系统的标准化部分是硬件抽象层接口,所以我们在某平台移植Camera系统 时,主要工作是移植Camera驱动程序和Camera硬件抽象层。 在Linux系统中,Camera驱动程序使用了Linux标准的Video for Linux 2(V4L2)驱动程序。无论是 内核空间还是用户空间,都使用V4L2驱动程序框架来定义数据类和控制类。所以在移植Android中的 Camera系统时,也使用标准的V4L2驱动程序作为Camera的驱动程序。 Camera的硬件抽象层是V4L2和CameraService之间的接口,是一个C++接口类,我们需要具体的实现者 来继承这个类,并且实现其中的虚函数。Camera的硬件抽象层需要具备取景器、视频录制、相片拍摄 等功能。在Camera系统中,具体任务分配如下所示。 ● V4L2驱动程序:任务是获得Video数据。 ● Camera的硬件抽象层:任务是将纯视频流和取景器、实现预览、向上层发送数据等功能组织起来 。 ● 其他算法库和硬件:任务是实现自动对焦和成像增强等功能。

x0c16.3 移植和调试 本章前面内容讲解了Camera系统的基本结构和我们需要移植的任务。本节内容中,将详细讲解在 Android平台中移植和调试Camera系统的方法。

x0c16.3.1 V4L2驱动程序 在Camera系统中,V4L2驱动程序的任务是获得Video数据。 1.V4L2 API V4L2是V4L的升级版本,为Linux下视频设备程序提供了一套接口规范,包括一套数据结构和底层 V4L2驱动接口。V4L2驱动程序向用户空间提供字符设备,主设备号是81。对于视频设备来说,次设备 号是0~63。次设备号在64~127之间的是Radio设备,次设备号在192~223之间的是Teletext设备 ,次设备号在224~255之间的是VBI设备。 V4L2中常用的结构体在内核文件include/linux/videodev2.h中定义,代码如下所示。

常用的ioctl接口命令也在文件include/linux/videodev

2.h中定义,代码如下所示。

2.操作V4L2的流程 在V4L2中提供了很多访问接口,我们可以根据具体需要选择操作方法。不过需要注意的是,很少有驱 动完全实现了所有的接口功能。所以在使用时需要参考驱动源代码,或仔细阅读驱动提供者的使用说 明。接下来将简单列举一种V4L2的操作流程供读者参考。 step1 打开设备文件,代码如下。

如果需要使用非阻塞模式调用视频设备,当没有可用的视频数据时不会阻塞,而会立刻返回。 step2 获取设备的capability,代码如下。

x0c在此需要查看设备具有什么功能,比如是否具有视频输入特性。 step3 选择视频输入,代码如下。

每一个视频设备可以有多个视频输入,如果只有一路输入,则可以没有这个功能。 step4 检测视频支持的制式,代码如下。

step5 设置视频捕获格式,代码如下。

step6 向驱动申请帧缓存,代码如下。

在结构v4l2_requestbuffers中定义了缓存的数量,驱动会根据这个数量申请对应数量的视频缓存。 通过多个缓存可以建立FIFO,这样可以提高视频采集的效率。 step7 获取每个缓存的信息,并mmap到用户空间。代码如下。

x0cstep8 开始采集视频,代码如下。

step9 取出FIFO缓存中已经采样的帧缓存,代码如下。

通过上述代码,可以根据返回的找到对应的mmap映射好的缓存,实现取出视频数据的功能 。 step10 将刚刚处理完的缓冲重新放入队列尾,这样可以循环采集,代码如下。

step11 停止视频的采集,代码如下。

step12 关闭视频设备,代码如下。

3.V4L2驱动框架 在上述使用V4L2的流程中,各个操作都需要有底层V4L2驱动的支持。在内核中有一些非常完善的例子 。例如在Linux 2.6.26内核目录/drivers/media/video//zc301/中,文件zc301_core.c实现了

x0cZC301视频驱动代码。 (1)V4L2驱动注册、注销函数 在Video核心层文件drivers/media/video/videodev.c”中提供了注册函数,代码如下所示。

● video_device:要构建的核心数据结构。 ● Type:表示设备类型,此设备号的基地址受此变量的影响。 ● Nr:如果end-ba>nr>0,次设备号=ba(基准值,受type影响)+nr;否则将系统自动分配合 适的次设备号。 我们具体需要的驱动只需构建video_device结构然后调用注册函数即可。例如在文件zc301_core.c中 的如下实现代码。

在Video核心层文件drivers/media/video/videodev.c中提供了如下注销函数。

(2)构建struct video_device 在结构video_device中包含了视频设备的属性和操作方法,具体可以参考文件zc301_core.c,代码如 下所示。

在上述ZC0301的驱动中并没有实现struct video_device中的很多操作函数,例如vidioc_querycap、 vidioc_g_fmt_cap,

这是因为在struct file_operations zc0301_fops中的zc0301_ioctl实现了前面 的所有ioctl操作,所以无须在struct video_device再次实现struct video_device中的操作。 另外也可以使用下面的代码来构建struct video_device。

x0c结构video_ioctl2是在文件videodev.c中实现的,在video_ioctl2中会根据ioctl不同的cmd来调用 video_device中的操作方法。 4.实现Video核心层 具体实现代码请参考内核文件/drivers/media/videodev.c,实现流程如下所示。 step1 注册256个视频设备,代码如下所示。

在上述代码中注册了256个视频设备和video_class类,video_fops为这256个设备共同的操作方法。 step2 实现V4L2驱动的注册函数,代码如下所示。

x0cx0c从上面的注册函数代码中可以看出,注册V4L2驱动的过程只是创建了设备节点,例如 /dev/video0,并且保存了video_device结构指针。 step3 打开视频驱动。 使用下面的代码在用户空间调用open()函数打开对应的视频文件。

对应/dev/video0目录的文件操作结构是在文件/drivers/media/videodev.c中定义的video_fops。代 码如下所示。

上述代码只是实现了open操作,后面的其他操作需要使用video_open()来实现,代码如下所示。

x0cx0c16.3.2 硬件抽象层 在Android 2.1及其以前的版本中,Camera系统的硬件抽象层的头文件保存在如下目录中。

在Android 2.2及其以后的版本中,Camera系统的硬件抽象层的头文件保存在如下目录中。

在上述目录中主要包含了如下头文件。 ● CameraHardwareInterface.h:其中定义了C++接口类,此类需要根据系统的情况来实现继承。其 中的核心文件是。 ● CameraParameters.h:其中定义了Camera系统的参数,可以在本地系统的各个层次中使用这些参 数。 ● Camera.h:其中提供了Camera系统本地对上层的接口。 1.Android 2.1及其以前的版本 在Android 2.1及其以前的版本中,在文件CameraHardwareInterface.h中首先定义了硬件抽象层接口 的回调函数类型,对应代码如下所示。

然后定义类CameraHardwareInterface,在类中定义了各个接口函数。代码如下所示。

x0c可以将上述代码中的接口分为如下几类。 ● 取景预览:startPreview、stopPreview、uOverlay和tOverlay。 ● 录制视频:startRecording、stopRecording、recordingEnabled和releaRecordingFrame。 ● 拍摄照片:takePicture和cancelPicture。 ● 辅助功能:autoFocus(自动对焦)、tParameters和getParameters。 2.Android 2.2及其以后的版本 在Android 2.2及其以前的版本中,在文件Camera.h中首先定义了通知信息的枚举值,对应代码如下 所示。

x0c然后在文件CameraHardwareInterface.h中定义如下三个回调函数。

然后定义类CameraHardwareInterface,在类中的各个函数和其他Android版本的相同。区别是回调函 数不再由

各个函数分别设置,所以在startPreview和startRecording缺少了回调函数的指针和 void*类型的附加参数。主要代码如下所示。

x0c因为在新版本的Camera系统中增加了ndCommand(),所以需要在文件Camera.h中增加新命令和返回 值。具体代码如下所示。

3.实现Camera硬件抽象层 在startPreview()的实现中,保存预览回调函数并建立预览线程。在预览线程的循环中,等待视频数 据的到达;视频帧到达后调用预览回调函数,将视频帧送出。

x0c取景器预览的主要步骤如下所示。 step1 在初始化的过程中,建立预览数据的内存队列(多种方式)。 step2 在startPreview()中建立预览线程。 step3 在预览线程的循环中,等待视频数据到达。 step4 视频到达后使用预览回调机制将视频向上传送。 过程不需要使用预览回调函数,可以直接将视频数据输入到Overlay上。如果使用Overlay实现取景器 ,则需要有以下两个变化。 ● 在tOverlay()函数中,从ISurface接口中取得Overlay类。 ● 在预览线程的循环中,不使用预览回调函数,直接将数据输入到Overlay上。 录制视频的主要步骤如下所示。 step1 在startRecording()的实现(或者在tCallbacks)中保存录制视频回调函数。 step2 录制视频可以使用自己的线程,也可以使用预览线程。 step3 通过录制回调函数将视频帧送出。 当调用releaRecordingFrame()后,表示上层通知Camera硬件抽象层,这一帧的内存已经用完,可 以进行下一次的处理。如果在V4L2驱动程序中使用原始数据(RAW),则视频录制的数据和取景器预 览的数据为同一数据。当调用releaRecordingFrame()时,通常表示编码器已经完成了对当前视频 帧的编码,对这块内存进行释放。在这个函数的实现中,可以设置标志位,标记帧内存可以再次使用 。 由此可见,对于Linux系统来说,摄像头驱动部分大多使用Video for Linux 2 (V4L2)驱动程序 ,在此处主要的处理流程如下所示。 step1 如果使用映射内核内存的方式(V4L2_MEMORY_MMAP),则构建预览的内存MemoryHeapBa需 要从V4L2驱动程序中得到内存指针。 step2 如果使用用户空间内存的方式(V4L2_MEMORY_USERPTR),则MemoryHeapBa中开辟的内存 是在用户空间建立的。 step3 在预览的线程中,使用VIDIOC_DQBUF调用阻塞等待视频帧的到来,处理完成后使用 VIDIOC_QBUF调用将帧内存再次压入队列,然后等待下一帧的到来。

x0c16.4 实现Camera系统的硬件抽象层 在Android系统中已经实现了一个Camera硬件抽象层的“桩”,这样可以根据“宏”来配置。此“桩 ”使用假的方式实现取景器预览和照片拍摄功能。在Camera系统的“桩”实现中使用黑白格子来代替 来自硬件的视频流,这

样可以在不接触硬件的情况下让Camera系统不用硬件也可以运行。因为没有视 频输出设备,所以不会使用Overlay来实现Camera硬件抽象层的“桩”。

x0c16.4.1 Java程序部分 在文件packages/apps/Camera/src/com/android/camera/中已经包含了对Camera的调用 。在文件中包含了对包的引用,具体引用代码如下所示。

然后定义类Camera,此类继承了活动Activity类,在它的内部包含了一个re. Camera。对应代码如下所示。

调用Camera功能的代码如下所示。

在类Camera中,大部分代码使用JNI调用下层得到,例如下面的代码。

还有下面的代码。

在上面的两段代码中,两个tPreviewDisplay参数不同,后一个是本地方法,参数为Surface类型 ,前一个通过调用后一个实现,但自己的参数以SurfaceHolder为类型。

x0c16.4.2 Camera的Java本地调用部分 Camera的Java本地调用(JNI)部分在如下文件中实现。

在文件android_hardware_中定义了一个JNINativeMethod(Java本地调用方法)类型的数 组gMethods,具体代码如下所示。

JNINativeMethod的第一个成员是一个字符串,表示Java本地调用方法的名称,此名称是在Java程序 中调用的名称;第二个成员也是一个字符串,表示Java本地调用方法的参数和返回值;第三个成员是 Java本地调用方法对应的C语言函数。 通过函数register_android_hardware_Camera()将gMethods注册为类android/media/Camera,对应的 实现如下所示。

其中类android/hardware/Camera和Java类相对应。

x0c16.4.3 Camera的本地库 文件frameworks/ba/libs/ui/的功能是实现文件Camera.h中提供的接口,其中最重要的 代码片段如下所示。

在上述代码中,通过如下调用代码得到一个名为的服务,此调用返回值的类型是 IBinder,根据实现将其转换成类型ICameraService使用。

函数connect()的实现代码如下所示。

函数connect()通过调用getCameraService得到一个ICameraService,再通过ICameraService的cs>connect(c)得到一个ICamera类型的指针。调用connect()函数会得到一个Camera类型的指针。在正 常情况下,已经初始化完成了Camera的成员mCamera。 函数startPreview()的实现代码如下所示:

x0c其他函数的实现过程也与函数tDataSource类似。在库中的其他一些文件与头文件的名 称相同,分别是:

此处的类BnCameraClient和BnCameraService虽然实现了onTransact()函数,但是由于还有纯虚函数 没有实现,所以不能实例化这个类。

x0c16.4.4 Camera服务 目录frameworks/ba/camera/libcamerarvice/实现一个Camera的服务,此服务是继承 ICameraService的具体实现。在此目录下和硬件抽象层“桩”实现相关的文件说明如下所示。 ● Cam

:Camera硬件抽象层“桩”实现。 ● CameraHardwareStub.h:Camera硬件抽象层“桩”实现的接口。 ● CannedJpeg.h:包含一块JPEG数据,在拍照片时作为JPEG数据。 ● FakeCamera.h和:实现假的Camera黑白格取景器效果。 在文件中,使用宏USE_CAMERA_STUB决定是否使用真的Camera。如果宏为真,则使用 和构造一个假的Camera,如果为假,则使用 构造一个实际上的Camera服务。文件的主要代码如下所示。

文件继承了BnCameraService的实现,在此类内部又定义了类 Client,CameraService::Client继承了BnCamera。在运作的过程中,函数 CameraService::connect()的功能是得到一个CameraService::Client。在使用过程中,主要是通过 调用这个类的接口来实现完成Camera的功能。因为CameraService::Client本身继承了BnCamera类 ,而BnCamera类继承了ICamera,所以可以将此类当成ICamera来使用。 类CameraService和CameraService::Client的结果如下所示。

x0c在CameraService中,静态函数instantiate()用于初始化一个Camera服务,此函数的代码如下所示。

函数CameraService::instantiate()注册了一个名为的服务,此服务和文件 中调用的名称相对应。 Camera整个运作机制是:在文件中调用ICameraService的接口,此时实际上调用的是 BpCameraService。而BpCameraService通过Binder机制和BnCameraService实现两个进程的通信。因 为BpCameraService的实现就是此处的CameraService,所以虽然是在另外一个进程中运行 的,但是调用ICameraService的接口就像直接调用一样,从函数connect()中可以得到一个ICamera类 型的指针,整个指针的实现实际上是CameraService::Client。 上述Camera功能的具体实现就是CameraService::Client所实现的,其构造函数如下所示。

在构造函数中,通过调用openCameraHardware()得到一个CameraHardwareInterface类型的指针,并 作为其成员mHardware。以后对实际的Camera的操作都通过这个指针进行,这是一个简单的直接调用 关系。 其实真正的Camera功能已经通过实现CameraHardwareInterface类来完成了。在这个库中,文件 CameraHardwareStub.h和定义了一个“桩”模块的接口,可以在没有 Camera硬件的情况下使用。例如对于仿真器的情况使用的文件就是和它依赖 的文件。 类CameraHardwareStub的结构如下所示。

在类CameraHardwareStub中包含了线程类PreviewThread,此线程可以处理PreView,即负责刷新取景 器的内容。实际的Camera硬件接口通常可以通过对V4L2捕获驱动的调用来实现,同时还需要一个 JPEG编码程序将从驱动中取出的数据编码成JPEG文件。 在文

件FakeCamera.h和中实现了类FakeCamera,用于实现一个假的摄像头输入数据的 内存。定义代码如下所示。

x0c当在CameraHardwareStub中设置参数后会调用函数initHeapLocked(),此函数的实现代码如下所示。

x0c定义函数startPrevie()来创建一个线程,此函数的实现代码如下所示。

通过上面建立的线程可以调用预览回调机制,将预览的数据传递给上层的CameraService。

x0c创建预览线程函数previewThread(),建立一个循环以得到假的摄像头输入数据的来源,并通过预览 回调函数将输出传到上层中去。函数previewThread()的主要代码如下所示。

在上述文件中还定义了其他的函数,函数的具体功能一看名字便知,为节省本书篇幅将不再一一进行 详细讲解,请读者参考开源的代码文件。

x0c16.5 MSM平台实现Camera系统 在MSM平台中,和Camera系统相关的文件如下所示。 ● drivers/media/video/msm/msm_v4l2.c:V4L2驱动程序的入口文件。 ● drivers/media/video/msm/msm_camera.c:公用库函数。 ● drivers/media/video/msm/s5k3e2fx.c:摄像头传感器驱动文件,使用i2c接口控制。 文件msm_camera.h是和摄像头相关的头文件,其中定义了各种额外的ioctl命令,主要代码如下所示 。

x0c文件msm_camera.c用于辅助实现Camera系统的功能,其中包含了供内核调用的文件,也提供了给用户 空间的接口。其中在用户空间的设备节点就是dev/msm_camera/中的三个设备:配置设备config0、控 制设备control0和帧数据设备frame0,上面的ioctl命令都是为这些设备节点使用的。 在文件msm_camera.c中为内核空间提供了接口,主要代码如下所示。

MSM平台中的Camera硬件抽象层已经包含在Android代码中,此部分的内容保存在如下文件中。 ● 文件hardware/msm7k/libcamera/camera_ifc.h:定义Camera接口中的常量。 ● 文件hardware/msm7k/libcamera/QualcommCameraHardware.h:硬件抽象层的头文件。 ● 文件hardware/msm7k/libcamera/:硬件抽象层的实现。 在文件QualcommCameraHardware.h中定义了类MemPool,此类表示一个内存。类AshmemPool和 PmemPool是MemPool的继承者,PreviewPmemPool和RawPmemPool是MemPool的继承者。实现代码如下所 示。

x0cx0cx0c16.6 OMAP平台实现Camera系统 在OMAP平台中,可以使用高级的ISP(图像信号处理)模块通过外接(i2c方式连接)的Camera Sensor驱动来获取视频帧的数据。 OMAP平台中Camera系统相关的文件保存在如下目录中。

此目录主要由如下三部分组成。 ● Vedio for Linux 2设备:实现文件是omap34xxcam.h和omap34xxcam.c。 ● ISP:实现文件是isp目录中的isp.c、isph3a.c、isppreview.c、ispresizer.c,提供了通过 ISP进行的3A、预览、改变尺寸等功能。 ● Camera Sensor驱动:lv8093.c或imx046.c,使

用v4l2-int-device结构来注册。 在文件omap34xxcam.c中通过v4l2_int_master定义了v4l2_int主设备,对应的代码如下所示。

还需要定义omap34xxcam_fops来注册video中的v4l2_file_operations结构,定义代码如下所示。

另外还需要通过文件lv8093.c或imx046.c实现Camera系统的传感器功能,并连接在系统的i2c总线上 。通过结构v4l2-int-device从设备进行注册,在运行时被文件omap34xxcam.c直接调用。 OMAP平台的Camera硬件抽象层是基于OMAP的V4L2驱动程序实现的,并调用Overlay系统作为视频输出 ,所以Camera硬件抽象层的uOverlay()的返回值是true。为了提高性能,需要直接映射Overlay中 的内存以作为Camera输出的内存。当在OMAP的Camera硬件抽象层中调用V4L2驱动程序的时候,需要使 用V4L2_MEMORY_USERPTR标识来表示来自用户空间的内存。 在OMAP平台的Camera硬件抽象层中可以使用自动对焦AutoFocus、自动增强AutoEnhance和自动平衡 AutoWhiteBalance等增强型功能。上述增强型功能是通过OMAP SOC内部的ISP模块提供的基本机制实 现的,算法部分功能是被用户空间库支持的。 第17章 Wi-Fi系统、蓝牙系统和GPS系统 本章将讲解Android平台中非常重要的三大驱动系统,分别是Wi-Fi系统、蓝牙系统和GPS系统。之所 以将上述三个系统放在同一章中进行讲解,是因为这三个系统都和连接有关。在本章的内容中,将详 细讲解上述三大驱动系统的基本知识和移植方法,为学习本书后面的知识打下基础。

x0c17.1 Wi-Fi系统 Wi-Fi是一种可以将个人电脑、手持设备(如PDA、手机)等终端以无线方式互相连接的技术。WiFi是一个无线网络通信技术的品牌,为Wi-Fi联盟所持有,目的是改善基于IEEE 802.11标准的无线网 络产品之间的互通性。

x0c17.1.1 Wi-Fi系统的结构 Wi-Fi系统的上层接口包括数据部分和控制部分。数据部分通常是一个和以太网卡类似的网络设备 ,控制部分用于实现接入点操作和安全验证处理。 在软件层,Wi-Fi系统包括Linux内核程序和协议,还包括本地部分、Java框架类。Wi-Fi系统向 Java应用程序层提供了控制类的接口。 Android平台中Wi-Fi系统的基本层次结构如图17-1所示。

图17-1 Wi-Fi系统的层次结构 由图17-1可知,Android平台中Wi-Fi系统从上到下主要包括Java框架类、Android适配器库、 wpa_supplicant守护进程、驱动程序和协议,这几部分的系统结构如图17-2所示。

x0c图17-2 Wi-Fi的系统结构 图17-2中各个部分的具体说明如下所示。 (1)Wi-Fi用户空间的程序和库,对应路径如下所示。

在此生成库和守护进程wpa_supplicant。 (2)Wi-Fi管理库,即适配器库,通过调用库成为wpa_supplicant在Android中的客 户端。对应路径如下所示。

(3)JNI部

分的对应路径如下所示。

(4)Java框架部分的对应路径如下所示。

x0c在将作为Android平台的API供Java应用程序层使用。 (5)Wi-Fi Settings应用程序的对应路径如下所示。

x0c17.1.2 需要移植的内容 我们先看Wi-Fi在Android中是如何工作的:Android使用一个修改版wpa_supplicant作为daemon来控 制Wi-Fi,代码位于如下目录中。

wpa_supplicant是通过socket与文件hardware/libhardware_legacy/wifi/wifi.c进行通信的。UI通 过 package(frameworks/ba/wifi/java/android/net/wifi/)发送命令给文件 wifi.c。相应的JNI实现位于文件frameworks/ba/core/jni/android_net_wifi_中,更高 一级的网络管理位于如下目录中。

在Android中的无线局域网部分是标准的系统,并且针对特定的硬件平台,所以需要移植和改动的内 容并不多。 在Linux内核中有Wi-Fi的标准协议,不同硬件平台的差异仅仅体现在Wi-Fi芯片驱动程序。除了这些 芯片级驱动的差异外,在Android中实现其他无线局域网部分的方法在Linux内核中已经给出了具体方 法。 而在Android用户空间中,使用了标准的wpa_supplicant守护进程,这也是一个标准的实现,所以无 须为Wi-Fi增加单独的硬件抽象层代码,只需进行简单的配置工作即可。

x0c17.1.3 移植和调试 1.本地实现 本地实现部分主要包括wpa_supplicant及wpa_supplicant适配层。WPA是Wi-Fi Protected Access的 缩写,中文含义为“Wi-Fi网络安全存取”。WPA是一种基于标准的可互操作的WLAN安全性增强解决方 案,可大大增强现有及未来无线局域网系统的数据保护和访问控制水平。 在移植过程中,需要特别注意wpa_supplicant,这是一个标准的开源项目,已经被移植到很多平台上 。我们比较关心的是wpa_supplicant在接收到上层的命令后是怎么将命令发给DRIVER的。DRIVER在接 收到命令后的解析动作,以及之后调用驱动功能函数的流程及驱动对寄存器控制的细节。由于需要注 意代码保密,所以之后不会提及具体使用了哪块Wi-Fi芯片,也不会提及此Wi-Fi DRIVER是在什么平 台什么产品。 wpa_supplicant适配层是通用的wpa_supplicant的封装,在Android中作为Wi-Fi部分的硬件抽象层来 使用。wpa_supplicant适配层主要用于封装与wpa_supplicant守护进程的通信,以提供给Android框 架使用。它实现了加载、控制和消息监控等功能。wpa_supplicant适配层的头文件如下所示。

wpa_supplicant的标准结构框图如图17-3所示。

图17-3 wpa_supplicant的标准结构框图 我们重点关注框图的下半部分,即wpa_supplicant是如何与Driver进行联系的。整个过程以AP发出 SCAN命令为主线。由于现在大部分Wi-Fi Driver都支持WEXT,所以假设我们的设备走的是WEXT这条线 ,其实用NDIS也一样,整

个流程差不多。 首先,在文件Driver.h中存在一个名为wpa_driver_ops的结构体,这个结构体在Driver.c中被声明如 下。

x0c然后文件在driver_wext.c中填写了该结构体的成员,代码如下所示。

上述成员其实都是驱动和wpa_supplicant的接口。以SCAN为例的代码如下所示。

通过如下代码可以看出wpa_cupplicant是通过IOCTL调用SOCKET与Driver进行通信的,并给Driver下 达SIOCSIWSCAN命令。

这样,当一个命令从AP到Framework到C++本地库再到wpa_supplicant适配层,再在wpa_supplicant下 通过CMD命令就打通了到DRIVER的路线。 因为Wi-Fi模块是采用SDIO总线来控制的,所以应该先记录下CLIENT DRIVER的SDIO部分的结构。此部 分的SDIO分为三层,分别是SdioDrv、SdioAdapter、SdioBusDrv。其中SdioBusDrv是Client Driver中SDIO与WIFI模块的接口,SdioAdapter是SdioDrv和SdioBusDrv之间的适配层,SdioDrv是 Client Driver中的SDIO与LINUX KERNEL中的MMC SDIO的接口。这三部分只需要关注一下SdioDrv就可 以了,另外两层都只是对它的封装罢了。 在SdioDrv中提供了下面的功能。

x0cSDIO的读写实际上调用了MMCCore中的如下功能函数。

SDIO功能部分读者只需简单了解即可,一般HOST部分芯片厂商都会提供完整的解决方案。我们的主要 任务还是Wi-Fi模块。 首先看Wi-Fi模块的入口函数wlanDrvIf_ModuleInit(),在此调用了wlanDrvIf_Create()。主要代码 如下所示。

在调用完wlanDrvIf_Create()函数后,实际上Wi-Fi模块的初始化就结束了。接下来分析如何初始化 。先看wlanDrvIf_SetupNetif (drv)这个函数的主体,对应代码如下所示。

x0c在此初始化wlanDrvWext_Inti(dev)后,说明wpa_supplicant与Driver直接的联系是走的WEXT这条路 。也就是说event的接收、处理也应该是在WEXT部分来实现的,确定此论点之后,剩下的工作量顿减 三分之一。接下来需要注册网络设备dev,在wlan_netdev_ops中的定义代码如下所示。

上述代码名字对应的都是Linux网络设备驱动的命令字,最后需要调用rc=drvMain_CreateI,通过此 函数完成了相关模块的初始化工作。 2.JNI层 Android中的Wi-Fi系统的JNI部分实现的源代码文件如下。

JNI层的接口注册到Java层的源代码文件如下。

WifiNative将为WifiService、WifiStateTracker、WifiMonitor等几个Wi-Fi框架内部组件提供底层 操作支持。 此处实现的本地函数都是通过调用wpa_supplicant适配层的接口来实现的(包含适配层的头文件 wifi.h)。wpa_supplicant适配层是通用的wpa_supplicant的封装,在Android中作为Wi-Fi部分的硬 件抽象层来使用。wpa_supplicant适配层主要用于封装与wpa_supplicant守护进程的通信,以提供给 Android框架使用。它实现了加载、控制和消息监控等功能。wpa_supplicant适配层的头文件如下所 示。

文件wifi.h是Wi-Fi适配

器层对JNI部分的接口,其中包含了一些加载和连接的控制接口,主要包括如 下两个接口。 ● wifi_command():负责将命令发送到Wi-Fi下层。 ● wifi_wait_for_event():负责事件进入通道,此函数将被阻塞,一直到收到一个Wi-Fi事件为止 ,并且以字符串的形式返回。

x0c在文件wifi.h中定义上述接口的代码如下所示。

在文件wifi.c中实现了上述两个接口,具体代码如下所示。

3.Java FrameWork层 Wi-Fi系统的Java部分代码实现的目录如下所示。

x0cWi-Fi系统Java层的核心是根据IWifiManger接口所创建的Binder服务器端和客户端,服务器端是 WifiService,客户端是WifiManger。 编译生成文件,并生成(服务器端抽象类)和 (客户端代理实现类)。WifiService通过继承实现,而 客户端通过getService()函数获取(即Service的代理类),将其作为参数 传递给WifiManger,供其与WifiService通信时使用。 Wi-Fi系统Java部分的核心是根据IWifiManager接口所创建的Binder服务器端和客户端,服务器端是 WifiService,客户端是WifiManager。具体结构如图17-4所示。

图17-4 JNI接口结构 图17-4中主要构成元素的具体说明如下所示。 (1)WiFiManger是Wi-Fi部分与外界的接口,用户通过它来访问Wi-Fi的核心功能。 WifiWatchdogService这一系统组件也是用WifiManger来执行一些具体操作。

x0c(2)WifiService是服务器端的实现,作为Wi-Fi的核心,处理实际的驱动加载、扫描、链接/断开 等命令,以及底层上报的事件。对于主动的命令控制,Wi-Fi是一个简单的封装,针对来自客户端的 控制命令,调用相应的WifiNative底层实现。 当接收到客户端的命令后,一般会将其转换成对应的自身消息塞入消息队列中,以便客户端的调用可 以及时返回,然后在WifiHandler的handleMessage()中处理对应的消息。而底层上报的事件 ,WifiService则通过启动WifiStateTracker来负责处理。WifiStateTracker和WifiMonitor的具体功 能如下所示。 ● WifiStateTracker除了负责WiFi的电源管理模式等功能外,其核心是WifiMonitor所实现的事件 轮询机制,以及消息处理函数handleMessage()。 ● WifiMonitor通过开启一个MonitorThread来实现事件的轮询,轮询的关键函数是前面提到的阻塞 式函数rEvent()。获取事件后,WifiMonitor通过一系列的Handler通知给 WifiStateTracker。这里WifiMonitor的通知机制是将底层事件转换成WifiStateTracker所能识别的 消息,塞入WifiStateTracker的消息循环中,最终在handleMessage()中由WifiStateTracker完成对 应的处理。 WifiStateTracker同样是Wi-Fi部分与外界的接口,它不像WifiManger那样直接被实例化操作,而是 通过I

ntent机制发送消息通知给客户端注册的BroadcastReceiver,以完成和客户端的接口。 (3)WifiWatchdogService是ConnectivityService所启动的服务,但它并不是通过Binder来实现的 服务。它的作用是监控同一个网络内的接入点(Access Point),如果当前接入点的DNS无法ping通 ,就自动切换到下一个接入点。WifiWatchdogService通过WifiManger和WifiStateTracker辅助完成 具体的控制动作。在WifiWatchdogService初始化时,通过registerForWifiBroadcasts注册获取网络 变化的BroadcastReceiver,也就是捕获WifiStateTracker所发出的通知消息,并开启一个 WifiWatchdogThread线程来处理获取的消息。通过更改_WARCHDOG_ON的配置 ,可以开启和关闭WifiWatchdogService。 4.Settings中的Wi-Fi设置 Android的Settings应用程序对Wi-Fi的使用,是典型的Wi-Fi应用方式,也是用户可见的Android WiFi管理程序。此部分源代码的目录如下所示。

Settings里的Wi-Fi部分是用户可见的设置界面,提供Wi-Fi开关、扫描AP、链接/断开的基本功能。 另外,通过实现ck接口提供了一组回调函数,用以响应用户关心的Wi-Fi状态的变 化。 WifiEnabler和WifiLayer都是WifiSettings的组成部分,同样通过WifiManger来完成实际的功能,注 册一个BroadcastReceiver来响应WifiStateTracker所发出的通知消息。WifiEnabler其实是一个比较 简单的类,提供开启和关闭Wi-Fi的功能,设置里面的外层Wi-Fi开关菜单,就是直接通过它来做到的 ;而WifiLayer则提供更复杂的Wi-Fi功能,如AP选择等以供用户自定义。 具体结构如图17-5所示。

x0c图17-5 Setting中的Wi-Fi设置结构

x0c17.1.4 OMAP平台实现Wi-Fi 在OMAP平台的Wi-Fi系统中,内核部分符合标准的Linux框架,在Android开源工程中,通过如下目录 中的文件实现了和局域网系统相关的功能。

其中文件system/wlan/ti/wilink_6_1/platforms/os/linux/src/WlanDrvIf.c是驱动程序的入口,初 始化函数是wlanDrvIf_ModuleInit(),具体代码如下所示。

在上述函数中,通过调用函数wlanDrvIf_Create()来注册Wi-Fi网络设备。函数 wlanDrvIf_Create()的具体代码如下所示。

在OMAP平台中,使用另一个较为特殊的驱动来代替wpa_supplicant中的WEXT标准驱动。此特殊部分驱 动的实现文件保存在如下目录中。

此目录中文件的主要代码如下所示。

x0c这样经过编译后会生成静态库libCustomWifi.a,这个库被wpa_supplicant可执行程序连接,以作为 插件来使用。 通过文件system/wlan/ti/wilink_6_1/config/wpa_来配置OMAPpigtail,代码如下 。

在文件system/wlan/ti/wilink_6_1/wpa_supplicant_lib/driver_ti.c中定义了wpa_driver_ops类型 的结构体wpa_driver_custom_ops,其中列出了对WPA驱动操作。具体代码如下所示。

x0cx0c17.1.5 配置Wi-Fi

的流程 下面将简要介绍实现并配置Wi-Fi系统的基本流程。 (1)配置Android支持Wi-Fi 在文件中添加如下代码。

此时在文件external/wpa_supplicant/中设置WPA_BUILD_SUPPLICANT为true,默认使用驱 动文件driver_wext.c。如果使用定制的wpa_supplicant驱动(例如madwifi),那么可以进行如下设 置。

(2)使wpa_supplicant能够调试信息 wpa_supplicant的默认设置是MSG_INFO,为了输出更多信息,可进行如下修改。 首先在文件common.c中设置:

然后在文件common.c中把#define wpa_printf宏中的if ((level) >= MSG_INFO)改为if ((level)>= MSG_DEBUG)。 (3)配置wpa_ wpa_supplicant是通过wpa_中的如下语句来指定控制socket的。

应该在文件中配置好,然后复制到$(TARGET_OUT_ETC)/wifi(即文件 /system/etc/wifi/wpa_),此位置会在中再次检测。 通常将wpa_进行如下配置。

有时需要增加驱动:

如果遇到AP连接问题,则需要修改ap_scan=0以让驱动连接来代替wpa_supplicant。如果要连接到 non-WPA or open wireless networks,则需要增加如下代码。

(4)配置路径和权限 Google修改的wpa_supplicant需要运行在Wi-Fi用户和组下,具体代码见文件 wpa_supplicant/os_unix.c中的函数os_program_init()。如果配置错误,则会出现以下错误:

此时需要确认文件中有如下配置:

x0c如果系统的/system目录为只读,那么应该使用路径/data/misc/wifi/wpa_。 (5)运行wpa_supplicant和dhcpcd 在文件中必须确保有如下语句。

根据所用的Wi-Fi驱动名字,修改wlan0为自己驱动的名字。 (6)编译Wi-Fi驱动为module或kernel built in ● 编译为module 在文件中添加如下代码。

编译为kernel built in。首先在文件hardware/libhardware_legacy/wifi/wifi.c中修改 interface的名字,然后在文件中添加如下代码。

最后在文件hardware/libhardware_legacy/wifi/wifi.c中,当insmod/rmmod时直接return 0。 (7)Wi-Fi需要的firmware Android不使用标准的hotplug binary,Wi-Fi需要的firmware要复制到/etc/firmware目录,或者复 制到Wi-Fi驱动指定的位置,然后Wi-Fi驱动会自动加载。 (8)修改Wi-Fi驱动适合Android Google修改的wpa_supplicant要求SIOCSIWPRIV ioctl发送命令到驱动和接收信息,例如signal strength、mac address of the AP和link speed等。所以要正确实现Wi-Fi驱动,需要从 SIOCSIWPRIV ioctl返回RSSI (signal strength)和MACADDR信息。如果没实现这个ioctl,则会出现 如下错误。

x0c(9)设置 建议将文件/system/etc/dhcpcd/配置为:

到此为止,我们的工作全部完成,此时在Android上就可以运行我们的Wi-Fi系统了。

x0c17.1.6 具体演练——在Android下实现Ethernet 我们知道,Android

源代码本身不支持Ethernet上网,主要因为它针对手机设计,而手机上一般不会 带有RJ45模块,因此要想在Android上实现Ethernet功能,就要增加Framework层和App层代码,工作 量还是很大的。还好网络上有基于上网本开发的开源项目android-x86,它已经实现了Ethernet功能 ,只要参考它的源代码修改。下面将以Android 2.2为例,介绍在Android系统中如何实现Ethernet上 网的功能。 (1)修改Linux驱动 以RJ45芯片是MCS7830为例,在Linux Kernel源代码中已包含了它的驱动,只要在配置时选中它即可 编译。

(2)修改Android源代码 下面只列出Android 2.2需要修改的源代码文件,包括所在的文件夹位置,具体修改内容参考本书附 带的源代码。

x0c(3)修改 在文件中增加以下代码。

完成上面的工作后,在Setting中就可以看到Ethernet Setting的选项,此时使用RJ45接上网线就可 以上网了。

x0c17.2 蓝牙系统 蓝牙是一种支持设备短距离通信(一般10m内)的无线电技术,能够在移动电话、PDA、无线耳机、笔 记本电脑、相关外设等众多设备之间进行无线信息交换。利用“蓝牙”技术,能够有效地简化移动通 信终端设备之间的通信,也能够成功地简化设备与因特网之间的通信,从而使数据传输变得更加迅速 、高效,为无线通信拓宽道路。本节将简要讲解Android平台中蓝牙系统驱动的移植知识,为读者学 习本书后面的知识打下基础。

x0c17.2.1 蓝牙系统的结构 Android平台的蓝牙系统是基于BlueZ实现的,是通过nux中一套完整的蓝牙协议栈开源实现的。当前 BlueZ广泛应用于各种Linux版本中,并被芯片公司移植到各种芯片平台上使用。在Linux 2.6内核中 已经包含了完整的BlueZ协议栈,在Android系统中已经移植并嵌入了BlueZ的用户空间实现,并且随 着硬件技术的发展而不断更新。 蓝牙(Bluetooth)技术实际上是一种短距离无线电技术。在Android系统中的蓝牙除了使用Kernel支 持外,还需要用户空间的BlueZ的支持。 Android平台中Wi-Fi系统的基本层次结构如图17-6所示。

图17-6 蓝牙系统的层次结构 Android平台中蓝牙系统从上到下主要包括Java框架中的BlueTooth类、Android适配库、BlueZ库、驱 动程序和协议,这几部分的系统结构如图17-7所示。

x0c图17-7 蓝牙系统结构 在图17-7中各个层次结构的具体说明如下所示。 (1)BlueZ库 Android蓝牙设备管理的库的路径如下所示。

可以分别生成库、和hcidump等众多相关工具和库。BlueZ库提供了 对用户空间蓝牙的支持,其中包含了主机控制协议HCI及其他众多内核实现协议的接口,并且实现了 所有蓝牙应用模式Profile。 (2)蓝牙的JNI部分 此部分的代码路径如下

所示。

(3)Java框架层

x0cJava框架层的实现代码保存在如下路径。

蓝牙的服务部分负责管理并使用底层本地服务,并封装成系统服务。而在oth部分中 包含了各个蓝牙平台的API部分,以供应用程序层使用。 (4)BlueTooth的适配库 BlueTooth适配库的代码路径如下所示。

此层用于生成库及相关工具和库,能够实现对蓝牙设备的管理,例如蓝牙设备的电 源管理。

x0c17.2.2 需要移植的内容 在Android平台中,蓝牙系统的本地层和框架层都是标准的程序,我们所需要移植的内容仅仅是蓝牙 驱动程序。蓝牙驱动程序包括针对硬件接口的USB、SDIO和UART驱动,此部分驱动的内容也是标准的 。如果使用UART蓝牙芯片,则需要使用芯片特定的高速串口。另外在驱动中还包含了电源管理和芯片 配置。因为通常硬件接口都比较标准,所以有很多蓝牙芯片通过用户空间的初始化代码直接对芯片进 行写入操作,以完成初始化操作。 1.BlueZ Android所采用的蓝牙用库空间的库是BlueZ。这是一套Linux平台的蓝牙协议栈完整开源实现,广泛 用在各Linux发行版,并被移植到众多移动平台上。在Android系统中,BlueZ提供了很多分散的应用 ,例如守护进程和一些工具。BlueZ通过D-BUS IPC机制来提供应用层接口。 2.适配层 BlueZ的适配层BlueZ在Android中使用,需要经过Android的BlueZ适配层的封装。BlueZ适配层源代码 及头文件路径如下所示。

该目录中除了包含生成适配层库的源代码之外,还包含了BlueZ头文件和BlueZ配置 文件等目录。由于BlueZ使用D-BUS作为与上层沟通的接口,因此适配层构造比较简单,封装了蓝牙的 开关功能,以及射频开关。 3.JNI和Java部分 在Android中还定义了Bluetooth通过JNI到上层的接口,此功能保存在如下目录中。

在此目录中的主要实现文件如下所示。 ● android_bluetooth_ ● android_bluetooth_ ● android_bluetooth_ ● android_bluetooth_ ● android_bluetooth_ ● android_bluetooth_

x0c17.2.3 具体移植 1.驱动程序 在Android系统的蓝牙驱动程序中,主要包括针对硬件接口的USB、SDIO和UART驱动,此部分驱动的内 容也是标准的。如果使用UART蓝牙芯片,则需要使用芯片特定的高速串口。另外在驱动中还包含了电 源管理和芯片配置。此部分代码保存在如下目录中。

Android系统的蓝牙驱动程序保存在如下目录中。

和其他驱动程序相比,蓝牙系统的协议层位于内核空间的上层部分。接下来将简要介绍这些驱动的实 现过程。 (1)连接硬件部分 在蓝牙驱动程序中,主要包括针对硬件接口的USB、SDIO和UART驱动,

除了UART蓝牙芯片外,都是标 准的内核驱动程序。无论使用上述哪一种接口,都需要在内核中开启驱动支持,通过make menuconfig配置路径为Networking Support=>Bluetooth subsystem support=> Bluetooth device drivers,进入后可以根据需要来开启使用的硬件连接方式。 (2)蓝牙协议栈 在Linux2.6的内核中已经包含了蓝牙协议栈,通过make menuconfig配置路径为Networking Support=>Bluetooth subsystem support,在该层中已经列出了各种协议支持,其中最重要的是 HCIP、L2CAP、SCO、RFCOMM、BNEP等。为了避免不必要的麻烦,在此建议全部开启。 (3)电源管理 在Android中使用标准的Linux rfkill接口来管理蓝牙芯片的电源,需要实现一个平台设备和 rfkill的逻辑控制。接口rfkill的内核在如下头文件中定义。

2.本地代码 在本地代码部分主要完成配置性的工作,例如初始化蓝牙芯片和配置蓝牙服务。 (1)初始化蓝牙芯片 初始化蓝牙芯片是通过BlueZ工具hciattach进行的,此工具在如下目录的文件中实现。

hciattach命令主要用来初始化蓝牙设备,它的命令格式如下所示。

其中最重要的参数就是type和speed,type决定了要初始化的设备的型号,可以使用hciattach–l来 列出所支持的设备型号。 并不是所有的参数对所有的设备都是适用的,有些设备会忽略一些参数设置。例如,查看 hciattach的代码就可以看到,多数设备都忽略bdaddr参数。hciattach命令内部的工作步骤是:首先 打开指定的tty设备,然后做一些通用的设置,如flow等,然后设置波特率为initial_speed,然后根 据type调用各自的初始化代码,最后将波特率重新设置为speed。所以调用hciattach时,要根据你的 实际情况,设置好initial_speed和speed。 对于type BCSP来说,它的初始化代码只做了一件事,就是完成BCSP协议的同步操作,它并不对蓝牙 芯片做任何pskey设置。

x0c(2)蓝牙服务 在蓝牙服务方面一般不用我们自己定义,只需要使用初始化脚本文件中的默认内容即可。例 如下面的代码。

x0c在上述代码中,每一个rvice后面列出了一种Android服务。 (3)管理蓝牙电源 在Android系统的如下目录中实现了libbluedroid。

可以调用rfkill接口来控制管理电源。如果已经实现了rfkill接口,则无须再进行配置。如果在文件 中已经实现了hciattach服务,则说明在libbluedroid中已经实现对其调用以操作蓝牙的初始 化。

x0c17.2.4 MSM平台的蓝牙驱动 在MSM平台中,通常使用高通自带的芯片实现UART高速连接。 1.驱动部分 电源管理功能的实现文件保存在如下目录中。

其中在文件board-mahimahi-rfkill.c中定义函数mahimahi_rfkill_probe(),此函数完成对rfkill接 口的初始化工作,主要代码如下所示。



上述代码中的bt_rfk是一个由rfkill_allocate分配出来的rfkill类型的结构体,在完成初始化工作 以后,使用rfkill_register()将其注册到系统中。 定义函数bluetooth_t_power(),此函数是一个rfkill结构的toogle_radio指针,功能是实现蓝牙 的开关功能。具体代码如下所示。

x0c2.用户空间部分 因为高通公司有自己的芯片产品,所以在MSM平台上配备了专门的初始化程序hci_qcom_init。此部分 代码并没有在开源代码中,并且相对独立,所以可以将其视为hciattach之前提前进行一部分初始化 工作。其中和hciattach有关的代码如下所示。

初始化工作完成以后调用kill_hciattach结束,通过hciattach服务直接调用sh来解析执行脚本。当 libbuedroid启动该服务的时候执行该脚本,当停止服务的时候回调该脚本中的kill_hciattach。

x0c17.3 定位系统 我们平常所说的定位系统是全球定位系统(Global Positioning System)。简单地说,这是一个由 覆盖全球的24颗卫星组成的卫星系统。这个系统可以保证在任意时刻,地球上任意一点都可以同时观 测到4颗卫星,以保证卫星可以采集到该观测点的经纬度和高度,以便实现导航、定位、授时等功能 。这项技术可以用来引导飞机、船舶、车辆及个人,安全、准确地沿着选定的路线,准时到达目的地 。在智能手机中,也具有定位系统的功能。本节将简要讲解Android平台中定位系统驱动的移植知识 ,为读者学习本书后面的知识打下基础。

x0c17.3.1 定位系统的结构 在Android平台中,定位系统有如下两个数据来源。 ● GPS定位:使用GPS设备实现定位,是现实应用中最常用的定位方式。 ● Network定位:使用Cell基站或Wi-Fi热点定位。 对Android的源代码来说,GPS相关部分是开源项目的一部分,而Network定位部分只在开源代码中提 供了接口。两者虽然底层技术实现不同,但作为定位数据的提供方有着很多共同的地方,并使用同一 套框架。Android定位系统为上层应用提供了统一的接口,可以广泛应用于Google地图等现实应用中 。Android定位系统的基本层次结构如图17-8所示。

图17-8 定位系统的层次结构 Android平台中定位系统从上到下主要包括Java框架类、硬件抽象层和驱动程序,这几部分的系统结 构如图17-9所示。

x0c图17-9 定位系统结构 在图17-9中各个层次结构的具体说明如下所示。 (1)硬件抽象层(HAL层) 硬件抽象层的主要功能保存在如下目录中。

HAL层相当于一个Linux应用程序接口,通过open()和clo()等函数操作硬件设备。Android的源代码 只实现了模拟器的GPS接口,具体在文件gps_qemu.c中实现。在2.2版本中提供了对QCOM公司的GPS的 实现,保存在以下目录中。

(2)JNI层 GPS部分

的JNI的本地部分实现源代码保存在以下文件中。

由此可见,JNI层只有一个文件,起承上启下的作用。上层承接Framework,下层调用HAL层具体硬件 抽象实现。 (3)Java框架层 GPS部分的Java层的实现代码保存在以下目录中。

x0c17.3.2 需要移植的内容 在Android平台中,我们需要移植的GPS内容位于JNI层下面,即驱动层硬件抽象层。GPS硬件的驱动程 序通常是串口驱动程序,比较容易实现,在Linux系统的用户空间中通常用tty设备来表示。 而对于GPS硬件抽象层来说是比较复杂的,在Android平台中给出了少量的使用参考信息,这些信息保 存在文件gps_qemu.c中。虽然此文件中的结构可以重复使用,但是需要在此基础上做大量的修改工作 和升级工作之后才能使用。

x0c17.3.3 移植和调试 1.驱动程序 GPS硬件设备分为硬GPS和软GPS两种。其中硬GPS一般是功能独立的模块,不需要特别多的控制,通电 之后就可以运行,可以直接输出NMEA数据,驱动方法十分简单。而软GPS通常需要主控芯片控制其运 行状态,输出的大多是裸卫星数据,需要主控方进行计算,才能得到最终的NMEA数据。两者的共同点 是最终的输出都是NMEA数据。 注意: NMEA(National Marine Electronics Association,国际海洋电子协会)一般是指 NMEA0183,这是一套工业标准的接收机信号输出协议。 2.硬件抽象层 GPS系统的硬件抽象层在如下头文件中定义。

由此可见GPS功能是hardware_legacy系统的一部分,此部分使用了非标准的接口。 首先在文件gps.h中定义了如下入口。

定义接口GpsInterface,具体代码如下所示。

接口GpsInterface是GPS模块中最重要的数据结构,它是底层驱动实现的接口,如果要porting到自己 的板子上,就需要实现这些接口。该接口定义在gps.h中,模拟器实现在gps_qemu.c中。 通过GPSGpsCallbacks定义GPS回调函数指针组,具体代码如下所示。

x0c在文件com_android_rver_android_location_中实现了回调函数 GpsCallbacks。因为Google已经实现了,所以我们不需要做任何动作。 然后定义GpsLocation表示Locatin数据信息,底层驱动获得Location的raw信息,通常是NMEA码,然 后通过解析就得到了Location信息。其中Android2.3比Android2.2新增加了size属性以获取其大小。 定义代码如下所示。

定义GpsStatus表示GPS的状态信息,定义代码如下所示。

定义GpsXtraInterface用于从网络上下载星历表,定义代码如下所示。

x0c定义AgpsInterface,表示使用手机基站的定位方式,定义代码如下所示。

用AgpsCallbacks表示AGPS系统的回调函数,定义代码如下所示。

定义AgpsStatus表示在回调函数中使用的参数类型,功能是获取AGPS的状态信息,定义代码如下所示 。

3.上

层应用 (1)LocationManager。 在文件中启动LocationManager服务,在系统启动之后此服务就已经启动了。 在文件(frameworkbarvicesjavacomandroidrver)的init2()函数 中启动一个线程来注册Android的诸多服务,例如Bluetooth Service、NetworkManagement Service和Notification Manager等,当然也包括Location Service。此函数的定义代码如下所示。

x0c在ServerThread线程的run()函数中,定义LocationManager服务的代码如下所示。

在run()函数中的后半部分是服务对系统的反馈,就是systemReady()函数。LocationManager服务对 应的反馈函数如下所示。

其中locationF是LocationManagerService的final类型,一旦赋值不能更改。

在Android 2.1版本中LocationManagerService的构造函数的实现文件是LocationManagerService. java(frameworksbarvicesjavacomandroidrver),定义代码如下所示。

而Android版本中的定义代码如下所示。

x0c由此可见2.1版本是在构造函数的时候就启动一个自身服务线程,2.2版本是在反馈机制中通过 systemReady()函数启动自身服务线程实现的,对应代码如下所示。

然后通过线程函数run()来调用函数initialize(),对应代码如下所示。

(2)initialize()函数。 在文件(frameworksbarvicesjavacomandroidrver)中 定义initialize()函数的代码如下所示。

函数initialize()中最重要的就是loadProviders()函数了,该函数先调用loadProvidersLocked,然 后loadProvidersLocked()函数又调用_loadProvidersLocked()函数。我们先看 _loadProvidersLocked函数的实现代码。

上面的if语句很重要,因为在上述代码中得到了HAL层的GPS接口GpsInterface,此功能通过调用 GpsLocationProvider的isSupported()函数才调用到文件 (hardware/libhardware_legacy/gps)中的gps_get_interface()。 (3)在文件 (frameworksbalocationjavacomandroidinternallocation)中定 义了如下代码。

x0c通过上述一段简短的代码就调用了native方法,也就是JNI层定义的方法。函数 native_is_supported()对于JNI层来说是android_location_GpsLocationProvider_is_supported方 法。 (4)文件android_location_(frameworksbacorejni)。 此文件的核心代码如下所示。

前面已经提到JNI起到承上启下的作用,函数gps_get_interface()属于HAL层的调用,在文件 中实现。 (5)在文件(hardwarelibhardware_legacygps)中定义了如下代码。

然后通过函数gps_find_hardware()得到GPS接口,下面是模拟器中的gpsinterface代码。

(6)在文件gps_qemu.c(hardwarelibhardware_legacygps)中实现gps_get_qemu_interface,对 应代码如下所示。

x0cqemuGpsInterface在文件gps_qemu.c中实现,对应代码如下所示。

(7)构造函数GpsLocationPr

ovider()。 在底层得到GPS的接口之后,需要通过如下代码新建一个GpsLocationProvider对象。

在构造函数GpsLocationProvider()中有两个参数,分别是mContext和this。我们先看看构造函数 GpsLocationProvider()前面的几句代码。

由此可见,在GpsLocationProvider类中的成员变量mLocationManager是构造函数的第二个参数,也 就是说是LocationManagerService对象。 接着看函数_loadProvidersLocked(),代码如下所示。

x0c在构造完GpsLocationProvider之后将其添加到全局变量ArrayList mProviders中,以备以后调用。 函数_loadProvidersLocked()的最后一行代码调用updateProvidersLocked()函数,此代码仍然在文 件中实现,具体代码如下所示。

从上面_loadProvidersLocked()函数代码来看,在mProviders这个ArrayList中有两个元素,分别是 gpsProvider和passiveProvider。其中gpsProvider是GpsLocationProvider类型的,它的 isEnabled()函数返回的是fal,因为它并没有被enable。而passiveProvider是PassiveProvider类 型,它总是enable的。所以gpsProvider会调用el语句中的updateProviderListenersLocked(name,

x0ctrue)函数。我们主要分析这个el语句,对于passiveProvider不做分析。代码如下所示。

在上述if(enabled)语句段中启动了GPS的服务。 (8)函数enable()。 通过调用GpsLocationProvider类中的函数enable()和enableLocationTracking()就启动了GPS的 LocationManager服务。下面对这两个函数进行分析。 首先看enable()函数,定义在文件中,代码如下所示。

x0c在函数handleMessage()中定义了各种message对应的处理函数。对于enable消息还带有一个参数 ,enable函数中带的参数值为1,所以调用handleEnable函数。对应代码如下所示。

在函数handleEnable()中主要实现了如下两件事。 ● 调用native的初始化方法对GPS进行初始化 调用native方法native_init,就是JNI层的方法android_location_GpsLocationProvider_init,在 文件andoird_location_中进行了定义,代码如下所示。

在初始化函数中会确认是否已经得到GpsInterface,如果没有得到则通过gps_get_interface()方法 再次尝试得到,其实该接口已经在android_location_GpsLocationProvider_is_supported函数中得 到了。然后在第二个if语句中调用初始化方法sGpsInterface->init。 在android_location_GpsLocationProvider_init的后半部分,试图通过GpsInterface>get_extension方法去得到GPS相关的扩展接口,可是在Android的模拟器实现中并没有实现这个函数 ,在文件gps_qume.c中明显写着return NULL。代码如下所示。

x0c在sGpsInterface->init中,也就是在qemu_gps_init()方法中先调用了gps_state_init,然后注册了 回调函数,此回调函数就是在JNI层实现的,而且有JNI层传下来的函数。对应

代码如下所示。

在上述gps_state_init()函数中,首先打开串口,然后建立socket通信,并上报线程监听到底层的数 据。 ● 启动一个线程去监听事件 建立线程监听事件的代码如下所示。

x0c我们先来看GpsEventThread中的函数run(),代码如下所示。

在函数run()中还是需要调用native函数 android_location_GpsLocationProvider_wait_for_event(),这个函数在一个while循环中等待事件 的触发(由回调函数触发),然后调用GpsLocationProvider类的数据上报函数(Location数据)。 主要代码如下所示。

(9)函数enableLocationTracking()。 函数enableLocationTracking()在文件中实现,定义代码如下所示。

同样也可以使用Handler的方式调用函数handleEnableLocationTracking(),代码如下所示。

然后调用函数startNavigating(),此函数的定义代码如下所示。

x0c函数startNavigating()中的核心语句是调用native方法native_start,通过此代码调用了JNI层的函 数android_location_GpsLocationProvider_start()。 第18章 电话系统 对于一款手机来说,最重要的功能是拨打/接听电话和收发短信。在Android底层架构中,电话和短 信功能是通过电话系统实现的。本章将详细讲解Android电话系统的基本知识和移植方法,为读者学 习本书后面的知识打下基础。

x0c18.1 电话系统基础 Android系统作为一个智能手机平台,电话(Telephony)部分功能自然十分重要。电话系统的主要功 能是呼叫(Call)、短信(SMS)、数据连接(Data Connection)、SIM卡和电话本等功能。本书将 介绍绝大多数功能的实现框架。

x0c18.1.1 电话系统简介 Android的Radio Interface Layer(RIL)提供了电话服务和Radio硬件之间的抽象层。RIL负责数据 的可靠传输、AT命令的发送及respon的解析。应用处理器通过AT命令集与带GPRS功能的无线通信模 块通信。AT command是由Hayes公司发明的,是一个调制解调器制造商采用的一个调制解调器命令语 言,每条命令以字母“AT”开头。 Android电话功能各个部分的具体说明如下所示。 (1)hardware/ril/include/telephony/目录中的文件ril.h是ril部分的基础头文件。其中定义的结 构体RIL_RadioFunctions的代码如下所示。

在结构体RIL_RadioFunctions中包含了几个函数指针的结构体,这实际上是一个移植层的接口。在实 现下层的库之后,由rild守护进程得到这些函数指针,执行对应的函数。 其中几个重要的函数指针的原型如下所示。

其中最为重要的函数是onRequest(),这是一个请求执行的函数。 (2)rild守护进程。 rild守护进程的文件包含在hardware/ril/rild目录中,其中包含了文件rild.c和 radiooptions.c,这个目录中的文件经过编译后会生成一个可执行程序,这个程序在系统中的

安装路 径位于:

文件rild.c是这个守护进程的入口,它具有一个主函数的入口main(),执行的过程是将请求转换成 AT命令的字符串,给下层的硬件执行。在运行过程中,使用dlopen打开/system/lib/路径中名称为 的动态库,然后从中取出RIL_Init符号来运行。 RIL_Init符号是一个函数指针,执行这个函数后,返回的是一个RIL_RadioFunctions类型的指针。得 到这个指针后,调用RIL_register()函数,将这个指针注册到libril库之中,然后进入循环。事实上 ,这个守护进程提供了一个申请处理的框架,而具体的功能都是在和中完成的。 (3)动态库。 动态库的路径如下所示。

其中实现Android电话功能的主要文件是reference-ril.c和atchannel.c。这个库必须实现的是一个 名为RIL_Init的函数,这个函数执行的结果是返回一个RIL_RadioFunctions结构体的指针,指针指向 函数指针。这个库在执行的过程中需要创建一个线程来执行实际的功能。在执行的过程中,这个库将

x0c打开一个/dev/ttySXXX的终端(终端的名字是从上层传入的),然后利用这个终端控制硬件执行。 (4)动态库。 库的目录如下所示。

其中的主要文件是,此库需要实现以下几个接口。

这些函数也是被rild守护进程调用的,不同的vendor可以通过自己的方式实现这几个接口,这样可以 保证RIL能够在不同系统间移植。其中函数RIL_register()把外部的RIL_RadioFunctions结构体注册 到这个库之中,在恰当的时候调用相应的函数。在Android电话功能执行的过程中,这个库处理了一 些将请求转换成字符串的功能。

x0c18.1.2 电话系统结构 Android电话系统主要分为MODEM驱动、RIL(Radio Interface Layer)、电话服务框架和应用共4层 结构。具体结构如图18-1所示。

图18-1 电话系统的层次结构 由图18-1可知,Android平台中Wi-Fi系统从上到下主要包括Java应用、Java框架、本地RIL层和 Modem驱动,这几部分的具体说明如下所示。 (1)Modem驱动 实现电话功能的主要硬件是通信模块(MODEM),MODEM通过与通信网络进行沟通传输语音及数据,完 成呼叫、短信等相关电话功能。对于目前大部分独立通信模块而言,无论是2G还是3G都已经非常成熟 ,模块化相当完善,硬件接口非常简单,也有着相对统一的软件接口。一般的MODEM模块装上SIM卡 ,直接上电即可工作,自动完成初始的找网、网络注册等工作,完成之后即可打电话、发短信等。但 独立模块因为体积问题,在手机设计中较少使用,而是使用chip-on-board的方式。另外也有不少 MODEM基带与应用处理器共存。 (2)RIL硬件抽象层 RIL负责数据的

可靠传输、AT命令的发送及Respon解析。应用处理器通过AT命令集与带GPRS功能的 无线通信模块通信。AT command由Hayes公司发明,是一个调制解调器制造商采用的一个调制解调器 命令语言,每条命令以字母AT开头。 RIL支持的本地代码包括RIL库和守护进程,主要代码路径如下所示。

具体编译结果如下所示。 ● /system/bin/rild:守护进程; ● /system/lib/:生成RIL库;

x0c● /system/lib/:生成RIL参考库。 在Android电话系统中没有JNI部分,RIL守护进程通过名为rild的Socker和Java框架层进行通信。 (3)Java框架 此部分的代码路径如下所示。

在此目录中存在如下Java类。 ● android/telephony:实现了Java类ony、及android. 。 ● com/android/internal/telephony:实现了内部的类ony、 d. 及,带GSM的是GSM的 专用协议,不带的是通用部分。 (4)应用层 在电话系统中,通过Service实现Phone应用,同时实现Phone的UI界面逻辑。而短信和网络选择分别 在MMS和Settings应用中实现。

x0c18.2 需要移植的内容 在移植Android的电话系统时,需要移植两部分内容,分别是MODEM驱动和RIL硬件抽象层。当前大多 数3G MODEM是通过USB设备实现连接的,即使用USB转Serial接口。当使用USB转Serial接口模式时 ,一般使用标准驱动来实现。对于一些特定型号的MODEM设备,就需要不同的电源/重启操作,需要 自行开发USB/Serial的驱动程序。 根据各个系统的硬件差别,在移植的过程中的主要工作是实现一个类似库的应 用。当宏RIL_SHLIB被定义的时候,将使用库的形式;当没有被定义的时候,将使用守护进程的方式 (在这种情况下不需要rild)。Java框架及应用电话部分RIL移植需要考虑的主要问题如下。 ● RIL设备所使用的不同端。 ● 在RIL_RadioFunctions的onRequest函数中需要处理的不同命令。

x0c18.3 移植和调试 本章前面内容讲解了Android电话系统的基本结构和我们需要移植的任务。本节将详细讲解在 Android平台中移植和调试电话系统的方法。

x0c18.3.1 驱动程序 在介绍驱动移植之前,需要先了解rild与及librefrence_的关系。 (1)rild 用于实现一个main()函数,作为整个ril层的入口点,负责完成初始化。 (2) 与rild结合相当紧密,是其共享库,编译时就已经建立了这一关系。组成部分为文件和 ril_。驻留在rild这一守护进程中,主要完成与上层通信的工作,接受ril请求 并传递给librefrence_,同时把来自librefrence_的反馈回传给调用进程。 (3)librefrence_r

rild通过手动的dlopen方式加载,结合稍微松散,这是因为主要负责与Modem硬件通 信的缘故。这样做更方便替换或修改以适配更多的MODEM种类。它转换来自的请求为AT命令 ,同时监控Modem的反馈信息,并传递回。在初始化时,rild通过符号RIL_Init获取一组函 数指针并以此与之建立联系。 (4)radiooptions radiooptiongs通过获取启动参数,利用socket与rild进行通信,可供调试时配置Modem参数。 经过前面内容的介绍,我们知道移植MODEM驱动的主要工作是USB转Serial接口,以及实现特殊 USB/Serial。上述驱动保存在如下目录中。

在Android电话系统的Modem驱动中,通常使用USB转Serial标准实现AT和数据通道的接口,此功能可 以通过文件drivers/usb/rial/option.c实现。首先需要定义下面的option_1port_device设备。具 体代码如下所示。

x0c上述驱动会通过option_init注册成为usb_rial_driver,然后通过对usb_driver注册来响应USB设 备枚举。对应的USB Driver如下。

为了匹配枚举中定义的设备,需要定义WENDOR ID和PRODUCT ID。在文件option.c中已经定义了很多 可以支持的设备,只需在数据里添加设备IDS即可。数组option_ids[]的代码如下所示。

我们可以定义两个需要的ID,按照下面的格式将其添加到上述option_ids[]数组中。

x0c上述代码中的加粗部分是我们定义的ID内容。 此时开启MODEM电源后,会出现两个名称分别为/dev/ttyusb0和/dev/ttyusb1的端口。

x0c18.3.2 RIL接口 1.RIL目录分析 (1)目录hardware/ril/libril 此目录下的代码负责与上层客户进程进行交互。在接收到客户进程命令后,调用相应函数进行处理 ,然后将命令响应结果传回客户进程。在收到来自网络端的事件后,也传给客户进程。 ● 文件ril_commands.h:其中列出了telephony可以接收的命令、每个命令对应的处理函数,以及 命令响应的处理函数。例如下面的代码。

● 文件ril_unsol_commands.h:其中列出了telephony可以接收的事件类型和对每个事件的处理函 数。例如下面的代码。

● 文件ril_event.h/cpp:实现了处理与事件源(端口,MODEM等)相关的功能。ril_event_loop监 视所有注册的事件源,当某事件源有数据到来时,相应事件源的回调函数被触发(firePending -> ev->func())。 ● 文件功能较为庞大,各个部分的具体功能如下所示。 ○ RIL_register函数:打开监听端口,接收来自客户进程的命令请求(s_fdListen = android_get_control_socket(SOCKET_NAME_RIL);),当与某客户进程建立连接时,调用 listenCallback函数;创建一个单独线程监视并处理所有事件源(通过ril_event_loop)。 ○ listenCallback函数:当与客户进程建立连接时,此函数被调用。此函数接

着调用 processCommandsCallback处理来自客户进程的命令请求。 ○ processCommandsCallback函数:具体处理来自客户进程的命令请求。对于每一个命令 ,ril_commands.h中都规定了对应的命令处理函数(dispatchXXX),processCommandsCallback会调 用这个命令处理函数进行处理。 ○ dispatch系列函数:此函数接收来自客户进程的命令与相应参数,并调用onRequest进行处理。 ○ RIL_onUnsolicitedRespon函数:将来自网络端的事件封装(通过调用responXXX)后传给客 户进程。 ○ RIL_onRequestComplete函数:将命令的最终响应结构封装(通过调用responXXX)后传给客户 进程。 ○ respon系列函数:对于每一个命令,都规定了一个对应的respon函数来处理命令的最终响应 ;对于每一个网络端的事件,也规定了一个对应的respon函数来处理此事件。respon函数可被 onUnsolicitedRespon或者onRequestComplete调用。 (2)目录hardware/ril/reference-ril

x0c在此目录下的代码主要负责与MODEM进行交互。 ● 文件reference-ril.c:此文件的核心是函数onRequest()和onUnsolicited()。 ○ onRequest函数:在这个函数里,每一个RIL_REQUEST_XXX请求都被转化成相应的AT command,发 送给MODEM,然后睡眠等待。当收到此AT command的最终响应后,线程被唤醒,将响应传给客户进程 (RIL_onRequestComplete -> ndRespon)。 ○ onUnsolicited函数:这个函数处理MODEM从网络端收到的各种事件,如网络信号变化、拨入的电 话、收到短信等。然后将时间传给客户进程(RIL_onUnsolicitedRespon-> ndRespon)。 ● 文件atchannel.c:负责向MODEM读写数据。其中,写数据(主要是AT command)功能运行在主线 程中,读数据功能运行在一个单独的读线程中。此文件的核心功能是函数 at_nd_command_full_nolock,此函数运行在主线程里面,用于将一个AT command命令写入MODEM后 进入睡眠状态(使用pthread_cond_wait或类似函数),直到MODEM读线程将其唤醒。唤醒后此函数获 得了AT command的最终响应并返回。函数readerLoop运行在一个单独的读线程里面,负责从MODEM中 读取数据。读到的数据可分为三种类型:网络端传入的事件;MODEM对当前AT command的部分响应 ;MODEM对当前AT command的全部响应。对于第三种类型的数据(AT command的全部响应),读线程 唤醒(pthread_cond_signal)睡眠状态的主线程。 ● 文件at_tok.c:提供AT响应的解析函数。 ● 文件misc.c:只提供一个字符串匹配函数。 (3)目录hardware/ril/rild 在此目录下的代码主要是为了生成rild和radiooptions的可执行文件。 ● 文件radiooptions.c:用于生成radiooptions的可执行文件,radooptions程序仅仅是把命令行 参数传递给socket{rild-debug}去处理而已,从

而达到与rild通信,可供调试时配置MODEM参数。 ● 文件rild.c:用于生成rild的可执行文件。 2.RIL接口移植 实现RIL接口的过程比较复杂,复杂程度主要体现在维护工作、需要处理的命令和较多结构体上。在 文件hardware/ril/include/telephony/ril.h中定义了RIL接口,并且同一个目录下的 ril_cdma_sms.h作为对CDMA协议的有利补充而存在。 在文件ril.h中首先定义核心结构RIL_Env,具体代码如下所示。

上述结构体是由库作为标准实现的,可以为硬件抽象层的实现库调用,能够在发生请求时 针对不同的情况而做出具体响应,例如有请求完成函数响应、上报信息函数响应和请求中周期处理函 数三个响应。 在结构体RIL_RadioFunctions定义需要的函数指针,具体代码如下所示。

x0cRIL实现库需要实现结构体RIL_RadioFunctions中的内容,当通过RIL_Init返回rild后,rild会调用 下面的函数完成注册。

此时成功搭建了rild到RIL的实现库的请求发送路径和响应路径。

x0c18.4 电话系统实现流程分析 本节将简要介绍Android电话系统的实现流程。

x0c18.4.1 初始启动流程 主入口功能是通过文件rild.c中的main()函数实现的,这个过程主要完成如下三个任务。 (1)开启中的event机制,这是在函数RIL_startEventLoop()中实现的,是最核心的由多 路I/O驱动的消息循环。 此任务的核心内容是通过RIL_startEventLoop()函数实现的,此函数在文件中实现,其主要 目的是通过pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL)建立一个dispatch线程 ,入口点在eventLoop。而在eventLoop中会调用ril_中的ril_event_loop()函数,以建立 起消息(event)队列机制。接下来看上述消息队列的机制,实现代码都在文件ril_中。主要 代码如下所示。

每个ril_event结构都与一个fd句柄绑定(可以是文件、socket、管道等),并且带一个func指针去 执行指定的操作。具体流程是:当完成ril_event_init后,通过ril_event_t配置一个新 ril_event,并通过ril_event_add加入队列之中(通常用rilEventAddWakeup来添加)。add会把队列 里所有ril_event的fd放入一个fd集合readFds中。这样ril_event_loop能通过一个多路复用I/O的机 制(lect)来等待这些fd,如果任何一个fd有数据写入,则进入分析流程 processTimeouts(),processReadReadies(&rfds, n),firePending()。后文会详细分析这些流程。 在进入ril_event_loop之前,通过pipe机制实现挂入一个s_wakeupfd_event,这个event的目的是可 以在某些情况下内部唤醒ril_event_loop的多路复用阻塞,比如一些带timeout的命令到期的时候。 到此为止,第一个任务分析完毕,这样便建立起了基于event队列的消息循环,稍后便可以接收上层 发来的请求了(在第三

个任务中,上层请求的event对象建立)。 (2)初始化librefrence_,也就是跟硬件或模拟硬件modem通信的部分(后面统一称硬件 ),通过RIL_Init函数完成。 此任务的入口是RIL_Init,RIL_Init首先通过参数获取硬件接口的设备文件或模拟硬件接口的 socket,然后新开一个线程继续初始化处理,即mainLoop循环处理。 mainLoop的主要任务是建立起与硬件的通信,然后通过read方法阻塞等待硬件的主动上报或响应。在 注册一些基础回调(timeout, readerclo)后,mainLoop将首先打开硬件设备文件,建立起与硬件 的通信,s_device_path和s_port是前面获取的设备路径参数,在此将其打开。两者可以同时打开并 拥有各自的reader,由此可见很容易添加双卡双待等支持。 接下来通过at_open()函数建立起这一设备文件上的reader等待循环,此功能是通过新建一个线程的 方式完成的,新建线程的代码如下所示。

x0c此线程的入口点是readerLoop。 因为AT命令都是以rn或nr的换行符来作为分隔符的,所以readerLoop是line驱动的,除非出错或超时 ,否则会读到一行完整的响应或主动上报时才返回。这个循环跑起来以后,已经建立了基本的AT响应 机制。 有了响应机制后,就可以通过如下代码在initializeCallback中执行一些Modem的初始化命令,主要 都是AT命令的方式。

(3)通过RIL_Init获取一组函数指针RIL_RadioFunctions,通过RIL_register完成注册,并打开接 收上层命令的socket通道。 此任务是由RIL_Init的返回值开始的,这是一个RIL_RadioFunctions结构的指针。此指针的定义代码 如下所示。

上述指针中最重要的是onRequest域,上层来的请求都由这个函数进行映射后转换成对应的AT命令发 送给硬件。rild通过RIL_register注册这一指针。 在RIL_register中还需要完成另外一个任务,就是打开前面提到的与上层通信的socket接口 (s_fdListen是主接口,s_fdDebug供调试时使用)。然后将这两个socket接口使用任务(1)中实现 的机制进行注册(仅列出s_fdListen),对应代码如下所示。

这样将两个socket加到任务(1)中建立起来多路复用I/O的检查句柄集合中,一旦有上层来的(调试 )请求,event机制便能响应处理了。到此为止,启动流程全部介绍完毕。

x0c18.4.2 request流程 (1)多路复用I/O机制的运转 request接收是通过ril_event_loop中的多路复用I/O实现的,其中使用ril_event_t来配置一个 event,此处主要有如下两种event。 ● ril_event_add:添加使用多路I/O的even,负责将其挂到队列,同时将event的通道句柄fd加入 到watch_table,然后通过lect进行等待。 ● ril_timer_add:添加timer event,它将其挂在队列,同时重新计算最短超时时间。 无论是哪一种add,

最终都会调用triggerEvLoop来刷新队列,并更新超时值或等待对象。在刷新之后 ,ril_event_loop从阻塞的位置使用lect返回。此时只有两种可能:一是超时,二是等待到了某 I/O操作。 ● 超时处理:在processTimeouts中完成,需要摘下超时的event并将其加入pending_list。 ● 检查有I/O操作的通道的处理:在processReadReadies中完成,需要将超时的event加入 pending_list。 在firePending中检索pending_list的event并依次执行event->func。当完成上述操作之后,计算新 的超时时间,并重新lect(选择)阻塞与多路I/O。 在初始化完成以后,在队列上会挂着如下三个event对。 ● s_listen_event:名为rild的socket,主要使用requet & respon通道实现。 ● s_debug_event:名为rild-debug的socket,调试用requet & respon通道,其流程与 s_listen_event基本相同。 ● s_wakeupfd_event:一个无名管道,用于队列主动唤醒。 (2)request的传入和dispatch 上层部分的核心代码保存在如下文件中。

此文件是Android Java框架处理radio(gsm)的核心组件,首先看其中的函数dial(),代码如下所示。

在上述代码中,rr是以RIL_REQUEST_DIAL为request号而申请的一个RILRequest对象,此request号在 Java框架和rild库中共享。在RILRequest初始化的时候,会连接名为rild的socket(也就是rild中 s_listen_event绑定的socket)。 是Parcel对象,Parcel是一套简单的序列化协议,用于将对象或对象的成员序列化成字节流 ,以供传递参数之用。在此可以看到String address和int clirMode都是将依次序列化的成员。在之 前rr初始化的时候,request号与request的序列号已经成为头两个将被序列化的成员,这为后面的 request解析打下了基础。 nd到handleMessage的流程比较简单,nd会将rr直接传递给另一个线程的handleMessage,目的是 执行data = ll()序列化操作,并将data字节流写入到rild socket。 如果此时返回rild,lect会发现rild socket有了请求链接的信号,这会导致s_listen_event被挂 入pending_list,从而执行event->func,即执行下面的代码。

x0c接下来运行下面的代码以获取传入的socket描述符。

然后通过record_stream_new建立起一个record_stream将其与s_fdCommand绑定,在此无须关注 record_stream的具体流程,只需关注command event回调和processCommandsCallback()函数即可。 从前面的event机制分析可以得出,一旦s_fdCommand上有数据,此回调函数就会被调用。 processCommandsCallback通过record_stream_get_next阻塞读取s_fdCommand上发来的数据,直到收 到一个完整的request(request包的完整性由record_stream的机制保证)为止,然后将其送达 processCommandBuffer。 进入processCommandBuffer以后就说明正式进入了命令的解析部分,每个命令将以Reque

stInfo的形 式存在。对应代码如下所示。

此处的pRI是一个RequestInfo结构指针,在上层和rild之间的request号是统一的,在文件中 定义了这个号。对应代码如下所示。

在定义时包含了一个ril_commands.h的枚举。pRI直接访问数组s_commands[]以获取自己的pCI,这是 一个CommandInfo结构,定义代码如下所示。

(3)request的详细解析 对于dial而言,CommandInfo结构的初始化是通过如下代码实现的。

在此执行了dispatchFunction之后,也就是执行dispatchDial()函数之后,我们可以看到其实有很多 种类的dispatch函数,比如dispatchVoid、dispatchStrings、dispatchSIM_IO等。这些函数的区别 在于Parcel传入的参数形式,其中Void就是不带参数的,Strings以string[]作为参数。 当拥有request号和参数后,就可以进行具体的request()函数调用了。代码如下。

s_callbacks是获取自libreference-ril的RIL_RadioFunctions结构指针,request请求在这里转入底 层的libreference-ril处理,handler是reference-ril.c中的onRequest。 onRequest进行一个简单的switch分发,RIL_REQUEST_DIAL的基本流程如下所示。

x0c在requestDial中将命令和参数转换成对应的AT命令,然后调用公共nd command接口 at_nd_command。除了这个接口之外,还有如下常用的接口。

接下来需要执行at_nd_command_full,前面的几个接口都会最终到这里为止,然后通过一个互斥的 at_nd_command_full_nolock调用来完成最终的写出操作。

x0c18.4.3 respon流程 通过前面的request流程,终止了at_nd_command_full_nolock中的writeline操作,因为这里完成 命令写出到硬件设备的操作,接下来就是等待硬件响应,也就是respon的过程了。 在实现respon获取信息时,在readerLoop中用readline()函数以“行”为单位接收信息。AT主要有 如下两种respon方式。 ● 主动上报:比如网络状态、短信和来电等都不需要经过请求,此方式用unsolicited来专门描述 。 ● 真正意义上的respon:即命令的响应。 此时可以看到所有的“行”都是经过SMS自动上报筛选的,因为短信的AT处理通常比较麻烦,无论收 发都单独列出。这里是因为要即时处理这条短信消息(两行,标志+pdu),而不能拆开处理。处理 函数是onUnsolicited()。 除SMS特例之外,所有的line都要经过processLine处理,来看下面的流程。

在此需要重点关注handleUnsolicited自动上报和switch s_type具体响应信息。另外具体响应需要 handleFinalRespon这样的标准响应来最终完成。 (1)onUnsolicite(主动上报响应) 实现函数如下。

respon的主要的解析过程是由文件at_tok.c中的函数完成的,其实就是字符串按块解析,具体的解 析方式由每条命令或上报信息自行决定。onUnsolicited只解析出头部(一般

是+XXXX的形式),然后 按类型决定下一步操作,操作方式有RIL_onUnsolicitedRespon和RIL_requestTimedCallback两种 。 ● RIL_onUnsolicitedRespon 将unsolicited信息直接返回给上层。通过Parcel传递,将 RESPONSE_UNSOLICITED,unsolRespon(request号)写入Parcel,然后通过s_unsolRespons数组 ,查找到对应的responFunction完成进一步的解析,存入Parcel中。最终通过ndRespon将其传 递回原进程。具体流程如下所示。

● RIL_requestTimedCallback 通过event机制实现的timer机制,回调对应的内部处理函数。通过internalRequestTimedCallback将 回调添加到event循环,最终完成callback上挂的函数的回调。例如pollSIMState和 onPDPContextListChanged等回调不用返回上层,直接在内部处理就可以实现。 (2)switch s_type命令的具体响应及handleFinalRespon标准响应 命令类型(s_type)在nd command时设置,具体有NO_RESULT、NUMERIC、SINGLELINE和 MULTILINE几种类型供不同的AT使用。这几个类型的解析方式类似,通过比较AT头标记等判断处理 ,如果是对应的响应,就通过addIntermediate挂到一个临时结果sp_respon-> p_intermediates队 列里。如果不是对应响应,那么应该是穿插其中的自动上报,用onUnsolicite来处理。具体响应只是 起了一个获取响应信息到临时的结果,需要等待具体分析的作用。无论有无具体响应,最终都以标准 响应handleFinalRespon来完成,例如会接受到OK、ERROR等标准响应来结束,这是大多数AT命令的 规范。

x0c第19章 其他系统 本书前面的内容中已经讲解了Android平台中主要的和硬件相关的驱动系统。其实除了已经讲解的驱 动系统外,在Android平台中还有其他几种常见的系统。本章将简单讲解Android平台中其他几种常见 的驱动系统,分别是警报器系统、光系统和电池系统。希望通过本章内容的学习,为本书画上一个圆 满的句号。

x0c19.1 Alarm警报器系统 在Android系统中,警报器系统又叫时钟系统或闹钟系统。Alarm闹钟是Android系统中在标准RTC驱动 上开发的一个新的驱动,提供了一个定时器用于把设备从睡眠状态唤醒,当然因为它是依赖RTC驱动 的,所以它同时还可以为系统提供一个掉电时还能运行的实时时钟。当系统断电时,主板上的RTC芯 片将继续维持系统的时间,这样保证再次开机后系统的时间不会错误。当系统开始时,内核从RTC中 读取时间来初始化系统时间,关机时又将系统时间写回到RTC中,关机阶段将有主板上另外的电池来 供应RTC计时。Android中的Alarm在设备处于睡眠模式时仍保持活跃,它可以设置来唤醒设备。

x0c19.1.1 Alarm系统的结构 Android平台中Alarm系统的基本层次结构如图19-1所示。

图19-1 Alarm系统的基本层次结构

由图19-1可知,Android平台中Wi-Fi系统从上到下主要包括:AlarmManager、AlarmManagerService Java、AlarmManagerService JNI、Alarm驱动程序和实时时钟(RTC)驱动程序,这几部分的系统结 构如图19-2所示。

x0c图19-2 Alarm的系统结构 图19-2中各个部分的具体说明如下所示。 (1)RTC驱动程序 Linux的Alarm驱动程序代码路径在内核的drivers/rtc/目录中,各个硬件的具体实现不同。 (2)Alarm驱动程序 这是Android特定内核的组件,能够调用RTC系统的功能,但是本身和硬件无关。 (3)本地JNI部分 代码路径如下所示。

此文件是Alarm部分的本地代码,同时提供了JNI的接口。 (4)Java部分 此部分的代码路径如下所示。

在文件中实现了包中的AlarmManagerService,在文件 中实现了包中的AlarmManager类,它通过使用 AlarmManagerService服务实现,并对Java层提供了平台API。

x0c19.1.2 需要移植的内容 Android的Alarm系统的Java层、本地部分的代码都是标准的,所以不需要更改。内核中的Alarm驱动 程序与硬件无关,在Android系统中都是相同的。所以警报器系统的移植实际上就是RTC驱动程序的移 植。RTC驱动程序是Linux中一种标准的驱动程序,它在用户空间也提供了设备节点(自定义的字符设 备或MISC字符设备)。根据Android系统的情况,不直接使用RTC驱动程序,而是通过Alarm驱动程序 调用RTC系统,而Android系统的用户空间只调用Alarm驱动程序。

x0c19.1.3 移植和调试 1.RTC驱动程序 RTC驱动程序RTC是Linux中标准的Alarm驱动程序框架。驱动程序的框架内容在内核文件 inlcude/linux/rtc.h中定义。首先在此文件中定义了如下两个函数,功能是分别实现注册和注销 RTC设备。具体代码如下所示。

然后定义结构体rtc_class_ops,具体代码如下所示。

结构体struct rtc_device是对struct device的扩展,是在RTC驱动程序中使用的,其中也包含了 rtc_class_ops结构。RTC驱动程序的实现实际上就是实现rtc_class_ops中的函数指针,主要包括时 间和警报器这两方面的内容。 在用户空间中,可以通过RTC驱动程序的设备节点对其进行调试,调试的方法是通过ioctl命令实现的 。这些命令是在文件rtc.h中定义的,以RTC开头。例如下面就是4个命令。

2.Alarm驱动程序 Alarm驱动程序为用户空间提供了设备节点/dev/alarm,这是一个主设备号为10的Misc字符设备,并 且其次设备号是动态生成的。Alarm驱动程序是由内核代码中的如下文件实现的。

在头文件include/linux/android_alarm.h中提供了到用户空间的各ioctl命令接口。在Alarm设备的 Suspend和Resume过程中,通过调用RTC中的函数rtc_read_time()和rtc_t_alarm()进行操作,表示 通过RTC系统存

/取当前的状态。主要代码如下所示。

x0c3.应注意的几个问题 Alarm在用户空间中的本地JNI部分的代码保存在frameworks/ba/rvices/jni/目录中,由文件 com_android_rver_实现。此文件调用了Alarm驱动程序,向上层提供了 JNI的接口。其中方法列表如下所示。

在此提供了对包中的AlarmManagerService类的支持。

在Java代码方面,通过如下文件实现了中的AlarmManagerService类。

此文件调用了JNI部分的代码,实现了一个Android中的服务。AlarmManagerService等服务类一般不 作为平台的API给Java应用程序的层使用。文件 frameworks/ba/core/java/android/app/是包中的AlarmManager类 ,这个类是平台的API。文件配合同一目录中的联合使用,实 现调用服务内容的功能。

x0c19.1.4 模拟器环境的具体实现 Alarm系统和其他系统相比,比较特殊的是只有模拟器的RTC驱动程序,具体是在文件 drivers/rtc/rtc-goldfish.c中实现的。GoldFish的Alarm驱动由模拟器的虚拟环境触发中断,并填 充相关的寄存器,在驱动程序中取得信息。 函数goldfish_rtc_read_time()用于获取当前的时间,具体代码如下所示。

在上述代码中,通过读取TIME_TIME_LOW和TIME_TIME_HIGH这两个虚拟寄存器来获得当前的时间。

x0c19.1.5 MSM平台实现Alarm 在MSM平台中,Alarm驱动程序是在文件drivers/rtc/rtc-MSM7k00a.c中实现的,具体的功能是调用 RPC(远程过程调用)完成的。通过函数msmrtc_probe()实现探测初始化,具体代码如下所示。

msm_rtc_ops是一个rtc_class_ops类型的结构体,其中实现了成员read_time、t_time和 t_alarm。其中函数msmrtc_timeremote_read_time()负责读取当前的时间。具体代码如下所示。

具体功能的实现过程是通过统一的RPC在远端完成的,此处调用的是RPC的 TIMEREMOTE_PROCEEDURE_GET_JULIAN命令。 函数msmrtc_suspend()用于实现此驱动程序模块的挂起功能。在进行处理时,先根据所设置的警报器 时间得到距离系统现在时间的差值,然后调用PM(电源管理)函数msm_pm_t_max_sleep_time()把 数值设置为睡眠的时间。这样在时间到达的时候就可以进行PM系统的唤醒。

x0c19.2 Lights光系统 在Android平台中,Lights光系统用于统一控制系统中的各个光源,例如屏幕背光、键盘按键光、电 池光等。Lights光系统基本上是一个用于输出控制的系统。Lights光系统从驱动程序、硬件抽象层、 本地框架到Java层都具有内容,但是没有向应用程序层直接提供API。

x0c19.2.1 Lights光系统的结构 Android平台中Lights光系统的基本层次结构如图19-3所示。

图19-3 Lights光系统的基本层次结构 Lights光系统的结构如图19-4所示。

x0c图19-4 Lights

光系统的系统结构 图19-4中各个部分的具体说明如下所示。 (1)驱动程序 特定硬件平台光系统的驱动程序,可以使用Linux中的LED驱动程序实现。 (2)硬件抽象层 光系统硬件抽象层的接口路径为如下所示。

此文件是Android中一个标准的硬件模块。 (3)本地类 代码路径如下所示。

本地类调用了硬件抽象层,同时提供了JNI的接口。 (4)Java类 代码路径如下所示。

x0c文件通过调用LightsService JNI来实现包中的 LightsService类。此类不是平台的API,被Android系统Java框架中的其他一些部分调用。

x0c19.2.2 需要移植的内容 在移植Android光系统时,需要移植光系统的硬件抽象层和驱动程序这两个方面的内容。光系统本身 只有简单的控制功能,所以其驱动程序的实现过程比较简单。在Linux系统中,LED驱动程序框架可以 作为光系统驱动程序。LED驱动程序可以提供给用户空SYS文件系统作为接口。 光系统的硬件抽象层是Android中一个标准的硬件模块,其底层和Android系统本地部分的接口,这部 分内容的实现功能是控制各个光源的状态。

x0c19.2.3 移植和调试 1.驱动程序 Android的光系统的驱动比较简单,可以使用字符设备的文件来操作file_operation和SYS文件系统等 。在Linux系统中,LED驱动程序框架比较适合作为光系统,其头文件是linclude/linux/leds.h。在 Linux菜单配置的Device Drivers选项中,可以打开NEW_LEDS和LEDS_CLASS以获取LED驱动的支持。 在文件leds.h中,使用结构体led_classdev来表示LED设备,这是LED驱动程序的核心。定义此结构体 的代码如下所示。

当实现了一个led_classdev并完成注册后,系统会出现一个led设备,同时在SYS文件系统的 /sys/class/leds/目录中出现一个名为led_classdev的设备名称成员的子目录。在每个设备子目录中 将出现一个brightness文件,通过读写这个文件可以获得LED设备的亮度。 对于LED驱动程序来说,在调试时可以直接读写SYS文件系统中相应设备的brightness文件。 2.硬件抽象层 Android光系统的硬件抽象层是一个Android标准硬件模块,其接口在如下文件中定义。

在其中定义结构体ight_state_t来表示光设备的状态,具体代码如下所示。

然后定义光设备结构体light_device_t,具体代码如下所示。

x0c实现Android光系统的硬件抽象层的方法比较简单,只需实现相应的设置接口即可。相对于Android中 的其他硬件模块,光系统的主要特点是需要为每一个光源实现一个设备light_device_t。当在 Android中打开模块函数(hw_module_methods_t中的open)时,需要通过参数“返回”一个 light_device_t类型的指针。这个指针表示的是一个光设备,通过模块的打开函数中指定的名称来

确 定得到哪一个设备。当实现光系统的硬件抽象层后,将生成名称为的动态库,可以 将其放置在目标文件系统的/system/lib/hw目录中。 3.上层实现 在Android光系统中,本地代码和JNI部分在如下文件中实现。

文件com_android_rver_提供了对Java层次的JNI接口,其定义方法列表如下所 示。

然后定义一个light_device_t类型的结构体Devices,代码如下所示。

还需要定义函数tLight_native()来设置光线的情况,具体代码如下所示。

x0c19.2.4 MSM平台实现光系统 在MSM的mahimahi平台中,虽然有不同的发光源,但是这些不同的硬件统一实现了几个 led_classdev设备。 MSM的背光设备在文件中定义,主要代码如下所示。

Android光系统的硬件抽象层在以下目录中实现。

在此需要定义函数t_light_backlight()来实现背光设备的t_light,具体代码如下所示。

LCD_FILE定义为SYS文件系统中可以控制设备的文件的路径。由于在light_state_t中的参数是 color,表示32位的ARGB数据,而背光硬件只支持亮度,因此使用rgb_to_brightness()函数将RGB转 化为亮度的整数值。其他光源还包括键盘、按键、电池等实现方式,和背光的实现类似,都是通过 SYS文件系统控制驱动完成的。

x0c19.3 Battery电池系统 在Android系统中,使用电池的方式有很多种,例如有AC、USB、Battery等不同的模式。在应用程序 层次中,通常包括电池状态显示的功能。因此在Android系统的软件方面需要在一定程度上获得电池 的状态,例如包括驱动程序和用户空间内容。Android系统中的电池系统主要负责统计电池信息。

x0c19.3.1 Battery系统的结构 Android平台中Battery系统的基本层次结构如图19-5所示。

图19-5 Battery系统的基本层次结构 由图19-5可知,Android平台中Wi-Fi系统从上到下主要包括:Java框架、本地JNI程序和底层驱动程 序,这几部分的系统结构如图19-6所示。

x0c图19-6 Battery的系统结构 图19-6中各个部分的具体说明如下所示。 (1)驱动程序 特定硬件平台电池的驱动程序,可以使用Linux的Power Supply驱动程序向用户空间提供信息。 (2)本地JNI 代码路径如下所示。

在此文件的类中调用SYS文件系统访问驱动程序,同时提供了JNI的接口。 (3)Java代码 此部分的代码路如下所示。 ● frameworks/ba/rvices/java/com/android/rver/Batter 。 ● frameworks/ba/core/java/android/os/:包中和Battery相关的部分。 ● frameworks/ba/core/java/com/android/internal/os/:和Battery相关的内部部分。 文件通过调用BatteryService JNI来实现包中的类 BatteryService。在文件中定义Java应用程序层可以使用的常量。

x0c19.3.2

需要移植的内容 在移植Android电池系统时,在驱动程序层以上的部分都是Android系统中默认的内容。在移植的过程 中基本不需要改动,电池系统需要移植的部分仅仅是Battery驱动程序。Battery驱动程序使用了 Linux标准的Power Supply驱动程序,此驱动程序与上层的接口是SYS文件系统,功能是读取SYS文件 系统中的文件来获取电池相关的信息。

x0c19.3.3 移植和调试 1.驱动程序 Android的Battery驱动程序需要使用SYS文件系统向用户空间提供接口,SYS系统的路径是由上层的程 序指定的。在Linux的标准Power Supply驱动程序中,使用的文件系统的路径如下所示。

在上述目录中,每个子目录表示一种能源供应设备的名称。 驱动程序Power Supply的头文件是include/linux/power_supply.h,注册和注销驱动程序的函数如下 所示。

其中结构体power_supply是驱动程序需要实现的部分,主要代码如下所示。

2.上层实现 文件com_android_rver_是Battery部分的本地和JNI代码,此文件被保存在如 下目录中。

在文件com_android_rver_中,提供方法列表的代码如下所示。

这是com_android_rver包中的BatteryService类的本地方法,只存在一个名为native_update()的 函数,此函数既没有参数,也没有返回值。 再看函数android_rver_BatteryService_update(),其实现代码如下所示。

x0c接下来需要定义函数register_android_rver_BatteryService(),此函数用于得到各种属性的处理 。具体代码如下所示。

x0cx0c上述代码的处理流程是:根据设备类型判定设备后,得到各个设备的相关属性,例如是交流或者 USB设备,则只需得到它们是否在线(onLine);如果是电池设备,则需要得到更多的信息,例如状 态(status)、健康程度(health)、容量(capacity)和电压(voltage_now)等。 文件frameworks/ba/rvices/java/com/android/rver/实现了 d. rver包中的BatteryService类,此类本身继承了Binder。文件 frameworks/ba/core/java/android/os/实现了Battery系统对应用程序层的 常量。 另外,在文件frameworks/ba/core/java/com/android/internal/os/中 ,和是其核心实现内容。和Battery相关的信息是通过广 播的方式由Java框架层传递给Java应用程序层的。在Java应用程序层中可以使用 BroadcastReceiver广播接收器来获取和电池相关的信息。

x0c19.3.4 在模拟器中实现电池系统 在Goldfish内核模拟器环境中实现了电池部分的驱动程序,实现文件为 drivers/power/goldfish_battery.c。在此驱动程序中注册了两个Power Supply设备,分别是 battery和ac。在函数goldfish_battery_probe()中构造了一个power_supply类型的结构来调用函数

power_supply_register(),并将其注册到系统中。 定义函数goldfish_battery_interrupt(),在此驱动中实现中断处理,此处使用的中断号是17。此函 数的实现代码如下所示。

此处的处理内容是来自模拟器的虚拟寄存器和虚拟中断。当Power Supply发生变化的时候,模拟器环 境将自动更新虚拟寄存器信息,并触发电池相关的中断,由本驱动程序读取虚拟寄存器,然后通过 Power Supply驱动框架更新SYS文件系统中的内容。

x0c

本文发布于:2022-12-30 12:20:11,感谢您对本站的认可!

本文链接:http://www.wtabcd.cn/fanwen/fan/90/59716.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

上一篇:春游作文500字
下一篇:朗诵的技巧
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图