Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Оператор new[] и uCOS-II
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > Cредства разработки для МК > GNU/OpenSource средства разработки
aas
Доброе время суток!

Имеется контроллер ARM7-TDMI-S (LPC2214), на нем многозадачное приложение C++ с оконной графической оболочкой на основе uCOS-II и uC-GUI. Размер памяти 1 мег. Исключения не используются, STL и шаблоны используются. Компилятор используется GCC.
Вообще я не большой знаток C++ и вообще работы с микрокрнтроллерами, раньше программировал на Java большие веб-порталы, базы данных и т.д. Но мне от предшественника достался уже настроенный проект и среда разработки, т.е. вноси изменения, нажимай кнопочку, и компилируй, прошивай, отлаживай и т.д.

С некоторых пор, когда я сильно увеличил интенсивность использование динамической памяти, около 20-30 обращений в секунду. Программа стала виснуть примерно через 2 - 20 минут работы. На локализацию причины ушло более месяца, и то я не уверен, что локализовал верно.

Начал я с алгоритма распределения памяти, от предшественника досталась папка с исходниками newlib-1.16.0. Оттуда я вытащил исходник malloc и free, интересные алгоритмы, но ничего криминального я там не нашел (спасибо китайцу, их писавшему, очень подробные комментарии оставил, а также больше количество отключаемого диагностического кода!). Как и положено, еще мой предшественник переопределил вызовы, включающие мютекс при обращении к этим функциям. Поигрался я с параметрами компиляции, походил по ним отладчиком, все вроде работает там.
Но потом я заметил, что некорректные адреса иногда возвращает именно вызов new[], а не malloc, причем, new[] сам вызывает malloc, и этот malloc возвращает адреса корректные, а из new[], как правило, тоже вылетают адреса корректные, те же, что malloc вернул. Но иногда вместо них вылетают нулевые указатели, а иногда даже указатели на его собственный, этого самого new[], код (судя по таблице символов), причем, не на точку входа, а куда-то на середину кода. И вот после того, как я по этому указателю что-то пишу, становится все совсем плохо sad.gif.

В общем, заменил я вызов new char[ xxx ] на malloc( xxx ), а delete[] на free(), и все вроде как заработало. Потом я вернул new[] и delete[], но стал запрещать прерывания на время их работы (макросы OS_ENTER_CRITICAL() и OS_EXIT_CRITICAL в uCOS), тоже вроде не падает (по крайней мере, еще не упало, пока я пишу это сообщение...).

Вот я и не понимаю, что такого может быть в new[] и delete[], что они не работаю нормально в много задачной среде? В программе не так уж и много мест, где они вызываются. Даже оконная оболочка ими не пользуется, а обращается напрямую к malloc и free, а с ними то я проблем не нашел. STL еще, наверное, их использует, я так догадываюсь, но он тоже вызывается не так часто. Но все-таки хотелось бы иметь нормальные варианты new и delete, которые корректно конструкторы и деструкторы вызывают, ну и т.д.

Правильно ли я понимаю проблему, и какие есть способы ее решить?

Спасибо за ответы!

AlexandrY
Цитата(aas @ Jun 25 2014, 17:01) *
Правильно ли я понимаю проблему, и какие есть способы ее решить?

Спасибо за ответы!


Ну что, видать грабли засели где-то в программе и кто-то портит память в связанных цепочках менеджера управления памятью.
malloc и new скорее всего ни при чем. Просто они пользуются памятью из неудачного места.

А может элементарно стека не хватает по задачу.
Или бывает что стек соседней задачи наезжает на стек задачи которая использует new.
Вообщем начать надо с контроля стеков.

А лучше перейти на Cortex. Там влет с помощью JTAG можно вычислить посторонние записи в память.
aas
Цитата(AlexandrY @ Jun 25 2014, 19:14) *
Ну что, видать грабли засели где-то в программе и кто-то портит память в связанных цепочках менеджера управления памятью.
malloc и new скорее всего ни при чем. Просто они пользуются памятью из неудачного места.

А может элементарно стека не хватает по задачу.
Или бывает что стек соседней задачи наезжает на стек задачи которая использует new.
Вообщем начать надо с контроля стеков.

А лучше перейти на Cortex. Там влет с помощью JTAG можно вычислить посторонние записи в память.


Напишу чуть подробнее, что происходит. Я вытащил исходники _malloc_r, _free_r и еще чего-то из этой серии (кажется, _realloc_r и еще что-то, я посмотрел по sym и map файлам, что ко мне линкуется из этих функций, их и вытащил) в файл и прикрепил к сборке. То есть я прикрепил файл, поставил в нем настройки ка для сборки в составе newlib, и открыл макросами сборку только этих функций. Т.е. вся подсистема памяти (в объеме, в котором я ее использую, включая глобальные структуры данных) теперь берется из этого файла, а не из библиотеки. Потом включил я в ней все проверки целостности структур данных, которые еще автор оставил для режима debug, добавил свои кое-какие, в общем весь файл в ассертах, если вдруг что там было бы разрушено, сразу вылетел бы ассерт (у меня он красит низкоуровневым вызовом экран красным и останавливается на точке останова). Поставил также точки останова при возврате нулевого указателя. Более того, адрес последнего выделенного блока памяти пишется в глобальную переменную, чтобы при останове посмотреть, что это было. В общем, по этой причине в корректной работе менеджера памяти у меня сомнений нет.

А вот некорректность new[] я отследил уже двумя способами, во-первых, assert сработал по нулевому указателю, который оно возвращает иногда (причем, malloc, который оно вызвало, вернул указатель нормальный. Во-вторых, assert, который сработал уже во _free_r на то, что указатель не из кучи, да и вообще нечетный (хотя должен делиться на 8). Из кучи адрес или нет, отследить у меня легко: в памяти сначала идут код и статические данные, потом стеки всех задач, потом начинается куча и до конца памяти. Причем, по этому указателю лежит текст, который там и должен лежать, т.е. который я перед этим писал по указателю, который вернул мне new[]. Стеки тоже очень большие, работают от силы на треть размера

P.S. А можно подробнее, какие возможности добавились в Cortex по поиску таких вот неприятных глюков?

den_po
Нену, если проблема в new, почему бы не смотреть его код? Ну или просто переписать по-своему.
aas
Цитата(den_po @ Jun 26 2014, 00:50) *
Нену, если проблема в new, почему бы не смотреть его код? Ну или просто переписать по-своему.


Я пока не нашел, к сожалению, где посмотреть его код (в смысле, исходник), ну и как правильно его переписать тем более. Там какой-то старый компилятор GNU ARM, еще 2007 или 2008 года, версию сейчас не помню, завтра на работе посмотрю ее...

Но меня удивляет еще другое: что могло заставить разработчиков компилятора или библиотеки сделать new[] не thread-safe, если malloc уже сделан thread-safe?
den_po
Цитата(aas @ Jun 26 2014, 01:14) *
Я пока не нашел, к сожалению, где посмотреть его код (в смысле, исходник),

libstdc++

Цитата(aas @ Jun 26 2014, 01:14) *
ну и как правильно его переписать тем более

Если совсем просто, то как-то так:
Код
void* operator new(size_t sz){ return malloc(sz); };
void* operator new[](size_t sz){ return malloc(sz); };
void operator delete(void * p){ free(p); };
void operator delete[](void * p){ free(p); };
void* operator new(size_t size, void* p){ return p; }
void* operator new[](size_t size, void* p){ return p; }
void operator delete (void*, void*){ }
void operator delete[] (void*, void*){ }

А вообще гугль перегрузка операторов new delete
Непомнящий Евгений
Цитата(aas @ Jun 26 2014, 01:14) *
Но меня удивляет еще другое: что могло заставить разработчиков компилятора или библиотеки сделать new[] не thread-safe, если malloc уже сделан thread-safe?


new вызывает конструктор объекта, а delete - деструктор. Возможно тут порылась собака?
Причем эти вызовы вставляются компилятором. Т.е. к примеру new в библиотеке опеределен просто как void* operator new[](size_t sz){ return malloc(sz); };, а когда у вас в коде написано new Test[1050], то сначала будет вызвана функция void* operator new[](size_t sz){ return malloc(sz); }, а затем - 1050 раз конструктор класса Test (а если вылетит исключение, то для уже сконструированных объектов будет вызван деструктор)

Кроме того, new и delete могут быть переопределены пользователем для каждого класса индивидуально.
AlexandrY
Цитата(aas @ Jun 25 2014, 18:55) *
А вот некорректность new[] я отследил уже двумя способами, ...

P.S. А можно подробнее, какие возможности добавились в Cortex по поиску таких вот неприятных глюков?


Не там копаете, определенно.... wink.gif

Я работал немало с uC-GUI.
Надеюсь ваш предыдущий разработчик не пытался ее использовать из разных задач одновременно.

Дальше, использование стандартных new и malloc в uCOS это малопонятный выбор.
В uCOS есть свой проверенный действительно многозадачный менеджер памяти.

Разнообразия ошибок у вас должно быть больше.
Припомните не было ли с вашим софтом еще других странностей не объясняемых только проблемой с New.

Все это по причине того что одни задачи пишут по ошибке в область памяти других задач.
Потому то вас запрещение прерываний и спасало.

В Cortex-ах на такие действия можно поставить брекпойнт и отследить. В ARM7 такой возможности нет.
Вам надо спускаться на уровень ассемблера и там смотреть состояние и порядок доступа к разным областям памяти. Assert-ы в этой теме как мертвому припарка.

Правда бывают вообще терминальные случаи связанные с особенностями функционирования внешних шин памяти.
Один раз было когда функция пакетного возврата регистров из стека в SDRAM могла исказить содержимое одного из регистров в этом пакете при определенном содержимом соседних регистров.
Хотя отдельное чтение этой ячейки в SDRAM показывало правильное значение.
Вот это был баг! Даже JTAG был бессилен.
Требовалась тончайшая настройка контроллера SDRAM. Проблема целостности сигналов однако. biggrin.gif
aas
Доброе время суток!

Цитата(AlexandrY @ Jun 27 2014, 10:13) *
Не там копаете, определенно.... wink.gif

Я работал немало с uC-GUI.
Надеюсь ваш предыдущий разработчик не пытался ее использовать из разных задач одновременно.


Нет, uC-GUI используется только в двух задачах, в соответствии с тем, как в книжке написано, одна задача, собственно, интерфейса, вторая отрисовывает WM_PAINT.

Цитата(AlexandrY @ Jun 27 2014, 10:13) *
Дальше, использование стандартных new и malloc в uCOS это малопонятный выбор.
В uCOS есть свой проверенный действительно многозадачный менеджер памяти.


Я где-то читал, этот менеджер умеет только куски памяти фиксированного размера выделять. Хотя, возможно, это была устаревшая информация, документацию именно к используемой версии я столь тщательно не читал (нужную версию книжки автора системы удалось скачать буквально пару недель назад только).
А тот менеджер, что я использую, я прошел его код полностью, и при условии защиты мютексом там проблем вроде не должно быть.

Цитата(AlexandrY @ Jun 27 2014, 10:13) *
Разнообразия ошибок у вас должно быть больше.
Припомните не было ли с вашим софтом еще других странностей не объясняемых только проблемой с New.


К счастью, нет, остальное работает стабильно все. Кроме одного очень редкого глюка с GUI - иногда не отрисовывается фон окна и вместо него может вылезти кусок логотипа фирмы, который на десктопе нарисован. Это случается в среднем раз в 3-5 месяцев, и я не знаю как это отследить sad.gif. При этом программа не виснет.

Цитата(AlexandrY @ Jun 27 2014, 10:13) *
Все это по причине того что одни задачи пишут по ошибке в область памяти других задач.
Потому то вас запрещение прерываний и спасало.


А что подразумевается под областью памяти других задач? В нескольких местах у меня одна задача выделяет память, и отправляет указатель на нее в параметрах сообщения другой задаче, а другая задача эту память уже освобождает. Т.е. один кусок памяти в разное время используется разными задачами.

Цитата(AlexandrY @ Jun 27 2014, 10:13) *
В Cortex-ах на такие действия можно поставить брекпойнт и отследить. В ARM7 такой возможности нет.
Вам надо спускаться на уровень ассемблера и там смотреть состояние и порядок доступа к разным областям памяти. Assert-ы в этой теме как мертвому припарка.


Я вот нашел пока только то, что new[] иногда возвращает указатель на свой код вместо кода выделенной памяти. И запрещение прерываний действительно спасает от этого. Почему оно так делает, пока не знаю. Разве этому оператору нужны статические структуры данных? Опять такти, заменил я new на malloc, и все работает и без запрещения прерываний. Не пойму, в чем разница между new[] и malloc может быть для типа char? Там нету конструктора. Попробовал код этого места глянуть, но закопался, там сплошные вызовы каких-то внутренний функций библиотеки. Судя по именам, часть из них для работы с исключениями. Но кода там явно намного больше, чем просто вызов malloc.

Цитата(AlexandrY @ Jun 27 2014, 10:13) *
Правда бывают вообще терминальные случаи связанные с особенностями функционирования внешних шин памяти.
Один раз было когда функция пакетного возврата регистров из стека в SDRAM могла исказить содержимое одного из регистров в этом пакете при определенном содержимом соседних регистров.
Хотя отдельное чтение этой ячейки в SDRAM показывало правильное значение.
Вот это был баг! Даже JTAG был бессилен.
Требовалась тончайшая настройка контроллера SDRAM. Проблема целостности сигналов однако. biggrin.gif


Мдаа, если я с таким столкнусь, искать, наверное, год буду, а не месяц sm.gif

Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.