### Linux 驅動程式介紹 在此筆記中會先粗略地介紹Linux驅動程式的相關基礎觀念,主要是我在進行 Linux Driver相關開發時的學習紀錄。 驅動程式(Driver)是 Kernel(核心空間)與 User Space(使用者空間)之間的橋樑。 * Linux 系統可以想像成三個層級: ``` ┌─────────────────────────────┐ │ User Space │ ← 應用程式區域 │ (App, Shell, Libraries) │ └────────────┬────────────────┘ │ system call (read, write, ioctl...) ┌────────────┴────────────────┐ │ Kernel Space │ ← 核心空間 │ (Drivers, Scheduler, MMU) │ └────────────┬────────────────┘ │ ┌────────────┴────────────────┐ │ Hardware │ ← 實體硬體 │ (CPU, GPIO, UART, etc.) │ └─────────────────────────────┘ ``` | 層級 | 功能 | 舉例 | | -------- | -------- | -------- | | User Space | 執行應用程式。透過系統呼叫 (system call) 存取資源 | cat /dev/led、echo 1 > /dev/gpio17 | | Kernel Space | 控制硬體的驅動程式 (Driver) | GPIO driver、UART driver、USB driver | | Hardware | 實際裝置、暫存器、匯流排 (bus) | 晶片上的 peripheral、GPIO pin | * 驅動程式負責做為底層以及上層的溝通橋樑: * 將上層的抽象操作(例如 read/write)轉換成實際硬體操作(例如寄存器寫入) * 將硬體事件(如中斷、資料輸入)回報給 Kernel 或 User Space * 透過驅動與硬體互動的流程範例: * 在這個過程中,應用程式只是「寫一個檔案」 * 驅動程式把這個動作轉成「寫入暫存器」 * Kernel 幫你隔離硬體細節 ``` User Program: echo 1 > /dev/myled ← write() │ ▼ Kernel: myled_write() ← 驅動程式的 file_operations.write() iowrite32(1, gpio_addr) ← 寫入硬體暫存器 │ ▼ Hardware: LED 亮起 ``` --- ### Linux中驅動程式的種類 | 類型 | 說明 | 例子 | | -------- | -------- | -------- | | Character Driver | 以 byte 為單位存取 | ART、GPIO、I2C| | Block Driver | 以區塊 (block) 存取 | SD card、eMMC | | Network Driver | 提供封包傳輸介面 | Ethernet、Wi-Fi | | Platform Driver | 用於嵌入式 SoC 裝置 | GPIO controller、SPI device | --- ### Linux Kernel Module Linux模組(Kernal Module)是一種可以動態載入與卸載的核心程式碼,用以開發Driver,讓開發者無須重新編譯整個核心就能擴充功能。 它通常是: * 一個 .ko 檔(kernel object) * 包含驅動程式、檔案系統、或其他核心功能 * 可用 insmod / rmmod 命令載入與移除 目的: * 讓驅動程式能在「系統運行中」被載入,而不必重新開機或重編 kernel。 #### 基本 Module 架構: * 一個最簡單的 Linux kernel module 會長這樣: ```javascript= #include <linux/module.h> #include <linux/init.h> MODULE_LICENSE("Dual BSD/GPL"); static int my_init_module(void) { printk(KERN_ALERT "Hello, world\n"); return 0; } static void my_cleanup_module(void) { printk(KERN_ALERT "GoodBye, Happy world\n"); } // insmod module_init(my_init_module); // rmmod module_exit(my_cleanup_module); ``` ![S__6905871](https://hackmd.io/_uploads/H1fJ7WbCel.jpg =80%x) #### 編譯方式 * 建立一個 Makefile: 指定目標架構為是 Orange Pi(ARM64 架構)進行開發,並呼叫 kernel 的 build system 來編譯這個目錄底下的模組產生 Driver (.ko)。。 ``` .PHONY:clean ifeq ($(KERNELRELEASE),) # Assume the source tree is where the running kernel was built # You should set KERNELDIR in the environment if it's elsewhere #/home/cyh/orangepi-build/kernel/orange-pi-5.4-sun50iw9 KERNELDIR ?= /home/cyh/orangepi-build/kernel/orange-pi-5.4-sun50iw9 # The current directory is passed to sub-makes as argument PWD := $(shell pwd) modules: $(MAKE) ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- -C $(KERNELDIR) M=$(PWD) modules else obj-m := MyModule.o endif clean: rm -f *.o rm -f *.ko *.cmd rm -f *.mod.* rm -f *.mod rm -f [mM]odule* ``` * 編譯指令: `make` 會產生:`MyModule.ko` #### 載入與移除模組 ``` sudo insmod mymodule.ko # 載入模組 sudo rmmod mymodule # 移除模組 dmesg | tail # 查看核心訊息 ``` 輸出示例: ``` [ 123.456] MyModule: init [ 130.789] MyModule: exit ``` #### 常用巨集與函式 | 名稱 | 功能 | | -------- | -------- | | module_init() | 指定模組載入時要呼叫的函式 | | module_init() | 指定模組載入時要呼叫的函式 | | module_exit() | 指定模組卸載時要呼叫的函式 | | MODULE_LICENSE() | 宣告授權(GPL 非常重要) | | MODULE_AUTHOR() | 作者資訊 | | MODULE_DESCRIPTION() | 模組描述 | | printk() | 核心空間的輸出函式(類似 printf)| #### 模組載入的生命週期 ``` insmod mydriver.ko ↓ module_init() → 初始化驅動(註冊裝置、申請資源) ↓ 使用中(open/read/write) ↓ rmmod mydriver ↓ module_exit() → 釋放資源、登出裝置 ```