ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2022-04-03 13:34:23  阅读:173  来源: 互联网

标签:字符 return read ret dev Linux 驱动 printk user


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

由于没有实际数据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。这样整个驱动就完成了!

标签:字符,return,read,ret,dev,Linux,驱动,printk,user
来源: https://www.cnblogs.com/yinsedeyinse/p/16074539.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有