日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

cdev linux_Linux设备管理(二)_从cdev_add说起

發(fā)布時間:2024/9/3 linux 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 cdev linux_Linux设备管理(二)_从cdev_add说起 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

我在Linux字符設(shè)備驅(qū)動框架一文中已經(jīng)簡單的介紹了字符設(shè)備驅(qū)動的基本的編程框架,這里我們來探討一下Linux內(nèi)核(以4.8.5內(nèi)核為例)是怎么管理字符設(shè)備的,即當(dāng)我們獲得了設(shè)備號,分配了cdev結(jié)構(gòu),注冊了驅(qū)動的操作方法集,最后進(jìn)行cdev_add()的時候,究竟是將哪些內(nèi)容告訴了內(nèi)核,內(nèi)核又是怎么管理我的cdev結(jié)構(gòu)的,這就是本文要討論的內(nèi)容。我們知道,Linux內(nèi)核對設(shè)備的管理是基于kobject的(參見Linux設(shè)備管理(一)_kobject_kset_kobj_type),這點從我們的cdev結(jié)構(gòu)中就可以看出,所以,接下來,你將看到"fs/char_dev.c"中實現(xiàn)的操作字符設(shè)備的函數(shù)都是基于"lib/kobject.c"以及"drivers/base/map.c"中對kobject操作的函數(shù)。好,現(xiàn)在我們從cdev_add()開始一層層的扒。

cdev_map對象

//fs/char_dev.c

27 static struct kobj_map *cdev_map;

內(nèi)核中關(guān)于字符設(shè)備的操作函數(shù)的實現(xiàn)放在"fs/char_dev.c"中,打開這個文件,首先注意到就是這個在內(nèi)核中不常見的靜態(tài)全局變量cdev_map(27),我們知道,為了提高軟件的內(nèi)聚性,Linux內(nèi)核在設(shè)計的時候盡量避免使用全局變量作為函數(shù)間數(shù)據(jù)傳遞的方式,而建議多使用形參列表,而這個結(jié)構(gòu)體變量在這個文件中到處被使用,所以它應(yīng)該是描述了系統(tǒng)中所有字符設(shè)備的某種信息,帶著這樣的想法,我們可以在"drivers/base/map.c"中找到kobj_map結(jié)構(gòu)的定義:

//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的數(shù)組,而在這個probe結(jié)構(gòu)中,第一個成員next(21)顯然是將這些probe結(jié)構(gòu)通過鏈表的形式連接起來,dev_t類型的成員dev顯然是設(shè)備號,get(25)和lock(26)分別是兩個函數(shù)接口,最后的重點來了,void*作為C語言中的萬金油類型,在這里就是我們cdev結(jié)構(gòu)(通過后面的分析可以看出),所以,這個cdev_map是一個struct kobj_map類型的指針,其中包含著一個struct probe指針類型、大小為255的數(shù)組,數(shù)組的每個元素指向的一個probe結(jié)構(gòu)封裝了一個設(shè)備號和相應(yīng)的設(shè)備對象(這里就是cdev),可見,這個cdev_map封裝了系統(tǒng)中的所有的cdev結(jié)構(gòu)和對應(yīng)的設(shè)備號,最多為255個字符設(shè)備。

cdev_add

了解了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 }

函數(shù)很短,(460-461)就是將我們之前獲得設(shè)備號和設(shè)備號長度填充到cdev結(jié)構(gòu)中,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()

kobj_map()

這個函數(shù)在內(nèi)核的設(shè)備管理中占有重要的地位,這里我們只從字符設(shè)備的角度分析它的功能,先上實現(xiàn)

//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 < n; i++, p++) {

49 p->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 < n; i++, p++, index++) {

58 struct probe **s = &domain->probes[index % 255];

59 while (*s && (*s)->range < range)

60 s = &(*s)->next;

61 p->next = *s;

62 *s = p;

63 }

64 mutex_unlock(domain->lock);

65 return 0;

66 }

這個函數(shù)的設(shè)計也很單純,就是封裝好一個probe結(jié)構(gòu)并將它的地址放入probes數(shù)組進(jìn)而封裝進(jìn)cdev_map,(48-55)j就是根據(jù)傳入的設(shè)備號的個數(shù),將設(shè)備號和cdev依次封裝到kmalloc_array分配的n個probe結(jié)構(gòu)中,(57-63)就是遍歷probs數(shù)組,直到找到一個值為NULL的元素,再將probe的地址存入probes。至此,我們就將我們的cdev放入的內(nèi)核的數(shù)據(jù)結(jié)構(gòu),當(dāng)然,cdev中大量屬性都是由內(nèi)核幫我們填充的。

chrdev_open()

將設(shè)備放入的內(nèi)核,我們再來看看內(nèi)核是怎么找到一個特定的cdev的,對一個字符設(shè)備的訪問流程大概是:文件路徑=>inode=>chrdev_open=>cdev->fops->my_chr_open。所以只要通過VFS找到了inode,就可以找到chrdev_open(),這里我們就來關(guān)注一個chrdev_open()是怎么從內(nèi)核的數(shù)據(jù)結(jié)構(gòu)中找到我們的cdev并回調(diào)里滿的my_chr_open()的。首先,chrdev_open()嘗試將inode->i_cdev(一個cdev結(jié)構(gòu)指針)保存在局部變量p中(359),如果p為空,即inode->i_cdev為空(360),我們就根據(jù)inode->i_rdev(設(shè)備號)通過kobj_lookup搜索cdev_map,并返回與之對應(yīng)kobj(364),由于kobject是cdev的父類,我們根據(jù)container_of很容易找到相應(yīng)的cdev結(jié)構(gòu)并將其保存在inode->i_cdev中(367),找到了cdev,我們就可以將inode->devices掛接到inode->i_cdev的管理鏈表中,這樣下次就不用重新搜索,直接cdev_get()即可(378)。找到了我們的cdev結(jié)構(gòu),我們就可以將其中的操作方法集inode->i_cdev->ops傳遞給filp->f_ops(386-390),這樣,我們就可以回調(diào)我們的設(shè)備打開函數(shù)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 }

總結(jié)

以上是生活随笔為你收集整理的cdev linux_Linux设备管理(二)_从cdev_add说起的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。