萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 作業系統 >  >> Linux 教學

Linux 核心與裝置檔案對話機制:ioctl 與字元裝置驅動實作

裝置檔案是使用者空間存取硬體的介面。一般資料傳輸透過 read/write 即可,但裝置控制參數(如序列埠鮑率、磁碟幾何資訊)無法套用一般檔案語意。Linux 採用 ioctl(I/O Control) 機制,提供標準化、可擴展的裝置控制介面。本文解析 ioctl 運作原理,並以完整字元裝置驅動範例示範實作細節。

ioctl 核心概念

  • 三大參數:檔案描述子 fd、請求碼 request、可變參數 argp(通常為結構體指標)。
  • 請求碼編碼:包含裝置主號、ioctl 類型(讀/寫/雙向/無參數)、序號、參數大小。核心巨集 _IO、_IOR、_IOW、_IOWR 協助產生唯一請求碼,避免衝突。
  • 頭檔共享:定義請求碼的頭檔(如 chardev.h)必須同時被使用者程式與核心模組包含,確保雙方對請求碼含義一致。
  • 官方註冊:正式專案應向核心社群申請專屬 ioctl 號碼範圍,參考 Documentation/ioctl-number.txt。

字元裝置驅動完整範例 chardev.c

範例實作一個虛擬字元裝置 char_dev,支援 open、release、read、write、ioctl 五大操作,展示核心模組標準架構。

模組骨架與裝置狀態

#define DEVICE_NAME "char_dev"
#define BUF_LEN 80
static int Device_Open = 0;
static char Message[BUF_LEN];
static char *Message_Ptr;

開啟與釋放:防止併發存取

static int device_open(struct inode *inode, struct file *file) {
    if (Device_Open) return -EBUSY;
    Device_Open++;
    Message_Ptr = Message;
    MOD_INC_USE_COUNT;  // 增加模組引用計數
    return SUCCESS;
}

static int device_release(struct inode *inode, struct file *file) {
    Device_Open--;
    MOD_DEC_USE_COUNT;
    return 0;
}

讀寫:核心與使用者空間資料搬移

核心無法直接存取使用者空間緩衝區,必須使用 put_user/get_user(或 copy_to_user/copy_from_user)跨越保護邊界。

static ssize_t device_read(struct file *file, char __user *buffer, size_t length, loff_t *offset) {
    int bytes_read = 0;
    if (*Message_Ptr == 0) return 0; // EOF
    while (length && *Message_Ptr) {
        put_user(*(Message_Ptr++), buffer++);
        length--; bytes_read++;
    }
    return bytes_read;
}

static ssize_t device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) {
    int i;
    for (i = 0; i < length && i < BUF_LEN; i++)
        get_user(Message[i], buffer + i);
    Message_Ptr = Message;
    return i;
}

ioctl 處理:依請求碼分派功能

static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    switch (cmd) {
        case IOCTL_SET_MSG:   // 設定訊息
            // copy_from_user 處理 arg 指向的緩衝區
            break;
        case IOCTL_GET_MSG:   // 取得訊息
            // copy_to_user 處理
            break;
        default:
            return -ENOTTY;
    }
    return SUCCESS;
}

檔案操作結構與模組註冊

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = device_read,
    .write = device_write,
    .unlocked_ioctl = device_ioctl,
    .open = device_open,
    .release = device_release,
};

static int __init chardev_init(void) {
    major = register_chrdev(0, DEVICE_NAME, &fops);
    // 建立 /dev/char_dev 節點(配合 udev 或手動 mknod)
    return 0;
}
module_init(chardev_init);
module_exit(chardev_exit);

使用者空間測試程式

#include "chardev.h"
int main() {
    int fd = open("/dev/char_dev", O_RDWR);
    ioctl(fd, IOCTL_SET_MSG, "Hello Kernel");
    char buf[BUF_LEN];
    ioctl(fd, IOCTL_GET_MSG, buf);
    printf("Device says: %s\n", buf);
    close(fd);
    return 0;
}

關鍵提醒

  • 核心 2.6 後 ioctl 原型改為 unlocked_ioctl,不再持有 Big Kernel Lock。
  • 64 位元相容性:compat_ioctl 處理 32 位元使用者程式在 64 位元核心上的 ioctl 呼叫。
  • 錯誤碼:-ENOTTY 表示不支援的請求碼,-EFAULT 表示使用者緩衝區位址無效。

總結

ioctl 以編碼請求碼統一裝置控制介面,字元裝置驅動透過 file_operations 掛載各操作函式,核心與使用者空間資料交換靠 copy_to/from_user 完成。掌握此模式,即可開發各類硬體控制介面,從 GPIO、I2C 到複雜 GPU/網卡驅動皆遵循此架構。

copyright © 萬盛學電腦網 all rights reserved