## 字元裝置 (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);
}
```