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

openwrt ubus 深入分析

🔍 ubus 深入分析

1️⃣ 架构概述

ubus 采用传统的客户端-服务器(C/S) 模型,其核心组件包括:

  • ubusd 守护进程:作为服务器端,负责管理所有客户端连接、对象注册以及消息路由。
  • 客户端库 (libubus):提供给应用程序使用的编程接口,方便进程创建客户端、注册对象和方法,以及调用远程方法。
  • 命令行工具 (ubus):用于通过 shell 与 ubus 交互,例如列出注册的对象、调用方法等。

下图展示了 ubus 的整体架构和通信流程:

客户端A (服务提供者)ubusd (服务器)客户端B (调用者)连接与注册阶段连接请求返回 HELLO 确认注册对象及方法 (如 echo)发现与调用阶段连接请求返回 HELLO 确认查询对象 (如 lookup echo)返回对象 ID调用方法 (invoke echo, 含参数)消息路由转发调用请求 (含参数)处理请求执行本地方法返回方法执行结果转发响应转发结果数据处理响应客户端A (服务提供者)ubusd (服务器)客户端B (调用者)

这种设计使得两个客户端之间的所有通信都必须通过 ubusd 中转。客户端之间不直接建立连接。

2️⃣ 核心数据结构

理解 ubus 的核心数据结构对于深入其工作原理至关重要。

2.1 struct ubus_context

代表一个 ubus 客户端连接上下文,每个连接到 ubusd 的客户端都维护一个此结构。

struct ubus_context {struct list_head requests;       // 请求列表struct avl_tree objects;         // 注册的对象树struct list_head pending;        // 挂起的请求struct uloop_fd sock;            // 与ubusd通信的socketuint32_t local_id;               // ubusd分配的客户端IDuint16_t request_seq;            // 请求序列号int stack_depth;void (*connection_lost)(struct ubus_context *ctx); // 连接断开回调// ... 其他字段
};

2.2 struct ubus_object

代表一个在 ubus 上注册的对象,该对象包含多个可供调用的方法。

struct ubus_object {struct avl_node avl;             // 用于插入ubus_context的objects树const char *name;                // 对象名 (e.g., "network")uint32_t id;                     // 由ubusd分配的对象IDconst char *path;struct ubus_object_type *type;ubus_state_handler_t subscribe_cb;bool has_subscribers;const struct ubus_method *methods; // 方法数组int n_methods;                   // 方法数量// ... 其他字段
};

2.3 struct ubus_method

定义了对象提供的一个具体方法及其处理函数和策略。

struct ubus_method {const char *name;               // 方法名 (e.g., "restart")ubus_handler_t handler;         // 方法处理回调函数unsigned long mask;const struct blobmsg_policy *policy; // 参数策略(定义期望的参数)int n_policy;                   // 策略条数
};

2.4 struct blobmsg_policy

用于定义方法参数的策略,指导消息的解析和验证。

struct blobmsg_policy {const char *name;               // 参数名enum blobmsg_type type;         // 参数类型 (e.g., BLOBMSG_TYPE_STRING)
};

2.5 struct ubus_request_data

代表一个正在处理的调用请求,通常在处理函数中用于回复调用者。

struct ubus_request_data {uint32_t object;uint32_t peer;uint16_t seq;/* internal use */bool deferred;int fd;
};

这些核心数据结构共同协作,管理客户端连接、对象注册、方法调用以及消息传递。

3️⃣ 实现机制

3.1 消息格式与序列化

ubus 使用 blobmsg(Binary Large Object Message)格式来序列化数据,这是一种二进制的、类似 JSON 格式的编码方式。消息通常包含一个消息头(struct ubus_msghdr)和负载数据(blobmsg 格式的有效载荷)。

3.2 通信流程

  1. 连接管理
    • 客户端通过 ubus_connect()ubus_auto_connect() 连接到 ubusd 的 Unix Domain Socket(默认路径为 /var/run/ubus.sock)。
    • 连接成功后,ubusd 会为客户端分配一个唯一的 local_id
  2. 对象注册
    • 服务提供者客户端使用 ubus_add_object() 将其 ubus_object 注册到 ubusd
    • ubusd 收到注册请求后,会为对象分配一个唯一 ID,并将其存入数据库(avl_tree),以便后续查找。
  3. 方法调用
    1. 查找对象:调用者首先向 ubusd 发送 UBUS_MSG_LOOKUP 消息,查找指定名称的对象 ID。
    2. 调用方法:获得对象 ID 后,调用者发送 UBUS_MSG_INVOKE 消息,其中包含对象 ID、方法名和参数(blobmsg 格式)。
    3. 路由与转发ubusd 根据对象 ID 找到对应的客户端连接,将调用请求转发给该客户端。
    4. 处理与回复:服务提供者客户端收到请求后,解析参数,找到对应的 ubus_method 并执行其 handler 函数。处理完成后,使用 ubus_send_reply() 将结果返回给 ubusd,再由 ubusd 转发给原始调用者。
  4. 事件机制
    除了 RPC 调用,ubus 还提供了发布-订阅模式的事件机制。
    • 客户端可以使用 ubus_register_event_handler() 订阅特定事件。
    • 任何客户端都可以使用 ubus_send_event() 发布一个事件,ubusd 会将该事件通知给所有订阅者。

4️⃣ 一个简单的 ubus 实例

下面是一个简单的 ubus 服务端和客户端示例,演示如何注册一个对象并提供方法。

4.1 服务端代码 (ubus_echo_server.c)

这个服务端注册了一个名为 “echo” 的对象,该对象提供了一个名为 “repeat” 的方法。该方法接收一个字符串和一个数字,然后将该字符串重复数字指定的次数并返回。

#include <libubox/blobmsg_json.h>
#include <libubus.h>
#include <stdio.h>
#include <string.h>static struct ubus_context *ctx;/* 定义方法参数的策略 */
enum {ECHO_MSG,ECHO_TIMES,__ECHO_MAX
};static const struct blobmsg_policy echo_policy[] = {[ECHO_MSG] = { .name = "msg", .type = BLOBMSG_TYPE_STRING },[ECHO_TIMES] = { .name = "times", .type = BLOBMSG_TYPE_INT32 },
};/* echo方法的处理函数 */
static int ubus_echo_repeat(struct ubus_context *ctx, struct ubus_object *obj,struct ubus_request_data *req, const char *method,struct blob_attr *msg) {struct blob_attr *tb[__ECHO_MAX];char *received_msg = NULL;int times = 0;struct blob_buf b = {};char result_str[1024] = {0}; // 简单起见,固定大小int i;/* 解析传入的参数 */blobmsg_parse(echo_policy, __ECHO_MAX, tb, blob_data(msg), blob_len(msg));if (!tb[ECHO_MSG] || !tb[ECHO_TIMES]) {fprintf(stderr, "Error: Missing required parameters (msg and times).\n");return UBUS_STATUS_INVALID_ARGUMENT;}received_msg = blobmsg_get_string(tb[ECHO_MSG]);times = blobmsg_get_u32(tb[ECHO_TIMES]);if (times <= 0) {fprintf(stderr, "Error: 'times' must be a positive integer.\n");return UBUS_STATUS_INVALID_ARGUMENT;}/* 构建结果字符串 */result_str[0] = '\0';for (i = 0; i < times && strlen(result_str) < sizeof(result_str) - strlen(received_msg); i++) {strcat(result_str, received_msg);}/* 准备回复数据 */blob_buf_init(&b, 0);blobmsg_add_string(&b, "original_message", received_msg);blobmsg_add_u32(&b, "repeated_times", times);blobmsg_add_string(&b, "result", result_str);/* 发送回复 */ubus_send_reply(ctx, req, b.head);/* 释放blob_buf资源 */blob_buf_free(&b);return UBUS_STATUS_OK;
}/* 定义对象的方法列表 */
static struct ubus_method echo_methods[] = {UBUS_METHOD("repeat", ubus_echo_repeat, echo_policy),
};/* 定义对象类型 */
static struct ubus_object_type echo_obj_type =UBUS_OBJECT_TYPE("echo", echo_methods);/* 定义对象本身 */
static struct ubus_object echo_obj = {.name = "echo",.type = &echo_obj_type,.methods = echo_methods,.n_methods = ARRAY_SIZE(echo_methods),
};/* 连接断开回调 */
static void connection_lost(struct ubus_context *ctx) {fprintf(stderr, "UBUS connection lost. Exiting.\n");// 可以考虑在这里添加重连逻辑exit(1);
}int main(int argc, char *argv[]) {const char *ubus_socket = NULL; // 默认NULL表示使用默认路径int ret;/* 初始化uloop(ubus依赖的事件循环) */uloop_init();/* 连接到ubusd */ctx = ubus_connect(ubus_socket);if (!ctx) {fprintf(stderr, "Failed to connect to ubusd.\n");return 1;}ctx->connection_lost = connection_lost;/* 将socket fd加入到事件循环中监听 */ubus_add_uloop(ctx);/* 向ubusd注册我们的echo对象 */ret = ubus_add_object(ctx, &echo_obj);if (ret) {fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));ubus_free(ctx);return 1;}printf("Echo server object registered successfully.\n");/* 进入事件处理循环 */uloop_run();/* 清理资源 */ubus_free(ctx);uloop_done();return 0;
}

4.2 客户端代码 (ubus_echo_client.c)

这个客户端通过命令行参数获取要发送的消息和重复次数,然后调用服务端的 “echo” 对象的 “repeat” 方法。

#include <libubox/blobmsg_json.h>
#include <libubus.h>
#include <stdio.h>static struct ubus_context *ctx;
static uint32_t echo_obj_id;
static int callback_received = 0;/* 调用结果的回调函数 */
static void echo_call_callback(struct ubus_request *req, int type, struct blob_attr *msg) {struct blob_buf *b = (struct blob_buf *)req->priv;if (!msg) {fprintf(stderr, "Error: Received empty response.\n");callback_received = 1;return;}/* 将收到的blobmsg属性解析成JSON字符串并打印 */char *json_str = blobmsg_format_json(msg, true);if (json_str) {printf("Server response:\n%s\n", json_str);free(json_str);} else {fprintf(stderr, "Error formatting response.\n");}callback_received = 1;
}int main(int argc, char *argv[]) {const char *ubus_socket = NULL;int ret;struct blob_buf b = {};unsigned int times_to_repeat;if (argc != 3) {fprintf(stderr, "Usage: %s <message> <times>\n", argv[0]);return 1;}times_to_repeat = atoi(argv[2]);if (times_to_repeat <= 0) {fprintf(stderr, "Error: 'times' must be a positive integer.\n");return 1;}/* 初始化uloop */uloop_init();/* 连接到ubusd */ctx = ubus_connect(ubus_socket);if (!ctx) {fprintf(stderr, "Failed to connect to ubusd.\n");return 1;}/* 查找echo对象的ID */ret = ubus_lookup_id(ctx, "echo", &echo_obj_id);if (ret || !echo_obj_id) {fprintf(stderr, "Failed to lookup 'echo' object: %s\n", ubus_strerror(ret));ubus_free(ctx);return 1;}/* 准备调用参数 */blob_buf_init(&b, 0);blobmsg_add_string(&b, "msg", argv[1]);blobmsg_add_u32(&b, "times", times_to_repeat);/* 发起异步调用 */ret = ubus_invoke(ctx, echo_obj_id, "repeat", b.head, echo_call_callback, &b, 3000);if (ret) {fprintf(stderr, "Failed to invoke method: %s\n", ubus_strerror(ret));blob_buf_free(&b);ubus_free(ctx);return 1;}/* 等待回调函数被执行(收到回复) */while (!callback_received) {ubus_handle_event(ctx); // 处理ubus事件}/* 清理资源 */blob_buf_free(&b);ubus_free(ctx);uloop_done();return 0;
}

4.3 编译和运行

  1. 编译
    在 OpenWRT 环境中,通常需要将这些源文件加入到你的软件包的 Makefile 中。如果是手动编译,需要链接 ubusuboxblobmsg_json 等库:

    # 服务端
    $(CC) -o ubus_echo_server ubus_echo_server.c -lubus -lubox -lblobmsg_json
    # 客户端
    $(CC) -o ubus_echo_client ubus_echo_client.c -lubus -lubox -lblobmsg_json
    
  2. 运行

    • 首先确保 ubusd 正在运行(在 OpenWRT 上通常是默认运行的)。
    • 然后先运行服务端:
      ./ubus_echo_server
      
    • 在另一个终端运行客户端进行测试:
      ./ubus_echo_client "Hello " 5
      
    • 客户端应该会输出从服务器返回的 JSON 响应。

5️⃣ 常用工具与调试手段

5.1 命令行工具 ubus

ubus 自带的命令行工具是与系统交互和调试的利器。

命令用途示例
list列出所有已注册的对象ubus list
list -v详细列出所有对象及其方法(和方法的参数签名)ubus list -v
call调用一个对象的方法ubus call network.interface.wan status
call (带参数)调用方法并传入参数(JSON格式)ubus call echo repeat '{"msg":"hi", "times":3}'
listen监听所有或特定的 ubus 事件ubus listen &
send发送一个事件ubus send my_event '{"data": "value"}'
wait_for等待一个或多个对象注册完成ubus wait_for echo network

5.2 调试手段

  1. 查看系统状态

    • ubus list -v:这是最常用的命令,可以查看系统当前注册了哪些服务,以及每个服务提供了哪些方法和参数。如果您的服务没有出现在列表中,说明注册失败。
  2. 详细日志

    • 编译时开启调试:在编译 ubus 相关代码时,可以启用调试信息(通常通过定义 DEBUG 宏),这样会在运行时输出更多细节到 stderr 或 syslog。
    • 使用 logread:在 OpenWRT 上,使用 logread 命令查看系统日志,许多 ubus 相关的错误和信息会记录在这里。
  3. ubus listen

    • 在一个终端运行 ubus listen 可以实时观察到系统上所有的 ubus 事件,这对于调试基于事件的程序非常有帮助。
  4. strace / ltrace

    • 使用 strace 跟踪您的客户端或服务端程序的系统调用,查看 socket 连接、读写等操作是否正常。
    • 使用 ltrace 跟踪库函数的调用,可以帮助判断程序逻辑。
  5. 手动调用测试

    • 使用 ubus call 命令手动调用您编写的服务方法,传入不同的参数,检查返回值是否符合预期。这是验证服务功能最直接的方式。
  6. libubox 提供的 ustreamuloop 调试

    • 如果问题涉及事件循环或 socket 读写,可以尝试在代码中添加更详细的日志,跟踪 uloop 事件的处理过程。

6️⃣ 应用场景与局限性

6.1 典型应用场景

  • 系统配置与管理:例如 networkserviceucisystem 等对象提供了管理系统网络、服务、配置和重启关机等功能。
  • 状态查询:查询网络接口状态、无线状态、DHCP租约等。
  • 事件通知:系统服务(如 netifd)通过事件通知其他进程网络接口的变化、DHCP事件等。
  • 小型 RPC 调用:适合在系统内多个守护进程之间进行轻量级的命令调用和数据交换。

6.2 局限性

局限性描述建议
数据量限制单次消息传输不建议超过 ~60KB,否则可能工作不正常。传输大量数据应考虑其他机制,如文件或共享内存。
非高并发并非为高并发场景设计,多线程支持不佳。避免在多线程中并发调用同一上下文的方法。
递归调用风险A调用B,B又调用C的递归调用容易导致问题(如全局变量冲突)。尽量避免复杂的嵌套调用,设计扁平化的接口。
仅限本地基于 Unix Socket,只能用于同一台机器上的进程间通信。跨机器通信需选用网络通信机制,如 HTTP/gRPC。

💎 总结

ubus 是 OpenWRT 生态系统的核心通信枢纽,其设计的精髓在于轻量简单。它通过清晰的 C/S 架构、基于 blobmsg 的序列化以及 对象-方法 的抽象模型,为嵌入式环境下的进程间通信提供了一个高效可靠的解决方案。

虽然它在处理大数据量和高并发方面存在局限,但这恰恰符合其针对嵌入式设备的定位。理解和掌握 ubus,对于进行 OpenWRT 平台下的开发、系统管理和故障排查都至关重要。通过本文介绍的原理、代码实例和工具,希望你能更好地运用这一技术。

http://www.dtcms.com/a/353982.html

相关文章:

  • C# 字符和字符串
  • 怎么解决大模型幻觉问题
  • 【完全二叉树】 P10990 [蓝桥杯 2023 国 Python A] 彩色二叉树|普及+
  • 车辆识别码vin构成
  • python // 和%区别
  • K8S EFK日志收集全流程实战
  • MySQL数据库精研之旅第十二期:探秘视图,数据库中的 “虚拟表” 魔法
  • stm32 hal库spi dma_tx_rx的几个关键函数执行过程jlink trace分析
  • Rust 登堂 之 迭代器Iterator(三)
  • 如何构建灵活、可控、可扩展的多云网络底座
  • 【高级机器学习】1. Hypothesis 与 Objective Function
  • solidworks2024保姆级安装教程及解锁版安装包下载!
  • 【编号478】美国土地利用数据本土、阿拉斯加、夏威夷岛土地利用数据
  • 蛋白质残基 - 残基距离计算:单蛋白工具与批量处理方案
  • 【目标检测】论文阅读5
  • 记录一次内存问题排查
  • 比赛竞猜算法设计思路
  • MySQL InnoDB vs MyISAM
  • 【系统分析师】高分论文:论信息系统开发方法及应用
  • 前端漏洞(下)- 会话固定漏洞
  • Databend 亮相 DTCC 2025:存算分离架构引领湖仓一体化
  • 漫谈《数字图像处理》之霍夫变换
  • 一文辨析编程语言的强类型与弱类型、静态类型与动态类型
  • 【Java知识】Java线程相关对象全面解析与最佳实践
  • 吴恩达机器学习(一)
  • 盲埋孔在那里下单?猎板PCB盲埋孔制造优势
  • vue3 之异步轮训 hook 封装
  • 深度解析BiTGAN:基于双向Transformer生成对抗网络的长期人体动作预测
  • S 3.1深度学习--卷积神经网络
  • JavaScript工厂模式