Linux驱动开发:ioctl接口实现详解
## 1. Linux驱动开发中的ioctl接口实现 ### 1.1 ioctl命令编码规范 在Linux设备驱动开发中ioctl命令采用32位编码格式各比特位定义如下 | 比特位 | 功能说明 | |--------|---------------------------------| | 31-30 | 数据传输方向br00 - 无参数br01 - 写方向br10 - 读方向br11 - 读写双向 | | 29-16 | 参数数据大小字节数 | | 15-8 | 设备类型幻数Magic Number | | 7-0 | 命令编号 | 内核头文件include/uapi/asm-generic/ioctl.h提供了标准宏定义 c #define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size))) #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))) #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))1.2 典型命令定义示例定义串口设备的控制命令幻数s#define VS_MAGIC s #define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int) // 设置波特率 #define VS_GET_BAUD _IOR(VS_MAGIC, 1, unsigned int) // 获取波特率 #define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option) // 设置帧格式 #define VS_GET_FFMT _IOR(VS_MAGIC, 3, struct option) // 获取帧格式2. 内核层实现架构2.1 系统调用流程ioctl系统调用通过以下路径传递到驱动层用户空间调用ioctl(fd, cmd, arg)内核执行sys_ioctl()系统调用经过do_vfs_ioctl()路由最终调用驱动注册的unlocked_ioctl操作关键内核函数调用链sys_ioctl() → do_vfs_ioctl() → vfs_ioctl() → file_operations.unlocked_ioctl()2.2 驱动实现模板典型字符设备驱动实现框架static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { if (_IOC_TYPE(cmd) ! VS_MAGIC) return -ENOTTY; switch (cmd) { case VS_SET_BAUD: vsdev.baud arg; break; case VS_GET_BAUD: if (copy_to_user((unsigned int __user *)arg, vsdev.baud, sizeof(unsigned int))) return -EFAULT; break; case VS_SET_FFMT: if (copy_from_user(vsdev.opt, (struct option __user *)arg, sizeof(struct option))) return -EFAULT; break; default: return -ENOTTY; } return 0; } static struct file_operations vser_ops { .owner THIS_MODULE, .unlocked_ioctl vser_ioctl, // 其他操作函数... };3. 用户空间调用示例3.1 应用层测试程序#include sys/ioctl.h #include fcntl.h int main() { int fd open(/dev/vser0, O_RDWR); unsigned int baud 9600; struct option opt {8, 1, 1}; // 8N1帧格式 ioctl(fd, VS_SET_BAUD, baud); ioctl(fd, VS_SET_FFMT, opt); ioctl(fd, VS_GET_BAUD, baud); ioctl(fd, VS_GET_FFMT, opt); printf(Current config: %d baud, %d%d%d format\n, baud, opt.datab, opt.parity, opt.stopb); close(fd); return 0; }3.2 数据交换注意事项用户-内核空间数据传输必须使用copy_from_user()/copy_to_user()指针参数需要进行类型转换(struct option __user *)arg返回值处理成功返回0错误返回负的错误码如-EFAULT4. 工程实践要点4.1 命令设计规范幻数选择原则在驱动范围内唯一避免与内核预定义命令冲突参考include/uapi/asm-generic/ioctl.h参数大小检查if (_IOC_SIZE(cmd) ! sizeof(struct option)) return -EINVAL;4.2 错误处理机制典型错误返回码-ENOTTY // 无效的命令类型 -EFAULT // 用户空间内存访问错误 -EINVAL // 无效参数4.3 并发控制建议实现方案static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct vser_dev *dev filp-private_data; mutex_lock(dev-lock); // 处理命令 mutex_unlock(dev-lock); return ret; }5. 完整驱动示例包含初始化、设备注册和ioctl实现的完整模块#include linux/ioctl.h #include linux/cdev.h #define VSER_MAJOR 256 static struct class *vser_class; struct vser_dev { unsigned int baud; struct option opt; struct cdev cdev; struct mutex lock; }; static int __init vser_init(void) { dev_t dev MKDEV(VSER_MAJOR, 0); register_chrdev_region(dev, 1, vser); struct vser_dev *vsdev kzalloc(sizeof(*vsdev), GFP_KERNEL); cdev_init(vsdev-cdev, vser_ops); cdev_add(vsdev-cdev, dev, 1); mutex_init(vsdev-lock); vsdev-baud 115200; vsdev-opt (struct option){8, 0, 1}; // 8N1默认配置 vser_class class_create(THIS_MODULE, vser); device_create(vser_class, NULL, dev, NULL, vser0); return 0; }