由於工作關係,對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中各塊驅動的流程。