compat_ioctl unsigned long long 数据类型无法正常工作

compat_ioctl not working properly for unsigned long long data type

我正在学习设备驱动程序中的 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.

的原因

当我从用户 space(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);
}

我在这里犯了什么错误。

您的 MSG_GET_ADDRESS ioctl 请求代码定义为:

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

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

MSG_GET_ADDRESS 的数值在 32 位 process/kernel 和 64 位 process/kernel 中会有所不同。特别是,编码后的大小会有所不同。

在 32 位 process/kernel 上,_IOC_SIZE(MSG_GET_ADDRESS) 将为 4。在 64 位 process/kernel 上,_IOC_SIZE(MSG_GET_ADDRESS) 将为 8。这是由于32 位和 64 位系统上的不同 sizeof(unsigned long) 值。

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

一个解决方案是在 driver 中定义一个 32 位版本的 ioctl 请求代码来镜像 "official" MSG_GET_ADDRESS 请求代码:

#define MSG32_GET_ADDRESS _IOR(MSG_MAGIC_NUMBER, 4, compat_ulong_t)

请注意,此请求代码不需要在 user-mode headers 中,因为它仅用于内核模式。但是,如果更方便,您可以将其包含在 user-mode headers 中,但包裹在 #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;

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