嵌入式操作系统 | 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 一条命令一条命令地查询,配套课堂练习与课后作业。
本节目标
- 能说出 ubus 是干什么的,以及 ubusd 和 socket 文件的作用
- 会写一个注册对象和方法、返回程序状态的 C 程序(blobmsg 组返回值)
- 会先起 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;
}