前面字符设备用register_chrdev注册设备,用unregister_chrdev注销设备。新的字符设备驱动使用linux推荐的新API。此外,前面测试的时候要自己建立设备节点,本节学习如何在加载驱动的时候自动新建节点。
旧方法缺陷:
register_chrdev注册只需要给一个主设备号,但是这样就导致该主设备号下的次设备号全都归属该设备,比如led,太浪费资源。
解决方案:
<未给定设备号,向内核申请并注册设备号 PARA:设备号指针,起始设备号,申请个数,设备名>
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
<给定主设备号,注册设备号 通常用这个 para:起始设备号(上一行给定的),申请个数,设备名>
int register_chrdev_region(dev_t from, unsigned count, const char *name)
<注销设备号>
void unregister_chrdev_region(dev_t from, unsigned count)
/---------------------------------------------------------------/
int major; <主设备号>
int minor; <次设备号>
dev_t devid; <设备号>
if (major) { <定义了主设备号>
devid = MKDEV(major, 0); <大部分驱动次设备号都选择 零>
register_chrdev_region(devid, 1, "test");
} else { <没有定义设备号>
alloc_chrdev_region(&devid, 0, 1, "test"); <申请设备号>
major = MAJOR(devid); <获取分配号的主设备号>
minor = MINOR(devid); <获取分配号的次设备号>
}
unregister_chrdev_region(devid, 1); <注销设备号>
使用cdev结构体表示一个字符设备。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; <操作函数集合>
struct list_head list;
dev_t dev; <设备号>
unsigned int count;
};
struct cdev test_cdev <定义了一个字符设备test_dev>
用于初始化字符设备。
<函数原型>
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
--------------------------------------------------------------------
struct cdev testcdev;
<设备操作函数>
static struct file_operations test_fops = {
.owner = THIS_MODULE,
/* 其他具体的初始项 */
};
testcdev.owner = THIS_MODULE;
cdev_init(&testcdev, &test_fops); <初始化cdev结构体变量>
向linux系统添加字符设备。
<函数原型 Para:字符设备,设备号,添加个数>
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
-------------------------------------------------------
cdev_add(&testcdev, devid, 1);
卸载驱动时删除字符设备。
结合unregister_chrdev_region相当于unregister_chrdev。
<函数原型>
void cdev_del(struct cdev *p)
---------------------------------
cdev_del(&testcdev);
前面使用modprobe加载模块之后会自动在/dev下创建设备节点文件,rmmod后就会删除文件。这其实是mdev用户程序完成的,该程序可以根据系统中硬件设备状态来创建或者删除设备文件。mdev是BusyBox根据Linux下的udev构建的简化版,用于嵌入式Linux。热插拔事件也是mdev管理的。
下面学习如何用mdev实现自动管理节点。
1)创建和删除类
位置:驱动入口函数,cdev_add之后。
<创建:>
<owner一般为THIS_MODULE,name是类名,返回值是指向结构体class的指针也就是创建的类>
struct class *class_create (struct module *owner, const char *name)
<删除:>
void class_destroy(struct class *cls);
2)创建设备
位置:驱动出口函数。
<创建:>
<class-要创建到哪个类下面;parent-父设备-一般为NULL;devt-设备号>
<drvdata-设备可能使用的数据,一般为NULL;fmt-设备名>
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
<删除:>
void device_destroy(struct class *class, dev_t devt)
3)设置文件私有数据
其实就是写成结构体封装一下,再open函数中作为私有数据添加到设备文件:
<设备结构体>
struct test_dev{
dev_t devid; <设备号>
struct cdev cdev; <cdev>
struct class *class; <类>
struct device *device; <设备>
int major; <主设备号>
int minor; <次设备号>
};
static int test_open(struct inode *inode, struct file *filp)
{
filp->private_data = &testdev; /* 设置私有数据 */
return 0;
}