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开始 - 并发访问未加锁,仅适用于单客户端测试场景