Linux ioctl 详解

1. ioctl 概述

ioctl(input/output control)是设备驱动程序中用来处理特殊设备控制命令的系统调用,用于实现设备特定的操作。

1.1 基本概念

  • 设备特定操作:无法用标准文件操作(read/write)实现的设备控制
  • 用户空间与内核空间通信:用户程序通过ioctl向驱动程序发送命令
  • 灵活性强:可以传递任意类型的数据结构

2. ioctl 基本用法

2.1 用户空间接口

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

2.2 简单示例

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>

// 简单的ioctl示例
int main() {
    int fd;
    const char *device = "/dev/mydevice";
    
    // 打开设备文件
    fd = open(device, O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }
    
    int value = 100;
    
    // 发送ioctl命令
    if (ioctl(fd, 0x1234, &value) < 0) {
        perror("ioctl");
        close(fd);
        return -1;
    }
    
    printf("ioctl command sent successfully\n");
    close(fd);
    return 0;
}

3. ioctl 命令编码

3.1 命令编码规范

Linux内核提供了标准的ioctl命令编码宏:

#include <linux/ioctl.h>

// ioctl命令编码宏
_IO(type, nr)              // 无参数命令
_IOR(type, nr, datatype)   // 读命令(从驱动读取数据)
_IOW(type, nr, datatype)   // 写命令(向驱动写入数据)
_IOWR(type, nr, datatype)  // 读写命令

3.2 命令编码示例

// ioctl_commands.h - 用户空间和内核空间共享的头文件
#ifndef IOCTL_COMMANDS_H
#define IOCTL_COMMANDS_H

#include <linux/ioctl.h>

// 魔术字 - 每个设备唯一
#define MYDEVICE_IOC_MAGIC 'k'

// 命令序列号
#define MYDEVICE_IOC_RESET        _IO(MYDEVICE_IOC_MAGIC, 0)
#define MYDEVICE_IOC_GET_STATUS   _IOR(MYDEVICE_IOC_MAGIC, 1, int)
#define MYDEVICE_IOC_SET_VALUE    _IOW(MYDEVICE_IOC_MAGIC, 2, int)
#define MYDEVICE_IOC_GET_VALUE    _IOR(MYDEVICE_IOC_MAGIC, 3, int)
#define MYDEVICE_IOC_SET_CONFIG   _IOW(MYDEVICE_IOC_MAGIC, 4, struct myconfig)
#define MYDEVICE_IOC_GET_CONFIG   _IOR(MYDEVICE_IOC_MAGIC, 5, struct myconfig)

// 最大命令号
#define MYDEVICE_IOC_MAXNR 5

// 配置结构体
struct myconfig {
    int speed;
    int mode;
    char name[32];
};

#endif

4. 完整的驱动示例

4.1 字符设备驱动 with ioctl

// mydevice_driver.c - 内核模块
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include "ioctl_commands.h"  // 共享的头文件

#define DEVICE_NAME "mydevice"
#define MAX_DEVICES 1

struct mydevice_data {
    struct cdev cdev;
    dev_t devno;
    int value;
    int status;
    struct myconfig config;
    struct mutex lock;  // 保护并发访问
};

static struct mydevice_data mydev;
static int device_open_count = 0;

static int mydevice_open(struct inode *inode, struct file *filp)
{
    mutex_lock(&mydev.lock);
    if (device_open_count) {
        mutex_unlock(&mydev.lock);
        return -EBUSY;
    }
    device_open_count++;
    mutex_unlock(&mydev.lock);
    
    filp->private_data = &mydev;
    return 0;
}

static int mydevice_release(struct inode *inode, struct file *filp)
{
    mutex_lock(&mydev.lock);
    device_open_count--;
    mutex_unlock(&mydev.lock);
    return 0;
}

static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct mydevice_data *dev = filp->private_data;
    int ret = 0;
    int tmp;
    struct myconfig config;
    
    // 检查命令类型
    if (_IOC_TYPE(cmd) != MYDEVICE_IOC_MAGIC) {
        pr_err("Invalid ioctl magic\n");
        return -ENOTTY;
    }
    
    if (_IOC_NR(cmd) > MYDEVICE_IOC_MAXNR) {
        pr_err("Invalid ioctl command number\n");
        return -ENOTTY;
    }
    
    mutex_lock(&dev->lock);
    
    switch (cmd) {
    case MYDEVICE_IOC_RESET:
        dev->value = 0;
        dev->status = 0;
        memset(&dev->config, 0, sizeof(dev->config));
        pr_info("Device reset\n");
        break;
        
    case MYDEVICE_IOC_GET_STATUS:
        tmp = dev->status;
        if (copy_to_user((int __user *)arg, &tmp, sizeof(tmp))) {
            ret = -EFAULT;
        }
        break;
        
    case MYDEVICE_IOC_SET_VALUE:
        if (copy_from_user(&tmp, (int __user *)arg, sizeof(tmp))) {
            ret = -EFAULT;
        } else {
            dev->value = tmp;
            pr_info("Value set to %d\n", dev->value);
        }
        break;
        
    case MYDEVICE_IOC_GET_VALUE:
        tmp = dev->value;
        if (copy_to_user((int __user *)arg, &tmp, sizeof(tmp))) {
            ret = -EFAULT;
        }
        break;
        
    case MYDEVICE_IOC_SET_CONFIG:
        if (copy_from_user(&config, (struct myconfig __user *)arg, sizeof(config))) {
            ret = -EFAULT;
        } else {
            dev->config = config;
            pr_info("Config set: speed=%d, mode=%d, name=%s\n",
                   dev->config.speed, dev->config.mode, dev->config.name);
        }
        break;
        
    case MYDEVICE_IOC_GET_CONFIG:
        if (copy_to_user((struct myconfig __user *)arg, &dev->config, sizeof(dev->config))) {
            ret = -EFAULT;
        }
        break;
        
    default:
        ret = -ENOTTY;
        break;
    }
    
    mutex_unlock(&dev->lock);
    return ret;
}

static const struct file_operations mydevice_fops = {
    .owner = THIS_MODULE,
    .open = mydevice_open,
    .release = mydevice_release,
    .unlocked_ioctl = mydevice_ioctl,
    .compat_ioctl = mydevice_ioctl,  // 兼容32位应用
};

static int __init mydevice_init(void)
{
    int ret;
    dev_t dev;
    
    // 分配设备号
    ret = alloc_chrdev_region(&dev, 0, MAX_DEVICES, DEVICE_NAME);
    if (ret < 0) {
        pr_err("Failed to allocate device number\n");
        return ret;
    }
    mydev.devno = dev;
    
    // 初始化互斥锁
    mutex_init(&mydev.lock);
    
    // 初始化设备数据
    mydev.value = 0;
    mydev.status = 0;
    memset(&mydev.config, 0, sizeof(mydev.config));
    strncpy(mydev.config.name, "default", sizeof(mydev.config.name) - 1);
    
    // 初始化cdev
    cdev_init(&mydev.cdev, &mydevice_fops);
    mydev.cdev.owner = THIS_MODULE;
    
    // 添加cdev到系统
    ret = cdev_add(&mydev.cdev, mydev.devno, MAX_DEVICES);
    if (ret) {
        pr_err("Failed to add cdev\n");
        unregister_chrdev_region(mydev.devno, MAX_DEVICES);
        return ret;
    }
    
    pr_info("MyDevice driver loaded, major=%d, minor=%d\n",
           MAJOR(mydev.devno), MINOR(mydev.devno));
    
    return 0;
}

static void __exit mydevice_exit(void)
{
    cdev_del(&mydev.cdev);
    unregister_chrdev_region(mydev.devno, MAX_DEVICES);
    mutex_destroy(&mydev.lock);
    pr_info("MyDevice driver unloaded\n");
}

module_init(mydevice_init);
module_exit(mydevice_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Example device driver with ioctl");

4.2 用户空间测试程序

// test_mydevice.c - 用户空间测试程序
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include "ioctl_commands.h"  // 共享的头文件

int main() {
    int fd;
    int ret;
    int value, status;
    struct myconfig config;
    
    printf("MyDevice ioctl test program\n");
    
    // 打开设备文件
    fd = open("/dev/mydevice", O_RDWR);
    if (fd < 0) {
        perror("open");
        printf("Make sure the device driver is loaded and device node exists\n");
        return -1;
    }
    
    printf("Device opened successfully\n");
    
    // 测试1: 重置设备
    printf("\n1. Resetting device...\n");
    if (ioctl(fd, MYDEVICE_IOC_RESET) < 0) {
        perror("ioctl RESET");
    } else {
        printf("Device reset successful\n");
    }
    
    // 测试2: 设置值
    printf("\n2. Setting value to 42...\n");
    value = 42;
    if (ioctl(fd, MYDEVICE_IOC_SET_VALUE, &value) < 0) {
        perror("ioctl SET_VALUE");
    } else {
        printf("Value set to %d\n", value);
    }
    
    // 测试3: 读取值
    printf("\n3. Reading value...\n");
    if (ioctl(fd, MYDEVICE_IOC_GET_VALUE, &value) < 0) {
        perror("ioctl GET_VALUE");
    } else {
        printf("Value read: %d\n", value);
    }
    
    // 测试4: 读取状态
    printf("\n4. Reading status...\n");
    if (ioctl(fd, MYDEVICE_IOC_GET_STATUS, &status) < 0) {
        perror("ioctl GET_STATUS");
    } else {
        printf("Status: %d\n", status);
    }
    
    // 测试5: 设置配置
    printf("\n5. Setting configuration...\n");
    memset(&config, 0, sizeof(config));
    config.speed = 9600;
    config.mode = 1;
    strncpy(config.name, "test_config", sizeof(config.name) - 1);
    
    if (ioctl(fd, MYDEVICE_IOC_SET_CONFIG, &config) < 0) {
        perror("ioctl SET_CONFIG");
    } else {
        printf("Configuration set:\n");
        printf("  Speed: %d\n", config.speed);
        printf("  Mode: %d\n", config.mode);
        printf("  Name: %s\n", config.name);
    }
    
    // 测试6: 读取配置
    printf("\n6. Reading configuration...\n");
    if (ioctl(fd, MYDEVICE_IOC_GET_CONFIG, &config) < 0) {
        perror("ioctl GET_CONFIG");
    } else {
        printf("Configuration read:\n");
        printf("  Speed: %d\n", config.speed);
        printf("  Mode: %d\n", config.mode);
        printf("  Name: %s\n", config.name);
    }
    
    close(fd);
    printf("\nTest completed\n");
    
    return 0;
}

5. 高级 ioctl 用法

5.1 可变长度数据传递

// 处理可变长度数据的ioctl示例

// 在共享头文件中添加
#define MYDEVICE_IOC_SEND_DATA    _IOW(MYDEVICE_IOC_MAGIC, 6, struct iovec)
#define MYDEVICE_IOC_RECV_DATA    _IOWR(MYDEVICE_IOC_MAGIC, 7, struct iovec)

// 在驱动中处理可变长度数据
case MYDEVICE_IOC_SEND_DATA: {
    struct iovec iov;
    void *buffer;
    
    if (copy_from_user(&iov, (struct iovec __user *)arg, sizeof(iov))) {
        ret = -EFAULT;
        break;
    }
    
    // 分配内核缓冲区
    buffer = kmalloc(iov.iov_len, GFP_KERNEL);
    if (!buffer) {
        ret = -ENOMEM;
        break;
    }
    
    // 拷贝用户数据到内核
    if (copy_from_user(buffer, iov.iov_base, iov.iov_len)) {
        ret = -EFAULT;
        kfree(buffer);
        break;
    }
    
    // 处理数据...
    pr_info("Received %zu bytes of data\n", iov.iov_len);
    
    kfree(buffer);
    break;
}

5.2 权限检查

#include <linux/cred.h>

static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    // 检查用户权限
    if (!capable(CAP_SYS_ADMIN)) {
        pr_warn("Permission denied for non-admin user\n");
        return -EPERM;
    }
    
    // 或者检查特定命令的权限
    switch (cmd) {
    case MYDEVICE_IOC_RESET:
        if (!capable(CAP_SYS_ADMIN)) {
            return -EPERM;
        }
        break;
    // ... 其他命令处理
    }
    
    return 0;
}

6. 内核与用户空间数据结构兼容性

6.1 处理32/64位兼容性

// 使用 compat_ioctl 处理32位应用
#ifdef CONFIG_COMPAT
static long mydevice_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    void __user *argp = compat_ptr(arg);
    
    switch (cmd) {
    case MYDEVICE_IOC_SET_CONFIG32:
        // 处理32位版本的数据结构
        break;
    // ... 其他兼容命令
    default:
        return mydevice_ioctl(filp, cmd, (unsigned long)argp);
    }
    
    return 0;
}
#endif

static const struct file_operations mydevice_fops = {
    .owner = THIS_MODULE,
    .open = mydevice_open,
    .release = mydevice_release,
    .unlocked_ioctl = mydevice_ioctl,
    .compat_ioctl = mydevice_compat_ioctl,
};

7. 调试和错误处理

7.1 ioctl 调试技巧

// 在驱动中添加调试信息
static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    // 记录ioctl调用
    pr_debug("ioctl called: cmd=0x%x, arg=0x%lx\n", cmd, arg);
    
    switch (cmd) {
    case MYDEVICE_IOC_SET_VALUE:
        pr_debug("SET_VALUE: arg=%ld\n", arg);
        break;
    // ... 其他命令
    }
    
    return ret;
}

// 在用户空间检查错误
void check_ioctl_error(int ret, const char *operation) {
    if (ret < 0) {
        switch (errno) {
        case ENOTTY:
            fprintf(stderr, "%s: Invalid ioctl command\n", operation);
            break;
        case EFAULT:
            fprintf(stderr, "%s: Bad address\n", operation);
            break;
        case EPERM:
            fprintf(stderr, "%s: Permission denied\n", operation);
            break;
        case EINVAL:
            fprintf(stderr, "%s: Invalid argument\n", operation);
            break;
        default:
            perror(operation);
            break;
        }
    }
}

8. 最佳实践

8.1 ioctl 设计准则

// 1. 使用唯一的魔术字
#define MYDEVICE_IOC_MAGIC 'k'  // 确保不与其他驱动冲突

// 2. 验证命令参数
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));
}

// 3. 使用适当的锁机制
mutex_lock(&dev->lock);
// 处理命令
mutex_unlock(&dev->lock);

// 4. 处理并发访问
static atomic_t ioctl_in_use = ATOMIC_INIT(0);

if (atomic_cmpxchg(&ioctl_in_use, 0, 1) != 0) {
    return -EBUSY;
}
// 处理命令
atomic_set(&ioctl_in_use, 0);

// 5. 提供清晰的错误码
- ENOTTY:  无效的命令
- EFAULT:  错误的内存地址
- EINVAL:  无效的参数
- EPERM:   权限不足
- EBUSY:   设备忙

8.2 安全性考虑

// 1. 验证用户输入
case MYDEVICE_IOC_SET_CONFIG:
    if (copy_from_user(&config, (void __user *)arg, sizeof(config))) {
        return -EFAULT;
    }
    
    // 验证配置参数
    if (config.speed < 0 || config.speed > 1000000) {
        return -EINVAL;
    }
    
    // 确保字符串以null结尾
    config.name[sizeof(config.name) - 1] = '\0';
    break;

// 2. 限制缓冲区大小
#define MAX_DATA_SIZE 4096

case MYDEVICE_IOC_SEND_DATA:
    if (data_size > MAX_DATA_SIZE) {
        return -E2BIG;
    }

ioctl是Linux设备驱动开发中非常重要的机制,它提供了设备特定操作的灵活接口。正确使用ioctl需要仔细设计命令编码、处理并发访问、确保安全性,并提供清晰的错误处理。