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