VisualDSP: поиск утечки памяти в приложении Печать
Добавил(а) microsin   

Пользоваться готовыми библиотеками ADI довольно удобно, однако иногда этот черный ящик преподносит неприятные сюрпризы. Например, мне довелось столкнуться с утечками памяти в службе файловой системы (File System Service, сокращенно FSS), модуль adi_fss.c (находится в каталоге установки VisualDSP++ 5.0). Описание примера утечки памяти и её исправления см. во врезке "Утечка памяти FSS");

[Как найти ошибку утечки памяти]

Обычно ошибка заключается в том, что нет парности вызовов malloc / free. Следует просмотреть все эти вызовы, и убедиться, что каждому вызову выделения блока памяти malloc (или аналогичных функций heap_malloc, _adi_fss_malloc и т. п.) строго соответствует вызов free. Если этих вызовов немного, и Вы ищете утечку в собственном коде, то обычно разобраться просто. Но если Вы ищете утечку в коде библиотек сторонних производителей, которые активно используют динамическую память (например, библиотеки Analog Devices, поставляющиеся вместе с VisualDSP), то поиск ошибки может быть затруднен.

Решить проблему можно, если создать функцию проверки свободного места в куче, которая будет писать информацию в лог, например такую:

void heapinfo (void)
{
   printf("[Свободно в кучах] 0: %i, 1: %i\n",
           heap_space_unused(0),
           heap_space_unused(1));
}

Этот пример выводит информацию о куче 0 (это быстрая память SRAM) и куче 1 (обычно это память SDRAM). Функцию heapinfo можно вызывать в нужных местах, где Вы хотите диагностировать выделение и освобождение памяти. Подробнее про использование нескольких куч см. [1].

Следующий пример диагностической функции выводит адрес выделенного (или освобождаемого) блока памяти вместе с комментарием. Она может облегчить поиск нужного места в коде, где выделяется или освобождаеися память. Здесь выдается информация только по использовании кучи 0, и кроме свободного места в куче выводятся данные, на сколько уменьшилось или увеличилось свободное место. Отрицательное значение покажет уменьшение свободного места в куче (соответствует выделению памяти), а положительное покажет увеличение (соответствует освобождению памяти).

void showalloc (void* blockaddress, char *comment)
{
   char tmpmsg[128];
   static int freelast = 0;
   int freemem = heap_space_unused(0);
   printf("%08X %s: %i %i\n", blockaddress, comment, freemem, freemem - freelast);
   freelast = freemem;
}

Примеры вызова функции:

...
showalloc(NULL, "НАЧАЛО: heap0"); 
...
ADI_RAMDISK_DEF *pDevice = (ADI_RAMDISK_DEF*)_adi_fss_malloc(0,sizeof(ADI_RAMDISK_DEF));
showalloc(pDevice, "pDevice, RAMdisk PDD open");
...
pDevice->pDev=(ADI_FSS_DEVICE_DEF *)_adi_fss_malloc(0,sizeof(ADI_FSS_DEVICE_DEF));
showalloc(pDevice->pDev, "pDevice->pDev в функции adi_fss_RegisterDevice");
...
showalloc(NULL, "КОНЕЦ: heap0"); 
...

В лог будет выведено примерно следующее:

00000000 НАЧАЛО: heap0:                                  14184 0
FF904850 pDevice, RAMdisk PDD open:                      14088 -96
FF904960 pDevice->pDev в функции adi_fss_RegisterDevice: 13872 -216
00000000 КОНЕЦ: heap0:                                   14184 312

[Поиск утечки на уровне модуля]

Место утечки можно также определить, если временно поменять используемую в модуле кучу. Для этого в программе обычно делаются макроопределения наподобие следующих:

#define PDD_DEFAULT_HEAP               1  //Индекс кучи для физического драйвера
#define ADI_RAMDISK_GEN_HEAPID         1  //Индекс кучи для RAM-диска
#define ADI_FSS_CACHE_GENERAL_HEAP_ID  0  //Индекс кучи для FSS

Тогда во всех местах модуля, где используется выделение / освобождение памяти с указанием индекса кучи, вместо числового идентификатора кучи следует использовать символический, определенный через #define. Тогда будет проще сразу для целого модуля поменять используемую кучу, и после этого проверить, как изменилось использование памяти. Если например, Вы поменяли в модуле кучу с 1 на 0, и утечка памяти стала происходить в куче 0, то следовательно ошибка где-то в этом модуле.

Код службы FSS [2] находится в модуле adi_fss.c, и в нем была ошибка с утечкой памяти.

Примечание: эти модули имеются даже 2 разных версий, и почему Analog Devices допустила дублирование кода, непонятно. Одна версия adi_fss.c, более старая (помечена в комментариях датой 2009-07-27 14:01:21), находится в каталоге VisualDSP 5.0 \ Blackfin \ Examples \ ADSP-BF527 EZ-KIT Lite \ Services \ File System \ VDK \ shell_browser, а другая версия, более новая (2010-12-10 09:07:11), находится в каталоге VisualDSP 5.0 \ Blackfin \ lib \ src \ services \ fss. Я взял более новую версию.

Ошибка с утечкой памяти находится в функции adi_fss_Terminate. Суть ошибки в том, что при завершении работы службы FSS она должна освобождать выделенную для работы память и закрывать все используемые драйверы, чтобы они в свою очередь также освободили выделенную для себя память. Идеология сервисов и драйверов библиотек ADI построена на обмене команд между отдельными компонентами библиотек, когда для завершения соответствующего драйвера ему посылается команда завершения (например, для драйвера устройства FSS это команда ADI_FSS_CMD_DEREGISTER_DEVICE), либо, как вариант, можно напрямую вызвать соответствующую функцию завершения драйвера adi_fss_DeRegisterDevice. Но почему-то Analog Devices отошла от собственной модели, и в функции adi_fss_Terminate освобождала ресурсы драйверов в цикле очистки списка устройств pDeviceList. Вот кусок кода adi_fss_Terminate, где освобождается память:

/*********************************************************************
    Функция:       adi_fss_Terminate
    Описание:      Завершает File System Service, размонтирует все
                    носители данных (media) и закрывает все открытые
                   драйверы устройств. При этом открытые файлы
                   не будут аккуратно закрыты.
*********************************************************************/
__ADI_FSS_SECTION_CODE
u32 adi_fss_Terminate (void)   /* Завершение FSS */
{
   ...
      /* Проход по списку устройств */
      pDevice = pDeviceList;
      while (pDevice)
      {
         pCurrDevice = pDevice;
         pDevice = pDevice->pNext;
         // < ---- в этом месте вкралась ошибка: не освобождается память,
         // которая была ранее выделена для поля pDev
         pCurrDevice->pDev->DeviceHandle = NULL;
         _adi_fss_free(-1,pCurrDevice);
      }
      /* Сброс указателя на список устройств */
      pDeviceList = NULL;
   ...
}

Почему был выбран такой принцип освобождения памяти, непонятно. На самом деле в цикле прохода по списку устройств ИМХО нужно было бы посылать драйверам команду ADI_FSS_CMD_DEREGISTER_DEVICE с помощью функции adi_fss_Control, например вот так:

...
pDevice = pDeviceList;
while (pDevice)
{
   pCurrDevice = pDevice;
   pDevice = pDevice->pNext;
   adi_fss_Control(ADI_FSS_CMD_DEREGISTER_DEVICE,
                   (void*)&pCurrDevice->pDev->DeviceHandle);
   pCurrDevice->pDev->DeviceHandle = NULL;
   //_adi_fss_free(-1,pCurrDevice);    // Освобождение памяти должна теперь
                                       // делать функция adi_fss_DeRegisterDevice
}
...

Однако этой правкой дело не ограничивается, нужно еще доработать функцию adi_fss_DeRegisterDevice, чтобы она корректно освобождала память и демонтировала устройство в зависимости от его типа. Вот кусок старого кода adi_fss_DeRegisterDevice:

__ADI_FSS_SECTION_CODE
u32 adi_fss_DeRegisterDevice (ADI_DEV_DEVICE_HANDLE DeviceHandle)
{
   ...
   if (DeviceFound)
   {
      UnMountDevice (DeviceHandle);
      adi_dev_Control (DeviceHandle, ADI_PID_CMD_MEDIA_ACTIVATE, (void*)false);
      /* Удаление записи драйвера из списка и освобождение его памяти */
      if (pAhead)
      {
         pAhead->pNext = pDevice->pNext;
      }
      else
      {
         pDeviceList = pDevice->pNext;
      }
      _adi_fss_free(-1, pDevice->pDev);
      _adi_fss_free(-1, pDevice);
      Result = ADI_FSS_RESULT_SUCCESS;
   }
   return Result;
}

Вот исправленный код, который проверяет тип устройства pDevice->Type:

__ADI_FSS_SECTION_CODE
u32 adi_fss_DeRegisterDevice (ADI_DEV_DEVICE_HANDLE DeviceHandle)
{
   ...
   if (DeviceFound)
   {
      if (ADI_FSS_DEVICE_TYPE_REGISTERED_PID == pDevice->Type)
      {
         UnMountDevice (DeviceHandle);
         adi_dev_Control (DeviceHandle, ADI_PID_CMD_MEDIA_ACTIVATE, (void*)false);
         _adi_fss_free(-1, pDevice->pDev);
      }
      /* Удаление записи драйвера из списка и освобождение его памяти */
      if (pAhead) {
         pAhead->pNext = pDevice->pNext;
      }
      else {
           pDeviceList = pDevice->pNext;
      }
      _adi_fss_free(-1, pDevice);
      Result = ADI_FSS_RESULT_SUCCESS;
   }
   return Result;
}

[Более простой вариант исправления ошибки]

Можно поступить проще (но не в соответствии с концепцией "все свое ношу с собой"), и ввести исправление только в функцию adi_fss_Terminate, например вот так:

...
/* Проход по списку устройств */
pDevice = pDeviceList;
while (pDevice)
{
   pCurrDevice = pDevice;
   pDevice = pDevice->pNext;
   if (ADI_FSS_DEVICE_TYPE_REGISTERED_PID == pCurrDevice->Type)
   {
      adi_fss_DeRegisterDevice (pCurrDevice->pDev->DeviceHandle);
   }
   pCurrDevice->pDev->DeviceHandle = NULL;
   _adi_fss_free(-1,pCurrDevice);
}
/* сброс списка указателей на устройства */
pDeviceList = NULL;
...

[Ссылки]

1. VisualDSP: работа с динамически выделяемой памятью.
2. Служба файловой системы Blackfin.