Linux内核驱动学习(三)字符型设备驱动之初体验
Linux字符型设备驱动之初体验
前言
驱动总共分为字符型设备驱动,块设备驱动,网络设备驱动。对于字符型设备驱动的资料,网上比较多,《Linux Kernel Driver》这本书可以了解一下,对于学习Linux驱动有很大的帮助,当然还有很多优秀的书籍,暂不一一列举,本文简单总结了在学习字符型设备驱动的过程中遇到的问题,以及对该类驱动的理解。
框架
字符型设备
什么是字符型设备?字符型以字符(Byte/Char)为单位进行数据传输的设备,如键盘,串口等等设备,所以Linux环境编程中文件I/O进行操作的系统接口如open
,read
,write
,close
等等,在字符型设备驱动中同样需要支持这些接口。这里会用到file_operations
结构体,在后面会讲到。
程序实现
下面是一个简单字符型设备驱动程序,可以在系统注册一个字符型设备驱动,目前未实现open
,read
,write
,close
等接口。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#define DRIVER_DATA_SIZE 4096
static int major_dev_index = 0;
struct cnc_character_st{
struct cdev device;
u8 data[DRIVER_DATA_SIZE];
};
static struct cnc_character_st *character_dev;
//TODO
static ssize_t cnc_character_read (struct file * fd, char __user * data, size_t len, loff_t * offset){
ssize_t ret = 0;
printk("%s call\n",__func__);
return ret;
}
//TODO
static ssize_t cnc_character_write (struct file * fd, const char __user * data, size_t len, loff_t * offset){
ssize_t ret = 0;
return ret;
}
//TODO
static long cnc_character_unlocked_ioctl (struct file * fd, unsigned int data, unsigned long cmd){
long ret = 0;
return ret;
}
//TODO
static int cnc_character_open (struct inode * node, struct file * fd){
int ret = 0;
return ret;
}
//TODO
static int cnc_character_release (struct inode * node, struct file * fd){
int ret = 0;
return ret;
}
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,
};
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;
}
static int unregister_device(struct cnc_character_st *mdev){
int ret= 0;
kfree(character_dev);
return ret;
}
static int __init cnc_character_init(void){
int ret = 0;
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);
}
if(ret < 0){
return ret;
}
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);
return 0;
failed:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(cnc_character_init);
static void __exit cnc_character_exit(void){
printk("%s call\n",__func__);
unregister_device(character_dev);
}
module_exit(cnc_character_exit);
MODULE_AUTHOR("zhaojunhui@cncgroup.top");
MODULE_VERSION("1.0");
MODULE_LICENSE("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
- 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 <linux/kobject.h>
#include <linux/kdev_t.h>
#include <linux/list.h>
struct file_operations;
struct inode;
struct module;
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
void cd_forget(struct inode *);
#endif
- 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
。
kobj
kobject
是所有设备驱动模型的基类,而cdev
可以理解为是它的派生类,这里使用了面向对象的思想,通过访问cdev
中的kobj
成员,就能使用kobject
中所有功能。关于kobject
的详细内容可以参考内核文档Documentation/kobject.txt
。
owner
首先明确一点的是owner
是struct module
的指针变量,owner=THIS_MODULE;
,这里将指针指向当前的模块,关于THIS_MODULE
以及struct module
的知识可以参考这篇博客。
file_operations
这个结构体位于/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
dev_t
设备注册过程
设备的初始化在函数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
在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
内核编译
Makefile
obj-$(CONFIG_DEMO_CHARACTER_DRIVER) +=demo_character.o
- 1
Kconfig
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
文章来源: great.blog.csdn.net,作者:小麦大叔,版权归原作者所有,如需转载,请联系作者。
原文链接:great.blog.csdn.net/article/details/86764967
- 点赞
- 收藏
- 关注作者
评论(0)