裝置檔案是使用者空間存取硬體的介面。一般資料傳輸透過 read/write 即可,但裝置控制參數(如序列埠鮑率、磁碟幾何資訊)無法套用一般檔案語意。Linux 採用 ioctl(I/O Control) 機制,提供標準化、可擴展的裝置控制介面。本文解析 ioctl 運作原理,並以完整字元裝置驅動範例示範實作細節。
fd、請求碼 request、可變參數 argp(通常為結構體指標)。_IO、_IOR、_IOW、_IOWR 協助產生唯一請求碼,避免衝突。chardev.h)必須同時被使用者程式與核心模組包含,確保雙方對請求碼含義一致。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;
}
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;
}
ioctl 原型改為 unlocked_ioctl,不再持有 Big Kernel Lock。compat_ioctl 處理 32 位元使用者程式在 64 位元核心上的 ioctl 呼叫。-ENOTTY 表示不支援的請求碼,-EFAULT 表示使用者緩衝區位址無效。ioctl 以編碼請求碼統一裝置控制介面,字元裝置驅動透過 file_operations 掛載各操作函式,核心與使用者空間資料交換靠 copy_to/from_user 完成。掌握此模式,即可開發各類硬體控制介面,從 GPIO、I2C 到複雜 GPU/網卡驅動皆遵循此架構。