当前位置: 首页 > web >正文

【NextPilot日志移植】Logger::run()主循环解析

📌 NextPilot 日志系统主循环逐行解析


🧱 函数作用

Logger::run() 是日志系统的主循环函数,负责:

  • 控制日志启停(根据飞行状态、MAVLink 命令)
  • 实时订阅传感器数据(如 IMU、电池、GPS)
  • 写入日志到文件(.ulg)或通过 MAVLink 传输
  • 管理缓冲区、处理丢包、记录性能数据

📁 移植关键性

此函数控制日志系统的整个生命周期,包括初始化、数据采集、缓冲区管理、资源清理等。移植时需重点关注:

  1. uORB 主题订阅机制(如 orb_subscribe()
  2. 多线程安全(如 _writer.lock() / unlock()
  3. 日志文件路径与权限(如 mkdir()LOG_ROOT
  4. 定时器与回调(如 hrt_call_every()

📄 逐行解析

1. 初始化日志根目录

if (_writer.backend() & LogWriter::BackendFile) {int mkdir_ret = mkdir(LOG_ROOT[(int)LogType::Full], S_IRWXU | S_IRWXG | S_IRWXO);if (mkdir_ret == 0) {PX4_INFO("log root dir created: %s", LOG_ROOT[(int)LogType::Full]);} else if (errno != -EEXIST) {PX4_ERR("failed creating log root dir: %s (%i)", LOG_ROOT[(int)LogType::Full], errno);if ((_writer.backend() & ~LogWriter::BackendFile) == 0) {return;}}
}
  • 功能:创建日志存储根目录(如 /fs/microsd/log
  • 关键点
    • LOG_ROOT 定义日志路径,需适配目标平台
    • mkdir() 需支持跨平台(如 NuttX、Linux、Windows)
    • 若目录已存在(errno == EEXIST),继续执行

2. 订阅参数更新主题

uORB::Subscription parameter_update_sub(ORB_ID(parameter_update));
  • 功能:创建 parameter_update 主题的订阅者,监听参数变化
  • 关键点
    • ORB_ID(parameter_update) 获取主题 ID
    • 需确保 uORB::Subscription 类已正确实现

3. 初始化所有订阅主题

if (!initialize_topics()) {return;
}
  • 功能:调用 initialize_topics() 订阅所有传感器数据(如 IMU、电池)
  • 关键点
    • initialize_topics() 需正确实现 uORB 主题的订阅逻辑
    • 若订阅失败(如主题未定义),函数返回 false

4. 计算最大日志消息缓冲区大小

int max_msg_size = 0;
for (int sub = 0; sub < _num_subscriptions; ++sub) {if (_subscriptions[sub].get_topic()->o_size > max_msg_size) {max_msg_size = _subscriptions[sub].get_topic()->o_size;}
}
max_msg_size += sizeof(ulog_message_data_s);
  • 功能:确定缓冲区大小,确保能容纳最大日志消息
  • 关键点
    • _subscriptions[sub].get_topic()->o_size 是传感器数据结构体大小
    • ulog_message_data_s 是 ULog 消息头结构体

5. 分配日志消息缓冲区

if (max_msg_size > _msg_buffer_len) {if (_msg_buffer) {delete[] (_msg_buffer);}_msg_buffer_len = max_msg_size;_msg_buffer     = new uint8_t[_msg_buffer_len];if (!_msg_buffer) {PX4_ERR("failed to alloc message buffer");return;}
}
  • 功能:动态分配缓冲区用于存储日志消息
  • 关键点
    • 缓冲区大小需足够大,避免溢出
    • 若已有缓冲区,先释放再重新分配

6. 初始化日志写入器

if (!_writer.init()) {PX4_ERR("writer init failed");return;
}
  • 功能:调用 LogWriterinit() 方法初始化底层写入器(如文件系统、MAVLink)
  • 关键点
    • LogWriter 是抽象类,需实现 init() 的具体逻辑(如文件打开、网络连接)
    • 若初始化失败,直接返回

7. 注册关机钩子

register_shutdown_hook(&Logger::request_stop_static);
  • 功能:注册关机回调函数,确保日志在系统关闭前保存
  • 关键点
    • request_stop_static() 是静态函数,用于触发日志停止
    • 需实现 register_shutdown_hook() 的跨平台兼容性

8. 启动日志文件

if ((_log_mode == LogMode::boot_until_disarm || _log_mode == LogMode::boot_until_shutdown) && !disable_boot_logging) {start_log_file(LogType::Full);
}
  • 功能:根据日志模式启动日志文件(如开机即开始记录)
  • 关键点
    • _log_mode 定义日志记录策略(如 boot_until_disarm 表示从开机到解锁停止)
    • start_log_file() 需正确实现日志文件名生成、目录创建、文件打开等逻辑

9. 初始化定时器回调

struct hrt_call timer_call{};
rt_sem_init(&_timer_callback_data.semaphore, "callback_lock", 0, RT_IPC_FLAG_PRIO);
  • 功能:初始化高精度定时器,用于定期触发日志写入
  • 关键点
    • hrt_call_every() 设置定时器回调函数(timer_callback
    • rt_sem 是实时信号量,需适配目标平台(如 NuttX、FreeRTOS)

10. 主循环逻辑

while (!should_exit()) {// 检查是否需要启动/停止日志const bool logging_started = start_stop_logging();// 处理 MAVLink 日志启停命令handle_vehicle_command_update();// 如果看门狗触发,初始化负载输出if (_timer_callback_data.watchdog_triggered.load()) {_timer_callback_data.watchdog_triggered.store(false);initialize_load_output(PrintLoadReason::Watchdog);}// 获取当前时间const hrt_abstime loop_time = hrt_absolute_time();// 如果日志已启动,开始记录数据if (_writer.is_started(LogType::Full)) {if (!was_started) {adjust_subscription_updates();}// 输出 CPU 负载日志if (_next_load_print != 0 && loop_time >= _next_load_print) {_next_load_print = 0;write_load_output();if (_should_stop_file_log) {_should_stop_file_log = false;stop_log_file(LogType::Full);continue;}}// 参数更新时写入日志if (!_should_stop_file_log) {if (parameter_update_sub.updated()) {parameter_update_s pupdate;parameter_update_sub.copy(&pupdate);write_changed_parameters(LogType::Full);}}// 获取缓冲区锁_writer.lock();// 遍历所有订阅主题,写入更新数据for (int sub_idx = 0; sub_idx < _num_subscriptions; ++sub_idx) {LoggerSubscription &sub = _subscriptions[sub_idx];const bool try_to_subscribe = (sub_idx == next_subscribe_topic_index);if (copy_if_updated(sub_idx, _msg_buffer + sizeof(ulog_message_data_s), try_to_subscribe)) {// 构建日志消息头const size_t   msg_size       = sizeof(ulog_message_data_s) + sub.get_topic()->o_size_no_padding;const uint16_t write_msg_size = static_cast<uint16_t>(msg_size - ULOG_MSG_HEADER_LEN);const uint16_t write_msg_id   = sub.msg_id;// 写入日志消息头_msg_buffer[0] = (uint8_t)write_msg_size;_msg_buffer[1] = (uint8_t)(write_msg_size >> 8);_msg_buffer[2] = static_cast<uint8_t>(ULogMessageType::DATA);_msg_buffer[3] = (uint8_t)write_msg_id;_msg_buffer[4] = (uint8_t)(write_msg_id >> 8);// 写入完整日志if (write_message(LogType::Full, _msg_buffer, msg_size)) {// 记录字节数(调试用)}// 如果是任务日志,按需写入if (sub_idx < _num_mission_subs) {if (_writer.is_started(LogType::Mission)) {if (_mission_subscriptions[sub_idx].next_write_time < (loop_time / 100000)) {write_message(LogType::Mission, _msg_buffer, msg_size);}}}}}// 处理事件日志(如系统错误、警告)handle_event_updates(total_bytes);// 处理普通日志消息(如 PX4_WARN、PX4_ERR)log_message_s log_message;if (_log_message_sub.update(&log_message)) {const char *message     = (const char *)log_message.text;int         message_len = strlen(message);if (message_len > 0) {uint16_t write_msg_size = sizeof(ulog_message_logging_s) - sizeof(ulog_message_logging_s::message) - ULOG_MSG_HEADER_LEN + message_len;_msg_buffer[0]          = (uint8_t)write_msg_size;_msg_buffer[1]          = (uint8_t)(write_msg_size >> 8);_msg_buffer[2]          = static_cast<uint8_t>(ULogMessageType::LOGGING);_msg_buffer[3]          = log_message.severity + '0';rt_memcpy(_msg_buffer + 4, &log_message.timestamp, sizeof(ulog_message_logging_s::timestamp));rt_strncpy((char *)(_msg_buffer + 12), message, sizeof(ulog_message_logging_s::message));write_message(LogType::Full, _msg_buffer, write_msg_size + ULOG_MSG_HEADER_LEN);}}// 写入同步魔数(用于日志校验)if (loop_time - _last_sync_time > 500_ms) {uint16_t write_msg_size = static_cast<uint16_t>(sizeof(ulog_message_sync_s) - ULOG_MSG_HEADER_LEN);_msg_buffer[0]          = (uint8_t)write_msg_size;_msg_buffer[1]          = (uint8_t)(write_msg_size >> 8);_msg_buffer[2]          = static_cast<uint8_t>(ULogMessageType::SYNC);_msg_buffer[3]  = 0x2F;_msg_buffer[4]  = 0x73;_msg_buffer[5]  = 0x13;_msg_buffer[6]  = 0x20;_msg_buffer[7]  = 0x25;_msg_buffer[8]  = 0x0C;_msg_buffer[9]  = 0xBB;_msg_buffer[10] = 0x12;write_message(LogType::Full, _msg_buffer, write_msg_size + ULOG_MSG_HEADER_LEN);_last_sync_time = loop_time;}// 更新缓冲区统计信息for (int i = 0; i < (int)LogType::Count; ++i) {if (!_statistics[i].dropout_start && (_writer.get_buffer_fill_count_file((LogType)i > _statistics[i].high_water)) {_statistics[i].high_water = _writer.get_buffer_fill_count_file((LogType)i;}}// 发布日志状态publish_logger_status();// 释放缓冲区锁_writer.unlock();// 通知写入线程_writer.notify();// 更新订阅检查索引if (next_subscribe_topic_index != -1) {if (++next_subscribe_topic_index >= _num_subscriptions) {next_subscribe_topic_index = -1;next_subscribe_check       = loop_time + TRY_SUBSCRIBE_INTERVAL;}} else if (loop_time > next_subscribe_check) {next_subscribe_topic_index = 0;}// 调试输出debug_print_buffer(total_bytes, timer_start);was_started = true;} else {// 未记录日志时尝试提前订阅新主题if (next_subscribe_topic_index != -1) {if (!_subscriptions[next_subscribe_topic_index].valid()) {_subscriptions[next_subscribe_topic_index].subscribe();}if (++next_subscribe_topic_index >= _num_subscriptions) {next_subscribe_topic_index = -1;next_subscribe_check       = loop_time + TRY_SUBSCRIBE_INTERVAL;}} else if (loop_time > next_subscribe_check) {next_subscribe_topic_index = 0;}was_started = false;}// 更新参数update_params();// 等待下一次循环if (polling_topic_sub) {lockstep_progress(_lockstep_component);} else {rt_sem_take(&_timer_callback_data.semaphore, RT_WAITING_FOREVER);}rt_thread_mdelay(_log_interval / 1000 + 1);
}

🔁 循环核心逻辑流程图

[主循环开始]↓
检查日志启停条件(start_stop_logging)↓
处理 MAVLink 命令(handle_vehicle_command_update)↓
如果看门狗触发,初始化负载输出↓
若日志已启动:├─ 更新订阅索引(adjust_subscription_updates)├─ 遍历所有订阅主题:│   ├─ 检查是否有更新(copy_if_updated)│   ├─ 构建日志消息头│   └─ 写入日志(write_message)├─ 处理事件日志(handle_event_updates)├─ 处理普通日志(如 PX4_WARN)├─ 写入同步魔数(用于日志校验)├─ 更新缓冲区统计信息(丢包、高水位)├─ 发布日志状态(publish_logger_status)├─ 释放锁(_writer.unlock())└─ 通知写入线程(_writer.notify())
否则:↓
尝试提前订阅新主题(加速首次采集)↓
更新参数(update_params)↓
等待下一次循环(rt_sem_take / rt_thread_mdelay)

🧠 核心函数详解

1. start_stop_logging()

  • 作用:根据飞行状态(如解锁、加锁)或 MAVLink 命令控制日志启停
  • 关键逻辑
    • 如果飞行器已解锁且日志模式为 boot_until_disarm,启动日志
    • 如果飞行器加锁,停止日志
    • 如果收到 VEHICLE_CMD_LOGGING_STOP 命令,停止日志

2. handle_vehicle_command_update()

  • 作用:处理来自 MAVLink 的日志控制命令(如 VEHICLE_CMD_LOGGING_START
  • 关键逻辑
    • 调用 ack_vehicle_command(...) 回复命令处理结果
    • 根据命令类型启动或停止日志

3. copy_if_updated()

  • 作用:检查订阅的主题是否有新数据,若有则复制到缓冲区
  • 关键逻辑
    • orb_check() 检查是否有新数据
    • orb_copy() 将数据复制到 _msg_buffer

4. write_message()

  • 作用:将日志数据写入文件或 MAVLink
  • 关键逻辑
    • 调用 _writer.write_message() 实际写入
    • 记录丢包和缓冲区使用情况

5. publish_logger_status()

  • 作用:发布日志状态(如缓冲区使用率、写入速度)
  • 关键逻辑
    • 构造 logger_status_s 结构体
    • 调用 orb_publish() 发布状态消息

🔧 移植注意事项

步骤移植建议
uORB 主题订阅确保 ORB_ID(...) 定义与目标平台一致
日志文件路径适配文件系统(如 LOG_ROOT 支持 SD 卡、NAND、网络)
多线程安全使用互斥锁(如 pthread_mutex_trt_mutex
缓冲区管理避免缓冲区溢出,合理设置 _log_interval
日志文件写入实现 LogWriterFile::start_log_file() 的文件写入逻辑
MAVLink 支持实现 LogWriterMavlink::start_log() 的网络传输逻辑

📦 相关结构体与枚举

1. ULogMessageType

enum class ULogMessageType {DATA = 'D',FORMAT = 'F',INFO = 'I',INFO_MULTIPLE = 'M',PARAMETER = 'P',SYNC = 'S',DROPOUT = 'L',LOGGING = 'T'
};

2. logger_status_s

struct logger_status_s {uint64_t timestamp;size_t buffer_used_bytes;size_t buffer_size_bytes;size_t num_messages;
};

📌 总结

功能模块核心函数移植难点
日志启停控制start_stop_logging()飞行状态检测、MAVLink 命令处理
主题订阅copy_if_updated()uORB 主题注册与数据获取
日志写入write_message()文件/MAVLink 写入逻辑实现
缓冲区管理_msg_buffer跨平台内存分配与释放
同步魔数write_message(..., ULogMessageType::SYNC)确保日志校验正确
性能监控write_perf_data()资源占用统计与分析

📝 扩展阅读

  • uORB 主题定义msg/ 目录下的 .msg 文件
  • ULog 格式定义src/modules/logger/ulog.h
  • 日志文件分析工具:QGroundControl、PlotJuggler
http://www.xdnf.cn/news/5111.html

相关文章:

  • 图像配准简单概述
  • 日常知识点之随手问题整理(思考单播,组播,广播哪个更省带宽)
  • MySQL初阶:数据库约束和表的设计
  • Linux基础(关于进程相关命令)
  • WPDRRC 模型:构建动态闭环的信息安全防御体系
  • 深度学习系统学习系列【8】之设计卷积神经网络架构(Pytorch版本)
  • RHCSA Linux系统软件管理和进程管理
  • flowable-适配其他类型数据库,不修改源码解决方案
  • 位运算(二进制中1的个数)
  • uniapp自定义导航栏搭配插槽
  • Linux的进程与线程
  • 笔记,麦克风的灵敏度
  • Jedis高版本的JedisPoolConfig没有maxActive和maxWait
  • Linux使用Docker部署安装应用
  • Papyrus字体介绍
  • 为什么消息队列系统不像数据库系统那样可以配置读写分离?
  • Docker基础入门:容器化技术详解
  • PH热榜 | 2025-05-09
  • class path resource [] cannot be resolved to absolute file path
  • powershell_bypass.cna 插件(适配 Cobalt Strike 4.0 的免费版本下载地址)
  • FreeRTOS菜鸟入门(十四)·事件
  • Prometheus生产实战全流程详解(存储/负载/调度篇)
  • 认识拦截器
  • 如何获取NumPy数组中前N个最大值的索引
  • Qt6.x检查网络是否在线(与Qt 5.x不同)
  • 有关SOA和SpringCloud的区别
  • 软件设计师教程——第一章 计算机系统知识(下)
  • 数据库插入数据时自动生成
  • Python开发后端InfluxDB数据库测试接口
  • Python 数据分析与可视化:开启数据洞察之旅(5/10)