Linux device management is an important mechanism in the Linux system. It is used to achieve unified management and operation of devices, such as creation, deletion, search, modification, etc. Linux device management involves three core concepts: kobject, kset and kobj_type. A kobject is a general-purpose kernel object that can be used to represent any type of device. kset is a container object that can be used to organize and manage a group of related kobjects. kobj_type is a type object that can be used to define the attributes and operations of kobject.
Here we will discuss how the Linux kernel (taking the 4.8.5 kernel as an example) manages character devices, that is, when we obtain the device number, assign the cdev structure, and register the driver operation Method set, what exactly is told to the kernel when cdev_add() is performed at the end, and how does the kernel manage my cdev structure? This is what this article will discuss. . We know that the Linux kernel's management of devices is based on kobject, which can be seen from our cdev structure. So, next, you will see the functions for operating character devices implemented in "fs/char_dev.c" They are all based on the functions that operate on kobject in **"lib/kobject.c" and "drivers/base/map.c". Okay, now we start layer by layer from cdev_add()**.
//fs/char_dev.c 27 static struct kobj_map *cdev_map;
The implementation of the character device operating functions in the kernel is placed in **"fs/char_dev.c". When you open this file, the first thing you notice is this static global variable that is not common in the kernel cdev_map**(27), we know that in order to improve the cohesion of software, the Linux kernel tries to avoid using global variables as a way to transfer data between functions during design, and it is recommended to use formal parameter lists more, and this structure variable It is used everywhere in this file, so it should be some kind of information describing all character devices in the system. With this idea, we can find it in **"drivers/base/map.c" ##kobj_map** structure definition:
//drivers/base/map.c 19 struct kobj_map { 20 struct probe { 21 struct probe *next; 22 dev_t dev; 23 unsigned long range; 24 struct module *owner; 25 kobj_probe_t *get; 26 int (*lock)(dev_t, void *); 27 void *data; 28 } *probes[255]; 29 struct mutex *lock; 30 };
next(21) is obviously the probe The structures are connected in the form of a linked list. The member dev of the dev_t type is obviously the device number, and get(25) and lock(26) are two functions respectively. Interface, here comes the final point. void* is a snake oil type in C language. Here it is our cdev structure (as can be seen from the following analysis). Therefore, this cdev_map is a struct kobj_map type. Pointer, which contains an array of struct probe pointer type and size 255. Each element of the array points to a probe structure that encapsulates a device number and the corresponding device object (here is cdev). It can be seen that this cdev_map Encapsulates all cdev structures and corresponding device numbers in the system, up to 255 characters device .
cdev_map, we can explore cdev_add()
//fs/char_dev.c 456 int cdev_add(struct cdev *p, dev_t dev, unsigned count) 457 { 458 int error; 459 460 p->dev = dev; 461 p->count = count; 462 463 error = kobj_map(cdev_map, dev, count, NULL, 464 exact_match, exact_lock, p); 465 if (error) 466 return error; 467 468 kobject_get(p->kobj.parent); 469 470 return 0; 471 }
kobject_get()(468) does nothing:
//lib/kobject.c 591 struct kobject *kobject_get(struct kobject *kobj) 592 { 593 if (kobj) { ... 598 kref_get(&kobj->kref); 599 } 600 return kobj; 601 }
kobj_map()
//drivers/base/map.c 32 int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range, 33 struct module *module, kobj_probe_t *probe, 34 int (*lock)(dev_t, void *), void *data) 35 { 36 unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; 37 unsigned index = MAJOR(dev); 38 unsigned i; 39 struct probe *p; ... 44 p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL); ... 48 for (i = 0; i owner = module; 50 p->get = probe; 51 p->lock = lock; 52 p->dev = dev; 53 p->range = range; 54 p->data = data; 55 } 56 mutex_lock(domain->lock); 57 for (i = 0, p -= n; i probes[index % 255]; 59 while (*s && (*s)->range next; 61 p->next = *s; 62 *s = p; 63 } 64 mutex_unlock(domain->lock); 65 return 0; 66 }
encapsulates a probe structure and puts its address into the probes array and then encapsulates it into cdev_map, (48-55)j is based on the incoming device number The number of probes is to encapsulate the device number and cdev into n probe structures allocated by kmalloc_array in turn. (57-63) It is to traverse the probs array until an element with a value of NULL is found, and then store the probe address into probes. At this point, we have put our cdev into the kernel's data structure. Of course, a large number of attributes in cdev are filled in by the kernel for us.
将设备放入的内核,我们再来看看内核是怎么找到一个特定的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 }
通过本文,我们了解了Linux设备管理的原理和方法。它们可以用来实现对设备的统一管理和操作。我们应该根据实际需求选择合适的方法,并遵循一些基本原则,如使用正确的引用计数,使用正确的锁机制,使用正确的属性文件等。Linux设备管理是Linux系统中最基本的机制之一,它可以实现对设备的抽象和封装,也可以提升系统的可维护性和可扩展性。希望本文能够对你有所帮助和启发。
The above is the detailed content of Device management in Linux systems: starting with the dev_add function. For more information, please follow other related articles on the PHP Chinese website!