15 嵌入式操作系统 | 库的下载、编译、安装与验证(libubox / ubus / uci)

嵌入式操作系统 | 库的下载、编译、安装与验证(libubox / ubus / uci)

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

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

一句话概述:一步一步把 OpenWrt 的三个核心库(libubox、ubus、uci)从源码编译安装到 Ubuntu 上:装依赖、解压固定版本源码包、按依赖顺序 CMake 构建、验证安装结果,并把每一步会看到的现象和报错含义讲清楚,配套课堂练习与课后作业。

本节目标

  1. 能说出 libubox / ubus / uci 各是干什么的,以及为什么必须自己编译
  2. 会按 libubox → ubus → uci 的顺序用 CMake 编译安装,并知道文件装到了哪里
  3. 会用命令验证装好了没有,遇到报错知道去看哪一句

一、讲解内容

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-75a3b870 libubox-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;
}
相关推荐
云杂项3 小时前
tmux指南:安装、配置与高效使用(Ubuntu上)
linux·服务器·ubuntu
艸肅3 小时前
Ubuntu 安装 JDK (含手动安装)
java·ubuntu·jdk
zhangrelay8 小时前
ROS2 Lyrical实验5导航Nav2
linux·笔记·学习·ubuntu·机器人
编码如写诗11 小时前
【k8s】全新Ubuntu 26.04 使用kt 超简单安装 k8s 最新1.37.1+KubeSphere4.1.3
ubuntu·容器·kubernetes
牢姐与蒯11 小时前
Linux信号(二).信号产生续 && 信号保存
linux·运维·服务器·ubuntu
阿钱真强道1 天前
22 嵌入式操作系统 | 串口命令行调试(socat / stty / printf / hexdump)
ubuntu·cmake·uci·ubus·libubox
阿钱真强道1 天前
20 嵌入式操作系统 | ubus:把自己的程序状态暴露出去
c语言·进程通信·ubus·ubusd·blobmsg
承渊政道1 天前
Linux系统学习【进程信号详细解析——认识、产生、保存以及捕捉信号】
linux·学习·ubuntu·ssh·vs code·进程信号
牢姐与蒯1 天前
Linux信号(一).信号产生
linux·运维·服务器·ubuntu