Skip to content

LV030-文件私有数据

一、概述

在之前编写的驱动程序中, 将生成字符设备的一些硬件属性(设备号、 类、 设备名称等) 全都写成了变量的形式, 虽然这样编写驱动代码不会产生报错, 但是会显得有点不专业。通常在驱动开发中会为设备定义相关的设备结构体, 将硬件属性的描述信息全部放在该结构体中, 这一节对设备结构体的功能实现和文件私有数据进行学习。

二、文件私有数据

Linux 中并没有明确规定要使用文件私有数据, 但是在 linux 驱动源码中, 广泛使用了文件私有数据, 这是 Linux 驱动遵循的“潜规则” , 实际上也体现了 Linux 面向对象的思想。 struct file 结构体中专门为用户留了一个域用于定义私有数据。 我们来看一下:fs.h - include/linux/fs.h - struct file

c
struct file {
	// ......
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;
	// ......
	/* needed for tty driver, and maybe others */
	void			*private_data;
	// ......
};

文件私有数据的概念在 Linux 驱动中有着非常广泛的应用, 文件私有数据就是将私有数据 private_data 指向设备结构体。 通过它可以将私有数据一路从 open 函数带到 read, write 函数层层传入。 一般是在 open 的时候赋值, read、 write 时使用。 open 函数中私有数据的使用如下所示:

c
struct device_test dev1;
static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data = &dev1;
    return 0;
};

在上述代码中, 定义了一个设备结构体 dev1, 然后在 open 函数中, 将私有数据 private_data 指向了设备结构体 dev1。我们可以在 read write 函数中通过 private_data 访问设备结构体, 如下所示:

c
static ssize_t cdev_test_write(struct file *file,const char _user *buf, size_t size, loff_t *off_t)
{
    struct device_test *test_dev = (struct device_test *)file->private_data;
    return 0;
}

三、使用实例

1. 源码编写

1.1 chrdev_data_private_demo.c

c
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"

#define CHRDEV_NAME "sdev"    /* 设备名, cat /proc/devices 查看与设备号的对应关系 */
#define CLASS_NAME  "sclass"  /* 类名,在 /sys/class 中显示的名称 */
#define DEVICE_NAME "sdevice" /* 设备节点名,在 /sys/class/class_name/ 中显示的名称以及 /dev/ 下显示的节点名 */
#define BUFSIZE     32        /* 设置最大偏移量为 32 */

#define CMD_TEST0 _IO('S', 0)
#define CMD_TEST1 _IOW('S', 1, int)
#define CMD_TEST2 _IOR('S', 2, int)
#define CMD_TEST3 _IOW('S', 3, int)

struct __CMD_TEST{
	int a;
	int b;
	int c;
};

typedef struct __CHAR_DEVICE
{
    dev_t dev_num;         // 定义 dev_t 类型(32 位大小)的变量 dev_num, 用来存放设备号
    struct cdev s_cdev;    // 定义 cdev 结构体类型的变量 scdev
    struct class *class;   // 定于 struct class *类型结构体变量 class,表示要创建的类
    struct device *device; // 设备
    char buf[BUFSIZE];     // 设置数据存储数组 mem
} _CHAR_DEVICE;

_CHAR_DEVICE g_sdev;  //定义一个 device_test 结构体变量

static int sdev_open(struct inode *inode, struct file *file)
{
    printk("This is sdev_open!\n");
    file->private_data = &g_sdev;  //设置私有数据
    return 0;
}

static ssize_t sdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    int i = 0;
    loff_t offset = *off; // 将读取数据的偏移量赋值给 loff_t 类型变量 p
    size_t count = size;
    _CHAR_DEVICE *pDev=(_CHAR_DEVICE *)file->private_data; //在 write 函数中读取 private_data

    if (offset > BUFSIZE)
    {
        return 0;
    }

    if (count > BUFSIZE - offset)
    {
        count = BUFSIZE - offset;
    }

    if (copy_to_user(buf, pDev->buf + offset, count))
    { 
        // 将 mem 中的值写入 buf,并传递到用户空间
        printk("copy_to_user error!\n");
        return -1;
    }

    for (i = 0; i < BUFSIZE; i++)
    {
        printk("buf[%d] %c\n", i, pDev->buf[i]); // 将 mem 中的值打印出来
    }
    printk("read offset is %llu, count is %d\n", offset, count);
    *off = *off + count; // 更新偏移值

    return count;
}

static ssize_t sdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    int i = 0;
    loff_t offset = *off; // 将读取数据的偏移量赋值给 loff_t 类型变量 p
    size_t count = size;
    _CHAR_DEVICE *pDev=(_CHAR_DEVICE *)file->private_data; //在 write 函数中读取 private_data

    if (offset > BUFSIZE)
    {
        return 0;
    }

    if (count > BUFSIZE - offset)
    {
        count = BUFSIZE - offset;
    }

    if (copy_from_user(pDev->buf + offset, buf, count))
    { 
        // 将 buf 中的值,从用户空间传递到内核空间
        printk("copy_to_user error \n");
        return -1;
    }

    for (i = 0; i < BUFSIZE; i++)
    {
        printk("buf[%d] %c\n", i, pDev->buf[i]); // 将 mem 中的值打印出来
    }
    printk("write offset is %llu, count is %d\n", offset, count); // 打印写入的值
    *off = *off + count;                         // 更新偏移值

    return count;
}

static int sdev_release(struct inode *inode, struct file *file)
{
    printk("This is sdev_release!\n");
    return 0;
}

static loff_t sdev_llseek(struct file *file, loff_t offset, int whence)
{
    loff_t new_offset = 0; // 定义 loff_t 类型的新的偏移值
    switch (whence)    // 对 lseek 函数传递的 whence 参数进行判断
    {
        case SEEK_SET:
            if (offset < 0 || offset > BUFSIZE)
            {
                return -EINVAL; // EINVAL = 22 表示无效参数
            }
            new_offset = offset; // 如果 whence 参数为 SEEK_SET,则新偏移值为 offset
            break;
        case SEEK_CUR:
            if ((file->f_pos + offset < 0) || (file->f_pos + offset > BUFSIZE))
            {
                return -EINVAL;
            }
            new_offset = file->f_pos + offset; // 如果 whence 参数为 SEEK_CUR,则新偏移值为 file-> f_pos + offset,file-> f_pos 为当前的偏移值
            break;
        case SEEK_END:
            if (file->f_pos + offset < 0)
            {
                return -EINVAL;
            }
            new_offset = BUFSIZE + offset; // 如果 whence 参数为 SEEK_END,则新偏移值为 BUFSIZE + offset,BUFSIZE 为最大偏移量
            break;
        default:
            break;
    }
    file->f_pos = new_offset; // 更新 file-> f_pos 偏移值

    return new_offset;
}

static long sdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int val = 0;//定义 int 类型向应用空间传递的变量 val
    switch(cmd)
    {
        case CMD_TEST0:
            printk("this is CMD_TEST0\n");
            break;
        case CMD_TEST1:
            printk("this is CMD_TEST1\n");
            printk("arg is %ld\n",arg);//打印应用空间传递来的 arg 参数
            break;
        case CMD_TEST2:
            val = 1;//将要传递的变量 val 赋值为 1
            printk("this is CMD_TEST2\n");
            if(copy_to_user((int *)arg, &val, sizeof(val)) != 0)
            {
                //通过 copy_to_user 向用户空间传递数据
                printk("copy_to_user error \n");
            }
            break;
        case CMD_TEST3:
        {
            struct __CMD_TEST cmd_test3 = {0};
            if (copy_from_user(&cmd_test3, (int *)arg, sizeof(cmd_test3)) != 0)
            {
                printk("copy_from_user error\n");
            }
            printk("cmd_test3.a = %d\n", cmd_test3.a);
            printk("cmd_test3.b = %d\n", cmd_test3.b);
            printk("cmd_test3.c = %d\n", cmd_test3.c);
            break;
        }
        default:
            break;
    }

    return 0;
}

static struct file_operations cdev_ops = {
    .owner = THIS_MODULE,//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
	.open = sdev_open,
	.read = sdev_read,
	.write = sdev_write,
	.release = sdev_release,
    .llseek = sdev_llseek,
    .unlocked_ioctl = sdev_ioctl,
}; // 定义 file_operations 结构体类型的变量 g_cdev_dev_ops

// 模块入口函数
static int __init chrdev_data_private_demo_init(void)
{
    int ret;          // 定义 int 类型的变量 ret,用来判断函数返回值
    int major, minor; // 定义 int 类型的主设备号 major 和次设备号 minor
    printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
           __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
    printk("chrdev_data_private_demo module init!\n");

    ret = alloc_chrdev_region(&g_sdev.dev_num, 0, 1, CHRDEV_NAME); // 自动获取设备号,设备名为 chrdev_name
    if (ret < 0)
    {
        printk("alloc_chrdev_region is error!\n");
    }
    printk("alloc_register_region is ok!\n");
    major = MAJOR(g_sdev.dev_num); // 使用 MAJOR()函数获取主设备号
    minor = MINOR(g_sdev.dev_num); // 使用 MINOR()函数获取次设备号
    printk("major is %d, minor is %d !\n", major, minor);

    cdev_init(&g_sdev.s_cdev, &cdev_ops); // 使用 cdev_init()函数初始化 g_sdev.s_cdev 结构体,并链接到 g_cdev_dev_ops 结构体
    g_sdev.s_cdev.owner = THIS_MODULE;          // 将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    ret = cdev_add(&g_sdev.s_cdev, g_sdev.dev_num, 1); // 使用 cdev_add()函数进行字符设备的添加
    if (ret < 0)
    {
        printk("cdev_add is error !\n");
    }
    printk("cdev_add is ok !\n");
    g_sdev.class = class_create(THIS_MODULE, CLASS_NAME);          // 使用 class_create 进行类的创建,类名称为 class_dev
    g_sdev.device = device_create(g_sdev.class, NULL, g_sdev.dev_num, NULL, DEVICE_NAME); // 使用 device_create 进行设备的创建,设备名称为 device_dev

    return 0;
}

// 模块出口函数
static void __exit chrdev_data_private_demo_exit(void)
{
    // 需要注意的是, 字符设备的注册要放在申请字符设备号之后,
    // 字符设备的删除要放在释放字符驱动设备号之前。
    cdev_del(&g_sdev.s_cdev);                // 使用 cdev_del()函数进行字符设备的删除
    unregister_chrdev_region(g_sdev.dev_num, 1); // 释放字符驱动设备号
    device_destroy(g_sdev.class, g_sdev.dev_num); // 删除创建的设备
    class_destroy(g_sdev.class);           // 删除创建的类
    printk("chrdev_data_private_demo exit!\n");
}

module_init(chrdev_data_private_demo_init); // 将__init 定义的函数指定为驱动的入口函数
module_exit(chrdev_data_private_demo_exit); // 将__exit 定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo chrdev_data_private_demo 查看) */
MODULE_LICENSE("GPL v2");            /* 源码的许可证协议 */
MODULE_AUTHOR("sumu");               /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description");   /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

1.2 chrdev_data_private_demo_app.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <sys/ioctl.h>

#define BUFSIZE   32 /* 设置最大偏移量为 64, 方便打印完整的内存空间数据*/
#define CMD_TEST0 _IO('S', 0)
#define CMD_TEST1 _IOW('S', 1, int)
#define CMD_TEST2 _IOR('S', 2, int)
#define CMD_TEST3 _IOW('S', 3, int)

struct __CMD_TEST{
	int a;
	int b;
	int c;
};

static char usrdata[] = {"sumu"};

void usage_info(void)
{
	printf("\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
	printf("+       help information  @sumu          +\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");

	printf("help:\n");
	printf("use format: ./app_name /dev/device_name arg1 ... \n");
	printf("            ./chrdev_data_exchange_demo_app.out /dev/sdevice 1 x # 从x位置读取 \n");
	printf("            ./chrdev_data_exchange_demo_app.out /dev/sdevice 2 x # 从x位置写入 \n");
	printf("            ./chrdev_data_exchange_demo_app.out /dev/sdevice 3 x # x为任意值 \n");
    printf("            驱动中buf最大为1024字节 \n");
	printf("\n");

	printf("command info:\n");
	printf("  (1)load module  : insmod module_name.ko\n");
	printf("  (2)unload module: rmmod module_name.ko\n");
	printf("  (3)show module  : lsmod\n");
	printf("  (4)view device  : cat /proc/devices\n");
	printf("  (5)create device node: mknod /dev/device_name c major_num secondary_num \n");
	printf("  (6)show device node  : ls /dev/device_name \n");
	printf("  (7)show device vlass  : ls /sys/class \n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
}

int main(int argc, char *argv[])
{
    int fd = -1;
    int ret = 0;
    char *filename = NULL;
    unsigned int arg1 = 0;
    unsigned int arg2 = 0;

    char readbuf[BUFSIZE] = {0};
    char writebuf[BUFSIZE] = {0};

    unsigned int off1 = 0; // 定义读写偏移位置
    unsigned int off2 = 0; // 定义读写偏移位置
    unsigned int off = 0; // 定义读写偏移位置

    printf("*** Build Time: %s %s,Git Version: %s Git Remote: %s***\n", 
            __DATE__, __TIME__, GIT_VERSION, GIT_PATH);
    // ./xxx.out /dev/sdevice x x
    if (argc != 4) 
    {
        usage_info();
        return -1;
    }
    // 解析参数
    filename = argv[1];
    arg1 = atoi(argv[2]);
    arg2 = atoi(argv[3]);
    printf("%s %s %d %d\n", argv[0], filename, arg1, arg2);

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("can't open file %s !\n", filename);
        return -1;
    }

    off1 = lseek(fd, 0, SEEK_END); // 读取字符设备文件可读写最大的大小
    printf("%s mem buf size is %d\n", filename, off1);
    if (arg1 == 1)
    { 
        off = arg2;// 获取读写的偏移位置
        /* 从驱动文件读取数据 */
        ret = lseek(fd, off, SEEK_SET); // 将偏移量设置为距离起始地址 off 的位置
        if (ret < 0)
        {
            printf("lseek %s %d failed!\n", filename, off);
        }
        
        ret = read(fd, readbuf, BUFSIZE);
        if (ret < 0)
        {
            printf("read file %s failed!\n", filename);
        }
        off2 = lseek(fd, 0, SEEK_CUR); // 读取当前位置的偏移量
        /*  读取成功,打印出读取成功的数据 */
        printf("read data \"%s\" from %s! off=%d off2=%d\n", readbuf, filename, off, off2);

    }
    else if (arg1 == 2)
    {
        off = arg2;// 获取读写的偏移位置
        ret = lseek(fd, off, SEEK_SET); // 将偏移量设置为距离起始地址 off 的位置
        if (ret < 0)
        {
            printf("lseek %s %d failed!\n", filename, off);
        }

        /* 向设备驱动写数据 */
        memcpy(writebuf, usrdata, sizeof(usrdata));
        ret = write(fd, writebuf, sizeof(usrdata));
        if (ret < 0)
        {
            printf("write file %s failed!\n", filename);
        }
        off2 = lseek(fd, 0, SEEK_CUR); // 读取当前位置的偏移量
        printf("write \"%s\" to %s success!off=%d off2=%d\n", usrdata, filename, off, off2);
    }
    else if (arg1 == 3)
    {
        int val = 0;
        struct __CMD_TEST cmd_test3 = {2, 3, 5};
        ioctl(fd,CMD_TEST0);
        ioctl(fd,CMD_TEST1, 1);
        ioctl(fd,CMD_TEST2, &val);
        printf("val = %d\n", val);

        ioctl(fd,CMD_TEST3, &cmd_test3);
    }
    /* 关闭设备 */
    ret = close(fd);
    if (ret < 0)
    {
        printf("can't close file %s !\n", filename);
        return -1;
    }

    return 0;
}

2. 开发板测试

shell
# 加载驱动
insmod xxx_demo.ko

# app 测试
./xxx_demo_app.out /dev/dev_node 3 x # ioctl 测试

# 卸载驱动
rmmod xxx_demo.ko