本节书摘来自异步社区《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类型的值显示)。但可以使用如下命令从日志中查看单词数。

# sh build.sh
# echo 'I love you.'  >  /dev/wordcount
# dmesg

执行上面的命令后,如果输出如图6-13所示白框中的信息,说明驱动程序成功统计了单词数。

虽然使用echo和dmesg命令可以测试Linux驱动程序,但这种方式并不是真正的测试。为了使测试效果更接近真实环境,一般需要编写专门用于测试的程序。本节将为word_count驱动编写一个专门的测试程序(test_word_count.c)。test_word_count.c通过直接操作/dev/wordcount设备文件与word_count驱动进行交互。测试程序的代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#include <string.h>
int main(int argc, char *argv[])
{int testdev;           // 打开设备文件(/dev/wordcount)的句柄unsigned char buf[4];      // 表示单词数的4个字节// 打开设备文件testdev = open("/dev/wordcount", O_RDWR);// 如果open函数返回-1,表示打开设备文件失败if (testdev == -1){printf("Cann't open file \n");return 0;}// 如果test_word_count后面跟有命令行参数,程序会将第1个参数值当作待统计的字符串// 如果没有命令行参数,则只读取设备文件中的值if (argc > 1){// 向设备文件写入待统计的字符串write(testdev, argv[1], strlen(argv[1]));// 输出待统计的字符串printf("string:%s\n", argv[1]);}// 读取设备文件中的单词数(4个字节)read(testdev, buf, 4);int n = 0;    // 单词数// 将4个字节还原成int类型的值n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8| ((int) buf[3]);// 分别输出从设备文件获取的4个字节的值printf("word byte display:%d,%d,%d,%d\n", buf[0], buf[1], buf[2], buf[3]);// 输出统计出的单词数printf("word count:%d\n", n);// 关闭设备文件close(testdev);return 0;
}

test_word_count程序可以跟1个命令行参数(多个命令行参数只会使用第1个命令行参数)。如果命令行参数值含有空格,需要使用单引号(')或双引号(")将参数值括起来。可以使用下面的一组命令测试word_count驱动程序。

# gcc test_word_count.c  -o test_word_count
# test_word_count
# test_word_count  "I love you."

执行上面的命令后,如果输出如图6-14所示的信息(假设word_count以前统计过一个含有4个单词的字符串),表示word_count驱动成功测试。


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驱动。

insmod: init_module ‘/data/local/word_count.ko’ failed(Function not implemented)

注意

建议上传文件到Android模拟器或开发板时,将文件放到/data/local目录,系统很多其他的目录,如/system/bin,都是只读的,除非将word_count.ko文件打包进system.img,否则无法向这些目录写数据,即使有root权限也不行。
用于Android模拟器的goldfish内核默认不允许动态装载Linux驱动模块,因此需要在编译Linux内核之前执行如下命令配置Linux内核。

# cd ~/kernel/goldfish
# make menuconfig

执行上面的命令后,会出现如图6-15所示的设置界面。按空格键将第二项“Enable loadable module support”选中(前面是[*]),然后按回车键进入子菜单,选中前3项,如图6-16所示,否则Linux驱动模块仍然无法安装和卸载。当退出设置菜单时保持设置。最后按节的方法重新编译Linux内核,成功编译内核后,Android模拟器可以使用新生成的zImage内核文件动态装载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文件,并输入如下的内容。

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
# 指定要编译的源代码文件
LOCAL_SRC_FILES:= test_word_count.c
# 指定模块名,也是编译后生成的可执行文件名
LOCAL_MODULE := test_word_count
LOCAL_MODULE_TAGS := optional
include $(BUILD_EXECUTABLE)

Android.mk文件中有如下两个地方需要说明一下。

LOCAL_MODULE_TAGS

表示当前工程(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源代码之前执行如下命令。

# export TARGET_BUILD_VARIANT = user
或使用lunch命令设置编译模式。# lunch full-eng

其中full表示建立的目标,除了full目标(为所有的平台建立)外,还有专门为x86建立的full-x86。详细的建立目标执行lunch命令后就会列出。在图4-8已经显示了Android4支持的建立目标的编译模式。读者可以到第4章查看该图。

include $(BUILD_EXECUTABLE)

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)。

# ln -s  /root/drivers/ch06/word_count  /sources/android/android4/ development/ word_
count
现在进入/sources/android/android4目录,执行下面的命令初始化编译命令。# source ./build/envsetup.sh
可以使用下面两种方法编译test_word_count.c。(1)进入/sources/android/android4/development/word_count目录,并执行如下的命令。# mm
(2)在/sources/android/android4目录下执行如下的命令。# mmm  development/word_count

成功编译后可以在< Android源代码目录 >/out/target/product/generic/system/bin目录中找到test_word_count文件。在随书光盘和模拟环境中已经带了编译好的test_word_count程序(包括Emulator版本和Ubuntu Linux版本),可执行程序一般不需要考虑Linux内核的版本,用交叉编译器编译的支持ARM处理器的程序既可以在Android模拟器上运行,也可以在S3C6410开发板或其他有root权限的手机中运行。

现在执行下面的命令将test_word_count文件上传到Android模拟器。

# adb push  ./emulator/test_word_count  /data/local
然后进入Android模拟器的终端,并执行下面的命令测试word_count驱动(需要先使用chmod命令设置test_word_count的可执行权限)。# chmod 777 /data/local/test_word_count
# /data/local/test_word_count
# /data/local/test_word_count  'a bb ccc ddd eee'

执行上面的命令后,如果输出的单词个数是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模拟器,启动模拟器的命令如下:

#  emulator -avd myavd -kernel /root/kernel/goldfish/arch/arm/boot/zImage

为了方便,读者也可以在随书光盘的Ubuntu Linux虚拟环境中直接执行如下的命令来启动Android模拟器。其中emulator.sh文件在/root/drivers目录中。

# sh emulator.sh

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设备文件后返回的句柄,打开失败返回-1jint 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所示。


在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.cinclude $(BUILD_SHARED_LIBRARY)
注意

为了方便读者在Eclipse中开发Android应用程序,本节的例子采用了节的方法进行配置。详细的配置信息请读者查看随书光盘或虚拟环境中的例子。虚拟环境中的所有配置和目录位置与笔者写作本书时使用的Ubuntu Linux的环境完全相同,读者可直接运行程序。但在随书光盘中的例子需要将相关的路径修改成读者自己机器上的路径。当然,如果恰巧读者机器的环境与笔者完全相同,就不需要做任何修改了。
在编写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;@Overridepublic 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开发权威指南》。

6.4.4 使用Java代码直接操作设备文件来测试Linux驱动
如果Android拥有root权限,完全可以直接使用Java代码操作/dev/wordcount设备文件(没有root权限,Linux驱动模块是无法安装的)。本节将介绍如何使用Java代码来测试Linux驱动(测试程序不使用一行C/C++代码)。本节示例的路径如下。

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;@Overridepublic 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){}}
}

本例的运行效果和使用方法与上一节的例子类似。读者可以运行随书光盘或虚拟环境中的例子与上一节的例子进行比较。

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所示。


▲图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内核源代码

将word_count.c文件放到<Linux内核目录>/drivers/char目录中。第2步:修改Kconfig文件打开/root/kernel/goldfish/drivers/char/Kconfig文件,找到endmenu,并在endmenu前面添加如下代码。config WORD_COUNTbool "word_count driver" helpThis is a word count driver. It can get a word count from /dev/wordcount

其中,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所示的内容,随便找个位置插入如下内容。


obj-$(CONFIG_WORD_COUNT)      += word_count.o

通过第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所示。


在配置完.config文件后,读者可以打开.config文件,并找到CONFIG_WORD_COUNT,会发现该变量的值已被设成“y”。

第5步:编译Linux内核

进入/root/kernel/goldfish目录,执行下面的命令编译Linux内核。# make

如果读者以前编译过当前的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设备文件的访问权限。

# adb shell  chmod 777  /dev/wordcount

如果读者不想将word_count.c复制到/root/kernel/goldfish/drivers/char目录,可以使用下面的命令在/root/kernel/goldfish/drivers/char目录建立一个符号链接。

# ln  -s  /root/drivers/ch06/word_count  /root/kernel/ goldfish/drivers/ char/word_ count

将word_count目录加入Linux内核源代码树的步骤如下(在进行下面的步骤之前需要将上面步骤所做的设置注释掉)。

第1步:建立新的Kconfig文件

在word_count目录中建立一个Kconfig文件,并输入如下内容:config WORD_COUNTtristate "word_count driver" default yhelpThis is a word count driver. It can get a word count from /dev/wordcount

其中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文件

word_count目录中的Makefile文件目前的内容如下:obj-m := word_count.o
在Makefile文件中已经将编译类型设为Linux驱动模块(obj-m表示编译成.ko文件)。但现在要将word_count驱动加入Linux内核源代码树中,因此需要使用CONFIG_WORD_COUNT变量来代替m,所以Makefile文件的内容需要按如下内容修改。obj-$(CONFIG_WORD_COUNT) := word_count.o

修改Makefile文件后,如果还想使用前面几节的脚本文件测试word_count驱动,需要将.config文件中CONFIG_WORD_COUNT变量值设为m,如果.config文件中没有该变量,就添加一个CONFIG_WORD_COUNT变量。当然,也可以使用make menuconfig命令设置。

为了可以单独编译word_count驱动,也可以和Linux内核一同编译,我们可以采用如下形式重新编写Makefile文件。当CONFIG_WORD_COUNT变量未定义时,说明没有与Linux内核一同编译。

#  与Linux内核一同编译
ifdef CONFIG_WORD_COUNTobj-$(CONFIG_WORD_COUNT)      :=  word_count.o
else# 单独编译obj-m := word_count.o
endif
第3步:修改上层目录的Kconfig文件为了能找到word_count目录中的Kconfig文件,需要在drivers/char/Kconfig文件中引用word_count目录中的Kconfig文件。现在打开/root/kernel/goldfish/drivers/char/Kconfig文件,在“endmenu”之前添加如下一行代码。source "drivers/char/word_count/Kconfig"
第4步:修改上层目录的Makefile文件在drivers/char/Makefile文件中添加如下一行,以便使make命令可以找到word_count目录中的Makefile文件。obj-$(CONFIG_WORD_COUNT)  += word_count/

接下来的工作就和前面介绍的五步中的第4步和第5步一样了。在进入如图6-24所示的设置界面时,可以按“M”键将word_count驱动模块编译成.ko文件。


当修改Linux内核设置后重新编译内核,以前使用该Linux内核编译的Linux驱动模块可能由于格式错误无法安装,因此,在重新编译Linux内核后,需要重新编译Linux驱动模块。
如果想将word_count驱动模块编译进其他内核也可采用与上面类似的做法。

《Android深度探索(卷1):HAL与驱动开发》——6.4节使用多种方式测试Linux驱动...相关推荐

  1. Android深度探索(卷1)HAL与驱动开发 第四章 源代码的下载和编译 读书笔记

    Android深度探索(卷1)HAL与驱动开发 第四章 源代码的下载和编译 读书笔记     本章学习了使用git下载两套源代码并搭建两个开发环境.分别为Android源代码和Linux内核源代码.A ...

  2. Android深度探索(卷1)HAL与驱动开发 心得体会 第十章 嵌入式Linux的调用技术

    Android深度探索(卷1)HAL与驱动开发 心得体会 第十章  嵌入式Linux的调用技术 对于复杂的Linux驱动以及HAL等程序库,需要使用各种方法对其进行调试.例如,设置断点,逐步跟踪代码. ...

  3. Android深度探索(卷1)HAL与驱动开发学习笔记(8)

    Android深度探索(卷1)HAL与驱动开发学习笔记(8) 第八章 蜂鸣器驱动   L i n u x驱动的代码重用有很多种方法.可以采用标准C程序的方式.将要重用的代码放在其他的文件(在头文件中声 ...

  4. Android深度探索(卷1)HAL与驱动开发第六章总结

    操作系统是通过各种驱动程序赖家与硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式.设备驱动程序是操作系统最基本的组成部分之一,在Linux内核源程序中也 ...

  5. Android深度探索(卷1)HAL与驱动开发--读书笔记(第一章)

    Android系统架构是由四层构成,第一层为Linux内核,主要包括Linux驱动程序以及内存管理.进程管理.电源管理的等程序.并且不同的Android的版本的驱动可能并不通用.第二层为C/C++代码 ...

  6. Android深度探索(卷1)HAL与驱动开发读后感---第四章

    第4章  源代码的下载和编译 4.1  下载.编译和测试Android源代码     主要学习了:配置源代码的下载环境,Android源代码目录结构解析,下载Android源代码中的一部分,编译And ...

  7. Android深度探索(卷1)HAL与驱动开发第五章总结

    开发板是开发和学习嵌入式技术的主要硬件设备,开发板的型号和种类很多,目前流行的是基于S3C6410 ARM11架构的开发板,S3C6410是由三星公司推出的一款低功耗,高性价比的RISC处理器,它基于 ...

  8. ANDROID深度探索(卷1)HAL与驱动开发 第四章

    配置android源代码下载环境 (1)创建一个用于存放下载脚本文件(repo)的目录(可将该脚本文件一放到任何目录中,在这里使用~/bin).#mkdir ~/bin    #PATH=~/bin: ...

  9. Android深度探索-卷1第二章心得体会

    这章介绍了搭建Android开发环境的的搭建,主要是在Linux上搭建Android开发环境 总体来说因为都是在Linux下开发的,so,只介绍了在Linux环境下的搭建 在搭建过程中全是命令操作,和 ...

最新文章

  1. spring中的BeanPostProcessor
  2. linux常用指令笔记(1)
  3. Linux I/O 那些事儿
  4. arm qt5 iconv 问题
  5. PowerDesigner 中SQL文件、数据库表反向生成PDM
  6. 轻松搭建基于Serverless的Go应用(Gin、Beego 举例)
  7. Just For Fun:在windows下模拟一个windows病毒软件(windows.h)
  8. matlab 识别调试,有关matlab的人脸识别程序,但调试是不成功
  9. 活用这25种图表效果,你的数据可视化也能变得高级炫酷!
  10. android selector
  11. 快速入门学习数字图像处理(冈萨雷斯第三版)
  12. 常用测试用例设计方法4-场景法
  13. Hive中变量的使用
  14. Youtube字幕下载转SRT字幕
  15. 照片加水印怎么弄?方法详细介绍
  16. percona toolkit系列(gh-ost)
  17. 关于计算机技术的报纸,报社电子计算机中心
  18. windows服务器dmp文件分析,如何用WinDbg分析MEMORY.DMP文件
  19. Java 知半径,求周长面积
  20. C# 超市管理系统源码

热门文章

  1. 使用TS自动抓取镜像
  2. prometheus下载慢_Prometheus + Grafana 监控 SpringBoot
  3. Android开发之适配器-ListView适配器的重复数据
  4. Java 有关于线程
  5. UVA 12034 Race
  6. vue.use无非就是为Vue对象注入新的方法和属性
  7. linux环境下python的部署
  8. JSP_include指令和lt;jsp:includegt;
  9. 视频培训网站发布问题
  10. POJ 3358 Period of an Infinite Binary Expansion ★ (数论好题:欧拉函数)