20 嵌入式操作系统 | ubus:把自己的程序状态暴露出去

嵌入式操作系统 | ubus:把自己的程序状态暴露出去

本课程开源地址(Gitee) :https://gitee.com/fujianxinxi/qianrushixitongyingyongkaifa.git

课件、示例代码与验收脚本都在该仓库,可直接 git clone 或下载 ZIP 使用。
模块六 · 2 学时 · 环境:VMware + Ubuntu 20.04

一句话概述:讲清 ubus 的对象/方法模型:先启动 ubusd,再写一个注册 sensordata 对象的 C 程序,把运行状态(设备名、运行时长、被查询次数、传感器读数)用 blobmsg 返回去,最后用 ubus list 和 ubus call 一条命令一条命令地查询,配套课堂练习与课后作业。

本节目标

  1. 能说出 ubus 是干什么的,以及 ubusd 和 socket 文件的作用
  2. 会写一个注册对象和方法、返回程序状态的 C 程序(blobmsg 组返回值)
  3. 会先起 ubusd、再跑自己的程序、最后用 ubus list / ubus call 查询

一、讲解内容

1.1 知识讲解

ubus 是什么。 它是 OpenWrt 里的进程间通信(IPC)机制:一个程序把自己的"能力"注册成一个对象 ,对象里可以有若干方法 ;别的程序(或命令行的 ubus)通过名字就能调用它、拿回数据。路由器上执行 ubus call network.interface.lan status 查询网络状态,用的就是这套机制。

两个关键东西:

名字 是什么
ubusd 一个守护进程(相当于"总机"),所有程序都连它
/var/run/ubus/ubus.sock 通信用的 unix socket 文件;程序启动时先 ls -l 看它一眼,就知道 ubusd 在不在

(这个路径是课时 15 编译时钉死在库里的默认值,ubusd 启动时会自动创建所在的目录。)

写一个 ubus 服务端就三步:

步骤 函数 干什么
① 连上总机 ubus_connect(NULL) 连接 ubusd,并 ubus_add_uloop(ctx) 让它的消息走 uloop
② 注册对象 ubus_add_object(ctx, &obj) 把自己登记成 sensordata,方法是 status
③ 等人调用 方法回调 别人 ubus call 时,回调被调用,用 blobmsg 组装返回值发回去

blobmsg 是什么。 它是 libubox 提供的一套"键值对表"接口:先用 blob_buf_init 开一个空表,再用 blobmsg_add_string / add_u32 / add_double 往里放字段,最后通过 ubus_send_reply 发回去。命令行那边看到的就是一段 JSON(正好接上下一课)。

1.2 操作步骤(一条命令一步,看现象)

第 1 步:编译(注意要链接 4 个库)

shell 复制代码
cd ~/lab/课时20-ubus状态查询
gcc -Wall -Wextra -o ubus_status ubus_status.c -lubus -lubox -lblobmsg_json -ljson-c

为什么这么多 -l:程序用了 ubus 的接口(-lubus)、uloop 和 blobmsg(-lubox)、blobmsg 与 JSON 互转(-lblobmsg_json -ljson-c)。少一个就会看到 undefined reference to ...。

第 2 步:先启动 ubusd(终端 A)

shell 复制代码
sudo ubusd &
ls -l /var/run/ubus/ubus.sock

现象(出现 socket 文件说明总机已经开机):

text 复制代码
srw-rw-rw- 1 root root 0 Sep 25 08:15 /var/run/ubus/ubus.sock

第 3 步:运行我们的程序(终端 A,会让出提示符前先打印三行)

shell 复制代码
sudo ./ubus_status

现象(程序注册完就不动了,正等别人来查):

text 复制代码
① 连接 ubusd...
   已连接
② 已注册对象: sensordata(方法: status)
③ 现在到另一个终端执行: ubus list ; ubus call sensordata status

第 4 步:另开一个终端 B,先看有哪些对象

shell 复制代码
ubus list

现象(能看到我们刚注册的对象):

text 复制代码
sensordata

第 5 步:查询状态(这就是"让程序把状态说出来")

shell 复制代码
ubus call sensordata status

现象(返回一段 JSON;多查几次 query_count 会加 1):

text 复制代码
{
"device": "ubuntu-gateway",
"uptime_s": 49321,
"query_count": 1,
"sensor": "温度",
"value": 25.500000,
"status": "ok"
}

再执行一次同样的命令:

text 复制代码
{
"device": "ubuntu-gateway",
"uptime_s": 49321,
"query_count": 2,
"sensor": "温度",
"value": 26.000000,
"status": "ok"
}

第 6 步:回到终端 A 看程序那边的现象

text 复制代码
← 收到第 1 次 ubus 查询
← 收到第 2 次 ubus 查询

两边对应上了 :终端 B 每执行一次 ubus call,终端 A 的程序回调就被调用一次------这就是"查询程序状态"的完整链路。

第 7 步:收工

shell 复制代码
# 终端 A 按 Ctrl+C 停掉 ubus_status,然后:
sudo pkill -x ubusd        # 关掉总机

1.3 常见错误与排查

现象 原因 解决
程序打印"连不上 ubusd" ubusd 没启动 先 sudo ubusd &,再 ls -l /var/run/ubus/ubus.sock 确认
ubus list 是空的 自己的程序没在运行(或刚退出) 先把 ./ubus_status 跑起来(它会一直等)
ubus call ... : Object not found 对象名写错,或程序没注册成功 对象名必须是 sensordata;先 ubus list 看一眼
ubus call 没反应 程序卡住了(比如注册后没进 uloop) 检查是否调用了 uloop_run()
编译报 undefined reference to ubus_... 链接参数少写了 四条都要写:-lubus -lubox -lblobmsg_json -ljson-c
ubusd: command not found 课时 15 没装好 ubus 回课时 15 检查,which ubusd 应为 /usr/local/sbin/ubusd

二、学生练习

本节命令速查表:

命令 作用 示例
gcc ... -lubus -lubox -lblobmsg_json -ljson-c 编译 ubus 程序 见第 1 步
sudo ubusd & 启动总机 sudo ubusd &
ls -l /var/run/ubus/ubus.sock 确认总机在 ls -l /var/run/ubus/ubus.sock
sudo ./ubus_status 运行自己的服务程序 sudo ./ubus_status
ubus list 看有哪些对象 ubus list
ubus call 对象 方法 调用方法查询 ubus call sensordata status
sudo pkill -x ubusd 关掉总机 sudo pkill -x ubusd
编号 任务 提示 预期结果
L20.1 按步骤跑通查询 先 ubusd → 再程序 → 再 call ubus list 有 sensordata,call 返回 JSON
L20.2 连查 3 次 连续执行 ubus call query_count 依次 1、2、3
L20.3 加一个字段 在 handle_status 里加 blobmsg_add_u32(&b, "heartbeat", 42) call 结果里多一项
L20.4 加第二个方法 仿照 status 加一个 ping 方法(返回 {"pong":true}) ubus call sensordata ping 能调通
L20.5 不启动 ubusd 就运行程序 先 sudo pkill -x ubusd 程序打印"连不上 ubusd"并退出
L20.6 观察"程序退出后"的现象 停掉 ubus_status 后再 ubus list 对象消失,ubus call 报 Object not found

三、作业

  • 必做:完成 L20.1、L20.2,提交两个终端截图(程序端 + ubus call 的 JSON 输出),并回答:① ubusd 是什么,socket 文件有什么用?② 我们的程序是怎么"把状态交出去"的?③ 为什么 ubus call 的输出是 JSON?
  • 选做:把课时 18 的采集程序和新知识合起来------在 ubus 对象里返回"最后一次采集的温度",让查询能拿到实时值(提示:把采集结果存到全局变量,回调里读它)
  • 提交:截图 + 文字回答,命名 课时20-作业截图.png

四、本节小结与下节预告

本节把程序状态暴露给了别人:启动 ubusd、注册 sensordata 对象、用 blobmsg 返回字段,再用 ubus list / ubus call 查询。你注意到没有------ubus call 打出来的就是 JSON。下节课我们就专门学 JSON:怎么用它生成和解析数据。

附录:怎么运行 + 完整代码

一个课件就够用:先按附录 A 的顺序敲命令(服务端/对端先开,自己的程序后开),再按附录 B 抄代码。行尾请用 LF。

附录 A|怎么运行(注意先后顺序)

shell 复制代码
gcc -Wall -Wextra -o ubus_status ubus_status.c -lubus -lubox -lblobmsg_json -ljson-c

sudo ubusd &                                             # 终端A:先启动 ubusd
ls -l /var/run/ubus/ubus.sock                             # 确认 socket 在
sudo ./ubus_status                                        # 再运行自己的程序(前台等着被查)

ubus list                                                 # 终端B:看对象
ubus call sensordata status                               # 查询状态(返回 JSON)
sudo pkill -x ubusd                                       # 收工

附录 B|文件 ubus_status.c

c 复制代码
/*
 * ubus_status.c ------ 课时 20:注册一个 ubus 对象,把程序状态暴露出去
 *
 * 编译:gcc -Wall -Wextra -o ubus_status ubus_status.c -lubus -lubox -lblobmsg_json -ljson-c
 * 运行:sudo ./ubus_status          # 需要先启动 ubusd(见 ubus_check.sh start)
 *
 * 做法三步:
 *   ① 连上 ubusd(ubus_connect)
 *   ② 注册一个对象 sensordata,里面有一个方法 status
 *   ③ 别人执行 ubus call sensordata status 时,我们的回调被调用,
 *      用 blobmsg 组一个表返回去(ubus 命令行会把它打成 JSON)
 */
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <libubox/blobmsg.h>
#include <libubox/uloop.h>
#include <libubus.h>

static struct ubus_context *ctx;
static unsigned int query_count = 0;      /* 被查询了几次 */

/* 方法回调:有人 ubus call sensordata status 时,uloop 会走到这里 */
static int handle_status(struct ubus_context *c, struct ubus_object *obj,
                         struct ubus_request_data *req, const char *method,
                         struct blob_attr *msg)
{
    struct blob_buf b = { 0 };

    (void)c; (void)obj; (void)method; (void)msg;
    query_count++;

    blob_buf_init(&b, 0);                                  /* 准备一个空表 */
    blobmsg_add_string(&b, "device", "ubuntu-gateway");    /* 字符串字段 */
    blobmsg_add_u32(&b, "uptime_s", (uint32_t)(time(NULL) % 100000));
    blobmsg_add_u32(&b, "query_count", query_count);       /* 自己被查了几次 */
    blobmsg_add_string(&b, "sensor", "温度");
    blobmsg_add_double(&b, "value", 25.0 + query_count * 0.5);  /* 假装最新读数 */
    blobmsg_add_string(&b, "status", "ok");

    ubus_send_reply(c, req, b.head);                        /* 把表发回去 */
    blob_buf_free(&b);

    printf("← 收到第 %u 次 ubus 查询\n", query_count);
    fflush(stdout);
    return 0;
}

/* 方法表:本对象只有 status 一个方法,不用参数 */
static const struct ubus_method status_methods[] = {
    UBUS_METHOD_NOARG("status", handle_status),
};

static struct ubus_object_type status_type =
    UBUS_OBJECT_TYPE("sensordata", status_methods);

static struct ubus_object status_obj = {
    .name      = "sensordata",        /* ubus call 时用的对象名 */
    .type      = &status_type,
    .methods   = status_methods,
    .n_methods = ARRAY_SIZE(status_methods),
};

static void on_signal(int sig)
{
    (void)sig;
    uloop_end();
}

int main(void)
{
    uloop_init();
    signal(SIGINT, on_signal);
    signal(SIGTERM, on_signal);

    printf("① 连接 ubusd...\n");
    ctx = ubus_connect(NULL);          /* NULL = 用默认 socket /var/run/ubus/ubus.sock */
    if (!ctx) {
        printf("   连不上 ubusd:先执行 ./ubus_check.sh start 把 ubusd 起起来\n");
        return 1;
    }
    printf("   已连接\n");
    ubus_add_uloop(ctx);               /* 让 ubus 的消息也走 uloop 事件循环 */

    if (ubus_add_object(ctx, &status_obj) != 0) {
        printf("② 注册对象失败\n");
        return 1;
    }
    printf("② 已注册对象: sensordata(方法: status)\n");
    printf("③ 现在到另一个终端执行: ubus list ; ubus call sensordata status\n");
    fflush(stdout);

    uloop_run();                       /* 事件循环:等别人来调用 */

    ubus_free(ctx);
    uloop_done();
    printf("\n④ 程序退出(共被查询 %u 次)\n", query_count);
    return 0;
}
相关推荐
小小龙学IT3 小时前
三菱 PLC MC 协议(SLMP / QnA 兼容 3E 帧)深度解析:从帧结构到 C++/Go 双语言采集实战
c语言·c++·golang
Logic1013 小时前
C语言/数据结构位运算题解:异或XOR找出游戏速通中的“独特初始能量值“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
Chen—LSN4 小时前
C语言——⽂件操作(1)
c语言·开发语言·c++·经验分享·笔记·算法·链表
木由里予4 小时前
GDB 使用手册(详细版)
c语言·vscode
Chen—LSN5 小时前
C语言——文件操作(2)
c语言·开发语言·c++·经验分享·笔记·算法·c#
Escalating_xu6 小时前
【C 语言】深入理解指针(2):数组名、数组传参、二级指针与指针数组
java·c语言·开发语言
心中有你02147 小时前
C语言实现文件异或加密与RLE压缩算法
c语言·开发语言
阿钱真强道7 小时前
18 嵌入式操作系统 | uloop 定时器:周期任务与网络上报
c语言·定时器·周期任务·uloop
多多爱学习7 小时前
括号有几层?得看还有多少个左括号没闭合
c语言·c++·算法·面试