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