2. 编写第一个字符设备驱动

任务目标

在QNX环境下实现一个最小字符设备驱动,完成设备注册,并实现open/read/write/close四个核心文件操作。驱动将维护一个内核缓冲区,用户态通过read/write系统调用与之交互。

环境与依赖

  • QNX SDP 7.0或更高版本,目标板为x86_64或ARM64
  • 宿主机安装QNX Momentics IDE或命令行工具链
  • 需要目标机root权限加载驱动

驱动实现

创建设备驱动源文件simple_char.c,完整代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <devctl.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>

#define DEVICE_NAME "simple_char"
#define BUF_SIZE 256

static char device_buffer[BUF_SIZE];
static int buffer_len = 0;

static int dev_open(resmgr_context_t *ctp, io_open_t *msg, void *extra) {
    return iofunc_open_default(ctp, msg, IOFLAG_O_DEFAULT, 0, 0);
}

static int dev_read(resmgr_context_t *ctp, io_read_t *msg, iofunc_ocb_t *ocb) {
    int bytes_to_read;
    int bytes_left = buffer_len - ocb->offset;

    if (bytes_left <= 0) {
        return EOK;  // EOF
    }

    bytes_to_read = min(msg->nbytes, bytes_left);
    memset(msg, 0, sizeof(*msg));  // 复用消息缓冲区作为数据返回区

    memcpy(msg, device_buffer + ocb->offset, bytes_to_read);
    ocb->offset += bytes_to_read;

    return _RESMGR_PTR(ctp, msg, bytes_to_read);
}

static int dev_write(resmgr_context_t *ctp, io_write_t *msg, iofunc_ocb_t *ocb) {
    int bytes_to_write = msg->nbytes;

    if (ocb->offset + bytes_to_write > BUF_SIZE) {
        bytes_to_write = BUF_SIZE - ocb->offset;
    }

    memcpy(device_buffer + ocb->offset, msg->data, bytes_to_write);
    ocb->offset += bytes_to_write;
    buffer_len = ocb->offset;

    return _RESMGR_PTR(ctp, msg, bytes_to_write);
}

static int dev_close(resmgr_context_t *ctp, void *reserved, iofunc_ocb_t *ocb) {
    return iofunc_close_default(ctp, reserved, ocb);
}

int main(int argc, char **argv) {
    dispatch_t *dpp;
    resmgr_attr_t resmgr_attr;
    resmgr_connect_funcs_t connect_funcs;
    resmgr_io_funcs_t io_funcs;
    iofunc_attr_t attr;
    int id;

    dpp = dispatch_create();
    if (dpp == NULL) {
        perror("dispatch_create");
        return 1;
    }

    memset(&resmgr_attr, 0, sizeof(resmgr_attr));
    resmgr_attr.nparts_max = 1;
    resmgr_attr.msg_max_size = 2048;

    memset(&connect_funcs, 0, sizeof(connect_funcs));
    connect_funcs.open = dev_open;

    memset(&io_funcs, 0, sizeof(io_funcs));
    io_funcs.read = dev_read;
    io_funcs.write = dev_write;
    io_funcs.close_dup = dev_close;

    memset(&attr, 0, sizeof(attr));
    iofunc_attr_init(&attr, S_IFCHR | 0666, 0, 0);

    id = resmgr_attach(dpp, &resmgr_attr, "/dev/simple_char",
                       _FTYPE_ANY, 0, &connect_funcs, &io_funcs, &attr);
    if (id == -1) {
        perror("resmgr_attach");
        return 1;
    }

    printf("Device /dev/simple_char registered, waiting for requests...\n");
    dispatch_block(dpp);
    return 0;
}

编译与部署

在QNX目标机上执行以下命令编译(或使用QNX Momentics IDE创建C项目并添加此文件):

qcc -Vgcc_ntox86_64 -o simple_char simple_char.c
# ARM64目标板使用: qcc -Vgcc_ntoaarch64 -o simple_char simple_char.c

将编译产物拷贝到目标机,以root身份运行:

./simple_char &
ls -l /dev/simple_char   # 确认设备节点已创建

验证方法

在目标机shell中执行以下命令验证驱动功能:

# 测试write
echo "Hello QNX" > /dev/simple_char

# 测试read
cat /dev/simple_char
# 预期输出: Hello QNX

# 测试open/close(通过上述命令已隐式完成)
# 测试多次写入覆盖
echo "First" > /dev/simple_char
echo "Second" > /dev/simple_char
cat /dev/simple_char
# 预期输出: Second(偏移量从0开始,覆盖写入)

通过条件:cat能正确读出之前echo写入的内容,且dmesg或控制台无错误输出。若写入后立即读取返回空,检查驱动是否以root运行以及设备节点权限。

关键说明

  • 驱动使用QNX资源管理器框架,resmgr_attach将设备路径/dev/simple_char绑定到文件操作回调
  • 缓冲区为全局静态数组,驱动退出后数据丢失,这是预期行为
  • 当前实现未处理lseek,文件偏移量由OCB自动管理,每次open从0开始
  • 并发访问未加锁,仅适用于单客户端测试场景