Update according to review

This commit is contained in:
renpeiying
2025-12-19 16:47:52 +08:00
parent 7044dfc1a9
commit 4f30d217dd
4 changed files with 105 additions and 23 deletions
+3 -3
View File
@@ -39,7 +39,7 @@ Built-in implementations of standard I/O can be selected using several Kconfig o
:SOC_USB_SERIAL_JTAG_SUPPORTED: - :ref:`CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG<CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG>` — Enables USB Serial/JTAG for standard I/O. See :doc:`usb-serial-jtag-console` for details about hardware connections required.
- :ref:`CONFIG_ESP_CONSOLE_NONE<CONFIG_ESP_CONSOLE_NONE>` — Disables standard I/O. If this option is selected, ``stdin``, ``stdout``, and ``stderr`` will be mapped to ``/dev/null`` and won't produce any output or generate any input.
Enabling one of these option will cause the corresponding VFS driver to be built into the application and used to open ``stdin``, ``stdout``, and ``stderr`` streams. Data written to ``stdout`` and ``stderr`` will be sent over the selected interface, and input from the selected interface will be available on ``stdin``.
Enabling one of these options will cause the corresponding VFS driver to be built into the application and used to open ``stdin``, ``stdout``, and ``stderr`` streams. Data written to ``stdout`` and ``stderr`` will be sent over the selected interface, and input from the selected interface will be available on ``stdin``.
.. only:: SOC_USB_SERIAL_JTAG_SUPPORTED
@@ -98,7 +98,7 @@ When the interrupt-driven driver is installed, it is also possible to enable/dis
USB Serial/JTAG
^^^^^^^^^^^^^^^
Similar to UART, the VFS driver for USB Serial/JTAG defaults to a simplified implementation: writes are blocking (busy-wait until all the data has been sent) and reads are non-blocking, returning only the data present in the FIFO. This behavior can be changed to use the interrupt driven, blocking read and write functions of USB Serial/JTAG driver using a call to the :cpp:func:`usb_serial_jtag_vfs_use_nonblocking` function. Note that the USB Serial/JTAG driver has to be initialized using :cpp:func:`usb_serial_jtag_driver_install` beforehand. It is also possible to revert to the basic non-blocking functions using a call to :cpp:func:`usb_serial_jtag_vfs_use_nonblocking`.
Similar to UART, the VFS driver for USB Serial/JTAG defaults to a simplified implementation: writes are blocking (busy-wait until all the data has been sent) and reads are non-blocking, returning only the data present in the FIFO. This behavior can be changed to use the interrupt-driven, blocking read and write functions of the USB Serial/JTAG driver by calling :cpp:func:`usb_serial_jtag_vfs_use_nonblocking`. Note that the USB Serial/JTAG driver has to be initialized using :cpp:func:`usb_serial_jtag_driver_install` beforehand. It is also possible to revert to the basic non-blocking functions using a call to :cpp:func:`usb_serial_jtag_vfs_use_nonblocking`.
When the interrupt-driven driver is installed, it is also possible to enable/disable non-blocking behavior using ``fcntl`` function with ``O_NONBLOCK`` flag.
@@ -116,7 +116,7 @@ VFS drivers provide an optional newline conversion feature for input and output.
Applications can configure this behavior globally using the following Kconfig options:
- For output
- For output
- :ref:`CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF<CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF>`
- :ref:`CONFIG_LIBC_STDOUT_LINE_ENDING_CR<CONFIG_LIBC_STDOUT_LINE_ENDING_CR>`
- :ref:`CONFIG_LIBC_STDOUT_LINE_ENDING_LF<CONFIG_LIBC_STDOUT_LINE_ENDING_LF>`
@@ -9,14 +9,16 @@ Default LibC changed from Newlib to PicolibC
Since ESP-IDF v6.0, the default LibC used in builds has switched to the PicolibC implementation.
.. note::
PicolibC is a Newlib fork with a rewritten stdio implementation whose goal is to consume less memory.
PicolibC is a Newlib fork with a rewritten stdio implementation whose goal is to consume less memory.
In most cases, no application behavior changes are expected, except for reduced binary size and less stack consumption on I/O operations.
.. warning::
**Breaking change:** It is not possible to redefine stdin, stdout, and stderr for specific tasks as was possible with Newlib. These streams are global and shared between all tasks. This is POSIX-standardized behavior.
:ref:`CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY`, which is enabled by default, provides limited compatibility with Newlib by providing thread-local copies of global stdin, stdout, stderr, and the getreent() implementation. If a library built with Newlib headers operates with "internal" fields of "struct reent", there may be task stack corruption. Note that manipulating "struct reent" fields is expected only by the Newlib library itself.
**Breaking change:** It is not possible to redefine stdin, stdout, and stderr for specific tasks as was possible with Newlib. These streams are global and shared between all tasks. This is POSIX-standardized behavior.
:ref:`CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY`, which is enabled by default, provides limited compatibility with Newlib by providing thread-local copies of ``global stdin``, ``stdout``, ``stderr``, and the ``getreent()`` implementation. If a library built with Newlib headers operates with "internal" fields of "struct reent", there may be task stack corruption. Note that manipulating ``struct reent`` fields is expected only by the Newlib library itself.
If you are not linking against external libraries built against Newlib headers, you may disable :ref:`CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY` to save a small amount of memory.
@@ -42,7 +44,6 @@ The test code was compiled with both Newlib and Picolibc, and the results were c
.. list-table:: Comparison of Newlib vs Picolibc
:header-rows: 1
:widths: 30 20 20 20
:stub-columns: 1
* - Metric
- Newlib
@@ -66,7 +67,8 @@ The test code was compiled with both Newlib and Picolibc, and the results were c
- 0.59%
.. note::
Even when :ref:`CONFIG_LIBC_NEWLIB_NANO_FORMAT` is enabled, which disables float formatting, applications with Picolibc are still smaller by 6% (224,592 vs 239,888 bytes).
Even when :ref:`CONFIG_LIBC_NEWLIB_NANO_FORMAT` is enabled, which disables float formatting, applications with Picolibc are still smaller by 6% (224,592 vs 239,888 bytes).
Xtensa
------
@@ -370,9 +372,9 @@ The LP-Core will now wake up the main CPU when it encounters an exception during
Heap
----
In previous versions of ESP-IDF, the capability `MALLOC_CAP_EXEC` would be available regardless of the memory protection configuration state. This implied that a call to e.g., :cpp:func:`heap_caps_malloc` with `MALLOC_CAP_EXEC` would return NULL when CONFIG_ESP_SYSTEM_MEMPROT_FEATURE or CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT are enabled.
In previous versions of ESP-IDF, the capability ``MALLOC_CAP_EXEC`` would be available regardless of the memory protection configuration state. This implied that a call to e.g., :cpp:func:`heap_caps_malloc` with ``MALLOC_CAP_EXEC`` would return NULL when ``CONFIG_ESP_SYSTEM_MEMPROT_FEATURE`` or ``CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT`` are enabled.
Since ESP-IDF v6.0, the definition of `MALLOC_CAP_EXEC` is conditional, meaning that if CONFIG_ESP_SYSTEM_MEMPROT is enabled, `MALLOC_CAP_EXEC` will not be defined. Therefore, using it will generate a compile time error.
Since ESP-IDF v6.0, the definition of ``MALLOC_CAP_EXEC`` is conditional, meaning that if CONFIG_ESP_SYSTEM_MEMPROT is enabled, ``MALLOC_CAP_EXEC`` will not be defined. Therefore, using it will generate a compile time error.
``esp_common``
--------------
+24 -11
View File
@@ -7,13 +7,13 @@ ESP-IDF 提供了 C 标准输入输出功能,如 ``stdin``、``stdout`` 和 ``
类似 POSIX 系统,这些流是文件描述符的缓冲封装:
- ``stdin`` 用于从用户读取输入的缓冲流,封装文件描述符 ``STDIN_FILENO`` (0)。
- ``stdout`` 用于向用户写入输出的缓冲流,封装文件描述符 ``STDOUT_FILENO`` (1)。
- ``stderr`` 用于向用户写入错误信息的缓冲流,封装文件描述符 ``STDERR_FILENO`` (2)。
- ``stdin`` 用于读取用户输入的缓冲流,封装文件描述符 ``STDIN_FILENO`` (0)。
- ``stdout`` 用于向用户写入输出的缓冲流,封装文件描述符 ``STDOUT_FILENO`` (1)。
- ``stderr`` 用于向用户写入错误信息的缓冲流,封装文件描述符 ``STDERR_FILENO`` (2)。
在 ESP-IDF 中, ``stdout````stderr`` 没有实际区别,因为两者都发送到相同的物理接口。大多数应用程序通常只使用 ``stdout``。例如,对于 ESP-IDF 的日志函数,无论日志等级如何,始终写入 ``stdout``
底层的 stdin、stdout 和 stderr 文件描述符是基于 :doc:`VFS 驱动 <../api-reference/storage/vfs>` 实现的。
底层的 `stdin``stdout``stderr` 文件描述符是基于 :doc:`VFS 驱动 <../api-reference/storage/vfs>` 实现的。
在 {IDF_TARGET_NAME} 上,ESP-IDF 提供了用于以下 I/O 接口的 VFS 驱动实现:
@@ -33,8 +33,8 @@ ESP-IDF 提供了 C 标准输入输出功能,如 ``stdin``、``stdout`` 和 ``
.. list::
- :ref:`CONFIG_ESP_CONSOLE_UART_DEFAULT<CONFIG_ESP_CONSOLE_UART_DEFAULT>` — 启用 UART 用于标准 I/O,保持默认选项(引脚号、波特率)
- :ref:`CONFIG_ESP_CONSOLE_UART_CUSTOM<CONFIG_ESP_CONSOLE_UART_CUSTOM>` — 启用 UART 用于标准 I/O,通过 Kconfig 配置 TX/RX 脚号和波特率
- :ref:`CONFIG_ESP_CONSOLE_UART_DEFAULT<CONFIG_ESP_CONSOLE_UART_DEFAULT>` — 启用 UART 用于标准 I/O,保持默认选项项(管脚号、波特率)
- :ref:`CONFIG_ESP_CONSOLE_UART_CUSTOM<CONFIG_ESP_CONSOLE_UART_CUSTOM>` — 启用 UART 用于标准 I/O,通过 Kconfig 配置 TX/RX 脚号和波特率
:esp32s2 or esp32s3: - :ref:`CONFIG_ESP_CONSOLE_USB_CDC<CONFIG_ESP_CONSOLE_USB_CDC>` — 启用 USB CDC(使用 USB_OTG 外设)用于标准 I/O。硬件连接要求请参见 :doc:`usb-otg-console`
:SOC_USB_SERIAL_JTAG_SUPPORTED: - :ref:`CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG<CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG>` — 启用 USB Serial/JTAG 用于标准 I/O。硬件连接要求请参见 :doc:`usb-serial-jtag-console`
- :ref:`CONFIG_ESP_CONSOLE_NONE<CONFIG_ESP_CONSOLE_NONE>` — 禁用标准 I/O。选择此选项时, ``stdin````stdout````stderr`` 将映射到 ``/dev/null``,不会产生输出或输入。
@@ -60,13 +60,26 @@ ESP-IDF 提供了 C 标准输入输出功能,如 ``stdin``、``stdout`` 和 ``
标准流与 FreeRTOS 任务
-----------------------
ESP-IDF 根据 :ref:`CONFIG_LIBC` 中所选择的 LibC 实现,提供了两种不同的标准 I/O 流实现方式。 ``stdin````stdout````stderr`` 流在这两种实现下的行为存在差异,尤其体现在 FreeRTOS 任务之间的共享方式上。
两种实现的共同点是,每个流 (``stdin````stdout````stderr``) 都具有一个互斥锁 (mutex) 用于保护,防止多个任务并发访问同一个流。例如,当两个任务同时向 ``stdout`` 写数据时,该互斥锁可以确保各个任务的输出不会相互混杂。
Newlib
^^^^^^
在 ESP-IDF 中,为节省 RAM ``stdin````stdout````stderr````FILE`` 对象在所有 FreeRTOS 任务间共享,但每个任务具有唯一的指针。这就说明:
- 可以为某个任务单独更改 ``stdin````stdout````stderr`` 而不影响其他任务。例如 ``stdin = fopen("/dev/uart/1", "r")``
- 若要更改新任务的默认 ``stdin``, ``stdout``, ``stderr`` 流,请在创建任务前修改 ``_GLOBAL_REENT->_stdin`` (``_stdout``, ``_stderr``)。
- 使用 ``fclose`` 关闭默认 ``stdin````stdout````stderr`` 时也会关闭 ``FILE`` 流对象,并影响所有其他任务。
每个流 (``stdin``, ``stdout``, ``stderr``) 都有一个互斥锁用于保护,防止多个任务并发访问。例如,如果两个任务同时写入 ``stdout``,互斥锁将确保各任务的输出不会混在一起。
Picolibc
^^^^^^^^
根据 POSIX 标准,默认的 ``stdin````stdout````stderr`` 流是全局的,在所有 FreeRTOS 任务之间共享。这意味着:
- 修改 ``stdin````stdout````stderr`` 会影响所有其他任务,无法为特定任务单独更改标准 I/O 流。
- 对于需要线程本地 (thread-local) 的流,应在应用程序代码中通过打开文件流并在任务中使用它来实现,例如 ``fscanf()````fprintf()`` 等。
阻塞与非阻塞 I/O
-----------------
@@ -78,7 +91,7 @@ UART
使用 UART 驱动的应用程序,可通过调用 :cpp:func:`uart_vfs_dev_use_driver` 来使用该驱动提供的中断驱动型阻塞读写函数。也可通过 :cpp:func:`uart_vfs_dev_use_nonblocking` 回退到基本的非阻塞函数。
安装中断驱动后,也可使用带有 ``O_NONBLOCK`` flag ``fcntl`` 函数来启用/禁用非阻塞行为。
安装中断驱动后,也可使用带有 ``O_NONBLOCK`` 标志``fcntl`` 函数来启用/禁用非阻塞行为。
.. only:: SOC_USB_SERIAL_JTAG_SUPPORTED
@@ -87,14 +100,14 @@ UART
与 UART 类似,USB Serial/JTAG VFS 驱动默认使用简化实现:写操作阻塞(忙等待直到所有数据发送完成),读操作非阻塞,只返回 FIFO 中的数据。可通过调用 :cpp:func:`usb_serial_jtag_vfs_use_nonblocking` 使用中断驱动阻塞读写函数,将此行为更改为使用 USB Serial/JTAG 驱动程序的中断驱动和阻塞式读写函数。注意,USB Serial/JTAG 驱动需先使用 :cpp:func:`usb_serial_jtag_driver_install` 初始化。也可通过相应函数回退到基本非阻塞函数。
安装中断驱动后,也可使用带有 ``O_NONBLOCK`` flag ``fcntl`` 函数启用/禁用非阻塞行为。
安装中断驱动后,也可使用带有 ``O_NONBLOCK`` 标志``fcntl`` 函数启用/禁用非阻塞行为。
.. only:: esp32s2 or esp32s3
USB CDC(使用 USB_OTG 外设)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
USB CDC VFS 驱动默认提供阻塞 I/O。带有 ``O_NONBLOCK`` flag ``fcntl`` 函数启用非阻塞行为。
USB CDC VFS 驱动默认提供阻塞 I/O。带有 ``O_NONBLOCK`` 标志``fcntl`` 函数启用非阻塞行为。
换行符转换
------------
@@ -124,7 +137,7 @@ UART
缓冲
-----
默认情况下,标准 I/O 流是按行缓冲的。这意味着写入流的数据在写入换行符或缓冲区满之前不会发送到底层设备。例如,调用 ``printf("Hello")`` 时,文本你调用 ``printf("\n")``缓冲区因其他输出填满,文本不会立即发送到 UART
默认情况下,标准 I/O 流是按行缓冲的。这意味着写入流的数据在写入换行符或缓冲区满之前不会发送到底层设备。例如,调用 ``printf("Hello")`` 时,文本不会立即发送到 UART;只有当你调用 ``printf("\n")``者由于其他打印操作导致 stream 缓冲被填满,文本才会真正发送
可使用 ``setvbuf()`` 函数更改此行为。例如,禁用 ``stdout`` 缓冲:
@@ -3,6 +3,73 @@
:link_to_translation:`en:[English]`
默认 LibC 已从 Newlib 更改为 PicolibC
--------------------------------------
自 ESP-IDF v6.0 起,构建时默认使用的 LibC 已更改为 PicolibC。
.. note::
PicolibC 是 Newlib 的一个分支,重新实现了 stdio,其目标是减少内存消耗。
虽然二进制文件大小减小,且 I/O 操作时的栈消耗会降低,但在大多数情况下,应用程序的行为不会发生变化。
.. warning::
**重大变更:** 无法像使用 Newlib 时那样,为特定任务重新定义 ``stdin````stdout````stderr``。这些流是全局的,并在所有任务之间共享。这是符合 POSIX 标准的行为。
:ref:`CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY` 是默认启用的,通过提供全局 ``stdin````stdout````stderr`` 的线程本地副本以及 ``getreent()`` 实现,为 Newlib 提供有限兼容性。如果使用 Newlib 头文件构建的库操作 ``struct reent`` 的内部字段,可能会导致任务栈损坏。请注意,只有 Newlib 库本身才应该操作 ``struct reent`` 字段。
如果你没有链接使用 Newlib 头文件构建的外部库,可以禁用 :ref:`CONFIG_LIBC_PICOLIBC_NEWLIB_COMPATIBILITY` 来节省少量内存。
Newlib 仍然在 ESP-IDF 工具链中维护。如需切换使用 Newlib,可在 menuconfig 中通过 :ref:`CONFIG_LIBC` 选项选择 LIBC_NEWLIB。
Newlib 与 Picolibc 对比
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
以下示例说明切换到 Picolibc 原因:
.. code-block:: c
FILE *f = fopen("/dev/console", "w");
for (int i = 0; i < 10; i++)
{
fprintf(f, "hello world %s\n", "🤖");
fprintf(f, "%.1000f\n", 3.141592653589793);
fprintf(f, "%1000d\n", 42);
}
该示例代码使用 Newlib 和 Picolibc 分别编译,并在 ESP32-C3 上对比结果:
.. list-table:: Newlib 与 Picolibc 对比
:header-rows: 1
:widths: 30 20 20 20
* - 指标
- Newlib
- Picolibc
- 差异
* - 二进制大小(字节)
- 280,128
- 224,656
- 19.80%
* - 栈使用(字节)
- 1,748
- 802
- 54.12%
* - 堆使用(字节)
- 1,652
- 376
- 77.24%
* - 性能(CPU 周期)
- 278,232,026
- 279,823,800
- 0.59%
.. note::
即使启用 :ref:`CONFIG_LIBC_NEWLIB_NANO_FORMAT` 选项(禁用浮点格式化),使用 Picolibc 的应用程序仍比 Newlib 小 6%224,592 v.s. 239,888 字节)。
Xtensa
------
@@ -305,9 +372,9 @@ LP-Core 在深度睡眠期间遇到异常时,将唤醒主 CPU。此功能默
----
在 ESP-IDF 早期版本中,无论是否启用内存保护配置, ``MALLOC_CAP_EXEC``始终可用。这可能引起误解,例如,当启用了 ``CONFIG_ESP_SYSTEM_MEMPROT_FEATURE````CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT`` 时,如果调用 :cpp:func:`heap_caps_malloc` 并指定 `MALLOC_CAP_EXEC`,该函数会返回 ``NULL``
在 ESP-IDF 版本中,无论是否启用内存保护配置, ``MALLOC_CAP_EXEC``可用。这意味着,当启用了 ``CONFIG_ESP_SYSTEM_MEMPROT_FEATURE````CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT`` 时,如果调用 :cpp:func:`heap_caps_malloc` 并指定 ``MALLOC_CAP_EXEC``,该函数会返回 ``NULL``
自 ESP-IDF v6.0 起, `MALLOC_CAP_EXEC` 的定义改为条件性定义。也就是说,当启用 ``CONFIG_ESP_SYSTEM_MEMPROT`` 时, `MALLOC_CAP_EXEC` 将不会被定义。因此,如果代码中仍然使用 `MALLOC_CAP_EXEC`,会在编译阶段报错。
自 ESP-IDF v6.0 起, ``MALLOC_CAP_EXEC`` 的定义改为条件性定义。也就是说,当启用 ``CONFIG_ESP_SYSTEM_MEMPROT`` 时, ``MALLOC_CAP_EXEC`` 将不会被定义。因此,如果代码中仍然使用 ``MALLOC_CAP_EXEC``,会在编译阶段报错。
``esp_common``
----------------