06、文件操作集
一、文件操作集简介
在Linux设备驱动开发中,注册字符设备时,我们需要将cdev结构体(设备描述信息)和file_operations结构体(驱动功能入口)关联起来。具体步骤是:
- 通过cdev_init()函数把设备信息(cdev)和功能操作表(file_operations)绑定在一起
- 这个file_operations结构就像一个功能目录,里面存放着驱动程序的各种操作函数地址(如读、写、打开、关闭等)
- 当用户程序通过系统调用(如open/read/write)访问设备时,内核会自动到这个目录里找到对应的函数地址
- 然后直接执行驱动程序里预先写好的对应功能代码,这样就完成了设备操作的整个流程
简单来说:file_operations就是驱动程序和系统调用之间的连接桥梁,它把程序请求和驱动功能对应起来,让设备操作能够顺利执行。
file_operations 结构体定义在“内核源码/include/linux/fs.h” 文件中, 下面对部分常用函数进行说明:
owner 是第一个 file_operations 成员, 它并不是一个操作, 而一个指向拥有该结构的模块的指针, 避免正在操作时被卸载, 一般为初始化为 THIS_MODULES (在 <linux/module.h> 中定义的宏)
read 函数指针用来从设备中同步读取数据, 读取成功返回读取的字节数。 与应用程序中的read 函数对应。
write 函数指针用来发送数据给设备. 写入成功返回写入的字节数。 与应用程序中的 write函数对应。
unlocked_ioctl 函数指针提供对于设备的控制功能, 与应用程序中的 ioctl 函数对应。
open 函数指针用于打开设备,与应用程序中的 open 函数对应。
release 函数指针在 file 结构体释放时被调用
至此对于 file_operations 文件操作集的部分常用函数就介绍完了, 填充了部分常用函数的file_operations 结构体如下
二、实验代码
驱动
C
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
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 dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号
static struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
static struct file_operations cdev_fops_test = {
.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = chrdev_open,
.read = chrdev_read,
.write = chrdev_write,
.release = chrdev_release,
};//定义file_operations结构体类型的变量cdev_test_ops
static struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
static int __init chrdev_fops_init(void)//驱动入口函数
{
int ret;//定义int类型的变量ret,用来对函数返回值进行判断
int major,minor;//定义int类型的主设备号major和次设备号minor
ret = alloc_chrdev_region(&dev_num,0,1,"chrdev_name");//自动获取设备号,设备名chrdev_name
if (ret < 0){
printk("alloc_chrdev_region is error \n");
}
printk("alloc_chrdev_region is ok \n");
major = MAJOR(dev_num);//使用MAJOR()函数获取主设备号
minor = MINOR(dev_num);//使用MINOR()函数获取次设备号
printk("major is %d\n",major);
printk("minor is %d\n",minor);
cdev_init(&cdev_test,&cdev_fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体
cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
ret = cdev_add(&cdev_test,dev_num,1); //使用cdev_add()函数进行字符设备的添加
if (ret < 0){
printk("cdev_add is error \n");
}
printk("cdev_add is ok \n");
class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
device_create(class_test,NULL,dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_test
return 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{
device_destroy(class_test,dev_num);//删除创建的设备
class_destroy(class_test);//删除创建的类
cdev_del(&cdev_test);//删除添加的字符设备cdev_test
unregister_chrdev_region(dev_num,1);//释放字符设备所申请的设备号
printk("module exit \n");
}
module_init(chrdev_fops_init);//注册入口函数
module_exit(chrdev_fops_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
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
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
app
C
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc,char *argv[])
{
int fd;//定义int类型的文件描述符
char buf[32];//定义读取缓冲区buf
fd=open(argv[1],O_RDWR,0666);//调用open函数,打开输入的第一个参数文件,权限为可读可写
if(fd<0){
printf("open is error\n");
return -1;
}
printf("open is ok\n");
/*如果第二个参数为read,条件成立,调用read函数,对文件进行读取*/
if(!strcmp(argv[2], "read")){
read(fd,buf,32);
}
/*如果第二个参数为write,条件成立,调用write函数,对文件进行写入*/
else if(!strcmp(argv[2], "write")){
write(fd,"hello\n",6);
}
close(fd);//调用close函数,对取消文件描述符到文件的映射
return 0;
}
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
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