在Android模擬器和Ubuntu上測試Linux驅動
三、使用AndroidNDK測試Linux驅動
在 Android系統中Linux驅動主要的使用者是APK程序。因此,Linux驅動做完后必須要用APK程序進行測試才能說明Linux驅動可以正常使 用。由於上一節在Android虛擬機上使用C語言編寫的可執行程序測試了Linux驅動,因此很容易想到可以利用Android NDK來測試Linux驅動,
由於Android NDK也使用C/C++來編寫程序,因此可以利用上一節的C語言代碼,當然,還得加上一些AndroidNDK特有的代碼。在使用AndroidNDK測試Linux驅動之前需要做如下兩件事。
1. 由於Linux驅動模塊不會隨Android系統啟動而裝載,因此必須執行build.sh腳本文件安裝word_count驅動。
2. 不能使用默認方式啟動Android模擬器,而要使用我們自己編譯的Linux內核啟動Android模擬器,啟動模擬器的命令如下:
# emulator-avd myavd -kernel /root/kernel/goldfish/arch/arm/boot/zImage
為了方便,讀者也可以在隨書光盤帶的Ubuntu Linux虛擬環境中直接執行如下的命令來異步啟動Android模擬器。其中emulator.sh文件在/root/drivers目錄中。
# sh emulator.sh &
本節的例子已經包含在隨書光盤和虛擬環境中,路徑如下:
隨書光盤:<光盤根目錄>/sources/ch06/word_count/word_count_ndk
虛擬環境:/root/drivers/ch06/word_count/word_count_ndk
word_count_ndk工程的代碼部分由WordCountNDKTestMain.java和ndk_test_word_count.c文件組成。工程結構如圖6-17所示。
ndk_test_word_count.c文件用於訪問word_count驅動。該文件包含兩個供Java訪問的函數,分別用來讀取/dev /wordcount設備文件中的單詞數和向/dev/wordcount設備文件寫入字符串。下面先看看ndk_test_word_count.c文 件的完整代碼。
#include <string.h> #include <jni.h> #include <fcntl.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> // JNI函數:readWordCountFromDev // 用於從/dev/wordcount設備文件讀取單詞數 jint Java_mobile_android_word_count_ndk_WordCountNDKTestMain_readWordCountFromDev( JNIEnv* env, jobject thiz) { int dev; // open函數打開/dev/wordcount設備文件后返回的句柄,打開失敗返回-1 jint wordcount = 0; // 單詞數 unsigned char buf[4]; // 以4個字節形式存儲的單詞數 // 以只讀方式打開/dev/wordcount設備文件 dev = open("/dev/wordcount", O_RDONLY); // 從dev/wordcount設備文件中讀取單詞數 read(dev, buf, 4); int n = 0; // 存儲單詞數的int類型變量 // 將由4個字節表示的單詞數轉換成int類型的值 n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8 | ((int) buf[3]); // 將int類型的單詞數轉換成jint類型的單詞數 wordcount = (jint) n; // 關閉/dev/wordcount設備文件 close(dev); // 返回單詞數 return wordcount; } // 將jstring類型的值轉換成char *類型的值 char* jstring_to_pchar(JNIEnv* env, jstring str) { char* pstr = NULL; // 下面的代碼會調用Java中的String.getBytes方法獲取字符串的字節數 // 獲取java.lang.String類 jclass clsstring = (*env)->FindClass(env, "java/lang/String"); // 將字符串“utf-8”轉換成jstring類型的值 jstring strencode = (*env)->NewStringUTF(env, "utf-8"); // 獲取java.lang.String.getBytes方法 jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); // 調用String.getBytes方法將str變量的值轉換成jbytearray類型的值 jbyteArray byteArray = (jbyteArray)( (*env)->CallObjectMethod(env, str, mid, strencode)); // 獲取字節長度 jsize size = (*env)->GetArrayLength(env, byteArray); // 將jbytearray類型的值轉換成jbyte*類型的值 jbyte* pbyte = (*env)->GetByteArrayElements(env, byteArray, JNI_FALSE); if (size > 0) { // 為char*類型變量pstr分配空間 pstr = (char*) malloc(size); // 將pbyte變量中的值復制到pstr變量中 memcpy(pstr, pbyte, size); } // 返回轉換后的值 return pstr; } // JNI函數:writeStringToDev // 用於向/dev/wordcount設備文件寫入字符串 void Java_mobile_android_word_count_ndk_WordCountNDKTestMain_writeStringToDev( JNIEnv* env, jobject thiz, jstring str) { int dev; // open函數打開/dev/wordcount設備文件后返回的句柄,打開失敗返回-1 // 以只寫方式打開/dev/wordcount設備文件 dev = open("/dev/wordcount", O_WRONLY); // 將jstring類型字符串轉換成char* 類型的值 char* pstr = jstring_to_pchar(env, str); if (pstr != NULL) { // 向/dev/wordcount設備文件寫入字符串 write(dev,pstr, strlen(pstr)); } // 關閉/dev/wordcount設備文件 close(dev); }
編寫上面的代碼有一個重點就是jstring_to_pchar函數。該函數可以將jstring類型的數據轉換成char*類型的數據。轉換的基 本思想就是調用Java方法String.getBytes,獲取字符串對應的字節數組(jbyteArray)。由於write函數需要的是char *類型的數據,因此,還必須將jbyteArray類型的數據轉換成char *類型的數據。采用的方法是先將jbyteArray類型的數據轉換成jbyte類型的數據,然后調用memcpy函數將jbyte類型的數據復制到使用 malloc函數分配的char *指針空間中。在jstring_to_pchar函數中有如下的一行代碼。
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"];
看到getMethodID方法最后一個參數的值是"(Ljava/lang/String;)[B",可能Android NDK初學者會對此感到困惑,以為是寫錯了。實際上這是JNI(Android NDK程序實際上就是遵循JNI規則的程序)對方法參數和返回類型的描述。在JNI程序中為了方便描述Java數據類型,將簡單類型使用了一個大寫英文字 母表示,如表6-1所示。
除了表6-1所示的Java簡單類型外,還有一些數據類型需要在JNI代碼中與其對應。表6-2是這些數據類型在JNI中的描述符。
從表6-2所示的數據類型對照關系很容易想到本例中的"(Ljava/lang/String;)[B"是什么意思。jstring_to_pchar函數調用的是如下的getBytes方法的重載形式。
public byte[] getBytes(String charsetName) throwsUnsupportedEncodingException
在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文件,代碼如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ndk_test_word_count LOCAL_SRC_FILES := ndk_test_word_count.c include $(BUILD_SHARED_LIBRARY)
在編寫Java代碼調用JNI函數之前,先看一下本例的界面,如圖6-18所示。
讀者需要先在PC上運行build.sh腳本文件安裝word_count驅動。然后單擊“從/dev/wordcount讀取單詞數”按鈕,會在按鈕下 方輸出當前/dev/wordcount設備文件中統計出的單詞數。讀者也可以在輸入框中輸入一個由空格分隔的字符串,然后單擊“向/dev /wordcount寫入字符串”按鈕,再單擊“從/dev/wordcount讀取單詞數”按鈕,就會統計出字符串中包含的單詞數,效果如圖6-19所 示。
下面看一下本例中Java部分(WordCountNDKTestMain.java)的完整代碼。
package mobile.android.word.count.ndk; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class WordCountNDKTestMain extends Activity { private TextView tvWordCount; private EditText etString; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tvWordCount = (TextView) findViewById(R.id.textview_wordcount); etString = (EditText) findViewById(R.id.edittext_string); } // “從/dev/wordcount讀取單詞數”按鈕的執行代碼 public void onClick_ReadWordCountFromDev(View view) { // 顯示單詞數 tvWordCount.setText("單詞數:" + String.valueOf(readWordCountFromDev())); } // “向/dev/wordcount寫入字符串”按鈕的執行代碼 public void onClick_WriteStringToDev(View view) { // 向/dev/wordcount設備文件寫入字符串 writeStringToDev(etString.getText().toString()); Toast.makeText(this, "已向/dev/wordcount寫入字符串", Toast.LENGTH_LONG).show(); } // native方法 public native int readWordCountFromDev(); public native void writeStringToDev(String str); static { System.loadLibrary("ndk_test_word_count"); } }
WordCountNDKTestMain.java中的代碼只是簡單地調用了JNI函數來操作/dev/wordcount文件。其他的代碼都是常規的Android應用級別的代碼。如果讀者對這部分不熟悉,可以參閱筆者所著的《Android開發權威指南》。
四、使用Java代碼直接操作設備文件來測試Linux驅動
如果Android擁有root權限,完全可以直接使用Java代碼操作/dev/wordcount設備文件(沒有root權限,Linux驅動模塊是 無法安裝的)。本節將介紹如何使用Java代碼來測試Linux驅動(測試程序不使用一行C/C++代碼)。本節示例的路徑如下:
隨書光盤:<光盤根目錄>/sources/ch06/word_count/word_count_java
虛擬環境:/root/drivers/ch06/word_count/word_count_java
word_count_java工程中只有一個源代碼文件WordCountJavaTestMain.java。該文件的內容如下:
package mobile.android.word.count.java; import java.io.FileInputStream; import java.io.FileOutputStream; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class WordCountJavaTestMain extends Activity { private TextView tvWordCount; private EditText etString; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tvWordCount = (TextView) findViewById(R.id.textview_wordcount); etString = (EditText) findViewById(R.id.edittext_string); } // “從/dev/wordcount讀取單詞數”按鈕的執行代碼 public void onClick_ReadWordCountFromDev(View view) { // 顯示單詞數 tvWordCount.setText("單詞數:" + String.valueOf(readWordCountFromDev())); } // “向/dev/wordcount寫入字符串”按鈕的執行代碼 public void onClick_WriteStringToDev(View view) { // 向/dev/wordcount設備文件寫入字符串 writeStringToDev(etString.getText().toString()); Toast.makeText(this, "已向/dev/wordcount寫入字符串", Toast.LENGTH_LONG).show(); } // 下面是用Java實現的操作/dev/wordcount設備文件的代碼 // 讀取/dev/wordcount設備文件中的單詞數 private int readWordCountFromDev() { int n = 0; byte[] buffer = new byte[4]; try { // 打開/dev/wordcount設備文件 FileInputStream fis = new FileInputStream("/dev/wordcount"); // 從設備文件中讀取4個字節 fis.read(buffer); // 將4個字節轉換成int類型的值 n = ((int) buffer[0]) << 24 | ((int) buffer[1]) << 16 | ((int) buffer[2]) << 8 | ((int) buffer[3]); fis.close(); } catch (Exception e) { } return n; } // 向/dev/wordcount設備文件中寫入字符串 private void writeStringToDev(String str) { try { // 打開/dev/wordcount設備文件 FileOutputStream fos = new FileOutputStream("/dev/wordcount"); // 寫入字符串 fos.write(str.getBytes("iso-8859-1")); fos.close(); } catch (Exception e) { } } }
本例的運行效果和使用方法與上一節的例子類似。讀者可以運行隨書光盤或虛擬環境中的例子與上一節的例子進行比較。
本文節選至《Android深度探索(卷1):HAL與驅動開發》, 接下來幾篇文章將詳細闡述如何開發ARM架構的Linux驅動,並分別利用android程序、NDK、可執行文件測試Linux驅動。可在ubuntu Linux、Android模擬器和S3C6410開發板(可以選購OK6410-A開發板,需要刷Android)