萬盛學電腦網

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

Linux ioctl 系統呼叫深度解析:用戶空間與驅動程式交互的核心機制

ioctl (I/O Control) 是字元裝置驅動程式與用戶空間交換控制資訊的標準介面。不同於 read/write 傳輸資料流,ioctl 處理裝置專屬的「控制命令」:設定鮑率、啟動 DMA、查詢感測器狀態、配置 GPIO 等。

函數原型與參數

// 用戶空間 (glibc 包裝)
#include 
int ioctl(int fd, unsigned long request, ...);

// 核心空間 (驅動實作)
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg);
// 舊版
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
  • fd:open() 回傳的檔案描述符
  • request/cmd:編碼後的命令碼 (包含類型、序號、方向、資料大小)
  • arg:使用者空間緩衝區位址 (輸入/輸出/雙向)

命令碼編碼結構 (Linux Kernel 規範)

32 位元整數編碼 (定義於 asm-generic/ioctl.h):

| 類型 (8 bits) | 序號 (8 bits) | 方向 (2 bits) | 大小 (14 bits) |
|    'V' (0x56) |     0x01      |   _IOC_WRITE  |   sizeof(struct) |
  • 類型 (Magic Number):單一字元區分裝置類別 (如 'V' 視訊、'K' 鍵盤、'M' MTD)
  • 序號:同類型下 0-255 區分不同命令
  • 方向:_IOC_NONE(0) _IOC_READ(1) _IOC_WRITE(2) _IOC_READ|_IOC_WRITE(3) — 從核心視角 (READ = 核心→用戶)
  • 大小:參數結構體大小,核心驗證緩衝區邊界

核心提供的編碼巨集 (必須使用,勿手寫數字)

// 無參數
#define MY_IOCTL_RESET _IO('M', 0)

// 讀取 (核心→用戶)
#define MY_IOCTL_GET_STATUS _IOR('M', 1, struct my_status)

// 寫入 (用戶→核心)
#define MY_IOCTL_SET_CONFIG _IOW('M', 2, struct my_config)

// 雙向
#define MY_IOCTL_EXCHANGE _IOWR('M', 3, struct my_exchange)

驅動程式實作範例

// my_char_drv.c
#include 
#include "my_ioctl_cmds.h"  // 共用命令碼定義

static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    struct my_device *dev = filp->private_data;
    int ret = 0;

    // 驗證魔數與序號 (防止誤傳他裝置命令)
    if (_IOC_TYPE(cmd) != 'M') return -ENOTTY;
    if (_IOC_NR(cmd) > MY_IOCTL_MAXNR) return -ENOTTY;

    // 驗證資料大小 (防止緩衝區溢位)
    if (_IOC_DIR(cmd) & _IOC_READ)
        ret = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
        ret = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
    if (ret) return -EFAULT;

    switch (cmd) {
        case MY_IOCTL_RESET:
            reset_device(dev);
            break;
        case MY_IOCTL_GET_STATUS:
            if (copy_to_user((void __user *)arg, &dev->status, sizeof(dev->status)))
                return -EFAULT;
            break;
        case MY_IOCTL_SET_CONFIG:
            if (copy_from_user(&dev->config, (void __user *)arg, sizeof(dev->config)))
                return -EFAULT;
            apply_config(dev);
            break;
        default:
            return -ENOTTY;  // 未知命令
    }
    return 0;
}

static struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = my_ioctl,
    .compat_ioctl = compat_ptr_ioctl,  // 32-bit 相容
    // ... open, release, read, write
};

用戶空間呼叫範例

#include 
#include 
#include "my_ioctl_cmds.h"

int main() {
    int fd = open("/dev/mydev", O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    struct my_config cfg = { .rate = 115200, .mode = 1 };
    if (ioctl(fd, MY_IOCTL_SET_CONFIG, &cfg) < 0)
        perror("ioctl SET_CONFIG");

    struct my_status st;
    if (ioctl(fd, MY_IOCTL_GET_STATUS, &st) < 0)
        perror("ioctl GET_STATUS");
    printf("Status: %d\n", st.value);

    close(fd);
    return 0;
}

ioctl 流程圖

關鍵設計原則

  1. 命令碼唯一性:魔數+序號全域唯一,避免誤操作他裝置
  2. 方向正確性:_IOC_READ = 核心→用戶 (copy_to_user),_IOC_WRITE = 用戶→核心 (copy_from_user)
  3. 大小驗證:核心自動檢查 _IOC_SIZE,防止緩衝區溢位
  4. 存取權檢查:access_ok() 驗證用戶空間位址合法
  5. 32/64 位元相容:實作 compat_ioctl 處理結構體大小差異
  6. 回傳值規範:成功 0,錯誤負錯誤碼 (-EFAULT, -ENOTTY, -EINVAL)

常見裝置 ioctl 範例

裝置類型常用 ioctl用途
終端機 (tty)TCGETS/TCSETS, TIOCGWINSZ取得/設定終端屬性、視窗大小
網路介面SIOCGIFCONF, SIOCSIFADDR介面配置、IP 位址設定
區塊裝置BLKGETSIZE64, BLKFLSBUF取得磁碟大小、刷新快取
音效 (ALSA)SNDRV_CTL_IOCTL_*混音器控制、PCM 參數
視訊 (V4L2)VIDIOC_G_FMT, VIDIOC_S_FMT格式協商、緩衝區管理

除錯與追蹤

# 追蹤 ioctl 呼叫
strace -e trace=ioctl ./myapp

# 核心動態追蹤 (需 debugfs)
echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_ioctl/enable
cat /sys/kernel/debug/tracing/trace_pipe

ioctl 雖介面簡單,但命令碼編碼、資料複製方向、版本相容、安全檢查環環相扣。驅動開發者必須精通 _IO/_IOR/_IOW/_IOWR 巨集與 copy_to/from_user 機制,才能寫出健壯、安全的字元裝置驅動。

copyright © 萬盛學電腦網 all rights reserved