首页 > 代码库 > Linux Debugging(八): core真的那么难以追踪吗?

Linux Debugging(八): core真的那么难以追踪吗?

本周遇到了好几个core都非常有典型性。

在这里和大家分享下。

相信有过Linux编程经验的人,肯定都遇到过。

感觉周围人非常多对core有天然的恐惧感,尤其对刚入行不久的同学来说。当然了。也有工作好几年看到core也束手无策的。今天就分析一下,core,事实上大部分都是非常easy解决的。假设一个core非常难以复现,那么说明还是非常复杂的,算是Corner case。可能须要非常长时间。脑子里要有非常好的执行时状态才干够(阅读源代码,学习的是逻辑。将源代码相应到执行时的状态,分析一些状态机的转换,再去分析可能会发生的情况)。

相信前几篇文章会对这样的Corner case的分析与解决打下比較好的基础。

相反,那种每次必现,或者复现比率非常高的case,是非常easy解决的。


多线程必定出core?

假设是你新增加的代码引入的core,实际上非常easy解决的,简单的对照一下改动的diff。然后看一下是否有比較低级的错误。

假设发现不了,看是否是多线程的问题?单线程假设没有出core,改成多线程就出core。那么就说明多线程竞争某些变量了:同一时候改动某些变量导致出问题。这个时候你可能第一反应会加锁。

我本人非常反感加锁。即使你加锁的粒度非常小,作用域也够小。可是仅仅要是加锁,就代表有堵塞。就代表维护起来会非常麻烦。这些共享变量真的那么值得加锁吗?可否换成局部变量?假设他是一块动态内存。为了调用某个接口时不要频繁申请释放内存(比方这个接口每秒几千次的调用),那么初始化时候申请一块内存是绝对合理的:请把它设置为线程变量吧。

每一个线程初始化时候申请这块内存。

当然了你假设实现的是一个框架或者架构调用的接口,这个接口要做到线程安全的。那么看起来你并不能控制这个线程什么时候启动;线程数目会是多少个,那么就没有办法了吗?

实际上,方法有非常多,比方,你能够在一个map中维护一个“线程”变量的相应关系

__gnu_cxx::hash_map< pid_t, void *> thread_data_map;
void * thread_buffer;
std::map<pid_t, void *>::iterator it;
lock
it = thread_data_map.find(pid);
if (it == thread_data_map.end()) {
    //init "thread data"
    thread_data_map[pid] = create_buffer();
} else {
    thread_buffer = it->second;
}
unlock


这里不得不使用了一个锁。实际上因为线程数是有限的,因此这个效率还好。我本周实现了一个qps能够达到2000+的在线应用,基本上锁的代价在整个的call stack中能够忽略不计。

当然了。比較好的框架可能会提供OnThreadInit这样的接口。那么在这边申请线程变量吧:

int pthread_setspecific (pthread_key_t key, const void *value);

在实现逻辑的函数获取该变量即可:

void *pthread_getspecific (pthread_key_t key);

什么时候要使用线程变量?看多线程下是否对该变量有写操作,假设有就要申请线程变量(或者加锁),否则必定出core。


不要给自己埋下一个坑:


今天一个同学的core看起来是做了一个“优化”,节省了申请变量的时间。

void init() {
    my_struct * some_var;
    ...
    some_var->res = new some_res;
    some_var->res->set_value1(some_common_value1);
    some_var->res->set_value2(some_common_value2);
}

void * thread_func(my_struct * some_var) {
     
    some_var->res->set_value3(value_3);
   ...
}

set_value3就是一个出core的原因。

这个也是一个典型的多线程必定出core的case。实际上,res不是必需提前申请吧。把它改成一个局部变量。性能差点儿没有的损耗。当然了,假设这个资源非常大,那么就当成线程变量吧。


那么怎样分析core?

实际上,上述的场景出现的core假设仅从core本身,可能一时不好排查问题出在哪里。并且core的位置可能还不一样,不可避免的出现替罪羊;比方调用第三方的模块。实际上是自己的全局变量导致到了第三方的调用栈时出core了。

因此一定要排查自己的处理是否正确。

确定调用的接口是否线程安全。假设是你的同事。你得确定他说的是对的。就像今天又另外的一个core,调用者坚称他写的是线程安全的,仿佛不是线程安全的就像是代码写的不好似得;最后排查出他写的代码不是线程安全时。他问你什么是线程安全的。

那么回答一下,怎样分析core?

首先了解清楚core出现的应用场景。比方长句子处理会有core;多线程处理会有core。特殊字符会有core。这个QA会给你一个比較清楚的说明。然后通过core的call stack去大概确定大概的源代码位置。

源代码面前,了无秘密。

你读源代码,它展现的是逻辑;可是你脑中。要有个runtime的执行时调用栈,多线程的调度。天马行空。

当然了。还是解决不了,開始从core得到更具体的信息吧!

看看调用栈的參数是什么,切换一个线程看看其它的几个线程的frame是什么。

还解决不了?

那么它是Corner case,仅仅要把应用在core后马上启动即可了。记得EMC有个bug的选项是unable to root cause, 形容的非常贴切。同一时候不要忘记自我安慰:码的码多了,肯定有bug,谁能保证服务100%呢?


Linux Debugging(八): core真的那么难以追踪吗?