天天看點

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

本節書摘來自異步社群《android深度探索(卷1):hal與驅動開發》一書中的第6章,第6.4節使用多種方式測試linux驅動,作者李甯,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

6.4 使用多種方式測試linux驅動

android深度探索(卷1):hal與驅動開發

在上一節已經實作了一個簡單的linux驅動程式,該驅動的功能是統計給定字元串中的單詞數,并且在最後已經将該linux驅動的源代碼成功編譯成動态linux驅動子產品word_count.ko。下一步就是測試該子產品。測試的方法很多,最常用的就是直接在ubuntu linux中測試。當然,這對于本章實作的linux驅動是沒問題的,但是對于需要直接通路硬體的驅動在ubuntu linux上測試就不太友善。在這種情況下就需要在相應的硬體上進行測試。

對于一個linux驅動程式,一開始可以在ubuntu linux上做前期開發和測試。對于通路硬體的部分也可以在ubuntu linux用軟體進行模拟。當基本開發完成後,就需要在開發闆或工程樣機上使用真實的硬體進行測試。當然,最後還需要在最終銷售的手機上進行測試。最終測試通過,linux驅動才能算真正開發完成。在開發linux驅動的過程中一個重要的步驟就是測試。本節将結合實際的開發流程介紹在不同平台上測試linux驅動程式。這些測試平台包括ubuntu linux、android模拟器和s3c6410開發闆。

6.4.1 使用ubuntu linux測試linux驅動

本節将介紹如何在ubuntu linux下測試驅動程式。由于上一節編寫的linux驅動程式通過4個位元組從裝置檔案(/dev/wordcount)傳回單詞數,是以不能使用cat指令測試驅動程式(cat指令不會将這4個位元組還原成int類型的值顯示)。但可以使用如下指令從日志中檢視單詞數。

執行上面的指令後,如果輸出如圖6-13所示白框中的資訊,說明驅動程式成功統計了單詞數。

雖然使用echo和dmesg指令可以測試linux驅動程式,但這種方式并不是真正的測試。為了使測試效果更接近真實環境,一般需要編寫專門用于測試的程式。本節将為word_count驅動編寫一個專門的測試程式(test_word_count.c)。test_word_count.c通過直接操作/dev/wordcount裝置檔案與word_count驅動進行互動。測試程式的代碼如下:

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

test_word_count程式可以跟1個指令行參數(多個指令行參數隻會使用第1個指令行參數)。如果指令行參數值含有空格,需要使用單引号(')或雙引号(")将參數值括起來。可以使用下面的一組指令測試word_count驅動程式。

執行上面的指令後,如果輸出如圖6-14所示的資訊(假設word_count以前統計過一個含有4個單詞的字元串),表示word_count驅動成功測試。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

6.4.2 在android模拟器上通過原生(native)c程式測試linux驅動

雖說我們開發的是linux驅動,但本書主要介紹的是android版的linux核心,是以,linux驅動隻在ubuntu linux上測試成功還不能保證在android裝置上一定能正常工作,是以必須在android裝置上進行測試。android裝置有很多種類,如安裝了android的開發闆、運作android系統的手機或平闆電腦等。但離我們最近的并不是這些硬體裝置,而是android模拟器。android模拟器可以模拟絕大多數真實的環境,是以可以利用android模拟器測試linux核心。

在android模拟器上測試linux驅動首先應該想到的,也是最先應該做的就是将word_count.ko驅動子產品安裝在模拟器上。可能讀者使用過adb shell指令。如果進入android模拟器的指令提示符為“#”,說明通過指令行方式進入android模拟器直接就是root權限(指令提示符為“$”,表示非root權限),是以從理論上可以使用insmod指令将word_count.ko驅動子產品直接安裝在android模拟器中。現在我們來測試一下,看看是否可以将word_count.ko安裝在android模拟器上。現在執行build.sh腳本,并選擇“android模拟器”,腳本會自動将word_count.ko檔案上傳到android模拟器的/data/local目錄,并進行安裝。如果讀者選擇的是s3c6410開發闆,在安裝word_count.ko時就會輸出如下的錯誤資訊,表示編譯linux驅動的linux核心版本與目前android模拟器的版本不相同,無法安裝。是以在編譯linux驅動時,必須選擇與目前運作的linux核心版本相同的linux核心進行編譯,否則就無法安裝linux驅動。

注意

建議上傳檔案到android模拟器或開發闆時,将檔案放到/data/local目錄,系統很多其他的目錄,如/system/bin,都是隻讀的,除非将word_count.ko檔案打包進system.img,否則無法向這些目錄寫資料,即使有root權限也不行。

用于android模拟器的goldfish核心預設不允許動态裝載linux驅動子產品,是以需要在編譯linux核心之前執行如下指令配置linux核心。

執行上面的指令後,會出現如圖6-15所示的設定界面。按空格鍵将第二項“enable loadable module support”選中(前面是[*]),然後按Enter鍵進入子菜單,選中前3項,如圖6-16所示,否則linux驅動子產品仍然無法安裝和解除安裝。當退出設定菜單時保持設定。最後按節的方法重新編譯linux核心,成功編譯核心後,android模拟器可以使用新生成的zimage核心檔案動态裝載linux驅動子產品。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

現在執行build.sh腳本檔案完成對word_count驅動的編譯、上傳和安裝的工作,然後進入android模拟器的終端,使用echo和dmesg指令可以測試word_count驅動和檢視測試結果,方法與上一節相同。

編譯可在android模拟器上運作的linux驅動子產品要使用goldfish核心,使用其他的核心編譯word_count.c,安裝時會出現如下錯誤。

insmod: error inserting 'word_count.ko': -1 invalid module format

在android模拟器上不僅可以使用linux指令測試驅動,也可以像ubuntu linux一樣使用本地c/c++程式進行測試。可能有的讀者要問,android不是隻能運作由java編寫的apk程式嗎?頂多是在apk程式中嵌入ndk代碼。還能直接運作普通的linux程式嗎?答案是肯定的。不過要滿足如下兩個條件。

android模拟器、開發闆或手機需要有root權限。

可執行檔案需要使用交叉編譯器進行編譯,以便支援arm處理器。

現在使用交叉編譯器來編譯在上一節編寫的test_word_count.c檔案。為了使編譯步驟盡可能簡單,我們使用android.mk設定編譯參數,并使用make指令進行編譯。首先在/root/drivers/ch06/word_count目錄中建立一個android.mk檔案,并輸入如下的内容。

android.mk檔案中有如下兩個地方需要說明一下。

表示目前工程(android.mk檔案所在的目錄)在什麼模式下編譯。如果設為optional,表示不考慮模式,也就是說,在任何模式下都會編譯。該變量可以設定的值有user、userdebug、eng、optional。其中eng是預設值。

user:限制使用者對android系統的通路,适合于釋出産品。

userdebug:類似于user模式,但擁有root通路權限,并且可以從日志中擷取大量的調試資訊。

eng:一般在開發的過程中設定該模式。除了擁有userdebug的全部功能外,還會帶有大量的調試工具。

local_module_tags的值與target_build_variant變量有關。target_build_variant變量用于設定目前的編譯模式,可設定的值包括user、userdebug和eng。如果想改變編譯模式,可以在編譯android源代碼之前執行如下指令。

其中full表示建立的目标,除了full目标(為所有的平台建立)外,還有專門為x86建立的full-x86。詳細的建立目标執行lunch指令後就會列出。在圖4-8已經顯示了android4支援的建立目标的編譯模式。讀者可以到第4章檢視該圖。

build_executable表示建立可執行的檔案。可執行檔案路徑是< android源代碼目錄 >/ out/target/product/generic/system/bin/test_word_count。如果想編譯成動态庫(.so)檔案,可以使用include $(build_shared_library)。動态庫的路徑是< android源代碼目錄 >/ out/target/product/ generic/system/lib/test_word_count.so。如果想編譯成靜态庫(.a)檔案,可以使用include $(build_static_library)。靜态庫的路徑是< android源代碼目錄 >/ out/target/product/generic/ obj/static_libraries/test_word_count_intermediates/test_word_count.

為了将test_word_count.c檔案編譯成可在android模拟器上運作的可執行程式,可以将word_count目錄複制到< android源代碼目錄 >的某個子目錄,也可以在< android源代碼目錄 >目錄中為word_count目錄建立一個符号連結。為了友善,我們采用如下指令為word_count目錄在< android源代碼目錄 >/development目錄建立一個符号連結(假設android源代碼的目錄是/sources/android/android4/development/word_count)。

成功編譯後可以在< android源代碼目錄 >/out/target/product/generic/system/bin目錄中找到test_word_count檔案。在随書CD光牒和模拟環境中已經帶了編譯好的test_word_count程式(包括emulator版本和ubuntu linux版本),可執行程式一般不需要考慮linux核心的版本,用交叉編譯器編譯的支援arm處理器的程式既可以在android模拟器上運作,也可以在s3c6410開發闆或其他有root權限的手機中運作。

現在執行下面的指令将test_word_count檔案上傳到android模拟器。

執行上面的指令後,如果輸出的單詞個數是5,表示程式測試成功。

6.4.3 使用android ndk測試linux驅動

在android系統中linux驅動主要的使用者是apk程式。是以,linux驅動做完後必須要用apk程式進行測試才能說明linux驅動可以正常使用。由于上一節在android虛拟機上使用c語言編寫的可執行程式測試了linux驅動,是以很容易想到可以利用android ndk來測試linux驅動,

由于android ndk也使用c/c++來編寫程式,是以可以利用上一節的c語言代碼,當然,還得加上一些android ndk特有的代碼。在使用android ndk測試linux驅動之前需要做如下兩件事。

由于linux驅動子產品不會随android系統啟動而裝載,是以必須執行build.sh腳本檔案安裝word_count驅動。

不能使用預設方式啟動android模拟器,而要使用我們自己編譯的linux核心啟動android模拟器,啟動模拟器的指令如下:

為了友善,讀者也可以在随書CD光牒的ubuntu linux虛拟環境中直接執行如下的指令來啟動android模拟器。其中emulator.sh檔案在/root/drivers目錄中。

word_count_ndk工程的代碼部分由wordcountndktestmain.java和ndk_test_word_count.c檔案組成。工程結構如圖6-17所示。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

ndk_test_word_count.c檔案用于通路word_count驅動。該檔案包含兩個供java通路的函數,分别用來讀取/dev/wordcount裝置檔案中的單詞數和向/dev/wordcount裝置檔案寫入字元串。下面先看看ndk_test_word_count.c檔案的完整代碼。

編寫上面的代碼有一個重點就是jstring_to_pchar函數。該函數可以将jstring類型的資料轉換成char類型的資料。轉換的基本思想就是調用java方法string.getbytes,擷取字元串對應的位元組數組(jbytearray)。由于write函數需要的是char 類型的資料,是以,還必須将jbytearray類型的資料轉換成char 類型的資料。采用的方法是,先将jbytearray類型的資料轉換成jbyte類型的資料,然後調用memcpy函數将jbyte類型的資料複制到使用malloc函數配置設定的char 指針空間中。在jstring_to_pchar函數中有如下的一行代碼。

看到getmethodid方法最後一個參數的值是"(ljava/lang/string;)[b",可能android ndk初學者會對此感到困惑,以為是寫錯了。實際上這是jni(android ndk程式實際上就是遵循jni規則的程式)對方法參數和傳回類型的描述。在jni程式中為了友善描述java資料類型,将簡單類型使用了一個大寫英文字母表示,如表6-1所示。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

在jni中調用java方法需要指定方法參數和傳回值的資料類型。在jni中的格式如下:

"(參數類型)傳回值類型"

getbytes方法的參數類型是string,根據表6-2的描述,string類型中jni的描述符是" ljava/lang/string; "。getbytes方法的傳回值類型是byte[]。這裡就涉及一個數組的表示法。在jni中數組使用左中括号([)表示,後面是數組中元素的類型。每一維需要使用一個“[”。byte[]是一維位元組數組,是以使用"[b"表示。如果是byte[][][],應使用"[[[b"表示。如果java方法未傳回任何值(傳回值類型是void),則用v表示。如void mymethod(int value)的參數和傳回值類型可表示為"(i)v"。

android ndk程式還需要一個android.mk檔案,代碼如下:

為了友善讀者在eclipse中開發android應用程式,本節的例子采用了節的方法進行配置。詳細的配置資訊請讀者檢視随書CD光牒或虛拟環境中的例子。虛拟環境中的所有配置和目錄位置與筆者寫作本書時使用的ubuntu linux的環境完全相同,讀者可直接運作程式。但在随書CD光牒中的例子需要将相關的路徑修改成讀者自己機器上的路徑。當然,如果恰巧讀者機器的環境與筆者完全相同,就不需要做任何修改了。

在編寫java代碼調用jni函數之前,先看一下本例的界面,如圖6-18所示。

讀者需要先在pc上運作build.sh腳本檔案安裝word_count驅動。然後單擊“從/dev/wordcount讀取單詞數”按鈕,會在按鈕下方輸出目前/dev/wordcount裝置檔案中統計出的單詞數。讀者也可以在輸入框中輸入一個由空格分隔的字元串,然後單擊“向/dev/wordcount寫入字元串”按鈕,再單擊“從/dev/wordcount讀取單詞數”按鈕,就會統計出字元串中包含的單詞數,效果如圖6-19所示。

下面看一下本例中java部分(wordcountndktestmain.java)的完整代碼。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

wordcountndktestmain.java中的代碼隻是簡單地調用了jni函數來操作/dev/wordcount檔案。其他的代碼都是正常的android應用級别的代碼。如果讀者對這部分不熟悉,可以參閱筆者所著的《android開發權威指南》。

6.4.4 使用java代碼直接操作裝置檔案來測試linux驅動

如果android擁有root權限,完全可以直接使用java代碼操作/dev/wordcount裝置檔案(沒有root權限,linux驅動子產品是無法安裝的)。本節将介紹如何使用java代碼來測試linux驅動(測試程式不使用一行c/c++代碼)。本節示例的路徑如下。

word_count_java工程中隻有一個源代碼檔案wordcountjavatestmain.java。該檔案的内容如下:

本例的運作效果和使用方法與上一節的例子類似。讀者可以運作随書CD光牒或虛拟環境中的例子與上一節的例子進行比較。

6.4.5 使用s6410開發闆測試linux驅動

前面幾節使用了不同方法來測試word_count驅動,但歸根結底都是在pc上進行測試。那麼本節将換一種平台來測試word_count驅動。當然,如果讀者有android手機的相應linux核心源代碼,也可以使用本節的方法在手機上測試word_count驅動。

6.4.2節、6.4.3節和6.4.4節中的例子都可以在s3c6410開發闆上運作(有的需要重新編譯,有的可以直接運作)。下面就挨個介紹如何使其在s3c6410開發闆上運作。

首先應打開s3c6410開發闆的電源開關,然後使用usb資料線連接配接s3c6410開發闆和pc。最後執行build.sh腳本檔案将word_count驅動安裝在s3c6410開發闆上。

1.在s6410開發闆上使用可執行程式測試linux驅動

由于在s3c6410開發闆運作的是android 2.3.4,是以,需要在android 2.3.4下使用6.4.2節的方法重新編譯test_word_count.c檔案。然後将編譯好的test_word_count程式上傳到開發闆。測試的方法與android模拟器相同。

本書的主題之一就是介紹如何将android移植到不同的硬體上。那麼使用test_word_count在不同硬體平台上運作實際上也是一種移植,隻不過這種移植并不是移植作業系統,而是移植應用程式,所有可稱為應用程式移植。最簡單的應用程式移植就是将應用程式源代碼編譯成可在不同目标平台運作的二進制檔案。當然,如果恰巧這些平台中都包含應用程式所使用的api,那麼直接在不同平台編譯即可(有時需要使用交叉編譯器)。但不幸的是,在很多時候,并不是所有的api在各個平台都有。有的api可能名字變化了,但有的api在某些平台根本就沒實作。面對這樣的情況,一般需要先移植這些api,然後再移植應用程式。應用程式移植在android系統中也會經常發生,如果某些特殊的android系統(基于arm晶片)需要一些用c語言實作的library或可執行程式,但android平台并沒有這些功能,而其他平台(如ubuntu linux)有這樣的程式,完全可以修改并重新編譯成arm平台的目标檔案放到android系統中。

2.在s6410開發闆上使用android ndk測試linux驅動

在eclipse中重新編譯節編寫的android ndk程式就可以在s3c6410開發闆上運作,測試方法與6.4.3節使用的方法相同,測試效果如圖6-20所示。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

▲圖6-20 在s3c6410開發闆上使用android ndk測試word_count驅動

3.在s6410開發闆上使用java代碼測試linux驅動

在6.4.4節編寫的測試word_count驅動的android程式可以使用同樣的方法在s3c6410開發闆上運作。測試效果與圖6-20類似。

6.4.6 将驅動編譯進linux核心進行測試

前面幾節都是将linux驅動編譯成子產品,然後動态裝載進行測試。動态裝載驅動子產品不會随着android系統的啟動而自動裝載,是以android系統每次啟動都必須使用insmod或modprobe指令裝載linux驅動子產品。

對于嵌入式系統(包括嵌入式android、嵌入式linux等)一般都采用将linux驅動編譯進核心的方式。這樣做雖然沒有動态裝載靈活,但linux驅動會随着android的啟動而自動裝載。一般在開發過程中為了測試和調試友善,會将linux驅動以子產品形式裝載到linux核心中。當linux驅動通過最終測試後,會将linux驅動編譯進linux核心再進行測試。

本節将介紹如何将word_count驅動編譯進linux核心,并分别在android模拟器和s3c6410開發闆上測試word_count驅動。

linux核心源代碼被設計成可裝卸式結構。也就是說隻需要修改配置檔案,就可以使某個linux驅動編譯成子產品(.ko檔案),或編譯進linux核心,當然,也可以将該linux驅動從linux核心去除。核心的配置檔案如下。

.config:該檔案位于linux核心源代碼的頂層目錄,為隐藏檔案。該檔案用于配置linux核心中的子產品。在.config檔案中可以對linux驅動進行三方面的配置:編譯成驅動子產品(.ko檔案)、編譯進核心和從linux核心去除。可以手工修改.config檔案,也可以使用make menuconfig指令用菜單方式來設定.config檔案。

kconfig:每一個想要連接配接進linux核心的子產品目錄都有該檔案。該檔案主要用于定義make menuconfig指令顯示的菜單(包括菜單項名稱、幫助資訊、選項類型、子產品依賴等資訊),除此之外,kconfig檔案還可以導入位于其他目錄的kconfig檔案。make指令通過kconfig檔案的遞歸引用,可以找到linux核心中的所有kconfig檔案,進而建立一個完整的配置菜單。

makefile:一般與kconfig檔案同時出現。每有一個kconfig檔案,就必要有一個makefile檔案。該檔案用于指定如何編譯makefile檔案所在目錄的源代碼。

現在還使用word_count驅動的例子來詳細說明如何将一個linux驅動加入linux核心源代碼樹中。由于word_count驅動屬于字元驅動,是以可以使用如下的步驟将word_count驅動加入linux核心源代碼樹。

第1步:将word_count.c檔案放入linux核心源代碼

其中,config後面的字元串将作為shell變量名的後半部分,前半部分是config_。也就是說,每一個具體的子產品都會對應一個shell變量來儲存該子產品的3個編譯行為(生成.ko檔案、編譯進linux核心或從linux核心中去除)。word_count驅動子產品的變量是config_word_count。該變量的值會儲存在.config檔案中。

bool表示word_count驅動隻能進行兩項設定(被編譯進核心與從linux核心中去除),後面會介紹如何設定菜單項的三項設定。bool後面的字元串就是菜單項的文本。help用于設定菜單項的幫助資訊。

第3步:修改makefile檔案

打開/root/kernel/goldfish/drivers/char/makefile檔案。該檔案大多都是如圖6-21所示的内容,随便找個位置插入如下内容。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

通過第2步的設定産生了一個config_word_count變量,而在第3步中obj-後使用了該變量,而不是使用固定的值(y或m)。make指令在編譯linux核心時會将該變量替換成相應的值。

第4步:設定.config檔案

.config檔案可以通過手工配置,也可以通過make menuconfig指令在菜單中配置。在這裡我們采用菜單配置的方法。現在進入linux核心頂層目錄(/root/kernel/goldfish)。然後執行make menuconfig指令顯示配置菜單,并進入“device drivers”>“character devices”子菜單,找到“word_count_driver”菜單項,按空格鍵将“word_count_driver”菜單項前設定成星号(*),如圖6-22所示。然後退出配置界面并儲存所做的修改。

按“h”鍵可以顯示word_count驅動的幫助資訊,如圖6-23所示。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

在配置完.config檔案後,讀者可以打開.config檔案,并找到config_word_count,會發現該變量的值已被設成“y”。

第5步:編譯linux核心

如果讀者以前編譯過目前的linux核心,并不需要擔心編譯的時間過長,因為make足夠智能,它隻會編譯最新修改的子產品及其依賴的子產品。

當成功編譯linux核心後,讀者可以到/root/kernel/goldfish/arch/arm/boot目錄找到zimage檔案,并使用android模拟器運作這個核心。讀者會發現,在/dev目錄中有一個wordcount裝置檔案,而我們并沒有運作build.sh腳本檔案安裝word_count驅動。這是因為android模拟器在裝載zimage核心檔案時已自動裝載了word_count驅動。不過在使用前面的例子測試word_count驅動時仍然需要執行下面的指令設定/dev/wordcount裝置檔案的通路權限。

如果讀者不想将word_count.c複制到/root/kernel/goldfish/drivers/char目錄,可以使用下面的指令在/root/kernel/goldfish/drivers/char目錄建立一個符号連結。

将word_count目錄加入linux核心源代碼樹的步驟如下(在進行下面的步驟之前需要将上面步驟所做的設定注釋掉)。

第1步:建立新的kconfig檔案

其中tristate表示三态類型(編譯進核心、編譯成子產品,從linux核心移除)。如果使用tristate代替bool,菜單項前面就變成尖括号。按“y”鍵,尖括号中顯示星号(*),表示編譯進核心。按“m”鍵,尖括号中顯示m,表示編譯成子產品。按“n”鍵,尖括号在符号消失,表示word_count驅動被忽略。如果不斷按“空格”鍵,這3種狀态會循環切換。

default用來設定預設值。如果使用tristate,default可以設定y、m和n三個值,分别對應編譯進核心、編譯成子產品和從linux核心中移除。當子產品第一次設定時會處于default設定的預設狀态。

如果使用tristate,必須按照節的方法打開“enable loadable module support”選項,否則無法将驅動設為編譯成子產品狀态(m狀态),菜單項前面仍然是一對中括号。

第2步:修改makefile檔案

修改makefile檔案後,如果還想使用前面幾節的腳本檔案測試word_count驅動,需要将.config檔案中config_word_count變量值設為m,如果.config檔案中沒有該變量,就添加一個config_word_count變量。當然,也可以使用make menuconfig指令設定。

為了可以單獨編譯word_count驅動,也可以和linux核心一同編譯,我們可以采用如下形式重新編寫makefile檔案。當config_word_count變量未定義時,說明沒有與linux核心一同編譯。

接下來的工作就和前面介紹的五步中的第4步和第5步一樣了。在進入如圖6-24所示的設定界面時,可以按“m”鍵将word_count驅動子產品編譯成.ko檔案。

《Android深度探索(卷1):HAL與驅動開發》——6.4節使用多種方式測試Linux驅動

當修改linux核心設定後重新編譯核心,以前使用該linux核心編譯的linux驅動子產品可能由于格式錯誤無法安裝,是以,在重新編譯linux核心後,需要重新編譯linux驅動子產品。

如果想将word_count驅動子產品編譯進其他核心也可采用與上面類似的做法。

繼續閱讀