您的位置:控制工程论坛网论坛 » 软件与程序 » 软件编程经典培训实例 10、分配资源是否已正确释放

xilinxue

xilinxue   |   当前状态:在线

总积分:16186  2024年可用积分:0

注册时间: 2008-06-26

最后登录时间: 2020-03-22

空间 发短消息加为好友

软件编程经典培训实例 10、分配资源是否已正确释放

xilinxue  发表于 2008/11/6 13:00:41      1181 查看 6 回复  [上一主题]  [下一主题]

手机阅读

  案例 1

          在对接入网A产品的网管软件测试中,发现了一个WINDSOWS资源损耗的的问题:当网管软件运行几天后,WINDOWS总会出现“资源不够”的告警提示。如果网管软件不关掉再重新启动的话,就会出现WINDOWS资源完全耗尽的现象,最终网管系统反应很慢,无法正常工作。
 从现象上可以判断出,网管软件存在隐蔽的内存泄露或资源不释放的问题,并且这种资源耗尽是一个缓慢的过程。如何定位这个问题呢?        
 定位这种问题可以利用WINDOWS中的一个系统资源监视工具。打开Windows的“附件/系统工具/资源状况”,这是一个系统资源、用户资源、和GDI资源的实时监视工具。
 工具有了,那么如何发现导致不断消耗资源的特定操作呢?
 首先和开发人员共同探讨,列出几个最可能消耗资源的操作和一些操作组合,这样就缩小了监视范围,避免没有范围的碰运气,否则如大海捞针。
 监视前,首先重新启动WINDOWS,最好不运行其他的程序,打开“系统状况”这个监视工具,然后运行网管软件,记下此时的资源状况数据。
 然后针对一个可疑的操作,快速大量地重复进行。这种重复性的操作可以利用QArun测试工具执行,QArun可以记录操作者的一次操作步骤,然后按照设定的次数重复操作。操作后,观察此时的资源状况,并记下此时的数据,与操作前的数据比较,如果操作前后的数据数据没有变化或变化很小,可排除此项操作,否则就可断定此项操作会引起资源耗尽。
 对其它可疑的操作和操作组合重复以上过程。
 通过以上的步骤,终于找出引起资源耗尽的罪魁祸首。分析相应部分的代码,发现引起资源耗尽原因有:内存泄露,画笔和画刷资源用完后未释放等。

1楼 0 0 回复
  • xilinxue

    xilinxue   |   当前状态:在线

    总积分:16186  2024年可用积分:0

    注册时间: 2008-06-26

    最后登录时间: 2020-03-22

    空间 发短消息加为好友

    xilinxue   发表于 2008/11/6 12:58:01


          某产品后台软件版本,是用C++写的,程序员在写代码时,经常在构造函数中申请一块内存,而不释放,在程序其他代码中也经常只管申请,不管释放。
    例如:
     void WarnSvr::SaveWarnData()
    {
         ......


        for(int m="0";m<RecordsInBuffer[EVENT_ALARM];m++)
        {
       HISTORY_FILTER_INDEX* item=
                    new HISTORY_FILTER_INDEX;
       item->Csn=Buffer[EVENT_ALARM][m].Csn;
       
                                   item->Position=m
                    +(RecordsInHistoryFile-RecordsInBuffer[EVENT_ALARM]);


       //If a warn with a certain Csn is not in EventFilterIndex
       //it is not necessary to be added to  HistoryFilterIndex
       int item_total=EventFilterIndex.GetItemsInContainer();
       BOOL find_flag=false;
       for(int k="0";k<item_total;k++)
        if(EventFilterIndex[k]->Csn==item->Csn)
         {
         find_flag=true;
         break;
         }
       if(find_flag)
         {
        HistoryFilterIndex.Add(item);
        if(HistoryFilterIndex.IsFull())
          ClearIndexEntry();
        }
    //建议在此处加上:               
    //                             else
    //                                      delete item;
    }。
            有的程序员认为,后台运行的环境有大量内存,几个字节的浪费不会造成死机等重大事故。然而,长时间累计起来,必然会造成资源紧张而出现故障。
             实际上,这种思想是造成我们产品不稳定的原因之一。我们的主机在网上能运行几个月几年,大家对内存的分配释放较敏感,而我们的后台产品往往只能正常运行几天。这个地方不注意也是原因之一吧。

    2楼 回复本楼

    引用 xilinxue 2008/11/6 12:58:01 发表于2楼的内容

  • xilinxue

    xilinxue   |   当前状态:在线

    总积分:16186  2024年可用积分:0

    注册时间: 2008-06-26

    最后登录时间: 2020-03-22

    空间 发短消息加为好友

    xilinxue   发表于 2008/11/6 12:58:22


     在进行代码审查过程中,造成内存泄漏的代码比较多。下面举几种常见的内存泄漏错误,供测试人员在代码审查中参考:
     1. 函数有多个出口时,没有在每个出口处对动态申请的内存进行释放。一般在异常处理时容易出现这种错误。下面的代码段就是这样的例子:
    .....
            pRecord = new char[pTable->GetRecordLength()];
     assert(pRecord != NULL);


     if (pTable->GoTop(FALSE) != DBIERR_NONE)
      return; // 如果从这里返回,pRecord将得不到释放
         .....
            pTable->Close();
     delete[] pRecord;
          }



     2. 给指针赋值时,没有检查指针是否为空,如果指针不为空,那么指针原来指向的内存将丢失。请看如下代码段:
    ....
     struct FileInfo * pdbffile = new struct FileInfo;
     pdbffile->pfileinfo = new struct ffblk;
     pdbffile->srcname = srcRootPath;
     pdbffile->desname = desRootPath;
     pdbffile->prev = NULL;
     pfile = pdbffile;
    //赋值之前没有检查一下pfile是否为空,如果不为空,会造成pfile指向的内存丢失。
     dbf_start_needed = FALSE;
     dbf_Finish = FALSE;
     flag_begined = TRUE;
     if(FALSE == Copy(TRUE))
     {
      dbf_start_needed = TRUE;
      WarnMsgOut("Error occurs while copying files in directory <dbf>,trying again.");
     }
        }


     3. 连续二次内存动态分配,在第二次分配失败时,忘记释放第一次已经申请到的内存。
     ....
     pMsgDB_DEV = (PDBDevMsg)GetBuff( sizeof( DBDevMsg ), __LINE__);
     if( pMsgDB_DEV == NULL )
      return;


     pMsgDBApp_To_Logic = (LPDBSelfMsg)GetBuff( sizeof(DBSelfMsg), __LINE__ );
     if( pMsgDBApp_To_Logic == NULL )
      return;//此处返回造成pMsgDB_DEV指向的内存丢失
     ....
     
     4.代码中缺少应有的条件分支处理,导致程序未执行任何操作而退出时,也可能没有释放应释放的内存,这种情况一般是缺少应有的else分支,或switch语句的default分支没有应有的处理。
    static void  OncePowerCmdHandle( struct HT_Appmsg  * msg )
    {
     ... ...
         pPower_test_answer =(struct _oncepower_test_answer *)GetBuff(sizeof(struct _oncepower_test_answer),__LINE__);
     if( pPower_test_answer == NULL_PTR )    
       return;
     ... ...
     if (TSS_State[testpsn].state == TEST_DEV_BUSY ||
      TSS_State[testpsn].state == TEST_DEV_ERROR )
     {...
     }
     else if (TSS_State[testpsn].state == TEST_DEV_IDLE )
     {...
      }
     // 缺少 else 分支,可能造成 pPower_test_answer 得不到释放
    }


    造成内存泄漏的情况很多,以上是几种典型的情况。
    虽然内存泄露一般出现在异常情况下,毕竟给系统造成很大的隐患,使系统的健壮性降低。测试人员在作代码审查时,对上述几种情况要尤其注意。

    3楼 回复本楼

    引用 xilinxue 2008/11/6 12:58:22 发表于3楼的内容

  • xilinxue

    xilinxue   |   当前状态:在线

    总积分:16186  2024年可用积分:0

    注册时间: 2008-06-26

    最后登录时间: 2020-03-22

    空间 发短消息加为好友

    xilinxue   发表于 2008/11/6 12:58:42

      在进行SAR的PDU包发收的测试过程中要同时考虑几个边界值,即发送包大小范围[0-Nmax],SAR的PDU包接收的最大值Kmax,MBUF块的大小M.在实测中,将SAR的PDU包接收的最大值设为2000(Kmax=2000B), MBUF的块长设为512(M = 512B),则发送包大小的正确分支的取值为下限0,上限Nmax=2000,然后在0与2000之间随机取若干值,再考虑MBUF的块长,还可增加M倍数的若干选值及其附近值.以上是测试的一般思路,但由于很偶然的机会选择包长2000,及Kmax=2000B,才发现问题.原因如下:
    MBUF块长512,但块中实际存放数据的只有500(MBUF头上有2个长字,尾部有1个长字共12B只用于块控制),而发送的包长正好是500的整数倍4,由于是整数倍,所以SAR(BT8230)从FREE链上摘成5个MBUF(原因从略),而SAR驱动只知道有4个MBUF,这样到上层用户时,只释放4个MBUF,从而漏掉1个MBUF,经过很短一段时间后,内存即被耗尽.(此问题非常严重,因为在实际运用中,是500的整数倍的PDU包的概率较小,但一旦出现就会发生一次内存泄漏,这样经过若干天或若干月的运行后会使系统崩溃)
    以前未发现此问题的原因是因为原来使用的缓冲块长为2048,减去12B的控制信息,实际存放数据的长度为2036.由于只考虑了2048这个值,忽略了2036,所以在选取上下限中的若干值时,选取包的长度是2036的倍数的概率就非常小,因而未发现该问题.
    由于测试中一般很难将取值范围中的所有值覆盖全,所以在选取上下限中的若干取值时要格外仔细,考虑的方面尽可能全,因为很有可能其中某些值就是测试边界值.凡是涉及的数字尽量选取,象该例中正确分支的测试边界为0,2000,512及其整数倍,500 及其整数倍,12 及其整数倍等值,它们是必测的边界值,而非可测可不测的随机选取的所谓若干选值.
    4楼 回复本楼

    引用 xilinxue 2008/11/6 12:58:42 发表于4楼的内容

  • xilinxue

    xilinxue   |   当前状态:在线

    总积分:16186  2024年可用积分:0

    注册时间: 2008-06-26

    最后登录时间: 2020-03-22

    空间 发短消息加为好友

    xilinxue   发表于 2008/11/6 12:59:07

     ABIS.CPP中的函数rel_ABIS_CCB_conn( )中,在进行消息链表Msg_Queue[ces]的拆链操作时,对于相应的CCB只进行了一次拆链操作,即只拆除了一个节点,如果出现该CCB对应的消息节点不止一个的情况就会出现大量节点不能释放的问题。
    if( Msg_Queue[ces].msghead != NULL_PTR )//message buffer notempty
    {
          //get first message record
          pMsgRecord = Msg_Queue[ces].msghead;
          //release buffer-messages concerning with ccb_no
          for( index = 0; index < MSGBUFFERNUM; index++ )
          {
             //这里要对pMsgRecord的值进行判断
              if( (pMsgRecord != NULL_PTR) && pMsgRecord->CCB_no ==
               ccb_no )        
             {
                //free the message buffer
                if( pMsgRecord == Msg_Queue[ces].msghead )//head
                Msg_Queue[ces].msghead = pMsgRecord->pnext;
                else if( pMsgRecord == Msg_Queue[ces].msgtail )//tail
                {
                        Msg_Queue[ces].msgtail = pPrevMsgRecord;
                        Msg_Queue[ces].msgtail->pnext = NULL_PTR;
                }
                else//not head and tail
                {
                       pPrevMsgRecord->pnext = pMsgRecord->pnext;
                }
                //put buffer back to buffer pool
                if( Msg_Buffer.empty_num == 0 )
                {
                      Msg_Buffer.linkhead = Msg_Buffer.linktail = pMsgRecord;
                      pMsgRecord->pnext = NULL_PTR;//这里将
                                                                                  pMsgRecord->pnext置为空
                      Msg_Buffer.empty_num++;
                }
                else
                {
                      Msg_Buffer.linktail->pnext = pMsgRecord;
                      pMsgRecord->pnext = NULL_PTR;//这里将
                                                                                  pMsgRecord->pnext置为空
                      Msg_Buffer.linktail = pMsgRecord;
                      Msg_Buffer.empty_num++;
                }
             }       
             else if( pMsgRecord == NULL_PTR )
                         break;//end of if
             //get next message record
               pPrevMsgRecord = pMsgRecord;
               pMsgRecord = pMsgRecord->pnext;//这时pMsgRecord为
                                                                           NULL_PTR将跳出for循环语句
          }//end of for
    }//end of if
    这里在拆除一个节点后导致pMsgRecord为NULL_PTR,再进行判断时将会跳出循环,这样将不能保证所有与同一个CCB有关的节点均被拆除,这时如果与同一个CCB对应的消息节点不止一个则这些消息节点均无法释放,造成可用的节点数不断减少,直接影响系统的建链过程,给系统的稳定带来隐患。
    后与开发人员联系,根据这段算法编写小程序验证了该问题,并提出了相应的解决方案,消除了该隐患。
    5楼 回复本楼

    引用 xilinxue 2008/11/6 12:59:07 发表于5楼的内容

  • xilinxue

    xilinxue   |   当前状态:在线

    总积分:16186  2024年可用积分:0

    注册时间: 2008-06-26

    最后登录时间: 2020-03-22

    空间 发短消息加为好友

    xilinxue   发表于 2008/11/6 13:00:21


    1、建立一个呼叫,并保持通话。在AM控存监控操作界面中观察通话建立在哪一块FBI板上。
    2、将有通话的FBI板拔出,观察通话情况,此时话音中断,但信令仍然保持。观察AM控存监控操作界面和E3M板2K网界面,发现AM侧因为检测到光纤已断,将通话在CTN、E3M板上占用的时隙置为空闲,即在AM控存监控操作界面和E3M板2K网界面观察不到时隙占用情况。
    3、分别在30秒、1分钟、3分钟时将拔出的FBI板插回原槽位,发现每次插回FBI板后话音立即恢复。
    4、观察BAM上的打印消息,发现打印的各模块占用CTN板大HW上DM时隙的空闲个数比较混乱。打印消息如下图所示:
        
    其中:
    1) 由于模块1、2、3、4各占用CTN板上两条大HW,每个DM时隙个数为256(即由两条大HW的两个DM组成,由于与OPT相联的大HW上有两个保留时隙,因此此DM上空闲时隙个数为:254。
    2)  由于E3M板只与一条大HW相联,故每个DM上空闲的时隙个数为:128。


     



      本现象对应2个问题:idle_count打印混乱,BM释放故障光路的时隙和对应的CCB、无线信道等资源。
        
    1、idle_count打印混乱是由于函数restore_one_hw中的一些处理不当造成的,以前被当作B型机的历史遗留问题没有重视;


    2、B2模块有2条光路,如果断掉其中一条,模块状态不会改变,原B型机程序对此不作任何处理,但应该增加这个功能,以免光路故障导致资源吊死。


    解决方法:
         问题一: 将函数restore_one_hw中原代码作如下改动:
                                 mod_dm[mod][i].tail.tsn = idle_dm_head + 125;
              ( idle_dm_head == 384 ) ?
                  mod_dm[mod][i].idle_count += TS_PER_DM - 1:
                  mod_dm[mod][i].idle_count += TS_PER_DM - 1;


      改为:
       if ( idle_dm_head != 384 )
       {
                           mod_dm[mod][i].tail.tsn = idle_dm_head + 127;
                  mod_dm[mod][i].idle_count += TS_PER_DM;
       }
        else
       {
                  mod_dm[mod][i].tail.tsn = idle_dm_head + 126;
                  mod_dm[mod][i].idle_count += TS_PER_DM - 1;
       }
          问题二分析如下:
               目前的模块状态是由IPATH调用DBMS模块的边检查实现的,只要存在一条可用的光路,即认为相邻模块为正常,对于具体的OPT板上的时隙状态的维护没有与呼叫控制的接口。具体的OPT板状态功能的检测是由IPATH完成的,在BM侧没有专门维护OPT和MC2板的模块,将转交OS组处理。


           总结:
             在拔出FBC板后,通话话音被中断,AM/CM侧已将与被拔出的FBC  板相关的资源全部置为不可用,此时BM侧主机程序也应该与AM/CM侧一致,释放掉所占用的资源,并将原通话的信令连接断开。这可能是由于不同模块的开发人员缺少相互间了解而造成的,即AM/CM侧与BM侧开发人员交流不够。作为测试人员对类似两个或多个模块相关的部分应该充分进行测试,不要想当然,往往是看起来不可能出问题的地方也容易测出问题。

    6楼 回复本楼

    引用 xilinxue 2008/11/6 13:00:21 发表于6楼的内容

  • xilinxue

    xilinxue   |   当前状态:在线

    总积分:16186  2024年可用积分:0

    注册时间: 2008-06-26

    最后登录时间: 2020-03-22

    空间 发短消息加为好友

    xilinxue   发表于 2008/11/6 13:00:41


     在进行有关排队指示的系统测试中,先闭塞掉基站的所有业务信道TCH,进行呼叫,再直接挂机或超时释放,发现TC存在中继资源吊死的问题。  
     由于此问题重现,后经定位分析,发现是ccb超时后收到AIR发来的clear cmd,进入 rel_one_bm_res( )时,由于ccb所登记的CIC还放在pre_occupied_res,并没有放入occuped_res,而rel_one_bm_res()只对存入occuped_res的CIC进行判断,并向AIE发UNBOOKCIC,而没有对存入pre_occupied_res的CIC进行判断,并UNBOOK掉,导致TC的中继资源吊死。应在超时函数或释放函数中对pre_occupied_res的CIC进行处理。
    在此过程中,CIC资源还存放在老CCB的pre_occupied_res中,在超时函数或释放函数中均未对pre_occupied_res中的CIC进行处理(即向AIE UNBOOK),导致TC中继资源吊死。
     在超时函数RR_time_out()中timer_name为TN_WAIT_ASS_READY时,和释放函数rel_one_bam_res()中增加对CCB的pre_occupied_res中的CIC的判断和释放处理。
            在使用资源同时,就要周密地考虑好资源的释放问题,只有这样,才能使我们的系统不断地稳定下来。
      资源的释放对于我们的交换机来说是至关重要的,一点点的疏忽都可能最终使我们的交换机因为无资源使用而死掉,要知道,“千里长堤,毁于蚁穴”。
    7楼 回复本楼

    引用 xilinxue 2008/11/6 13:00:41 发表于7楼的内容

总共 , 当前 /