最近完成工具链和uClibc库升级的任务,升级之后出现了一个bug,就是大软件起来之后,原先ps能看到6个线程的进程,现在只能看到三个,导致出现了一些功能上的问题。
有了问题,当然要处理啦。毕竟是自己埋下的坑,含着泪也要把它填起来。刚开始在代码里面加上打印,发现都没有显示,觉得特别奇怪。心想是不是阻塞在哪个函数了。经过从main函数开始打印,慢慢的定位到daemon这个函数。心想这个函数时做什么的?为什么会阻塞呢?
上网百度之后,发现daemon函数的作用就是将进程放到后台执行。所以我的打印就看不到了,并不是阻塞在这里。
但是奇怪的现象是当我把这个函数注释掉之后,编译出来的程序又是完好如初了。(ps:可以看到6个线程)
这就让我把问题定位在了daemon函数上。后续再进行排查。发现程序在进行第一个pthread_create函数之后,就不再继续放下执行了。(打开daemon函数)
最终我将问题定位在daemon函数和pthread_create两个之间
问题解决的过程我就不再详细说明了。因为过程也花了好几天,和老大慢慢确认问题,一起总结。也了解到了很多新的知识点。
首先你要了解以下几点内容:
main函数不是程序最初的入口
首先第一点,我们之前一直听过一句话,main函数是程序的入口,其实并不是的。你仔细想想就知道肯定不是这样的。因为你并没有初始化堆栈,stdin,stdout,stderr等参数,你是怎么使用的呢?这肯定是在main函数之前就已经设置好的了。
GNU C attribute 设置属性
GNU C attribute 是GCC的一大特色,他可以为函数,变量,类型赋予属性。
其中
attribute((constructor)) // 构造函数在main函数被调用之前调用
attribute((destructor)) // 析构函数在main函数被调用之后调
1 .file 1 "uttsn.c"
2 .section .mdebug.abi32
3 .previous
4 .abicalls
5 .section .rodata.str1.4,"aMS",@progbits,1
6 .align 2
7 $LC0:
8 .ascii "son thread %d\n\000"
9 .align 2
463编译器:
1 .file 1 "uttsn.c"
2 .section .mdebug.abi32
3 .previous
4 .gnu_attribute 4, 3
5 .abicalls
6 .section .rodata.str1.4,"aMS",@progbits,1
7 .align 2
8 $LC0:
9 .ascii "son thread %d\012\000"
所以463会在main函数之前初始化gnu_attribute相关的东西。
进入正题:
pthread_initialize函数是被设置为构造函数的,在main函数之前执行。该函数的作用是记录主线程的pid
static void pthread_initialize(void) __attribute__((constructor));
daemon函数的源码:
35 int daemon( int nochdir, int noclose )
36 {
37 int fd;
38
39 switch (fork()) {
40 case -1:
41 return(-1);
42 case 0:
43 break;
44 default:
45 _exit(0);
46 }
47
48 if (setsid() == -1)
49 return(-1);
50
51 /* Make certain we are not a session leader, or else we
52 * might reacquire a controlling terminal */
53 if (fork())
54 _exit(0);
55
56 if (!nochdir)
57 chdir("/");
58
59 if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) {
60 dup2(fd, STDIN_FILENO);
61 dup2(fd, STDOUT_FILENO);
62 dup2(fd, STDERR_FILENO);
63 if (fd > 2)
64 close(fd);
65 }
66 return(0);
67 }
68
综上所述:
用了新的工具链之后出现问题,主要的原因是由于,463支持在main函数之前初始构造函数。记录主线程的pid。而342是不具有这个操作的。
而daemon函数时将主线程exit(0)。这样就导致,463编译的软件,调用之后daemon之后,主线程就直接退出了,当我们调用pthread_create函数之后,会发唤醒消息给pthread_initialize记录的pid线程时,让他继续执行。(其实在这里就已经错误了,因为pthread_initialize记录的是父进程的pid.而调用pthread_create的进程是子进程)。这样就导致子进程没有收到唤醒。一直处于阻塞。
一般如果没有在main函数之前进行pthread_initialize。在pthread_create调用时,也会再次调用。所以342之前一直没有问题。
因此,处理该问题的方式有两种:
1:修改daemon函数。不调用fork函数.(其实我很奇怪这个fork的目的何在)
2: 将pthread_initialize函数attribute((constructor))属性去除。