内核的misc驱动框架详解:内核源码分析、蜂鸣器驱动分析

1、什么是misc类设备

(1)misc类设备就是杂项类设备,那些功能不太复杂,不太好归类到已经创建好的类中,这些设备都可以归类到misc类设备。misc类设备就好像一个收纳盒子,不知道怎么归类的设备就归类到misc类设备里;
(2)misc类设备本质上是字符类设备,而且归类是建议不是强制,你想把设备归到哪一类设备都是可以的,只有按照相应驱动框架的注册方式去注册驱动,但是良好的归类习惯,能让人更容易理解你写的代码;

2、misc驱动框架的加载

static const struct file_operations misc_fops = {
	.owner		= THIS_MODULE,
	.open		= misc_open,
};

static int __init misc_init(void)
{
	int err;

#ifdef CONFIG_PROC_FS
	proc_create("misc", 0, NULL, &misc_proc_fops);
#endif

	//创建misc类
	misc_class = class_create(THIS_MODULE, "misc");
	err = PTR_ERR(misc_class);
	if (IS_ERR(misc_class))
		goto fail_remove;

	err = -EIO;

	//注册misc字符设备,主设备号是10,次设备号范围[0,255]
	if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
		goto fail_printk;
	misc_class->devnode = misc_devnode;
	return 0;

fail_printk:
	printk("unable to get major %d for misc devices\n", MISC_MAJOR);
	class_destroy(misc_class);
fail_remove:
	remove_proc_entry("misc", NULL);
	return err;
}

subsys_initcall(misc_init);

(1)CONFIG_PROC_FS宏:内核是否开启了proc文件系统,如果定义了该宏,会看到"/proc/misc"文件,里面记录了注册的misc类设备,可以用cat命令查看;
(2)创建了misc类,在sysfs目录下可以看到"/sys/class/misc"目录,将来misc类设备都放到该目录下;
(3)注册了名字为misc、主设备号是10的字符设备,设备节点的操作方法是misc_fops变量定义的,所以misc类设备的主设备号都是10;
(4)subsys_initcall(misc_init):将misc_init函数放到".initcall4.init"段,在内核启动过程中会自动调用,宏的分析参考博客:《内核加载驱动机制详解(module_init & module_exit)》

3、misc类设备驱动的注册

3.1、struct miscdevice结构体

struct miscdevice  {
	int minor;	//次设备号
	const char *name;	//misc设备的名字
	const struct file_operations *fops;	//设备节点的操作方法
	struct list_head list;	//用来挂接的链表节点
	struct device *parent;	//设备的父节点
	struct device *this_device;
	const char *nodename;
	mode_t mode;
};
构建表格
minor次设备号
namemisc设备的名字,在./sys/class/misc目录中可以看到
fops设备节点的操作方法
list用来挂接的链表节点

3.2、misc驱动框架注册函数

int misc_register(struct miscdevice * misc)
{
	struct miscdevice *c;
	dev_t dev;
	int err = 0;

	INIT_LIST_HEAD(&misc->list);

	mutex_lock(&misc_mtx);

	//遍历已经注册的misc类设备,查看次设备号是否已经被注册
	list_for_each_entry(c, &misc_list, list) {
		if (c->minor == misc->minor) {
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		}
	}

	//如果此设备是MISC_DYNAMIC_MINOR,表示自动分配次设备号
	if (misc->minor == MISC_DYNAMIC_MINOR) {
		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
		if (i >= DYNAMIC_MINORS) {
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		}
		misc->minor = DYNAMIC_MINORS - i - 1;
		set_bit(i, misc_minors);
	}

	//构建主次设备号
	dev = MKDEV(MISC_MAJOR, misc->minor);

	//在misc类下面创建设备
	misc->this_device = device_create(misc_class, misc->parent, dev,
					  misc, "%s", misc->name);
	if (IS_ERR(misc->this_device)) {
		int i = DYNAMIC_MINORS - misc->minor - 1;
		if (i < DYNAMIC_MINORS && i >= 0)
			clear_bit(i, misc_minors);
		err = PTR_ERR(misc->this_device);
		goto out;
	}

	//添加到内核管理misc类设备的链表中
	list_add(&misc->list, &misc_list);
 out:
	mutex_unlock(&misc_mtx);
	return err;
}

(1)将misc驱动中构建好的struct miscdevice结构体通过misc_register()函数注册到内核的misc驱动框架中;
(2)遍历已经注册的misc类设备,查看次设备号是否已经被注册;
(3)如果此设备是255,misc驱动框架将自动分配次设备号;
(4)构建主次设备号;
(5)在sysfs中创建misc类的设备;

3.3、misc设备的次设备号分配问题

#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);

int misc_register(struct miscdevice * misc)
{
	······
	//如果此设备是MISC_DYNAMIC_MINOR,表示自动分配次设备号
	if (misc->minor == MISC_DYNAMIC_MINOR) {
		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); //返回位图中bit值第一个为0的下标
		if (i >= DYNAMIC_MINORS) {
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		}
		
		//换算得到次设备号
		misc->minor = DYNAMIC_MINORS - i - 1;
		
		//根据申请到的次设备号,位图中将对应的bit值置1
		set_bit(i, misc_minors);
	}
	
	······
}

(1)在注册misc驱动时,将次设备号填255,就代表由misc驱动框架自动分配次设备号;
(2)自动分配的次设备号范围是[0,63],并且是按从大到小开始分配的,具体分析上面的代码;
(3)对位图不熟悉的参考博客:https://blog.csdn.net/weixin_42031299/article/details/124853167;

3.4、内核如何记录注册的misc类驱动

int misc_register(struct miscdevice * misc)
{
	······
	//遍历已经注册的misc类设备,查看次设备号是否已经被注册
	list_for_each_entry(c, &misc_list, list) {
		if (c->minor == misc->minor) {
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		}
	}
	······
	//将misc类设备的struct miscdevice结构体添加到misc_list链表中
	list_add(&misc->list, &misc_list);
	······
}


static int misc_open(struct inode * inode, struct file * file)
{
	······
	//根据次设备号在注册的misc驱动程序中进行匹配
	list_for_each_entry(c, &misc_list, list) {
		if (c->minor == minor) {
			new_fops = fops_get(c->fops);		//获取匹配到的misc驱动的fops
			break;
		}
	}
	······
}

(1)在内核中用链表来管理misc类设备,在注册时添加到链表,在open操作misc设备时进行遍历链表;
(2)在misc设备的管理链表中,通过次设备号来唯一的标记设备;

4、misc设备的操作分析

4.1、共用主设备号的影响

(1)在加载misc驱动框架的时候,调用register_chrdev()函数注册了主设备号为10、名字是misc的字符驱动,我们后续通过misc_register()函数
注册的misc驱动都是主设备号相同,次设备号不同;
(2)主设备号相同的设备,都是共用struct file_operations结构体的,也就是misc_fops变量,这里面只实现了open方法,所以misc类的设备节点的open方法都是同一个函数;
(3)不同的misc设备会有不同的read、write等方法,明显不能共用struct file_operations结构体,并且misc_fops变量还只实现了open方法;
(4)所以misc_fops变量的open方法,肯定会想办法将设备节点的fops操作方法,替换成misc驱动的fops;

4.2、如何通过次设备号区分各个misc设备

static int misc_open(struct inode * inode, struct file * file)
{
	//解析处次设备号
	int minor = iminor(inode);
	······
	//根据次设备号在注册的misc驱动程序中进行匹配
	list_for_each_entry(c, &misc_list, list) {
		if (c->minor == minor) {
			new_fops = fops_get(c->fops);		//获取匹配到的misc驱动的fops
			break;
		}
	}
	······
}

(1)在调用misc_register()注册misc驱动时,函数内部调用了device_create()函数创建了"/dev/xxx"设备节点,设备节点的inode结构体里就保存了主次设备号,在open打开设备节点时,内核是调用的misc_open()函数,会传入设备节点的struct inode结构体;
(2)根据解析得到的此设备号,在记录misc设备的链表中找到之前注册的struct miscdevice结构体;
(3)主次设备号更详细的内容参见博客:https://blog.csdn.net/weixin_42031299/article/details/124557522;

4.3、open操作

static int misc_open(struct inode * inode, struct file * file)
{
	//解析出次设备号
	int minor = iminor(inode);
	
	struct miscdevice *c;
	int err = -ENODEV;
	const struct file_operations *old_fops, *new_fops = NULL;

	mutex_lock(&misc_mtx);

	//根据次设备号在注册的misc驱动程序中进行匹配,得到注册的驱动的fops
	list_for_each_entry(c, &misc_list, list) {
		if (c->minor == minor) {
			new_fops = fops_get(c->fops);		//获取匹配到的misc驱动的fops
			break;
		}
	}

	//上一步中没有查找到,尝试手动加载对应主次设备号的驱动,然后再次去匹配
	if (!new_fops) {
		mutex_unlock(&misc_mtx);
		request_module("char-major-%d-%d", MISC_MAJOR, minor);	//尝试手动加载对应主次设备号的驱动
		mutex_lock(&misc_mtx);

		list_for_each_entry(c, &misc_list, list) {
			if (c->minor == minor) {
				new_fops = fops_get(c->fops);
				break;
			}
		}
		if (!new_fops)
			goto fail;
	}

	err = 0;
	old_fops = file->f_op;
	
	//将设备节点文件的fops替换成对应misc设备的fops
	file->f_op = new_fops;	
	if (file->f_op->open) {
	
		//将misc驱动框架的struct miscdevice结构体保存在struct file结构体中,后续会用
		file->private_data = c;	
		
		//调用注册的misc驱动的fops的open方法
		err=file->f_op->open(inode,file);	//调用新的fops的open方法
		if (err) {
			fops_put(file->f_op);
			file->f_op = fops_get(old_fops);
		}
	}
	
	fops_put(old_fops);
fail:
	mutex_unlock(&misc_mtx);
	return err;
}

(1)解析出次设备号,然后根据次设备号去找到注册驱动时的struct miscdevice结构体;
(2)将设备节点文件的struct file结构体的f_op替换成misc驱动struct miscdevice结构体的fops;
(3)调用misc驱动真正的open函数;

4.4、read、write等操作

经过open函数的替换,后续的read、write等操作方法,都是调用我们在注册misc驱动时,填充的struct miscdevice结构体里的fops,
这个是和具体硬件相关的,后面以蜂鸣器的驱动具体分析;

5、X210开发板的蜂鸣器驱动分析

后续补充

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正在起飞的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值