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: работа с динамически выделяемой памятью. |