由於工作關系,對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://www.linuxidc.com/Linux/2012-09/70046.htm
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中各塊驅動的流程。