LV06-03-chrdev-06-文件私有数据

本文主要是字符设备驱动——文件私有数据的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、概述

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

二、文件私有数据

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

1
2
3
4
5
6
7
8
9
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 函数中私有数据的使用如下所示:

1
2
3
4
5
6
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 访问设备结构体, 如下所示:

1
2
3
4
5
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

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#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

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#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. 开发板测试

1
2
3
4
5
6
7
8
# 加载驱动
insmod xxx_demo.ko

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

# 卸载驱动
rmmod xxx_demo.ko