ctime 可重入问题引起的死锁

遇到一个死锁,堆栈类似如下

#0 0x00002b6b7d16cc38 in __lll_mutex_lock_wait () from /lib64/libc.so.6
#1 0x00002b6b7d126a9d in _L_lock_1769 () from /lib64/libc.so.6
#2 0x00002b6b7d126876 in __tz_convert () from /lib64/libc.so.6

搜了一下,发现之前有人已经讨论过这个问题了,在这里,http://lists.gnu.org/archive/h… ,他给出了一个样例程序如下

#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

volatile char *r;

void handler(int sig)
{
time_t t;

time(&t);
r = ctime(&t);
}

int main()
{
struct itimerval it;
struct sigaction sa;
time_t t;
int counter = 0;

memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sigaction(SIGALRM, &sa, NULL);

it.it_value.tv_sec = 0;
it.it_value.tv_usec = 1000;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 1000;
setitimer(ITIMER_REAL, &it, NULL);

while(1) {
counter++;
time(&t);
r = ctime(&t);
printf("Loop %d\n",counter);
}

return 0;
}

这个程序可以稳定的重现死锁的问题,gdb attach 上去之后看到的堆栈也是类似的,一开始以为是 ctime 的可重入问题,觉得改成 ctime_r 就可以很容易的解决,动手之后发现修改 ctime_r 并不奏效

另外,下面这个代码,却又不会引起死锁

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>

int i = 0;
volatile char* r;

void* thread(void*) {
    while (1) {
        time_t t = time(NULL);
        r = ctime(&t);
        printf("%d\n", i++);
    }
    return NULL;
}

int main() {
    pthread_t tid;
    for (int j = 0; j < 100; j++) {
        pthread_create(&tid, NULL, thread, NULL);
    }
    sleep(1);
    return 0;
}

在其他地方,也有见到类似的讨论,例如这里,https://bugs.debian.org/cgi-bi… ,说道

According to signal(7), ctime is not safe to be called from a signal
handler (nor, more generally, are malloc or free). Probably debug messages
from signal handler context should not attempt to display the time, or
should use some built-in time implementation that doesn’t malloc or care
about timezones, or something.

另外,这里,http://stackoverflow.com/quest… ,https://bugzilla.redhat.com/sh… ,https://bugzilla.redhat.com/sh… ,也都有讨论

现在的问题是

  1. 为什么在那个信号处理的例子中会死锁,而多线程的不会
  2. 对于死锁的那个例子,怎么避免

经过 kamus 大哥指点,这个其实不是 ctime 本身的问题,而是 ctime 内部使用了 malloc,通过 strace 分析可以发现 ctime 的系统调用如下

brk(0) = 0x501000
brk(0x522000) = 0x522000
open("/etc/localtime", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=165, ...}) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=165, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b5a8a93c000
read(3, "TZif\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3\0\0\0\3\0"..., 4096) = 165
close(3) = 0
munmap(0x2b5a8a93c000, 4096) = 0

至于信号处理和多线程,这个就跟递归是类似,不能递归的对同一把锁上锁,但是可以多线程对同一把锁上锁,只要其他线程稍后把这个锁释放了就可以了,最后,解决方法就是,不要在信号处理的函数中使用 ctime 或者 malloc 之类的函数。

Leave a Reply

Your email address will not be published. Required fields are marked *