3. 字符设备驱动框架:驱动入口点函数、xxxDrv()与xxxDevCreate()函数实现、设备描述符管理

字符设备驱动是VxWorks I/O子系统中最基础的驱动模型。本章聚焦驱动框架的三个核心构件:驱动入口点函数表、驱动安装/设备创建函数、以及设备描述符管理机制。理解这三者的协作关系,是后续分析并发访问与锁机制的前提。

3.1 驱动入口点函数表(DRV_ENTRY_TABLE)

每个字符设备驱动必须向VxWorks I/O系统注册一组标准操作函数,称为入口点函数表。该表通过iosDrvInstall()注册,返回一个驱动编号(drvNum)。

入口点函数 函数原型 说明
xxxOpen() int xxxOpen (DEV_HDR *pDevHdr, char *name, int mode) 打开设备,返回文件描述符关联的设备上下文
xxxClose() int xxxClose (DEV_HDR *pDevHdr) 关闭设备,释放打开时分配的资源
xxxRead() int xxxRead (DEV_HDR *pDevHdr, char *buf, int nBytes) 从设备读取数据
xxxWrite() int xxxWrite (DEV_HDR *pDevHdr, char *buf, int nBytes) 向设备写入数据
xxxIoctl() int xxxIoctl (DEV_HDR *pDevHdr, int func, int arg) 设备控制命令,如配置参数、获取状态
关键点:入口点函数的第一个参数均为 DEV_HDR *pDevHdr,这是I/O系统与驱动交互的统一设备头指针。驱动通过该指针获取设备私有数据结构。

3.2 xxxDrv() 函数实现:驱动安装

xxxDrv() 是驱动的初始化入口,通常在系统启动或驱动加载时调用一次。其核心职责是向I/O系统注册入口点函数表。

/* 驱动安装函数示例 */
STATUS xxxDrv (void)
{
    int drvNum;

    /* 注册入口点函数表 */
    drvNum = iosDrvInstall (xxxOpen,    /* 打开 */
                            xxxClose,   /* 关闭 */
                            xxxRead,    /* 读 */
                            xxxWrite,   /* 写 */
                            xxxIoctl);  /* 控制 */

    if (drvNum == ERROR)
        return (ERROR);

    /* 保存驱动编号,供后续设备创建使用 */
    xxxDrvNum = drvNum;

    return (OK);
}
实现要点:
  • iosDrvInstall() 参数顺序固定:open, close, read, write, ioctl。不可调换。
  • 驱动编号 drvNum 是全局资源,需保存到全局变量或设备私有结构中。
  • 若某个入口点函数不需要,可传入 NULL,但 open/close 通常必须实现。

3.3 xxxDevCreate() 函数实现:设备创建

xxxDevCreate() 负责在I/O系统中注册一个具体设备实例。每个设备实例对应一个设备描述符(DEV_HDR),并关联到已安装的驱动。

/* 设备创建函数示例 */
STATUS xxxDevCreate (char *devName, int arg1, int arg2)
{
    XXX_DEV *pDev;

    /* 1. 分配设备私有结构体 */
    pDev = (XXX_DEV *) memalign (sizeof (XXX_DEV), 4);
    if (pDev == NULL)
        return (ERROR);

    /* 2. 初始化设备私有数据 */
    pDev->regBase   = arg1;
    pDev->irqNum    = arg2;
    pDev->semMutex  = semMCreate (SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
    pDev->openCount = 0;

    /* 3. 填充DEV_HDR头 */
    pDev->devHdr.drvNum = xxxDrvNum;
    pDev->devHdr.name   = devName;

    /* 4. 注册到I/O系统 */
    iosDevAdd (&pDev->devHdr, devName, xxxDrvNum);

    return (OK);
}
常见错误:设备创建时未初始化锁(如 semMutex),导致后续并发访问时出现竞态条件。务必在 xxxDevCreate() 中完成所有同步原语的初始化。

3.4 设备描述符管理:DEV_HDR 与设备链表

VxWorks I/O系统通过设备描述符链表管理所有已注册的设备。每个设备实例对应一个 DEV_HDR 结构体,该结构体是设备在I/O系统中的唯一标识。

设备描述符链表结构 I/O 系统核心 设备链表头指针 DEV_HDR 节点 1 drvNum=0, name="/tyCo/0" pNext → 节点2 DEV_HDR 节点 2 drvNum=1, name="/xxx/1" pNext → NULL 设备描述符管理规则 • iosDevAdd() 将DEV_HDR插入链表头部 • iosDevFind() 按名称遍历链表查找设备

3.5 DEV_HDR 结构体详解

DEV_HDR 是设备描述符的公共头部,定义在 iosLib.h 中。驱动开发者通常将 DEV_HDR 作为私有设备结构体的第一个成员,实现“继承”效果。

/* VxWorks 标准 DEV_HDR 结构 */
typedef struct dev_hdr
{
    DL_NODE  node;       /* 双向链表节点,用于链接所有设备 */
    short    drvNum;     /* 驱动编号,关联到入口点函数表 */
    char    *name;       /* 设备名称,如 "/tyCo/0" */
} DEV_HDR;

/* 驱动私有设备结构体示例 */
typedef struct xxx_dev
{
    DEV_HDR  devHdr;     /* 公共设备头,必须为第一个成员 */
    SEM_ID   semMutex;   /* 互斥信号量 */
    int      regBase;    /* 寄存器基地址 */
    int      irqNum;     /* 中断号 */
    int      openCount;  /* 打开计数 */
} XXX_DEV;
关键设计模式:将 DEV_HDR 作为私有结构体的第一个成员,使得 DEV_HDR * 指针可以安全地强制转换为 XXX_DEV *。这是VxWorks驱动中广泛使用的“公共头继承”模式。

3.6 设备查找与打开流程

当应用程序调用 open("/xxx/0", ...) 时,I/O系统执行以下步骤:

  1. 名称解析:遍历设备链表,调用 iosDevFind() 查找名称匹配的 DEV_HDR。
  2. 驱动关联:从 DEV_HDR.drvNum 获取驱动编号,定位对应的入口点函数表。
  3. 调用xxxOpen:调用驱动注册的 xxxOpen() 函数,传入 DEV_HDR * 指针。
  4. 返回fd:xxxOpen() 返回文件描述符,应用程序后续通过fd操作设备。
调试提示:使用 iosDevShow() 命令可查看当前系统中所有已注册的设备及其驱动编号。这是排查“设备未找到”类问题的第一手段。

3.7 多实例设备管理

同一驱动可管理多个设备实例,每个实例通过不同的设备名称区分。例如,串口驱动可管理 /tyCo/0、/tyCo/1 等多个设备。

设备名称 驱动编号 私有数据 状态
/tyCo/0 0 regBase=0xFFE0, irq=5 已打开
/tyCo/1 0 regBase=0xFFE8, irq=6 空闲
/xxx/0 1 regBase=0x9000, irq=10 已打开
并发风险:多实例设备共享同一套入口点函数。当两个任务分别操作不同实例时,若驱动代码中使用了全局变量而非实例私有数据,将导致数据错乱。务必通过 DEV_HDR * 获取当前实例的私有结构。

3.8 本章小结

  • 驱动入口点函数表通过 iosDrvInstall() 注册,包含 open/close/read/write/ioctl 五个标准接口。
  • xxxDrv() 负责驱动安装,保存驱动编号;xxxDevCreate() 负责设备创建,分配私有结构并注册到设备链表。
  • DEV_HDR 是设备描述符的公共头部,驱动私有结构体应将其作为第一个成员,实现类型安全的指针转换。
  • 设备链表管理所有已注册设备,iosDevFind() 按名称查找,iosDevAdd() 插入新设备。
  • 多实例驱动必须使用实例私有数据,避免全局变量导致的并发问题。