驱动-文件私有数据

Android 驱动中的文件私有数据机制
在 Linux/Android 驱动开发中,文件私有数据(file private data)是一种重要的机制,它允许驱动程序为每个打开的文件实例存储特定的数据。


基本原理

文件私有数据是通过 file 结构体中的 private_data 字段实现的:

将生成字符设备的一些硬件属性(设备号、 类、 设备名称
等) 全都写成了变量的形式, 虽然这样编写驱动代码不会产生报错, 但是会显得有点不专业。
通常在驱动开发中会为设备定义相关的设备结构体, 将硬件属性的描述信息全部放在该结构体

文件私有数据简介

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

基础回顾 - file_operations

我们在之前的实践中 用到了 file_operations 结构体,对应的内核系统调用方法open/read/write/release ,我们再次简要看看代码。


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

static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    printk("This is chrdev_read \n");
    return 0;
}

static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    printk("This is chrdev_write \n");
    return 0;
}
static int chrdev_release(struct inode *inode, struct file *file)
{
    return 0;
}

static struct file_operations misc_fops = {
    .owner = THIS_MODULE,      // 将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    .open = chrdev_open,       // 将open字段指向chrdev_open(...)函数
    .read = chrdev_read,       // 将open字段指向chrdev_read(...)函数
    .write = chrdev_write,     // 将open字段指向chrdev_write(...)函数
    .release = chrdev_release, // 将open字段指向chrdev_release(...)函数
}; // 定义file_operations结构体类型的变量misc_fops

这里重点看chrdev_open 、chrdev_read、chrdev_write、chrdev_release 方法 里面的参数 file 它是每个方法里面共有的一个结构体参数,是一个结构体类型的指针类型。

那么在这个结构体有木有共用的指针类型,不就可以实现传递吗? 这里就是 file 的属性 private_data 指针类型 出场了。 如下定义:

void *private_data;  // 文件私有数据指针 

那么如果把部分数据定义在这结构体里面,它又是个指针,那么不就实现了不同方法之间共用了吗? 通过file结构体指针找private_data 类型结构体指针,那么数据就实现了传递。 这个private_data 就是存储私有数据的东西。

下面给一下完整的file 结构体定义,里面是有一个 private_data 类型指针变量的。

struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY

void *f_security;
#endif

/* needed for tty driver, and maybe others */
void *private_data;//私有数据


#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_headf_ep_links;
struct list_headf_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
}

小结

文件私有数据就是将私有数据
private_data 指向设备结构体。 通过它可以将私有数据一路从 open 函数带到 read, write 函数层
层传入。 一般是在 open 的时候赋值, read、 write 时使用

open 的时候赋值

定义了一个设备结构体 dev1, 然后在 open 函数中, 将私有数据 private_data
指向了设备结构体 dev1

struct device_test dev1;

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

read write 函数中通过 private_data 访问设备结构体

在 read write 函数中通过 private_data 访问设备结构体


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;
}

 

实验

源码程序 file.c

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

struct device_test
{

    dev_t dev_num;         // 设备号
    int major;             // 主设备号
    int minor;             // 次设备号
    struct cdev cdev_test; // cdev
    struct class *class;   // 类
    struct device *device; // 设备
    char kbuf[32];         // 缓存区buf
};

struct device_test dev1; // 定义一个device_test结构体变量

/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{

    file->private_data = &dev1; // 设置私有数据
    printk("This is cdev_test_open\r\n");

    return 0;
}

/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *test_dev = (struct device_test *)file->private_data; // 在write函数中读取private_data

    if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
    {
        printk("copy_from_user error\r\n");
        return -1;
    }
    printk("This is cdev_test_write\r\n");

    printk("kbuf is %s\r\n", test_dev->kbuf); // 打印kbuf的值
    return 0;
}

/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{

    struct device_test *test_dev = (struct device_test *)file->private_data;

    if (copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
    {
        printk("copy_to_user error\r\n");
        return -1;
    }

    printk("This is cdev_test_read\r\n");
    return 0;
}

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

/*设备操作函数*/
struct file_operations cdev_test_fops = {
    .owner = THIS_MODULE,         // 将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    .open = cdev_test_open,       // 将open字段指向chrdev_open(...)函数
    .read = cdev_test_read,       // 将open字段指向chrdev_read(...)函数
    .write = cdev_test_write,     // 将open字段指向chrdev_write(...)函数
    .release = cdev_test_release, // 将open字段指向chrdev_release(...)函数
};

static int __init chr_fops_init(void) // 驱动入口函数
{
    /*注册字符设备驱动*/
    int ret;
    /*1 创建设备号*/
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); // 动态分配设备号
    if (ret < 0)
    {
        printk("alloc_chrdev_region is error\n");
    }
    printk("alloc_chrdev_region is ok\n");

    dev1.major = MAJOR(dev1.dev_num); // 获取主设备号
    dev1.minor = MINOR(dev1.dev_num); // 获取次设备号

    printk("major is %d \r\n", dev1.major); // 打印主设备号
    printk("minor is %d \r\n", dev1.minor); // 打印次设备号
    /*2 初始化cdev*/
    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_fops);

    /*3 添加一个cdev,完成字符设备注册到内核*/
    cdev_add(&dev1.cdev_test, dev1.dev_num, 1);

    /*4 创建类*/
    dev1.class = class_create(THIS_MODULE, "test");

    /*创建设备*/
    dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");

    return 0;
}

static void __exit chr_fops_exit(void) // 驱动出口函数
{
    /*注销字符设备*/
    unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号
    cdev_del(&dev1.cdev_test);                 // 删除cdev
    device_destroy(dev1.class, dev1.dev_num);  // 删除设备
    class_destroy(dev1.class);                 // 删除类
}
module_init(chr_fops_init);
module_exit(chr_fops_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

源码解读

这笔务必对比一下之前了解的内容开发流程,比如最近的杂项设备实验, 开发框架流程都是一摸一样的,这里看区别:

这里私有数据实验有一个结构体,基本上把以前的变量都放到了结构体里面的,然后获取变量和变量使用都是用指针引用的方式来使用的。

struct device_test
{

    dev_t dev_num;         // 设备号
    int major;             // 主设备号
    int minor;             // 次设备号
    struct cdev cdev_test; // cdev
    struct class *class;   // 类
    struct device *device; // 设备
    char kbuf[32];         // 缓存区buf
};

创建设备号: 用&dev1.dev_num
alloc_chrdev_region(&dev1.dev_num

 /*2 初始化cdev*/
    dev1.cdev_test.owner = THIS_MODULE;

  dev1.major = MAJOR(dev1.dev_num); // 获取主设备号
    dev1.minor = MINOR(dev1.dev_num); // 获取次设备号    

   /*3 添加一个cdev,完成字符设备注册到内核*/
    cdev_add(&dev1.cdev_test, dev1.dev_num, 1);

  /*2 初始化cdev*/
    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_fops);

    /*3 添加一个cdev,完成字符设备注册到内核*/
    cdev_add(&dev1.cdev_test, dev1.dev_num, 1);

    /*4 创建类*/
    dev1.class = class_create(THIS_MODULE, "test");

    /*创建设备*/
    dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");

全部都是用的结构体指针属性来表示的。 







我们在字符设备实验中,简要再次看看: 我们都是声明变量的方式来实现的呀
static dev_t dev_num;//定义dev_t类型(32位大小)的变量dev_num
static struct cdev cdev_test;  //定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备

struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类

    int ret;//定义int类型的变量ret,用来判断函数返回值
    int major,minor;//定义int类型的主设备号major和次设备号minor



编译文件Makefile

#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += file.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

测试app

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

int main(int argc, char *argv[]) //主函数
{
    int fd;
    char buf1[32] = "nihao";  //定义写入缓存区buf1
    fd = open("/dev/test", O_RDWR); //打开/dev/test设备
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    write(fd,buf1,sizeof(buf1)); //向/dev/test设备写入数据
    close(fd);
    return 0;
}

编译程序-编译测试程序

wfc123@ubuntu:~/Linux/project/linuxstudy/08_private$ make
make -C /home/wfc123/Linux/rk356x_linux/kernel M=/home/wfc123/Linux/project/linuxstudy/08_private modules
make[1]: 进入目录“/home/wfc123/Linux/rk356x_linux/kernel”
  CC [M]  /home/wfc123/Linux/project/linuxstudy/08_private/file.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/wfc123/Linux/project/linuxstudy/08_private/file.mod.o
  LD [M]  /home/wfc123/Linux/project/linuxstudy/08_private/file.ko
make[1]: 离开目录“/home/wfc123/Linux/rk356x_linux/kernel”


wfc123@ubuntu:~/Linux/project/linuxstudy/08_private$ cd app
wfc123@ubuntu:~/Linux/project/linuxstudy/08_private/app$ ls -l
总用量 4
-rwxrw-rw- 1 wfc123 wfc123 469 414 20:46 app.c
wfc123@ubuntu:~/Linux/project/linuxstudy/08_private/app$ aarch64-linux-gnu-gcc app.c -o app
wfc123@ubuntu:~/Linux/project/linuxstudy/08_private/app$ ls -l
总用量 20
-rwxrwxr-x 1 wfc123 wfc123 13264 414 20:47 app
-rwxrw-rw- 1 wfc123 wfc123   469 414 20:46 app.c
wfc123@ubuntu:~/Linux/project/linuxstudy/08_private/app$ 

执行命令实验

insmod 加载驱动

看到驱动加载成功,如下结果
在这里插入图片描述

查看device 是否生成了设备 ls /dev/test -al

结果如下, 已经有了设备 test

[root@topeet:/mnt/sdcard]# ls /dev/test -al
crw------- 1 root root 236, 0 Jan  3 08:46 /dev/test
[root@topeet:/mnt/sdcard]#

执行命令 ./app

结果如下: 通过结构体 私有数据存储的buff 也进行传递。 所有的封装的属性在私有数据里面定义 和 之前 定义的变量属性 得到的实验结果是一样的。
在这里插入图片描述

总结

这里对私有数据 结构体 file,通过它实现了了私有数据的共享,file 在open 、read、write 等文件操作中共享,利用共享的私有数据 定义的又是指针类型,然后封装数据,实现效果的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值