免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3477 | 回复: 3

【分享】优雅处理段错误 [复制链接]

论坛徽章:
0
发表于 2012-04-20 20:21 |显示全部楼层
本帖最后由 wwdwwd 于 2012-04-20 20:23 编辑

摘要:某些进程在结束前必须要处理一些额外的过程才能结束,尤其是数据存储的模块,进程停止前为保证数据的完整性可能要做一些事情,如果发生段错误,这时就需要先截获segv信号,处理完后再让程序出core
一般进程收到段错误信号默认是dump core文件然后退出,但有些进程在退出时需要处理额外的过程才能结束,这时就不能让信号执行默认的动作了,我们就需要截获段错误信号,然后在信号处理函数中
处理额外过程,我们称之为other_function,但是我们处理完后其实还是需要让程序core出来,以便知道是哪儿出问题了。
基础知识预备:
1.对线程而言,有三种信号类型:异步信号,同步信号,定向信号。其中异步信号是指传递给某些解除了对该信号的阻塞的线程的信号,同步信号是指传递给引发该信号的线程的信号,定向信号是指由pthread_kill函数发送给指定线程的信号。像SIGSEGV(段错误信号),SIGPFE(浮点错误)这样的错误信号就是同步于引发他们的线程的,因为引发这些信号的线程将等待信号处理程序处理完成后才能继续进行,这样的信号只能由本线程处理,而其他信号因为不与特定的线程相关,所以他们是异步的,例如其他进程给本进程发送的信号。如果有几个线程都解除了对同一个异步信号的阻塞,当有信号到达时,线程运行系统就从中选取一个来处理。
2.对于SIGSEGV信号,如果是由进程段错误导致的,则只能设定信号处理函数,不能阻塞或忽略,如果有是由别的进程发送的,则可以阻塞或忽略。
我们考虑了大概三个方案:
方案1:直接设置信号处理函数,在信号处理函数中处理other_function,处理完后再signal(SIGSEGV,SIG_DFL),这看起来比较完美,但实际上有锁的问题。如果出现段错误的线程中使用了锁,在没有解锁之前发生段错误,other_function中也使用了同一个锁,则容易出现死锁,造成进程hang住。other_function中无锁时此方案比较简单易行。
方案2:其他线程阻塞信号,由专有线程处理段错误信号。这个不可行,因为我们之前说了,段错误信号如果由进程产生则不能被阻塞或忽略
方案3:直接设置信号处理函数,但使用专有线程处理other_function。接到段错误信号后调用信号处理函数,信号处理函数中设置开始处理标记,然后定期检查专有线程是否处理完,专有线程定期检查开始处理标记,发现设定了就开始处理other_function,处理完之后设置处理完标记,然后推出。信号处理函数中定期检查处理完标记,发现一旦被处理完了则再signal(SIGSEGV,SIG_DFL),然后退出,等待出core。
这个方案也会出现方案1中的死锁问题,但是此时的死锁是两个线程之间的死锁,而不是同一个线程的死锁,所以是可以处理的,信号处理函数中设置一个超时时间即可,即信号处理函数发现专有线程太久没有处理完other_function就知道出现死锁了,这时没辙了,直接signal(SIGSEGV,SIG_DFL),让程序出core完事,由于这种可能是比较少的,所以是可以接受的。
另外一个问题是:如果other_function中本身也出core就比较囧了,此时可以在信号处理函数中做判断,如果开始处理标记已设定,说明已经已经在处理core了,再进来就直接signal(SIGSEGV,SIG_DFL),让程序出core了事
说的比较乱,直接看代码吧
  1. #include <stdint.h>
  2. #include <stdio.h>
  3. #include <pthread.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. #include <string.h>
  8. volatile bool begin_segv_handle=false;
  9. volatile bool already_handle_other_function=false;
  10. void segv_handler(int signo);

  11. void * core_thread(void *args) {
  12.         int i=0;
  13.         while(1) {
  14.                 printf("in core thread\n");
  15.                 sleep(1);
  16.                 if(i++>2) {
  17.                         strcpy(NULL,"abc");
  18.                 }
  19.         }
  20.         return NULL;
  21. }

  22. void * other_function_thread(void *args) {
  23.         int i=0;
  24.         while(1) {
  25.                 printf("in other_function_thread\n");
  26.                 if(begin_segv_handle) {
  27.                         printf("set already_handle_other_function\n");
  28.                         already_handle_other_function = true;
  29.                         break;
  30.                 }
  31.                 sleep(1);
  32.         }
  33.         return NULL;
  34. }
  35. void segv_handler(int signo) {
  36.         printf("in segv_handler\n");
  37.         if(begin_segv_handle) {
  38.                 signal(SIGSEGV,SIG_DFL);
  39.                 return ;
  40.         }
  41.         begin_segv_handle = true;
  42.         int i=0;
  43.         while(1) {
  44.                 if(i++>10) {
  45.                         break;
  46.                 }
  47.                 if(already_handle_other_function){
  48.                         break;               
  49.                 }
  50.                 sleep(1);
  51.                
  52.         }
  53.         signal(SIGSEGV,SIG_DFL);
  54. }
  55. int main(void)
  56. {
  57.         signal(SIGSEGV,segv_handler);
  58.         pthread_t tid_core_thread=0UL,tid_other_function_thread=0UL;
  59.         pthread_create(&tid_core_thread,NULL,core_thread,NULL);
  60.         pthread_create(&tid_other_function_thread,NULL,other_function_thread,NULL);
  61.         pthread_join(tid_core_thread,NULL);
  62.         pthread_join(tid_other_function_thread,NULL);
  63.         return 0;
  64. }
复制代码

论坛徽章:
0
发表于 2012-04-20 21:11 |显示全部楼层
说的不错,受益匪浅,谢谢指导

论坛徽章:
0
发表于 2012-04-21 00:52 |显示全部楼层
一旦发生段错误,内存中的数据应全部被认为不可信任。
任何依靠段错误之前的内存中不可信任数据的处理,实际上都可能是扩散故障。程序已经段错误了,你再“保持数据完整性”是完全没意义的。
简单core dump后异常终止是最明智的处理。

论坛徽章:
0
发表于 2012-04-22 14:19 |显示全部楼层
JohnBull 发表于 2012-04-21 00:52
一旦发生段错误,内存中的数据应全部被认为不可信任。
任何依靠段错误之前的内存中不可信任数据的处理,实 ...

1.段错误不等同于内存覆盖,内存中的数据不是完全不可信任;
2.在多人协作的模块,别人的程序发生段错误,一般不会损坏我的数据,因为我的数据一般是单独的线程,暴露有限的接口,不会让外人直接访问我的数据;
3.如果发生内存覆盖这样的错误,只要不是特别严重的,一般数据都不会被损坏。因为别的线程覆盖栈中的数据跟我没关系,覆盖堆中的数据,只有不涉及到我的线程,问题也不大,因为每个线程的使用的堆地址空间是隔开的。
4.有一些数据模块是这样的:停止进程前dump数据索引,启动时根据索引启动会很快,如果没有索引就巨慢,一般需要启动1个小时以上,如果真的是出core后不dump索引的话,就会面临着模块1小时无法服务的困境,是非常危险的。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP