4. 核心数据结构

在 VxWorks 字符设备驱动中,核心数据结构是驱动框架的骨架。理解 DEV_HDR、设备私有结构体、文件描述符表 以及 驱动函数表 (DRV_ENTRY) 之间的关系,是编写正确、可并发访问驱动的第一步。本节将逐一剖析这些结构的设计意图与内存布局。

4.1 DEV_HDR:设备对象的标准头部

DEV_HDR 是 VxWorks I/O 系统中所有设备对象的公共头部。它必须作为设备结构体的第一个成员出现,使得 I/O 子系统能够通过统一的指针操作来识别和管理设备。

成员 类型 描述
drvNum int 该设备所属的驱动编号,由 iosDrvInstall() 分配
devName char * 设备名称字符串指针,如 "/tyCo/0"
type int 设备类型标识,用于区分字符设备、块设备等
reserved int 保留字段,通常用于对齐或扩展
关键设计原则: 驱动开发者自定义的设备结构体必须以 DEV_HDR 开头。通过强制类型转换,I/O 系统可以将任何设备指针视为 DEV_HDR * 来获取驱动号和设备名。
DEV_HDR 在设备结构体中的布局 DEV_HDR drvNum devName type reserved 设备私有数据 (DEV_PRIVATE) pLock semId bufAddr bufSize flags ... DEV_HDR 必须是结构体的第一个成员

4.2 设备私有结构体设计

设备私有结构体用于存储每个设备实例的独有状态。在并发访问场景下,私有结构体是锁机制的主要载体。一个典型的设计包含同步原语、硬件资源和状态标志。

/* 设备私有结构体示例 */
typedef struct my_dev
{
    DEV_HDR   devHdr;          /* 标准设备头部,必须为第一成员 */
    SEM_ID    semLock;         /* 互斥信号量,保护临界区 */
    SEM_ID    semBufFull;      /* 同步信号量,缓冲区满 */
    SEM_ID    semBufEmpty;     /* 同步信号量,缓冲区空 */
    char *    pBuf;            /* 数据缓冲区指针 */
    int       bufSize;         /* 缓冲区大小 */
    int       rdIdx;           /* 读索引 */
    int       wrIdx;           /* 写索引 */
    BOOL      isOpen;          /* 设备打开标志 */
    int       openCount;       /* 打开计数,用于多任务共享 */
} MY_DEV;
设计建议: 将信号量、互斥量等同步原语直接嵌入私有结构体,而非使用全局变量。这保证了每个设备实例拥有独立的锁域,是实现设备级并发隔离的基础。
私有结构体中的锁域设计 MY_DEV 结构体内存布局 DEV_HDR devHdr SEM_ID semLock (互斥信号量) ← 并发保护核心 char *pBuf | int bufSize | int rdIdx | int wrIdx | ... 锁与数据同属一个结构体,便于管理

4.3 文件描述符表 (Fd Table)

文件描述符表是 VxWorks I/O 系统维护的全局表,用于将用户态的文件描述符 (fd) 映射到具体的设备对象和驱动函数。每个打开的文件描述符对应一个文件描述符表项。

表项字段 类型 说明
pDevHdr DEV_HDR * 指向设备对象的指针
pDrvEntry DRV_ENTRY * 指向驱动函数表的指针
value int 文件描述符值,用于快速索引
options int 打开时的选项标志 (O_RDONLY, O_NONBLOCK 等)
taskId int 打开该文件描述符的任务 ID
并发风险: 多个任务可以同时持有指向同一个 DEV_HDR 的文件描述符。这意味着驱动函数中的 pDevHdr 参数可能被多个任务并发访问。驱动开发者必须假设 open()、read()、write() 等函数会在不同任务上下文中同时执行。
文件描述符表到设备对象的映射 任务 A 任务 B 文件描述符表 fd=3 → pDevHdr=0x1000 fd=4 → pDevHdr=0x1000 fd=5 → pDevHdr=0x2000 设备对象 A (0x1000) 设备对象 B (0x2000) 多个 fd 可指向同一设备对象 → 并发访问风险

4.4 驱动函数表 (DRV_ENTRY)

DRV_ENTRY 是驱动函数表的结构体类型,它定义了驱动需要实现的七个标准 I/O 函数入口。驱动通过 iosDrvInstall() 将函数表注册到 I/O 系统。

/* DRV_ENTRY 结构体定义 */
typedef struct drv_entry
{
    int       (*create) ();    /* 设备创建函数 */
    int       (*remove) ();    /* 设备移除函数 */
    int       (*open)   ();    /* 设备打开函数 */
    int       (*close)  ();    /* 设备关闭函数 */
    int       (*read)   ();    /* 设备读取函数 */
    int       (*write)  ();    /* 设备写入函数 */
    int       (*ioctl)  ();    /* 设备控制函数 */
} DRV_ENTRY;
函数指针 典型并发问题 锁策略
open 多任务同时打开同一设备 使用引用计数 + 互斥锁
close 关闭时仍有其他任务在读写 等待所有读写完成后再释放
read / write 缓冲区竞争、索引错乱 互斥锁保护缓冲区操作
ioctl 配置变更与读写并发 读写锁或互斥锁
调试要点: 在调试并发问题时,可以在每个驱动函数的入口和出口处添加日志,打印当前任务 ID 和获取到的锁状态。这有助于快速定位死锁或锁未释放的场景。
驱动函数表与 I/O 系统调用流程 用户调用 read() I/O 系统 DRV_ENTRY 函数表 create → myDevCreate remove → myDevRemove read → myDevRead ← 当前调用 write → myDevWrite ioctl → myDevIoctl I/O 系统通过 fd 找到 DRV_ENTRY,再调用对应函数

4.5 数据结构关系总结

四个核心数据结构构成了 VxWorks 字符设备驱动的完整链路:

  1. DEV_HDR 是设备对象的身份证,提供驱动号和设备名。
  2. 设备私有结构体 是设备的具体实现,包含锁、缓冲区和状态。
  3. 文件描述符表 是用户态与内核态之间的桥梁,实现 fd 到设备对象的映射。
  4. DRV_ENTRY 是驱动行为的定义表,I/O 系统通过它调用具体的驱动函数。
常见错误: 在驱动函数中直接使用全局变量而非通过 pDevHdr 获取私有结构体。这会导致多设备实例时数据错乱。正确的做法是使用 container_of 或强制类型转换从 DEV_HDR * 得到私有结构体指针。