【5】字符设备ioctl操作

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。

ioctl 的定义

用户空间的ioctl是一个可变参数函数,系统调用原型:

/*
 * fd: 用户程序打开设备时使用open函数返回的文件标示符
 * cmd: 用户程序对设备的控制命令
 */
int ioctl(int fd, unsigned long cmd, ...);

驱动程序的ioctl原型是:

/*
 * filep: 待操作的设备文件的file结构体指针
 * cmd: 用户空间传递的命令
 * arg: 用户空间的数据,可能是一个地址,或者一个数值,或者啥也没有,取决于用户空间的ioctl最后的可变参数
 */
long (*unlocked_ioctl) (struct file* filep, unsigned int cmd, unsigned long arg);

在驱动程序中实现的ioctl函数体内,实际上是可以通过一个switch{case}的结构,将每一个case对应一个命令码(cmd),做出一些相应的操作。简言之就是用户空间通过调用ioctl系统调用传递cmd参数到内核空间,通过cmd的一一对应来决定命令需要进行的操作是什么

ioctl CMD的结构与操作宏

结构
在这里插入图片描述

  • 设备类型(type):幻数,一个字母
  • 序列号(number):代表改设备的第几个指令
  • 方向(direction):如果命令涉及到数据的传输,则该字段定义数据的传输方向。可以使用的值包括_IOC_NONE(没有数据传输)、_IOC_READ、_IOC_WRITE、_IOC_READ|_IOC_WRITE(双向数据传输)。数据传输是从应用程序的角度看的,也就是说IOC_READ意味着从设备中读取数据,对应驱动程序应该是往用户空间写入数据
  • 数据尺寸(size):所涉及到的用户数据的大小。

规则
命令编号有着确定的规则:命令号要在系统范围内唯一。否则可能会造成错误的匹配,进而完成意想不到的错误操作。为了避免这种错误,Linux内核约定了方法为驱动程序选择ioctl编号,首先应该看看include/asm/ioctl.hDocumentation/ioctl-number.txt这两个文件。ioctl-number.txt文件中罗列了内核所使用的幻数,在选择自己的幻数的时候要避免和内核冲突。

操作宏
内核<linux/ioctl.h>头文件中定义了一组构造命令编号的宏。

_IO(type, nr)             // 构造无参数的命令编号,即没有数据传递的命令
_IOR(type, nr, datatype)  // 构造从驱动程序中读取数据的命令编号
_IOW(type, nr, datatype)  // 用于向驱动中写入数据的命令
_IOWR(type, nr, datatype) // 用于双向传输

type和nr位字段通过参数传入,size位字段通过对datatype参数取sizeof获得。
同样也定义了一组解开位字段的宏:

_IOC_DIR(cmd)   // 提取方向
_IOC_TYPE(cmd)  // 提取幻数
_IOC_NR(cmd)    // 提取序数
_IOC_SIZE(cmd)  // 提取数据大小

对于direction字段,也有定义对应的宏:

_IOC_NONE              // 值为0,没有数据传输
_IOC_READ              // 值为1,从设备驱动读取数据
_IOC_WRITE             // 值为2,向设备驱动写入数据
_IOC_READ | _IOC_WRITE // 双向传输

CMD 的构建

创建幻数
首先要创建幻数,我们打开ioctl-number.txt文件,先看一个很奇怪的疑问。
‘c’ all linux/cm4000_cs.h conflict!
‘c’ 00-7F linux/comstats.h conflict!
‘c’ 00-7F linux/coda.h conflict!
冲突啊!!! 幻数的本意是为了避免在错误的设备中使用正确的命令,所以不是绝对不能冲突的东西。另外就是两个驱动不同时出现在内核中,也就不会出现问题。其实很好理解,内核那么多驱动程序,如果全部不冲突,也不好做到啊。但是要尽可能的不要冲突

创建cmd

#define CDEV_IOC_MAGIC 'x'
#define CDEV_IOC_CLEAN _IO(CDEV_IOC_MAGIC, 0)
#define CDEV_IOC_WRITE _IOW(CDEV_IOC_MAGIC, 1, int)
#define CDEV_IOC_READ _IOR(CDEV_IOC_MAGIC, 2, int)
#define CDEV_IOC_WR _IOWR(CDEV_IOC_MAGIC, 3, charp)

ioctl 应用示例

填充unlocked_ioctl函数,可以按照如下步骤进行:

  1. 利用_IOC_TYPE检测幻数有效性
  2. 利用access_ok来检测arg是否符合要求
  3. 利用switch case结构,定义每个cmd的处理

驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define CDEV_IOC_MAGIC 'x'
#define CDEV_IOC_CLEAN _IO(CDEV_IOC_MAGIC, 0)
#define CDEV_IOC_WRITE _IOW(CDEV_IOC_MAGIC, 1, int)
#define CDEV_IOC_READ _IOR(CDEV_IOC_MAGIC, 2, int)
#define CDEV_IOC_WR _IOWR(CDEV_IOC_MAGIC, 3, int)

static unsigned int major = 222;
static unsigned int minor = 0;
static dev_t dev_no;
static struct class* cls = NULL;
static struct device* cls_dev = NULL;
static int k_value = 1314;

static long hello_ioctl(struct file* filep, unsigned int cmd, unsigned long arg) {
    int result;

    printk("hello_ioctl");
    if (_IOC_TYPE(cmd) != CDEV_IOC_MAGIC) {
        printk("magic num error!\n");
        return -ENODEV;
    }

    if (!access_ok((void __user *)arg, _IOC_SIZE(cmd))) {
        printk("size error!\n");
        return -ENODEV;
    }

    switch (cmd) {
        case CDEV_IOC_CLEAN: {
            printk("== CDEV_IOC_CLEAN ==\n");
            k_value = 1314;
        } break;
        case CDEV_IOC_WRITE: {
            printk("== CDEV_IOC_WRITE ==\n");
            result = copy_from_user(&k_value, (int*)arg, sizeof(int));
            printk("== CDEV_IOC_WRITE == k_value: %d\n", k_value);
        } break;
        case CDEV_IOC_READ: {
            printk("== CDEV_IOC_READ ==\n");
            result = copy_to_user((int*)arg, &k_value, sizeof(int));
        } break;
        case CDEV_IOC_WR: {
            printk("== CDEV_IOC_WR ==\n");
            result = get_user(k_value, (int*)arg);
            printk("== CDEV_IOC_WR == k_value: %d\n", k_value);

            k_value = 957;
            result = put_user(k_value, (int*)arg);
        } break;
        default: {
            printk("unsupport cmd:0x%x\n", cmd);
            return -EINVAL;
        } break;
    }

    return 0;
}

static struct file_operations hello_ops = {
    .unlocked_ioctl = hello_ioctl,
};

static int hello_init(void) {
    int result = 0;

    printk("hello init enter!");

    dev_no = MKDEV(major, minor);

    result = register_chrdev(major, "hello", &hello_ops);
    if (result < 0) {
        printk("register chrdev failed! result: %d\n", result);
        return result;
    }

    cls = class_create(THIS_MODULE, "hello_cls");
    if (IS_ERR(cls) != 0) {
        printk("class create failed!");
        result = PTR_ERR(cls);
        goto err_1;
    }

    cls_dev = device_create(cls, NULL, dev_no, NULL, "hello_dev");
    if (IS_ERR(cls_dev) != 0) {
        printk("device create failed!");
        result = PTR_ERR(cls_dev);
        goto err_2;
    }

    return 0;

err_2:
    class_destroy(cls);
err_1:
    unregister_chrdev(major, "hello");
    return result;
}

static void hello_exit(void) {
    printk("hello exit enter!");
    device_destroy(cls, dev_no);
    class_destroy(cls);
    unregister_chrdev(major, "hello");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

用户空间代码

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

#define CDEV_IOC_MAGIC 'x'
#define CDEV_IOC_CLEAN _IO(CDEV_IOC_MAGIC, 0)
#define CDEV_IOC_WRITE _IOW(CDEV_IOC_MAGIC, 1, int)
#define CDEV_IOC_READ _IOR(CDEV_IOC_MAGIC, 2, int)
#define CDEV_IOC_WR _IOWR(CDEV_IOC_MAGIC, 3, int)

int main(int argc, char const *argv[]) {
    int value = 9999;
    char buffer[32] = {0};

    int fd = open("/dev/hello_dev", O_RDWR);

    if (ioctl(fd, CDEV_IOC_CLEAN) < 0) {
        printf("ioctl CDEV_IOC_CLEAN failed!\n");
        return 0;
    }

    if (ioctl(fd, CDEV_IOC_READ, &value) < 0) {
        printf("ioctl CDEV_IOC_READ failed!\n");
        return 0;
    }
    printf("read k_value = %d\n", value); // 1314

    value = 369;
    if (ioctl(fd, CDEV_IOC_WRITE, &value) < 0) {
        printf("ioctl CDEV_IOC_WRITE failed!\n");
        return 0;
    }
    if (ioctl(fd, CDEV_IOC_READ, &value) < 0) {
        printf("ioctl CDEV_IOC_READ failed!\n");
        return 0;
    }
    printf("write end, read k_value = %d\n", value); // 369

    value = 9090;
    if (ioctl(fd, CDEV_IOC_WR, &value) < 0) {
        printf("ioctl CDEV_IOC_WR failed!\n");
        return 0;
    }
    printf("read+write k_value = %d\n", value);

    close(fd);
    return 0;
}

结果
在这里插入图片描述

利用arg做消息分发

可以将arg定义为结构体,然后利用arg结构体中的内容做消息分发。

struct ioctl_transfer {
    int type;
    int value;
};

#define CDEV_IOC_MAGIC 'x'
#define CDEV_MSGSIZE(N) ((((N) * (sizeof(struct ioctl_transfer))) < (1 << _IOC_SIZEBITS)) ? ((N) * (sizeof(struct ioctl_transfer))) : 0)
#define CDEV_IOCTL_MESSAGE(N) _IOWR(CDEV_IOC_MAGIC, 0, char[CDEV_MSGSIZE(N)])

示例中根据ioctl_transfer中type的不同,做不同处理,type为X2_MAGNIFICATION则将value乘以2,type为X10_MAGNIFICATION则乘以10,代码如下。
驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define CDEV_IOC_MAGIC 'x'

struct ioctl_transfer {
    int type;
    int value;
};

#define X2_MAGNIFICATION 0x00F1
#define X10_MAGNIFICATION 0x00F2

static unsigned int major = 222;
static unsigned int minor = 0;
static dev_t dev_no;
static struct class* cls = NULL;
static struct device* cls_dev = NULL;

static long hello_ioctl(struct file* filep, unsigned int cmd, unsigned long arg) {
    int result;
    struct ioctl_transfer arg_data;

    printk("hello_ioctl cmd:%u\n", cmd);
    if (_IOC_TYPE(cmd) != CDEV_IOC_MAGIC) {
        printk("magic num error!\n");
        return -ENODEV;
    }

    if (!access_ok((void __user *)arg, _IOC_SIZE(cmd))) {
        printk("size error!\n");
        return -ENODEV;
    }

    if (copy_from_user(&arg_data, (void __user *)arg, sizeof(struct ioctl_transfer))) {
        printk("get arg data error!\n");
        return -ENODEV;
    }

    printk("arg.type: 0x%x, arg.value: %d\n", arg_data.type, arg_data.value);
    switch (arg_data.type) {
        case X2_MAGNIFICATION: {
            arg_data.value *= 2;
            result = copy_to_user((void __user *)arg, &arg_data, sizeof(struct ioctl_transfer));
        } break;
        case X10_MAGNIFICATION: {
            arg_data.value *= 10;
            result = copy_to_user((void __user *)arg, &arg_data, sizeof(struct ioctl_transfer));
        } break;
        default: {
            return -ENODEV;
        } break;
    }
    return result;
}

static struct file_operations hello_ops = {
    .unlocked_ioctl = hello_ioctl,
};

static int hello_init(void) {
    int result = 0;

    printk("hello init enter!");

    dev_no = MKDEV(major, minor);

    result = register_chrdev(major, "hello", &hello_ops);
    if (result < 0) {
        printk("register chrdev failed! result: %d\n", result);
        return result;
    }

    cls = class_create(THIS_MODULE, "hello_cls");
    if (IS_ERR(cls) != 0) {
        printk("class create failed!");
        result = PTR_ERR(cls);
        goto err_1;
    }

    cls_dev = device_create(cls, NULL, dev_no, NULL, "hello_dev");
    if (IS_ERR(cls_dev) != 0) {
        printk("device create failed!");
        result = PTR_ERR(cls_dev);
        goto err_2;
    }

    return 0;

err_2:
    class_destroy(cls);
err_1:
    unregister_chrdev(major, "hello");
    return result;
}

static void hello_exit(void) {
    printk("hello exit enter!");
    device_destroy(cls, dev_no);
    class_destroy(cls);
    unregister_chrdev(major, "hello");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

应用层代码

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

struct ioctl_transfer {
    int type;
    int value;
};

#define X2_MAGNIFICATION 0x00F1
#define X10_MAGNIFICATION 0x00F2

#define CDEV_IOC_MAGIC 'x'
#define CDEV_MSGSIZE(N) ((((N) * (sizeof(struct ioctl_transfer))) < (1 << _IOC_SIZEBITS)) ? ((N) * (sizeof(struct ioctl_transfer))) : 0)
#define CDEV_IOCTL_MESSAGE(N) _IOWR(CDEV_IOC_MAGIC, 0, char[CDEV_MSGSIZE(N)])

int main(int argc, char const *argv[]) {
    int fd = open("/dev/hello_dev", O_RDWR);

    static struct ioctl_transfer transfer1 = {
        .type = X2_MAGNIFICATION,
        .value = 9,
    };
    ioctl(fd, CDEV_IOCTL_MESSAGE(1), &transfer1);
    printf("transfer1 type: 0x%x, value: %d\n", transfer1.type, transfer1.value);

    static struct ioctl_transfer transfer2 = {
        .type = X10_MAGNIFICATION,
        .value = 30,
    };
    ioctl(fd, CDEV_IOCTL_MESSAGE(1), &transfer2);
    printf("transfer2 type: 0x%x, value: %d\n", transfer2.type, transfer2.value);

    close(fd);
    return 0;
}

结果:
在这里插入图片描述
在这里插入图片描述

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值