docs: update wifi and nvs cn trans

This commit is contained in:
intern 2022-01-24 14:07:30 +08:00
parent 5c72bbcb3d
commit c7fd71a893
4 changed files with 2466 additions and 1817 deletions

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ Introduction
Non-volatile storage (NVS) library is designed to store key-value pairs in flash. This section introduces some concepts used by NVS.
Underlying storage
^^^^^^^^^^^^^^^^^^
@ -378,5 +377,3 @@ API Reference
.. include-build-file:: inc/nvs_flash.inc
.. include-build-file:: inc/nvs.inc

File diff suppressed because it is too large Load Diff

View File

@ -11,9 +11,9 @@
底层存储
^^^^^^^^^^^^^^^^^^
NVS 通过调用 ``spi_flash_{read|write|erase}`` API 对主 flash 的部分空间进行读、写、擦除操作,包括 ``data`` 类型和 ``nvs`` 子类型的所有分区。应用程序可调用 ``nvs_open`` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 ``nvs_open_from_part`` API 选择使用指定名称的任意分区。
NVS 库通过调用 :ref:`esp_partition <flash-partition-apis>` API 使用主 flash 的部分空间,即类型为 ``data`` 且子类型为 ``nvs`` 的所有分区。应用程序可调用 :cpp:func:`nvs_open` API 选择使用带有 ``nvs`` 标签的分区,也可以通过调用 :cpp:func:`nvs_open_from_partition` API 选择使用指定名称的任意分区。
NVS 库后续版本可能会增加其他存储器后端,实现将数据保存至其他 flash 芯片SPI 或 I2C 接口、RTC 或 FRAM 中。
NVS 库后续版本可能会增加其他存储器后端,将数据保存至其他 flash 芯片SPI 或 I2C 接口、RTC 或 FRAM 中。
.. note:: 如果 NVS 分区被截断(例如,更改分区表布局时),则应擦除分区内容。可以使用 ESP-IDF 构建系统中的 ``idf.py erase_flash`` 命令擦除 flash 上的所有内容。
@ -23,15 +23,15 @@ NVS 库后续版本可能会增加其他存储器后端,实现将数据保存
键值对
^^^^^^^^^^^^^^^
NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最大键长为 15 个字符,值可以为以下几种类型:
NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的最大键长为 15 个字符。值可以为以下几种类型:
- 整数型:``uint8_t````int8_t````uint16_t````int16_t````uint32_t````int32_t````uint64_t````int64_t``
- 以 ``\0`` 结尾的字符串;
- 以 0 结尾的字符串;
- 可变长度的二进制数据 (BLOB)
.. note::
字符串值当前上限为 4000 字节其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小减去 4000 字节的 97.6%,以较低值为准。
字符串值当前上限为 4000 字节其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小的 97.6% 减去 4000 字节,以较低值为准。
后续可能会增加对 ``float````double`` 等其他类型数据的支持。
@ -46,19 +46,139 @@ NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最
命名空间
^^^^^^^^^^
为了减少不同组件之间键名的潜在冲突NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,即最多可占 15 个字符。命名空间的名称在调用 ``nvs_open````nvs_open_from_part`` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*````nvs_set_*````nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。
为了减少不同组件之间键名的潜在冲突NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则,例如,最多可占 15 个字符。命名空间的名称在调用 :cpp:func:`nvs_open`:cpp:type:`nvs_open_from_partition` 中指定,调用后将返回一个不透明句柄,用于后续调用 ``nvs_get_*````nvs_set_*````nvs_commit`` 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。
NVS 迭代器
^^^^^^^^^^^^^
迭代器允许根据指定的分区名称、命名空间和数据类型轮询 NVS 中存储的键值对。
您可以使用以下函数,执行相关操作:
- ``nvs_entry_find``:返回一个不透明句柄,用于后续调用 ``nvs_entry_next````nvs_entry_info`` 函数;
- ``nvs_entry_next``:返回指向下一个键值对的迭代器;
- ``nvs_entry_info``:返回每个键值对的信息。
如果未找到符合标准的键值对,``nvs_entry_find````nvs_entry_next`` 将返回 NULL此时不必释放迭代器。若不再需要迭代器可使用 ``nvs_release_iterator`` 释放迭代器。
安全性、篡改性及鲁棒性
^^^^^^^^^^^^^^^^^^^^^^^^^^
NVS 与 ESP32 flash 加密系统不直接兼容。但如果 NVS 加密与 ESP32 flash 加密一起使用时,数据仍可以加密形式存储。更多详情请参阅 :ref:`nvs_encryption`
NVS 与 {IDF_TARGET_NAME} flash 加密系统不直接兼容。但如果 NVS 加密与 {IDF_TARGET_NAME} flash 加密一起使用时,数据仍可以加密形式存储。详情请参阅 :ref:`nvs_encryption`
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的人都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值。但是,针对擦除操作没有相应的防篡改功能。
如果未启用 NVS 加密,任何对 flash 芯片有物理访问权限的用户都可以修改、擦除或添加键值对。NVS 加密启用后,如果不知道相应的 NVS 加密密钥,则无法修改或添加键值对并将其识别为有效键值。但是,针对擦除操作没有相应的防篡改功能。
当 flash 处于不一致状态时NVS 库会尝试恢复。在任何时间点关闭设备电源,然后重新打开电源,不会导致数据丢失;但如果关闭设备电源时正在写入新的键值对,这一键值对可能会丢失。该库还应当能对 flash 中的任意数据进行正确初始化。
.. _nvs_encryption:
NVS 加密
--------------
NVS 分区内存储的数据可使用 AES-XTS 进行加密,类似于 IEEE P1619 磁盘加密标准中提到的加密方式。为了实现加密,每个条目被均视为一个扇区,并将条目相对地址(相对于分区开头)传递给加密算法,用作扇区号。可通过 :ref:`CONFIG_NVS_ENCRYPTION` 启用 NVS 加密。NVS 加密所需的密钥存储于其他分区,并且被 :doc:`Flash 加密 <../../security/flash-encryption>` 保护。因此,在使用 NVS 加密前应先启用 :doc:`Flash 加密 <../../security/flash-encryption>`
启用 :doc:`Flash 加密 <../../security/flash-encryption>` 时,默认启用 NVS 加密。这是因为 Wi-Fi 驱动在默认的 NVS 分区中存储了凭证(如 SSID 和密码)。启用平台级加密后,仍需将它们作为默认选项进行加密。
使用 NVS 加密,分区表必须包含 :ref:`nvs_key_partition`。在分区表选项 (menuconfig->Partition Table) 下,为 NVS 加密提供了两个包含 :ref:`nvs_key_partition` 的分区表,您可以通过工程配置菜单 (``idf.py menuconfig``) 进行选择。请参考 :example:`security/flash_encryption` 中的例子,了解如何配置和使用 NVS 加密功能。
.. _nvs_key_partition:
NVS 密钥分区
^^^^^^^^^^^^^^^^^
应用程序如果想使用 NVS 加密,则需要编译进一个类型为 `data`,子类型为 `key` 的密钥分区。该分区应标记为 `已加密` 且最小为 4096 字节。如需了解更多详细信息,请参考 :doc:`分区表 <../../api-guides/partition-tables>`。在分区表选项 (menuconfig->Partition Table) 下提供了两个包含 :ref:`nvs_key_partition` 的额外分区表,可以直接用于 :ref:`nvs_encryption`。这些分区的具体结构见下表:
.. highlight:: none
::
+-----------+--------------+-------------+----+
| XTS encryption key (32) |
+---------------------------------------------+
| XTS tweak key (32) |
+---------------------------------------------+
| CRC32 (4) |
+---------------------------------------------+
可以通过以下两种方式生成 :ref:`nvs_key_partition` 中的 XTS 加密密钥:
1. 在 ESP 芯片上生成密钥:
启用 NVS 加密时,可用 :cpp:func:`nvs_flash_init` API 函数来初始化加密的默认 NVS 分区,在内部生成 ESP 芯片上的 XTS 加密密钥。在找到 :ref:`nvs_key_partition`API 函数利用 :component_file:`nvs_flash/include/nvs_flash.h` 提供的 :cpp:func:`nvs_flash_generate_keys` 函数,自动生成并存储该分区中的 NVS 密钥。只有当各自的密钥分区为空时,才会生成并存储新的密钥。可以借助 :cpp:func:`nvs_flash_secure_init_partition` 用同一个密钥分区来读取安全配置,以初始化一个定制的加密 NVS 分区。
API 函数 :cpp:func:`nvs_flash_secure_init`:cpp:func:`nvs_flash_secure_init_partition` 不在内部产生密钥。当这些 API 函数用于初始化加密的 NVS 分区时,可以在启动后使用 `nvs_flash.h` 提供的 :cpp:func:`nvs_flash_generate_keys` API 函数生成密钥,以加密的形式把密钥写到密钥分区上。
2. 使用预先生成的密钥分区:
:ref:`nvs_key_partition` 中的密钥不是由应用程序生成,则需要使用预先生成的密钥分区。可以使用 :doc:`NVS 分区生成工具 </api-reference/storage/nvs_partition_gen>` 生成包含 XTS 加密密钥的 :ref:`nvs_key_partition`。用户可以借助以下两个命令,将预先生成的密钥分区储存在 flash 上:
i) 建立并烧录分区表
::
idf.py partition-table partition-table-flash
ii) 调用 :component_file:`parttool.py<partition_table/parttool.py>`,将密钥存储在 flash 上的 :ref:`nvs_key_partition` 中。详见 :doc:` 分区表 </api-guides/partition-tables>` 的分区工具部分。
parttool.py --port /dev/ttyUSB0 --partition-table-offset "nvs_key partition offset" write_partition --partition-name="name of nvs_key partition" --input "nvs_key partition"
由于分区已标记为 `已加密`,而且启用了 :doc:`Flash 加密 <../../security/flash-encryption>`,引导程序在首次启动时将使用 flash 加密对密钥分区进行加密。
应用程序可以使用不同的密钥对不同的 NVS 分区进行加密,这样就会需要多个加密密钥分区。应用程序应为加解密操作提供正确的密钥或密钥分区。
加密读取/写入
^^^^^^^^^^^^^^^^^^^^
``nvs_get_*````nvs_set_*`` 等 NVS API 函数同样可以对 NVS 加密分区执行读写操作。
**加密默认的 NVS 分区:**
无需额外步骤即可启用默认 NVS 分区的加密。启用 :ref:`CONFIG_NVS_ENCRYPTION` 时, :cpp:func:`nvs_flash_init` API 函数会在内部使用找到的第一个 :ref:`nvs_key_partition` 执行额外步骤,以启用默认 NVS 分区的加密(详情请参考 API 文档)。另外,:cpp:func:`nvs_flash_secure_init` API 函数也可以用来启用默认 NVS 分区的加密。
**加密一个自定义的 NVS 分区:**
使用 :cpp:func:`nvs_flash_secure_init_partition` API 函数启用自定义 NVS 分区的加密,而非 :cpp:func:`nvs_flash_init_partition`
使用 :cpp:func:`nvs_flash_secure_init`:cpp:func:`nvs_flash_secure_init_partition` API 函数时,应用程序如需在加密状态下执行 NVS 读写操作,应遵循以下步骤:
1. 使用 ``esp_partition_find*`` API 查找密钥分区和 NVS 数据分区;
2. 使用 ``nvs_flash_read_security_cfg````nvs_flash_generate_keys`` API 填充 ``nvs_sec_cfg_t`` 结构;
3. 使用 ``nvs_flash_secure_init````nvs_flash_secure_init_partition`` API 初始化 NVS flash 分区;
4. 使用 ``nvs_open````nvs_open_from_partition`` API 打开命名空间;
5. 使用 ``nvs_get_*````nvs_set_*`` API 执行 NVS 读取/写入操作;
6. 使用 ``nvs_flash_deinit`` API 释放已初始化的 NVS 分区。
NVS 分区生成程序
------------------
NVS 分区生成程序帮助生成 NVS 分区二进制文件,可使用烧录程序将二进制文件单独烧录至特定分区。烧录至分区上的键值对由 CSV 文件提供,详情请参考 :doc:`NVS 分区生成程序 <nvs_partition_gen>`
应用示例
-------------------
ESP-IDF :example:`storage` 目录下提供了数个代码示例:
:example:`storage/nvs_rw_value`
演示如何读取及写入 NVS 单个整数值。
此示例中的值表示 {IDF_TARGET_NAME} 模组重启次数。NVS 中数据不会因为模组重启而丢失,因此只有将这一值存储于 NVS 中,才能起到重启次数计数器的作用。
该示例也演示了如何检测读取/写入操作是否成功,以及某个特定值是否在 NVS 中尚未初始化。诊断程序以纯文本形式提供,帮助您追踪程序流程,及时发现问题。
:example:`storage/nvs_rw_blob` 
演示如何读取及写入 NVS 单个整数值和 Blob二进制大对象并在 NVS 中存储这一数值,即便 {IDF_TARGET_NAME} 模组重启也不会消失。
* value - 记录 {IDF_TARGET_NAME} 模组软重启次数和硬重启次数。
* blob - 内含记录模组运行次数的表格。此表格将被从 NVS 读取至动态分配的 RAM 上。每次手动软重启后,表格内运行次数即增加一次,新加的运行次数被写入 NVS。下拉 GPIO0 即可手动软重启。
该示例也演示了如何执行诊断程序以检测读取/写入操作是否成功。
:example:`storage/nvs_rw_value_cxx`
这个例子与 :example:`storage/nvs_rw_value` 完全一样,只是使用了 C++ 的 NVS 处理类。
内部实现
---------
@ -70,7 +190,7 @@ NVS 按顺序存储键值对,新的键值对添加在最后。因此,如需
页面和条目
^^^^^^^^^^^^^^^^^
NVS 库在其操作中主要使用两个实体:页面和条目。页面是一个逻辑结构,用于存储部分的整体日志。逻辑页面对应 flash 的一个物理扇区,正在使用中的页面具有与之相关联的序列号。序列号赋予了页面顺序,较高的序列号对应较晚创建的页面。页面有以下几种状态:
NVS 库在其操作中主要使用两个实体:页面和条目。页面是一个逻辑结构,用于存储部分的整体日志。逻辑页面对应 flash 的一个物理扇区,正在使用中的页面具有与之相关联的 *序列号*。序列号赋予了页面顺序,较高的序列号对应较晚创建的页面。页面有以下几种状态:
空或未初始化
页面对应的 flash 扇区为空白状态(所有字节均为 ``0xff``)。此时,页面未存储任何数据且没有关联的序列号。
@ -79,7 +199,8 @@ NVS 库在其操作中主要使用两个实体:页面和条目。页面是一
此时 flash 已完成初始化,页头部写入 flash页面已具备有效序列号。页面中存在一些空条目可写入数据。任意时刻至多有一个页面处于活跃状态。
写满状态
Flash 已写满键值对,状态不再改变。用户无法向写满状态下的页面写入新键值对,但仍可将一些键值对标记为已擦除。
Flash 已写满键值对,状态不再改变。
用户无法向写满状态下的页面写入新键值对,但仍可将一些键值对标记为已擦除。
擦除状态
未擦除的键值对将移至其他页面,以便擦除当前页面。这一状态仅为暂时性状态,即 API 调用返回时,页面应脱离这一状态。如果设备突然断电,下次开机时,设备将继续把未擦除的键值对移至其他页面,并继续擦除当前页面。
@ -106,11 +227,13 @@ Flash 扇区映射至逻辑页面并没有特定的顺序NVS 库会检查存
页面结构
^^^^^^^^^^^^^^^^^^^
当前,我们假设 flash 扇区大小为 4096 字节,并且 ESP32 flash 加密硬件在 32 字节块上运行。未来有可能引入一些编译时可配置项(可通过 menuconfig 进行配置),以适配具有不同扇区大小的 flash 芯片。但目前尚不清楚 SPI flash 驱动和 SPI flash cache 之类的系统组件是否支持其他扇区大小。
当前,我们假设 flash 扇区大小为 4096 字节,并且 {IDF_TARGET_NAME} flash 加密硬件在 32 字节块上运行。未来有可能引入一些编译时可配置项(可通过 menuconfig 进行配置),以适配具有不同扇区大小的 flash 芯片。但目前尚不清楚 SPI flash 驱动和 SPI flash cache 之类的系统组件是否支持其他扇区大小。
页面由头部、条目状态位图和条目三部分组成。为了实现与 ESP32 flash 加密功能兼容,条目大小设置为 32 字节。如果键值为整数型,条目则保存一个键值对;如果键值为字符串或 BLOB 类型,则条目仅保存一个键值对的部分内容(更多信息详见条目结构描述)。
页面由头部、条目状态位图和条目三部分组成。为了实现与 {IDF_TARGET_NAME} flash 加密功能兼容,条目大小设置为 32 字节。如果键值为整数型,条目则保存一个键值对;如果键值为字符串或 BLOB 类型,则条目仅保存一个键值对的部分内容(更多信息详见条目结构描述)。
页面结构如下图所示,括号内数字表示该部分的大小(以字节为单位)::
页面结构如下图所示,括号内数字表示该部分的大小(以字节为单位)。
::
+-----------+--------------+-------------+-------------------------+
| State (4) | Seq. no. (4) | version (1) | Unused (19) | CRC32 (4) | 页头部 (32)
@ -127,15 +250,15 @@ Flash 扇区映射至逻辑页面并没有特定的顺序NVS 库会检查存
| Entry 125 (32) |
+------------------------------------------------------------------+
头部和条目状态位图写入 flash 时不加密。如果启用了 ESP32 flash 加密功能,则条目写入 flash 时将会加密。
头部和条目状态位图写入 flash 时不加密。如果启用了 {IDF_TARGET_NAME} flash 加密功能,则条目写入 flash 时将会加密。
通过将 0 写入某些位可以定义页面状态值,表示状态改变。因此,如果需要变更页面状态,并不一定要擦除页面,除非要将其变更为擦除状态。
通过将 0 写入某些位可以定义页面状态值,表示状态改变。因此,如果需要变更页面状态,并不一定要擦除页面,除非要将其变更为 *擦除* 状态。
头部中的 ``version`` 字段反映了所用的 NVS 格式版本。为实现向后兼容,版本升级从 0xff 开始依次递减例如version-1 为 0xffversion-2 为 0xfe)。
头部中的 ``version`` 字段反映了所用的 NVS 格式版本。为实现向后兼容,版本升级从 0xff 开始依次递减例如version-1 为 0xffversion-2 为 0xfe,以此类推)。
头部中 CRC32 值是由不包含状态值的条目计算所得4 到 28 字节)。当前未使用的条目用 ``0xff`` 字节填充。
条目结构和条目状态位图详细信息见下文描述。
条目结构和条目状态位图详细信息见下文描述。
条目和条目状态位图
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -151,6 +274,7 @@ Flash 扇区映射至逻辑页面并没有特定的顺序NVS 库会检查存
擦除2'b00
条目中的键值对已丢弃,条目内容不再解析。
.. _structure_of_entry:
条目结构
@ -167,12 +291,12 @@ Flash 扇区映射至逻辑页面并没有特定的顺序NVS 库会检查存
Primitive +--------------------------------+
+--------> | Data (8) |
| Types +--------------------------------+
+-> Fixed length --
+-> Fixed length --
| | +---------+--------------+---------------+-------+
| +--------> | Size(4) | ChunkCount(1)| ChunkStart(1) | Rsv(2)|
Data format ---+ BLOB Index +---------+--------------+---------------+-------+
|
| +----------+---------+-----------+
| +----------+---------+-----------+
+-> Variable length --> | Size (2) | Rsv (2) | CRC32 (4) |
(Strings, BLOB Data) +----------+---------+-----------+
@ -180,10 +304,10 @@ Flash 扇区映射至逻辑页面并没有特定的顺序NVS 库会检查存
条目结构中各个字段含义如下:
命名空间 (NS, NameSpace)
该条目的命名空间索引,详细信息见命名空间实现章节。
该条目的命名空间索引,详细信息见命名空间实现章节。
类型 (Type)
一个字节表示的值的数据类型,可能的类型见 ``nvs_types.h````ItemType`` 枚举
一个字节表示的值的数据类型,:component_file:`nvs_flash/include/nvs_handle.hpp` 下的 :cpp:type:`ItemType` 枚举了可能的类型
跨度 (Span)
该键值对所用的条目数量。如果键值为整数型,条目数量即为 1。如果键值为字符串或 BLOB则条目数量取决于值的长度。
@ -192,10 +316,10 @@ Flash 扇区映射至逻辑页面并没有特定的顺序NVS 库会检查存
用于存储 BLOB 类型数据块的索引。如果键值为其他数据类型,则此处索引应写入 ``0xff``
CRC32
对条目下所有字节进行校验所得的校验和CRC32 字段不计算在内)。
对条目下所有字节进行校验所得的校验和CRC32 字段不计算在内)。
键 (Key)
即以零结尾的 ASCII 字符串,字符串最长为 15 字节,不包含最后一个字节的 NULL (``\0``) 终止符。
即以零结尾的 ASCII 字符串,字符串最长为 15 字节,不包含最后一个字节的终止符。
数据 (Data)
如果键值类型为整数型,则数据字段仅包含键值。如果键值小于八个字节,使用 ``0xff`` 填充未使用的部分(右侧)。
@ -205,21 +329,22 @@ CRC32
- 块大小
整个 BLOB 数据的大小(以字节为单位)。该字段仅用于 BLOB 索引类型条目。
- ChunkCount
存储过程中 BLOB 分成的数据块量。该字段仅用于 BLOB 索引类型条目。
- ChunkStart
- ChunkCount
存储过程中 BLOB 分成的数据块量。该字段仅用于 BLOB 索引类型条目。
- ChunkStart
BLOB 第一个数据块的块索引,后续数据块索引依次递增,步长为 1。该字段仅用于 BLOB 索引类型条目。
如果键值类型为字符串或 BLOB 数据块,数据字段的这八个字节将保存该键值的一些附加信息,如下所示:
- 数据大小
实际数据的大小(以字节为单位)。如果键值类型为字符串,此字段也应将零终止符包含在内。此字段仅用于字符串和 BLOB 类型条目。
- CRC32
数据所有字节的校验和,该字段仅用于字符串和 BLOB 类型条目。
可变长度值(字符串和 BLOB写入后续条目每个条目 32 字节。第一个条目的 span 字段将指明使用了多少条目。
可变长度值(字符串和 BLOB写入后续条目每个条目 32 字节。第一个条目的 `Span` 字段将指明使用了多少条目。
命名空间
^^^^^^^^^^
@ -246,89 +371,6 @@ CRC32
哈希列表中每个节点均包含一个 24 位哈希值和 8 位条目索引。哈希值根据条目命名空间、键名和块索引由 CRC32 计算所得,计算结果保留 24 位。为减少将 32 位条目存储在链表中的开销,链表采用了数组的双向链表。每个数组占用 128 个字节,包含 29 个条目、两个链表指针和一个 32 位计数字段。因此,每页额外需要的 RAM 最少为 128 字节,最多为 640 字节。
.. _nvs_encryption:
NVS 加密
--------------
NVS 分区内存储的数据可使用 AES-XTS 进行加密,类似于 IEEE P1619 磁盘加密标准中提到的加密方式。为了实现加密每个条目被均视为一个扇区并将条目相对地址相对于分区开头传递给加密算法用作扇区号。NVS 加密所需的密钥存储于其他分区,并进行了 :doc:`flash 加密 <../../security/flash-encryption>`。因此,在使用 NVS 加密前应先启用 :doc:`flash 加密 <../../security/flash-encryption>`
.. _nvs_key_partition:
NVS 密钥分区
^^^^^^^^^^^^^^^^^
应用程序如果想使用 NVS 加密,则需要编译进一个类型为 ``data``,子类型为 ``key`` 的密钥分区。该分区应标记为已加密,且最小为 4096 字节,具体结构见下表。如需了解更多详细信息,请参考 :doc:`分区表 <../../api-guides/partition-tables>`
::
+-----------+--------------+-------------+----+
| XTS encryption key(32) |
+---------------------------------------------+
| XTS tweak key (32) |
+---------------------------------------------+
| CRC32(4) |
+---------------------------------------------+
使用 NVS 分区生成程序生成上述分区表,并烧录至设备。由于分区已标记为已加密,而且启用了 :doc:`flash 加密 <../../security/flash-encryption>`,引导程序在首次启动时将使用 flash 加密对密钥分区进行加密。您也可以在设备启动后调用 ``nvs_flash.h`` 提供的 ``nvs_flash_generate_keys`` API 生成加密密钥,然后再将密钥以加密形式写入密钥分区。
应用程序可以使用不同的密钥对不同的 NVS 分区进行加密,这样就会需要多个加密密钥分区。应用程序应为加解密操作提供正确的密钥或密钥分区。
加密读取/写入
^^^^^^^^^^^^^^^^^^^^
``nvs_get_*````nvs_set_*`` 等 NVS API 函数同样可以对 NVS 加密分区执行读写操作。但用于初始化 NVS 非加密分区和加密分区的 API 则有所不同:初始化 NVS 非加密分区可以使用 ``nvs_flash_init````nvs_flash_init_partition``,但初始化 NVS 加密分区则需调用 ``nvs_flash_secure_init````nvs_flash_secure_init_partition``。上述 API 函数所需的 ``nvs_sec_cfg_t`` 结构可使用 ``nvs_flash_generate_keys`` 或者 ``nvs_flash_read_security_cfg`` 进行填充。
应用程序如需在加密状态下执行 NVS 读写操作,应遵循以下步骤:
1. 使用 ``esp_partition_find*`` API 查找密钥分区和 NVS 数据分区;
2. 使用 ``nvs_flash_read_security_cfg````nvs_flash_generate_keys`` API 填充 ``nvs_sec_cfg_t`` 结构;
3. 使用 ``nvs_flash_secure_init````nvs_flash_secure_init_partition`` API 初始化 NVS flash 分区;
4. 使用 ``nvs_open````nvs_open_from_part`` API 打开命名空间;
5. 使用 ``nvs_get_*````nvs_set_*`` API 执行 NVS 读取/写入操作;
6. 使用 ``nvs_flash_deinit`` API 释放已初始化的 NVS 分区。
NVS 迭代器
^^^^^^^^^^^^^
迭代器允许根据指定的分区名称、命名空间和数据类型轮询 NVS 中存储的键值对。
您可以使用以下函数,执行相关操作:
- ``nvs_entry_find``:返回一个不透明句柄,用于后续调用 ``nvs_entry_next````nvs_entry_info`` 函数;
- ``nvs_entry_next``:返回指向下一个键值对的迭代器;
- ``nvs_entry_info``:返回每个键值对的信息。
如果未找到符合标准的键值对,``nvs_entry_find````nvs_entry_next`` 将返回 NULL此时不必释放迭代器。若不再需要迭代器可使用 ``nvs_release_iterator`` 释放迭代器。
NVS 分区生成程序
------------------
NVS 分区生成程序帮助生成 NVS 分区二进制文件,可使用烧录程序将二进制文件单独烧录至特定分区。烧录至分区上的键值对由 CSV 文件提供,详情请参考 :doc:`NVS 分区生成程序 <nvs_partition_gen>`
应用示例
-------------------
ESP-IDF :example:`storage` 目录下提供了两个代码示例:
:example:`storage/nvs_rw_value`
演示如何读取及写入 NVS 单个整数值。
此示例中的值表示 ESP32 模组重启次数。NVS 中数据不会因为模组重启而丢失,因此只有将这一值存储于 NVS 中,才能起到重启次数计数器的作用。
该示例也演示了如何检测读取/写入操作是否成功,以及某个特定值是否在 NVS 中尚未初始化。诊断程序以纯文本形式提供,帮助您追踪程序流程,及时发现问题。
:example:`storage/nvs_rw_blob` 
演示如何读取及写入 NVS 单个整数值和 Blob二进制大对象并在 NVS 中存储这一数值,即便 ESP32 模组重启也不会消失。
* value - 记录 ESP32 模组软重启次数和硬重启次数。
* blob - 内含记录模组运行次数的表格。此表格将被从 NVS 读取至动态分配的 RAM 上。每次手动软重启后,表格内运行次数即增加一次,新加的运行次数被写入 NVS。下拉 GPIO0 即可手动软重启。
该示例也演示了如何执行诊断程序以检测读取/写入操作是否成功。
API 参考
-------------