在 Linux 里,“万物皆文件”。你想控制一个硬件,在代码眼里就是在读写一个文件。这些函数就是你和硬件沟通的“招式”。

1. device_open:签到/初始化

  • 动作: 应用程序执行 open("/dev/my_device", ...) 时被触发。
  • 什么时候用: 就像去健身房要先在台前登记一样。
  • 干什么:
    • 检查设备是否忙碌(有没有别人正在用)。
    • 给设备通电,初始化硬件状态。
  • 一句话总结: “准备开干,设备起床!”

2. device_release:撤退/清理

  • 动作: 应用程序执行 close(fd) 时触发。
  • 什么时候用: 程序运行完了,或者崩溃了。
  • 干什么:
    • 关灯、断电(省电)。
    • 释放占用的内存。
  • 一句话总结: “打完收工,设备睡觉。”

3. device_ioctl:发指令(万能遥控器)

  • 动作: ioctl(fd, 命令, 参数)
  • 为什么用: read/write 只能传数据流(像搬砖),但如果你想让马达“转快点”或“反转”,这不属于搬砖,而是发控制命令
  • 什么时候用: 凡是没法归类为“读”或“写”的操作,都塞在这里。
    • 例子:调节屏幕亮度、设置串口波特率。
  • 一句话总结: “我是遥控器,负责切歌和调音量。”

4. device_mmap:暴力提速(VIP 通道)

  • 动作: mmap(...)
  • 原理: 正常数据传输需要从“硬件 -> 内核 -> 用户程序”搬运好几次。mmap 直接把硬件内存地址映射到你的程序空间。
  • 什么时候用: * 大数据量:比如视频采集(摄像头),每秒几十 MB。
    • 高性能要求:如果用普通的 read 搬运数据,CPU 会累死。
  • 一句话总结: “不走中间商(内核),直接拿货。”

5. device_poll:这就是“轮询”

  • 动作: 配合 select / poll / epoll 使用。
  • 为什么用: 应用程序问你:“喂,驱动,现在有数据没?”。如果没数据,驱动会把这个程序“挂起”在等待队列里;一旦硬件来数据了,驱动再把它叫醒。
  • 什么时候用: 只要你想让你的驱动支持多路复用(同时管好几个设备),就必须写这个函数。
  • 一句话总结: “给监控中心(epoll)留个门铃,有货就按铃。”

🛠 我该选哪一个?(场景决策表)

需求场景 推荐使用的函数 理由
设备开关机、重置 open / release 负责硬件的生命周期管理。
获取温湿度传感器数值 read (未列出但在 file_ops 中) 简单的字节流数据传输。
控制电机转速、修改配置 ioctl 这种属于“控制指令”,非纯数据传输。
摄像头实时监控、高速刷屏 mmap 数据量巨大,必须减少拷贝次数。
多个传感器同时监控,谁有数读谁 poll 配合上层的 epoll 实现高效等待。

// 1. 头文件包含
#include <linux/...>  // 内核标准头文件
#include "local.h"    // 本地头文件

// 2. 数据结构定义
struct device_data {
    // 设备状态、寄存器、DMA、同步机制等
};

// 3. 文件操作函数
static int device_open(...)     // 打开设备
static int device_release(...)  // 关闭设备  
static long device_ioctl(...)   // 控制命令
static int device_mmap(...)     // 内存映射
static unsigned int device_poll(...) // 轮询

// 4. 文件操作结构体
static const struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .unlocked_ioctl = device_ioctl,
    .mmap = device_mmap,
    .poll = device_poll,
};

// 5. 平台驱动函数
static int device_probe(...)    // 设备探测
static int device_remove(...)   // 设备移除

// 6. 平台驱动结构体
static struct platform_driver driver = {
    .probe = device_probe,
    .remove = device_remove,
    .driver = {
        .name = "device-name",
    },
};

💡 给小白的建议:实现顺序

如果你在写第一个驱动程序,建议遵循这个顺序,不要一次性全写:

  1. 第一阶段: 先写 openrelease。能加载驱动并打印出 “Hello World” 就行。
  2. 第二阶段: 加入 readwrite。尝试从驱动里读出一个字符串,或者传一个数字进去。
  3. 第三阶段: 加入 ioctl。尝试通过命令控制一个 LED 灯的亮灭。
  4. 高级阶段: 当你觉得程序在那“死等”数据很浪费 CPU 时,再去研究 pollmmap

总结: 这些函数就像是一套模版。你不需要全部用上,你的设备需要什么功能,你就往对应的模版格子里填代码就行了。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐