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 < 0) perror();
。
用户程序作为 ioctl 标识符传递的数字似乎与内核中的数字不匹配。我看到你在内核中有pr_info
;当用户程序工作时它的输出是什么?
是的,32 位/64 位两种情况下的数字都不相同。在这种情况下如何处理。我们需要新建一个ioctl命令代码吗?
【参考方案1】:
您的MSG_GET_ADDRESS
ioctl 请求代码定义为:
#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 数据类型不能正常工作的主要内容,如果未能解决你的问题,请参考以下文章