嵌入式操作系统 | 库的下载、编译、安装与验证(libubox / ubus / uci)
本课程开源地址(Gitee) :https://gitee.com/fujianxinxi/qianrushixitongyingyongkaifa.git
课件、示例代码与验收脚本都在该仓库,可直接
git clone或下载 ZIP 使用。
模块五 · 2 学时 · 环境:VMware + Ubuntu 20.04
一句话概述:一步一步把 OpenWrt 的三个核心库(libubox、ubus、uci)从源码编译安装到 Ubuntu 上:装依赖、解压固定版本源码包、按依赖顺序 CMake 构建、验证安装结果,并把每一步会看到的现象和报错含义讲清楚,配套课堂练习与课后作业。
本节目标
- 能说出 libubox / ubus / uci 各是干什么的,以及为什么必须自己编译
- 会按 libubox → ubus → uci 的顺序用 CMake 编译安装,并知道文件装到了哪里
- 会用命令验证装好了没有,遇到报错知道去看哪一句
一、讲解内容
1.1 知识讲解
这三个库是什么。 它们来自 OpenWrt 生态,后面几节课全靠它们:
| 库 | 作用 | 我们后面用它做什么 |
|---|---|---|
| libubox | 基础工具库,里面有 uloop 事件循环、blobmsg、jshn 等 | 课时 16~18 写 uloop 程序 |
| ubus | 进程间通信(IPC):一个程序把自己的状态暴露出来,别的程序来问 | 课时 20 查询程序状态 |
| uci | 统一配置读写(OpenWrt 的配置文件都是 uci 格式) | 课时 19 读写配置文件 |
为什么必须自己编译。 Ubuntu 的软件源里没有 这三个包(我在实验环境里查过:apt-cache policy libubox-dev libubus-dev libuci-dev 没有任何结果)。它们只在 OpenWrt 生态里,所以只能下载源码自己编。
版本要钉死。 这三个库上游不打 tag ,OpenWrt 是用提交号(commit)钉版本的。我们采用 OpenWrt 23.05 发布分支里的这几个提交(写在 依赖源码包/VERSIONS.txt):
| 库 | 日期 | commit |
|---|---|---|
| libubox | 2023-05-23 | 75a3b870cace1171faf57bd55e5a9a2f1564f757 |
| ubus | 2023-06-05 | f787c97b34894a38b15599886cacbca01271684f |
| uci | 2023-08-10 | 5781664d5087ccc4b5ab58505883231212dbedbc |
依赖顺序不能颠倒。 ubus 和 uci 都要链接 libubox,所以顺序只能是 libubox → ubus → uci。先编 ubus 会报找不到 libubox。
CMake 就三步。 这三个库都用 CMake 构建(要求 ≥3.13,Ubuntu 20.04 的 3.16 够用):
text
mkdir build && cd build # 建一个单独的编译目录,不要污染源码
cmake .. # 生成 Makefile(检查依赖、环境)
make # 真正编译
sudo make install # 安装到 /usr/local
sudo ldconfig # 刷新动态库缓存
装到哪去了(要记住这张表)。
| 内容 | 位置 |
|---|---|
| 库文件 .so | /usr/local/lib/libubox.so、libubus.so、libuci.so、libblobmsg_json.so |
| 头文件 | /usr/local/include/libubox/*.h、/usr/local/include/libubus.h、/usr/local/include/uci.h |
| 命令 | /usr/local/bin/ubus、/usr/local/bin/uci、/usr/local/bin/jshn、/usr/local/sbin/ubusd |
编译自己的程序时不用写路径,直接 gcc xxx.c -o xxx -lubox(gcc 会自动找 /usr/local/include 和 /usr/local/lib)。
ldconfig 是干什么的。 运行时找 .so 是按缓存找的,make install 之后必须 sudo ldconfig 刷新缓存,否则运行时会报 error while loading shared libraries: libubox.so: cannot open shared object file。
-DBUILD_LUA=OFF 是什么意思。 这两个库默认要编译 Lua 绑定(需要 lua 开发包)。我们没有学 Lua,直接关掉,少装一个依赖。
1.2 操作步骤(一条命令一步,每步都看现象)
第 0 步:装编译要用的工具(每条都说明为什么)
shell
sudo apt update
sudo apt install -y build-essential cmake pkg-config libjson-c-dev socat git
| 装的东西 | 为什么需要 |
|---|---|
build-essential |
提供 gcc、make 和标准头文件;cmake 生成完 Makefile 后要敲 make 编译 |
cmake |
三个库的官方构建系统就是 CMake(不装就没法 cmake ..) |
pkg-config |
cmake 阶段用 pkg-config 去找 json-c(现象:-- Checking for one of the modules 'json-c') |
libjson-c-dev |
libubox 编译期强制依赖 json-c ,不装会直接 cmake 失败 |
socat |
课时 22 / 23 用它造一对虚拟串口(虚拟机里没有真串口) |
git |
老师用 --online 从 GitHub 拉源码;平时查版本也用得上 |
如果 apt 下载很慢,先把源换成国内镜像(清华):
sudo sed -i 's|//us.archive.ubuntu.com|//mirrors.tuna.tsinghua.edu.cn|g; s|//security.ubuntu.com|//mirrors.tuna.tsinghua.edu.cn|g' /etc/apt/sources.list && sudo apt update
第 1 步:准备源码包(两种方式,选一种)
shell
# 方式一(推荐,不需要网络):用课程资料自带的固定版本源码包
ls ~/lab/依赖源码包/
# libubox-2023-05-23-75a3b870.tar.gz
# ubus-2023-06-05-f787c97b.tar.gz
# uci-2023-08-10-5781664d.tar.gz
# VERSIONS.txt ← 里面写了每个包的 commit 和 sha256 校验值
# 方式二:没有课程资料/想自己从 GitHub 下(需要能访问 GitHub)
# 三条 wget 直接抄,一个字都不用改;先建目录再下,这样和第 2 步的命令完全对得上
mkdir -p ~/lab/依赖源码包 && cd ~/lab/依赖源码包
wget https://github.com/openwrt/libubox/archive/75a3b870cace1171faf57bd55e5a9a2f1564f757.tar.gz -O libubox-2023-05-23-75a3b870.tar.gz
wget https://github.com/openwrt/ubus/archive/f787c97b34894a38b15599886cacbca01271684f.tar.gz -O ubus-2023-06-05-f787c97b.tar.gz
wget https://github.com/openwrt/uci/archive/5781664d5087ccc4b5ab58505883231212dbedbc.tar.gz -O uci-2023-08-10-5781664d.tar.gz
ls -lh
方式二的现象(三个包,每个几十 KB 到一百多 KB;-O 后面的名字就是我们自己起的,和方式一一致):
text
-rw-r--r-- 1 lab lab 88K libubox-2023-05-23-75a3b870.tar.gz
-rw-r--r-- 1 lab lab 44K ubus-2023-06-05-f787c97b.tar.gz
-rw-r--r-- 1 lab lab 57K uci-2023-08-10-5781664d.tar.gz
下载失败怎么办(国内很常见)。
github.com打不开时,把地址换成 GitHub 的下载站codeload.github.com,其余一字不变:
shell# 上面三条 wget 的备用写法(任选一种,下到的文件是一样的) wget https://codeload.github.com/openwrt/libubox/tar.gz/75a3b870cace1171faf57bd55e5a9a2f1564f757 -O libubox-2023-05-23-75a3b870.tar.gz wget https://codeload.github.com/openwrt/ubus/tar.gz/f787c97b34894a38b15599886cacbca01271684f -O ubus-2023-06-05-f787c97b.tar.gz wget https://codeload.github.com/openwrt/uci/tar.gz/5781664d5087ccc4b5ab58505883231212dbedbc -O uci-2023-08-10-5781664d.tar.gz两个站都不通?那就老实回到方式一用课程自带的包------这也是推荐方式一的原因:上课不用等网。
两种方式的三个差别(先看一遍,免得后面以为自己下错了):
方式一:课程自带包 方式二:GitHub 下载 包文件位置/名字 已在 ~/lab/依赖源码包/,名字同上用上面的命令下,名字和方式一完全一样 tar解压出的目录名libubox-2023-05-23-75a3b870libubox-75a3b870cace1171faf57bd55e5a9a2f1564f757(GitHub 习惯用全 commit 号命名)sha256sum结果和 VERSIONS.txt里的值一模一样不一样 !GitHub 是重新打的包(压缩方式/打包顺序不同),内容一样但字节不同,不要拿 VERSIONS.txt 的值去比对,会以为下坏了 方式二解压出来的目录名不一样,第 2 步末尾给了三条
mv改名命令,敲完目录名就和课件统一了。
第 2 步:解压
shell
mkdir -p ~/lab/libs && cd ~/lab/libs
tar xf ~/lab/依赖源码包/libubox-2023-05-23-75a3b870.tar.gz
tar xf ~/lab/依赖源码包/ubus-2023-06-05-f787c97b.tar.gz
tar xf ~/lab/依赖源码包/uci-2023-08-10-5781664d.tar.gz
ls
现象:出现三个目录(名字带日期和提交号前 8 位),例如 libubox-2023-05-23-75a3b870。
text
libubox-2023-05-23-75a3b870 ubus-2023-06-05-f787c97b uci-2023-08-10-5781664d
用的是方式二(GitHub 下载)?再多敲三条 mv ,把目录名改成课件里的短名字,这样第 3~5 步的 cd 你能一个字不差地抄:
shell
mv libubox-75a3b870cace1171faf57bd55e5a9a2f1564f757 libubox-2023-05-23-75a3b870
mv ubus-f787c97b34894a38b15599886cacbca01271684f ubus-2023-06-05-f787c97b
mv uci-5781664d5087ccc4b5ab58505883231212dbedbc uci-2023-08-10-5781664d
ls
(长的名字敲前几个字符按 Tab 补全,不用手打全;ls 出来三个目录名和方式一一模一样就对了。)
第 3 步:编译安装 libubox(第一个,必须有)
shell
cd libubox-2023-05-23-75a3b870
mkdir build && cd build
cmake .. -DBUILD_LUA=OFF -DBUILD_EXAMPLES=OFF
make
sudo make install
sudo ldconfig
cmake .. 的现象(关键是最后两行 Configuring done / Generating done):
text
-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29.1")
-- Checking for one of the modules 'json-c'
-- Looking for clock_gettime - found
-- Configuring done
-- Generating done
make 的现象(最后出现 Built target ...,本环境实测没有报错):
text
[ 55%] Building C object CMakeFiles/ubox.dir/uloop.c.o
[ 88%] Built target ubox-static
[100%] Built target jshn
sudo make install 的现象(会一行行打印装到哪,看 Installing:):
text
-- Installing: /usr/local/lib/libubox.so
-- Installing: /usr/local/bin/jshn
-- Installing: /usr/local/share/libubox/jshn.sh
第 4 步:编译安装 ubus(同样三步,注意它要能找到刚装的 libubox)
shell
cd ~/lab/libs/ubus-2023-06-05-f787c97b
mkdir build && cd build
cmake .. -DBUILD_LUA=OFF -DBUILD_EXAMPLES=OFF
make
sudo make install
sudo ldconfig
make install 的现象(注意多了 sbin 里的 ubusd 和头文件 libubus.h):
text
-- Installing: /usr/local/lib/libubus.so
-- Installing: /usr/local/bin/ubus
-- Installing: /usr/local/sbin/ubusd
-- Installing: /usr/local/include/libubus.h
第 5 步:编译安装 uci
shell
cd ~/lab/libs/uci-2023-08-10-5781664d
mkdir build && cd build
cmake .. -DBUILD_LUA=OFF
make
sudo make install
sudo ldconfig
第 6 步:验证装好了没有(四条命令,逐条看结果)
shell
# ① 库文件在不在(能看到三个 .so 及大小,说明装好了)
ls -l /usr/local/lib/libubox.so /usr/local/lib/libubus.so /usr/local/lib/libuci.so
# ② 头文件在不在(编译时要 include 它们)
ls /usr/local/include/libubox/uloop.h /usr/local/include/libubus.h /usr/local/include/uci.h
# ③ 命令在不在
which ubus ubusd uci jshn
# ④ 最实在的验证:写一个最小 uloop 程序,能编能跑才算真的好了
cd ~/lab/libs
cat > uloop_min.c <<'EOF'
#include <stdio.h>
#include <libubox/uloop.h>
static struct uloop_timeout t;
static void on_timeout(struct uloop_timeout *to)
{
printf("uloop 定时器回调被调用:库真的能用\n");
uloop_end();
}
int main(void)
{
uloop_init();
t.cb = on_timeout;
uloop_timeout_set(&t, 500);
uloop_run();
uloop_done();
return 0;
}
EOF
gcc -Wall -Wextra -o uloop_min uloop_min.c -lubox
./uloop_min
现象(①~③ 都有输出、④ 打印两行):
text
$ ls -l /usr/local/lib/libubox.so /usr/local/lib/libubus.so /usr/local/lib/libuci.so
-rw-r--r-- 1 root root 433000 /usr/local/lib/libubox.so
-rw-r--r-- 1 root root 281480 /usr/local/lib/libubus.so
-rw-r--r-- 1 root root 309776 /usr/local/lib/libuci.so
$ which ubus ubusd uci jshn
/usr/local/bin/ubus
/usr/local/sbin/ubusd
/usr/local/bin/uci
/usr/local/bin/jshn
$ ./uloop_min
uloop 定时器回调被调用:库真的能用
第 7 步:万一要重装。 前面第 0~6 步就是完整流程:把第 2 步的 ~/lab/libs 删掉,从解压重新来一遍即可(rm -rf ~/lab/libs)。不要用别人写好的脚本,自己敲一遍才知道哪一步会出什么问题。
1.3 常见错误与排查
| 现象 | 原因 | 解决 |
|---|---|---|
cmake 报 Could NOT find json-c / None of the required 'json-c' found |
没装 json-c 开发包 | sudo apt install -y libjson-c-dev(这是 libubox 唯一强制依赖) |
cmake: command not found |
没装 cmake | sudo apt install -y cmake |
make: command not found |
没装 build-essential | sudo apt install -y build-essential |
编 ubus 时报找不到 ubox / libubox/usock.h |
顺序错了:还没装 libubox | 先做第 3 步,再回头编 ubus |
error while loading shared libraries: libubox.so |
装完没刷新动态库缓存 | sudo ldconfig(或重新登录) |
make 报某条 warning 被当成 error(-Werror) |
官方默认把警告当错误 | 重新 cmake .. -DCMAKE_C_FLAGS="-Wno-error" 后再 make(本环境 Ubuntu 20.04 + gcc 9.4 实测不需要) |
tar: 无法打开: No such file or directory |
源码包路径写错 | 用 ls ~/lab/依赖源码包/ 确认文件名再敲 |
二、学生练习
本节命令速查表:
| 命令 | 作用 | 示例 |
|---|---|---|
| apt install 包名 | 装编译依赖 | sudo apt install -y build-essential cmake pkg-config libjson-c-dev socat git |
| tar xf 包名 | 解压源码包 | tar xf libubox-2023-05-23-75a3b870.tar.gz |
| cmake ... -D开关 | 生成 Makefile 并检查依赖 | cmake .. -DBUILD_LUA=OFF -DBUILD_EXAMPLES=OFF |
| make / sudo make install | 编译 / 安装 | make && sudo make install |
| sudo ldconfig | 刷新动态库缓存 | sudo ldconfig |
| ls -l 路径 / which 命令 | 验证装到哪了 | ls -l /usr/local/lib/libubox.so |
| 编号 | 任务 | 提示 | 预期结果 |
|---|---|---|---|
| L15.1 | 装齐依赖并检查版本 | cmake --version、pkg-config --modversion json-c |
cmake ≥3.13,json-c 显示 0.13.1 |
| L15.2 | 按顺序编译三个库 | 严格 libubox → ubus → uci | 三次都出现 Built target 且 make install 有 Installing 输出 |
| L15.3 | 验证安装结果 | 用 1.2 第 6 步的四条命令 | .so、头文件、命令都在;最小程序能跑出提示 |
| L15.4 | 故意先编 ubus(把顺序打乱) | 临时把 libubox 的安装目录改名再编 ubus | 看到"找不到 ubox"的报错,理解依赖顺序 |
| L15.5 | 看库依赖了谁 | ldd /usr/local/lib/libubus.so |
列表里有 libubox.so、libjson-c.so |
| L15.6 | 校验源码包有没有传坏(方式一专用) | sha256sum ~/lab/依赖源码包/*.tar.gz 与 VERSIONS.txt 里的值逐行对照 |
三个包的 sha256 一模一样(方式二从 GitHub 下的包值不同,属正常,别慌) |
三、作业
- 必做:完成 L15.2、L15.3,提交三张截图(libubox 的
make install输出 /ls -l /usr/local/lib/lib*.so/./uloop_min运行结果),并回答:① 为什么必须按 libubox → ubus → uci 的顺序?②ldconfig不加会出什么问题?③ 三个库分别装到了哪些目录? - 选做:把安装前缀改成自己的目录试试:
cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/lab/mylibs -DBUILD_LUA=OFF,装完用ls和ldd观察文件去哪了(提示:这种装法要加-I -L才能编译) - 提交:截图 + 文字回答,命名
课时15-作业截图.png
四、本节小结与下节预告
本节把 OpenWrt 的三个核心库从源码编译安装到了 Ubuntu 上,并搞清了它们的依赖顺序、安装路径和验证方法------这是后面所有实验的地基。下节开始用 libubox 里的 uloop:先写一个最简单的 UDP 客户端,看看"事件循环"到底帮我们做了什么。
附录:怎么运行 + 完整代码
一个课件就够用:先按附录 A 的顺序敲命令(服务端/对端先开,自己的程序后开),再按附录 B 抄代码。行尾请用 LF。
附录 A|怎么运行(注意先后顺序)
shell
# 先装编译依赖(每条都在课件 1.2 第 0 步里解释了为什么)
sudo apt update
sudo apt install -y build-essential cmake pkg-config libjson-c-dev socat git
# ① 准备源码包:用课程自带的(方式一)
# 没有课程资料就按课件 1.2 第 1 步的方式二,用三条 wget 从 GitHub 下载
ls ~/lab/依赖源码包/ # 应看到三个 .tar.gz 和 VERSIONS.txt
# ② 解压(方式二下载的话,解压后按课件 1.2 第 2 步末尾的三条 mv 把长目录名改短)
mkdir -p ~/lab/libs && cd ~/lab/libs
tar xf ~/lab/依赖源码包/libubox-2023-05-23-75a3b870.tar.gz
tar xf ~/lab/依赖源码包/ubus-2023-06-05-f787c97b.tar.gz
tar xf ~/lab/依赖源码包/uci-2023-08-10-5781664d.tar.gz
ls # libubox-2023-05-23-75a3b870 ubus-2023-06-05-f787c97b uci-2023-08-10-5781664d
# ③ 按 libubox → ubus → uci 顺序编译安装(每个库都是这五条,一条都不能少)
cd ~/lab/libs/libubox-2023-05-23-75a3b870
mkdir build && cd build
cmake .. -DBUILD_LUA=OFF -DBUILD_EXAMPLES=OFF
make
sudo make install
sudo ldconfig
cd ~/lab/libs/ubus-2023-06-05-f787c97b
mkdir build && cd build
cmake .. -DBUILD_LUA=OFF -DBUILD_EXAMPLES=OFF
make
sudo make install
sudo ldconfig
cd ~/lab/libs/uci-2023-08-10-5781664d
mkdir build && cd build
cmake .. -DBUILD_LUA=OFF # uci 没有 EXAMPLES 开关
make
sudo make install
sudo ldconfig
# ④ 验证:三个 .so 在、命令在,再跑本课的最小程序(能打印就说明装好了)
ls -l /usr/local/lib/libubox.so /usr/local/lib/libubus.so /usr/local/lib/libuci.so
which ubus ubusd uci jshn
cd ~/lab/课时15-库的编译安装 # 课件 1.2 第 6 步建的 uloop_min.c(或按附录 B 新建一个)
gcc -Wall -Wextra -o uloop_min uloop_min.c -lubox
./uloop_min
附录 B|文件 uloop_min.c
c
/*
* uloop_min.c ------ 课时 15:验证 libubox 装好没有的最小程序
*
* 编译:gcc -Wall -Wextra -o uloop_min uloop_min.c -lubox
* 运行:./uloop_min (半秒后打印一行再退出)
*/
#include <stdio.h>
#include <libubox/uloop.h>
static struct uloop_timeout t;
static void on_timeout(struct uloop_timeout *to)
{
(void)to;
printf("uloop 定时器回调被调用:libubox 装好了、能编能跑\n");
uloop_end();
}
int main(void)
{
uloop_init();
t.cb = on_timeout;
uloop_timeout_set(&t, 500);
printf("进入 uloop_run(),等 0.5 秒...\n");
uloop_run();
uloop_done();
return 0;
}