備中的 ioctl 接口設(shè)計(jì)規(guī)范與 32/64 位兼容性避坑實(shí)戰(zhàn))
字符設(shè)備中的 ioctl 接口設(shè)計(jì)規(guī)范與 32/64 位兼容性避坑實(shí)戰(zhàn)在 Linux 設(shè)備驅(qū)動開發(fā)中ioctlInput/Output Control是最靈活、最常用但同時(shí)也是最容易滋生安全漏洞與兼容性災(zāi)難的系統(tǒng)調(diào)用接口。當(dāng)驅(qū)動工程師在 64 位 Linux 主機(jī)上寫完unlocked_ioctl并在 64 位測試程序中驗(yàn)證通過后往往以為萬事大吉。然而一旦將驅(qū)動移植到 64 位 ARM 內(nèi)核 32 位 Android/嵌入式用戶空間Multi-lib 架構(gòu)或者在 x86_64 服務(wù)器上運(yùn)行 32 位的遺留測試工具時(shí)系統(tǒng)便會神秘地返回-ENOTTYInappropriate ioctl for device或直接解引用空指針導(dǎo)致 Kernel Panic。寫出工業(yè)級的驅(qū)動代碼必須嚴(yán)格遵循內(nèi)核的ioctl編號編碼規(guī)范并妥善處理32/64 位數(shù)據(jù)對齊與compat_ioctl兼容層。一、 ioctl 命令編碼規(guī)范解構(gòu) 32 位 Command 字段很多初學(xué)者喜歡在頭文件里隨意寫#define CMD_SET_LED 1這種裸數(shù)字命令極易與系統(tǒng)已有的其他驅(qū)動沖突且無法攜帶方向與大小信息。Linux 內(nèi)核強(qiáng)制推行基于 32 位位域的命令編碼宏---------------------------------------------------------------- | dir (2 bits) | size (14 bits)| type (8 bits) | nr (8 bits) | ---------------------------------------------------------------- 31 30 29 16 15 8 7 0type8 位魔數(shù) Magic Number通常為一個(gè) ASCII 字符如k,v由內(nèi)核官方分配或選擇未被占用的字符防止命令串?dāng)_nr8 位序號碼該驅(qū)動內(nèi)的命令順序遞增編號0, 1, 2...size14 位數(shù)據(jù)長度傳輸結(jié)構(gòu)體的字節(jié)大小sizeof(type)dir2 位方向標(biāo)志_IOC_NONE無參數(shù)、_IOC_READ內(nèi)核向用戶態(tài)寫、_IOC_WRITE用戶態(tài)向內(nèi)核寫、_IOC_READ | _IOC_WRITE。標(biāo)準(zhǔn)命令定義范式#ifndef _MY_DEVICE_IOCTL_H #define _MY_DEVICE_IOCTL_H #include linux/ioctl.h #include linux/types.h #define MY_DEV_MAGIC M // 定義具有良好類型對齊的數(shù)據(jù)結(jié)構(gòu) struct mydev_config { __u32 timeout_ms; __u32 flags; __u64 buffer_phys_addr; // 顯式使用固定寬度類型 }; #define MYDEV_CMD_RESET _IO(MY_DEV_MAGIC, 0x01) #define MYDEV_CMD_GET_STATUS _IOR(MY_DEV_MAGIC, 0x02, __u32) #define MYDEV_CMD_SET_CONFIG _IOW(MY_DEV_MAGIC, 0x03, struct mydev_config) #endif二、 32/64 位混合架構(gòu)下的兼容性陷阱當(dāng) 64 位內(nèi)核運(yùn)行 32 位用戶態(tài)程序時(shí)兩者在 C 語言基本類型的尺寸和對齊上存在根本分歧-------------------------------------------------------- | C 數(shù)據(jù)類型 | 32-bit ABI (x86) | 64-bit ABI (x86_64)| -------------------------------------------------------- | long | 4 字節(jié) | 8 字節(jié) | | void* (指針) | 4 字節(jié) | 8 字節(jié) | | uint64_t 對齊方式 | 4 字節(jié)對齊 | 8 字節(jié)對齊 | --------------------------------------------------------致命場景結(jié)構(gòu)體 Padding 差異如果結(jié)構(gòu)體定義如下struct bad_struct { int cmd; // 4 字節(jié) long long data;// 在 32位系統(tǒng)按 4字節(jié)對齊(總長12)在 64位系統(tǒng)按 8字節(jié)對齊(總長16) };此時(shí)32 位用戶態(tài)計(jì)算出的sizeof(bad_struct)是 12編碼出的_IOW命令碼與 64 位內(nèi)核sizeof為 16計(jì)算出的命令碼完全不同。內(nèi)核在switch(cmd)時(shí)無法匹配直接返回-ENOTTY。三、 驅(qū)動實(shí)現(xiàn)unlocked_ioctl 與 compat_ioctl 雙通道為了實(shí)現(xiàn)無縫兼容字符驅(qū)動必須同時(shí)注冊.unlocked_ioctl和.compat_ioctl兩個(gè)函數(shù)指針。#include linux/module.h #include linux/fs.h #include linux/uaccess.h #include linux/compat.h #include my_device_ioctl.h static long mydev_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { // 校驗(yàn)?zāi)?shù)與序號碼 if (_IOC_TYPE(cmd) ! MY_DEV_MAGIC) return -ENOTTY; switch (cmd) { case MYDEV_CMD_RESET: // 執(zhí)行重置硬件邏輯 return 0; case MYDEV_CMD_SET_CONFIG: { struct mydev_config cfg; if (copy_from_user(cfg, (void __user *)arg, sizeof(cfg))) { return -EFAULT; } // 應(yīng)用配置... return 0; } default: return -ENOTTY; } } #ifdef CONFIG_COMPAT // 32 位兼容結(jié)構(gòu)體 (若存在指針或 long 類型時(shí)需要轉(zhuǎn)換) struct compat_mydev_config { compat_uptr_t user_ptr; // 32 位兼容指針 __u32 flags; }; static long mydev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { // 若結(jié)構(gòu)體使用了嚴(yán)格對齊的 __u32/__u64無指針差異可直接透傳 return mydev_unlocked_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); } #endif static const struct file_operations mydev_fops { .owner THIS_MODULE, .unlocked_ioctl mydev_unlocked_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl mydev_compat_ioctl, #endif };四、 工業(yè)級 ioctl 設(shè)計(jì)的五條鐵律規(guī)則錯(cuò)誤做法?正確規(guī)范?類型寬度使用int,long,size_t顯式使用linux/types.h的__u32,__u64指針嵌套在結(jié)構(gòu)體內(nèi)部嵌套二級用戶態(tài)指針盡量拍平成單塊連續(xù)內(nèi)存或分步發(fā)起獨(dú)立 ioctl內(nèi)存填充依賴編譯器自動插入填充字節(jié)顯式編寫__u32 __reserved;明確字節(jié)對齊空指針安全直接解引用(struct config*)arg必須通過copy_from_user/copy_to_user未定義命令返回-EINVAL必須返回-ENOTTY以便 glibc 與內(nèi)核做兼容探測將每一個(gè)系統(tǒng)調(diào)用接口都當(dāng)成公開網(wǎng)絡(luò) API 來嚴(yán)謹(jǐn)設(shè)計(jì)是保證驅(qū)動在跨平臺、跨架構(gòu)演進(jìn)中十年不崩塌的基本工程素養(yǎng)。