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

本文涉及的产品
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
日志服务 SLS,月写入数据量 50GB 1个月
简介:

 开发可统计单词个数的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模拟器和Ubuntu上测试Linux驱动


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

本文转自银河使者博客园博客,原文链接http://www.cnblogs.com/nokiaguy/archive/2013/03/11/2954618.html如需转载请自行联系原作者


银河使者

相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
相关文章
|
16天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
40 19
|
16天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
41 14
|
16天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
17天前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
17天前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
24 0
|
Unix Linux Apache
开发可统计单词个数的Android驱动程序(3)
开发可统计单词个数的Android驱动程序(1) 五、指定与驱动相关的信息 虽然指定这些信息不是必须的,但一个完整的Linux驱动程序都会指定这些与驱动相关的信息。
1058 0
|
Ubuntu Linux Android开发
开发可统计单词个数的Android驱动程序(1)&nbsp;
        X86架构的CPU采用的是复杂指令集(Complex Instruction Set Computer,CICS),而ARM架构的CPU使用的是精简指令集(Reduced Instruction Set Computer,RISC)。
914 0
|
29天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。