kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- title: 'Linux 設備開發 - 概述、編譯、安裝、HelloWrold' disqus: kyleAlien --- Linux 設備開發 - 概述、編譯、安裝、HelloWrold === ## Overview of Content 使用 make 4.8 版本 [TOC] ## Linux 內核板號規則 Linux 內核版號由幾個部份組成,可以用指令查看當前系統版本號 ```shell= uname -r ``` * 主版號:`6` * 次版號:`2` * 修正版本號:`0` * 微調版本號:`-` * 特製 Linux 系統調校描述:`generic` > ![](https://hackmd.io/_uploads/HJhKSkEz6.png) <!-- ### C 語言的編譯環境 - GNU * `GNU C` 是對標準 C 的拓展,是 Linux/UNIX 下最常用的 **C 語言編譯環境**, --> ### 常見開源協議 * 不同的開源協議可以不同程度的保障開源者的權益,如果違反協議很可能被起訴(雖然很多中小企業都不太考慮開源協議的限制 :joy:) :::info * 完整的開源協議搜尋,可以到 [**opensource**](https://opensource.org/licenses/) 網站搜尋 ::: * 以下來看看幾種常見的開源協議 | 協議 | 有利於 | 概述 | 補充 | | - | - | - | - | | GPL 協議 | 技術拓展 | GPL 會 **強迫全面的開源**,公開技術,但不利於商業 | 它也是具有傳染性的協議,因為它會包含 **衍生程式也需要開源** | | LGPL 協議 | 商業、Library | 基於 GPL 協議,但他是 **為了 Library 使用設計的原則**,LGPL 允許商業軟體 **通過 Library 引用而不用開源** | 採用 LGPL 商業代碼 **可以被發布、銷售** | | BSD 協議 | 使用者拓展、同時尊重原作 | **讓使用者自由拓展、修改源碼**;可將修改後的程式作為開源、軟體再發布 | 再次發布條件如下:^1.^在源代碼中帶有原來的 BSD 協議、^2.^ 如果發布二進制庫,也須聲明源代碼中的 BSD、^3.^ 不可用開源碼的作者、機構名、原來的名稱發布 | | Apache 2.0 協議 | 使用者拓展、同時尊重原作、商業友善 | 同樣允許程式修改、再發布,不過細節規範較多 | 不同點在於:^1.^ 程式中有有一份 `Apache License`、^2.^修改的代碼須在文件中說明、^3.^ 在拓展代碼終須帶有源有的協議、商標、專利、原作規定;^4.^再次發布須帶有 Notice 文件 | | MIT 協議 | 作者可以保留版權 | 寬鬆的協議許可 | 也就是說必須在你發行版裡面,必須包含原作的許可聲明協議 | ## Linux 設備驅動 在最初的系統中,並沒有所謂的「**驅動**」,軟體都可以直接訪問電腦的硬體元件;這造就了純軟體工程師要了解 **硬體的通訊協定**(`I2C`、`SPI`、`UART`、`CAN BUS`... 等等) :::danger * 這種直接訪問造成了「**高依賴**」 而高依賴,就說明了「**高耦合**」,模組與模組之間的依賴度高,無法相互替換(因為依賴了細節,直接通訊會有關於到裝置細節的設置) > 這也間接說明了 **「抽象」的重要**,抽象度越高,耦合度越度 ``` mermaid graph LR; 軟體工程師 -.-> |要了解通訊細節| 裝置A 軟體工程師 --> 直接通訊 直接通訊 -.-> |通訊 ok| 裝置A 直接通訊 -.-> |無法通訊 error | 裝置B ``` * **而這個「抽象的任務」就交由給「驅動」去設計** 驅動會統一界面(接口),這讓上層工程師只須專注了解界面所提供的功能即可,不必關注功能的實現 ``` mermaid graph LR; 軟體工程師 -.-> |只要了解驅動界面| 驅動 驅動 -.-> |通訊 ok| 裝置A 驅動 -.-> |通訊 ok| 裝置B ``` ::: ### Linux 驅動是甚麽? * 事實上 Linux 驅動是 **使用 Linux 為驅動開發者提供的 API**,本質上沒有很大的差異,不過就是在開發時要使用 **Linux 提供的框架** * **Linux 驅動的工作方式、特色** * **驅動存在的形式**: Linux 會將每個「**驅動映射為文件**」,這些文件會稱為 **設備、驅動文件**;並會將這種文件存放在 `/dev` 資料夾之下 > 這種設計方案使用訪問 Linux 驅動,就跟訪問文件一樣的簡單(跟普通文件一樣) ```mermaid graph TB; subgraph PC subgraph /dev 驅動_設備文件_影印機 驅動_設備文件_錄音裝置 驅動_設備文件_無限網卡 end 影印機 -.-> 驅動_設備文件_影印機 錄音裝置 -.-> 驅動_設備文件_錄音裝置 無限網卡 -.-> 驅動_設備文件_無限網卡 end ``` * **與驅動交互的方式**: 所以跟 Linux 驅動交互的方式,只需要與 **設備文件交換數據** 即可,也就是在程式中「開啟驅動文件」,並對文件下達指令即可 > 像是,要 Linux 系統下的影印機打印指令 > > 只需要使用 C 語言,透過系統提供的 IO API 來操作文件;常見的像是 > > 透過 `open` 函數開啟驅動文件 > 透過 `ioctl` 來操作該文件… 並下達影印指令 ```mermaid graph LR; 開發者 -.-> |open、ioctl|驅動_設備文件 驅動_設備文件 -.-> 開發者 subgraph PC subgraph /dev 驅動_設備文件 end 實體設備 -.-> 驅動_設備文件 end ``` 而文件與設備的映射關係,則是由 Linux 系統去負責,開發驅動的人員不必當心硬體文件映射的部份(當然你要研究也可以的~) * 然而 Linux 驅動文件並非使用手動去觸發才會執行(不然真的很麻煩,每次影印都要手動觸發驅動行為)… 通常會需要「**事件驅動機制**」,**由驅動來監聽事件**! :::success * **事件驅動機制** 是一種 **IoC 的軟體設計概念**,它會 **反轉高低層模組之間的關係** 如果使用 C 語言的話,可以通過 Callback 的方式達成這種事件驅動機制 ::: ### 驅動 & 內核 - 關係 * Linux 驅動開發只會與 Linux 內核有關!也就是說 **Linux 驅動與 Linux 內核是強耦合關係**,**相同的 Linux 驅動,對於不同版本的 Linux 內核不一定能互通** 而驅動又與應用沒有直接的關係,兩者屬於弱耦合 ```mermaid graph LR; 應用 --> 驅動界面 驅動界面 -.-> Linux_版本A 驅動界面 -.-> Linux_版本B subgraph Linux_版本A Linux_驅動A --> |強關聯| Linux_內核A end subgraph Linux_版本B Linux_驅動B --> |強關聯| Linux_內核B end ``` ### 物理設備 - 軟體抽象的分類、特點 * 電腦系統的硬體主要有幾個元件 CPU、記憶體、存儲器、外部設備,隨著晶片的進步;CPU 內部其實就自己包有這幾個部件了 > 有些 CPU 內部有 `RAM`、`Flash` 裝置 ```mermaid graph LR; subgraph PC CPU 記憶體 存儲器 外部設備 end ``` * 驅動則是針對存儲器(`register`)、外部設備,而 Linux 主要將這些設備為三大類 * **字符設備**(`Character devices`) 主要針對須以「**串行順序依次**」訪問的設備(也就是須照順序排隊的設備),它不須經過系統快速緩存 > Eg. 觸碰螢幕、滑鼠、鍵盤... 等等 * **塊設備**(`Block devices`) 可以用來進行「**任意順序**」訪問,以「塊」為單位訪問裝置,它會須經過系統快速緩存 > Eg. 最常見的就是硬碟 :::info * 在使用上: 塊設備有時候與字符設備類似,並沒有非常明顯的邊界,可以把塊設備當作字符設備訪問 > 都有實現 `Open`、`close`、`read`、`write`... 等等函數指標 * 在驅動實做上: 字符設備、塊設備有很大的差異 ::: * **網路設備**(`Network devices`) 主要是處理「數據的發送」、「接收」... 它沒有對應的文件系統節點;它與字符、塊設備的 **通訊方式有很大的不同** ## Linux 驅動開發 Linux 驅動程式與其他類型的 Linux 程式開發一樣,都由系統提供固定框架,由使用者 **按照框架** 約定去開發程式 這也就造就了 **Linux 驅動開發可以「有順序、規則」可尋** ### 驅動框架、步驟 1. **建立 Linux 驅動骨架**: * **初始化設備**,**告訴 Linux 有這個設備的存在、做基礎設置** ```c= // Linux 初始化設備時使用的函數(宏) module_init(...); ``` Linux 內核在使用驅動時需要 **裝載驅動**,而在裝載前需要進行 **初始化** 工作,而在這個奏中可能會涉及如下 > 可大概理解為 main 函數註冊(只不過是指驅動部份) * **註冊 ++設備文件++**,在 `/dev` 目錄下建立設備文件: **任何一個驅動「都需要設備文件」**,否則應用就無法操控設備(應用透過開啟文件、寫入... 等等行為來操作設備) ```c= // Linux 建立設備文件(宏) misc_register(...); ``` * **指定驅動訊息**: 也就是對於驅動程式的描述(像是 Manifest);像是可以透過 `modinfo` 命令獲取驅動程式的資訊 ```c= // Linux 描述設備(宏) MODULE_LICENSE(...); // 開源協議 MODULE_AUTHOR(...); // 作者姓名 MODULE_ALIAS(...); // 驅動別名 MODULE_DESCRIPTION(...); // 驅動描述 ``` * **指定系統回調**: Linux 驅動中,包還多種行為回調;**每當符合某些條件時… 系統就會呼叫這些驅動回調,並發送事件給 Linux 驅動** > 像是 `read`、`write`... 等等 ```c= // 像 Linux 註冊驅動回調(宏) misc_register(...); ``` :::info * IoC 的反轉點:由底層(系統)作為高層模組,呼叫驅動開發(低層) ::: * **釋放設備** Linux 系統退出時需要卸載 Linux 驅動,在卸載的過程中就會 **釋放 Linux 驅動佔用的資源**,像是 * **註銷 ++設備文件++**,刪除 `/dev` 目錄下的設備文件 ```c= // Linux 移除設備文件(宏) misc_deregister(...) ``` * 釋放虛擬內存地址空間 ```c= // Linux 釋放設備時使用的函數(宏) module_exit(...) ``` 2. **撰寫驅動的業務邏輯**: 這個步驟是開發者主要要處理的關鍵,在可以做一些與業務邏輯相關的工作,像是 * COM 驅動會根據傳輸率進行數據交換 * 傳送打印指令給影印機 3. **編寫 Linux 驅動**: * **Makefile 文件**: Linux 內核源碼的編譯歸則是通過 **Makefile 文件定義**,因此一個驅動會對應一個 Makefile 文件 * **編寫 Linux 驅動程序** Linux 驅動可以選擇一同編譯進內核,也可以作為單獨模塊,單獨編譯 :::info * **指令安裝、卸載 Linux 驅動**: * 如果驅動編譯進內核源碼,那在 Linux 啟動時,就會自動被裝載(不須指令安裝) * 如果使用單獨模塊,那就要使用 `insmod`、`modprobe` 命令裝載驅動(卸載使用 `rmmod` 指令) ::: ### 編譯檔 Makefile * Makefile 檔案的編寫設置如下 ```shell= obj-m := hello_world.o ## 依賴 process.o、data.o 模塊 hello_word-y := process.o data.o ``` 以上是設置將 `hello_world` 驅動編譯為模組(`obj-m`),並依賴兩個模塊;而對於 Makefile 不同設置會對編譯起到不同作用,如下表 | Makefile 設置 | 說明 | 補充 | | - | - | - | | `obj-m` | 將驅動作為模塊編譯 | 以上例來說,**會將 `hello_world.o` 編譯 `hello_world.ko` 文件中** | | | | 之後讓使用者使用 `insmod`、`modprobe` 命令裝載驅動 | | `obj-y` | 將驅動編譯進核心 | **`hello_world.o` 會編譯進 `built-in.o` 文中** | | | | 這時的文件是中間文件,最終所有驅動都會編譯進 `<核心源碼/drivers/char/built-in.o>` 文件 | | | | 是否真正編譯可以在編譯核心前,使用 `make menuconfig` 指令設置 | | `<module>-y`、`<module>-obj` | 當 Linux 驅動依賴其他模塊時,須使用這個方式標示 | `<module>` 代表的是模塊名 | ### 編寫 Linux 驅動檔 HelloWorld - 用戶、核心空間 * 「**用戶空間**」、「**內核空間**」不能相互訪問,那要如何交互傳遞訊息呢? 可以使用以下兩個方案 * 使用存在 `/proc` 資料夾下的「**虛擬文件**」 * 使用存在 `/dev` 資料夾下的「**設備文件**」 > 所以用戶空間假設要透過 設備文件訪問內核空間的話,就要做一個可以訪問內核空間的驅動程式,然後用戶空間的程式通過「設備文件」與「驅動程式」交互 ```mermaid graph TB; subgraph 使用者程式 內核空間 使用者空間 end subgraph /dev 設備文件 end 使用者空間 -.-> 設備文件 設備文件 --> 驅動 -.-> 內核空間 ``` * **Linux 最簡單的驅動檔** 如下(不需要 `/dev`, `/proc`... 單純註冊一個驅動) ```c= #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <asm/uaccess.h> static int hello_world_init(void) { printk("hello_world_init_success.\n"); return 0; } static void hello_world_exit(void) { printk("hello_world_exit_success.\n"); } // 註冊初始化、退出的驅動函數 module_init(hello_world_init); module_exit(hello_world_exit); ``` :::warning * **為甚麽不使用平常常用的 `printf` 就好了**? 因為在虛擬記憶體的規劃中,有分兩個大區塊「**用戶空間**」、「**內核空間**」,**這兩個空間是無法相互訪問的** * `printf` 就存在使用者空間中 > 如果在驅動開發中使用 `printf`,那編譯就會拋出異常,因為找不到 `stdio.h` 檔 :::info * 用戶空間的程式使用的頭文件 **是存在 `usr/include` 目錄** 之下 > ::: * `printk` 就存在內核空間中(核心空間也不可以使用用戶空間的函數,像是 `malloc`、`free`... 等等) > 在驅動開發中就要使用 `printk`、`kmalloc` :::info * 核心空間如果要使用內核空間的功能,可以到 Linux 內核源碼之下的 `include/` 目錄找尋需要的頭文件 > ![image](https://hackmd.io/_uploads/Sy_3hO1H6.png) ::: ::: ### 編譯 Linux 驅動檔 - ko 檔 * 編譯以上,最簡單的 Linux 驅動檔 HelloWorld,請用以下命令 ```shell= # 編譯命令如下 ## `-C` -> 指定 Header ## `M` -> 以編譯模塊 make -C /usr/src/linux-headers-3.13.0-170-generic/ M=/root/drivers/1/hello_world/ ``` 1. 編譯輸出結果: 編譯 Linux 驅動模塊後,**會輸出 `hello_world.ko`、`hello_world.o` 檔** > ![image](https://hackmd.io/_uploads/BJCDyc1H6.png) 2. 編譯輸出的檔案: 除了 `.o`、`.ko` 之外還有以下文件(一般來說不會使用到) > ![image](https://hackmd.io/_uploads/r11eXPgBa.png) :::warning * **缺少 `linux-header` 標頭檔,請先安裝以下套件** ```shell= apt install build-essential apt install linux-headers-generic ``` * **「`insmod Unknown symbol mcount (err 0)`」錯誤**? **make 版本如果是 4.8 以下,在 `insmod` 安裝驅動時就會出這個問題** > 安裝 make 4.8 版本 * 使用 `insmod` 加載時,如果 **出現 `disagrees about version of symbol module_layout` 錯誤**,那要注意編譯時使用的 `linux-header` > 或是 `Invalid module format` 錯誤 > > ![image](https://hackmd.io/_uploads/rk6GRP-S6.png) 如果使用與核心不同的 `linux-header` 可能就會造成這樣的問題,這時請按照核心版本去下載 `linux-header` > 我原先使用的是 Docker 容器的 `Ubuntu 14.04`,但就發生這個問題;所以我後來改用 `Oracle VM VirtualBox` ```shell= ## 查看核心版本 uname -r ## 使用核心版本對應的 linux-header 去編譯 make -C /usr/src/linux-headers-4.4.0-148-generic/ M=/home/kyle/Desktop/test/ ``` > ![image](https://hackmd.io/_uploads/r1r_HYgr6.png) ::: ### 使用 ko 檔 - 安裝、依賴、卸載 * **手動操作 `insmod`、`rmmod`... 指令** 1. **`depmod` 命令**:檢查驅動的依賴 ```shell= # 1. 記得使用 sudo 權限 # 2. 要用完整路徑,否則會錯誤 sudo depmod /home/kyle/Desktop/drivers/1/hello_world.ko ``` :::warning * 如果你之後要使用 `insmod` 命令安裝驅動,那這個步驟非必須;但如果你要用 `modprobe` 命令安裝驅動,那這個步驟就是必須 > `depmod` 會將 Linux 驅動模塊文件(包括路徑),添加到 `lib/module/<像是 4.4.0-148-generic>/modules.dep` 文件中 > ![image](https://hackmd.io/_uploads/ByFtgKKHa.png) ::: 2. **`insmod` 命令**:**加載 Linux 驅動(`.ko`)檔** ```shell= # 記得使用 sudo 權限 # 安裝驅動 - 方案 1 sudo insmod hello_world.ko ## --------------------------------------------------------------- # 安裝驅動 - 方案 2 # 安裝時不須使用全路徑(因為 depmod 已經設定),也不需要 `.ko` 結尾! sudo modprobe hello_world ``` :::danger * **加載失敗**? * 如果使用 Docker 容器,那可能會無法正常加載成功;如果要模擬 insmod 加載,建議使用「模擬器」 (像是 `Oracle VM VirtualBox`) * 如果已經加載過,請先執行卸載 ::: * **`dmesg` - 查看日誌**: 如果是第一次加載,並加載成功的話不會返回任何資訊,但可以使用 `dmesg` 查看 ```shell= dmesg | grep hello_world | tail -n 2 ``` > ![image](https://hackmd.io/_uploads/SyzP7YxHa.png) * **`lsmod` - 查看模塊**: 加載完成後就可以使用 `lsmod` 指令查看是否有 `hello_world` 模塊 ```shell= lsmod | grep hello_world ``` > ![image](https://hackmd.io/_uploads/rkMCmYgHp.png) * **`syslog` - 查看 `printk` 打印出來的訊息**: ```shell= cat /var/log/syslog | grep hello_world | tail -n 2 ``` > ![image](https://hackmd.io/_uploads/HkuqwFlr6.png) 3. **`rmmod` 命令**:**卸載 Linux 驅動(`.ko`)檔** ```shell= # 記得使用 sudo 權限 sudo rmmod hello_world.ko ``` > 同樣可以使用 日誌(`dmesg`)、模塊(`lsmod`) 查看… 這裡就不特別演示了 ### 驅動相關訊息 - MODULE_XXX * 我們可以透過 **`modinfo` 指令**,來查看模塊的相關訊息,其中就包括如下表 ```shell= modinfo hello_world.ko ``` | 模塊資訊 | 程式中使用的設置 | | - | - | | 作者 | `MODULE_AUTHOR` | | 描述 | `MODULE_DESCRIPTION` | | 別名 | `MODULE_ALIAS` | | 開源協議 | `MODULE_LICENSE` | > 其中也包括模塊的依賴 > ![image](https://hackmd.io/_uploads/BksnwdeB6.png) * 一般來說會將這些設置放在驅動程式的最下方,接下來我們將使用這些宏,再加上指令的方式查看編譯過後的 `.ko` 檔 ```c= #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <asm/uaccess.h> static int hello_world_init(void) { printk("hello_world_init_success.\n"); return 0; } static void hello_world_exit(void) { printk("hello_world_exit_success.\n"); } // 註冊初始化、退出的驅動函數 module_init(hello_world_init); module_exit(hello_world_exit); // 模塊資訊 MODULE_AUTHOR("Hello author"); MODULE_DESCRIPTION("Hello world device"); MODULE_ALIAS("Hello world module."); MODULE_LICENSE("GPL"); ``` 重新編譯後(編譯指令請看上面小節),使用 `modinfo` 指令查看編譯模塊 `.ko` 檔的資訊… 可以看到編譯過後的結果確實跟程式的設置相同 > ![image](https://hackmd.io/_uploads/Skvis_xST.png) :::info * **`vermagic` 表示什麼**? **Linux 驅動模塊在哪一個 Linux 內核版本下被編譯**(當然也包括了一些其他資訊) 從上可以看出,我使用的 Linux 內核是 `3.13.0-170-generic SMP mod_unload modversions retpoline` > `3.13.0-170-generic` 是 Kernel 版本號、`SMP` 代表支持對稱多處理器 > > * `mod_unload` 是 Linux kernel 模組支持卸載的一個特徵 > * `modversions` 是一個用於 kernel 模組的特殊標記 > * `retpoline ` 是一種用於減緩 Spectre 變體 2 攻擊的一種技術。 ::: ### 註冊、註銷 - dev 設備文件 * 設備文件被創建後,會存在 **`/dev` 目錄** 下;**建立時使用 `misc_register(...)` 函數、註銷時使用 `misc_deregister(...)` 函數** :::danger * **設備文件不能用一般的 `I/O` 函數建立** ::: 1. 這裡主要關注 「`miscdevice` 結構」的實現,它的幾個成員如下 | 成員 | 功能概述 | 補充 | | - | - | - | | `.name` | 設備文件名稱 | - | | `.minor` | 次要設備號 | 動態生成可以使用 `MISC_DYNAMIC_MINOR` 宏;也可以透過 `register_chrdev_region`、`alloc_chrdev_region` 函數同時指定主設備、次設備號 | | `.fops` | 設備被操作時,響應系統的函數 | 該成員的類型是 `file_operations`,然而這個類型有許多函數指標可以設置 | :::info * 主設備號不用設置? **雜項設備(`misc`)只能設置次要設備號**,主設備號預設為 10;主設備號 10 的設備是 **Linux 系統中擁有共同特性的簡單字符設備**(就是雜項設備) 也可以透過 `/proc/devices` 查看系統預設的主設備號 > ![image](https://hackmd.io/_uploads/S1o2Z_QS6.png) ::: 2. 再將該模塊自己實現的 `miscdevice` 的結構,透過 `misc_register` 函數註冊 ```c= #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <asm/uaccess.h> #define DEVICE_NAME "hello_world" // 描述設備文件觸發的事件、對應的回調函數指標(下一節說明) static struct file_operations dev_fops = { .owner = THIS_MODULE }; // 描述存在 `/dev` 之下文件資訊 // 先關注這個 static struct miscdevice misc = { .name = DEVICE_NAME, .minor = MISC_DYNAMIC_MINOR, .fops = &dev_fops }; static int hello_world_init(void) { int ret; ret = misc_register(&misc); printk("hello_world_init_success, ret = %d.\n", ret); return ret; } static void hello_world_exit(void) { misc_deregister(&misc); printk("hello_world_exit_success.\n"); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_AUTHOR("Hello author"); MODULE_DESCRIPTION("Hello world device"); MODULE_ALIAS("Hello world module."); MODULE_LICENSE("GPL"); ``` * 再次編譯為 `.ko` 檔案,並動態掛載驅動到系統中 ```shell= make -C /usr/src/linux-headers-4.4.0-148-generic/ M=/home/kyle/Desktop/drivers/2/ ``` > ![image](https://hackmd.io/_uploads/ryAS9mGBa.png) ```shell= ## 動態安裝 sudo insmod hello_world.ko ## 查看設備號(主 10, 次要 53) ls -laF /dev/hello_world ``` > ![image](https://hackmd.io/_uploads/rkKAcQfBa.png) ### 設定設備回調函數 * 在這裡要操縱「**用戶空間**」的應用程式、「**內核空間**」的驅動程式,並在驅動的地方撰寫兩邊交互的邏輯 :::success * **驅動開發的角度** **==驅動所站的角度是「核心」==**,所以會以「核心的角度」來操控內核函數(內核函數的名稱也是這樣設計的) * **用戶空間 跟 內核空間 無法相互訪問,如何傳遞數據**? 透過以下函數 | 函數 | 功能 | | - | - | | `copy_from_user` | 將數據從「使用者空間」複製到「核心空間」 | | `copy_to_user` | 將數據從「核心空間」複製到「使用者空間」 | ::: * 在 Linux 驅動中,回調函數 **關注的是 `file_operations` 結構**!(像是最常見的是 `read`、`write`) | `file_operations` 結構的成員 | 概述 | | - | - | | `read` | 函數指標,當系統需要 **讀取設備** 時,會回調 read 的函數 | | `write` | 函數指標,當系統需要 **寫入設備** 時,會回調 write 的函數 | ```c= #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <asm/uaccess.h> #define DEVICE_NAME "hello_world" static unsigned char mem[10000]; static char is_data_read_done = 'y'; static int written_count = 0; static struct file_operations dev_fops = { .owner = THIS_MODULE, // 註冊 Kernel Callback! .read = word_count_read, .write = word_count_write }; static struct miscdevice misc = { .name = DEVICE_NAME, .minor = MISC_DYNAMIC_MINOR, .fops = &dev_fops }; // Kernel 收到有數據「外部」傳給 hello 設備 // 就會呼叫這個 Callback,要求 hello 驅動處理 // // 站在 Kernel 的角度,將數據從 Kernel 傳送到 User,所以叫 read(核心複製到使用者) static ssize_t word_count_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { if(is_data_read_done == 'y') { return 0; } // 將數據從「核心空間」複製到「使用者空間」 copy_to_user(buf, (void*) mem, written_count); printk("--- read count to user(from kernel): %d\n", (int) written_count); is_data_read_done = 'y'; return written_count; } // Kernel 收到有數據從「應用」傳給該設備 // 就會呼叫這個 Callback,要求 hello 驅動處理 // // 站在 Kernel 的角度,將數據從 User 複製到 Kernel,所以叫 write(應用對核心寫入) static ssize_t word_count_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { // 將數據從「使用者空間」複製到「核心空間」 copy_from_user(mem, buf, count); is_data_read_done = 'n'; written_count = count; printk("--- user written count(to kernel): %d\n", (int)count); return count; } static int hello_world_init(void) { int ret; ret = misc_register(&misc); printk("--- hello_world_init_success, ret = %d.\n", ret); return ret; } static void hello_world_exit(void) { misc_deregister(&misc); printk("--- hello_world_exit_success.\n"); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_AUTHOR("Hello author"); MODULE_DESCRIPTION("Hello world device"); MODULE_ALIAS("Hello world module."); MODULE_LICENSE("GPL"); ``` :::warning * **參數中使用的 `__user` 宏**? 因為虛擬記憶體有將記憶體劃分為兩個空間(核心、使用者),但 **兩方不能相互訪問**;所以我們 **可以透過「`__user` 宏」**,來了解到 **「數據存在使用者空間」** * **為甚麽不使用** `word_count_write` 函數的 `count` 參數? 這裡另外使用 `written_count` 參數來紀錄的原因是因為,接下來要使用 **cat 命令測試讀取數據**,而這個命令會一次讀取 65536 字節 > **與實際真正字節數不同**,所以要另外紀錄 ::: 1. **編譯、安裝驅動** ```shell= ## 編譯 ko 檔 make -C /usr/src/linux-headers-4.4.0-148-generic/ M=/home/kyle/Desktop/drivers/3/ ## 移除先前驅動 sudo rmmod hello_world.ko ## 再次動態安裝驅動 sudo insmod hello_world.ko ``` > ![image](https://hackmd.io/_uploads/SkyW93uHp.png) 2. **測試寫入、讀取** * 使用 echo 對 `/dev/hello_world` 裝置 **寫入** 這個行為會觸發 `hello_world` 的 **`.write` 函數指標**,也就是 `word_count_write` 函數;**++將使用者空間數據寫入核心空間++** * 使用 cat **讀取** `/hello_world` 裝置的資訊 這個行為會觸發 `hello_world` 的 **`.read` 函數指標**,也就是 `word_count_read` 函數;**++把數據從核心空間讀取到使用者空間++**(使用者空間應用就是 cat) ```shell= # 切換到 root sudo su echo "hello 1 2 3" > /dev/hello_world cat /dev/hello_world exit ``` > ![image](https://hackmd.io/_uploads/SJJG5hurT.png) :::danger * **寫入裝置時 `Permission denied` 錯誤**? 命令中使用了管線(`>`)操作符,而這個操作符是由 shell 處理的;在這種情況下,sudo 只會應用於 echo 命令,而不會應用於重定向操作符 所以要先切換到 root(`sudo su`)再操作 ```shell= ## 出問題的命令 ------------------------------------------------------- sudo echo "hello123" > /dev/hello_world ## 返回輸出 ------------------------------------------------------- kyle@kyle-VirtualBox:~/Desktop/drivers/3$ sudo echo "hello123" > /dev/hello_world bash: /dev/hello_world: Permission denied ``` ::: 3. 使用 `dmesg` 命令查看 `printk` 的輸出 ```shell= dmesg | grep "\-\-\-" ``` > ![image](https://hackmd.io/_uploads/HkFis2_rp.png) ### 進階 - 計算輸入字元數量 * 將上面的範例進行拓展,目標是計算使用者輸入的「**單字數量**」;也就是重點多了兩個判斷 1. **判斷當前字元是否是算是一個 空白字元** ```java= static int is_space_char(char c) // ASCII 判斷 // 以空格、換行、`\t`、回車 為關鍵,遇到這幾個字元,就算是新字元的開始 if(c == ' ' || c == 9 || c == 13 || c== 10) { return TRUE; } else { return FALSE; } } ``` > ![image](https://hackmd.io/_uploads/ByW19sYSp.png) 2. 將字元陣列傳入,並計算有這個陣列中有幾個「**單字**」,細節請看註解 ```java= static int get_word_count(const char *buf) { // 當遇到 `\0`,代表陣列的資料已經讀完 if(*buf == '\0') { return 0; } // 計算單字數量,預設為一個單字 int count = 1; // 如果第一個單字是空格(或是 \t, \n... 等等) if(is_space_char(*buf) == TRUE) { // 改成從 0 開始計算 count--; } int index = 0; int is_space = 0; // 是否已經遇到空格 char curChar = ' '; // 掃描字符陣列中的所有「單字」 for(; (curChar = *(buf + index)) != '\0'; index++) { // 已經遇到空格,並且下一個非空格的單字 if(is_space == 1 && is_space_char(curChar) == FALSE) { // 設定沒有遇到空字元 is_space = 0; } // 已經遇到空格,並且下一個仍是空格 else if(is_space == 1 && is_space_char(curChar) == TRUE) { // 繼續往陣列中下一個字元判斷 continue; } // 遇到空格 if(is_space_char(curChar) == TRUE) { // 代表多了一個單字 count++; // 設定遇到空字元 is_space = 1; } } // 字符串用一個、多個空格結尾,不計數 if(is_space_char(*(buf + index - 1)) == TRUE) { count--; } return count; } ``` 3. 完整的驅動程式如下 ```java= #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <asm/uaccess.h> #define TRUE 1 #define FALSE 0 #define DEVICE_NAME "hello_world" static unsigned char mem[10000]; static int word_count = 0; static int is_space_char(char c) { if(c == ' ' || c == 9 || c == 13 || c== 10) { return TRUE; } else { return FALSE; } } static int get_word_count(const char *buf) { if(*buf == '\0') { return 0; } int count = 1; if(is_space_char(*buf) == TRUE) { count--; } int index = 0; int is_space = 0; char curChar = ' '; for(; (curChar = *(buf + index)) != '\0'; index++) { if(is_space == 1 && is_space_char(curChar) == FALSE) { is_space = 0; } else if(is_space == 1 && is_space_char(curChar) == TRUE) { continue; } if(is_space_char(curChar) == TRUE) { count++; is_space = 1; } } if(is_space_char(*(buf + index - 1)) == TRUE) { count--; } return count; } static ssize_t word_count_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { unsigned char tmp[4]; // big endian 轉換 tmp[0] = word_count >> 24; printk("--- tmp[0]: %d\n", (int) tmp[0]); tmp[1] = word_count >> 16; printk("--- tmp[1]: %d\n", (int) tmp[1]); tmp[2] = word_count >> 8; printk("--- tmp[2]: %d\n", (int) tmp[2]); tmp[3] = word_count; printk("--- tmp[3]: %d\n", (int) tmp[3]); copy_to_user(buf, (void*) tmp, 4); printk("--- count: %d\n", (int) count); printk("--- read word count: %d\n", (int) word_count); return count; } static ssize_t word_count_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { copy_from_user(mem, buf, count); mem[count] = '\0'; // 計算對該設備的輸入「單字」數量 word_count = get_word_count(mem); printk("--- write word count: %d\n", (int) word_count); return count; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .read = word_count_read, .write = word_count_write }; static struct miscdevice misc = { .name = DEVICE_NAME, .minor = MISC_DYNAMIC_MINOR, .fops = &dev_fops }; static int hello_world_init(void) { int ret; ret = misc_register(&misc); printk("--- hello_world_init_success, ret = %d.\n", ret); return ret; } static void hello_world_exit(void) { misc_deregister(&misc); printk("--- hello_world_exit_success.\n"); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_AUTHOR("Hello author"); MODULE_DESCRIPTION("Hello world device"); MODULE_ALIAS("Hello world module."); MODULE_LICENSE("GPL"); ``` :::danger * 以上程式不能使用 `cat` 命令測試 ::: * 編譯以上程式 ```shell= make -C /usr/src/linux-headers-4.4.0-148-generic/ M=/home/kyle/Desktop/drivers/4/ ``` > ![image](https://hackmd.io/_uploads/HydTkWQIa.png) ## Linux 驅動測試 對於 Linux 驅動,一開始可以在 Ubuntu Linux 中開發、測試;但在基礎開發完後仍有在 真實硬體(目標機)上測試 > 上面那種手動使用指令對 `/dev` 裝置輸入,並透過 `dmesg` 確認的方法,並不算真正的測試 ### Ubuntu 下測試驅動 :::warning 進行以下測試之前,請先確保「**進階 - 計算輸入字元數量**」小節的驅動已經安裝運行 ::: * 為了要讓測試效果正接近真正使用的環境,一般來說需要特別寫個測試程式;以下透過開啟文件來直接測試驅動 ```java= #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { int testDev; unsigned char buf[4]; testDev = open("/dev/hello_world", O_RDWR); if(testDev == -1) { printf("Cannot open hello_world file, did you insmod?\n"); return -1; } if(argc > 1) { char* str = argv[1]; write(testDev, str, strlen(str)); printf("The string (%s) been written.", str); } read(testDev, buf, 4); int word_count = ((int) buf[0] << 24 | (int) buf[1] << 16 | (int) buf[2] << 8 | (int) buf[3] ); printf("word byte display: %d, %d, %d, %d\n", buf[0], buf[1], buf[2], buf[4]); printf("word count: %d\n", word_count); close(testDev); return 0; } ``` 編譯測試程式並運行 ```shell= # 編譯測試程式 gcc hello_world_test.c -o hello_world_test # 空單字 sudo hello_world_test # 兩個單字 sudo hello_world_test "Yoyo 123" ``` > ![image](https://hackmd.io/_uploads/S1Tm02YBT.png) ## Appendix & FAQ :::info ::: ###### tags: `設備開發`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully