没有选择双面打印,还出来双面打印提示,每次复印还都出来一张白纸,怎么回事? 求迅速解答2天内要,谢谢
705
2022-05-29
linux字符型设备驱动之初体验
文章目录
Linux字符型设备驱动之初体验
前言
框架
字符型设备
程序实现
cdev
kobj
owner
file_operations
dev_t
设备注册过程
申请设备号
注册设备
register_device
如何构建
模块编译
内核编译
Makefile
Kconfig
总结
参考
前言
驱动总共分为字符型设备驱动,块设备驱动,网络设备驱动。对于字符型设备驱动的资料,网上比较多,《Linux Kernel Driver》这本书可以了解一下,对于学习Linux驱动有很大的帮助,当然还有很多优秀的书籍,暂不一一列举,本文简单总结了在学习字符型设备驱动的过程中遇到的问题,以及对该类驱动的理解。
框架
字符型设备
什么是字符型设备?字符型以字符(Byte/Char)为单位进行数据传输的设备,如键盘,串口等等设备,所以Linux环境编程中文件I/O进行操作的系统接口如open,read,write,close等等,在字符型设备驱动中同样需要支持这些接口。这里会用到file_operations结构体,在后面会讲到。
程序实现
下面是一个简单字符型设备驱动程序,可以在系统注册一个字符型设备驱动,目前未实现open,read,write,close等接口。
#include
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
cdev
在linux/cdev.h中可以阅读相关字符型设备驱动的信息,其中包括cdev结构体可以做一下分析,先定位到源码做一下分析
#ifndef _LINUX_CDEV_H #define _LINUX_CDEV_H #include
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
其中包括结构体cdev和cdev的一系列函数接口cdev_init,cdev_alloc,cdev_put,cdev_add,cdev_del,cd_forget。
kobject是所有设备驱动模型的基类,而cdev可以理解为是它的派生类,这里使用了面向对象的思想,通过访问cdev中的kobj成员,就能使用kobject中所有功能。关于kobject的详细内容可以参考内核文档Documentation/kobject.txt。
首先明确一点的是owner是struct module的指针变量,owner=THIS_MODULE;,这里将指针指向当前的模块,关于THIS_MODULE以及struct module的知识可以参考这篇博客。
这个结构体位于/linux/include/fs.h,代码如下。
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };
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
在file_operations定义了很多I/O操作接口,这里同样使用了面向对象编程的思想,每个接口可以在重新定义file_operations结构体变量的时候,重新赋于自定义功能的函数,如下,可以理解read,write,open,unlocked_ioctl,release是对抽象函数的实现。
static const struct file_operations cnc_character_ops = { .owner = THIS_MODULE, .read = cnc_character_read, .write = cnc_character_write, .open = cnc_character_open, .unlocked_ioctl = cnc_character_unlocked_ioctl, .release = cnc_character_release, };
1
2
3
4
5
6
7
8
设备注册过程
设备的初始化在函数cnc_character_init中完成具体的功能实现,主要分为两个部分,设备号的申请和设备的注册。其中设备注册单独封装到register_device函数中。
dev_t devno = MKDEV(major_dev_index, 0); if(major_dev_index){ ret = register_chrdev_region(devno, 1, "cnc_character"); }else{ ret = alloc_chrdev_region(&devno, 0, 1, "cnc_character"); major_dev_index = MAJOR(devno); }
1
2
3
4
5
6
7
8
character_dev = kmalloc(sizeof(struct cnc_character_st),GFP_KERNEL); if(!character_dev){ printk("%s failed malloc character_dev call\n",__func__); ret = -ENOMEM; goto failed; }else{ printk("%s success malloc character_dev call\n",__func__); } register_device(character_dev,major_dev_index,0);
1
2
3
4
5
6
7
8
9
10
11
在register_device中,主要用到了cdev提供的函数接口。
cdev_init初始化一个字符型设备并传入自定义的file_operations类型变量cnc_character_ops。
cdev_add将初始化的字符型设备添加到内核,并分配已经申请好的设备号。
static int register_device(struct cnc_character_st *mdev,int major_dev_index,int minor_dev_index){ int ret = 0; int dev_no = MKDEV(major_dev_index, minor_dev_index); // 初始化dev cdev_init(&mdev->device, &cnc_character_ops); mdev->device.owner = THIS_MODULE; ret = cdev_add(&mdev->device,dev_no,1); if(ret){ printk(KERN_ERR "cdev add device failed\n"); } return ret; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如何构建
模块编译
使用这个Makefile
KVERS = $(shell uname -r) # Kernel modules obj-m += demo_character.o # Specify flags for the module compilation. EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
1
2
3
4
5
6
7
8
9
10
内核编译
obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o
1
menuconfig DEMO_DRIVERS tristate "demo drivers" config DEMO_CHARACTER_DRIVER tristate "the most simplest character driver" help character driver endif
1
2
3
4
5
6
7
总结
总体上来说,字符型设备驱动框架还是相对简单的,通过这次学习加深了对cdev的认识和linux内核源码中面向对象的设计思想,但是这里还没有对devfs和sysfs做相应的介绍,后面继续学习这两者的区别以及总线驱动模型,总之,加油吧。
参考
https://blog.csdn.net/lucky_liuxiang/article/details/83413946
https://www.cnblogs.com/helloahui/p/3677192.html
https://blog.csdn.net/jk110333/article/details/8563647
Linux
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。