块设备驱动:解析收到的ioctl及虚拟块设备异常行为
嘿,我来帮你梳理下这个虚拟块设备的ioctl异常问题。你已经搞定了读写操作的并发保护(多读者单写者的模型很标准),但没处理ioctl就出现异常——这其实是块设备驱动的常见坑,因为用户空间的工具(比如磁盘工具、文件系统挂载工具)会默认给块设备发一堆ioctl请求来获取设备信息,没处理的话很容易触发意料之外的行为。
先搞清楚到底是哪些ioctl在搞事情
既然你已经在记录日志,那先给驱动加个最基础的ioctl入口,把收到的命令码打出来,这样就能定位异常来源:
#include <linux/fs.h> long your_vdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { printk(KERN_INFO "[vdev] Received ioctl cmd: 0x%x\n", cmd); // 默认返回ENOTTY,告诉用户空间该命令不被支持 return -ENOTTY; }
然后别忘了在你的file_operations结构体里注册这个函数:
static const struct file_operations vdev_fops = { .owner = THIS_MODULE, .read = vdev_read, .write = vdev_write, .unlocked_ioctl = your_vdev_ioctl, .compat_ioctl = your_vdev_ioctl, // 兼容32位用户空间的话加上 // 其他已实现的操作... };
重启驱动测试后,你就能在dmesg里看到具体的命令码——大概率是块设备常用的BLKSSZGET(获取扇区大小)、BLKGETSIZE(总扇区数)这类基础查询命令。
先实现几个必备的ioctl避免基础异常
哪怕你不需要复杂的ioctl功能,也得把块设备的基础信息查询命令实现了,否则很多用户空间工具会报错甚至崩溃。这里给你个示例:
#include <linux/blkdev.h> long your_vdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct vdev_dev *dev = filp->private_data; // 你的设备结构体指针 int ret = 0; switch(cmd) { // 返回逻辑扇区大小(你的设备是512字节) case BLKSSZGET: ret = put_user(512, (int __user *)arg); break; // 返回总扇区数(你的设备是2个扇区) case BLKGETSIZE: ret = put_user((unsigned long)2, (unsigned long __user *)arg); break; // 返回总字节数(2*512=1024) case BLKGETSIZE64: ret = put_user((u64)1024, (u64 __user *)arg); break; default: printk(KERN_INFO "[vdev] Unsupported ioctl cmd: 0x%x\n", cmd); ret = -ENOTTY; break; } return ret; }
别忘给ioctl加上并发保护
你已经给读写加了锁,但ioctl操作如果涉及设备状态修改(比如以后加个重置设备的命令),必须和读写操作互斥。比如如果是修改类ioctl,要加写锁;如果只是查询类,加读锁就行:
// 假设你的设备结构体里有读写锁:struct rw_semaphore rw_lock; case VDEV_RESET: // 自定义的重置设备命令 if (down_write_interruptible(&dev->rw_lock)) { return -ERESTARTSYS; } memset(dev->mem_region, 0, 1024); // 清空内存区域 up_write(&dev->rw_lock); return 0; case VDEV_GET_INFO: // 查询类命令 down_read(&dev->rw_lock); // 读取设备信息并拷贝到用户空间 up_read(&dev->rw_lock); return 0;
额外注意:用户空间参数的合法性检查
如果ioctl需要传递用户空间的指针,一定要用access_ok检查指针的合法性,否则容易触发内核Oops:
case VDEV_SET_PARAM: if (!access_ok((void __user *)arg, sizeof(struct vdev_param))) { return -EFAULT; } // 后续处理参数拷贝 break;
内容的提问来源于stack exchange,提问作者Arkaik
相关产品推荐
相关产品推荐

