linux gcc lpthread,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

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

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

#include

int main(){

int (*func)();

void* handle =dlopen(“./libt1.so”, RTLD_NOW);

if (!handle) {

fprintf(stderr, “%sn”, dlerror());

return -1;

}

func = (int (*)()) dlsym(handle, “func”);

func();

return 0;

}

$gcc -o m main.c -g -pthread -ldl

经gdb调试,依然使用的是/lib64/libpthread.so.0中的符号。

一切都符合预期。我猜是因为@@的效果。

你可能感兴趣的:(linux,gcc,lpthread)