---
# System prepended metadata

title: Linux Char Driver
tags: [Char Driver]

---

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

```

