平时很少写内核模块,今天遇到一个小问题,写了一个内核模块,想在用户态通过 ioctl
与该模块进行交互,于是在内核模块和用户态程序中定义了相同的枚举变量,如下所示:
enum {
CMD_1,
CMD_2,
CMD_3,
CMD_4,
};
很明显,CMD_3
等于 2
, 但在调用 ioctl(fd, CMD_3, …)
后,发现该请求一直没有到达内核模块,折腾了好长时间才知道 ioctl
的 cmd
不能随意指定,比如 2
就是一个特殊的值。在 ioctl
请求到达内核模块之前,内核中的其他代码还进行了处理,如下所示(4.19
内核):
int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
unsigned long arg)
{
int error = 0;
int __user *argp = (int __user *)arg;
struct inode *inode = file_inode(filp);
switch (cmd) {
case FIOCLEX:
set_close_on_exec(fd, 1);
break;
case FIONCLEX:
set_close_on_exec(fd, 0);
break;
case FIONBIO:
error = ioctl_fionbio(filp, argp);
break;
case FIOASYNC:
error = ioctl_fioasync(fd, filp, argp);
break;
case FIOQSIZE:
if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode) ||
S_ISLNK(inode->i_mode)) {
loff_t res = inode_get_bytes(inode);
error = copy_to_user(argp, &res, sizeof(res)) ?
-EFAULT : 0;
} else
error = -ENOTTY;
break;
case FIFREEZE:
error = ioctl_fsfreeze(filp);
break;
case FITHAW:
error = ioctl_fsthaw(filp);
break;
case FS_IOC_FIEMAP:
return ioctl_fiemap(filp, arg);
case FIGETBSZ:
/* anon_bdev filesystems may not have a block size */
if (!inode->i_sb->s_blocksize)
return -EINVAL;
return put_user(inode->i_sb->s_blocksize, argp);
case FICLONE:
return ioctl_file_clone(filp, arg, 0, 0, 0);
case FICLONERANGE:
return ioctl_file_clone_range(filp, argp);
case FIDEDUPERANGE:
return ioctl_file_dedupe_range(filp, argp);
default:
if (S_ISREG(inode->i_mode))
error = file_ioctl(filp, cmd, arg);
else
error = vfs_ioctl(filp, cmd, arg);
break;
}
return error;
}
在上面的代码中,进入内核模块的函数是 file_ioctl()
或 vfs_ioctl()
,但由于 FIGETBSZ
的值等于 2
,因此当传入的 cmd
等于 2
时,就无法进入内核模块。具体可以参考这个问题:ioctl is not called if cmd 2
解决办法是使用 ioctl
提供的宏(比如_IO
,_IOR
,_IOW
, _OIWR
)来定义 cmd
。典型的用法如 KVM
提供给用户态程序(比如 QEMU
)的 ioctl cmd
,如下所示:
#define KVMIO 0xAE
#define KVM_CREATE_VCPU _IO(KVMIO, 0x41)
#define KVM_RUN _IO(KVMIO, 0x80)
#define KVM_GET_REGS _IOR(KVMIO, 0x81, struct kvm_regs)
#define KVM_SET_REGS _IOW(KVMIO, 0x82, struct kvm_regs)
#define KVM_CREATE_DEVICE _IOWR(KVMIO, 0xe0, struct kvm_create_device)
简单场景的话,可以像下面这样使用:
#define CMD_1 _IO(’X', 0)
#define CMD_2 _IO(‘X', 1)
#define CMD_3 _IO(‘X', 2)
#define CMD_4 _IO(‘X', 3)