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系统中的唯一标识。
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系统执行以下步骤:
- 名称解析:遍历设备链表,调用
iosDevFind()查找名称匹配的DEV_HDR。 - 驱动关联:从
DEV_HDR.drvNum获取驱动编号,定位对应的入口点函数表。 - 调用xxxOpen:调用驱动注册的
xxxOpen()函数,传入DEV_HDR *指针。 - 返回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()插入新设备。 - 多实例驱动必须使用实例私有数据,避免全局变量导致的并发问题。