Вопрос

В многопоточном встроенном программном обеспечении (написанном на C или C++) потоку должно быть предоставлено достаточно места в стеке, чтобы он мог завершить свои операции без переполнения.Правильный размер стека имеет решающее значение в некоторых встроенных средах реального времени, потому что (по крайней мере, в некоторых системах, с которыми я работал) операционная система НЕ обнаружит это за вас.

Обычно размер стека для нового потока (отличного от основного потока) определяется во время создания потока (т. е.в аргументе функции pthread_create() или тому подобного).Часто эти размеры стека жестко запрограммированы на значения, которые, как известно, являются правильными на момент первоначального написания или тестирования кода.

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

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

Поскольку на вопросы «размера стека» обычно отвечают: «Они непереносимы», давайте предположим, что компилятор, операционная система и процессор — известные величины для этого исследования.Предположим также, что рекурсия не используется, поэтому мы не имеем дело с возможностью сценария «бесконечной рекурсии».

Каковы надежные способы оценки необходимого размера стека для потока?Я бы предпочел офлайновые (статический анализ) и автоматические методы, но любые идеи приветствуются.

Это было полезно?

Решение

Продолжительная оценка

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

Посмотри на это Ссылка для объяснения с иллюстрацией.

Преимущество в том, что это просто. Недостатком является то, что вы не можете быть уверены, что размер вашего стека в конечном итоге не превышает объем подержанного стека во время тестирования.

Статическая оценка

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

Также посмотрите на это вопрос.

Другие советы

Вы можете использовать статический инструмент анализа, например Stackanalyzer, если ваша цель соответствует требованиям.

Если вы хотите потратить значительные деньги, вы можете использовать коммерческий инструмент статического анализа, такой как KLOCWork. Хотя KLOCWork в первую очередь нацелен на обнаружение дефектов программного обеспечения и уязвимостей безопасности. Тем не менее, он также имеет инструмент под названием «kwstackoverflow», который можно использовать для обнаружения переполнения стека в задаче или потоке. Я использую для встроенного проекта, над которым я работаю, и у меня были положительные результаты. Я не думаю, что какой -либо инструмент идеально подходит, но я считаю, что эти коммерческие инструменты очень хороши. Большинство инструментов, которые я столкнулся, борются с указателями функций. Я также знаю, что многие поставщики компилятора, такие как Green Hills, теперь встраивают аналогичную функциональность прямо в своих компиляторах. Это, вероятно, лучшее решение, потому что компилятор обладает интимными знаниями обо всех деталях, необходимых для принятия точных решений о размере стека.

Если у вас есть время, я уверен, что вы можете использовать язык сценариев, чтобы сделать свой собственный инструмент анализа переполнения. Сценарий должен был бы идентифицировать точку входа задачи или потока, генерировать полное дерево вызова функции, а затем вычислить количество пространства стека, которое использует каждая функция. Я подозреваю, что, вероятно, есть бесплатные инструменты, которые могут генерировать полное дерево вызовов функции, чтобы это было облегчить. Если вы знаете, что специфика вашей платформы генерирует пространство стека, которую использует каждая функция, может быть очень простым. Например, первая инструкция сборки функции PowerPC часто является словом хранилища с инструкцией обновления, которая настраивает указатель стека на сумму, необходимую для функции. Вы можете взять размер в байтах прямо из первой инструкции, которая делает определение общего пространства стека, используемого относительно простым.

Все эти типы анализа дадут вам приближение к верхней границе наихудшего случая для использования стека, что именно то, что вы хотите знать. Конечно, эксперты (как те, с которыми я работаю) могут жаловаться, что вы выделяете слишком много места для стека, но это динозавры, которым не волнует хорошее качество программного обеспечения :)

Еще одна возможность, хотя он не рассчитывает использование стека, - это использование блока управления памятью (MMU) вашего процессора (если он имеет один) для обнаружения переполнения стека. Я сделал это на VXWorks 5.4, используя PowerPC. Идея проста, просто положите страницу защищенной памяти записи в самую верхнюю часть вашего стека. Если вы переполните, произойдет выполнение процессора, и вы будете быстро предупредить о проблеме переполнения стека. Конечно, это не говорит вам, сколько вам нужно, чтобы увеличить размер стека, но если у вас хорошее, что у вас есть отладки исключения/основные файлы, вы можете, по крайней мере, выяснить вызывающую последовательность, которая переполнила стек. Затем вы можете использовать эту информацию для правильного увеличения размера стека.

-Джхаус

Не бесплатно, но Покрытие Статический анализ стека.

Статическая (автономная) проверка стека не так сложно, как кажется. Я реализовал его для нашей встроенной IDE (Быстро)-В настоящее время он работает для ARM7 (NXP LPC2XXX), Cortex-M3 (STM32 и NXP LPC17XX), X86 и наших внутренних MIPS ISA, совместимых с мягким явлением FPGA.

По сути, мы используем простой разбор исполняемого кода для определения использования стека каждой функции. Наиболее значительное распределение стека выполняется в начале каждой функции; Просто обязательно посмотрите, как это изменяет с различными уровнями оптимизации, и, если применимо, наборы инструкций/инструкций ARM/THEM и т. Д. Помните, что задачи обычно имеют свои собственные стеки, и ISR часто (но не всегда) имеют отдельную область стека!

После того, как вы используете каждую функцию, довольно легко создать вызовой деревья из Parse и рассчитать максимальное использование для каждой функции. Наша IDE генерирует планировщиков (эффективные тонкие RToes) для вас, поэтому мы точно знаем, какие функции обозначаются как «задачи», а какие-ISR, поэтому мы можем определить наихудшее использование для каждой области стека.

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

Ни один подход не является идеальным, но сочетание оба даст вам довольно хорошую картину того, каким будет использование реального мира.

Как обсуждалось в ответе на этот вопрос, Общая техника - инициализировать стек с известным значением, а затем некоторое время запустить код и посмотреть, где останавливается шаблон.

Это не автономный метод, а над проектом, над которым я работаю, у нас есть команда отладки, которая считывает высокую воду во всех стеках задач в приложении. Это выводит таблицу использования стека для каждой задачи и количество доступного запас. Проверка этих данных после 24 -часового запуска с большим количеством взаимодействия с пользователем дает нам некоторую уверенность в том, что определенные распределения стека «безопасны».

Это работает с использованием хорошо испытанной техники заполнения стеков известным рисунком и предполагая, что единственный способ, которым это можно передать,-это обычное использование стека, хотя, если он записан каким-либо другим средством, переполнение стека является меньше всего ваших забот!

Эту проблему мы пытались решить на встраиваемой системе у меня на работе.Это просто безумие: слишком много кода (как нашего, так и стороннего), чтобы получить достоверный ответ.К счастью, наше устройство было основано на Linux, поэтому мы вернулись к стандартному поведению, предоставляя каждому потоку 2 МБ и позволяя диспетчеру виртуальной памяти оптимизировать использование.

Наша единственная проблема с этим решением заключалась в том, что один из сторонних инструментов выполнил mlock на все пространство памяти (в идеале для повышения производительности).Это привело к тому, что все 2 МБ стека для каждого потока его потоков (75-150 из них) были выгружены.Мы потеряли половину нашей памяти до тех пор, пока не разобрались и не закомментировали оскорбительную строку.

Примечание:Менеджер виртуальной памяти Linux (vmm) распределяет оперативную память частями по 4 КБ.Когда новый поток запрашивает 2 МБ адресного пространства для своего стека, vmm назначает фиктивные страницы памяти всем страницам, кроме самой верхней.Когда стек превращается в фиктивную страницу, ядро ​​обнаруживает ошибку страницы и заменяет фиктивную страницу настоящей (что занимает еще 4 КБ фактической оперативной памяти).Таким образом, стек потока может вырасти до любого необходимого размера (при условии, что он меньше 2 МБ), а vmm будет обеспечивать использование только минимального объема памяти.

Помимо некоторых из уже сделанных предложений, я хотел бы отметить, что часто во встроенных системах вы должны тесно управлять использованием стека, потому что вы должны сохранить размер стека при разумном размере.

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

Нет нет:

void func(myMassiveStruct_t par)
{
  myMassiveStruct_t tmpVar;
}

Да, да:

void func (myMassiveStruct_t *par)
{
  myMassiveStruct_t *tmpVar;
  tmpVar = (myMassiveStruct_t*) malloc (sizeof(myMassicveStruct_t));
}

Кажется довольно очевидным, но часто не так - особенно когда вы не можете использовать Malloc ().

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

Не на 100% уверен, но я думаю, что это также может быть сделано. Если у вас открыт порт JTAG, вы можете подключиться к Trace32 и проверить максимальное использование стека. Хотя для этого вам придется дать первоначальный довольно большой произвольный размер стека.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top