Linux驱动开发一.字符设备框架——4.驱动测试

2022/4/4 7:21:47

本文主要是介绍Linux驱动开发一.字符设备框架——4.驱动测试,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在前面的三章里我们完成了驱动的框架、应用程序的编写,但是并没有实现文件的实际读写功能(只是通过内核打印出了调试信息)。这一章我们着重实现文件实际的读写效果。

由于没有实际数据IO,我们只是在驱动中定义一个数据传递给应用程序;在应用程序中定义个用户数据用来传递给内核(驱动)。

内核空间和用户空间的数据交互

因为用户和驱动之间的数据是分别存在在内核空间和用户空间中,我们需要在内核空间和数据空间之间做数据交互。这就要用到下面内核里的两个函数(uaccess.h中):

static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
    if (access_ok(VERIFY_READ, from, n))
        n = __copy_from_user(to, from, n);
    else /* security hole - plug it */
        memset(to, 0, n);
    return n;
}

static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
    if (access_ok(VERIFY_WRITE, to, n))
        n = __copy_to_user(to, from, n);
    return n;
}

从函数名字就可以看出来,一个是从用户空间(user)拷贝数据(copy_from_user)另一个是向用户空间复制数据(copy_to_user)。通过这两个函数,就可以实现数据在内核空间和用户空间进行交互。

驱动程序编写

驱动程序的编写是在上一章的驱动上完成的,只是修改了dev_write和dev_read两个函数,还新增了两个变量用来存储数据。

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

#define DEV_MAJOR   200             //设备号
#define DEV_NAME    "DEVICE_TEST"   //设备名称

static char kerneldata[] = {"test data from kernel!"};    //测试用内核数据
static char writebuf[100];                                //写缓存

// /**
//  * @brief 打开设备文件
//  * 
//  * @return int 
//  */
static int dev_open(struct inode *inode, struct file *filp)
{
    printk("dev open!\r\n");
    return 0;
}

/**
 * @brief 关闭设备文件
 * 
 * @return int 
 */
static int dev_release(struct inode *inode, struct file *filp)
{
    printk("dev release!\r\n");
    return 0;
}

/**
 * @brief 读设备文件数据
 * 
 * @param filp 
 * @param buf 
 * @param count 
 * @param ppos 
 * @return ssize_t 
 */
static ssize_t dev_read(struct file *filp, 
                               __user char *buf,
                               size_t count, 
                               loff_t *ppos)
{
    int ret = 0;
    printk("dev read data!\r\n");                           
    ret = copy_to_user(buf,kerneldata,sizeof(kerneldata));  //向用户空间写入数据

    if (ret == 0){
        return 0;
    }
    else{
        printk("kernel read data error!");
        return -1;
    }
}


/**
 * @brief 设备文件数据写入
 * 
 * @param file 
 * @param buf 
 * @param count 
 * @param ppos 
 * @return ssize_t 
 */
static ssize_t dev_write(struct file *file, 
                        const char __user *buf, 
                        size_t count, 
                        loff_t *ppos)
{   
    int ret = 0;
    printk("dev write data!\r\n");
    ret = copy_from_user(writebuf,buf,count);           //从用户空间获取数据写入内核空间(writebuf)
    if (ret == 0){
        printk("get data from APP:%s\r\n",writebuf);
        return 0;
    }
    else{
        printk("kernelwrite err!\r\n");
        return -1;
    }
}   

/**
 * @brief 文件操作结构体
 * 
 */
static struct file_operations testDev_fops= {
    .owner = THIS_MODULE,
    .open = dev_open,
    .release = dev_release,
    .read = dev_read,
    .write = dev_write,
};

/**
 * @brief 初始化
 * 
 * @return int 
 */
static int __init dev_init(void)
{   
    int ret = 0;
    printk("device init!\r\n");

    //字符设备注册
    ret = register_chrdev(DEV_MAJOR, DEV_NAME, &testDev_fops);
    if(ret < 0 ){
        printk("device init failed\r\n");
    }
    return 0;
}

/**
 * @brief 卸载
 * 
 */
static void __exit dev_exit(void)
{   
    //字符设备注销
    unregister_chrdev(DEV_MAJOR,DEV_NAME);
    printk("device exit\r\n");

}

module_init(dev_init);      //模块加载
module_exit(dev_exit);      //模块卸载

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZeqiZ");

整个过程先放上面,在后面结合应用程序来大概讲一下。主要就是看下读和写两个函数。

应用程序

应用程序修改的地方大一些

/**
 * @file testAPP.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2022-04-02
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

/**
 * @brief 
 * 
 * @param argc                      //参数个数 
 * @param argv                      //参数
 * @return int 
 */
int main(int argc,char *argv[])
{
    char *filename;                 //文件名
    filename = argv[1];             //文件名为命令行后第二个参数(索引值为1)

    int ret = 0;                    //初始化操作返回值
    int f = 0;                      //初始化文件句柄
    int action = atoi(argv[2]);     //读写标志:0为读,1为写
    char readbuf[100];              //初始化读数据缓存
    char writebuf[100];             //初始化写数据缓存

    char testdata[] =  {"data from user Application!"};     //测试数据,准备写入内核的数据

    if(argc != 3){                                          //输入参数数量!=3,提示输入格式错误!
        printf("input format error!");
    }

    f = open(filename, O_RDWR);                             //打开文件

    if(f < 0){
        printf("open file %s failed!\r\n", filename);
    }

    /**
     * @brief 参数为0,进行读操作
     * 
     */
    if(action == 0){                             
        ret = read(f, readbuf , 100);
        if (ret < 0)
        {
            printf("read err!");
            return -1;
        }
        else{
            printf("read data from kernel:%s\r\n",readbuf);
            /*sleep1秒,等待printf函数打印完成*/
            sleep(1);
            ret = close(f);
            return 0;
        }
    }
    
    /**
     * @brief 参数为1,进行写操作
     * 
     */
    else if(action == 1){
        ret = write(f, testdata, sizeof(testdata));
        if(ret <0){
            printf("write err!");
            return -1;
            }
        else{
            close(f);
            return 0;
        }
    }

    else{
        close(f);
        return 0;
    }

}

因为我们通过应用文件操作驱动文件主要的方式就是读和写,所以方便起见命令的格式为

./APPtest /dev/testDev 0

一共3个参数,第一个是应用程序名称,第二个是设备节点文件,第三个是读写标志位。在函数一开始会判断参数数量是否和要求格式一致(3个),不一致时提示格式错误。正常的话打开文件进行后续操作。

文件读写操作

这一章主要就是讲一下怎么实现文件的读写操作。

读数据

当我们需要获取硬件的数据时(例如获取传感器信息)就要使用到读操作。在挂载模块并加载设备文件节点以后通过命令行输入如下命令

./testAPP /dev/testDev 0

第三个参数为0(argv[2]),在APP里action变量值就为0。通过if据判断调用read函数。read函数中的参数为文件句柄f,读取缓存readbuf,读取的数据长度100。在执行read函数时,内核执行文件操作结构体内read对应的函数dev_read。dev_read里最主要的功能就是调用下面的函数

ret = copy_to_user(buf,kerneldata,sizeof(kerneldata));  //向用户空间写入数据

就是将kerneldata变量里的值传递给用户空间。用户空间在拿到值以后通过printf函数打印出来。

这里有个bug:

printf函数和printk函数的优先级不知道是不是有什么关系,在打印readbuf里的内容时,在打印了一半的信息后关闭文件的提示信息会被printk打印出来。然后再继续打印(类似中断的效果)。所以我在这里加了个sleep,等了1秒钟,等消息被打印完全了再关闭文件。

写数据

写数据和读数据的过程基本一致,在命令行内输入下面的命令

./testAPP /dev/testDev 1

通过第3个参数1,程序调用if判断执行write函数,在write函数里我们将testdata给到buf内,内核中使用

    ret = copy_from_user(writebuf,buf,count);           //从用户空间获取数据写入内核空间(writebuf)
    if (ret == 0){
        printk("get data from APP:%s\r\n",writebuf);
        return 0;
    }

copy_from_user函数,将数据读出至writebuf内,如果无异常就将writebuf里的数据通过printk打印出来,而这里的打印信息和关闭的打印信息都是通过内核的printk打印的,所以不存在前面的bug。这样整个驱动就完成了!



这篇关于Linux驱动开发一.字符设备框架——4.驱动测试的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程