使用Android、S3C6410开发板和Ubuntu测试Linux驱动

开发可统计单词个数的Android驱动程序(2)

八、 指定回调函数

      本节讲的内容十分关键。不管Linux驱动程序的功能多么复杂还是多么“酷”,都必须允许用户空间的应用程序与内核空间的驱动程序进行交互才有意义。而最常用的交互方式就是读写设备文件。通过file_operations.read和file_operations.write成员变量可以分别指定读写设备文件要调用的回调函数指针。

     在本节将为word_count.c添加两个函数:word_count_read和word_count_write。这两个函数分别处理从设备文件读数据和向设备文件写数据的动作。本节的例子先不考虑word_count要实现的统计单词数的功能,先用word_count_read和word_count_write函数做一个读写设备文件数据的实验,以便让读者了解如何与设备文件交互数据。本节编写的word_count.c文件是一个分支,读者可在word_count/read_write目录找到word_count.c文件。可以用该文件覆盖word_count目录下的同名文件测试本节的例子。

     本例的功能是向设备文件/dev/wordcount写入数据后,都可以从/dev/wordcount设备文件中读出这些数据(只能读取一次)。下面先看看本例的完整的代码。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>

#define DEVICE_NAME "wordcount"       	//  定义设备文件名
static unsigned char mem[10000];        	 	//  保存向设备文件写入的数据
static char read_flag = 'y';                 	//  y:已从设备文件读取数据   n:未从设备文件读取数据
static int written_count = 0;                	// 向设备文件写入数据的字节数

//  从设备文件读取数据时调用该函数
//  file:指向设备文件、buf:保存可读取的数据   count:可读取的字节数  ppos:读取数据的偏移量
static ssize_t word_count_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    //  如果还没有读取设备文件中的数据,可以进行读取
    if(read_flag == 'n')
    {
        //  将内核空间的数据复制到用户空间,buf中的数据就是从设备文件中读出的数据
        copy_to_user(buf, (void*) mem, written_count);
        //  向日志输出已读取的字节数
        printk("read count:%d", (int) written_count);
        //  设置数据已读状态
        read_flag = 'y';
        return written_count;
    }
    //  已经从设备文件读取数据,不能再次读取数据
    else
    {
        return 0;
    }
}
//  向设备文件写入数据时调用该函数
//  file:指向设备文件、buf:保存写入的数据   count:写入数据的字节数  ppos:写入数据的偏移量
static ssize_t word_count_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    //  将用户空间的数据复制到内核空间,mem中的数据就是向设备文件写入的数据
    copy_from_user(mem, buf, count);
    //  设置数据的未读状态
    read_flag = 'n';
    //  保存写入数据的字节数
    written_count = count;
    //  向日志输出已写入的字节数
    printk("written count:%d", (int)count);
    return count;
}
//  描述与设备文件触发的事件对应的回调函数指针
//  需要设置read和write成员变量,系统才能调用处理读写设备文件动作的函数
static struct file_operations dev_fops =
{ .owner = THIS_MODULE, .read = word_count_read, .write = word_count_write };

//  描述设备文件的信息
static struct miscdevice misc =
{ .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops };

//  初始化Linux驱动
static int word_count_init(void)
{
    int ret;
    //  建立设备文件
    ret = misc_register(&misc);
    //  输出日志信息
    printk("word_count_init_success\n");
    return ret;
}

// 卸载Linux驱动
static void word_count_exit(void)
{
    //  删除设备文件
    misc_deregister(&misc);
    //  输出日志信息
    printk("word_init_exit_success\n");
}

//  注册初始化Linux驱动的函数
module_init( word_count_init);
//  注册卸载Linux驱动的函数
module_exit( word_count_exit);

MODULE_AUTHOR("lining");
MODULE_DESCRIPTION("statistics of word count.");
MODULE_ALIAS("word count module.");
MODULE_LICENSE("GPL");

编写上面代码需要了解如下几点。

1. word_count_read和word_count_write函数的参数基本相同,只有第2个参数buf稍微一点差异。word_count_read函数的buf参数类型是char*,而word_count_write函数的buf参数类型是const char*,这就意味着word_count_write函数中的buf参数值无法修改。word_count_read函数中的buf参数表示从设备文件读出的数据,也就是说,buf中的数据都可能由设备文件读出,至于可以读出多少数据,取决于word_count_read函数的返回值。如果word_count_read函数返回n,则可以从buf读出n个字符。当然,如果n为0,表示无法读出任何的字符。如果n小于0,表示发生了某种错误(n为错误代码)。word_count_write函数中的buf表示由用户空间的应用程序写入的数据。buf参数前有一个“__user”宏,表示buf的内存区域位于用户空间。

2. 由于内核空间的程序不能直接访问用户空间中的数据,因此,需要在word_count_read和word_count_write函数中分别使用copy_to_user和copy_from_user函数将数据从内核空间复制到用户空间或从用户空间复制到内核空间。

3. 本例只能从设备文件读一次数据。也就是说,写一次数据,读一次数据后,第二次无法再从设备文件读出任何数据。除非再次写入数据。这个功能是通过read_flag变量控制的。当read_flag变量值为n,表示还没有读过设备文件,在word_count_read函数中会正常读取数据。如果read_flag变量值为y,表示已经读过设备文件中的数据,word_count_read函数会直接返回0。应用程序将无法读取任何数据。

4. 实际上word_count_read函数的count参数表示的就是从设备文件读取的字节数。但因为使用cat命令测试word_count驱动时。直接读取了32768个字节。因此count参数就没什么用了(值总是32768)。所以要在word_count_write函数中将写入的字节数保存,在word_count_read函数中直接使用写入的字节数。也就是说,写入多少个字节,就读出多少个字节。

5.  所有写入的数据都保存在mem数组中。该数组定义为10000个字符,因此写入的数据字节数不能超过10000,否则将会溢出。

      为了方便读者测试本节的例子,笔者编写了几个Shell脚本文件,允许在UbuntuLinux、S3C6410开发板和Android模拟器上测试word_count驱动。其中有一个负责调度的脚本文件build.sh。本书所有的例子都会有一个build.sh脚本文件,执行这个脚本文件就会要求用户选择将源代码编译到那个平台,选择菜单如图6-11所示。用户可以输入1、2或3选择编译平台。如果直接按回车键,默认值会选择第1个编译平台(UbuntuLinux)。


build.sh脚本文件的代码如下:

source /root/drivers/common.sh
#  select_target是一个函数,用语显示图6-11所示的选择菜单,并接收用户的输入
#  改函数在common.sh文件中定义
select_target
if [ $selected_target == 1 ]; then
    source ./build_ubuntu.sh			# 执行编译成Ubuntu Linux平台驱动的脚本文件
elif [ $selected_target == 2 ]; then
    source ./build_s3c6410.sh			# 执行编译成s3c6410平台驱动的脚本文件
elif [ $selected_target == 3 ]; then
    source ./build_emulator.sh			# 执行编译成Android模拟器平台驱动的脚本文件
fi

      在build.sh脚本文件中涉及到了3个脚本文件(build_ubuntu.sh、build_s3c6410.sh和build_emulator.sh),这3个脚本文件的代码类似,只是选择的Linux内核版本不同。对于S3C6410和Android模拟器平台,编译完后Linux驱动,会自动将编译好的Linux驱动文件(*.so文件)上传到相应平台的/data/local目录,并安装Linux驱动。例如,build_s3c6410.sh脚本文件的代码如下:

source /root/drivers/common.sh
# S3C6410_KERNEL_PATH变量是适用S3C6410平台的Linux内核源代码的路径,
# 该变量以及其它类似变量都在common.sh脚本文件中定义
make  -C $S3C6410_KERNEL_PATH  M=${PWD}
find_devices
#  如果什么都选择,直接退出
if [ "$selected_device" == "" ]; then
    exit
else
    #  上传驱动程序(word_count.ko)
    adb -s $selected_device push ${PWD}/word_count.ko /data/local
    # 判断word_count驱动是否存在
    testing=$(adb -s $selected_device shell lsmod | grep  "word_count")
    if [ "$testing" != "" ]; then
        #  删除已经存在的word_count驱动
        adb -s $selected_device shell rmmod word_count
    fi
    #  在S3C6410开发板中安装word_count驱动
    adb -s $selected_device shell "insmod /data/local/word_count.ko"
fi

使用上面的脚本文件,需要在read_write目录建立一个Makefile文件,内容如下:

obj-m := word_count.o

现在执行build.sh脚本文件,选择要编译的平台,并执行下面的命令向/dev/word_count设备文件写入数据。

# echo ‘hello lining’ > /dev/wordcount

然后执行如下的命令从/dev/word_count设备文件读取数据。

# cat /dev/wordcount

如果输出“hello lining”,说明测试成功。

 注意:如果在S3C6410开发板和Android模拟器上测试word_count驱动,需要执行shell.sh脚本文件或adb shell命令进入相应平台的终端。其中shell.sh脚本在/root/drivers目录中。这两种方式的区别是如果有多个Android设备和PC相连时,shell.sh脚本会出现一个类似图6-11所示的选择菜单,用户可以选择进入哪个Android设备的终端,而adb shell命令必须要加-s命令行参数指定Android设备的ID才可以进入相应Android设备的终端。

九、实现统计单词数的算法

      本节开始编写word_count驱动的业务逻辑:统计单词数。本节实现的算法将由空格、制表符(ASCII:9)、回车符(ASCII:13)和换行符(ASCII:10)分隔的字符串算做一个单词,该算法同时考虑了有多个分隔符(空格符、制表符、回车符和换行符)的情况。下面是word_count驱动完整的代码。在代码中包含了统计单词数的函数get_word_count。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>

#define DEVICE_NAME "wordcount"    	//  定义设备文件名
static unsigned char mem[10000]; 		// 保存向设备文件写入的数据
static int word_count = 0;	              	//  单词数
#define TRUE -1
#define FALSE 0

//  判断指定字符是否为空格(包括空格符、制表符、回车符和换行符)
static char is_spacewhite(char c)
{
    if(c == ' ' || c == 9 || c == 13  || c == 10)
        return TRUE;
    else
        return FALSE;
}
//  统计单词数
static int get_word_count(const char *buf)
{
    int n = 1;
    int i = 0;
    char c = ' ';

    char flag = 0;   // 处理多个空格分隔的情况,0:正常情况,1:已遇到一个空格
    if(*buf == '\0')
        return 0;
    //  第1个字符是空格,从0开始计数
    if(is_spacewhite(*buf) == TRUE)
        n--;
    //  扫描字符串中的每一个字符
    for (; (c = *(buf + i)) != '\0'; i++)
    {
        //  只由一个空格分隔单词的情况
        if(flag == 1 && is_spacewhite(c) == FALSE)
        {
           flag = 0;
        }
        //  由多个空格分隔单词的情况,忽略多余的空格
        else if(flag == 1 && is_spacewhite(c) == TRUE)
        {
            continue;
        }
        //  当前字符为空格时单词数加1
        if(is_spacewhite(c) == TRUE)
        {
            n++;
            flag = 1;
        }
    }
    //  如果字符串以一个或多个空格结尾,不计数(单词数减1)
    if(is_spacewhite(*(buf + i - 1)) == TRUE)
        n--;
    return n;
}
//  从设备文件读取数据时调用的函数
static ssize_t word_count_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    unsigned char temp[4];
    //  将单词数(int类型)分解成4个字节存储在buf中
    temp[0] = word_count >> 24;
    temp[1] = word_count >> 16;
    temp[2] = word_count >> 8;
    temp[3] = word_count;
    copy_to_user(buf, (void*) temp, 4);
    printk("read:word count:%d", (int) count);

    return count;
}
//  向设备文件写入数据时调用的函数
static ssize_t word_count_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    ssize_t written = count;

    copy_from_user(mem, buf, count);
    mem[count] = '\0';
    //  统计单词数
    word_count = get_word_count(mem);
    printk("write:word count:%d", (int)word_count);
    return written;
}

//  描述与设备文件触发的事件对应的回调函数指针
static struct file_operations dev_fops =
{ .owner = THIS_MODULE, .read = word_count_read, .write = word_count_write };

//  描述设备文件的信息
static struct miscdevice misc =
{ .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops };

//  初始化Linux驱动
static int word_count_init(void)
{
    int ret;
    //  建立设备文件
    ret = misc_register(&misc);
    //  输出日志信息
    printk("word_count_init_success\n");
    return ret;
}

// 卸载Linux驱动
static void word_count_exit(void)
{
    //  删除设备文件
    misc_deregister(&misc);
    //  输出日志信息
    printk("word_init_exit_success\n");
}

//  注册初始化Linux驱动的函数
module_init( word_count_init);
//  注册卸载Linux驱动的函数
module_exit( word_count_exit);

MODULE_AUTHOR("lining");
MODULE_DESCRIPTION("statistics of word count.");
MODULE_ALIAS("word count module.");
MODULE_LICENSE("GPL");

编写word_count驱动程序需要了解如下几点。
1.  get_word_count函数将mem数组中第1个为“\0”的字符作为字符串的结尾符,因此在word_count_write函数中将mem[count]的值设为“\0”,否则get_word_count函数无法知道要统计单词数的字符串到哪里结束。由于mem数组的长度为10000,而字符串最后一个字符为“\0”,因此待统计的字符串最大长度为9999。

2.  单词数使用int类型变量存储。在word_count_write函数中统计出了单词数(word_count变量的值),在word_count_read函数中将word_count整型变量值分解成4个字节存储在buf中。因此,在应用程序中需要再将这4个字节组合成int类型的值。

十、编译、安装、卸载Linux驱动程序

      在上一节word_count驱动程序已经全部编写完成了,而且多次编译测试该驱动程序。安装和卸载word_count驱动也做过多次。word_count驱动与read_write目录中的驱动一样,也有一个build.sh和3个与平台相关的脚本文件。这些脚本文件与6.3.5节的实现类似,这里不再详细介绍。现在执行build.sh脚本文件,并选择要编译的平台。然后执行下面两行命令查看日志输出信息和word_count驱动模块(word_count.ko)的信息。

# dmesg |tail -n 1

# modinfo word_count.ko

如果显示如图6-12所示的信息,表明word_count驱动工作完全正常。

     本书的脚本文件都是使用insmod命令安装Linux驱动的,除了该命令外,使用modprobe命令也可以安装Linux驱动。insmod和modprobe的区别是modprobe命令可以检查驱动模块的依赖性。如A模块依赖于B模块(装载A之前必须先装载B)。如果使用insmod命令装载A模块,会出现错误。而使用modprobe命令装载A模块,B模块会现在装载。在使用modprobe命令装载驱动模块之前,需要先使用depmod命令检测Linux驱动模块的依赖关系。

# depmod  /root/drivers/ch06/word_count/word_count.ko

depmod命令实际上将Linux驱动模块文件(包括其路径)添加到如下的文件中。

 

/lib/modules/3.0.0-16-generic/modules.dep

 

使用depmod命令检测完依赖关系后,就可以调用modprobe命令装载Linux驱动。

# modprobe word_count

使用depmod和modprobe命令需要注意如下几点:

1. depmod命令必须使用Linux驱动模块(.ko文件)的绝对路径。

2. depmod命令会将内核模块的依赖信息写入当前正在使用的内核的modules.dep文件。例如,笔者的Ubuntu Linux使用的是Linux3.0.0.16,所以应到3.0.0-16-generic目录去寻找modules.dep文件。如果读者使用了其他Linux内核,需要到相应的目录去寻找modules.dep文件。

3. modprobe命令只需使用驱动名称即可,不需要跟.ko。

使用Android、S3C6410开发板和Ubuntu测试Linux驱动

本文节选至《Android深度探索(卷1):HAL与驱动开发》,接下来几篇文章将详细阐述如何开发ARM架构的Linux驱动,并分别利用android程序、NDK、可执行文件测试Linux驱动。可在ubuntu Linux、Android模拟器和S3C6410开发板(可以选购OK6410-A开发板,需要刷Android)

时间: 2024-11-10 01:37:20

使用Android、S3C6410开发板和Ubuntu测试Linux驱动的相关文章

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

6.4 使用多种方式测试Linux驱动 Android深度探索(卷1):HAL与驱动开发 在上一节已经实现了一个简单的Linux驱动程序,该驱动的功能是统计给定字符串中的单词数,并且在最后已经将该Linux驱动的源代码成功编译成动态Linux驱动模块word_count.ko.下一步就是测试该模块.测试的方法很多,最常用的就是直接在Ubuntu Linux中测试.当然,这对于本章实现的Linux驱动是没问题的,但是对于需要直接访问硬件的驱动在Ubuntu Linux上测试就不太方便.在这种情况下

led驱动加载-开发板和ubuntu互ping的问题

问题描述 开发板和ubuntu互ping的问题 遇到一个很奇怪的问题,开发板一加载led驱动以后,就和ubuntu ping不通了,加载之前是互通的,很困扰,不知道是驱动的问题,还是别的问题.请各位大侠帮帮忙.

android平台开发板外接罗技C525摄像头不支持扫码?有什么办法解决吗?

问题描述 android平台开发板外接罗技C525摄像头不支持扫码?有什么办法解决吗? 最近在做android平台飞凌OK335开发板上的扫码模块,外接USB摄像头可以调用,但是无法识别条码和二维码,有什么办法解决吗?谢谢各位大神!

实现开发板与ubuntu的共享--根文件系统NFS--Samba共享【sky原创】

虚拟机要选择桥接,并且禁用有线和无线网卡,开启本地连接,本地连接属性要写如下:     ip地址是在连上板子后,windows   cmd  下  ipconfig得出的 板子的网线最好连接交换机或者路由器,再由用另一根网线连到电脑上面 如果直接板子连在电脑上的话,有时候nfs可能不行,因为网线这时候相当于是在全双工通信模式, 两个tx两个rx,所以不一定连的通,此时就需要用如下的samba和tftp去传输文件   虚拟机要设置静态ip vim /etc/network/interfaces #

《Android深度探索(卷1):HAL与驱动开发》——6.1节Linux驱动到底是个什么东西

6.1 Linux驱动到底是个什么东西Android深度探索(卷1):HAL与驱动开发对于从未接触过驱动开发的程序员可能会感觉Linux驱动很神秘.感觉开发起来会很复杂.其实,这完全是误解.实际上Linux驱动和普通的Linux API没有本质的区别.只是使用Linux驱动的方式与使用Linux API的方式不同而已. 在学习Linux驱动之前我们先来介绍一下Linux驱动的工作方式.如果读者以前接触过Windows或其他非Unix体系的操作系统,最好将它们的工作方式暂时忘掉,因为这些记忆会干扰

Android驱动程序开发和调试环境配置

本文用<Android深度探索(卷1):HAL与驱动开发>的随书源代码为例详细说明如何配置Android驱动开发和测试环境,并且如何使用源 代码中的build.sh脚本文件在各种平台(Ubuntu  Linux.Android模拟器和S3C6410开发板)上编译.安装和测试Linux驱动.建议读者使用Ubuntu  Linux12.04或更高版本实验本文的方法.最好用root账号登录Linux. 一.安装交叉编译器 如果只是在Ubuntu Linux上测试Linux驱动就不需要安装交叉编译器了

《Android深度探索(卷1):HAL与驱动开发》——6.5节使用Eclipse开发和测试Linux驱动程序

6.5 使用Eclipse开发和测试Linux驱动程序 Android深度探索(卷1):HAL与驱动开发 在前面几节开发的word_count驱动和测试程序大多都需要在Linux终端进行编译(Android应用程序除外)和运行,而且也无法跟踪到Linux内核函数.变量.宏的内部(除非自己到Linux内核源代码中就寻找这些源代码文件),这并不利于深入了解Linux内核技术.在本节将为读者展示如何在Eclipse中开发Linux驱动程序,并且可以像跟踪Java代码一样直接跟踪到Linux内核源代码.

《Android深度探索(卷1):HAL与驱动开发》——1.5节如何学习Linux驱动开发

1.5 如何学习Linux驱动开发 Android深度探索(卷1):HAL与驱动开发 由于Linux的内核版本更新较快(稳定版本1至3月更新一次,升级版本1至2周更新一次),每一次内核的变化就意味着Linux驱动的变化(就算不需要修改驱动代码,至少也得在新的Linux内核版本下重新编译),所以Linux内核的不断变化对从事Linux驱动开发的程序员影响比较大.不过这对于学习Linux驱动开发来说影响相对较小.因为不管是哪个版本的Linux内核,开发Linux驱动的方法和步骤基本相同,只要掌握了一

《Android深度探索(卷1):HAL与驱动开发》——6.3节第一个Linux驱动:统计单词个数

6.3 第一个Linux驱动:统计单词个数Android深度探索(卷1):HAL与驱动开发源程序目录:<光盘根目录>/sources/word_count本节将给出我们的第1个Linux驱动的例子.这个驱动程序并没有访问硬件,而是利用设备文件作为介质与应用程序进行交互.应用程序通过向设备文件传递一个由空格分隔的字符串(每一个被空格隔开的子字符串称为一个单词),然后从设备文件读出来的是该字符串包含的单词数.本例的驱动程序使用C语言实现,源代码文件路径如下. 6.3.1 编写Linux驱动程序前的