技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 系统运维 --> Linux程序链接时-lpthread对程序正确性的影响

Linux程序链接时-lpthread对程序正确性的影响

浏览:2456次  出处信息

   理论上来说,多线程程序在链接时应该加上-lpthread或者-pthread。实际上很多时候忘记加这个也能链接过去,

   最近我线上的一个重要服务经常卡死,CPU使用率很高。用pstack看,经常是停留在这样的地方:

# 0x0000003a21e0e054 in __lll_lock_wait () from /lib64/libpthread.so.0
#1 0x0000003a21e0bca1 in pthread_cond_signal@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#2 0x00007f04f8e0696d in __db_pthread_mutex_unlock () from /usr/lib64/libdb-4.7.so
#3 0x00007f04f8e0655d in __db_tas_mutex_unlock () from /usr/lib64/libdb-4.7.so
#4 0x00007f04f8ea6b8e in __db_cursor_int () from /usr/lib64/libdb-4.7.so
#5 0x00007f04f8ebd9af in __db_cursor () from /usr/lib64/libdb-4.7.so
#6 0x00007f04f8ebe2c0 in __db_get () from /usr/lib64/libdb-4.7.so
#7 0x00007f04f8ebe63b in __db_get_pp () from /usr/lib64/libdb-4.7.so

大部分CPU都被__db_tas_mutex_unlock和__db_tas_mutex_lock这两个函数占去了。按理说unlock一个mutex不该占用太多cpu才对。(后来我发现这是bdb的mutex的实现太畸形太挫了)

我在网上发现有个工程师遇到了和我类似的问题
http://www.jimmo.org/threads-blocked-in-pthread_cond_signal-on-linux/ 他说如果忘记链接到pthread库,可能导致条件变量所依赖的mutex没有被正确初始化,而导致程序死锁等。理论上来说是这样的,但是实际上我没有办法重现作者的实验。


我发现libdb-4.7.so中pthread的符号和我预期的不一样
$ readelf -a /usr/lib64/libdb-4.7.so |grep pthread_cond_signal
000000370f88  000f00000007 R_X86_64_JUMP_SLO 0000000000000000 pthread_cond_signal + 0
    15: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_cond_signal@GLIBC_2.3.2 (3)

我自己如果编译一个小程序,例如
#include <pthread.h>

int func(){
 pthread_cond_signal(NULL);
 return 0;
}

$ gcc -o libt.so test.c -shared -fPIC
$ readelf -a libt.so  |grep pthread
000000201018  000300000007 R_X86_64_JUMP_SLO 0000000000000000 pthread_cond_signal + 0
    3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_cond_signal@GLIBC_2.3.2 (2)
   45: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_cond_signal@@GLIB
它的符号表中应该有两条记录。不知道为什么bdb中只有一条。

后来查了下文档终于搞明白,带@的是versioned symbol。weak symbol是给静态库用的,动态库没法用weak symbol。

glibc中的pthread的mutex等的实现是空的,这是为了提高单线程程序的执行效率。当某个程序真的需要使用多线程的时候,得让libpthread.so把正确的symbols填充进去。静态库可以通过weak symbol做到这一点,而动态库可以直接覆盖,也可以用versioned symbol。

$ nm /lib64/libc.so.6 | grep pthread_mutex
00000000000f8110 T pthread_mutex_destroy
00000000000f8140 T pthread_mutex_init
00000000000f8170 T pthread_mutex_lock
00000000000f81a0 T pthread_mutex_unlock
 
注意,是T,不是W。 (cond的输出更有所不同。稍后叙述)

当编译一个不带-pthread的程序的时候,
$ ldd t
    linux-vdso.so.1 =>  (0x00007fff5f4e2000)
    libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007fbef59f9000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fbef5775000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fbef555e000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fbef51ca000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fbef5d2f000)
 
当编译一个带-pthread的程序之后
$ ldd t
    linux-vdso.so.1 =>  (0x00007fff805fe000)
    libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007f72c5a26000)
    libm.so.6 => /lib64/libm.so.6 (0x00007f72c57a2000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f72c558b000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f72c536e000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f72c4fda000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f72c5d5c000)
 
libpthread.so.0一定是出现在libc.so.6之上。它也提供了同样的符号
$ nm /lib64/libpthread.so.0 | grep pthread_mutex_init
0000000000008d70 T __pthread_mutex_init
0000000000008d70 t __pthread_mutex_init_internal
0000000000008d70 T pthread_mutex_init

默认情况下,链接器是按顺序优先选择第一个找到的。所以它会使用libpthread.so.0中的符号替换libc.so.6中的。


条件变量要更复杂一些。
 
$ nm /lib64/libc.so.6 | grep pthread_cond_init
00000000000f7ff0 t __pthread_cond_init
0000000000127c30 t __pthread_cond_init_2_0
00000000000f7ff0 T pthread_cond_init@@GLIBC_2.3.2
0000000000127c30 T pthread_cond_init@GLIBC_2.2.5
 
libc中提供了两个版本的条件变量的实现,@@后面是版本号。一个是GLIBC_2.2.5,一个是GLIBC_2.3.2。其中GLIBC_2.3.2是基于NPTL的。由于它定义了多个版本的实现,所以就应该有一个默认实现。带@@的就是默认实现。
 
我没看出来libpthread和libc中的cond vars的实现有什么区别。

另外我又重复了一下网上那篇帖子中的实验


$ cat test.c
#include <pthread.h>
 
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
int func(){
  pthread_mutex_lock(&mutex);
  pthread_mutex_unlock(&mutex);
  return 0;
}
 
$ cat main.c
 
extern int func();
 
int main(){
  func();
  return 0;
}
 
$ gcc -shared -fPIC -o libt1.so test.c -g
$ gcc -o m main.c -g -lt1 -L. -Wl,-rpath,.
 
两次编译我都故意没有加-pthread,然后发现pthread_mutex_lock确实使用的是空实现。
但是动态库的符号是这样写的:
$ nm libt1.so  |grep pthread
U pthread_mutex_lock@@GLIBC_2.2.5
U pthread_mutex_unlock@@GLIBC_2.2.5
 
$ readelf -a libt1.so | grep pthread
000000200888  000500000007 R_X86_64_JUMP_SLO 0000000000000000 pthread_mutex_lock + 0
000000200890  000600000007 R_X86_64_JUMP_SLO 0000000000000000 pthread_mutex_unlock + 0
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutex_lock@GLIBC_2.2.5 (2)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutex_unlock@GLIBC_2.2.5 (2)
    58: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutex_lock@@GLIBC
    60: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND pthread_mutex_unlock@@GLI
 
 
当我修改主程序的链接参数后:
$ gcc -o m main.c -g -lt1 -L. -Wl,-rpath,. -pthread
$ ldd ./m
    linux-vdso.so.1 =>  (0x00007fff710bf000)
    libt1.so => ./libt1.so (0x00007fc37216f000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc371f47000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fc371bb3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fc372371000)
由于在它启动的时候,就已经链接到了pthread,所以也就没有问题。它会使用pthread的实现,无需修改so的链接参数。
 
然后我又试了一下dlopen。
我把main函数改成这样
#include <dlfcn.h>
#include <stdio.h>
 
 
int main(){
  int (*func)();
  void* handle =dlopen(“./libt1.so”, RTLD_NOW);
  if (!handle) {
       fprintf(stderr, “%s\n”, dlerror());
       return -1;
   }
  func = (int (*)()) dlsym(handle, “func”);
  func();
  return 0;
}
 
$gcc -o m main.c -g -pthread -ldl
经gdb调试,依然使用的是/lib64/libpthread.so.0中的符号。
一切都符合预期。我猜是因为@@的效果。

建议继续学习:

  1. 浅析C++多线程内存模型    (阅读:7084)
  2. C++ 多线程编程总结    (阅读:6776)
  3. 多线程队列的算法优化    (阅读:6498)
  4. 程序中的“多线程”    (阅读:5619)
  5. php多线程扩展    (阅读:4205)
  6. 为什么在多线程程序中要慎用volatile关键字?    (阅读:3967)
  7. Ameba , 一个简单的 lua 多线程实现    (阅读:3568)
  8. 解决IOS点击链接触发的颜色块    (阅读:3440)
  9. 多线程程序中操作的原子性    (阅读:2984)
  10. 如何获取用户访问过哪些网站    (阅读:2834)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1