首页 / 操作系统 / Linux / Linux程序链接时-lpthread对程序正确性的影响
理论上来说,多线程程序在链接时应该加上-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
", dlerror()); return -1; } func = (int (*)()) dlsym(handle, "func"); func(); return 0; } $gcc -o m main.c -g -pthread -ldl 经gdb调试,依然使用的是/lib64/libpthread.so.0中的符号。 一切都符合预期。我猜是因为@@的效果。 本文永久更新链接地址 :http://www.linuxidc.com/Linux/2015-01/111579.htm
收藏该网址