欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

踩坑之路-线程私有数据

程序员文章站 2024-01-16 20:04:40
...

背景介绍

  • 进程A
  • 进程B

动态链接库

c.so -----------我们模块的so
d.so
e.so
进程A会加载c.so、d.so
进程B会加载c.so、d.so、e.so

启动d.so 和e.so的构造函数中均会创建线程私有数据
进程A每次重启时,会产生core文件,产生core的位置在c.so
进程B不会出core文件。

定位过程

发现进程A出core的地方是c.so中的私有数据,发现私有数据是乱的,怀疑是初始化时有问题。

查看代码发现函数初始化私有数据时调用pthread_getspecific(0)查看是否已经有配置,如果没有则创建私有数据

进程A调用pthread_getspecific(0)不为空,进程B调用pthread_getspecific(0)为空。

这就比较奇怪了,相同的代码,为什么结果不一样呢?想了好久,只有可能进程启动过程中已有的流程导致的,就试着把线程私有数据的各个函数(pthread_key_create、pthread_key_delete、pthread_setspecific、pthread_getspecific)均断住后启动进程。
(由于不知道是私有数据创建有问题,还是创建后又删除了,就用了这个笨办法,后来想如果先看看源码就好了,能帮助清理下思路)

启动过程中发现进程A和B启动过程果然不一样
首先加载的so不一样,进程B先加载e.so,然后是d.so;进程A先加载d.so。
更特别的是进程B第一次调用pthread_key_create创建线程私有数据的key后,没有调用pthread_setspecific设置线程私有数据。
而进程A第一次调用pthread_key_create创建线程私有数据的key后,接着调用pthread_setspecific设置线程私有数据。
猜测是进程A获取私有数据是正好是d.so中设置的私有数据,不为NULL,进程B中获取的是e.so中的私有数据,而e.so的私有数据没有设置,正好获取的是NULL。

由于都是release的.so,没办法看出来各个值是多少,只能通过查看cpu寄存器中key的入参和出参变化来验证猜想。发现第一个设置私有数据的key,key的入参和出参均是0;第二个设置私有数据时,key的入参是0,出参是1。

直接看源码,果然如此。

e.so创建了私有数据Key,没设置私有数据,创建的私有数据Key正好为0,我们模块获取私有数据的Key正好是0,获取私有数据为NULL,无巧不成书,就这么巧合的导致了进程A有问题,进程B没有问题。

源码

创建私有数据Key

key的分配是直接从0开始,一直向后找第一个未使用的值。

int pthread_key_create(pthread_key_t * key, destr_function destr)
{
  int i;

  __pthread_mutex_lock(&pthread_keys_mutex);
  
  /* 遍历寻找第一个未使用的索引 */
  for (i = 0; i < PTHREAD_KEYS_MAX; i++) {
    if (! pthread_keys[i].in_use) {
     /* Mark key in use */
      pthread_keys[i].in_use = 1;
      pthread_keys[i].destr = destr;
      __pthread_mutex_unlock(&pthread_keys_mutex);
      *key = i;
      return 0;
    }
  }
  __pthread_mutex_unlock(&pthread_keys_mutex);
  return EAGAIN;
}

设置私有数据

先根据key查找一维空间有没有分配,如果没有分配,则创建一维空间,然后将数据挂在二维空间

int pthread_setspecific(pthread_key_t key, const void * pointer)
{
    pthread_descr self = thread_self();
    unsigned int idx1st, idx2nd;

    if (key >= PTHREAD_KEYS_MAX || !pthread_keys[key].in_use)
		return EINVAL;
    idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
    idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
    
    /* 查看一维是否为空 */
    if (THREAD_GETMEM_NC(self, p_specific[idx1st]) == NULL) {
	    void *newp = calloc(PTHREAD_KEY_2NDLEVEL_SIZE, sizeof (void *));
		if (newp == NULL)
	    	return ENOMEM;
		THREAD_SETMEM_NC(self, p_specific[idx1st], newp);
    }
    
    /* 在二维空间挂在私有数据 */
 	THREAD_GETMEM_NC(self, p_specific[idx1st])[idx2nd] = (void *) pointer;
    return 0;
}

获取私有数据

void * pthread_getspecific(pthread_key_t key)
{
    pthread_descr self = thread_self();
    unsigned int idx1st, idx2nd;

    if (key >= PTHREAD_KEYS_MAX)
		return NULL;
    idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
    idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
    
    /* 判断一维空间数据是否为空,以及当前key的值是否被使用 */
	if (THREAD_GETMEM_NC(self, p_specific[idx1st]) == NULL || !pthread_keys[key].in_use)
		return NULL;
		
	/* 获取当前key所对应的私有数据 */
    return THREAD_GETMEM_NC(self, p_specific[idx1st])[idx2nd];
}
相关标签: LINUX 踩坑系列