首页 > 代码库 > linux设备驱动之misc驱动框架源码分析(二)
linux设备驱动之misc驱动框架源码分析(二)
1、misc_open函数分析
该函数在driver/char/misc.c中,misc.c是驱动框架实现的,这里面的misc_Open函数是misc驱动框架为应用层提供的一个打开misc设备的一个接口。
1、首先我们要知道在misc.c中的misc_init函数中,将misc这种类设备注册成了字符设备驱动。代码如下
static int __init misc_init(void) { int err; #ifdef CONFIG_PROC_FS proc_create("misc", 0, NULL, &misc_proc_fops); #endif misc_class = class_create(THIS_MODULE, "misc"); err = PTR_ERR(misc_class); if (IS_ERR(misc_class)) goto fail_remove; err = -EIO; if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) //misc类的设备注册为字符设备驱动,因为使用的是register_chrdev函数。 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; }
上面代码中的register_chrdev函数参数中可以看出,misc_fops为包含了操作该驱动的方法结构体。结构体内容如下
static const struct file_operations misc_fops = { .owner = THIS_MODULE, .open = misc_open, };
因此可以知道,驱动框架中的misc_open函数为驱动框架为应用层提供的接口,接口函数是open函数。正常来说,驱动框架中是不会写驱动的操作方法的,应该是留给我们驱动开发的人写的。自己去写这个file_operations结构体中的方法将其填充然后注册。
2、misc_open函数的代码分析,代码与分析如下
static int misc_open(struct inode * inode, struct file * file) { int minor = iminor(inode); //从传进来的参数中得到该设备的次设备号,inode是flash中文件的节点,file是打开的那一份的设备文件的路径 struct miscdevice *c; int err = -ENODEV; const struct file_operations *old_fops, *new_fops = NULL; //定义两个file_opreation结构体,这个结构体就不用说了,放的是接口函数,驱动 //操作方法 mutex_lock(&misc_mtx); list_for_each_entry(c, &misc_list, list) { //遍历内核misc杂散类设备的链表,如果链表中存在这个设备的次设备号,那么就将内核链表中的 if (c->minor == minor) { //这个次设备号对应的设备的fops方法拿出来用,作为新的操作这个设备的方法。 new_fops = fops_get(c->fops); break; } } if (!new_fops) { //如果在内核misc链表中没有找到这个次设备号对应的fops,那么就request_module一次,在去找一次,如果还找不到,则 mutex_unlock(&misc_mtx); //跳到fail位置。在注册这个设备的时候,我们是会注册这个设备的fops //的。 //应用层用open操作这个设备的时候,对应到驱动层,就是先找到次设备 //号,因为注册这个驱动的时候,fops已经提供了,同时,这个设备也注册了,所以去内核链表中找这个次设备号, //找到次设备号后就去找fops方法,找到后就得到这个fops结构体,正常来说是一定能找到的。因为驱动注册是你提供了 //fops,设备创建注册时,也注册了对应的minor次设备号。 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; //将打开的文件的fop作为老的fop file->f_op = new_fops; //将从misc内核链表中得到的fops作为新的ops给打开的。 if (file->f_op->open) { //如果得到了open的函数方法实例 file->private_data = c; err=file->f_op->open(inode,file); //然后调用open函数,这是真正的执行的open。上层用open操作misc设备时,最终执行的就是这个函 //数,这个被绑定到miscdevice结构体中的fops下的open,是写驱动的人提供的,在x210-buzzer.c //提供的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; }
因此应用层使用open操作misc设备的时候,会映射到驱动中的/driver/char/misc.c文件中的misc_open函数执行file->f_op->open函数,这个函数映射的是
/drvier/char/buzzer/x210-buzzer.c中的
static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = x210_pwm_open, .release = x210_pwm_close, .ioctl = x210_pwm_ioctl, };
里的open绑定的x210_pwm_open,x210_pwm_open的函数体是
static int x210_pwm_open(struct inode *inode, struct file *file) { if (!down_trylock(&lock)) return 0; else return -EBUSY; }
2、misc在proc中的展现 proc不是很重要,因为proc文件系统现在用的越来越少,因为太乱,这里主要是为了让你知道这个代码是做什么的
cat /proc/misc文件时,会看到所有注册到misc中的设备的次设备号和名字,这个文件中的内容是通过遍历内核misc_list链表来显示出来的。
这个/proc/misc文件的创建是在/drvier/char/misc.c文件中的misc_init函数中的proc_create时创建的,代码如下
static int __init misc_init(void) { int err; #ifdef CONFIG_PROC_FS proc_create("misc", 0, NULL, &misc_proc_fops); //在/proc目录下创建出misc文件。 //misc_proc_fops结构体就是操作这个/proc/misc文件时的方法。 #endif
misc_proc_fops结构体内容为
static const struct file_operations misc_proc_fops = { .owner = THIS_MODULE, .open = misc_seq_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, };
cat /proc/misc文件时对应的方法应该是
static const struct seq_operations misc_seq_ops = { .start = misc_seq_start, .next = misc_seq_next, .stop = misc_seq_stop, .show = misc_seq_show, };
中的misc_seq_show方法,内容如下
static int misc_seq_show(struct seq_file *seq, void *v) { const struct miscdevice *p = list_entry(v, struct miscdevice, list); seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : ""); return 0; }
3、内核互斥锁
(1)何为互斥锁?
和应用中的互斥锁其实差不多。别人访问时我不能访问,访问时别人访问,需要上锁和解锁,不多说,
(2)内核中定义互斥锁:DEFINE_MUTEX
(3)上锁:mutex_lock和解锁:mutex_unlock
(4)内核防止竞争状态的手段:原子访问、自旋锁、互斥锁、信号量
(5)原子访问主要是用来计数(访问过程不能被打断)、自旋锁后面说、互斥锁和信号量很相似(其实就是计数值为1的信号量),互斥锁的出现比信号量晚,实现上比信号量优秀,尽量使用互斥锁。
原子操作:
比如你定义一个变量count,然后count++,实际上这个count++是可以被打断的,因为count++转换成可能是三句代码,当执行了两句突然被打断时(可能是时间片时间到了),被打断时又改了这个count的值,那么这个时候这个count++的值肯定是不对的了,所以为了防止这种现象就有了原子操作,原子操作的计数不可以被打断。
自旋锁:自旋锁跟原子操作是有本质区别的,在现在的驱动中用的越来越多。是在多核CPU的年代发明的,专门应对这种多处理器的CPU的,所以自旋锁会用的越来越多,后面会详细说。
信号量:和互斥锁基本相似,信号量用来计数的,比如说一个设备只能被打开7次,每个人打开这个设备就会记一次数,当第八打开时,数为大于7了,就不能打开这个设备了。这就是用信号量来做的一个打开计数的手段。
互斥锁:互斥锁就是一种特殊的信号,他只能被打开一次。一个人打开了,数值可能就是1了,另一个人就不能打开了,当这个人解锁的时候,这个值就为0了,另一个人就可以打开或访问了。所以互斥锁和信号量的区别就是次数的区别。能用互斥锁的时候用互斥锁不要信号量。照猫画虎就行。如果驱动是你自己写的,你自己要去选择用互斥锁和信号量的时候,这个时候你已经不会纠结了,因为你有了自己不去照别人的驱动写驱动功力的时候,你就已经知道什么时候用互斥锁什么时候用信号量了。
misc.c这个内核人写的,写驱动框架人写的代码已经分析的差不多了,这部分代码只要理解就行,并不是驱动工程师需要去写的,我们要写的是驱动,而不是驱动框架代码。
后面分析的蜂鸣器的驱动代码,这部分代码是驱动工程师需要去写的。
本文出自 “whylinux” 博客,谢绝转载!
linux设备驱动之misc驱动框架源码分析(二)