分步实现字符设备驱动
PART 1. 分析open函数的调用流程
用户层中进行打开文件操作{调用int open(const char *pathname, int flags);},虚拟文件系统层调用sys_open{假设打开字符设备节点}:
1.根据open传递的文件路径找到文件的inode结构体
open参数中的pathname为文件路径,Linux中存在于文件系统中的文件拥有inode号作为文件系统中的唯一标识{可以通过ls -i查看},该inode号亦为该文件inode结构体的索引号{struct inode用于存放文件的相关信息}
2.根据inode结构体找到文件对应的驱动对象结构体指针
inode结构体的成员中有dev_t类型的设备号及struct cdev字符设备驱动对象结构体的指针
3.根据驱动对象结构体指针找到操作方法结构体指针
字符设备驱动对象结构体的成员中有dev_t类型设备号{作为驱动存在于内核的标识,关联设备驱动和设备节点}和const struct file_operations操作方法结构体指针
4.调用操作方法结构体中对应的函数指针{此处的int (*open) (struct inode *, struct file *);}
PART 2. 分析register_chrdev{注册字符设备驱动的API}源码:
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
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;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
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;
}
根据以上分析可知分步注册流程(入口函数中):
1. 分配空间【cdev_alloc(void)】
2. 初始化【cdev_init(字符设备驱动对象指针,操作方法结构体指针)】
3. 申请设备号【静态:register_chrdev_region(要申请的设备号,要申请的设备资源数量,驱动名)】【动态:alloc_chrdev_region(用于接受设备号的变量,要申请的设备资源数量,驱动名)】
4. 注册进内核【cdev_add(字符设备驱动对象指针,要申请的设备号,设备数量)】
接着进行目录信息提交【class_create】、设备文件信息提交即可【device_create】,注销流程倒推同理。
示例代码如下:
#include
#include
#include
#include
#include
#include // cdev相关op
#include // kfree
// cdev结构体指针
struct cdev *cdev = NULL;
// 主设备号/起始次设备号/次设备号数目[后续被用于设备结点数目<可另设>]
int major = 0;
int minor = 0;
int count = 3;
// 字符设备驱动名
#define CNAME "mycdev"
// 操作方法结构体
const struct file_operations fop = {
};
// class/device结构体
struct class *cls = NULL;
struct device *dev = NULL;
/*入口函数*/
static int __init demo_init(void)
{
// 返回值
int ret = -1;
// 循环值
int i = 0;
// 设备号
dev_t devno;
/*register_chrdev分布注册[1-4]*/
// 1. 分配空间[cdev_alloc]
cdev = cdev_alloc();
if(NULL == cdev)
{
ret = -EFAULT;
printk("分配空间failed...\n");
goto ERR1;
}
printk("分配空间success...\n");
// 2. 初始化[cdev_init][部分:除了THIS_MODULE,设备号,设备号数量]
cdev_init(cdev, &fop);
printk("初始化success...\n");
// 3. 申请设备号[region][静态:register_chrdev_region|动态:alloc_chrdev_region]
if(major > 0)
{
ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
if(ret < 0)
{
printk("申请设备号failed[静态]...\n");
goto ERR2;
}
printk("申请设备号success[静态]...\n");
}
else if(0 == major)
{
ret = alloc_chrdev_region(&devno, minor, count, CNAME);
if(ret < 0)
{
printk("申请设备号failed[动态]...\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
printk("申请设备号success[动态]...\n");
}
else
{
ret = -EINVAL;
printk("申请设备号failed[提供的major错误]...\n");
goto ERR1;
}
// 4. 注册进内核[cdev_add]
ret = cdev_add(cdev, MKDEV(major, minor), count);
if(ret < 0)
{
printk("注册进内核failed...\n");
goto ERR3;
}
printk("注册进内核success...\n");
// 5. 向上提交目录信息[class_create][/sys/class/xxx]
cls = class_create(THIS_MODULE, "mycdevs");
if(IS_ERR(cls))
{
ret = PTR_ERR(cls);
printk("向上提交目录信息failed...\n");
goto ERR4;
}
printk("向上提交目录信息success...\n");
// 6. 向上提交设备文件信息[device_create]
for(i = 0; i < count; i++)
{
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
if(IS_ERR(dev))
{
ret = PTR_ERR(dev);
printk("向上提交设备文件信息[%d]failed...\n", i);
goto ERR5;
}
printk("向上提交设备文件信息[%d]success...\n", i);
}
return 0;
ERR5:
// 5. [device_destroy|移除已经提交成功的]
for(--i; i >= 0; i--)
{
device_destroy(cls, MKDEV(major, i));
printk("ERR5:移除设备文件信息[%d]success...\n", i);
}
// 5. [class_destroy]
class_destroy(cls);
printk("ERR5:移除目录信息success...\n");
ERR4:
// 4. 从内核卸载[cdev_del]
cdev_del(cdev);
printk("ERR4:从内核卸载success...\n");
ERR3:
// 3. 释放设备号[unregister_chrdev_region]
unregister_chrdev_region(MKDEV(major, minor), count);
printk("ERR3:释放设备号success...\n");
ERR2:
// 2. 释放空间[kfree]
kfree(cdev);
printk("ERR2:释放空间success...\n");
ERR1:
// 1. 返回错误码
printk("ERR1:返回错误码success...\n");
return ret;
}
/*出口函数*/
static void __exit demo_exit(void)
{
// 循环值
int i = 0;
// 5. 移除设备文件信息[device_destroy]
for(i = count-1; i >= 0; i--)
{
device_destroy(cls, MKDEV(major, i));
printk("移除设备文件信息[%d]success...\n", i);
}
// 4. 移除目录信息[class_destroy]
class_destroy(cls);
printk("移除目录信息success...\n");
/*unregister_chrdev分布注销[3-1]*/
// 3. 从内核卸载[cdev_del]
cdev_del(cdev);
printk("从内核卸载success...\n");
// 2. 释放设备号[unregister_chrdev_region]
unregister_chrdev_region(MKDEV(major, minor), count);
printk("释放设备号success...\n");
// 1. 释放空间[kfree]
kfree(cdev);
printk("释放空间success...\n");
return ;
}
/*三要素*/
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
/* else:
* 1.传参 module_param | MODULE_PARM_DESC
* 2.导出符号表 EXPORT_SYMBOL_GPL | KBUILD_EXTRA_SYMBOLS
* 3.内核用户数据传输 copy_from_user | copy_to_user
* 4.物理虚拟内存映射 ioremap | iounmap
* 5.安全的读取写入寄存器 readl | writel
* x.IS_ERR | MKDEV | _IO _IOW _IOR _IORW | ...
*/