## 字元裝置 (Char Driver) #### 驅動程式的識別 Linux 裝置驅動中主要是使用「主編號 (major number)」與「次編號 (minor number)」來進行識別。 1. 主編號 (Major Number) 代表裝置所配合的**驅動程式**。 * 當你在 user space 對 /dev/xxx 做操作(open/read/write)時,kernel 會根據「主編號」找到對應的驅動。 3. 次編號 (Minor Number) 次編號用來區分**同一個驅動底下的多個裝置**。 * 例如同時有多個 UART、LED、或多個感測器實體。 查看裝置command: `ls -all` example: ` crw------- 1 orangepi tty 244, 0 Jul 9 19:46 ttyAS0 ` * c代表字元裝置, 244為主編號, 0為次編號。 建立裝置節點:`sudo mknod /dev/CharDrvJoey c 240 0` (root權限) 代表: * 主編號 240:由你的 CharDrvExm driver 處理。 * 次編號 0:代表此 driver 的第 0 號裝置。 Char Driver(字元裝置驅動)是 Linux 裝置驅動的一種,用來提供「一次一筆資料(byte stream)」的存取方式。 常見例子: * /dev/ttyS0(UART) * /dev/input/mice * /dev/random 特點: * 以read/write/ioctl介面與 user space 溝通。 * 透過 file_operations 結構與 kernel 溝通。 * 需透過 major/minor number 建立對應的 /dev/xxx 裝置節點。 #### 檔案作業 驅動程式內部以一個`file結構`來代表一個以開啟的裝置,核心透過一個`file_operations結構`來存取驅動程式內部的作業函式,file結構包含一個`f_op`欄位是指向file_operations結構的指標。 * `file_operations結構` 定義了驅動程式如何回應 open/read/write/ioctl 等系統呼叫 ```javascript= 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 *); int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); ... }; ``` * `file結構`是 VFS 層在 kernel 內部用來描述一個已開啟檔案的資料結構。 每當 user space 執行 open("/dev/mychardev"),kernel 都會產生一個新的 struct file 物件。 * inode 與 file 的關係 當 user 執行: `int fd = open("/dev/mychardev", O_RDWR);` VFS 會: 1. 建立一個 inode 結構(記錄裝置 major/minor、權限、擁有者等)。 2. 建立一個 file 結構(描述這次開啟行為)。 3. 把對應的 file_operations 放入 file->f_op。 4. 呼叫你的 .open()。 你在 .open() 裡可以取得: ```javascript= int my_open(struct inode *inode, struct file *file) { int minor = MINOR(inode->i_rdev); file->private_data = &my_device_data[minor]; // 綁定私有資料 return 0; } ``` #### 字元裝置範例 dsadasdad --- ### 字元裝置實作 在先前我們已經先建立了驅動程式的範例,接著驅動程式在核心空間中註冊字元裝置, 然後透過 **/dev/CharDrvJoey** 這個「裝置節點」讓使用者空間來用標準 I/O 介面來傳輸資料。 #### 驅動與裝置節點的關係 | 名稱 | 層級 | 功能 | 例子 | | --------------------- | ------------ | ------------ | -------------------------------- | | **Driver(驅動程式)** | Kernel-space | 控制硬體或模擬裝置 | 你的 `.ko` 模組(例如 `CharDrvExm.ko`) | | **Device Node(裝置節點)** | User-space | 提供使用者程式的存取介面 | `/dev/CharDrvJoey` | 🔹 驅動是寫在核心裡的程式(C code)。 🔹 /dev/CharDrvJoey 是一個「特別的檔案」,讓使用者空間可以透過 **系統呼叫(syscall)** 間接和驅動互動。 #### 運作流程 當你: ``` sudo insmod CharDrvExm.ko ``` ##### 驅動會在核心裡 1. 註冊一個 主裝置號 (major number) 和 次裝置號 (minor number); 2. 使用: ``` register_chrdev_region(dev_t dev, unsigned count, const char *name); cdev_add(&cdev, dev, 1); ``` 在核心內部建立一個 字元裝置控制結構; ##### User Space 如何傳輸資料 1. 接著使用者在User Space建立所對應裝置節點,建立在/dev/底下一個節點,例如: ``` mknod /dev/CharDrvJoey c 88 0 ``` 2. 驅動以及裝置節點建立好後,使用者空間就能透過 /dev/CharDrvJoey 這個節點,用標準 I/O 介面來對Driver傳輸資料: ``` echo "Joey" > /dev/CharDrvJoey # write() → driver 的 write callback cat /dev/CharDrvJoey # read() → driver 的 read callback ``` 或在 C 程式中: ``` int fd = open("/dev/CharDrvJoey", O_RDWR); write(fd, "Joey", 4); read(fd, buffer, sizeof(buffer)); close(fd); ``` 相關使用command: * `cat /proc/modules` 查看系統所有驅動程式 * `cat /proc/devices` 查看系統所有字元裝置(含 major/minor) * `dmesg | grep mychardev` 顯示 kernel ring buffer 的訊息 (只顯示包含 mychardev) --- ### 整體架構概念 當你在使用者層執行 ( /dev/CharDrvJoey 為裝置節點 ): `cat /dev/CharDrvJoey` 或 `int fd = open("/dev/CharDrvJoey", O_RDWR); write(fd, "A", 1);` Linux kernel 會進行這些步驟: ``` User Space │ │ open() / read() / write() ▼ ────────────────────────────── Kernel VFS 層 (Virtual File System) │ │ 根據 /dev/CharDrvJoey 的主次編號 ▼ Char Device Driver (CharDrvJoey.ko) │ │ → file_operations (open/read/write) ▼ 你的驅動程式內部邏輯 ``` --- ### 虛擬檔案系統 虛擬檔案系統(procfs),它掛載在 /proc 目錄下,用於通過核心提供處理程序(process)的相關資訊。 這個虛擬檔案系統不會佔用儲存空間,而是動態地生成檔案系統內容來顯示系統資訊。 在proc/下的每個檔案,接聯繫到kernal內的專屬函式,這些函式在使用者讀取檔案時,即時的產生檔案的內容。 與/dev的差別: | 項目 | `/proc` | `/dev` | | -------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------- | | **全名** | Process File System(程序檔案系統) | Device File System(裝置檔案系統) | | **主要用途** | 顯示或控制 kernel 的內部狀態(例如:記憶體、CPU、模組、驅動資訊) | 讓使用者空間與硬體裝置互動 | | **對應對象** | Kernel 中的資料結構、狀態、統計資訊 | 真實裝置或虛擬裝置(character/block device) | | **典型檔案例子** | `/proc/cpuinfo`, `/proc/meminfo`, `/proc/modules`, `/proc/<pid>/status` | `/dev/sda`, `/dev/ttyS0`, `/dev/gpiochip0`, `/dev/CharDrvJoey` | | **驅動常見用法** | 用來做 debug、監控、設定介面(像 /proc/mydriver/debug) | 真正提供 I/O 功能給應用程式使用(如 /dev/mydriver) | * /dev:裝置節點(Device Node) → 就像是一個「硬體的門口」。 * 每個節點都對應到一個驅動程式(character 或 block driver)。 * 用戶空間程式(例如 cat, echo, 或你自己的 app)透過 open/read/write/ioctl 與 driver 溝通。 * /proc:核心資訊與控制介面 → 像是一個「觀察窗」或「控制面板」。 * 是 kernel 自己建立的「虛擬檔案系統」(procfs)。 * 裡面內容不是實體存在的檔案,而是 kernel 即時產生的資訊。 常用來: * 查看系統資訊:cat /proc/cpuinfo * 顯示模組資訊:cat /proc/modules * 寫入控制參數:echo 1 > /proc/sys/net/ipv4/ip_forward `cat /proc/modules` echo "HelloWorld" > /dev/CharDrv_Joey cat /dev/CharDrv_Joey ## Miscellaneous Devices 在 Linux 裡,每個字元裝置通常需要: * 一個 major number(代表驅動程式類別) * 一個或多個 minor number(代表驅動中的不同裝置) * 搭配 /dev/ 下的裝置節點,例如 /dev/mydevice 但如果每個驅動都要註冊自己的 major number,就會讓 major number 管理變得複雜。 因此,Linux 提供了一個統一的機制:misc device framework。 這個機制讓多個小型字元裝置共用同一個 major number(10),每個裝置只需要申請一個不同的 minor number。 #### 主要特性: | 特性 | 說明 | | ---------------------------- | ------------------------------------------------------- | | **共用 major number = 10** | 所有 misc 裝置的 major number 都是固定的 10。 | | **簡化註冊流程** | 不需要呼叫 `register_chrdev_region()` 或 `cdev_init()`。 | | **由 kernel 管理 minor number** | 可以自動分配 minor number。 | | **適合小型、輕量的驅動** | 如 watchdog、rtc、led、random、uinput、vcs、loopback driver 等。 | #### Miscellaneous Device 驅動程式的範例 ```javascript = static struct miscdevice joey_misc_device = { .minor = 88, // 分配 minor number 為 88 .name = DEVICE_NAME, .fops = &fops }; static int __init miscDrv_init_module(void) { int retVal; printk(KERN_ALERT "Hello, this is joey's Miscellaneous Device Driver example\n"); retVal = misc_register(&joey_misc_device); if(retVal) printk(KERN_ERR "Failed to register misc device\n"); else printk(KERN_ALERT "joey_misc device registered\n"); return 0; } static void __exit miscDrv_exit_module(void) { printk(KERN_ALERT "GoodBye, Joey\n"); misc_deregister(&joey_misc_device); } ```