2. 设备驱动框架基础:驱动类型与注册流程

在 QNX 实时操作系统中,设备驱动按照其管理的设备特性与数据交换方式,划分为三种核心类型:字符驱动(Char)、块驱动(Block) 和 网络驱动(Network)。每种类型对应不同的资源管理模型、入口点集合与注册机制。理解这些基础分类与注册流程,是后续实现中断映射与高效数据传输的前提。

2.1 驱动类型对比与选择

下表从数据模型、访问方式、缓存策略与典型应用场景四个维度,对三种驱动类型进行对比:

对比维度 字符驱动(Char) 块驱动(Block) 网络驱动(Network)
数据模型 字节流,无固定块大小 固定大小块(如 512B/4KB) 数据包(Packet)
访问方式 open/read/write/ioctl 块设备接口 + 文件系统挂载 Socket / io-pkt 协议栈
缓存策略 无内核缓存,直接传递用户缓冲区 内核块缓存层(Buffer Cache) mbuf 链管理
典型场景 串口、GPIO、SPI、传感器 SD/MMC、NAND Flash、硬盘 以太网、CAN、无线网卡
QNX 注册接口 io_open / io_read / io_write devb-* 框架 + blkio 接口 netmap / ifnet 结构体
选型建议: 对于大多数嵌入式外设(如 ADC、PWM、中断控制器),优先选择字符驱动模型,因为它接口简洁、延迟可控,且无需经过文件系统层。块驱动适用于需要挂载文件系统的存储设备;网络驱动则专为协议栈数据包收发设计。

2.2 字符驱动入口点与注册流程

字符驱动是 QNX 驱动开发中最常用的模型。其核心入口点通过 iofunc_funcs_t 和 resmgr_connect_funcs_t 结构体注册到资源管理器(Resource Manager)。

2.2.1 核心入口点结构

// 连接函数表:处理 open/close/unblock 等连接事件
resmgr_connect_funcs_t connect_funcs;

// I/O 函数表:处理 read/write/ioctl/devctl 等数据操作
iofunc_funcs_t io_funcs;

// 初始化连接函数表
memset(&connect_funcs, 0, sizeof(connect_funcs));
connect_funcs.open  = my_open;
connect_funcs.close = my_close;

// 初始化 I/O 函数表
memset(&io_funcs, 0, sizeof(io_funcs));
io_funcs.read  = my_read;
io_funcs.write = my_write;
io_funcs.ioctl = my_ioctl;

2.2.2 注册流程步骤

  1. 初始化 I/O 资源层: 调用 iofunc_func_init() 填充默认函数指针。
  2. 分配并初始化属性结构体: 使用 iofunc_attr_init() 设置设备权限、拥有者、设备类型。
  3. 创建资源管理器附加点: 调用 resmgr_attach() 将设备路径(如 /dev/mydevice)与函数表绑定。
  4. 进入消息分发循环: 调用 resmgr_block() 或 resmgr_loop() 等待客户端请求。
关键注册代码片段:
// 步骤1:初始化 I/O 函数表
iofunc_func_init(RESMGR_IO_NULL, 0, 0, 0, &io_funcs);

// 步骤2:初始化设备属性
iofunc_attr_init(&attr, S_IFCHR | 0666, NULL, NULL);

// 步骤3:注册到路径 /dev/mydevice
resmgr_attach(
    &dpp,                    // 资源管理器句柄
    &res_attr,               // 资源属性
    "/dev/mydevice",         // 设备路径
    _FTYPE_ANY,              // 文件类型
    0,                       // 标志
    &connect_funcs,          // 连接函数表
    &io_funcs,               // I/O 函数表
    &attr                    // 设备属性
);

// 步骤4:进入事件循环
resmgr_block(dpp);

2.3 块驱动入口点与注册流程

块驱动在 QNX 中通常基于 devb-* 框架实现。该框架封装了块缓存层与分区管理,驱动开发者只需实现底层的 blkio 接口。

2.3.1 块驱动核心接口

接口函数 功能描述 触发时机
blkio_open() 打开块设备,初始化硬件 设备挂载或首次访问
blkio_read() 读取指定 LBA 的数据块 文件系统下发读请求
blkio_write() 写入数据块到指定 LBA 文件系统下发写请求
blkio_capacity() 返回设备总块数 分区表扫描时
blkio_flush() 刷新写缓存到物理介质 sync 或缓存回写

2.3.2 注册流程

  1. 实现 blkio 接口结构体: 填充 blkio_ops_t 中的函数指针。
  2. 调用 blkio_register(): 将设备注册到 devb 框架,指定设备名称与容量。
  3. 启动 I/O 处理线程: 框架自动调用 blkio_read/write 处理来自文件系统的请求。
注意: 块驱动必须正确处理对齐访问与部分块读写。QNX 块缓存层可能下发非对齐请求,驱动内部需进行缓冲与合并。

2.4 网络驱动入口点与注册流程

网络驱动在 QNX 中通过 ifnet 结构体与 io-pkt 协议栈交互。驱动需要实现数据包发送、接收、中断处理以及接口统计等回调。

2.4.1 ifnet 核心入口点

struct ifnet my_ifnet;

// 初始化 ifnet 结构体
memset(&my_ifnet, 0, sizeof(my_ifnet));
strcpy(my_ifnet.name, "en0");          // 接口名称
my_ifnet.if_start = my_net_start;      // 启动发送队列
my_ifnet.if_input = my_net_input;      // 接收数据包
my_ifnet.if_ioctl = my_net_ioctl;      // 控制命令(如设置 MAC)
my_ifnet.if_watchdog = my_net_watchdog; // 看门狗定时器

// 设置接口能力标志
my_ifnet.if_capabilities = IFCAP_HWCSUM | IFCAP_VLAN_MTU;

2.4.2 注册流程

  1. 分配并初始化 ifnet: 设置接口名称、MTU、MAC 地址。
  2. 调用 if_attach(): 将接口注册到协议栈全局链表。
  3. 注册中断处理: 使用 InterruptAttach() 绑定接收/发送完成中断。
  4. 启动接口: 通过 if_up() 或 ioctl SIOCSIFFLAGS 使能接口。

2.5 驱动注册流程总览(SVG 架构图)

下图展示了三种驱动类型从初始化到注册完成的统一流程框架,突出资源管理器、devb 框架与 io-pkt 协议栈三种注册路径的差异:

QNX 设备驱动注册流程总览 驱动初始化入口(iofunc / blkio / ifnet) 字符驱动(Char) iofunc_funcs_t resmgr_connect_funcs_t resmgr_attach() resmgr_block() 循环 块驱动(Block) blkio_ops_t 接口 blkio_register() devb-* 框架接管 块缓存层 + 分区管理 网络驱动(Network) ifnet 结构体 if_attach() InterruptAttach() io-pkt 协议栈 设备就绪:等待客户端请求 三种驱动共用中断注册机制 但数据路径与注册 API 不同

2.6 驱动注册关键数据结构对比

三种驱动类型在注册过程中依赖的核心数据结构差异显著,下表汇总了关键结构体及其作用:

驱动类型 核心结构体 注册函数 设备路径/名称
字符驱动 resmgr_connect_funcs_t + iofunc_funcs_t resmgr_attach() /dev/xxx(POSIX 路径)
块驱动 blkio_ops_t blkio_register() /dev/hd0(块设备名)
网络驱动 ifnet if_attach() en0(接口名)
调试提示: 在 QNX 系统启动后,使用 ls -l /dev/ 查看已注册的字符/块设备;使用 ifconfig -a 查看已注册的网络接口。若设备未出现,优先检查注册函数返回值与错误日志(sloginfo)。

2.7 本章小结

  • 字符驱动 是最通用的模型,通过资源管理器注册,适合大多数外设。
  • 块驱动 基于 devb-* 框架,需要实现 blkio 接口,适合存储类设备。
  • 网络驱动 通过 ifnet 结构体与 io-pkt 协议栈集成,处理数据包收发。
  • 三种驱动类型的注册流程均包含 初始化 → 填充函数表 → 注册 → 进入服务循环 四个阶段。
  • 理解入口点与注册流程是后续实现中断映射与 DMA 数据传输的基础。