这个函数的设计也很单纯,就是封装好一个probe结构并将它的地址放入probes数组进而封装进cdev_map,(48-55)j就是根据传入的设备号的个数,将设备号和cdev依次封装到kmalloc_array分配的n个probe结构中,(57-63)就是遍历probs数组,直到找到一个值为NULL的元素,再将probe的地址存入probes。至此,我们就将我们的cdev放入的内核的数据结构,当然,cdev中大量属性都是由内核帮我们填充的。
chrdev_open()将设备放入的内核,我们再来看看内核是怎么找到一个特定的cdev的,对一个字符设备的访问流程大概是:文件路径=>inode=>chrdev_open=>cdev->fops->my_chr_open。所以只要通过VFS找到了inode,就可以找到chrdev_open(),这里我们就来关注一个chrdev_open()是怎么从内核的数据结构中找到我们的cdev并回调里满的my_chr_open()的。首先,chrdev_open()尝试将inode->i_cdev(一个cdev结构指针)保存在局部变量p中(359),如果p为空,即inode->i_cdev为空(360),我们就根据inode->i_rdev(设备号)通过kobj_lookup搜索cdev_map,并返回与之对应kobj(364),由于kobject是cdev的父类,我们根据container_of很容易找到相应的cdev结构并将其保存在inode->i_cdev中(367),找到了cdev,我们就可以将inode->devices挂接到inode->i_cdev的管理链表中,这样下次就不用重新搜索,直接cdev_get()即可(378)。找到了我们的cdev结构,我们就可以将其中的操作方法集inode->i_cdev->ops传递给filp->f_ops(386-390),这样,我们就可以回调我们的设备打开函数my_chr_open()(392);
//fs/char_dev.c 326 static struct kobject *cdev_get(struct cdev *p) 327 { 328 struct module *owner = p->owner; 329 struct kobject *kobj; 330 331 if (owner && !try_module_get(owner)) 332 return NULL; 333 kobj = kobject_get(&p->kobj); ... 336 return kobj; 337 } 351 static int chrdev_open(struct inode *inode, struct file *filp) 352 { 353 const struct file_operations *fops; 354 struct cdev *p; 355 struct cdev *new = NULL; 356 int ret = 0; ... 359 p = inode->i_cdev; 360 if (!p) { 361 struct kobject *kobj; 362 int idx; ... 364 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); ... 367 new = container_of(kobj, struct cdev, kobj); 369 /* Check i_cdev again in case somebody beat us to it while 370 we dropped the lock. */ 371 p = inode->i_cdev; 372 if (!p) { 373 inode->i_cdev = p = new; 374 list_add(&inode->i_devices, &p->list); 375 new = NULL; 376 } else if (!cdev_get(p)) 377 ret = -ENXIO; 378 } else if (!cdev_get(p)) 379 ret = -ENXIO; ... 386 fops = fops_get(p->ops); ... 390 replace_fops(filp, fops); 391 if (filp->f_op->open) { 392 ret = filp->f_op->open(inode, filp); ... 395 } 396 397 return 0; 398 399 out_cdev_put: 400 cdev_put(p); 401 return ret; 402 }