Оценка размера стека - PullRequest
29 голосов
/ 18 ноября 2009

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

Обычно размер стека для нового потока (отличного от основного потока) определяется в момент создания потока (т. Е. В аргументе pthread_create () или тому подобного). Часто эти размеры стеков жестко закодированы в значения, которые, как известно, хороши на момент написания или тестирования кода.

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

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

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

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

Ответы [ 10 ]

17 голосов
/ 18 ноября 2009

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

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

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

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

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

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

Также взгляните на этот вопрос.

12 голосов
/ 18 ноября 2009

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

5 голосов
/ 19 ноября 2009

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

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

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

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

-djhaus

4 голосов
/ 18 ноября 2009

Не бесплатно, но Coverity выполняет статический анализ стека.

3 голосов
/ 20 ноября 2009

Статическая (автономная) проверка стека не так сложна, как кажется. Я реализовал это для нашей встроенной IDE ( RapidiTTy ) & mdash; в настоящее время он работает для ARM7 (NXP LPC2xxx), Cortex-M3 (STM32 и NXP LPC17xx), x86 и нашего собственного MIPS ISA-совместимого программного ядра FPGA.

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

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

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

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

2 голосов
/ 18 ноября 2009

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

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

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

2 голосов
/ 18 ноября 2009

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

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

2 голосов
/ 18 ноября 2009

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

0 голосов
/ 19 января 2011

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

0 голосов
/ 20 ноября 2009

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

В некотором смысле, использование стекового пространства немного похоже на распределение памяти, но без (простого) способа определить, было ли ваше выделение выполнено успешно, поэтому неконтролируемое использование стека приведет к вечной борьбе, чтобы выяснить, почему ваша система снова падает. Так, например, если ваша система выделяет память для локальных переменных из стека, выделите эту память с помощью 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 ().

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...