1. 概念
ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。
在文件 I/O 中,ioctl 扮演着重要角色,本文将以驱动开发为侧重点,从用户空间到内核空间纵向分析 ioctl 函数。
2. 用户空间 ioctl
#include <sys/ioctl.h> int ioctl(int fd, int cmd, ...) ; 参数 描述 fd 文件描述符 cmd 交互协议,设备驱动将根据 cmd 执行对应操作 … 可变参数 arg,依赖 cmd 指定长度以及类型 ioctl() 函数执行成功时返回 0,失败则返回 -1 并设置全局变量 errorno 值
因此,在用户空间使用 ioctl 时,可以做如下的出错判断以及处理:
int ret; ret = ioctl(fd, MYCMD,(unsigned long)&stPhyInfo); if (ret == -1) { printf("ioctl: %s ", strerror(errno)); }
在实际应用中,ioctl 最常见的 errorno 值为 ENOTTY(error not a typewriter),顾名思义,即第一个参数 fd 指向的不是一个字符设备,不支持 ioctl 操作,这时候应该检查前面的 open 函数是否出错或者设备路径是否正确。
3. 驱动程序 ioctl
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
在新版内核中, 与 取代了 。unlocked_ioctl,顾名思义,应该在无大内核锁(BKL)的情况下调用;compat_ioctl,compat 全称 compatible(兼容的),主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,也是在无大内核锁的情况下调用。
在《Linux Kernel Development》中对两种 ioctl 方法有详细的解说。
在字符设备驱动开发中,一般情况下只要实现 unlocked_ioctl 函数即可,因为在 vfs 层的代码是直接调用 unlocked_ioctl 函数
// fs/ioctl.c static long vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int error = -ENOTTY; if (!filp->f_op || !filp->f_op->unlocked_ioctl) goto out; error = filp->f_op->unlocked_ioctl(filp, cmd, arg); if (error == -ENOIOCTLCMD) { error = -ENOTTY; } out: return error; }
内核态// ioctl-test-driver.c ...... static const struct file_operations fops = { .owner = THIS_MODULE, .open = test_open, .release = test_close, .read = test_read, .write = etst_write, .unlocked_ioctl = test_ioctl, }; ...... static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { //printk("[%s] ", __func__); int ret; struct msg my_msg; /* 检查设备类型 */ if (_IOC_TYPE(cmd) != IOC_MAGIC) { pr_err("[%s] command type [%c] error! ", __func__, _IOC_TYPE(cmd)); return -ENOTTY; } /* 检查序数 */ if (_IOC_NR(cmd) > IOC_MAXNR) { pr_err("[%s] command numer [%d] exceeded! ", __func__, _IOC_NR(cmd)); return -ENOTTY; } /* 检查访问模式 */ if (_IOC_DIR(cmd) & _IOC_READ) ret= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) ret= !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (ret) return -EFAULT; switch(cmd) { /* 初始化设备 */ case IOCINIT: init(); break; /* 读寄存器 */ case IOCGREG: ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg)); if (ret) return -EFAULT; msg->data = read_reg(msg->addr); ret = copy_to_user((struct msg __user *)arg, &msg, sizeof(my_msg)); if (ret) return -EFAULT; break; /* 写寄存器 */ case IOCWREG: ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg)); if (ret) return -EFAULT; write_reg(msg->addr, msg->data); break; default: return -ENOTTY; } return 0; }
用户态// ioctl-test.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include "ioctl-test.h" int main(int argc, char **argv) { int fd; int ret; struct msg my_msg; fd = open("/dev/ioctl-test", O_RDWR); if (fd < 0) { perror("open"); exit(-2); } /* 初始化设备 */ ret = ioctl(fd, IOCINIT); if (ret) { perror("ioctl init:"); exit(-3); } /* 往寄存器0x01写入数据0xef */ memset(&my_msg, 0, sizeof(my_msg)); my_msg.addr = 0x01; my_msg.data = 0xef; ret = ioctl(fd, IOCWREG, &my_msg); if (ret) { perror("ioctl read:"); exit(-4); } /* 读寄存器0x01 */ memset(&my_msg, 0, sizeof(my_msg)); my_msg.addr = 0x01; ret = ioctl(fd, IOCGREG, &my_msg); if (ret) { perror("ioctl write"); exit(-5); } printf("read: %#x ", my_msg.data); return 0; }
引用https://blog.csdn.net/qq_19923217/article/details/82698787。