开源鸿蒙5.0集成busybox(小型系统)
背景
完成了openharmony5.0对龙芯2k300小型系统的适配,进入系统发现可用的命令很少,例如(ip、stty等命令都不可用)。原因是开鸿用的是toybox,而toybox相较于busybox更轻便、简洁,维护也方便,但是命令也少,所以,笔者萌发了集成busybox的想法。
注:busybox采用 GPLv2 协议,要求衍生作品开源。本文移植busybox只为自行使用和分享移植方法,无任何商业用途。
下载busybox源码
下载链接:busybox download
笔者下载的当前最新版1.37.0版本
下载完成后,将压缩包解压至third_party/目录下,并重命名为busybox
# your_openharmony_root_dir 为读者的开鸿项目根目录
tar -zjf busybox-1.37.0.tar.bz2 -C your_openharmony_root_dir/third_paty/
cd your_openharmony_root_dir/third_paty/
mv busybox-1.37* busybox
为busybox创建编译脚本
busybox官方构建方式是类linux的编译构建模式,主要通过make命令来完成编译构建,而开鸿使用的是gn+ninja的编译构建模式,所以,笔者采用python脚本传递当前项目的交叉编译工具链,让python脚本来拉起busybox自身的编译系统的方式
cd your_openharmony_root_dir/third_paty/busybox/
vi build_busybox.py
# build_busybox.py文件内容如下,读者可依据自己需求,按开鸿的构建规则修改
build_busybox.py
#!/usr/bin/env python3
# Copyright (c) 2026
import argparse
import os
import pathlib
import shutil
import subprocess
import sys
def run(cmd, cwd, env):
print("[busybox-build] run:", " ".join(cmd))
subprocess.run(cmd, cwd=cwd, env=env, check=True)
def list_source_newfiles(src_dir):
tracked = set()
git_dir = src_dir / ".git"
if git_dir.exists():
out = subprocess.check_output(
["git", "ls-files", "--others", "--exclude-standard", "--cached"],
cwd=src_dir,
text=True,
)
for line in out.splitlines():
line = line.strip()
if line:
tracked.add(line)
return tracked
def disable_config_flags(config_file, disabled_flags):
if not config_file.exists():
raise FileNotFoundError(f"config file not found: {config_file}")
lines = config_file.read_text(encoding="utf-8").splitlines()
out = []
seen = set()
for line in lines:
replaced = False
for flag in disabled_flags:
if line.startswith(f"{flag}=") or line == f"# {flag} is not set":
out.append(f"# {flag} is not set")
seen.add(flag)
replaced = True
break
if not replaced:
out.append(line)
for flag in disabled_flags:
if flag not in seen:
out.append(f"# {flag} is not set")
config_file.write_text("\n".join(out) + "\n", encoding="utf-8")
def _parse_busybox_links_file(links_file):
"""Return absolute POSIX paths (leading '/') from busybox.links."""
if not links_file.exists():
raise FileNotFoundError(f"busybox.links not found: {links_file}")
paths = []
for raw in links_file.read_text(encoding="utf-8").splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
path = line if line.startswith("/") else f"/{line}"
paths.append(path)
return paths
def _busybox_link_path_to_usr_relpath(path_posix):
"""
Map BusyBox link path (as in busybox.links, e.g. /bin/ls) to a path
relative to product root_out_dir using merged-/usr layout:
/bin, /usr/bin -> usr/bin/... ; /sbin, /usr/sbin -> usr/sbin/...
"""
p = path_posix.strip()
if not p.startswith("/"):
p = f"/{p}"
rel = p.lstrip("/")
if not rel or ".." in pathlib.PurePosixPath(rel).parts:
return None
if rel.startswith("usr/bin/"):
return pathlib.Path("usr/bin") / rel[len("usr/bin/"):]
if rel.startswith("usr/sbin/"):
return pathlib.Path("usr/sbin") / rel[len("usr/sbin/"):]
if rel.startswith("bin/"):
return pathlib.Path("usr/bin") / rel[len("bin/"):]
if rel.startswith("sbin/"):
return pathlib.Path("usr/sbin") / rel[len("sbin/"):]
# LINK/ROOT/... entries produce a single path segment (e.g. /foo).
if "/" not in rel:
return pathlib.Path("usr/bin") / rel
return None
def _install_busybox_applet_symlinks(root_out_dir, busybox_bin, links_file):
"""
Create applet symlinks under <root_out>/usr/bin and <root_out>/usr/sbin
(matches fs.yml source_dir usr/bin and usr/sbin).
The cross-built busybox binary must not be executed on the host; use
busybox.links from the object tree instead of ``busybox --list``.
"""
root_out = root_out_dir.resolve()
busybox_bin = busybox_bin.resolve()
for path_posix in sorted(set(_parse_busybox_links_file(links_file))):
rel_under_out = _busybox_link_path_to_usr_relpath(path_posix)
if rel_under_out is None:
continue
link_path = (root_out / rel_under_out).resolve()
try:
link_path.relative_to(root_out)
except ValueError:
continue
link_path.parent.mkdir(parents=True, exist_ok=True)
target = os.path.relpath(busybox_bin, start=link_path.parent)
if link_path.exists() or link_path.is_symlink():
if link_path.is_symlink() and os.readlink(link_path) == target:
continue
link_path.unlink()
os.symlink(target, link_path)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--src-dir", required=True)
parser.add_argument("--work-dir", required=True)
parser.add_argument("--root-out-dir", required=True)
parser.add_argument("--clang-base", required=True)
parser.add_argument("--abi-target", required=True)
parser.add_argument("--sysroot", required=True)
parser.add_argument("--jobs", type=int, default=0)
args = parser.parse_args()
src_dir = pathlib.Path(args.src_dir).resolve()
work_dir = pathlib.Path(args.work_dir).resolve()
build_dir = work_dir / "out"
install_dir = work_dir / "install"
root_out_dir = pathlib.Path(args.root_out_dir).resolve()
stamp_file = work_dir / ".stamp"
build_dir.mkdir(parents=True, exist_ok=True)
install_dir.mkdir(parents=True, exist_ok=True)
# GN passes relative paths rebased to root_build_dir (e.g. out/.../loongsmall).
# Resolve those relative paths against current working directory to get
# stable absolute paths.
root_build_dir = pathlib.Path.cwd().resolve()
clang_base = pathlib.Path(args.clang_base)
if not clang_base.is_absolute():
clang_base = (root_build_dir / clang_base).resolve()
sysroot = pathlib.Path(args.sysroot)
if not sysroot.is_absolute():
sysroot = (root_build_dir / sysroot).resolve()
cc_path = (clang_base / "bin" / "clang").resolve()
cxx_path = (clang_base / "bin" / "clang++").resolve()
if not cc_path.exists():
raise FileNotFoundError(f"clang not found: {cc_path}")
if not cxx_path.exists():
raise FileNotFoundError(f"clang++ not found: {cxx_path}")
cc_bin = str(cc_path)
cxx_bin = str(cxx_path)
ar = f"{args.clang_base}/bin/llvm-ar"
nm = f"{args.clang_base}/bin/llvm-nm"
ar = str((clang_base / "bin" / "llvm-ar").resolve())
nm = str((clang_base / "bin" / "llvm-nm").resolve())
strip_bin = str((clang_base / "bin" / "llvm-strip").resolve())
objcopy_bin = str((clang_base / "bin" / "llvm-objcopy").resolve())
hostcc = shutil.which("cc") or shutil.which("gcc") or "cc"
env = os.environ.copy()
# Isolate busybox build from outer build system flags (sanitizers, -Werror, etc).
# busybox configuration controls its own CFLAGS/LDFLAGS via .config.
for k in [
"CFLAGS",
"CPPFLAGS",
"CXXFLAGS",
"LDFLAGS",
"ASFLAGS",
"LDLIBS",
"LIBS",
"ARFLAGS",
"RUSTFLAGS",
"EXTRA_CFLAGS",
"EXTRA_LDFLAGS",
"SANITIZE_CFLAGS",
"SANITIZE_LDFLAGS",
]:
env.pop(k, None)
target_flags = f"--target={args.abi_target} --sysroot={sysroot}"
include_flags = f"-isystem {sysroot}/usr/include/{args.abi_target}"
env["CC"] = cc_bin
env["CXX"] = cxx_bin
env["AR"] = ar
env["NM"] = nm
env["STRIP"] = strip_bin
env["LD"] = cc_bin
env["HOSTCC"] = hostcc
env["KCONFIG_NOTIMESTAMP"] = "1"
env["KCONFIG_NOSILENTUPDATE"] = "1"
env["KBUILD_OUTPUT"] = str(build_dir)
env["KCONFIG_CONFIG"] = str(build_dir / ".config")
# Busybox allyesconfig enables many legacy applets; keep build non-blocking by
# demoting selected warnings from errors in busybox only.
env["EXTRA_CFLAGS"] = (
"-Wno-error=deprecated-declarations -Wno-deprecated-declarations "
"-Wno-error=format-truncation -Wno-format-truncation "
"-Wno-error=format-overflow -Wno-format-overflow "
"-Wno-error=string-plus-int -Wno-string-plus-int "
"-Wno-error=unused-but-set-variable -Wno-unused-but-set-variable "
"-Wno-error=int-in-bool-context -Wno-int-in-bool-context "
"-Wno-error=misleading-indentation -Wno-misleading-indentation "
"-Wno-error=pointer-bool-conversion -Wno-pointer-bool-conversion "
"-Wno-error=incompatible-pointer-types-discards-qualifiers "
"-Wno-incompatible-pointer-types-discards-qualifiers "
"-Wno-error=self-assign -Wno-self-assign "
"-D__MUSL__ "
"-Wno-unknown-warning-option"
)
env["EXTRA_CFLAGS"] = f"{target_flags} {include_flags} " + env["EXTRA_CFLAGS"]
env["EXTRA_LDFLAGS"] = target_flags
before = list_source_newfiles(src_dir)
# Busybox Makefile uses STRIP in an objcopy-like way:
# $(STRIP) -s --remove-section=... input -o output
# llvm-objcopy expects: llvm-objcopy [options] input [output]
# so provide a wrapper that translates -s and -o.
strip_wrapper = work_dir / "strip.sh"
strip_wrapper.write_text(
"#!/bin/sh\n"
"LLVM_OBJCOPY=\"$1\"\n"
"shift\n"
"out=\"\"\n"
"in=\"\"\n"
"args=\"\"\n"
"while [ \"$#\" -gt 0 ]; do\n"
" a=\"$1\"\n"
" shift\n"
" if [ \"$a\" = \"-s\" ]; then\n"
" args=\"$args --strip-all\"\n"
" continue\n"
" fi\n"
" if [ \"$a\" = \"-o\" ]; then\n"
" out=\"$1\"\n"
" shift\n"
" continue\n"
" fi\n"
" case \"$a\" in\n"
" --*) args=\"$args '$a'\";;\n"
" -*) args=\"$args '$a'\";;\n"
" *) if [ -z \"$in\" ]; then in=\"$a\"; else args=\"$args '$a'\"; fi;;\n"
" esac\n"
"done\n"
"if [ -z \"$in\" ]; then\n"
" echo \"strip wrapper: missing input\" >&2\n"
" exit 2\n"
"fi\n"
"cmd=\"$LLVM_OBJCOPY $args '$in'\"\n"
"if [ -n \"$out\" ]; then\n"
" cmd=\"$cmd '$out'\"\n"
"fi\n"
"eval \"$cmd\"\n",
encoding="utf-8",
)
os.chmod(strip_wrapper, 0o755)
make_vars = [
f"CC={cc_bin}",
f"CXX={cxx_bin}",
f"LD={cc_bin}",
f"AR={ar}",
f"NM={nm}",
f"STRIP={strip_wrapper} {objcopy_bin}",
f"HOSTCC={hostcc}",
]
run(["make", f"O={build_dir}", *make_vars, "distclean"], cwd=src_dir, env=env)
run(["make", f"O={build_dir}", *make_vars, "allyesconfig"], cwd=src_dir, env=env)
disable_config_flags(
build_dir / ".config",
[
"CONFIG_INETD",
"CONFIG_FEATURE_INETD_RPC",
"CONFIG_PAM",
"CONFIG_DEBUG_SANITIZE",
# musl sysroot doesn't provide glibc GNU regex symbols used by these applets.
"CONFIG_GREP",
"CONFIG_EGREP",
"CONFIG_FGREP",
# Keep vi enabled, but turn off regex mode to avoid GNU regex dependency.
"CONFIG_FEATURE_VI_REGEX_SEARCH",
"CONFIG_SELINUX",
"FEATURE_SELINUX",
"CONFIG_FEATURE_SELINUX",
"CONFIG_CHCON",
"CONFIG_GETENFORCE",
"CONFIG_GETSEBOOL",
"CONFIG_LOAD_POLICY",
"CONFIG_MATCHPATHCON",
"CONFIG_RUNCON",
"CONFIG_SELINUXENABLED",
"CONFIG_SESTATUS",
"CONFIG_SETENFORCE",
"CONFIG_SETFILES",
"CONFIG_FEATURE_SETFILES_CHECK_OPTION",
"CONFIG_RESTORECON",
"CONFIG_SETSEBOOL",
"CONFIG_FEATURE_MOUNT_NFS",
# Avoid -static, which is incompatible with ASan/UBSan in many toolchains.
"CONFIG_STATIC",
],
)
jobs = args.jobs if args.jobs > 0 else (os.cpu_count() or 8)
run(["make", f"O={build_dir}", *make_vars, f"-j{jobs}"], cwd=src_dir, env=env)
run(
[
"make",
f"O={build_dir}",
*make_vars,
f"CONFIG_PREFIX={install_dir}",
"install",
],
cwd=src_dir,
env=env,
)
after = list_source_newfiles(src_dir)
leaked = sorted(after - before)
if leaked:
raise RuntimeError(
"build artifact leaked into source dir: " + ", ".join(leaked[:20])
)
busybox_bin = install_dir / "bin" / "busybox"
if not busybox_bin.exists():
raise FileNotFoundError(f"busybox binary not found: {busybox_bin}")
usr_bin = root_out_dir / "usr" / "bin"
usr_bin.mkdir(parents=True, exist_ok=True)
target_busybox = usr_bin / "busybox"
if target_busybox.exists() or target_busybox.is_symlink():
target_busybox.unlink()
shutil.copy2(busybox_bin, target_busybox)
links_file = build_dir / "busybox.links"
_install_busybox_applet_symlinks(root_out_dir, target_busybox, links_file)
stamp_file.write_text("busybox build done\n", encoding="utf-8")
return 0
if __name__ == "__main__":
sys.exit(main())
为busybox组件创建BUILD.gn和bundle.json文件
cd your_openharmony_root_dir/third_paty/busybox/
vi BUILD.gn
vi bundle.json
# BUILD.gn和bundle.json文件内容如下,读者可依据自己需求,按开鸿的构建规则修改
BUILD.gn
import("//build/config/clang/clang.gni")
import("//build/config/ohos/config.gni")
import("//build/ohos.gni")
import("//build/config/ohos/musl.gni")
_busybox_gen_dir = "$root_gen_dir/third_party/busybox"
action("busybox_install") {
script = "build_busybox.py"
outputs = [
"$_busybox_gen_dir/.stamp",
"$_busybox_gen_dir/install/bin/busybox",
]
args = [
"--src-dir",
rebase_path(".", root_build_dir),
"--work-dir",
rebase_path(_busybox_gen_dir, root_build_dir),
"--root-out-dir",
rebase_path("$root_out_dir", root_build_dir),
"--clang-base",
rebase_path(clang_base_path, root_build_dir),
"--abi-target",
abi_target,
"--sysroot",
# Use product sysroot (contains usr/include/<abi>/... like byteswap.h).
rebase_path("$root_out_dir/sysroot", root_build_dir),
]
}
ohos_prebuilt_executable("busybox") {
source = "$_busybox_gen_dir/install/bin/busybox"
deps = [ ":busybox_install" ]
subsystem_name = "thirdparty"
part_name = "busybox"
# Install paths are generated by build_busybox.py under $root_out_dir/usr/{bin,sbin}
# for fs.yml (top-level usr/bin, usr/sbin). GN's system image layout is system/bin,
# so disable default prebuilt install to avoid a duplicate under system/.
install_images = [
"system",
"ramdisk",
"updater",
]
install_enable = false
}
bundle.json
{
"name": "@ohos/busybox",
"description": "busybox",
"version": "1.0",
"license": "Public Domain",
"publishAs": "code-segment",
"segment": {
"destPath": "third_party/busybox"
},
"dirs": {},
"scripts": {},
"licensePath": "",
"readmePath": {
"en": ""
},
"component": {
"name": "busybox",
"subsystem": "thirdparty",
"syscap": [],
"features": [],
"adapted_system_type": [
"standard",
"small",
"mini"
],
"rom": "73KB",
"ram": "146KB",
"deps": {
"components": [
],
"third_party": []
},
"build": {
"sub_component": [
"//third_party/busybox:busybox"
],
"inner_kits": [
{
"name" : "//third_party/busybox:busybox"
}
],
"test": [
]
}
}
}
在产品配置中添加busybox组件
cd your_openharmony_root_dir/
vi vendor/company/borad_name/config.json # eg. vi vendor/hisilicon/hispark_taurus_linux/config.json
找到config.json文件中的third_party子系统配置部分"subsystem": "thirdparty",添加busybox组件{ "component": "busybox", "features": []}
若config.json文件中没有third_party子系统的配置就自行添加
示例:
{
"subsystem": "thirdparty",
"components": [
{ "component": "busybox", "features": []}
]
},
编译构建
完成这些后,按照开鸿官方的构建方法编译构建即可,笔者使用的是build,sh脚本来构建,hb构建工具在笔者环境中不能正常使用,就没有尝试
cd your_openharmony_root_dir/
./build.sh --product-name xxx --ccache # xxx 为config.json中product_name键的值,即读者要编译构建的目标
结束
编译构建完成后,out目录下找到对应的rootfs目录(这个目录就是项目编译出来的实际的文件系统),在rootfs目录下执行
ls usr/bin
# 正常情况会看到busybox这个可执行文件和一些命令的符号链接
然后就可以烧录到板卡上去验证了,笔者当前是可以正常使用的
若大家有更好的移植方法或文章有误的地方,欢迎大家在评论区指出
更多推荐

所有评论(0)