Merge branch 'docs/update-reducing-stack-size-article' into 'master'

feature(docs): update reducing stack size article

See merge request espressif/esp-idf!25234
This commit is contained in:
Alexey Lapshin 2024-05-01 00:10:05 +08:00
commit 0ddfb9caf1
4 changed files with 87 additions and 21 deletions

View File

@ -447,6 +447,8 @@ Consult :doc:`Heap Memory Debugging <../api-reference/system/heap_debug>` docume
.. only:: SOC_ASSIST_DEBUG_SUPPORTED
.. _Hardware-Stack-Guard:
Hardware Stack Guard
""""""""""""""""""""

View File

@ -45,24 +45,54 @@ To minimize static memory use:
.. _optimize-stack-sizes:
Reducing Stack Sizes
--------------------
Determining Stack Size
----------------------
In FreeRTOS, task stacks are usually allocated from the heap. The stack size for each task is fixed and passed as an argument to :cpp:func:`xTaskCreate`. Each task can use up to its allocated stack size, but using more than this will cause an otherwise valid program to crash, with a stack overflow or heap corruption.
Therefore, determining the optimum sizes of each task stack, minimizing the required size of each task stack, and minimizing the number of task stacks as whole, can all substantially reduce RAM usage.
To determine the optimum size for a particular task stack, users can consider the following methods:
Configuration Options for Stack Overflow Detection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- At runtime, call the function :cpp:func:`uxTaskGetStackHighWaterMark` with the handle of any task where you think there is unused stack memory. This function returns the minimum lifetime free stack memory in bytes.
.. only:: SOC_ASSIST_DEBUG_SUPPORTED
Hardware Stack Guard
~~~~~~~~~~~~~~~~~~~~
The Hardware Stack Guard is a reliable method for detecting stack overflow. This method uses the hardware's Debug Assistant module to monitor the CPU's stack pointer register. A panic is immediately triggered if the stack pointer register goes beyond the bounds of the current stack (see :ref:`Hardware-Stack-Guard` for more details). The Hardware Stack Guard can be enabled via the :ref:`CONFIG_ESP_SYSTEM_HW_STACK_GUARD` option.
End of Stack Watchpoint
~~~~~~~~~~~~~~~~~~~~~~~
The End of Stack Watchpoint feature places a CPU watchpoint at the end of the current stack. If that word is overwritten (such as in a stack overflow), a panic is triggered immediately. End of Stack Watchpoints can be enabled via the :ref:`CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` option, but can only be used if debugger watchpoints are not already being used.
Stack Canary Bytes
~~~~~~~~~~~~~~~~~~
The Stack Canary Bytes feature adds a set of magic bytes at the end of each task's stack, and checks if those magic bytes have changed on every context switch. If those magic bytes are overwritten, a panic is triggered. Stack Canary Bytes can be enabled via the :ref:`CONFIG_FREERTOS_CHECK_STACKOVERFLOW` option.
.. note::
When using the End of Stack Watchpoint or Stack Canary Bytes, it is possible that a stack pointer skips over the watchpoint or canary bytes on a stack overflow and corrupts another region of RAM instead. Thus, these methods cannot detect all stack overflows.
.. only:: SOC_ASSIST_DEBUG_SUPPORTED
Recommended and default option is :ref:`CONFIG_ESP_SYSTEM_HW_STACK_GUARD` which avoids this disadvantage.
Run-time Methods to Determine Stack Size
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- The :cpp:func:`uxTaskGetStackHighWaterMark` returns the minimum free stack memory of a task throughout the task's lifetime, which gives a good indication of how much stack memory is left unused by a task.
- The easiest time to call :cpp:func:`uxTaskGetStackHighWaterMark` is from the task itself: call ``uxTaskGetStackHighWaterMark(NULL)`` to get the current task's high water mark after the time that the task has achieved its peak stack usage, i.e., if there is a main loop, execute the main loop a number of times with all possible states, and then call :cpp:func:`uxTaskGetStackHighWaterMark`.
- Often, it is possible to subtract almost the entire value returned here from the total stack size of a task, but allow some safety margin to account for unexpected small increases in stack usage at runtime.
- Call :cpp:func:`uxTaskGetSystemState` at runtime to get a summary of all tasks in the system. This includes their individual stack high watermark values.
- When debugger watchpoints are not being used, users can set the :ref:`CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` option. This will cause one of the watchpoints to watch the last word of the task's stack. If that word is overwritten (such as in a stack overflow), a panic is triggered immediately. This is slightly more reliable than the default :ref:`CONFIG_FREERTOS_CHECK_STACKOVERFLOW` option of ``Check using canary bytes``, because the panic happens immediately, rather than on the next RTOS context switch. Neither option is perfect. In some cases, it is possible that the stack pointer skips the watchpoint or canary bytes and corrupts another region of RAM instead.
- Call :cpp:func:`uxTaskGetSystemState` to get a summary of all tasks in the system. This includes their individual stack high watermark values.
To reduce the required size of a particular task stack, users can consider the following methods:
Reducing Stack Sizes
--------------------
- Avoid stack heavy functions. String formatting functions (like ``printf()``) are particularly heavy users of the stack, so any task which does not ever call these can usually have its stack size reduced.
@ -71,12 +101,13 @@ To reduce the required size of a particular task stack, users can consider the f
- Avoid allocating large variables on the stack. In C, any large structures or arrays allocated as an automatic variable (i.e., default scope of a C declaration) uses space on the stack. To minimize the sizes of these, allocate them statically and/or see if you can save memory by dynamically allocating them from the heap only when they are needed.
- Avoid deep recursive function calls. Individual recursive function calls do not always add a lot of stack usage each time they are called, but if each function includes large stack-based variables then the overhead can get quite high.
To reduce the total number of tasks, users can consider the following method:
Reducing Task Count
^^^^^^^^^^^^^^^^^^^
- Combine tasks. If a particular task is never created, the task's stack is never allocated, thus reducing RAM usage significantly. Unnecessary tasks can typically be removed if those tasks can be combined with another task. In an application, tasks can typically be combined or removed if:
Combine tasks. If a particular task is never created, the task's stack is never allocated, thus reducing RAM usage significantly. Unnecessary tasks can typically be removed if those tasks can be combined with another task. In an application, tasks can typically be combined or removed if:
- The work done by the tasks can be structured into multiple functions that are called sequentially.
- The work done by the tasks can be structured into smaller jobs that are serialized (via a FreeRTOS queue or similar) for execution by a worker task.
- The work done by the tasks can be structured into multiple functions that are called sequentially.
- The work done by the tasks can be structured into smaller jobs that are serialized (via a FreeRTOS queue or similar) for execution by a worker task.
Internal Task Stack Sizes
^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -447,6 +447,8 @@ ESP-IDF 堆的实现包含许多运行时的堆结构检查,可以在 menuconf
.. only:: SOC_ASSIST_DEBUG_SUPPORTED
.. _Hardware-Stack-Guard:
硬件堆栈保护
""""""""""""""""""""

View File

@ -45,24 +45,54 @@ ESP-IDF 包含一系列堆 API可以在运行时测量空闲堆内存
.. _optimize-stack-sizes:
栈内存大小优化
确定栈内存大小
--------------------
在 FreeRTOS 操作系统中,任务栈通常从堆中分配。每个任务的栈大小固定,且会作为参数传递给 :cpp:func:`xTaskCreate`。每个任务可用的栈内存不得超过为其分配的栈内存大小,否则将导致栈内存溢出或堆内存损坏,使原本可用的程序崩溃。
因此,确定每个任务栈内存的最佳大小、最小化每个任务栈内存大小、以及最小化任务栈内存的整体数量,都可以大幅减少 RAM 的使用。
要确定特定任务栈内存的最佳大小,请执行以下操作:
栈溢出检测的配置选项
^^^^^^^^^^^^^^^^^^^^
- 程序运行时,如你认为某任务有未使用的栈内存,可通过其任务句柄调用 :cpp:func:`uxTaskGetStackHighWaterMark`。该函数将以字节为单位,返回任务中生命周期最短的空闲栈内存。
.. only:: SOC_ASSIST_DEBUG_SUPPORTED
硬件栈保护
~~~~~~~~~~~~
硬件栈保护是一种检测栈溢出的可靠方法,通过硬件的辅助调试模块来监视 CPU 的栈指针寄存器。如果栈指针寄存器超出了当前栈的边界,则立即触发紧急情况提示(更多详细信息,请参阅 :ref:`Hardware-Stack-Guard`)。可以通过 :ref:`CONFIG_ESP_SYSTEM_HW_STACK_GUARD` 选项启用硬件栈保护。
栈末尾监视点
~~~~~~~~~~~~~~
栈末尾监视点将 CPU 监视点放置在当前栈的末尾。如果该字被覆盖(例如栈溢出),则会立即触发紧急情况提示。在未使用调试器的监视点时,可以设置 :ref:`CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` 选项,启用栈末尾监视点功能。
栈金丝雀字节
~~~~~~~~~~~~~~
栈金丝雀字节功能在每个任务的栈末尾添加一组魔术字节,并在每次上下文切换时检查这些字节是否已更改。如果这些魔术字节被覆盖,则会触发紧急情况提示。可以通过 :ref:`CONFIG_FREERTOS_CHECK_STACKOVERFLOW` 选项启用栈金丝雀字节功能。
.. note::
使用栈末尾监视点或栈金丝雀字节时,栈指针可能在栈溢出时跳过监视点或金丝雀字节,损坏 RAM 的其他区域。因此,上述方法并不能检测所有的栈溢出。
.. only:: SOC_ASSIST_DEBUG_SUPPORTED
推荐启用默认选项 :ref:`CONFIG_ESP_SYSTEM_HW_STACK_GUARD`,避免这个缺点。
任务运行时确定栈内存大小的方法
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 调用 :cpp:func:`uxTaskGetStackHighWaterMark` 会返回任务整个生命周期中空闲栈内存的最小值,从而较好地显示出任务未使用的栈内存量。
- 从任务本身内部调用 :cpp:func:`uxTaskGetStackHighWaterMark` 是调用该函数最容易的方式:在任务达到其栈内存使用峰值后,调用 ``uxTaskGetStackHighWaterMark(NULL)`` 获取当前任务的高水位标记,换言之,如果有主循环,请多次执行主循环来覆盖各种状态,随后调用 :cpp:func:`uxTaskGetStackHighWaterMark`
- 通常可以用任务的栈内存总大小减去调用 :cpp:func:`uxTaskGetStackHighWaterMark` 的返回值,计算任务实际使用的栈内存大小,但应留出一定的安全余量,应对运行时栈内存使用量的小幅意外增长。
- 程序运行时,调用 :cpp:func:`uxTaskGetSystemState` 获取系统中所有任务的摘要,包括各栈内存的高水位标记值。
- 在未使用调试器的监视点时,可以设置 :ref:`CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` 选项。启用此选项时,系统会使用一个观察点,监视每个任务栈的最后一个字节。如果有新的数据覆盖了该字节(例如发生栈溢出),将立即触发 panic。相比默认 :ref:`CONFIG_FREERTOS_CHECK_STACKOVERFLOW` 选项的 ``Check using canary bytes``,这种方式更可靠,因其能够立即触发 panic而不是在下一次 RTOS 上下文切换时触发。然而,两种选项都存在缺点,有时栈指针可能会跳过监视点或 canary 字节,损坏 RAM 的其他区域。
- 调用 :cpp:func:`uxTaskGetSystemState` 来获取系统中所有任务的摘要,包括各栈内存的高水位标记值。
要减少特定任务栈内存大小,请执行以下操作:
减少栈内存大小
--------------
- 避免占用过多栈内存的函数。字符串格式化函数(如 ``printf()``)会使用大量栈内存,如果任务不调用这类函数,通常可以减小其占用的栈内存。
@ -71,12 +101,13 @@ ESP-IDF 包含一系列堆 API可以在运行时测量空闲堆内存
- 避免在栈上分配大型变量。在 C 语言声明的默认作用域中,任何分配为自动变量的大型结构体或数组都会占用栈内存。要优化这些变量占用的栈内存大小,可以使用静态分配,或仅在需要时从堆中动态分配。
- 避免调用深度递归函数。尽管调用单个递归函数并不一定会占用大量栈内存,但若每个函数都包含大量基于栈的变量,那么调用这些函数的开销将会很高。
要减少任务的整体数量,请执行以下操作:
减少任务数量
^^^^^^^^^^^^
- 合并任务。如果从未创建某个特定任务,就不会分配该任务的栈内存,从而极大减少 RAM 使用。如果某些任务可以与另一个任务合并,通常可以将不必要的任务删除。在应用程序中,如果满足以下条件,通常可以合并或删除任务:
合并任务。如果从未创建某个特定任务,就不会分配该任务的栈内存,从而极大减少 RAM 使用。如果某些任务可以与另一个任务合并,通常可以将不必要的任务删除。在应用程序中,如果满足以下条件,通常可以合并或删除任务:
- 任务所执行的内容可以按顺序分解为多个函数调用。
- 任务所执行的内容可以分解为较小的工作,这些工作可以通过 FreeRTOS 队列或类似机制串行化,并由工作任务执行。
- 任务所执行的内容可以按顺序分解为多个函数调用。
- 任务所执行的内容可以分解为较小的工作,这些工作可以通过 FreeRTOS 队列或类似机制串行化,并由工作任务执行。
内部任务栈内存大小
^^^^^^^^^^^^^^^^^^^^