-字符设备结构体
在Linux内核中, 使用cdev结构体来描述一个字符设备
struct cdev {
struct kobject kobj; //内嵌的kobject对象
struct module *owner;//所属模块
const struct file_operations *ops; //文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。
使用下面的宏来定义dev_t
MKDEV(int major, int minor)
使用下面的宏来获取主设备号和次设备号
MAJOR(dev_t dev)
MINOR(dev_t dev)
/*
函数功能:分配一些设备号
参数说明:
from:起始设备号,必须要包含主设备号
count: 连续分配的数量
name: 设备或驱动的名称
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
/*
函数功能:分配一些字符设备号
参数说明:
dev:第一个被分配的设备号
baseminor:起始次设备号
count:分配的次设备号数(分配的设备号数)
name: 相关设备或驱动的名称
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
/*
函数功能: 释放一些设备号
参数说明:
from:释放的起始设备号
count: 释放的设备号数
*/
void unregister_chrdev_region(dev_t from, unsigned count)
/*
函数功能:初始化一个cdev结构体
参数说明:
cdev:要初始化的cdev结构体
fops:该字符设备的file_operations (操作函数)
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
/*
函数功能:动态分配一个cdev结构体
说明:该函数不常用,因为字符设备都会封装一个新的结构体,
然后使用kzalloc来分配内存(结构体)
*/
struct cdev *cdev_alloc(void)
/*
函数功能: 添加一个字符设备到系统
参数说明:
p:设备的cdev结构体
dev:第一个设备的设备号
count:连续的次设备号数
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
/*
函数功能: 从系统中移除一个cdev
参数说明:
p:要移除的cdev结构体
*/
void cdev_del(struct cdev *p)
把上面的API分成两段,一段是分配和释放设备号的,另一段就是分配字符设备和注册到系统中的。
register_chrdev_region() 和 alloc_chrdev_region() 的区别:
register_chrdev_region() 函数用于已知起始设备的设备号的情况,而alloc_chrdev_region() 用于设备号未知,向系统动态申请未被占用的设备号的情况。alloc_chrdev_region() 相比于 register_chrdev_region() 的优点就在于它会自动避开设备号重复的冲突。
/*
设备结构体
*/
struct xxx_dev_t{
struct cdev cdev;
...
};
struct xxx_dev_t *dev;
dev_t devno;
//读设备
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t* f_pos)
{
...
copy_to_user(buf, ..., ...);
}
//写设备
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t* f_pos)
{
...
copy_from_user(..., buf, ...);
}
//操作函数file_operations
struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
...
};
//设备驱动模块加载函数
static int __init xxx_init(void)
{
...
devno = MKDEV(xxx_major, 0);
//(1)申请设备号
if(xxx_major)
{
register_chrdev_region(devno, 1, "xxx_dev");
}
else
{
alloc_chrdev_region(&devno, 0, 1, "xxx_dev");
}
//(2)为设备结构体申请内存(推荐使用devm_kzalloc)
dev = kzalloc(sizeof(struct xxx_dev_t), GFP_KERNEL);
//(3)初始化cdev
cdev_init(&dev.cdev, &xxx_fops);
dev.cdev.owner = THIS_MODULE;
//(4)向系统注册设备
cdev_add(dev.cdev, dev_no, 1);
}
module_init(xxx_init);
//设备驱动模块卸载函数
static void __exit xxx_exit(void)
{
//释放设备号
unregister_chrdev_region(dev_no, 1);
//注销设备
cdev_del(&dev.cdev);
...
}
module_exit(xxx_exit);
MODULE_LICENSE("GPL v2");
/*
函数功能:注册字符设备
参数说明:major:主设备号,0表示动态分配
name: 设备名
fops: 操作函数
返回值: 申请的主设备号
*/
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
/*
函数功能:注销字符设备
参数说明:major:主设备号
name: 设备名
*/
static inline void unregister_chrdev(unsigned int major, const char *name)
分析一下上面两个函数的源码
//register_chrdev调用了__register_chrdev
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
int err = -ENOMEM;
//1. 申请设备号
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
//2. 为设备结构体申请内存
cdev = cdev_alloc();
if (!cdev)
goto out2;
// 3. 初始化cdev
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
//4. 向系统注册设备
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
return err;
}
//unregister_chrdev调用__unregister_chrdev
void __unregister_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name)
{
struct char_device_struct *cd;
//释放设备号
cd = __unregister_chrdev_region(major, baseminor, count);
if (cd && cd->cdev)
cdev_del(cd->cdev); //注销设备
kfree(cd);
}
通过上面的分析发现和上面的驱动框架是一样的,只是做了个封装罢了。这时候是不是发现了两者之间的关系,是不是很多驱动都是用这两个函数来注册的。有些书就只讲前面的,而有些教程又只讲后面。所以搞得云里雾里的。
简化上面的驱动模板
/*
设备结构体
*/
struct xxx_dev_t{
struct cdev cdev;
...
};
struct xxx_dev_t *dev;
dev_t devno;
//读设备
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t* f_pos)
{
...
copy_to_user(buf, ..., ...);
}
//写设备
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t* f_pos)
{
...
copy_from_user(..., buf, ...);
}
//操作函数file_operations
struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.read = xxx_read,
.write = xxx_write,
...
};
//设备驱动模块加载函数
static int __init xxx_init(void)
{
...
//注册字符设备
xxx_major = register_chrdev(0, "xxx_dev", &xxx_fops);
}
module_init(xxx_init);
//设备驱动模块卸载函数
static void __exit xxx_exit(void)
{
//注销字符设备
unregister_chrdev(major, "xxx_dev");
...
}
module_exit(xxx_exit);
MODULE_LICENSE("GPL v2");