3. 使用资源管理器框架

任务说明

在QNX系统中,资源管理器(Resource Manager)是设备驱动与用户空间交互的标准框架。本案例将基于QNX资源管理器框架创建一个名为/dev/mydevice的设备节点,实现基本的打开、关闭、读取和写入消息处理,并验证客户端能够通过标准文件I/O与该设备通信。

环境依赖

  • QNX SDP 7.0或更高版本
  • 目标板或QNX虚拟机
  • 编译工具链:qcc

核心实现

以下代码实现了一个最小化的资源管理器,注册设备节点并处理客户端消息。核心逻辑包括:初始化资源管理器属性、创建设备节点、分发处理函数、处理打开/关闭/读/写消息。

#include <sys/iofunc.h>
#include <sys/dispatch.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

// 设备上下文结构
typedef struct {
    iofunc_attr_t attr;
    char buffer[256];
    int buffer_len;
} my_device_t;

// 设备属性
static my_device_t my_device;

// 打开设备时的处理
static int my_open(resmgr_context_t *ctp, io_open_t *msg, void *extra) {
    return iofunc_open_default(ctp, msg, &my_device.attr, 0, 0);
}

// 关闭设备时的处理
static int my_close(resmgr_context_t *ctp, void *reserved, void *extra) {
    return iofunc_close_default(ctp, reserved, &my_device.attr);
}

// 读取设备数据
static int my_read(resmgr_context_t *ctp, io_read_t *msg, void *extra) {
    int bytes_read = 0;
    int nbytes = msg->nbytes;

    // 检查是否有数据可读
    if (my_device.buffer_len > 0) {
        bytes_read = (nbytes < my_device.buffer_len) ? nbytes : my_device.buffer_len;
        memcpy(msg->data, my_device.buffer, bytes_read);
        // 清除已读取的数据
        memmove(my_device.buffer, my_device.buffer + bytes_read, my_device.buffer_len - bytes_read);
        my_device.buffer_len -= bytes_read;
    }

    if (bytes_read > 0) {
        return _RESMGR_PTR(ctp, msg->data, bytes_read);
    }
    return _RESMGR_EOF;
}

// 写入设备数据
static int my_write(resmgr_context_t *ctp, io_write_t *msg, void *extra) {
    int nbytes = msg->nbytes;
    int bytes_written = 0;

    // 检查缓冲区空间
    if (my_device.buffer_len + nbytes < sizeof(my_device.buffer)) {
        memcpy(my_device.buffer + my_device.buffer_len, msg->data, nbytes);
        my_device.buffer_len += nbytes;
        bytes_written = nbytes;
    } else {
        // 缓冲区满,写入能容纳的部分
        int available = sizeof(my_device.buffer) - my_device.buffer_len;
        if (available > 0) {
            memcpy(my_device.buffer + my_device.buffer_len, msg->data, available);
            my_device.buffer_len += available;
            bytes_written = available;
        }
    }

    return _RESMGR_PTR(ctp, msg->data, bytes_written);
}

// 消息分发表
static resmgr_io_funcs_t io_funcs = {
    _RESMGR_IO_NFUNCS,
    my_open,        // 打开
    my_close,       // 关闭
    my_read,        // 读取
    my_write        // 写入
};

int main(int argc, char **argv) {
    dispatch_t *dpp;
    resmgr_attr_t resmgr_attr;
    resmgr_context_t *ctp;
    int id;

    // 初始化设备属性
    memset(&my_device, 0, sizeof(my_device));
    my_device.buffer_len = 0;
    iofunc_attr_init(&my_device.attr, S_IFCHR | 0666, 0, 0);

    // 创建分发器
    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;

    // 创建资源管理器
    id = resmgr_attach(dpp, &resmgr_attr, "/dev/mydevice", _FTYPE_ANY, 0, &io_funcs, &my_device);
    if (id == -1) {
        perror("resmgr_attach");
        return 1;
    }

    printf("资源管理器已启动,设备节点: /dev/mydevice\n");

    // 消息循环
    ctp = resmgr_context_alloc(dpp);
    while (1) {
        if (resmgr_block(ctp) == EOK) {
            resmgr_handler(ctp);
        }
    }

    return 0;
}

编译与运行

  1. 将上述代码保存为my_resource_mgr.c。
  2. 编译:qcc -o my_resource_mgr my_resource_mgr.c
  3. 在目标板上运行:./my_resource_mgr(需要root权限)

验证方法

在另一个终端中执行以下命令验证设备节点功能:

# 1. 确认设备节点存在
ls -l /dev/mydevice

# 2. 写入数据
echo "Hello QNX" > /dev/mydevice

# 3. 读取数据
cat /dev/mydevice

# 4. 预期输出
Hello QNX

通过条件:

  • ls -l /dev/mydevice能显示字符设备节点,权限为crw-rw-rw-。
  • 写入操作无错误返回。
  • cat命令能正确读出之前写入的字符串内容。
  • 程序运行期间无崩溃或错误日志输出。

关键说明

  • 资源管理器使用dispatch机制接收客户端消息,resmgr_attach将路径名/dev/mydevice绑定到消息处理函数。
  • 设备属性通过iofunc_attr_init初始化为字符设备,权限设为0666允许所有用户访问。
  • 读写操作使用内部缓冲区存储数据,写入的数据可被后续读取操作获取。
  • 若需支持多个客户端并发访问,可在此基础上增加互斥锁保护缓冲区。