blogernice

导航

bus error, 段错误

bus error(core dumped) 总线错误

segmetation fault(core dumped)段错误
产生原因: 当硬件告诉操作系统一个有问题的内存引用时,操作系统通过发送信号给有问题的进程进行交流。(信号是一种事件通知或一个软件中断)。普通进程一般对“总线错误”或“段错误”信号将进行信息转储并且终止,俗称“程序coredump”。
(当然可以设置一个信号处理程序修改程序的缺省反应)。

总线错误:
引起原因: 几乎总是由于对未对齐的读或写引起的。它之所以称为总线错误是因为对未对齐的内存访问时,被阻塞的组件就是地址总线。

对齐(alignment)数据项只能存储在地址是数据项大小的整数倍的内存位置上,这样可以加速内存访问。如:访问一个8字节的double的数据时,地址只能是8的整数倍,所以存储一个double的地址只能是24,8008,但不能存储于地址1006因为它不能被8整除,只要保证这个原则,就可以保证一个原子项数据不会跨页或cache块的边界。

引起总线错误的小程序:
union
{
    char a[10];
    int i;
}u;

int *p =(int*)&(u.a[1]);

*p =17;/*p中未对齐的地址将会引起总线错误*/
因为数组和int的联合确保了a是按照int的4字节来对齐的,所以“a+1”肯定不是int来对齐的。(后面专门讨论下内存对齐问题)

段错误:
   引起直接原因:
(1)解除引用一个包含非法值的指针。
(2)解除引用一个空指针(常常是从系统返回,却未经过检查)。
(3)未得到正确权限进行访问。如:向只读文本段存储值就回引起段错误。
(4)用完了堆栈或堆空间。

   出现频率来分:
1 坏指针值错误:指针赋值前用它来引用内存;或向库函数传递坏指针(系统程序出现坏指针问题很可能还是出自自己的代码中);指针释放后还访问他的内容,
指针释放后,一定要把它置为NULL。

2 改写错误:越过数组边界写入数据,在动态分配内存两端写数据,或改写一堆管理数据结构(在动态内存两端写数据就会引起这种错误)。
    p= malloc(256); p[-1]=0; p[256]=0;
         
3 指针释放引起的错误:释放一个内存块两次,或释放一个未曾用malloc分配的内存,或释放正在使用中的内存,或释放一个无效的指针。
    如:
        for(p = start; p; p= p->next)
           {
             free(p);
           }
存在程序对下一次循环中对释放的指针p进行解除引用。

用dbx可以看出堆栈空间是否用完:
dbx a.out
(dbx) catch SIGSEGV
(dbx) run
...
signal SEGV(segmentation violation ) in <some_routine> at oxeff57708
(dbx)where
如果可以看到调用链说明堆栈空间还没有用完。如果看到以下情景:
fetch at oxeffe7a60 failed --- I/O error
(dbx)
那么堆栈空间已经用完,上面16禁止数就是可以提取或影射的堆栈地址。
C-SHELL中可以通过limit stacksize 10 来调整堆栈的空间为10K,进程的总线地址空间仍然收到交换区大小的限制,可以用swap -s命令查看交换区的大小。

 

总线错误和段错误问题的定位:

对现在的很多初级的程序原来说如果遇到总线错误(bus error)或者段错误(segementation fault/ core dump)是一件非常折磨人的事,让人一时间找不到什么好的方法也不知从何处下手去解决这个问题;和许多人一样,我很快也遇到了这样的问题:

 

        出现这个错误时,错误信息对引起这种事件的的错误的源代码并没有做简单或者详细的解释;只是简单的显示error:segement default 以上的信息并未提供如何从代码中寻找错误的线索,而且这两者之间的区别也并不是十分清楚,时至今日依然如此。
        大多数的错误都呈现出这样的一种事实:错误是操作系统所监测到的异常,而这个异常是尽可能的以操作系的处理规则而出现的问题而发出的warning;总线错误和段错误的准确原因是在不同的操作系统之间运行版本的问题。

 

        当硬件告诉系统有错误的内存引用时,会出现这两个error操作系统向出现错误的进程发送signal与之进行communication。信号就通知一种事件的发生或软中断的产生,在Linux系统中使用很广泛,但在应用程序的编写中几乎不会使用;在缺省的情况下,进程在收到段错误或者总线错误信号后就将信息转储并终止,当然可以对这些中断或者信号做一些处理,为它写一些handler函数,用于修改进程的缺省反映。

 

        信号是由于硬件的中断而产生的,对中断的编程是很困难的因为它是异步产生的对其发生的时间也是不可预测的。

 总线错误:

           总线错误几乎是由于data的未对齐的读或者写引起的,将他之所以称为是bus error;原因在于出现未对齐的内存的读写访问请求时,被堵塞的组间就是总线错误。对其(alignment)的意思是数据项只能存储在地址是数据项大小的整数倍的内存空间上。现在很多的RISC(reduced instruction set computer,精简指令集计算机)需要对数据对齐,因为数据的任意的对齐会对内存的空间和逻辑上会出现巨大的影响并使系统的性能和速度上变慢。通过内存对齐使内存的访问局限在一个cache或者单独的段 /页上可以极大的提高访问的速率和程序的执行速率,也不会出现内存的错乱现象,便于内存的管理。当然一个好的编译器会对内存的不对齐情况会有相应的warning提示。

 

段错误:

        段错误是由于内存管理单元MMU(负责支持虚拟内存的硬件)的异常所致,而该异常则通常是由于解除引用一个未初始化或者非法的指针引起的,如果指针引用一个并不位于你的地址空间的地址,操作系统会进行干涉,一个小的引起段错误的带码:

int* p = 0;p = 88;

一个微妙之处是如果初始化的指针恰好具有未对齐的值,它将产生总线错误而不是段错误,对于绝大多数架构而言cpu先看到地址,再把它发给MMU;另外的一点是,导致指针具有非法的值通常是由于不同的编程错误而引起。和总线不同此时的段错误更像是一个间接的症状引起的。

 

通常情况下导致段错误的几个直接原因:

        1,解除一个包含非法值的指针。

        2,解除引用一个空指针(常常由于从系统程序中返回空指针,并未经过检查就使用)。
        3,在未得到确定的权限时就进行访问,试图在只读的文件上执行写操作是出现段错误。

        4,超出了虚拟内存的大小(4G);或堆栈空间

已发生的频率为序,最终可能导致段错误的常见编程的错误:

P1: 坏指针错误:在指针之前就用它来引用内存,或者向库函数传送一个坏的指针。导致这种坏指针的原因是对指针进行释放后再去访问它所致。警记:在free之后含要将指针的指向NULL值,free(p); p = NULL;

P2:越过数组的边界写入数据,在动态分配内存时容易出现的额一种问题。

P3:两次释放同一块内存,或者释放未曾malloc过的内存或无效的指针;或者释放这在使用中的内存空间,一个极为常见的释放内存有关的错误语句:

for(p = head; p ; p = p -> next)  {
      free(p);
}
在这段代码中,执行下一次循环迭代时,程序已经释放的指针进行解除引用的操作,从而导致不可预料的错误。

修改办法:

struct node *p, *head, * q;
for(p = head; p ; p = p -> next)
{
      q = p - > next;
      free(p);
 }

综上基本上可以确定,出现段错误或者总线错误首先考虑到的就是指针的使用和内存访问方面的东西;一般的问题通过这些基本可以确定了。

posted on 2018-10-26 14:47  blogernice  阅读(1214)  评论(0)    收藏  举报