4. 字符设备驱动框架与第一个驱动

在 INTEGRITY 实时操作系统上开发字符设备驱动,核心工作是围绕 DEV_DRIVER 结构体注册一组入口函数,让内核在应用层调用 open、close、read、write 时能够路由到驱动实现。与 Linux 的 file_operations 不同,INTEGRITY 使用 DEV_DRIVER 与 DEV_DEV 两层结构:前者描述驱动能力(函数指针表),后者描述具体设备实例(设备名、句柄、私有数据)。理解这两层关系,是写出最小可用驱动的第一步。

一、驱动框架的两层结构

INTEGRITY 的字符设备模型可以概括为“驱动注册 + 设备实例化”。DEV_DRIVER 是驱动级对象,包含 open、close、read、write、ioctl 等函数指针,以及驱动名和标志位;DEV_DEV 是设备级对象,包含设备名、指向所属 DEV_DRIVER 的指针、设备句柄和私有数据指针。应用通过设备名打开设备时,内核先找到 DEV_DEV,再通过其 driver 字段找到 DEV_DRIVER,最后调用对应的函数指针。

这种分层带来的直接好处是:同一份驱动代码可以实例化多个设备,每个设备拥有独立的私有数据,而函数实现完全复用。对于最小驱动,我们只需要一个驱动对象和一个设备对象。

INTEGRITY 字符设备驱动调用链 应用层(Application) open("/dev/mydev") · read(fd, buf, len) · write(fd, buf, len) · close(fd) 系统调用 / 设备名查找 INTEGRITY 内核(Kernel) DEV_DEV(设备实例) name = "mydev" driver → DEV_DRIVER · devHandle · privateData DEV_DRIVER(驱动能力) open · close · read · write · ioctl driverName · flags · 函数指针表 driver 指针 函数指针调用 硬件 / 虚拟设备 寄存器读写 · 内存缓冲区 · 中断控制器 · 自定义数据源 关键:DEV_DEV 通过 driver 指针关联 DEV_DRIVER,内核据此将应用请求路由到具体函数实现

二、最小驱动的实现步骤

下面以 INTEGRITY 的 ghs 工具链为例,给出一个最小字符设备驱动的骨架。该驱动不操作真实硬件,而是维护一个 256 字节的内核缓冲区,用于演示 read 与 write 的数据通路。

第一步:定义私有数据结构。每个设备实例需要保存自己的状态。对于最小驱动,私有数据只需一个缓冲区、一个写指针和一个读指针。

typedef struct {
    char     buffer[256];
    uint32_t write_pos;
    uint32_t read_pos;
    uint32_t count;
} MyDevPrivate;

static MyDevPrivate g_mydev_priv;

第二步:实现四个入口函数。函数签名必须与 DEV_DRIVER 中的函数指针类型一致。INTEGRITY 的 open 返回 Error 类型,read/write 返回实际传输的字节数,close 返回 Error。

static Error MyDev_Open(DEV_DEV *dev, const char *name, int mode, void **pCookie)
{
    MyDevPrivate *priv = (MyDevPrivate *)dev->privateData;
    priv->read_pos  = 0;
    priv->write_pos = 0;
    priv->count     = 0;
    *pCookie = priv;
    return NO_ERROR;
}

static Error MyDev_Close(DEV_DEV *dev, void *cookie)
{
    (void)dev; (void)cookie;
    return NO_ERROR;
}

static int MyDev_Read(DEV_DEV *dev, void *cookie, char *buf, int len)
{
    MyDevPrivate *priv = (MyDevPrivate *)cookie;
    int n = (len < (int)priv->count) ? len : (int)priv->count;
    for (int i = 0; i < n; i++) {
        buf[i] = priv->buffer[priv->read_pos];
        priv->read_pos = (priv->read_pos + 1) % sizeof(priv->buffer);
    }
    priv->count -= n;
    return n;
}

static int MyDev_Write(DEV_DEV *dev, void *cookie, const char *buf, int len)
{
    MyDevPrivate *priv = (MyDevPrivate *)cookie;
    int n = (len < (int)(sizeof(priv->buffer) - priv->count))
            ? len : (int)(sizeof(priv->buffer) - priv->count);
    for (int i = 0; i < n; i++) {
        priv->buffer[priv->write_pos] = buf[i];
        priv->write_pos = (priv->write_pos + 1) % sizeof(priv->buffer);
    }
    priv->count += n;
    return n;
}

第三步:填充 DEV_DRIVER 并注册。将四个函数指针填入 DEV_DRIVER 结构体,调用 CreateDevice 或 RegisterDevice 完成注册。注册时需指定设备名,应用层通过该名称打开设备。

static DEV_DRIVER MyDev_Driver = {
    .driverName = "MyCharDev",
    .open       = MyDev_Open,
    .close      = MyDev_Close,
    .read       = MyDev_Read,
    .write      = MyDev_Write,
    .ioctl      = NULL,
    .flags      = 0
};

static DEV_DEV MyDev_Device = {
    .name        = "/dev/mydev",
    .driver      = &MyDev_Driver,
    .privateData = &g_mydev_priv
};

Error MyDev_Init(void)
{
    return RegisterDevice(&MyDev_Device);
}

三、加载到 INTEGRITY 目标系统验证

驱动编译为 .a 或 .o 后,需要链接进 INTEGRITY 内核镜像或作为可加载模块。典型流程是:在 integrity.gpj 工程文件中加入驱动源文件,重新构建内核,然后通过 MULTI 调试器或串口终端启动目标板。

验证时,在应用任务中调用 open("/dev/mydev", ...),若返回有效文件描述符,说明 DEV_DEV 查找和 open 入口均正常。随后写入一段字符串,再读回并比较内容。以下是一个最小验证任务:

void TestTask(void)
{
    int fd = open("/dev/mydev", O_RDWR);
    if (fd < 0) { printf("open failed\n"); return; }

    const char *msg = "INTEGRITY";
    int w = write(fd, msg, 9);
    printf("write %d bytes\n", w);

    char buf[16] = {0};
    int r = read(fd, buf, 9);
    printf("read %d bytes: %s\n", r, buf);

    close(fd);
}

预期输出为 write 9 bytes 和 read 9 bytes: INTEGRITY。若 open 失败,优先检查设备名是否与注册名一致、RegisterDevice 是否在应用启动前调用;若 read 返回 0,检查 write 是否成功以及 count 是否被正确更新。

调试提示:INTEGRITY 的 open 入口中 pCookie 是输出参数,用于把私有数据指针传递给后续 read/write。若忘记赋值,后续函数将收到空指针,导致异常。建议在 open 中先校验 dev->privateData 非空。

四、常见问题与排查

现象可能原因排查方法
open 返回错误设备名不匹配或未注册核对 DEV_DEV.name 与调用名;确认 RegisterDevice 返回值
read 返回 0缓冲区为空或 count 未更新检查 write 返回值;在 write 后打印 count
write 返回 0缓冲区已满检查 count 是否等于 256;先读后写
系统挂起函数指针为空或签名不匹配确认 DEV_DRIVER 中所有指针已赋值;核对参数类型