Home >System Tutorial >LINUX >Device management in Linux systems: starting with the dev_add function

Device management in Linux systems: starting with the dev_add function

PHPz
PHPzforward
2024-02-11 13:57:261063browse

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()**.

cdev_map object

//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 };

It can be seen from this that the core of kobj_map is an array of struct probe pointer type and size 255. In this probe structure, the first member

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 .
Device management in Linux systems: starting with the dev_add function

cdev_add

Understanding the functions of

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 }

The function is very short, (460-461) is to fill the cdev structure with the device number and device number length we obtained before,

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 }

So, the core work is obviously handed over to

kobj_map()

kobj_map()

This function occupies an important position in the kernel's device management. Here we only analyze its function from the perspective of character devices and implement it first

//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 }

The design of this function is also very simple, that is,

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.

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 }

通过本文,我们了解了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!

Statement:
This article is reproduced at:lxlinux.net. If there is any infringement, please contact admin@php.cn delete