转载:
由于工作关系,对Android关注将从FWK(Framework)转向BSP,也就是Linux Kernel。在工作的5年中,曾经数次研究过kernel,但一直没有合适的机会或者说推动力去深入研究。这次有机会了,岂能放过呢?
以前搞kernel,总是觉得没有合适的设备,都玩不转。最近琢磨了几天,打算从android虚拟设备goldfish开始吧。(惭愧啊,以前还买过一个板子,结果完了2天就腻味了)。
本随笔包括一下几个部分:
- 先介绍Android kernel的下载和编译。
- 配置模拟器以使之使用我们编译的kernel。
- 介绍下输入系统方面的内容。我的目标是在最短的时间内把Android的驱动撸一遍。在这个过程中,流程,模块之间的关系最重要。细节问题到以后碰到具体情况时再来深入研究。
一 Android GoldFish kernel下载和编译
老方法,用git下载。kernel和非kernel代码不在一个git库中,Android的代码由repo下载,而kernel得单独用git下载。goldfish的代码下载方法如下:
- 先在Android JB源码根目录下建立kernel目录。
- cd kernel,然后git clone http://android.googlesource.com/kernel/goldfish.git (还可以下载高通的msm,普通common及omap分支的kernel)
- 下载完成后,得到kernel/goldfish目录。cd kernel/goldfish
- git branch -a,查看所有分支。里边有2.6.29以及3.4的
- git checkout -b 2.6.29 remotes/origin/android-goldfish-2.6.29 建立本地分支2.6.29 用以跟踪远程的android-goldfish-2.6.29分支。此时goldfish目录下就有文件了。
下面就来编译。假设我们已经下载了JB源码。
- 还是在kernel/goldfish目录下。执行make ARCH=arm goldfish_armv7_defconfig 这个命令执行前,make将到arch/arm/config下读取goldfish_armv7_defconfig文件,获得板卡(恩,没有真实板卡,有一块虚拟的板卡)相关的编译配置文件(无非就是定义一些宏,使能kernel一些功能模块,驱动等等)。该命令执行完后,将得到一个.config文件。
- 设置环境变量export CROSS_COMPILE=Anroid-JB/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin/arm-eabi- 这个是设置交叉编译工具链的位置和前缀。这样在编译kernel时将使用这个前缀+gcc相关工具来编译kernel
- 然后就是make ARCH=arm。编译完成后最后一个输出就是 Kernel: arch/arm/boot/zImage is ready zImage就是最后编译得到的kernel内核镜像。关于kernel内核镜像的组成,请参考我的前一遍博文http://blog.csdn.net/innost/article/details/6693731
OK,到此我们就到得自己编译的kernel了。下面就是找个机器把它烧进去并启动之。由于我们没有真机,那就找模拟器吧。
二利用Android模拟器加载goldFish kernel
我在JB源码目录下建立了一个android emulator脚本,各位看看其内容:
#!/bin/sh
/disk/android/android-sdk-linux_86/tools/emulator #这是android emulator的文件位置
-avd 4.1 #启动4.1这个机器,我之前已经用AVD工具制造了一个名叫4.1的机器
-system /Android-4.1/out/target/product/generic/system.new.image #我自己定制了一个极简单的system.image,里边只有5个APK,这样启动速度贼快。此参数用来指定该机器运行的system镜像文件
-kernel /Android-4.1/kernel/goldfish/arch/arm/boot/zImage #此参数用来指定kernel镜像文件。现在已经指向我自己编译的kernel了
-ramdisk /thunderst/work-branches/Android-4.1/out/target/product/generic/ramdisk.new.img #我也重新定制了ramdisk,修改了其中的init程序。此参数指定ramdisk镜像文件
-partition-size 512 #指定system和data分区大小为512MB
& #后台运行
有了这个脚本,我就happy了。感觉比烧真机再启动要爽、快多了。
解释下ramdisk。ramdisk我们的android根目录的一个压缩表达文件。其操作如下:
- 假设已经有一个x夹文件,现在想把它打包成ramdisk文件。
- 首先读取x文件中的目录信息,然后写到结果文件ramdisk.temp。然后遍历x目录的所有文件(直接open,然后read吧,管你是二进制还是啥文本文件)。所有读取的数据都写到一个最终文件中。假设是ramdisk.temp. 这其实是得到一个archive的过程
- 再用gzip压缩此ramdisk.temp,得到ramdisk.image(后缀名是自己取的)。
如果现在已经有了一个ramdisk.image,如何还原它呢?
- 可用file ramdisk.image看看此文件信息,发现它是一个gzip压缩的文件(正如上面所讲)。gunzip ramdisk.image就可以了。
- 然后建立一个文件夹,mkdir test,并cd test
- cpio -i -F ../ramdisk 这样,刚才那个ramdisk.image就反archive到test目录了。以前x目录中的内容又回到test目录下了。
ramdisk下基本就是android 根目录的内容,例如init,init.rc等等。所以,如果你在模拟器上改了这些文件,重启机器后也没有用。因为这个根目录是解压ramdisk后得到的,而原始的ramdisk并不会得到修改。所以,如果你要修改根目录下的内容,那只能重新制作ramdisk了。方法就是上面讲的,非常非常简单。【请阅读《Embed Liux Primer》一书】
三 Android goldFish输入设备
3.1 /dev/input/event0的来历
说实话,我刚开始唯一知道的就是FWK中读取输入事件的是在EventHub的getEvents中,里边将打开/dev/input/event0设备。从此往上溯源。event0这个设备按道理应该是通过ueventd这种方式自动生成的。系统里边倒是有一个ueventd,在sbin下,可惜这是一个链接,由指向了/system下的init。init可以处理ueventd事件?我印象中2.2好像没这么搞。那有可能是之后的版本了。
查看init的代码,果然里边有个if分支将走向ueventd_main,这里就是打开ueventd.xxx.rc文件。这个文件和我之前理解的不太一样。它就是根据配置文件建立/dev/下的设备文件,并设置权限。
根据Ueventd.c的代码,当收到kernel报上来的属于input设备的事件后,将在/dev/input下按uevent传入的path名建立一个文件。【这部分代码需要兄弟们好好看看,不难。但以后如果有需要修改的话,事先了解下流程也行】
3.2 是谁发出了输入的uevent事件呢?
这个..我还真是第一次接触相关代码,只能靠野蛮搜索了。
- driver/input/input.c中的input_init函数建立了input输入系统的相关框架。
- 这个文件中定义了一个函数input_register_handler,用于注册输入事件处理handler。没办法,野蛮搜索cgrep input_register_handler。有较多地方会注册这个处理事件。但我重点关注evdev和keyboard的地方。用source insight打开这两个文件,加上一些printk输出。给个示例图:
图1 野蛮搜索使用input_register_handler的地方。重点关注evdev和keyboard
- 在那两个文件中,加上一点输出。(kernel的一些基本API还是需要知道的吧?建议阅读linux driver develop的第三版。)
- goldfish也有一个通用的driver,叫driver/input/keyboard/goldfish_events.c,其中它会注册一个platform_driver。platform_driver方面有一些基本的API,大家上网查查就知道用法了。和嵌入式系统关系很大。这个driver中有一个events_probe函数,用来判断哪些device可以交给goldfish_event driver来处理。这个应该是goldfish专用的driver。它应该和上面介绍的input是两个不同的东西。(我目前认为:input是input系统的一些通用框架,而goldfish_events是一个driver,它将探测一些device,然后再将这些device注册到input框架中。应该是这样,暂时不细研究了)。
- 大家可看看此驱动的events_probe函数,它将探测到一个qwerty2设备,然后注册到input框架中。图示如下:
图2 探测到一个设备,keymap为qwerty2,然后注册到input框架中
- 继续跟踪events_probe函数,里边有大量和input框架交互的地方。比较重要的一点就是为刚才那个qwerty2设备设置一些handler。从图1可知,两个重要的handler就是evdev和keyboard。
- 分别在这个两个文件中加一点输出。发现evdev中有个poll函数,而EventHub也会调用poll函数获取输入事件。从此可知,evdev这个handler将数据传递给EventHub。
3.3 小结
此趟目标还算是达到了,把输入事件的产生流程搞清楚了,这里简单总结如下:
- goldfish_events注册一个platform_driver。当它探测到输入设备时候,就会往input系统中注册
- kernel会往input设备中注册一些handler。一个设备可以有多个串行的handler
- goldfish_events将设置一个输入事件中断函数events_interrupt,当有事件来时候,该函数会将信息投递给input框架处理(调用input_event函数)
- input_event函数将调用各个handler处理之。对于goldfish来说,最重要的handler就是evdev,它把信息整理并上报给EventHub。
四总结
本随笔的目标:
- 搭建一个虚拟设备环境,以及编译goldfish kernel并运行之。
- 简单理顺了BSP中input相关的流程。
再次强调说明:这一系列的随笔是快速理顺Android BSP中各块驱动的流程。