axel 是LINUX下的一个不错的HTTP/ftp高速下载工具.支持多线程下载,断点续传,且可以从多个地址或者从一个地址的多个连接来下载同一个文件.适合网速不给力时多线程下载提高下载速度.比如在国内VPS或服务器上下载nmp一键安装包用Axel就比wget快太多了,尤其是开启线程后简直是秒下载。
今天猫哥分享下移植axel多线程下载工具神器到鸿蒙PC上的踩坑记。为啥说是踩坑记呢?因为这个过程中又遇到一个坑,特此记录分享下。下文中会把报错都截图发出来。顺便介绍下报错的原因及解决办法。
一、前言
axel 命令是一个轻量级、快速且用户友好的 Linux 命令行下载加速器。它通过将文件分割成片段并同时开启多个线程下载来加快下载速度,这对于大文件和网络不好时尤其有用。axel 支持 HTTP、HTTPS、FTP 和 FTPS 协议。
本文将详细介绍如何将 axel 移植到鸿蒙 PC 平台(aarch64),并解决移植过程中踩过的坑,虽然是针对当前库的,在其它库的移植中可以借鉴。
axel下载速度和 curl下载速度大比拼:
效果如图,可以看到axel开启了50个线程后速度直接起飞。
bash
#curl 下载方式
curl -L -O https://github.com/Harmonybrew/ohos-neovim/releases/download/0.11.4/neovim-0.11.4-ohos-arm64.tar.gz
#axel下载方式
./axel -k -n 50 https://github.com/Harmonybrew/ohos-neovim/releases/download/0.11.4/neovim-0.11.4-ohos-arm64.tar.gz

移植成功截图 :

二、环境准备
安装基础开发工具:
bash
sudo apt update
sudo apt install -y git curl cmake ninja-build gcc g++ make autoconf automake libtool yasm nasm
2.1配置ohos-sdk
- 创建工作目录:
bash
mkdir ~/myprj && cd ~/myprj
- 下载ohos-sdk:
bash
cd ~
wget https://cidownload.openharmony.cn/version/Master_Version/ohos-sdk-full_ohos/20250819_020817/version-Master_Version-ohos-sdk-full_ohos-20250819_020817-ohos-sdk-full_ohos.tar.gz
tar -zvxf version-Master_Version-ohos-sdk-full_ohos-20250819_020817-ohos-sdk-full_ohos.tar.gz
#注,截止目前有新的SDK了,使用这个吧:
wget https://cidownload.openharmony.cn/version/Master_Version/ohos-sdk-public_ohos/20251209_020142/version-Master_Version-ohos-sdk-public_ohos-20251209_020142-ohos-sdk-public_ohos.tar.gz
cd ~/ohos-sdk/linux
#解压工具链
unzip native-linux-x64-6.0.0.46-Beta1.zip
unzip toolchains-linux-x64-6.0.0.46-Beta1.zip
安装目录设定
bash
sudo mkdir -p /data/service/hnp
sudo chmod 777 -R /data/service/hnp
2.2 猫哥的exports.sh环境脚本
猫哥为了方面大家的移植,特准备了雷打不动的exports.sh脚本,方便服用。后面无论编译什么库,用这个脚本执行下以下命令,就加载好了交叉编译环境。
bash
#加载交叉编译工具链环境
source exports.sh
exports.sh脚本内容如下:
bash
echo "hello exports,交叉编译环境配置"
## SDK路径,你唯一需要根据实际的改的地方
SDK_PATH="/root/ohos-sdk/linux"
echo "SDK_PATH:$SDK_PATH"
export OHOS_SDK="$SDK_PATH"
export HNP_PERFIX=
export COMPILER_TOOLCHAIN=${OHOS_SDK}/native/llvm/bin/
BUILD_OS=$(uname)
PYTHON=$(python --version)
echo "python : $PYTHON"
export CC=${COMPILER_TOOLCHAIN}clang && echo "CC : ${CC}"
export CXX=${COMPILER_TOOLCHAIN}clang++ && echo "CXX : ${CXX}"
export HOSTCC=${CC} && echo "HOSTCC : ${HOSTCC}"
export HOSTCXX=${CXX} && echo "HOSTCXX : ${HOSTCXX}"
export CPP="${CXX} -E" && echo "CPP : ${CPP}"
export AS=${COMPILER_TOOLCHAIN}llvm-as && echo "AS : ${AS}"
export LD=${COMPILER_TOOLCHAIN}ld.lld && echo "LD : ${LD}"
export STRIP=${COMPILER_TOOLCHAIN}llvm-strip && echo "STRIP : ${STRIP}"
export RANLIB=${COMPILER_TOOLCHAIN}llvm-ranlib && echo "RANLIB : ${RANLIB}"
export OBJDUMP=${COMPILER_TOOLCHAIN}llvm-objdump && echo "OBJDUMP : ${OBJDUMP}"
export OBJCOPY=${COMPILER_TOOLCHAIN}llvm-objcopy && echo "OBJCOPY : ${OBJCOPY}"
export NM=${COMPILER_TOOLCHAIN}llvm-nm && echo "NM : ${NM}"
export AR=${COMPILER_TOOLCHAIN}llvm-ar && echo "AR : ${AR}"
export SYSROOT=${OHOS_SDK}/native/sysroot
export PKG_CONFIG_SYSROOT_DIR=${SYSROOT}/usr/lib/aarch64-linux-ohos
export PKG_CONFIG_PATH=${PKG_CONFIG_SYSROOT_DIR}
export PKG_CONFIG_EXECUTABLE=${PKG_CONFIG_SYSROOT_DIR}
export HNP_TOOL=${OHOS_SDK}/toolchains/hnpcli
export CMAKE=${OHOS_SDK}/native/build-tools/cmake/bin/cmake
export TOOLCHAIN_FILE=${OHOS_SDK}/native/build/cmake/ohos.toolchain.cmake
export WORK_ROOT=${PWD}
export ARCHIVE_PATH=${WORK_ROOT}/output
export COMM_DEP_PATH=${WORK_ROOT}/deps_install
export HNP_PUBLIC_PATH=${HNP_PERFIX}/data/service/hnp/
export MAKE_QUITE_PARAM=" -s "
export CONFIGURE_QUITE_PARAM=" --quiet "
export TARGET_PLATFORM=aarch64-linux-ohos
export TARGET=aarch64-linux-ohos
export CFLAGS="-fPIC -D__MUSL__=1 -D__OHOS__ -fstack-protector-strong --target=${TARGET_PLATFORM} --ld-path=${LD} --sysroot=${SYSROOT} -stdlib=libc++ "
export CXXFLAGS="${CFLAGS} "
export LD_LIBRARY_PATH=${SYSROOT}/usr/lib:${LD_LIBRARY_PATH}
export LDFLAGS="--ld-path=${LD} -Wc,--target=${TARGET_PLATFORM} --sysroot=${SYSROOT} -fuse-ld=lld "
export HOST_TYPE="--host=aarch64-linux --build=aarch64-linux"
#export NCURSES_INSTALL_HNP_PATH="${HNP_PUBLIC_PATH}/ncurses.org/ncurses_v6.4"
mkdir -p ${HNP_PUBLIC_PATH}
mkdir -p ${ARCHIVE_PATH}
chmod 777 -R ${HNP_PUBLIC_PATH}
mkdir -p code
echo "LDFLAGS:${LDFLAGS}"
#export PKG_CONFIG_PATH="${CUSTOM_PREFIX}/lib/pkgconfig:$PKG_CONFIG_PATH"
2.2 适配策略
从其他仓库如github上找到axel的源码,fork到自己的git项目上一份。然后clone到本地。
axel官方仓库地址:https://github.com/axel-download-accelerator/axel.git
在上一步执行完source exports.sh 配置好环境的前提下,再执行以下命令自动生成configure文件。
bash
autoreconf -i

发现到这里就报错了。不过这个呢不算坑,环境问题很好解决。更换 autoconf新版本就行了。
解决办法:
bash
# 下载最新Autoconf源码
wget http://ftp.gnu.org/gnu/autoconf/autoconf-2.72.tar.gz
tar xzf autoconf-2.72.tar.gz
cd autoconf-2.72
# 配置并安装
./configure --prefix=/usr/local
make -j$(nproc)
sudo make install
#先which以下看看原来的autoconf 在哪
which autoconf
# 如果autoconf在/usr/bin/下则需要执行下面的强制覆盖旧版本(否则不需要下面的)
sudo cp /usr/local/bin/autoconf /usr/bin/autoconf
sudo cp /usr/local/bin/autoheader /usr/bin/autoheader
sudo cp /usr/local/bin/autom4te /usr/bin/autom4te
sudo cp /usr/local/bin/autoreconf /usr/bin/autoreconf
sudo cp /usr/local/bin/autoscan /usr/bin/autoscan
sudo cp /usr/local/bin/autoupdate /usr/bin/autoupdate
接下来再次执行,
bash
autoreconf -i
又报了下面错误:

这个也好办,根据提示,少啥装啥就成。
bash
apt get install autoconf-archive
再次执行autoreconf -i 之后,应该就顺利的生成了configure。
接下来,准备我们的编译脚本,起个名字叫build_ohos.sh,放到下载的开源项目的根目录里。一开始我的编译脚本内容如下:
bash
#!/bin/bash
export AXEL_INSTALL_HNP_PATH=${HNP_PUBLIC_PATH}/axel.org/axel_2.17.14
make clean
# 编译安装axel
mkdir -p ${AXEL_INSTALL_HNP_PATH}
echo $LDFLAGS
./configure --host=aarch64-linux-musl \
--prefix=${AXEL_INSTALL_HNP_PATH}
make VERBOSE=1 -j$(nproc)
make install
# 生成鸿蒙HNP软件包
cp hnp.json ${AXEL_INSTALL_HNP_PATH}/
pushd ${AXEL_INSTALL_HNP_PATH}/../
${HNP_TOOL} pack -i ${AXEL_INSTALL_HNP_PATH} -o ${ARCHIVE_PATH}/
tar -zvcf ${ARCHIVE_PATH}/ohos_axel_2.17.14.tar.gz axel_2.17.14/
popd
看着很简单吧,接着开始编译吧:
bash
./build_ohos.sh
结果报以下错误:

这是警告,结果当成了错误导致编译不过!针对该问题,问了万能的AI,解释如下:
When working from a git repository the build system will detect that and will add -Werror to the CFLAGS if supported; so if you're not doing development you should probably consider passing --disable-Werror to configure in order to prevent build failures due to mere warnings.
问题原因:
在 Git 仓库中工作时,构建系统会自动添加 -Werror 选项,将警告视为错误。这可能导致非开发环境下的编译失败(如警告 unused-command-line-argument)
解决办法:
在 ./configure 命令中添加 --disable-Werror 参数。修改上面的build_ohos.sh脚本:
bash
AXEL_INSTALL_HNP_PATH=${HNP_PUBLIC_PATH}/axel.org/axel_2.17.14
make clean
# 编译安装axel
mkdir -p ${AXEL_INSTALL_HNP_PATH}
echo $LDFLAGS
./configure --host=aarch64-linux-musl \
##增加该参数
--disable-Werror \
--prefix=${AXEL_INSTALL_HNP_PATH}
make VERBOSE=1 -j$(nproc)
make install
# 生成鸿蒙HNP软件包
cp hnp.json ${AXEL_INSTALL_HNP_PATH}/
pushd ${AXEL_INSTALL_HNP_PATH}/../
${HNP_TOOL} pack -i ${AXEL_INSTALL_HNP_PATH} -o ${ARCHIVE_PATH}/
tar -zvcf ${ARCHIVE_PATH}/ohos_axel_2.17.14.tar.gz axel_2.17.14/
popd
接下来就是踩坑记了。以为没问题,该顺利了吧,结果又报了如下错误:
报错提示 pthread_cancel、pthread_setcancelstate 和 pthread_setcanceltype 未定义,原因在于链接器找不到 pthread 库。

添加 -pthread 链接选项试试?
加上了-pthread报错依旧,报错提示 pthread_cancel 和 pthread_setcancelstate 未定义,原因在于链接器找不到 pthread 库或符号被注释( musl C 库)。
注意:
这是一个非常典型的移植问题,特别是在从 glibc(常见的 Linux 发行版如 Ubuntu/CentOS 使用)切换到 musl(Alpine Linux 使用)时。这个地方有个坑,鸿蒙PC使用的musl的c库,不完全支持pthread的接口。经查资料了解,这两个函数属于 POSIX 线程(pthreads)标准,用于控制线程的异步终止。musl:遵循"单一库"原则,所有的 pthread 函数都直接实现在 libc.so 之中。在 musl 环境下,你不需要(也不应该)链接一个独立的 libpthread,因为 libc 已经包含了它。
来做一个测试:
验证下鸿蒙SDK的musl c库中缺少 pthread_cancel 和 pthread_setcancelstate等函数,是否属实?

musl的c库中的pthread不完整?确定是缺少 pthread_cancel 和 pthread_setcancelstate?
cpp
//root@VM-0-12-ubuntu:~/build/code# cat test.c
#include <pthread.h>
#include <stdio.h>
void* thread_func(void* arg) {
printf("Thread running\n");
return NULL;
}
int main() {
pthread_t thread;
if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {
perror("pthread_create");
return 1;
}
pthread_join(thread, NULL);
pthread_cancel(0);
printf("Thread joined\n");
return 0;
}
上述代码使用$CC test.c --target=aarch64-linux-ohos编译,直接报错了。
注意,这里的--target 参数一定不能少,否则不带这个参数,你编出来的竟是X64的二进制程序,虽然你用的是鸿蒙SDK中的Clang。至于原因,你猜猜?若未指定--target,编译器会认为输出文件在主机架构上运行,导致生成X64二进制。这是Clang LLVM架构无关性:LLVM通过IR(中间表示)实现跨平台编译,--target参数指示后端生成特定架构的机器码。
GCC在交叉编译时,通常通过编译器路径或环境变量自动识别目标架构(如arm-linux-gnueabi-gcc中的arm-linux-gnueabi部分)。GCC的--target参数可选,但隐式识别机制更可靠。
Clang的--target参数是强制性的,因为其架构识别依赖显式指定。Clang不依赖编译器路径自动识别,需明确指出目标架构(如--target=aarch64-linux-ohos)。
bash
$CC test.c --target=aarch64-linux-ohos
然而上述代码只要去掉pthread_cancel 就能编译过。
如何解决?
不侵入修改代码的情况下,目前好像无解?欢迎留言。有更好的办法没?但是这...,它本身就是个多线程下载工具,直接屏蔽掉,如果搞个桩强制让它过,怕用起来线程暴涨或者跑飞。
再接着试,尝试了下这种方式是否可行?竟可以过了。这是为什么?
bash
$CC test.c --target=aarch64-linux-ohos -static -pthread #给链接选项上加上static

结论:在 LDFLAGS 中强制加上 -static 是一个非常关键的操作,尤其是在处理像 musl 这种极简 C 库的环境时。
加上static, 它为啥能解决"编译不过"的问题?

结论:当你默认链接动态库时,链接器发现 .so 里没这东西,报错;当你强制 -static 时,链接器从 .a 里找到了这些函数,编译通过。
这说明什么?不是musl的标准c库pthread少东西,而是它的so动态库可能为了精简做了优化(导致so库一些函数没导出)。然而它的静态库里有,用静态库可以。
经查证,这是ohos官方对musl的c库做了优化魔改。这里是个踩坑点,这点需要开发者注意。
详解官方文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/musl-peculiar-symbol
Musl 并不存在"动态精简、静态完整"的双重标准,这是一种"非标准"的、由厂商实施的"剪裁"。因为许多芯片厂商(如瑞芯微、全志、博通等)或路由器系统(OpenWrt 早期版本、某些定制机顶盒)为了追求极致的启动速度和 Flash 占用也会这么做:
他们会修改 Makefile,在编译 libc.so 时通过 LDFLAGS 传入 --version-script 或 -Wl,--dynamic-list,强行隐藏不常用的符号。
目的:精简 .dynsym 符号表。因为动态符号表在运行时是常驻内存的,每多一个符号都会占用几十字节。对于只有 8MB 或 16MB Flash 的设备,这是一种极端优化。
结果:导致你发现 .so 里没有 pthread_cancel,但本地编译环境的 .a 文件因为没有经过这种处理,所以符号还在。
确认Musl SO库是否缺少 pthread_cancel 的方法如下:
使用 nm 工具查看 SO 库的符号表:
bash
find -name "libc.so"
# nm *so
$NM ./ohos-sdk/linux/native/sysroot/usr/lib/arm-linux-ohos/libc.so | grep pthread_cancel

对比 SO 库与静态库(.a)的符号:
bash
$NM ./ohos-sdk/linux/native/sysroot/usr/lib/arm-linux-ohos/libc.a | grep pthread_cancel


注意:
虽然 -static 很好用,但它有一个前置条件:
你的系统中必须已经安装了所有依赖库的静态版本(.a 文件)。比如这个库,也得保证依赖的openssl有对应的.a的静态库才行。
因为加上 -static 后,链接器会尝试将"所有"相关的库全部进行静态链接。这不仅仅包括 C 库(musl/libc),也包括你的项目所依赖的所有第三方库。
当你传递 -static 给 clang 或 gcc 时,链接器的逻辑会发生根本性改变:
- 默认(不加时):链接器会优先寻找 .so(动态共享库)。只有找不到 .so 时,才会去尝试找 .a(静态库)。
- 加了 -static:链接器会彻底无视所有的 .so 文件。它会强迫自己只去寻找 .a 文件。
比如正在移植的 axel 为例,如果你加了 -static,以下库都会被打包进二进制文件:
bash
1.C 标准库:即 musl (libc)。
2.线程库:libpthread(在 musl 中它是 libc 的一部分)。
3.加密库:libssl 和 libcrypto (OpenSSL)。
4.压缩库:libz (zlib)。
5.数学库:libm。
如何确认是否真的"全静态"了?
编译完成后,你可以用以下两个命令检查:
- 使用 file 命令:
bash
file ./axel
如果是全静态,输出会显示:statically linked。如果是动态,输出会显示:dynamically linked, interpreter /lib/ld-musl...。
- 使用 ldd 命令(查看依赖的动态库):
bash
ldd ./axel
如果是全静态,它会提示 not a dynamic executable(不是动态可执行文件)。如果是动态,它会列出一串 .so 的路径。
有"半静态"链接的方法吗?让选择哪个是动态,哪些是静态的?
有,但非常不推荐。可以不用全局 -static,而是针对性地控制:
bash
# 这种写法搞反顺序很容易造成bug或崩溃,存在潜在风险。
LDFLAGS="-Wl,-Bstatic -lc -Wl,-Bdynamic -lssl -lz"
风险一: 链接器的扫描顺序是从左往右。当它发现一个函数没找到时,会去右边的库里找。
问题:libssl 和 libz 是依赖 libc 的。如果你先静态链接了 libc,然后又去动态链接 lssl,当 lssl 运行时需要找 libc 的函数时,由于 libc 已经被"关"在静态代码块里了,动态库可能找不到它,或者导致符号冲突。
风险二:musl 的"唯一性"原则
正在使用的是 musl 库。musl 的设计哲学非常严苛:它极其不建议混合链接。
问题:如果你动态链接了 libssl.so,而这个 libssl.so 在运行时又试图去加载系统的 libc.so,此时你的程序里就会同时存在两个 libc(一个静态打包的,一个动态加载的)。这会导致全局变量(如 errno、线程局部存储 TLS)冲突,程序会立刻崩溃。
这种写法通常在什么场景用?
这种"半静态"写法通常用于以下场景:
"比如我想把难搞的第三方库静态打包,但保留最基础的系统 C 库为动态链接。"
bash
# 这种写法是比较常见的:静态链接 ssl,动态链接系统其他的,让系统的及最基础的动态链接
LDFLAGS="-Wl,-Bstatic -lssl -lcrypto -Wl,-Bdynamic -lz -lpthread -lc"
比如 你想让某些库动态,某些静态,请务必保证 libc 是动态的,而第三方库是静态的。
bash
# 假设你想静态打包 openssl,但动态链接 libc 和 zlib
LDFLAGS="-Wl,-Bstatic -lssl -lcrypto -Wl,-Bdynamic -lz -lc"
如果搞反了顺序,且在 musl 环境下风险极大。对于当前该工具来说,最稳妥的办法就是全静态链接。
最后,编译没成功:

到这里就不是坑啦,就是少依赖。因为它依赖了openssl.那么说明在编译此库前,你需要先把openssl库搞定吧,别乱了序。参考猫哥之前的移植文章,顺利编译openssl4.0.0库。
再接下来,修改上面的build_ohos.sh脚本,增加openssl库的头文件和库文件的路径,如下:
bash
#!/bin/bash
export AXEL_INSTALL_HNP_PATH=${HNP_PUBLIC_PATH}/axel.org/axel_2.17.14
make clean
# 设置依赖的OpenSSL路径(注,编译该库前确保openssl已编译)
OPENSSL_PATH=${HNP_PUBLIC_PATH}/openssl.org/openssl_4.0.0
# 编译安装axel
mkdir -p ${AXEL_INSTALL_HNP_PATH}
#增加链接依赖的openssl库,注意加上static,否则就是前面的坑
CFLAGS="${CFLAGS:-} -D_GNU_SOURCE -I${OPENSSL_PATH}/include "
LDFLAGS="${LDFLAGS:-} -static -pthread -L${OPENSSL_PATH}/lib "
CXXFLAGS="${CFLAGS} "
echo $LDFLAGS
./configure --host=aarch64-linux-musl \
--disable-Werror \
--prefix=${AXEL_INSTALL_HNP_PATH}
make VERBOSE=1 -j$(nproc)
make install
# 生成鸿蒙HNP软件包
cp hnp.json ${AXEL_INSTALL_HNP_PATH}/
pushd ${AXEL_INSTALL_HNP_PATH}/../
${HNP_TOOL} pack -i ${AXEL_INSTALL_HNP_PATH} -o ${ARCHIVE_PATH}/
tar -zvcf ${ARCHIVE_PATH}/ohos_axel_2.17.14.tar.gz axel_2.17.14/
popd
hnp.json文件内容:
bash
{
"type":"hnp-config",
"name":"axel",
"version":"2.17.14",
"install":{}
}
编译成功截图:

三、构建方法总结
- 加载exports.sh脚本配置交叉编译环境。
- 编写build_ohos.sh脚本针对要编译的项目和hnp.json描述文件。
- 执行build_ohos.sh构建。
第一步是通用的,省略后只需后两步骤。
四、关键配置解析
| 配置项 | 作用说明 | 必要性 |
|---|---|---|
--disable-Werror |
避免把警告当成错误阻碍编译 | 推荐 |
CFLAGS=要包含openssl的头文件 |
因为它依赖了openssl | 必需 |
LDFLAGS=-static |
static关键,静态链接pthread库,否则踩坑 | 必需 |
五、部署验证
5.1 文件结构
axel_2.17.11/
└── bin/
└── axel # 主程序
5.2 功能测试
bash
axel -n 10 http://example.com/large_file.iso
六、使用示例
-
基础下载:
bashaxel http://host/file.zip -
指定线程数:
bashaxel -n 16 http://host/file.zip -
限速下载:
bashaxel --max-speed=1M http://host/file.zip
七 真机验证
注: 这里推荐个验证的好办法,可以在鸿蒙pc的容器中验证。没真机的小伙伴们也有福音啦。感谢hqzing大佬的鸿蒙容器,介绍地址:《低成本玩转鸿蒙容器的丐版方案
》
鸿蒙PC容器使用
bash
#1.带镜像加速前缀docker.1ms.run/,让服务器支持arm64指令(只需首次执行)
docker run --rm --privileged docker.1ms.run/tonistiigi/binfmt --install arm64
#2.拉取鸿蒙容器镜像(只需首次执行)
docker run --name=ohos -itd --platform linux/arm64 docker.1ms.run/hqzing/docker-mini-openharmony:latest
#3.拷贝宿主机中生成的axel可执行程序到容器中
docker cp ./axel ohos:/root/
#4.进入容器
docker exec -it ohos sh
#5.验证测试
cd /root
#curl 下载方式
curl -L -O https://github.com/Harmonybrew/ohos-neovim/releases/download/0.11.4/neovim-0.11.4-ohos-arm64.tar.gz
#axel下载方式
./axel -k -n 50 https://github.com/Harmonybrew/ohos-neovim/releases/download/0.11.4/neovim-0.11.4-ohos-arm64.tar.gz
下载速度大比拼截图:

附件资源:
Linux 实用工具Axel安装及使用教程(支持多线程下载)
最后,欢迎加入开源鸿蒙PC社区 :https://harmonypc.csdn.net/,共同交流进步!