嵌入式操作系统 | 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 个库)
cd~/lab/课时20-ubus状态查询 gcc-Wall-Wextra-oubus_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)
sudoubusd&ls-l/var/run/ubus/ubus.sock现象(出现 socket 文件说明总机已经开机):
srw-rw-rw- 1 root root 0 Sep 25 08:15 /var/run/ubus/ubus.sock第 3 步:运行我们的程序(终端 A,会让出提示符前先打印三行)
sudo./ubus_status现象(程序注册完就不动了,正等别人来查):
① 连接 ubusd… 已连接 ② 已注册对象: sensordata(方法: status) ③ 现在到另一个终端执行: ubus list ; ubus call sensordata status第 4 步:另开一个终端 B,先看有哪些对象
ubus list现象(能看到我们刚注册的对象):
sensordata第 5 步:查询状态(这就是"让程序把状态说出来")
ubus call sensordata status现象(返回一段 JSON;多查几次query_count会加 1):
{ "device": "ubuntu-gateway", "uptime_s": 49321, "query_count": 1, "sensor": "温度", "value": 25.500000, "status": "ok" }再执行一次同样的命令:
{ "device": "ubuntu-gateway", "uptime_s": 49321, "query_count": 2, "sensor": "温度", "value": 26.000000, "status": "ok" }第 6 步:回到终端 A 看程序那边的现象
← 收到第 1 次 ubus 查询 ← 收到第 2 次 ubus 查询两边对应上了:终端 B 每执行一次ubus call,终端 A 的程序回调就被调用一次——这就是"查询程序状态"的完整链路。
第 7 步:收工
# 终端 A 按 Ctrl+C 停掉 ubus_status,然后:sudopkill-xubusd# 关掉总机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|怎么运行(注意先后顺序)
gcc-Wall-Wextra-oubus_status ubus_status.c-lubus-lubox-lblobmsg_json-ljson-csudoubusd&# 终端A:先启动 ubusdls-l/var/run/ubus/ubus.sock# 确认 socket 在sudo./ubus_status# 再运行自己的程序(前台等着被查)ubus list# 终端B:看对象ubus call sensordata status# 查询状态(返回 JSON)sudopkill-xubusd# 收工附录 B|文件ubus_status.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>staticstructubus_context*ctx;staticunsignedintquery_count=0;/* 被查询了几次 *//* 方法回调:有人 ubus call sensordata status 时,uloop 会走到这里 */staticinthandle_status(structubus_context*c,structubus_object*obj,structubus_request_data*req,constchar*method,structblob_attr*msg){structblob_bufb={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);return0;}/* 方法表:本对象只有 status 一个方法,不用参数 */staticconststructubus_methodstatus_methods[]={UBUS_METHOD_NOARG("status",handle_status),};staticstructubus_object_typestatus_type=UBUS_OBJECT_TYPE("sensordata",status_methods);staticstructubus_objectstatus_obj={.name="sensordata",/* ubus call 时用的对象名 */.type=&status_type,.methods=status_methods,.n_methods=ARRAY_SIZE(status_methods),};staticvoidon_signal(intsig){(void)sig;uloop_end();}intmain(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");return1;}printf(" 已连接\n");ubus_add_uloop(ctx);/* 让 ubus 的消息也走 uloop 事件循环 */if(ubus_add_object(ctx,&status_obj)!=0){printf("② 注册对象失败\n");return1;}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);return0;}