字符设备驱动注册------Linux2.6驱动注册框架
关于字符设备驱动有多种方法,自己写了一个Linux2.6驱动的框架,仅仅是分享,交流使用,哪里有错误的还请批评指正,欢迎交流;
Linux2.6注册可以分为静态和动态两种方式进行注册:
1 设备号申请
1.1.静态方式申请设备号
静态方式需要自己指定驱动的设备号,设备号数量等,注册函数:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
@from ---完整设备号,由主设备号major和次设备号minor合成,可以使用宏函数合成
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
@count ---指定生成设备数量,自己指定,一般来说1~255个(具体是否可以超不是很确定。。。)
@name ---设备名称
ret_value---成功返回zero,失败返回错误码。
1.2.动态方式申请设备号
动态方式需要指定开始次设备号和数量
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count)
@dev---完整设备号,需要给变量,会自动赋值
@baseminor---此设备号开始
@count---数目
ret_value---成功返回zero,失败返回错误码。
2 设备注册
2.1 结构体初始化(申请)
cdev结构体中需要填充owner项和ops项
struct cdev {`在这里插入代码片`
struct kobject kobj;
struct module *owner;//一般填充THIS_MODULE,代表这个模块
const struct file_operations *ops;//函数指针集
struct list_head list;//链表上下一个设备
dev_t dev;//完整设备号
unsigned int count;//申请设备号的数量
};
2.1.1静态初始化结构体
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
@cdev---结构体变量
@fops---函数指针集
2.1.2动态申请
struct cdev *cdev_alloc(void)
ret_vlaue---成功返回一个正确的地址,失败返回错误指针
2.2设备注册
函数:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
@p---struct cdev类型指针
@dev---完整设备号
@count---申请设备数量
到这里可以实现设备号的申请以及设备的注册了,但是并不会自动创建设备节点,可以使用mknod命令进行创建设备节点
mkond /dev/xxx -c major minor
但是这种方式十分的不方便,内核提供了自动创建设备节点的函数调用即可。
3.自动创建设备节点
3.1创建类
类可以在 /sys/class/ 目录下进行查看
创建class的函数:
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
class_creat函数只需要填充owner和name,owner还是 THIS_MODULE ,name的话可以自己随意起。
可以看出创建类的函数返回了一个struct class *类型的返回值,内核中的解释是
Returns &struct class pointer on success, or ERR_PTR() on error.
3.2创建设备节点
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
@class---类的指针变量
@parent---父系,一般赋NULL即可
@devt---完整设备号
@drvdata---设备数据,后期可以修改,此处赋NULL即可
@fmt---格式控制符,显示文本,参考printf函数的使用
@...---参考printf函数的使用
ret_value---成功返回一个正确的地址,失败返回错误指针。
4.关于ops函数指针集合的api函数
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
根据自己的需要进行函数的编写,一般来说open,release,read,write等比较常用。
5.完整代码
可以通过注释CHANGE_INIT来选择静态申请注册或者动态申请注册,可以选择对major加载模块时传参确定申请方式
insmod xxx.ko major=253
字符设备驱动代码:
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"
/*-------------------define------------------*/
//#define CHANGE_INIT //选择结构体申请方式,分动态和静态
#define BASE_NUM 0
#define CDEV_COUNT 3
#define CDEV_NAME "linux2_6_cdev"
#define CLASS_NAME "class_chrdev"
#define DEV_NODE_NAME "linux_2_6_"
/*---------------func statement--------------*/
int cdev_open(struct inode *, struct file *);
int cdev_close(struct inode *, struct file *);
/*------------variable definition------------*/
static int major = 0; //主设备号
static dev_t full_dev; //完整设备号
#ifdef CHANGE_INIT
static struct cdev cdev_str; //静态结构体变量
#else
static struct cdev *p_cdev_str; //动态申请结构体空间
#endif
static struct class *p_class_cdev;
static struct file_operations cdev_ops = {
.owner = THIS_MODULE,
.open = cdev_open,
.release = cdev_close,
};
int cdev_open(struct inode *i, struct file *fs)
{
printk("cddev_open:%s %d\n",__func__,__LINE__);
return 0;
}
int cdev_close(struct inode *i, struct file *fs)
{
printk("cddev_close:%s %d\n",__func__,__LINE__);
return 0;
}
static __init int linux2_6_init(void)
{
int i = 0;
struct device *p_ret_dev = NULL;
long ret_errno = -EBUSY;
/*静态申请设备号*/
if (major) {
full_dev = MKDEV(major,BASE_NUM);
if (register_chrdev_region(full_dev,CDEV_COUNT,CDEV_NAME)) {
printk("register chrdev is failed!\n");
goto reg_err;
}
printk("register succeed!\n");
}
/*动态申请设备号*/
else {
if (alloc_chrdev_region(&full_dev,BASE_NUM,CDEV_COUNT,CDEV_NAME)) {
printk("alloc is faile!\n");
goto reg_err;
}
printk("alloc register succeed!\n");
}
/*打印设备号*/
for(i=BASE_NUM;i<BASE_NUM+CDEV_COUNT;i++)
printk("major = %d minor = %d\n",MAJOR(full_dev),MINOR(full_dev+i));
#ifdef CHANGE_INIT
/*静态---结构体初始化*/
printk("static init\n");
cdev_init(&cdev_str,&cdev_ops);
cdev_str.owner = THIS_MODULE;
if (cdev_add(&cdev_str,full_dev,CDEV_COUNT)) {
printk("cdev_add is fail!\n");
goto cdev_err;
}
#else
/*动态---结构体初始化*/
printk("alloc init\n");
p_cdev_str = cdev_alloc();
if (IS_ERR(p_cdev_str)) {
ret_errno = PTR_ERR(p_cdev_str);
printk("alloc struct cdev is failed!\n");
goto cdev_err;
}
p_cdev_str->owner = THIS_MODULE;
p_cdev_str->ops = &cdev_ops;
if (cdev_add(p_cdev_str,full_dev,CDEV_COUNT)) {
printk("cdev_add is fail!\n");
goto class_err;
}
#endif
/*类创建*/
p_class_cdev = class_create(THIS_MODULE,CLASS_NAME);
if (IS_ERR(p_class_cdev)) {
ret_errno = PTR_ERR(p_class_cdev);
printk("class creat is faile!\n");
goto class_err;
}
/*设备节点创建*/
for(i=BASE_NUM;i<BASE_NUM+CDEV_COUNT;i++) {
p_ret_dev = device_create(p_class_cdev,NULL,full_dev+i,NULL,"%s%d",DEV_NODE_NAME,i);
if(IS_ERR(p_ret_dev)) {
ret_errno = PTR_ERR(p_ret_dev);
goto device_err;
}
}
return 0;
device_err:
for(i--;i>=BASE_NUM;i--)
device_destroy(p_class_cdev,full_dev+i);
class_destroy(p_class_cdev);
class_err:
#ifdef CHANGE_INIT
cdev_del(&cdev_str);
printk("static del\n");
#else
cdev_del(p_cdev_str);
printk("alloc del\n");
#endif
cdev_err:
unregister_chrdev_region(full_dev,CDEV_COUNT);
reg_err:
return (int)ret_errno;
}
static __exit void linux2_6_exit(void)
{
int i = 0;
for(i = BASE_NUM;i<BASE_NUM+CDEV_COUNT;i++)
device_destroy(p_class_cdev,full_dev+i);
class_destroy(p_class_cdev);
#ifdef CHANGE_INIT
cdev_del(&cdev_str);
printk("static del\n");
#else
cdev_del(p_cdev_str);
printk("alloc del\n");
#endif
unregister_chrdev_region(full_dev,CDEV_COUNT);
}
module_param(major,int,0644);
module_init(linux2_6_init);
module_exit(linux2_6_exit);
MODULE_LICENSE("GPL");
测试程序代码:
#include "stdio.h"
#include <unistd.h>
int main(int argc,char *argv[])
{
FILE *fp = NULL;
fp = fopen(argv[1],"r");
if(!fp) {
printf("main:file open failed\n");
return -1;
}
printf("main: file is open!\n");
sleep(5);
fclose(fp);
printf("main: file is close!\n");
return 0;
}
note:在内核开发中,如果调用返回值为指针的函数,出错判断条件不能简单地写为“if (ptr == NULL)”,正确的做法是先使用 IS_ERR() 或者 IS_ERR_OR_NULL() 判断指针返回值,然后再用 PTR_ERR() 将错误指针转为错误码,最后执行相应的出错处理。例如字符设备驱动开发中常见的device_create()函数,应该使用如下出错判断及处理:
if (IS_ERR(p_cdev_str)) {
ret_errno = PTR_ERR(p_cdev_str);
return ret_errno ;
}