Apache APR 常用 CLI 命令与工具详解

实测详解 APR 全部命令行工具:apr-1-config 与 apu-1-config 逐参数用法与组合模板、源码树自带的 testall 测试套件运行方法,并给出线程锁/时间/子进程/crypto 四个递进 C 示例的编译运行演示,基于 Ubuntu 22.04 与 CentOS 8 实测。

Apache APRApache APRC/C++命令行LinuxLinux系统2026/10/16

Apache APR 常用 CLI 命令与工具详解

目标:把 APR 在服务器上可直接操作的全部命令行工具一次讲清——apr-1-config、apu-1-config 两个配置脚本的逐参数用法与组合模板、源码树自带的 testall 自测套件,并用线程/锁、时间、子进程、加解密四个递进 C 示例演示「编译命令 + 运行结果」全流程;基于 apr 1.7.6 + apr-util 1.6.5 在 Ubuntu 22.04(GCC 11.4.0)与 CentOS 8(GCC 8.5.0)实测。

一、环境信息

  • 操作系统:Ubuntu 22.04(x86_64)为主,CentOS 8.4.2105(x86_64)对照;
  • 软件版本:apr 1.7.6、apr-util 1.6.5(源码编译安装于 /usr/local/apr);
  • 编译器:GCC 11.4.0(Ubuntu)/ 8.5.0(CentOS),GNU Make 4.3 / 4.2.1;
  • 演示目录:/root(示例源码与二进制均在此)。

二、工具概览

APR 是库(无独立守护进程),命令行工具天然少。apr 1.7.6 + apr-util 1.6.5 源码安装后,bin/ 目录实测只有两个工具:

$ ls /usr/local/apr/bin/
apr-1-config
apu-1-config
工具所属作用
apr-1-configapr 1.7.6输出编译/链接 APR 核心库所需的编译器参数与路径
apu-1-configapr-util 1.6.5输出编译/链接 apr-util 扩展库所需参数(含 LDAP/DBM 库信息)

💡 技术拓展:老版本 Apache 生态里常见的 dbmmanage、htdigest、htpasswd 等工具并不属于现代 apr-util——apr-util 自 1.3 时代后不再随包安装命令行工具,这些工具如今由 Apache httpd 的安装包提供(如 Ubuntu 的 apache2-utils)。apr-util 1.6.x 安装后只有上表两个 config 脚本(本文 ls 实测佐证)。

此外源码树附带两个测试套件工具(非安装产物,位于源码目录 test/ 下,编译源码后可用):apr 的 test/testall 与 apr-util 的 test/testall,见第六章。

三、前置条件

  • 已完成 APR 安装(仓库版路径 /usr/bin/,源码版路径 /usr/local/apr/bin/,本文以源码版演示;安装过程见《Apache APR 在 Ubuntu 22.04 集成使用指南》);
  • 运行示例需 gcc 与 make(Ubuntu apt-get install -y build-essential;CentOS yum install -y gcc make);
  • 验证工具可用:
$ /usr/local/apr/bin/apr-1-config --version
1.7.6
$ /usr/local/apr/bin/apu-1-config --version
1.6.5

四、apr-1-config 详解

4.1 全参数速查(实测输出)

apr-1-config [OPTION],每个 OPTION 独立输出一行信息。20 个参数逐个实测的结果:

参数实测输出用途
--prefix/usr/local/apr安装前缀
--bindir/usr/local/apr/bin可执行文件目录
--includedir/usr/local/apr/include/apr-1头文件目录
--ccgccconfigure 探测到的编译器
--cppgcc -E预处理器命令
--cflags-g -O2编译优化参数
--cppflags-DLINUX -D_REENTRANT -D_GNU_SOURCE平台宏(必带)
--includes-I/usr/local/apr/include/apr-1头文件搜索路径
--ldflags(空)链接器附加参数
--libs-luuid -lcrypt -lpthread依赖系统库(不含 -lapr-1!)
--srcdir/root/src/apr-1.7.6configure 时源码树路径
--installbuilddir/usr/local/apr/build-1构建规则目录
--link-ld-L/usr/local/apr/lib -lapr-1直接链接开关
--link-libtool/usr/local/apr/lib/libapr-1.lalibtool 链接开关
--shlib-path-varLD_LIBRARY_PATH动态库环境变量名
--apr-la-file/usr/local/apr/lib/libapr-1.la.la 文件路径
--apr-so-extlolibtool 对象后缀
--apr-lib-target-rpath $(libdir) $(OBJECTS)libtool 目标模板
--apr-libtool/usr/local/apr/build-1/libtoollibtool 副本路径
--version1.7.6版本号

参数解读要点:--cppflags 的三个宏(LINUX/REENTRANT/GNU_SOURCE)必须出现在编译命令里,缺失会导致线程相关声明不完整;--libs 与 --link-ld 的分工见下方组合模板;CentOS 8 上 --cflags 多 -pthread、--libs 多 -lrt -ldl(双环境实测差异,工具已自动吸收,脚本无需区分发行版)。

4.2 日常组合用法

# 一行取全部编译参数(推荐写法)
$ /usr/local/apr/bin/apr-1-config --cflags --cppflags --includes
 -g -O2 -DLINUX -D_REENTRANT -D_GNU_SOURCE -I/usr/local/apr/include/apr-1

# 官方推荐:直接链接(非 libtool 构建)
$ /usr/local/apr/bin/apr-1-config --link-ld --libs
 -L/usr/local/apr/lib -lapr-1 -luuid -lcrypt  -lpthread

# 官方推荐:libtool 构建(写 Makefile 时)
$ /usr/local/apr/bin/apr-1-config --link-libtool --libs
 /usr/local/apr/lib/libapr-1.la -luuid -lcrypt  -lpthread

# 版本号用于配置检查(如 httpd 编译脚本做最小版本判断)
$ test "$(/usr/local/apr/bin/apr-1-config --version)" = "1.7.6" && echo APR-OK
APR-OK

组合参数按顺序拼接输出(实测 --libs --link-ld 输出 -luuid -lcrypt -lpthread -L/usr/local/apr/lib -lapr-1)。

五、apu-1-config 详解

5.1 全参数速查(实测输出)

参数实测输出(初始 sdbm 构建)实测输出(启用 ldap/bdb 后)用途
--prefix/usr/local/apr同左安装前缀
--bindir/usr/local/apr/bin同左可执行目录
--includedir/usr/local/apr/include/apr-1同左头文件目录
--includes-I/usr/local/apr/include/apr-1 -I/usr/include同左头文件路径(多 -I/usr/include 给 expat)
--ldflags-L/usr/lib -L/usr/lib64同左链接搜索路径
--libs-lexpat-lldap -llber -ldb-5.3 -lexpat依赖库
--ldap-libs(空)-lldap -llberLDAP 链接库
--dbm-libs(空)-ldb-5.3DBM 链接库
--avoid-ldap(空)(空)从 --libs 剔除 LDAP 信息
--avoid-dbm(空)(空)从 --libs 剔除 DBM 信息
--srcdir/root/src/apr-util-1.6.5同左源码树路径
--link-ld-L/usr/local/apr/lib -laprutil-1同左直接链接开关
--link-libtool/usr/local/apr/lib/libaprutil-1.la同左libtool 链接开关
--apu-la-file/usr/local/apr/lib/libaprutil-1.la同左.la 文件路径
--old-expat@APR_XML_EXPAT_OLD@同左模板替换瑕疵(见 Q2)
--db-version05DBM 后端版本(0=sdbm 内置,5=BDB 5.x)
--version1.6.5同左版本号

💡 说明:两列输出对比直观展示 configure 选项如何改变工具行为——LDAP/BDB 启用是重编 apr-util(--with-ldap --with-berkeley-db)的结果。--avoid-ldap/--avoid-dbm 是布尔开关,作用是让同一次调用里的 --libs 输出不带对应库(便于构建系统按需裁剪),单独执行无输出属正常。

5.2 日常组合用法

# apu + apr 双库完整编译参数(crypto/DBD 等场景)
$ APR=/usr/local/apr/bin/apr-1-config; APU=/usr/local/apr/bin/apu-1-config
$ gcc app.c $($APR --cflags --includes) $($APU --includes) \
      $($APR --link-ld) $($APU --link-ld) $($APU --libs) $($APR --libs)

# 检查 DBM 后端类型(运维排查 .db 文件兼容性)
$ /usr/local/apr/bin/apu-1-config --db-version
5

# 验证 LDAP 支持是否真的编入(输出空 = 未启用)
$ /usr/local/apr/bin/apu-1-config --ldap-libs
 -lldap -llber

六、源码树测试套件:testall

APR 源码自带两套自测程序,用于安装后验证库正确性(不随 make install 安装,在源码树内编译运行)。这既是质量验证手段,也是排障利器(安装环境异常时 testall 会暴露)。

6.1 apr-util 测试套件

$ cd /root/src/apr-util-1.6.5/test
$ make
if test ! -d "./data"; then cp -r ./data data; fi
make[1]: Leaving directory '/root/src/apr-util-1.6.5/test'
$ ./testall
teststrmatch        :  SUCCESS
testuri             :  SUCCESS
testuuid            :  SUCCESS
testbuckets         :  SUCCESS
testpass            :  SUCCESS
testmd4             :  SUCCESS
testmd5             :  SUCCESS
testcrypto          :  SUCCESS
testldap            :  SUCCESS
testdbd             :  SUCCESS
testdate            :  SUCCESS
testmemcache        :  SUCCESS
testredis           :  SUCCESS
testxml             :  SUCCESS
testxlate           :  SUCCESS
testrmm             :  SUCCESS
testdbm             :  SUCCESS
testqueue           :  SUCCESS
testreslist         :  SUCCESS
testsiphash         :  SUCCESS
All tests passed.

⚠️ 注意:仅编了 openssl 驱动的环境下,testcrypto 用例运行时会滚出多行 Crypto driver 'nss' DSO could not be opened(按名探测未编译驱动的正常报错,stderr 输出),最终结果仍是 SUCCESS/All tests passed,不要误判为失败。

6.2 apr 核心测试套件

$ cd /root/src/apr-1.7.6/test && make
$ ./testall
...(40+ 用例,节选尾部)
testsockopt         :  SUCCESS
teststr             :  SUCCESS
teststrnatcmp       :  SUCCESS
testtable           :  SUCCESS
testtemp            :  SUCCESS
testthread          :  SUCCESS
testtime            :  SUCCESS
testud              :  SUCCESS
testuser            :  SUCCESS
testvsn             :  SUCCESS
testskiplist        :  SUCCESS
All tests passed.

两套套件在 Ubuntu 与 CentOS 8 双环境实测均全绿。

七、递进示例:从锁到 crypto 的编译运行

本节 4 个示例逐级递进,每个都给完整源码、编译命令与实测输出。编译命令统一用 5.2 节模板。

7.1 示例一:多线程 + 互斥锁(apr_thread_demo.c)

4 线程各累加计数器 10 万次,用 apr_thread_mutex 保护,验证并发正确性:

/* apr_thread_demo.c —— APR 线程 + 互斥锁:4 线程并发累加 */
#include <stdio.h>
#include <stdlib.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_thread_proc.h>
#include <apr_thread_mutex.h>

#define N_THREADS 4
#define N_LOOPS   100000

static apr_thread_mutex_t *g_lock = NULL;
static long long g_counter = 0;

static void *APR_THREAD_FUNC worker(apr_thread_t *th, void *data)
{
    for (int i = 0; i < N_LOOPS; i++) {
        apr_thread_mutex_lock(g_lock);   /* 进入临界区 */
        g_counter++;
        apr_thread_mutex_unlock(g_lock);
    }
    return NULL;
}

int main(void)
{
    apr_initialize();
    atexit(apr_terminate);

    apr_pool_t *pool;
    apr_pool_create(&pool, NULL);

    apr_thread_mutex_create(&g_lock, APR_THREAD_MUTEX_DEFAULT, pool);

    apr_thread_t *tid[N_THREADS];
    for (int i = 0; i < N_THREADS; i++) {
        apr_thread_create(&tid[i], NULL, worker, NULL, pool);
        printf("[+] thread %d started\n", i);
    }
    for (int i = 0; i < N_THREADS; i++) {
        apr_status_t rv;
        apr_thread_join(&rv, tid[i]);
    }
    printf("[=] expected %d, got %lld (%s)\n", N_THREADS * N_LOOPS, g_counter,
           g_counter == 400000 ? "OK" : "MISMATCH");

    apr_thread_mutex_destroy(g_lock);
    apr_pool_destroy(pool);
    return 0;
}

编译与实测运行(Ubuntu):

$ APR=/usr/local/apr/bin/apr-1-config
$ gcc apr_thread_demo.c -o apr_thread_demo $($APR --cflags --includes) $($APR --link-ld) $($APR --libs)
$ ./apr_thread_demo
[+] thread 0 started
[+] thread 1 started
[+] thread 2 started
[+] thread 3 started
[=] expected 400000, got 400000 (OK)

CentOS 8 同命令编译运行输出一致(实测 [=] expected 400000, got 400000 (OK)),线程 API 行为跨发行版一致。

7.2 示例二:时间处理(apr_time_demo.c)

微秒时间获取、本地/GMT 分解、strftime 格式化、RFC822 日期、apr_sleep 精度:

/* apr_time_demo.c —— APR 时间:获取/分解/格式化/测量 */
#include <stdio.h>
#include <stdlib.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_time.h>

int main(void)
{
    apr_initialize();
    atexit(apr_terminate);
    apr_pool_t *pool;
    apr_pool_create(&pool, NULL);

    apr_time_t now = apr_time_now();                    /* 微秒时间戳 */
    printf("[1] now = %" APR_INT64_T_FMT " usec\n", now);

    apr_time_exp_t lt, gt;
    apr_time_exp_lt(&lt, now);                          /* 本地时间 */
    apr_time_exp_gmt(&gt, now);                         /* GMT 时间 */
    printf("[2] local : %04d-%02d-%02d %02d:%02d:%02d (tm_gmtoff=%d)\n",
           lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday,
           lt.tm_hour, lt.tm_min, lt.tm_sec, (int)lt.tm_gmtoff);
    printf("    gmt   : %04d-%02d-%02d %02d:%02d:%02d\n",
           gt.tm_year + 1900, gt.tm_mon + 1, gt.tm_mday,
           gt.tm_hour, gt.tm_min, gt.tm_sec);

    apr_size_t rs;                                      /* 自定义格式化 */
    char buf[64];
    apr_strftime(buf, &rs, sizeof(buf), "%Y-%m-%d %H:%M:%S %Z", &lt);
    printf("[3] strftime: %s\n", buf);

    char rfc[APR_RFC822_DATE_LEN];                      /* HTTP 日期头格式 */
    apr_rfc822_date(rfc, now);
    printf("[4] rfc822: %s\n", rfc);

    apr_time_t t0 = apr_time_now();                     /* sleep 精度实测 */
    apr_sleep(250000);
    printf("[5] apr_sleep(250ms) actual cost = %" APR_INT64_T_FMT " usec\n",
           apr_time_now() - t0);
    apr_pool_destroy(pool);
    return 0;
}

实测输出(Ubuntu;> 250283 usec 表示 sleep 精度优于 1ms):

$ gcc apr_time_demo.c -o apr_time_demo $($APR --cflags --includes) $($APR --link-ld) $($APR --libs)
$ ./apr_time_demo
[1] now = 1792110837312075 usec
[2] local : 2026-10-16 00:33:57 (tm_gmtoff=0)
    gmt   : 2026-10-16 00:33:57
[3] strftime: 2026-10-16 00:33:57 UTC
[4] rfc822: Fri, 16 Oct 2026 00:33:57 GMT
[5] apr_sleep(250ms) actual cost = 250283 usec

(CentOS 8 实测同构输出,仅时间戳随运行时刻不同。)

7.3 示例三:子进程创建与回收(apr_proc_demo.c)

用 apr_proc_create 启动 /bin/echo 并 apr_proc_wait 回收退出码:

/* apr_proc_demo.c —— APR 子进程:spawn /bin/echo 并 wait */
#include <stdio.h>
#include <stdlib.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_thread_proc.h>

int main(void)
{
    apr_initialize();
    atexit(apr_terminate);
    apr_pool_t *pool;
    apr_pool_create(&pool, NULL);

    apr_procattr_t *attr;
    apr_procattr_create(&attr, pool);
    apr_procattr_io_set(attr, APR_NO_PIPE, APR_FULL_BLOCK, APR_FULL_BLOCK);
    apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH);   /* 按 PATH 查找 */

    apr_proc_t proc;
    const char *args[] = { "/bin/echo", "hello from APR child process", NULL };
    apr_status_t st = apr_proc_create(&proc, args[0], args, NULL, attr, pool);
    if (st != APR_SUCCESS) { fprintf(stderr, "proc create failed\n"); return 1; }
    printf("[1] child spawned: pid=%d\n", proc.pid);

    int exitcode = 0;
    apr_exit_why_e why;
    st = apr_proc_wait(&proc, &exitcode, &why, APR_WAIT);
    printf("[2] wait: status=%d exitcode=%d why=%d (%s)\n", st, exitcode, (int)why,
           why == APR_PROC_EXIT ? "normal exit" : "signaled");
    apr_pool_destroy(pool);
    return 0;
}

实测输出(Ubuntu 与 CentOS 8 一致,pid 每次不同):

$ gcc apr_proc_demo.c -o apr_proc_demo $($APR --cflags --includes) $($APR --link-ld) $($APR --libs)
$ ./apr_proc_demo
[1] child spawned: pid=38101
[2] wait: status=70005 exitcode=0 why=1 (normal exit)

💡 说明:status=70005 是 APR 把系统 waitpid 返回值封装后的原始状态码(低 8 位退出码 0、高位标记正常退出),判断结果请用 exitcode 与 why 字段,不要直接解读 status。

7.4 示例四:crypto 加解密(apr_crypto_demo.c,apr-util DSO 驱动)

加载 openssl 加密驱动,PBKDF2 派生 AES-256 密钥完成加解密回环(同时验证 apr-util DSO 模块加载机制):

/* apr_crypto_demo.c —— apr-util crypto:openssl 驱动 AES-256-CBC 加解密 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apu.h>
#include <apr_crypto.h>

int main(void)
{
    apr_initialize();
    atexit(apr_terminate);
    apr_pool_t *pool;
    apr_pool_create(&pool, NULL);

    const apu_err_t *err = NULL;
    const apr_crypto_driver_t *driver = NULL;
    apr_status_t st;
    st = apr_crypto_init(pool);            /* 全局初始化(必须先于 get_driver) */
    if (st != APR_SUCCESS) { fprintf(stderr, "crypto_init failed\n"); return 1; }
    st = apr_crypto_get_driver(&driver, "openssl", NULL, &err, pool);
    if (st != APR_SUCCESS) { fprintf(stderr, "load driver failed\n"); return 1; }
    printf("[1] openssl crypto driver loaded\n");

    apr_crypto_t *crypto = NULL;
    apr_crypto_make(&crypto, driver, NULL, pool);

    /* PBKDF2 由口令派生 AES-256 密钥 */
    apr_crypto_key_t *key = NULL;
    apr_size_t ivSize = 0;
    const char *pass = "q1w2e3r4";
    const unsigned char *salt = (const unsigned char *)"quzuo-salt-01";
    st = apr_crypto_passphrase(&key, &ivSize, pass, strlen(pass),
                               salt, strlen((const char *)salt),
                               APR_KEY_AES_256, APR_MODE_CBC, 1, 1000,
                               crypto, pool);
    if (st != APR_SUCCESS) { fprintf(stderr, "passphrase failed\n"); return 1; }
    printf("[2] key derived: ivSize=%d bytes\n", (int)ivSize);

    /* 加密 */
    const char *plain = "quzuo APR crypto demo";
    apr_size_t plen = strlen(plain);
    apr_crypto_block_t *ectx = NULL;
    const unsigned char *iv = NULL;
    apr_size_t blockSize = 0;
    apr_crypto_block_encrypt_init(&ectx, &iv, key, &blockSize, pool);
    unsigned char *cipher = NULL;
    apr_size_t clen = 0, flen = 0;
    apr_crypto_block_encrypt(&cipher, &clen, (const unsigned char *)plain, plen, ectx);
    apr_crypto_block_encrypt_finish(cipher + clen, &flen, ectx);
    clen += flen;
    printf("[3] plaintext(%d bytes) -> ciphertext(%d bytes), block=%d\n",
           (int)plen, (int)clen, (int)blockSize);

    /* 解密还原 */
    apr_crypto_block_t *dctx = NULL;
    apr_size_t dBlockSize = 0;
    apr_crypto_block_decrypt_init(&dctx, &dBlockSize, iv, key, pool);
    unsigned char *back = NULL;
    apr_size_t blen = 0, dlen = 0;
    apr_crypto_block_decrypt(&back, &blen, cipher, clen, dctx);
    apr_crypto_block_decrypt_finish(back + blen, &dlen, dctx);
    blen += dlen;
    printf("[4] decrypted(%d bytes): %.*s\n", (int)blen, (int)blen, back);
    apr_pool_destroy(pool);
    return 0;
}

编译(注意需要 apu-1-config 参与)与实测输出:

$ APR=/usr/local/apr/bin/apr-1-config; APU=/usr/local/apr/bin/apu-1-config
$ gcc apr_crypto_demo.c -o apr_crypto_demo $($APR --cflags --includes) $($APU --includes) \
      $($APR --link-ld) $($APU --link-ld) $($APU --libs) $($APR --libs)
$ ./apr_crypto_demo
[1] openssl crypto driver loaded
[2] key derived: ivSize=16 bytes
[3] plaintext(21 bytes) -> ciphertext(32 bytes), block=16
[4] decrypted(21 bytes): quzuo APR crypto demo

(Ubuntu 与 CentOS 8 双环境实测输出一致。)21 字节明文经 PKCS#7 填充成 32 字节密文、解密后完整还原,openssl DSO 驱动(apr_crypto_openssl-1.so)按名加载成功。

八、注意事项

  • 工具路径二选一要固定:仓库版 /usr/bin/apr-1-config(apr 1.6.3)与源码版 /usr/local/apr/bin/apr-1-config(1.7.6)版本不同,构建脚本写绝对路径避免混用;
  • --libs 不含库本体是设计如此:apr 自身以 --link-ld 输出,漏掉是链接错误的首要原因(实测 Q3 场景);
  • testall 需要在源码树内编译(make 后生成),且 apr-util 的 testcrypto 会打印未编译驱动的 DSO 报错(正常噪音);
  • crypto 示例必须先 apr_crypto_init(pool) 再 apr_crypto_get_driver(),顺序颠倒会段错误(实测定位过程:pthread_mutex_lock 未初始化崩溃)。

九、常见问题(FAQ)

Q1:apr-1-config 报 command not found? 工具未安装或不在 PATH。仓库方案确认包已装(dpkg -l | grep libapr1-dev / rpm -qa | grep apr-devel),源码方案用绝对路径 /usr/local/apr/bin/apr-1-config。

Q2:apu-1-config --old-expat 输出 @APR_XML_EXPAT_OLD@? apr-util 1.6.5 的模板替换瑕疵(实测原样输出占位符),不代表构建异常;判断 expat 用 --libs 是否含 -lexpat。

Q3:编译报 undefined reference to 'apr_initialize'? 链接参数漏 --link-ld。完整模板:gcc app.c $($APR --cflags --includes) $($APR --link-ld) $($APR --libs)(--libs 单独使用没有 -lapr-1,实测其输出仅 -luuid -lcrypt -lpthread)。

Q4:编译通过但运行报 libapr-1.so.0 cannot open shared object file? 动态库路径未注册:echo "/usr/local/apr/lib" > /etc/ld.so.conf.d/apr-1.conf && ldconfig(实测注册后 ldconfig -p 可见 libapr-1 条目)。

Q5:crypto 示例运行段错误(Segfault)? 最常见原因是漏掉 apr_crypto_init(pool)——apr-util 1.6.5 的 crypto 全局锁未初始化,apr_crypto_get_driver 内 pthread_mutex_lock 直接崩溃(实测 AddressSanitizer 定位到 crypto/apr_crypto.c:254)。补上初始化调用即恢复。

十、参考链接