当前位置: 首页 > news >正文

ioctl函数讲解

在学习Linux设备驱动和视频开发的时候,对于这个函数又去了解了一下,因为之前觉得似懂非懂,所以这里来个总结吧,下面的都是在v4l2上面来讲解一下
 

1.ioctl 是什么?

可以把 ioctl(Input/Output Control)想象成一个万能遥控器。就像平时用遥控器控制电视(换台、调音量、设置画质),ioctl 就是用来控制各种硬件设备的“遥控器”

2.为什么需要它?

  • 普通的 read() 和 write() 只能做简单的读写(像用键盘打字)
  • 但硬件设备有很多复杂操作:比如设置摄像头分辨率、调整麦克风音量、让打印机换纸...
  • 这些特殊操作,全都可以交给 ioctl 来搞定

3.函数原型

int ioctl(int fd, unsigned long request, ...);
  • fd:文件描述符(就是你用 open() 打开设备后得到的那个“门票”)
  • request:你要发送的“指令代码”(比如:调大音量、获取分辨率)
  • ...:附加参数(通常是个结构体指针,用来传递/接收数据)

这里也要解释一下:
误区:认为“结构体里必须包含命令字段”
真相:结构体只是用来传递数据,真正决定操作的是 request 参数。结构体的内容必须与 request 匹配

4.举个栗子 🌰:控制摄像头

假设你有一个摄像头设备 /dev/video0

  1. 打开设备

    int fd = open("/dev/video0", O_RDWR); // 拿到“门票”fd
  2. 设置视频格式

    struct v4l2_format fmt = {0};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;
    fmt.fmt.pix.height = 480;
    
    // 发送指令:告诉摄像头  我要设置格式为640x480
    ioctl(fd, VIDIOC_S_FMT, &fmt); 
    • VIDIOC_S_FMT 是预设的指令代码(表示“设置格式”)
    • &fmt 是传递的参数(具体要设置什么格式)

还是很抽象啊
比如说:

a.为什么 request 参数那么奇怪(比如 VIDIOC_S_FMT)?
  • 这些是 Linux 内核预先定义好的“魔法数字”,每个设备驱动都有自己的指令列表
  • v4l2 的指令都以 VIDIOC_ 开头(Video IO Control),比如:
    • VIDIOC_QUERYCAP:查询设备能力(“你能做什么?”)
    • VIDIOC_REQBUFS:申请缓冲区(“给我腾点内存存视频数据!”)
b. 为什么会失败?
  • 常见错误:errno = EINVAL(无效参数)
  • 可能原因:设备不支持你的指令、参数结构体填错了

这不得不感叹,搞出这东西的人,真tm天才

好吧,其实还是懵
我有俩个关键点不明白:

  1. request 参数决定一切

    • ioctl 的第二个参数 request 是核心指令,它决定了:
      • 你要执行什么操作(比如“设置分辨率”还是“查询设备信息”)
      • 第三个参数应该传什么类型的数据(可能是结构体指针、整数、甚至不需要参数)
  2. 结构体指针的作用

    • 当你发送一个 request(比如 VIDIOC_S_FMT),驱动会预期你传递一个特定结构体的指针(比如 struct v4l2_format *)。
    • 结构体里的字段必须符合驱动要求,否则会失败(比如填错了字段名,或传了非法值)

举个反例🌰

假设你发送 VIDIOC_S_FMT(设置格式),但传了一个错误的结构体:
我的理解是:ioctl已经做好了吃草莓的准备了,你硬是要塞个苹果给他吃....他发现味道不对啊,就出现错误了

// 错误!应该传 struct v4l2_format,但传成了 int
int width = 640;
ioctl(fd, VIDIOC_S_FMT, &width); // 会失败,驱动预期的是结构体
  • 驱动预期收到 struct v4l2_format *,但你传了 int *,导致内核无法解析数据,返回 EINVAL 错误

到这里,我还是一知半解,不过至少知道了ioctl是遥控器,然后可以给我的设备传输命令了

但是呢,有个问题来了ioctl怎么知道request这个命令的?
好吧,深入一点点内核和驱动的设计逻辑  不过丫的好多,好复杂,做个略懂就可以了


1. request 命令的本质(下面的有些容易懵圈,我自己也只做了解了)

request 是一个整数,但不是随便写的!它的二进制位中编码了关键信息

// 内核中定义 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(方向): 数据传递方向(_IOC_NONE 无数据,_IOC_READ 读,_IOC_WRITE 写)
  • type(类型): 一个字符,标识设备类型(比如 'V' 表示视频设备)
  • nr(序号): 命令的编号(比如 1 表示第一个命令)
  • size(大小): 第三个参数的数据大小(比如 sizeof(struct my_struct)

2. 命令的来源

2.1 驱动开发者定义

每个设备驱动会自己定义支持的 ioctl 命令。例如摄像头驱动(V4L2)定义了大量命令:

// 在 include/uapi/linux/videodev2.h 中
#define VIDIOC_QUERYCAP     _IOR('V', 0, struct v4l2_capability) // 查询设备能力
#define VIDIOC_S_FMT        _IOW('V', 4, struct v4l2_format)     // 设置格式
  • _IOR 表示这是一个读操作(驱动→用户),第三个参数类型是 struct v4l2_capability
  • _IOW 表示这是一个写操作(用户→驱动),参数类型是 struct v4l2_format
2.2 用户空间使用

当你在用户层调用 ioctl(fd, VIDIOC_S_FMT, &fmt) 时:

  • VIDIOC_S_FMT 已经被预定义为特定整数(包含方向、类型、序号、结构体大小)
  • 驱动通过解码 request 中的信息,知道你要执行设置格式操作,并期望收到一个 struct v4l2_format 结构体

这里我又难受了,什么IOC,IOR,IOW的

来写一下吧,这倒霉孩子
IOCIORIOW 是 Linux 内核中用于构造 ioctl 命令的层级式宏,它们的关系可以理解为“基础宏”和“快捷宏”的关系

这里是IOC之类的知识了

这里简单说一下结论吧....就好像_IOC是原材料,_IOR,_IOW就好像是预制菜

关于为什么要写这个....是因为request 命令的来源:由驱动开发者定义,通过宏 _IO_IOR_IOW_IOWR 生成

1.层级关系

  • _IOC最底层的宏,直接构造 ioctl 命令的整数。
  • _IO_IOR_IOW_IOWR:基于 _IOC 的快捷宏,简化了常用场景的命令构造。
宏名作用等价于
_IO生成一个无数据传输的命令(仅发送指令,不需要参数)_IOC(_IOC_NONE, type, nr, 0)
_IOR生成一个从内核读取数据的命令(用户→内核→用户)_IOC(_IOC_READ, type, nr, sizeof(t))
_IOW生成一个向内核写入数据的命令(用户→内核)_IOC(_IOC_WRITE, type, nr, sizeof(t))
_IOWR生成一个双向传输数据的命令(用户↔内核)_IOC(_IOC_READ|_IOC_WRITE, type, nr, sizeof(t))

2. 参数详解

所有宏最终都调用 _IOC,其参数如下:

_IOC(dir, type, nr, size)
参数作用
dir数据方向(_IOC_NONE_IOC_READ_IOC_WRITE 或其组合)
type设备类型标识符(一个 ASCII 字符,如 'V' 表示视频设备)
nr命令编号(0~255,区分同一设备的不同操作)
size用户传递的数据大小(例如 sizeof(struct my_data)

request 参数本质上是一个32位整数,它的二进制位被划分为4个关键字段:
有点啰嗦了,但是记得住一些 

| 方向 (2 bits) | 类型 (8 bits) | 序号 (8 bits) | 数据大小 (14 bits) |
  • 方向 (dir):用2位表示数据传输方向

    • _IOC_NONE (0): 无数据传输(纯命令)
    • _IOC_READ (1): 从驱动读取数据
    • _IOC_WRITE (2): 向驱动写入数据
    • _IOC_READ|_IOC_WRITE (3): 双向传输
  • 类型 (type):一个ASCII字符(如 'V' 表示视频设备),用于防止不同设备间的命令冲突

  • 序号 (nr):命令的编号(0~255),区分同一设备的不同操作

  • 数据大小 (size):用户传递的数据结构的大小(最大16KB)

//这里就知道了‘基础宏’和‘快捷宏’了
// 基础宏
#define _IOC(dir,type,nr,size) // 组合四个字段

// 快捷宏
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

到此为止吧,如果有不足的话,请看到这里的佬提醒我,不胜感激 

相关文章:

  • 电脑开机一段时间就断网,只有重启才能恢复网络(就算插网线都不行),本篇文章直接解决,不要再看别人的垃圾方法啦
  • 巧妙实现右键菜单功能,提升用户操作体验
  • Docker实战-使用docker compose搭建博客
  • WebXR教学 01 基础介绍
  • 开发指南103-jpa的find**/get**全解
  • 【信息系统项目管理师-案例真题】2012下半年案例分析答案和详解
  • 位运算实用技巧与LeetCode实战
  • JAVAweb-标签选择器,盒模型,定位,浮动
  • linux之perf(17)PMU事件采集脚本
  • 使用Supervisor管理PHP脚本进程任务
  • 有点感慨……
  • C语言(22)
  • 【实战】ChatChat0.3.1+DeepSeek+本地知识库部署使用(上)
  • rtthread的串口框架、485框架
  • c++中sleep是什么意思(不是Sleep() )
  • 保姆级教程 | Office-Word中图目录制作及不显示图注引文的方法
  • Linux第十四节 — 环境变量和进程地址空间
  • 在VS中如何将控制台(console)项目改为窗口(window)项目
  • python~http的请求参数中携带map
  • 【AI表格处理工具】
  • 退休11年后,71岁四川厅官杨家卷被查
  • 山东滕州车祸致6人遇难,肇事司机已被刑事拘留
  • 五一假期上海虹桥边检站出入境近4.7万人次,韩国入境旅客同比增118%
  • 印巴局势紧张之际,巴基斯坦两天内第二次进行导弹试射
  • 2类药物别乱吃,严重可致肝肾衰竭!多人已中招
  • 马克思主义理论研究教学名师系列访谈|金瑶梅:教师需要了解学生的现实发展,把握其思想发展动态