<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Linux字符設(shè)備驅(qū)動內(nèi)幕

          共 6458字,需瀏覽 13分鐘

           ·

          2020-10-26 00:46


          哈嘍,我是老吳,繼續(xù)記錄我的學(xué)習(xí)心得。

          一、保持專注的幾個技巧

          • 將最重要的事放在早上做。

          • 待在無干擾環(huán)境下,比如圖書館。

          • 意識到剛坐下開始投入工作前,有點負面小情緒是特別正常的現(xiàn)象。

          • 讓“開心一刻”成為計劃的一部分。

          • 擁有合情合理的日計劃和周計劃。


          二、Linux 字符設(shè)備驅(qū)動內(nèi)幕 (1)

          正文目錄:

          1. 什么是字符設(shè)備驅(qū)動?
          2.?快速體驗字符設(shè)備驅(qū)動和應(yīng)用程序?(超簡單的?demo)
          3.?字符設(shè)備在內(nèi)核里的抽象
          ????3.1?字符設(shè)備核心代碼概覽
          ????3.2?對字符設(shè)備進行抽象:?struct?cdev
          ??? 3.3 對字符設(shè)備的操作進行抽象:struct file_operations
          4.?更多值得學(xué)習(xí)的知識點
          5.?相關(guān)參考

          寫作目的:

          • 探索 Linux 字符設(shè)備驅(qū)動。

          測試環(huán)境:

          • Ubuntu 16.04
          • Gcc 5.4.0

          1. 什么是字符設(shè)備驅(qū)動?

          • 現(xiàn)實世界中存在著成千上萬的硬件設(shè)備,這些設(shè)備在硬件特性和使用方式上都各不相同。Linux 系統(tǒng)的大牛們從這些形形色色的設(shè)備中提取出共性,將它們抽象為三大類:字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備

          • 基于代碼質(zhì)量和復(fù)用性的考慮,Linux 內(nèi)核針對每一類硬件設(shè)備都提供了對應(yīng)的驅(qū)動模型框架,一般包括基本的內(nèi)核設(shè)施和文件系統(tǒng)接口。開發(fā)人員在寫某類設(shè)備驅(qū)動程序時,能一套完整的驅(qū)動模型框架可以使用,從而將精力放在硬件設(shè)備本身的控制上。

          • 簡單的 Linux 設(shè)備驅(qū)動程序結(jié)構(gòu)圖:


          • 詳細一點的 Linux 設(shè)備驅(qū)動程序結(jié)構(gòu)圖:


          2. 快速體驗字符設(shè)備驅(qū)動和應(yīng)用程序 (超簡單的 demo)

          1) 字符設(shè)備驅(qū)動 (chrdev_drv.c):

          字符設(shè)備的打開和讀函數(shù):

          static?struct?cdev?chr_dev;?//?字符設(shè)備抽象
          static?dev_t?ndev;??????????//?設(shè)備號
          static?int?chr_open(struct?inode?*nd,?struct?file?*filp)
          {
          ????printk("chr_open,?major=%d,?minor=%d\n",?MAJOR(nd->i_rdev),?MINOR(nd->i_rdev));
          ????return?0;
          }

          static?ssize_t?chr_read(struct?file?*filp,?char?__user?*u,?size_t?sz,?loff_t?*off)
          {
          ????printk("In?chr_read()\n");
          ????return?0;
          }

          static?int?chr_release(struct?inode?*nd,?struct?file?*filp)
          {
          ????printk("In?chr_release()\n");
          ????return?0;
          }

          文件操作函數(shù)集:

          struct?file_operations?chr_ops?=
          {

          ????.owner?=?THIS_MODULE,
          ????.open?=?chr_open,
          ????.read?=?chr_read,
          ????.release?=?chr_release,
          };

          模塊加載和卸載:

          static?int?demo_init(void)
          {
          ????int?ret;
          ????cdev_init(&chr_dev,?&chr_ops);
          ????ret?=?alloc_chrdev_region(&ndev,?0,?1,?"chr_dev");
          ????if(ret?0)
          ????????return?ret;

          ????printk("demo_init():major=%d,?minor=%d\n",?MAJOR(ndev),?MINOR(ndev));

          ????ret?=?cdev_add(&chr_dev,?ndev,?1);
          ????if(ret?0)
          ????????return?ret;

          ????return?0;
          }
          static?void?demo_exit(void)
          {
          ????printk("demo_exit...\n");

          ????cdev_del(&chr_dev);
          ????unregister_chrdev_region(ndev,?1);
          }

          2) 訪問字符設(shè)備的應(yīng)用程序:

          int?main()
          {
          ????int?ret;
          ????char?buf[32];
          ????int?fd?=?open("/dev/chr_dev",?O_RDONLY|O_NDELAY);
          ????if(fd?0)
          ????{
          ????????printf("open?file?%s?failed!\n",?CHR_DEV_NAME);
          ????????return?-1;
          ????}
          ????read(fd,?buf,?32);
          ????close(fd);

          ????return?0;
          }

          3) 不用完全理解代碼的含義,直接看運行效果:

          #?編譯驅(qū)動程序
          $?make?KERNELDIR=XXX/linux?ARCH=arm?CROSS_COMPILE=arm-linux-

          #?編譯應(yīng)用程序
          $?arm-linux-gcc?chrdev_app.c?-o?chrdev_app

          #?加載驅(qū)動模塊
          $?insmod?chrdev_drv.ko
          demo_init():major=242,?minor=0

          #?手動創(chuàng)建字符設(shè)備文件節(jié)點
          $?mknod?/dev/chr_dev?c?242?0

          #?運行應(yīng)用程序
          $?./chrdev_app
          chr_open,?major=242,?minor=0
          In?chr_read()
          In?chr_release()

          從上面測運行結(jié)果可知,應(yīng)用程序調(diào)用 chrdev_app.c / open() 會導(dǎo)致驅(qū)動程序 chrdev_drv.c / struct file_operations chr_ops->open() 被調(diào)用,read 操作也是類似。

          內(nèi)核是如何實現(xiàn)上述功能的?

          帶著這個困惑來了解字符設(shè)備驅(qū)動的框架,才不會迷失在內(nèi)核里各種復(fù)雜的代碼細節(jié)里。

          3 字符設(shè)備在內(nèi)核里的抽象

          3.1 字符設(shè)備核心代碼概覽

          在深入閱讀各種代碼之前,先整體地概覽一遍將會涉及到的程序文件,找出核心主干,能有效地避免陷入繁雜的代碼細節(jié)中。

          1) 分解 C source 文件,fs/char_dev.c (Linux-4.14):

          作用:
          char_dev.c 是字符設(shè)備驅(qū)動框架的核心實現(xiàn)文件,它位于 fs 目錄中,說明了字符設(shè)備驅(qū)動和文件系統(tǒng)是緊密聯(lián)系在一起的。

          內(nèi)容(以重要性排序):
          1> public 函數(shù):

          //?1.?字符設(shè)備子系統(tǒng)初始化
          void?__init?chrdev_init(void)?

          //?2.?struct?cdev?管理相關(guān)
          void?chrdev_show(struct?seq_file?*f,?off_t?offset)?
          void?cdev_put(struct?cdev?*p)?
          void?cd_forget(struct?inode?*inode)?
          int?cdev_add(struct?cdev?*p,?dev_t?dev,?unsigned?count)?
          void?cdev_set_parent(struct?cdev?*p,?struct?kobject?*kobj)?
          int?cdev_device_add(struct?cdev?*cdev,?struct?device?*dev)?
          void?cdev_device_del(struct?cdev?*cdev,?struct?device?*dev)?
          void?cdev_del(struct?cdev?*p)?
          struct?cdev?*cdev_alloc(void)?
          void?cdev_init(struct?cdev?*cdev,?const?struct?file_operations?*fops)

          //?3.?設(shè)備號管理相關(guān)
          __register_chrdev_region(unsigned?int?major,?unsigned?int?baseminor,?
          __unregister_chrdev_region(unsigned?major,?unsigned?baseminor,?int?minorct)?
          int?register_chrdev_region(dev_t?from,?unsigned?count,?const?char?*name)?
          int?alloc_chrdev_region(dev_t?*dev,?unsigned?baseminor,?unsigned?count,?
          int?__register_chrdev(unsigned?int?major,?unsigned?int?baseminor,?
          void?unregister_chrdev_region(dev_t?from,?unsigned?count)?
          void?__unregister_chrdev(unsigned?int?major,?unsigned?int?baseminor,
          • 在 Linux 代碼中,雙下劃線(__)開始的函數(shù)名表示這是一個低層接口, 應(yīng)當(dāng)小心使用。如果你調(diào)用這個函數(shù),確信你知道你在做什么。也就是說,閱讀代碼時,雙下劃線開頭的函數(shù)可以暫時放一邊。

          • struct cdev 管理相關(guān):cdev_add() / cdev_del() / cdev_init(),需重點關(guān)注。

          • 設(shè)備號管理相關(guān):alloc_chrdev_region() / register_chrdev_region(),需重點關(guān)注。

          2> public 變量:

          const?struct?file_operations?def_chr_fops?=?{
          ?.open?=?chrdev_open,
          ?.llseek?=?noop_llseek,
          };
          • 類似于高級語言的多態(tài)機制,在 Linux 各個子系統(tǒng)的驅(qū)動框架中一般用一個統(tǒng)一的 file_operations->open 函數(shù)關(guān)聯(lián)到各個具體的硬件驅(qū)動的 struct file_operations,需要重點關(guān)注。

          3> private 變量:

          static?struct?char_device_struct?{
          ?struct?char_device_struct?*next;
          ?unsigned?int?major;
          ?unsigned?int?baseminor;
          ?int?minorct;
          ?char?name[64];
          ?struct?cdev?*cdev;??/*?will?die?*/
          }?*chrdevs[CHRDEV_MAJOR_HASH_SIZE];

          static?struct?kobj_map?*cdev_map;?
          static?struct?kobj_type?ktype_cdev_default;
          static?struct?kobj_type?ktype_cdev_dynamic;
          • struct char_device_struct *chrdevs[] 數(shù)組是字符設(shè)備驅(qū)動框架的核心數(shù)據(jù)結(jié)構(gòu),需重點關(guān)注。

          4> private 函數(shù):

          static?inline?int?major_to_index(unsigned?major)?
          static?int?find_dynamic_major(void)?
          static?struct?kobject?*cdev_get(struct?cdev?*p)?
          static?int?chrdev_open(struct?inode?*inode,?struct?file?*filp)?
          static?void?cdev_purge(struct?cdev?*cdev)?
          static?struct?kobject?*exact_match(dev_t?dev,?int?*part,?void?*data)?
          static?int?exact_lock(dev_t?dev,?void?*data)?
          static?void?cdev_unmap(dev_t?dev,?unsigned?count)?
          static?void?cdev_default_release(struct?kobject?*kobj)?
          static?void?cdev_dynamic_release(struct?kobject?*kobj)?
          static?struct?kobject?*base_probe(dev_t?dev,?int?*part,?void?*data)
          • helper 類函數(shù),不太重要。

          2) 分解 C header 文件,include/linux/cdev.h (Linux-4.14):

          作用:
          包含字符設(shè)備驅(qū)動相關(guān)結(jié)構(gòu)體的定義、以及一些字符設(shè)備核心 API 的聲明。

          內(nèi)容:
          1> struct cdev 結(jié)構(gòu)體:

          struct?cdev?{
          ?struct?kobject?kobj;
          ?struct?module?*owner;
          ?const?struct?file_operations?*ops;
          ?struct?list_head?list;
          ?dev_t?dev;
          ?unsigned?int?count;
          }?__randomize_layout;
          • struct cdev 是字符設(shè)備驅(qū)動的核心抽象,需重點關(guān)注。

          3.2 對字符設(shè)備進行抽象: struct cdev

          編寫字符設(shè)備驅(qū)動程序就是為了管理和控制字符設(shè)備,Linux 內(nèi)核將字符設(shè)備抽象為一 個數(shù)據(jù)結(jié)構(gòu):struct cdev。這個結(jié)構(gòu)體似乎從 Linux-2.6 到現(xiàn)在 Linux-5.8 都沒有發(fā)生變化。

          1) struct cdev 成員簡介:

          目前沒必要完全理解這些成員的作用,有個大概印象就好:

          • struct kobject kobj: 內(nèi)嵌的內(nèi)核對象,與 Linux 設(shè)備驅(qū)動模型相關(guān),后續(xù)需專門寫一篇文章來介紹。

          • struct module *owner: 指向擁有這個結(jié)構(gòu)體的模塊的指針,這個成員用來在它的操作還在被使用時阻止模塊被卸載,一般初始化為 THIS_MODULE;

          • struct file_operations *ops: 在Linux通用文件模型下,字符設(shè)備的文件操作函數(shù)集,后續(xù)詳解。

          • struct list_head list:用于管理 struct cdev 的鏈表,后續(xù)需專門寫一篇文章來介紹。

          • 字符設(shè)備的設(shè)備號,由主設(shè)備號和次設(shè)備號構(gòu)成,后續(xù)需專門寫一篇文章來介紹。

          2) 兩種方式創(chuàng)建 struct cdev 對象:

          這里用對象一詞,是為了引導(dǎo)大家用面向?qū)ο蟮乃季S來看待 Linux 內(nèi)核的設(shè)計

          面向過程還是面向?qū)ο螅粦?yīng)該和語言綁定在一起,應(yīng)該理解為 2 種不同的編程思維。人腦是很美妙的,在不同的場景,只要你愿意,它就能應(yīng)用不同的思維方式來解決問題。設(shè)計 Linux 內(nèi)核代碼的神牛們,可謂是各個都是面向?qū)ο缶幊痰拇笈#毩?xí)編程就應(yīng)該練習(xí)對事物的抽象能力,C 程序員如果覺得自己缺乏這方面的能力,不如學(xué)習(xí)一下 Java 編程。

          靜態(tài)定義:

          static?struct?cdev?chr_dev;

          動態(tài)分配:

          struct?cdev?*my_cdev?=?cdev_alloc();

          cdev_alloc() 不僅會為struct cdev對象分配內(nèi)存空間,還會對該對象進行必要的初始化:

          struct?cdev?*cdev_alloc(void)
          {
          ?struct?cdev?*p?=?kzalloc(sizeof(struct?cdev),?GFP_KERNEL);
          ?if?(p)?{
          ??INIT_LIST_HEAD(&p->list);
          ??kobject_init(&p->kobj,?&ktype_cdev_dynamic);
          ?}
          ?return?p;
          }

          一個值得注意的點:
          我搜索了一下內(nèi)核源碼,發(fā)現(xiàn)大多數(shù)驅(qū)動都選擇靜態(tài)定義 struct cdev,這樣更簡單省事。

          3) 某個真實字符硬件的抽象:

          數(shù)據(jù)結(jié)構(gòu) struct cdev 作為字符設(shè)備的抽象,僅僅是為了滿足 Linux 內(nèi)核對字符設(shè)備驅(qū)動程序框架結(jié)構(gòu)設(shè)計的需要

          現(xiàn)實中,一個具體的字符硬件設(shè)備的數(shù)據(jù)結(jié)構(gòu)的抽象往往要復(fù)雜得多,在這種情況下 struct cdev 常常作為一種內(nèi)嵌的成員變量出現(xiàn)在實際設(shè)備的數(shù)據(jù)結(jié)構(gòu)中。

          例如 drvier/watchdog/watchdog_dev.c:

          struct?watchdog_core_data?{
          ?struct?kref?kref;
          ?struct?cdev?cdev;
          ?struct?watchdog_device?*wdd;
          ?struct?mutex?lock;
          ?unsigned?long?last_keepalive;
          ?unsigned?long?last_hw_keepalive;
          ?struct?delayed_work?work;
          ?unsigned?long?status;??/*?Internal?status?bits?*/
          };

          4) 初始化 cdev 對象:

          void?cdev_init(struct?cdev?*cdev,?const?struct?file_operations?*fops)
          {
          ?memset(cdev,?0,?sizeof?*cdev);
          ?INIT_LIST_HEAD(&cdev->list);
          ?kobject_init(&cdev->kobj,?&ktype_cdev_default);
          ?cdev->ops?=?fops;
          }

          cdev_init() 最重要的作用就是將 struct cdev 對象和 struct file_operations 對象綁定在一起。

          一些值得注意的點

          • cdev_init() 和 cdev_alloc() 中有一部分功能是重疊的(例如 kobject_init()),所以 cdev_init() 只能搭配靜態(tài)定義 struct cdev 的方式來使用。

          • 如果采用 cdev_alloc() 動態(tài)分配 struct cdev 對象,則需自行 cdev->ops = fops 。

          3.3 對字符設(shè)備的操作進行抽象:struct file_operations

          struct?file_operations?{
          ?struct?module?*owner;
          ?loff_t?(*llseek)?(struct?file?*,?loff_t,?int);
          ?ssize_t?(*read)?(struct?file?*,?char?__user?*,?size_t,?loff_t?*);
          ?ssize_t?(*write)?(struct?file?*,?const?char?__user?*,?size_t,?loff_t?*);
          ?ssize_t?(*read_iter)?(struct?kiocb?*,?struct?iov_iter?*);
          ?ssize_t?(*write_iter)?(struct?kiocb?*,?struct?iov_iter?*);
          ????
          ????[...]

          ?int?(*mmap)?(struct?file?*,?struct?vm_area_struct?*);
          ?int?(*open)?(struct?inode?*,?struct?file?*);
          ?int?(*flush)?(struct?file?*,?fl_owner_t?id);
          ?int?(*release)?(struct?inode?*,?struct?file?*);
          ?int?(*fsync)?(struct?file?*,?loff_t,?loff_t,?int?datasync);
          ?int?(*fasync)?(int,?struct?file?*,?int);
          ?int?(*lock)?(struct?file?*,?int,?struct?file_lock?*);

          ?[...];

          }?__randomize_layout;

          • 目前沒必要完全理解這些成員的作用,有個大概印象就好。

          • 字符設(shè)備驅(qū)動程序中一個極其關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),字符設(shè)備驅(qū)動程序的編寫,就是是圍繞著如何實現(xiàn) struct file_operations 中的那些函數(shù)指針成員而展開的。

          • 通過內(nèi)核文件系統(tǒng)組件在其間的穿針引線,應(yīng)用程序中對文件類函數(shù)(open、read、write)的調(diào)用,將最終被轉(zhuǎn)接到 struct file_operations 中對應(yīng)函數(shù)指針的具體實現(xiàn)上,后續(xù)需專門寫一篇文章來介紹。

          鑒于大多數(shù)人的注意力無法在一篇文章里上集中太久,更多的內(nèi)容將放在后面的文章里。建議大家可以先自行閱讀相關(guān)書籍,不是自己理解到的東西是消化不了的。

          4. 更多值得學(xué)習(xí)的知識點

          • 字符設(shè)備號的構(gòu)成與管理

          • 字符設(shè)備的注冊

          • 生成字符設(shè)備文件的方式有哪些?

          • 創(chuàng)建字符設(shè)備文件是發(fā)生了什么?

          • 字符設(shè)備文件是如何關(guān)聯(lián)到字符設(shè)備驅(qū)動的?

          • 分析一些真實的字符設(shè)備驅(qū)動

          5. 相關(guān)參考

          • 《Linux 內(nèi)核文檔》

          • 《Linux設(shè)備驅(qū)動程序》(ldd) / 第 3 章節(jié)

          • 《深入Linux設(shè)備驅(qū)動程序內(nèi)核機制》(ildd) / 第 2 章節(jié)

          • 《精通Linux設(shè)備驅(qū)動程序開發(fā)》(eldd) / 第 2 章節(jié)

          • 《Linux設(shè)備驅(qū)動開發(fā)詳解》(ldds) / 第 6 章節(jié)

          • 《深入Linux內(nèi)核架構(gòu)》(plka) / 第 6 章節(jié)

          • 《嵌入式應(yīng)用開發(fā)完全手冊》



          #推薦閱讀:
          ? ??專輯|Linux文章匯總
          ? ??專輯|程序人生
          ? ??專輯|C語言


          嵌入式Linux
          微信掃描二維碼,關(guān)注我的公眾號?


          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黑人久久 | 久久久久伊人 | 手机在线观看免费视频人 | 操逼123首页 | 国产农村乱╳╳╳乱免费下载 |