uclibc 中的 dlopen 不是线程安全的

解决Android项目中线程安全的dlopen问题
本文详细阐述了在Android项目中遇到的一个关于线程安全的dlopen问题,通过分析发现uclibc的dlopen可能不是线程安全的。作者通过修改uclibc源代码,加入互斥锁,以及创建一个包装器函数来解决这一问题,确保了程序的稳定性和兼容性。

在最近一个 port Android 的项目中,被一个问题困扰: “Angry Frogs"一启动就 crash

后来发现,有两个线程都在 load so 文件, 这两个线程是 gralloc 和 egl

如果延时 egl 的load(如下 usleep(500000) , 就能成功

EGLBoolean egl_init_drivers() {
    EGLBoolean res;
    usleep(500000);
    pthread_mutex_lock(&sInitDriverMutex);
    res = egl_init_drivers_locked();
    pthread_mutex_unlock(&sInitDriverMutex);
    return res;
}

或者把一些 .so 用 export LD_PRELOAD=/path/to/lib 的方法 load 先 preload 问题也解决了。

由此我们推测 uclibc 的 dlopen 不是线程安全的,直接改 uclibc 中的ldso/libdl/libdl.c, 加 mutex 锁

再替换 /lib/libdl.so 后,程序不 crash, 看来证明我们推测是正确的。


但我们不为维护 uclibc 的代码,我们最好还是做一个wrapper, 让所有Android 中所有调用 dlopen 的地方,都调用我的wrapper


1) 首先制造一个假冒的 dlfcn.h

 dlfcn.h 

#ifndef MY_LIBC_DLFCN_H
#define MY_LIBC_DLFCN_H

#include_next <dlfcn.h>


__BEGIN_DECLS
extern void *my_dlopen(const char *libname, int flag);
__END_DECLS

#define dlopen my_dlopen

#endif /* MY_LIBC_DLFCN_H */

只要在编译系统的 -I (include) 让此假 h文件的目录在真正的 dlfcn.h 之前,它就会被先 include


2) 实现假的 my_dlopen

#include <dlfcn.h>
#include <pthread.h>

static pthread_mutex_t dl_lock = PTHREAD_RWLOCK_INITIALIZER;

void *my_dlopen(const char *libname, int flag)
{
#undef dlopen  // dlopen defined as alien_dlopen in dlfcn.h, now undef it
     void *ret;
     pthread_mutex_lock(&dl_lock);
     ret = dlopen(libname,flag);
     pthread_mutex_unlock(&dl_lock);
     return ret;
}

此文件可以并入到android 最基础的库中,或者build成so,并在 build 系统多加个 link,这样其他模块在 link 的时候能够找到它,才能build 成功。


看 android 的 bionic 中 dlopen 实现就知道 bionic 中 dlopen dlclose dlsym dladdr 是线程安全的

bionic/linker/dlfcn.c

void *dlopen(const char *filename, int flag)
{
    soinfo *ret;

    pthread_mutex_lock(&dl_lock);
    ret = find_library(filename);
    if (unlikely(ret == NULL)) {
        set_dlerror(DL_ERR_CANNOT_LOAD_LIBRARY);
    } else {
        call_constructors_recursive(ret);
        ret->refcount++;
    }
    pthread_mutex_unlock(&dl_lock);
    return ret;
}


<think>好的,我现在需要解决用户在uClibc中编译usleep函数的问题。首先,用户提到他们遇到了编译问题,可能是在使用usleep时出现错误,比如未定义的引用或者找不到函数。我需要回顾相关的知识,结合提供的引用内容,找到可能的原因和解决方案。 首先,usleep函数属于UNIX系统中的休眠函数,声明在<unistd.h>头文件中,用于将进程挂起指定的微秒数。在C库中,如glibc或uClibc,这个函数需要正确的实现。如果编译时出现错误,可能是因为库中没有包含这个函数,或者链接时没有正确链接到库。 查看用户提供的引用内容,特别是引用[2]中提到,当海思自带的uClibc不支持某些选项时,可能需要重新编译uClibc源码。这可能意味着用户当前使用的uClibc版本可能没有启用usleep相关的功能,或者配置时禁用了某些选项。例如,uClibc在配置时可能有不同的选项来控制是否包含某些函数,比如usleep可能依赖于_POSIX_TIMERS或其他配置选项。 引用[3]中的代码示例使用了usleep,用户可能在编译这段代码时遇到问题。可能的错误包括“undefined reference to `usleep'”,这通常表示链接器找不到该函数的实现。这可能是因为uClibc库在编译时没有包含usleep的支持,或者用户没有正确链接到正确的库。 接下来,我需要考虑uClibc的配置选项。在编译uClibc时,可能有一些配置选项需要开启才能支持usleep。例如,检查uClibc的配置文件(如.config或通过make menuconfig),确保相关的选项如UCLIBC_HAS_USLEEP或类似的选项被启用。如果默认配置没有启用,用户需要重新配置并编译uClibc。 另外,用户可能需要检查他们的应用程序是否正确地包含了头文件<unistd.h>,并且在编译时是否正确链接到uClibc库。如果使用的是交叉编译环境,可能需要指定正确的sysroot和库路径,如引用[4]中的编译脚本所示,其中设置了sysroot和工具链路径。 此外,用户可能需要确认他们的uClibc版本是否支持usleep。某些较旧的版本或特定配置可能移除了该函数,或者将其实现依赖于其他功能模块。例如,usleep可能在uClibc中被弃用,转而使用nanosleep,这时用户可能需要调整代码,或者启用兼容性选项。 可能的解决步骤包括: 1. 检查uClibc的配置,确保启用了usleep相关的选项。 2. 重新编译并安装uClibc,确保生成的新库包含usleep。 3. 在应用程序编译时,确保正确链接到新编译的uClibc库。 4. 如果usleep不可用,考虑替代方案如nanosleep,并修改代码。 另外,引用[2]中提到通过编译uClibc源码来支持LD_PRELOAD,这可能意味着用户需要自行配置和构建uClibc以满足需求。类似地,如果usleep未被包含,用户需要找到对应的配置选项并启用它。 在具体操作时,用户可能需要进入uClibc的源码目录,运行make menuconfig或直接编辑.config文件,搜索与定时器或休眠函数相关的选项,如UCLIBC_HAS_USLEEP,将其设置为y,然后重新编译安装。之后,在编译应用程序时,确保使用新编译的库,可能需要通过-L和-l参数指定库路径。 总结,问题可能出在uClibc的配置未启用usleep,解决方案是重新配置、编译并链接正确的库。同时,检查代码中的头文件包含和编译参数是否正确。</think>### 解决方案:uClibc中编译usleep函数的方法 #### 问题分析 当在uClibc环境中编译使用`usleep`函数的代码时,常见的错误是`undefined reference to 'usleep'`,这通常由以下原因导致: 1. **uClibc配置未启用`usleep`支持**:uClibc默认配置可能关闭了某些休眠函数[^2]。 2. **头文件未正确包含**:未包含`<unistd.h>`导致编译器无法识别函数声明。 3. **链接库路径错误**:未正确链接到包含`usleep`实现的uClibc库。 --- #### 解决步骤 ##### 1. 验证uClibc配置 进入uClibc源码目录,检查或修改配置选项: ```bash cd uClibc-source make menuconfig ``` 在配置界面中,启用以下选项: - **UCLIBC_HAS_USLEEP**: 确保该选项被标记为`Y`(启用)。 - **UCLIBC_HAS_POSIX_TIMERS**: 部分版本的`usleep`依赖此选项[^2]。 ##### 2. 重新编译并安装uClibc 保存配置后重新编译库: ```bash make clean make make install ``` 生成的库文件默认位于`./lib`或指定安装路径。 ##### 3. 应用程序编译配置 在编译使用`usleep`的代码时,需指定新编译的uClibc库路径。例如: ```bash arm-linux-gcc -o program your_code.c -I/path/to/new-uClibc/include -L/path/to/new-uClibc/lib -lc ``` - **-I**: 指定包含`<unistd.h>`的头文件路径。 - **-L**: 指定链接库路径。 - **-lc**: 显式链接C标准库。 ##### 4. 替代方案(可选) 若uClibc不支持`usleep`,可改用`nanosleep`: ```c #include <time.h> void custom_usleep(int microseconds) { struct timespec ts; ts.tv_sec = microseconds / 1000000; ts.tv_nsec = (microseconds % 1000000) * 1000; nanosleep(&ts, NULL); } ``` --- #### 验证示例 引用[3]中代码的修改版: ```c #include <stdio.h> #include <sys/time.h> #include <unistd.h> // 确保包含头文件 int main() { struct timeval tv; while(1) { if (gettimeofday(&tv, NULL) < 0) { printf("get time failed\n"); } else { printf("child time: %u.%u\n", tv.tv_sec, tv.tv_usec); } usleep(1000 * 10); // 使用usleep前需确认库支持 } return 0; } ``` 编译命令: ```bash arm-linux-gcc -o test_program test.c -I/path/to/uClibc/include -L/path/to/uClibc/lib -lc ``` --- #### 关键引用说明 - **重新编译uClibc**:若默认库不支持特定功能(如`LD_PRELOAD`或`usleep`),需通过源码编译生成定制版本。 - **交叉编译环境**:引用[4]展示了如何通过NDK配置交叉编译参数,确保工具链和sysroot指向正确的uClibc版本。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值