Linux設備管理是Linux系統中一種重要的機制,它用來實現對設備的統一管理和操作,如創建,刪除,查找,修改等。 Linux裝置管理涉及三個核心的概念:kobject,kset和kobj_type。 kobject是一種通用的核心對象,它可以用來表示任何類型的設備。 kset是一種容器對象,它可以用來組織和管理一組相關的kobject。 kobj_type是一種類型對象,它可以用來定義kobject的屬性和操作。
這裡我們來探討一下Linux核心(以4.8.5核心為例)是怎麼管理字元裝置的,即當我們取得了裝置號,分配了cdev結構,註冊了驅動的操作方法集,最後進行cdev_add()的時候,究竟是將哪些內容告訴了內核,內核又是怎麼管理我的cdev結構的,這就是本文要討論的內容。我們知道,Linux核心對裝置的管理是基於kobject,這點從我們的cdev結構中就可以看出,所以,接下來,你將看到」fs/char_dev.c」中實現的操作字元裝置的函數都是基於**”lib/kobject.c”以及“drivers/base/map.c”中對kobject操作的函數。好,現在我們從cdev_add()**開始一層層的扒。
//fs/char_dev.c 27 static struct kobj_map *cdev_map;
核心中關於字元裝置的運算函數的實作放在**”fs/char_dev.c”中,開啟這個文件,首先註意到就是這個在核心中不常見的靜態全域變數 cdev_map**(27),我們知道,為了提高軟體的內聚性,Linux核心在設計的時候盡量避免使用全域變數作為函數間資料傳遞的方式,而建議多使用形參列表,而這個結構變數在這個檔案中到處被使用,所以它應該是描述了系統中所有字元設備的某種訊息,帶著這樣的想法,我們可以在**”drivers/base/map.c”中找到kobj_map**結構的定義:
//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 };
從中可以看出,kobj_map的核心就是一個struct probe指標型別、大小為255的數組,而在這個probe結構中,第一個成員next(21)顯然是將這些probe結構透過鍊錶的形式連接起來,dev_t類型的成員dev顯然是裝置號,get(25)和lock(26)分別是兩個函數接口,最後的重點來了,void*作為C語言中的萬金油類型,在這裡就是我們cdev結構(透過後面的分析可以看出),所以,這個cdev_map是一個struct kobj_map類型的指針,其中包含一個struct probe指針類型、大小為255的數組,數組的每個元素指向的一個probe結構封裝了一個設備號和相應的設備對象(這裡就是cdev),可見,這個cdev_map封裝了系統中的所有的cdev結構和對應的設備號,最多為255個字元設備。
#了解了cdev_map的功能,我們就可以一探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 }
函數很短,(460-461)就是將我們之前取得裝置號碼和裝置號碼長度填入cdev結構中,kobject_get()(468)也沒做什麼事:
//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 }
這個函數的設計也很單純,就是封裝好一個probe結構並將它的位址放入probes陣列進而封裝進cdev_map,(48-55)j就是根據傳入的裝置號的數,將裝置號碼和cdev依序封裝到kmalloc_array分配的n個probe結構中,(57-63)就是遍歷probs數組,直到找到一個值為NULL的元素,再將probe的位址存入probes。至此,我們就將我們的cdev放入的內核的資料結構,當然,cdev中大量屬性都是由內核幫我們填充的。
将设备放入的内核,我们再来看看内核是怎么找到一个特定的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系统中最基本的机制之一,它可以实现对设备的抽象和封装,也可以提升系统的可维护性和可扩展性。希望本文能够对你有所帮助和启发。
以上是Linux系統中的裝置管理:從dev_add函數說起的詳細內容。更多資訊請關注PHP中文網其他相關文章!