compat_ioctl 对于 unsigned long long 数据类型不能正常工作

Posted

技术标签:

【中文标题】compat_ioctl 对于 unsigned long long 数据类型不能正常工作【英文标题】:compat_ioctl not working properly for unsigned long long data type 【发布时间】:2019-09-01 01:47:51 【问题描述】:

我正在学习设备驱动程序中的 ioctl 功能,

file_operations 中有一个函数指针 .compat_ioctl,它允许 32 位进程在 64 位机器中使用 ioctl。

以下是我的驱动程序代码:

#ifndef __IOCTL_CMD_H
#define __IOCTL_CMD_H

#define MSG_MAGIC_NUMBER    0x21

#define MSG_IOCTL_GET_LENGTH    _IOR(MSG_MAGIC_NUMBER, 1, unsigned int)

#define MSG_IOCTL_CLEAR_BUFFER  _IO(MSG_MAGIC_NUMBER, 2)

#define MSG_IOCTL_FILL_BUFFER   _IOW(MSG_MAGIC_NUMBER, 3, unsigned char)

#define MSG_GET_ADDRESS     _IOR(MSG_MAGIC_NUMBER, 4, unsigned long)

#define MSG_IOCTL_MAX_CMDS      4

#endif


long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

    unsigned char ch;
    int retval = 0;
    long size = _IOC_SIZE(cmd);

    pr_info("%s: Cmd:%u\t Arg:%lu Size:%lu add:%p\n", __func__, cmd, arg, size, &ch);

    if (_IOC_TYPE(cmd) != MSG_MAGIC_NUMBER) return -ENOTTY;
    if (_IOC_NR(cmd) > MSG_IOCTL_MAX_CMDS) return -ENOTTY;

    //access_ok is kernel-oriented, so the concept of read and write is reversed

    retval = access_ok((void __user *)arg, size);

    pr_info("access_ok returned:%d\n", retval);
    if (!retval)
        return -EFAULT;

    switch(cmd)
    
        //Get Length of buffer
        case MSG_IOCTL_GET_LENGTH:
            pr_info("Get Buffer Length\n");
            put_user(MAX_SIZE, (unsigned int *)arg);
            break;
        //clear buffer
        case MSG_IOCTL_CLEAR_BUFFER:
            pr_info("Clear buffer\n");
            memset(kernel_buffer, 0, sizeof(kernel_buffer));
            break;
        //fill character
        case MSG_IOCTL_FILL_BUFFER:
            get_user(ch, (unsigned char *)arg);
            pr_info("Fill Character:%c\n", ch);
            memset(kernel_buffer, ch, sizeof(kernel_buffer));
            buffer_index = sizeof(kernel_buffer);
            break;
        //address of kernel buffer
        case MSG_GET_ADDRESS:
            put_user(0x12345678, (unsigned long*)arg);
            pr_info("MSG_GET_ADDRESS\n");
            break;
        default:
            pr_info("Unknown Command:%u\n", cmd);
            return -ENOTTY;
    
    return 0;



long device_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

    unsigned char ch;
    int retval = 0;
    long size = _IOC_SIZE(cmd);

    pr_info("%s: Cmd:%u\t Arg:%lu Size:%lu add:%p\n", __func__, cmd, arg, size, &ch);

    if (_IOC_TYPE(cmd) != MSG_MAGIC_NUMBER) return -ENOTTY;
    if (_IOC_NR(cmd) > MSG_IOCTL_MAX_CMDS) return -ENOTTY;

    //access_ok is kernel-oriented, so the concept of read and write is reversed

    retval = access_ok((void __user *)arg, size);

    pr_info("access_ok returned:%d\n", retval);
    if (!retval)
        return -EFAULT;

    switch(cmd)
    
        //Get Length of buffer
        case MSG_IOCTL_GET_LENGTH:
            pr_info("Get Buffer Length\n");
            put_user(MAX_SIZE, (unsigned int *)arg);
            break;
        //clear buffer
        case MSG_IOCTL_CLEAR_BUFFER:
            pr_info("Clear buffer\n");
            memset(kernel_buffer, 0, sizeof(kernel_buffer));
            break;
        //fill character
        case MSG_IOCTL_FILL_BUFFER:
            get_user(ch, (unsigned char *)arg);
            pr_info("Fill Character:%c\n", ch);
            memset(kernel_buffer, ch, sizeof(kernel_buffer));
            buffer_index = sizeof(kernel_buffer);
            break;
        //address of kernel buffer
        case MSG_GET_ADDRESS:
            put_user(0x12345678, (unsigned long*)arg);
            pr_info("MSG_GET_ADDRESS\n");
            break;
        default:
            pr_info("Unknown Command:%u\n", cmd);
            return -ENOTTY;
    
    return 0;





struct file_operations device_fops = 
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release,
    .llseek = device_lseek,
    .unlocked_ioctl = device_ioctl,
    .compat_ioctl = device_compat_ioctl
;

MSG_GET_ADDRESS ioctl 接受 unsigned long long,在 32 位进程中为 4 个字节,在 64 位进程中为 8 个字节。这就是我写 compat_ioctl 的原因。

当我从用户空间(32 位进程)调用以下代码时,它会因 compat_ioctl 定义中的未知 ioctl 而失败。

int main(int argc, char *argv[])

    char buffer[1024];  
    int fd;
    unsigned int length;
    int i = 0;
    unsigned long addr;

    fd = open("/dev/msg", O_RDWR);
    if (fd < 0) 
        perror("fd failed");
        exit(2);
    

    printf("Size:%d\n", _IOC_SIZE(MSG_GET_ADDRESS));
    printf("cmd:%u\n", MSG_GET_ADDRESS);

    ioctl(fd, MSG_GET_ADDRESS, &addr);
    perror("ioctl");
    getchar();
    printf("address:%x\n", addr);

    close(fd);

我在这里犯了什么错误。

【问题讨论】:

unsigned long long 始终为 64 位。 你应该打电话给ret = ioctl(); if (ret &lt; 0) perror(); 用户程序作为 ioctl 标识符传递的数字似乎与内核中的数字不匹配。我看到你在内核中有pr_info;当用户程序工作时它的输出是什么? 是的,32 位/64 位两种情况下的数字都不相同。在这种情况下如何处理。我们需要新建一个ioctl命令代码吗? 【参考方案1】:

您的MSG_GET_ADDRESSioctl 请求代码定义为:

#define MSG_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, unsigned long)

第三个参数的大小被编码到ioctl请求代码中。可以使用_IOC_SIZE(req) 宏从请求代码中提取大小。

与 64 位进程/内核相比,MSG_GET_ADDRESS 的数值在 32 位进程/内核中会有所不同。特别是编码后的大小会有所不同。

在 32 位进程/内核上,_IOC_SIZE(MSG_GET_ADDRESS) 将为 4。在 64 位进程/内核上,_IOC_SIZE(MSG_GET_ADDRESS) 将为 8。这是由于 32 位和64 位系统。

在支持 32 位兼容性的 64 位内核上运行 32 位进程时,32 位进程将使用 32 位版本的 MSG_GET_ADDRESS 请求代码调用 ioctl()。但是,您的驱动程序的 device_compat_ioctl() 正在寻找 64 位版本的 MSG_GET_ADDRESS 请求代码。

一种解决方法是在驱动中定义一个32位版本的ioctl请求码来镜像“官方”MSG_GET_ADDRESS请求码:

#define MSG32_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, compat_ulong_t)

请注意,此请求代码不需要在用户模式标头中,因为它仅用于内核模式。但是,如果更方便,您可以将其包含在用户模式标头中,但包装在 #ifdef __KERNEL__ / #endif 对中:

#ifdef __KERNEL__
#define MSG32_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, compat_ulong_t)
#endif

现在,您的 device_compat_ioctl 函数应该更改为处理 MSG32_GET_ADDRESS 请求代码而不是 MSG_GET_ADDRESS 请求代码:

        //address of kernel buffer
        case MSG32_GET_ADDRESS:
            put_user(0x12345678, (compat_ulong_t*)arg);
            pr_info("MSG_GET_ADDRESS\n");
            break;

注意:根据代码中的 cmets,MSG_GET_ADDRESS 实际上应该获取内核缓冲区的地址。我不知道您的用户空间代码打算用它做什么,但请注意,64 位内核地址不适合 32 位 unsigned long(或 32 位 compat_ulong_t 类型)。

【讨论】:

以上是关于compat_ioctl 对于 unsigned long long 数据类型不能正常工作的主要内容,如果未能解决你的问题,请参考以下文章

unlocked_ioctl和compat_ioctl

ioctl接口 -26

unsigned与signed区别

什么是unsigned char;;?

如何获取一个unsigned char*类型的字符串长度

MySQL中UNSIGNED和ZEROFILL的介绍