linux 内核 - ioctl 函数详解
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 值,如下:
EBADF d is not a valid descriptor.
EFAULT argp references an inaccessible memory area.
EINVAL Request or argp is not valid.
ENOTTY d is not associated with a character special device.
ENOTTY The specified request does not apply to the kind of object that the descriptor d references.
因此,在用户空间使用 ioctl 时,可以做如下的出错判断以及处理:
int ret;
ret = ioctl(fd, MYCMD);
if (ret == -1) {printf("ioctl: %s\n", 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.cstatic 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;
}
4. ioctl 用户与驱动之间的协议
前文提到 ioctl 方法第二个参数 cmd 为用户与驱动的 “协议”,理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:
在内核中,提供了宏接口以生成上述格式的 ioctl 命令:
// include/uapi/asm-generic/ioctl.h#define _IOC(dir,type,nr,size) \(((dir) << _IOC_DIRSHIFT) | \((type) << _IOC_TYPESHIFT) | \((nr) << _IOC_NRSHIFT) | \((size) << _IOC_SIZESHIFT))
- dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
- type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如
‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识; - nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
- size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;
通常而言,为了方便会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令:
// include/uapi/asm-generic/ioctl.h/* used to create numbers */
#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)))
_IO: 定义不带参数的 ioctl 命令
_IOW: 定义带写参数的 ioctl 命令(copy_from_user)
_IOR: 定义带读参数的ioctl命令(copy_to_user)
_IOWR: 定义带读写参数的 ioctl 命令
同时,内核还提供了反向解析 ioctl 命令的宏接口:
// include/uapi/asm-generic/ioctl.h/* used to decode ioctl numbers */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
5. ioctl_test 实例分析
本例假设一个带寄存器的设备,设计了一个 ioctl 接口实现设备初始化、读写寄存器等功能。在本例中,为了携带更多的数据,ioctl 的第三个可变参数为指针类型,指向自定义的结构体 struct msg。
1、ioctl-test.h,用户空间和内核空间共用的头文件,包含 ioctl 命令及相关宏定义,可以理解为一份 “协议” 文件,代码如下:
// ioctl-test.h#ifndef __IOCTL_TEST_H__
#define __IOCTL_TEST_H__#include <linux/ioctl.h> // 内核空间
// #include <sys/ioctl.h> // 用户空间/* 定义设备类型 */
#define IOC_MAGIC 'c'/* 初始化设备 */
#define IOCINIT _IO(IOC_MAGIC, 0)/* 读寄存器 */
#define IOCGREG _IOW(IOC_MAGIC, 1, int)/* 写寄存器 */
#define IOCWREG _IOR(IOC_MAGIC, 2, int)#define IOC_MAXNR 3struct msg {int addr;unsigned int data;
};#endif
2、ioctl-test-driver.c,字符设备驱动,实现了unlocked_ioctl 接口,根据上层用户的 cmd 执行对应的操作(初始化设备、读寄存器、写寄存器)。在接收上层 cmd 之前应该对其进行充分的检查,流程及具体代码实现如下:
// 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]\n", __func__);int ret;struct msg my_msg;/* 检查设备类型 */if (_IOC_TYPE(cmd) != IOC_MAGIC) {pr_err("[%s] command type [%c] error!\n", \__func__, _IOC_TYPE(cmd));return -ENOTTY; }/* 检查序数 */if (_IOC_NR(cmd) > IOC_MAXNR) { pr_err("[%s] command numer [%d] exceeded!\n", __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;
}
3、ioctl-test.c,运行在用户空间的测试程序:
// 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\n", my_msg.data);return 0;
}
linux 内核 - ioctl 函数详解相关推荐
- Linux内核开发函数详解
MKDEV 功能 将主.次设备号转换成dev_t类型 主设备号 major 次设备号 minor 头文件 定义在<linux/cdev.h>中的宏 MKDEV(int major,in ...
- ioctl函数详解(参数详解,驱动unlocked_ioctl使用、命令码如何封装)
@ioctl函数详解 一.ioctl函数的原型 在用户空间的函数原型 #include <sys/ioctl.h> int ioctl(int d, int request, ...); ...
- linux 系统函数调用脚本文件,Linux系统调用fsync函数详解
Linux系统调用fsync函数详解 发布时间:2013-11-14 19:55:10 作者:佚名 我要评论 Linux fsync函数主要用于将同步内存中所有已修改的文件数据到储存设备,多用 ...
- 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...
- Linux 文件锁 fcntl 函数详解
Linux 文件锁 fcntl 函数详解 #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd); ...
- Linux内核Thermal框架详解十四、Thermal Governor(4)
本文部分内容参考 万字长文 | Thermal框架源码剖析, Linux Thermal机制源码分析之框架概述_不捡风筝的玖伍贰柒的博客-CSDN博客, "热散由心静,凉生为室空" ...
- Linux内核Thermal框架详解十三、Thermal Governor(3)
接前一篇文章Linux内核Thermal框架详解十二.Thermal Governor(2) 二.具体温控策略 上一篇文章介绍并详细分析了bang_bang governor的源码.本文介绍第2种温控 ...
- Linux内核Thermal框架详解十二、Thermal Governor(2)
本文部分内容参考 万字长文 | Thermal框架源码剖析, Linux Thermal机制源码分析之框架概述_不捡风筝的玖伍贰柒的博客-CSDN博客, "热散由心静,凉生为室空" ...
- linux 中 sigaction 函数详解
linux 中 sigaction 函数详解 一.函数原型 sigaction 函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作) int sigaction(int signum, ...
最新文章
- PyTorch版YOLOv4更新了,不仅适用于自定义数据集,还集成了注意力和MobileNet
- 后生可畏!中国军团称霸阅读理解竞赛RACE:微信AI称王,高中生力压腾讯康奈尔联队(附资料)...
- python 类方法装饰器_python类装饰器即__call__方法
- 使用snmp4j实现Snmp功能(二)
- 通过系统表获取SQL,Access,Oracle数据库的元数据信息
- [Ubuntu] 16.04 上使用 pip 安装 mysqlclient
- BZOJ1026[SCOI2009]windy数
- acl在内核里的位置_在Linux中使用ACL(访问控制列表)保护文件/目录
- 功能接口 java8_Java 8功能接口
- access 导入 txt sql语句_从零开始学 MySQL - 数据库的导入导出和备份
- 事务、事务的隔离级别、一致性
- 翻译一定要知道的一些软件和工具
- 复数(Complex Number)
- Android遥控车软件设计,手机APP控制,Wifi遥控小车设计资料分享
- matlab用摄像头拍照,matlab调用摄像头拍照
- 如何提高测试用例评审效率?
- 机器学习数学基础——群论
- Android 5.1添加字库
- 股民学堂:短线炒股绝招 值得一学
- 高端光刻机为什么那么难制造?
热门文章
- Code Coverage
- 如何成为时间管理大师
- JDBC——ClassNotFoundException报错
- Mac XCode链接苹果手机iPhone总是不断连接断开、断断续续的解决方法
- 飞机复合外壳的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- 阴阳历转换(1936-2031)
- 小程序首页不显示tabbar,而非首页显示tabbar的方法
- 骨传导蓝牙耳机什么牌子,推荐几款比较热销的骨传导耳机
- 惊!亚马逊封号潮提前结束;50%海外电商零售市场属于独立站;TikTok重返印度…|洞悉跨境
- QMdiArea关闭窗口(QMdiSubWindow),如何不删除这个窗口