字符设备驱动开发:内核工程ioctl与poll接口实现 - 第1节

1.1 Linux 设备驱动分类

在 Linux 内核中,设备驱动是操作系统与硬件设备之间的桥梁。根据设备类型和交互方式的不同,Linux 设备驱动主要分为以下三大类:

驱动类型 设备文件类型 数据交换方式 典型设备
字符设备驱动 字符设备文件 (c) 按字节流顺序访问 串口、GPIO、I2C、SPI、键盘
块设备驱动 块设备文件 (b) 按固定大小块随机访问 硬盘、eMMC、NAND Flash、SD 卡
网络设备驱动 套接字 (socket) 数据包收发 以太网、WiFi、蓝牙、CAN
关键区分:字符设备不经过内核缓冲区(无 page cache),直接与用户空间交换数据;块设备通过内核的块 I/O 层和页缓存管理;网络设备通过内核网络协议栈收发数据包。
Linux 设备驱动三大分类 用户空间 (User Space) 内核空间 (Kernel Space) 字符设备驱动 open/read/write/ioctl 块设备驱动 submit_bio/request 网络设备驱动 net_dev->hard_start_xmit 硬件设备层 (Hardware) 本课程聚焦字符设备驱动,重点讲解 ioctl 与 poll 接口的内核工程实现

1.2 字符设备特点

字符设备(Character Device)是 Linux 中最基础、最灵活的驱动类型,具有以下核心特征:

  • 顺序字节流:数据按字节流顺序读写,不支持随机访问(无 lseek 偏移量概念)。
  • 设备文件:通过 /dev 目录下的设备文件与用户空间交互,主设备号标识驱动,次设备号标识具体设备实例。
  • 系统调用接口:用户空间通过 open()、close()、read()、write()、ioctl()、poll() 等标准系统调用操作设备。
  • 无内核缓冲:数据直接在用户缓冲区和设备之间传递,不经过内核页缓存,适合实时性要求高的场景。
  • 同步/异步模式:支持阻塞 I/O、非阻塞 I/O、I/O 多路复用(poll/select/epoll)和异步通知(SIGIO)。
工程经验:字符设备驱动是嵌入式系统中最常见的驱动形态。一个典型的嵌入式 Linux 系统可能包含数十个字符设备驱动,覆盖 GPIO、UART、I2C、SPI、PWM、ADC、定时器、看门狗等外设。
字符设备驱动核心特征 字符设备驱动 顺序字节流 无随机访问 设备文件 /dev 主/次设备号 系统调用接口 open/read/write 无内核缓冲 直接数据交换 字符设备驱动特点总结:简单、灵活、实时性好,适合嵌入式外设控制 本课程重点:ioctl 实现设备控制命令,poll 实现 I/O 多路复用事件通知

1.3 驱动与应用程序交互模型

字符设备驱动与应用程序之间的交互遵循“用户空间 → 系统调用 → 内核驱动 → 硬件”的经典分层模型。理解这一模型是开发高质量驱动的基石。

1.3.1 交互流程

  1. 应用层发起系统调用:用户程序调用 open()、read()、write()、ioctl()、poll() 等标准 C 库函数。
  2. 系统调用陷入内核:通过软中断(x86 的 int 0x80 或 ARM 的 svc)从用户态切换到内核态。
  3. VFS 层分发:虚拟文件系统(VFS)根据设备文件的主设备号找到对应的 file_operations 结构体。
  4. 驱动层执行:调用驱动注册的 open、read、ioctl 等回调函数,操作硬件寄存器或管理内核资源。
  5. 结果返回用户空间:驱动执行完成后,数据通过内核栈或共享缓冲区传回用户空间,系统调用返回。
核心数据结构:struct file_operations 是字符设备驱动的灵魂。它定义了驱动能够响应的所有系统调用回调函数指针,包括 .owner、.open、.release、.read、.write、.unlocked_ioctl、.poll 等。
字符设备驱动与应用程序交互模型 用户空间 (User Space) 应用程序调用 open/read/write/ioctl/poll 系统调用层 (sys_open / sys_ioctl / sys_poll) VFS 虚拟文件系统 (根据设备号查找 file_operations) 字符设备驱动层 (file_operations 回调) .open → .read → .write → .unlocked_ioctl → .poll → .release 硬件设备层 (寄存器操作 / 中断处理 / DMA) 数据流向:用户缓冲区 ↔ 内核驱动 ↔ 硬件寄存器,全程无内核页缓存介入

1.3.2 file_operations 结构体详解

struct file_operations 定义在 <linux/fs.h> 中,是字符设备驱动的核心接口表。以下是本课程重点关注的成员:

成员 系统调用 功能描述 本课程重点
.open open() 打开设备,初始化硬件或分配资源 基础
.release close() 关闭设备,释放资源 基础
.read read() 从设备读取数据到用户空间 基础
.write write() 从用户空间写入数据到设备 基础
.unlocked_ioctl ioctl() 设备控制命令接口,无需 BKL 锁 核心
.poll poll() / select() / epoll() 查询设备是否可读/可写,支持 I/O 多路复用 核心
内核版本注意:在 Linux 2.6.36 之后,.ioctl 已被移除,统一使用 .unlocked_ioctl(无需大内核锁)和 .compat_ioctl(32位兼容)。本课程所有示例均基于 .unlocked_ioctl 实现。

1.3.3 交互模型代码骨架

以下是一个最小字符设备驱动的 file_operations 注册骨架,展示驱动与应用程序的交互入口:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "my_char_dev"
#define CLASS_NAME  "my_class"

static int dev_major;
static struct class *dev_class;
static struct cdev my_cdev;

/* open 回调 */
static int my_open(struct inode *inode, struct file *filp)
{
    pr_info("Device opened\n");
    return 0;
}

/* release 回调 */
static int my_release(struct inode *inode, struct file *filp)
{
    pr_info("Device closed\n");
    return 0;
}

/* ioctl 回调 — 本课程核心 */
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    pr_info("ioctl cmd=0x%x, arg=0x%lx\n", cmd, arg);
    return 0;
}

/* poll 回调 — 本课程核心 */
static unsigned int my_poll(struct file *filp, struct poll_table_struct *wait)
{
    pr_info("poll called\n");
    return POLLIN | POLLRDNORM;  /* 始终可读 */
}

/* file_operations 结构体 */
static struct file_operations my_fops = {
    .owner          = THIS_MODULE,
    .open           = my_open,
    .release        = my_release,
    .unlocked_ioctl = my_ioctl,
    .poll           = my_poll,
};

/* 模块初始化 */
static int __init my_init(void)
{
    dev_major = register_chrdev(0, DEVICE_NAME, &my_fops);
    if (dev_major < 0) {
        pr_err("Failed to register device\n");
        return dev_major;
    }
    pr_info("Module loaded, major=%d\n", dev_major);
    return 0;
}

/* 模块退出 */
static void __exit my_exit(void)
{
    unregister_chrdev(dev_major, DEVICE_NAME);
    pr_info("Module unloaded\n");
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kernel Driver Expert");
MODULE_DESCRIPTION("Minimal char device driver skeleton");
工程要点:上述代码展示了字符设备驱动的最小框架。在实际工程中,.unlocked_ioctl 和 .poll 的实现需要结合硬件状态、等待队列、自旋锁等内核机制,这正是本课程后续章节要深入讲解的内容。

1.4 本章小结

  • Linux 设备驱动分为字符设备、块设备、网络设备三大类,字符设备以字节流顺序访问为特征。
  • 字符设备驱动通过 /dev 设备文件和 file_operations 结构体与用户空间交互。
  • 驱动与应用程序的交互模型为:用户空间 → 系统调用 → VFS → 驱动回调 → 硬件。
  • .unlocked_ioctl 和 .poll 是字符设备驱动中实现设备控制和 I/O 多路复用的核心接口,也是本课程的两大主线。