# 虚拟文件系统组件

[English]

## 概述

虚拟文件系统 (VFS) 组件为驱动程序提供一个统一接口，可以操作类文件对象。这类驱动程序可以是 FAT、SPIFFS 等真实文件系统，也可以是提供文件类接口的设备驱动程序。

VFS 组件支持 C 库函数（如 fopen 和 fprintf 等）与文件系统 (FS) 驱动程序协同工作。在高层级，每个 FS 驱动程序均挂载到以某个前缀开头的路径上。当一个 C 库函数需要打开文件时，VFS 组件将搜索与该文件所在文件路径相关联的 FS 驱动程序，并将调用传递给该驱动程序。针对该文件的读取、写入等其他操作的调用也将传递给这个驱动程序。

例如，将 FAT 文件系统驱动挂载到 `/fat` 前缀开头的路径上，之后即可调用 `fopen("/fat/file.txt", "w")`。之后，VFS 将调用 FAT 驱动的 `open` 函数，并将参数 `/file.txt` 和合适的打开模式传递给 `open` 函数；后续对返回的 `FILE*` 数据流调用 C 库函数也同样会传递给 FAT 驱动。

## 注册 FS 驱动程序

如需注册 FS 驱动程序，应用程序首先要定义一个 `esp_vfs_fs_ops_t` 结构体实例，并用指向 FS API 的函数指针填充它。

    // esp_vfs_fs_ops_t 及其子组件的声明必须是静态的
    static const esp_vfs_dir_ops_t myfs_dir = {
        .stat = &myfs_stat,
    };

    static const esp_vfs_fs_ops_t myfs = {
        .write = &myfs_write,
        .open = &myfs_open,
        .close = &myfs_close,
        .read = &myfs_read,
        .dir = &myfs_dir,
    };

    ESP_ERROR_CHECK(esp_vfs_register_fs("/data", &myfs, ESP_VFS_FLAG_STATIC, NULL));

### 非静态 `esp_vfs_fs_ops_t`

推荐使用静态分配的 `esp_vfs_fs_ops_t` 和 `ESP_VFS_FLAG_STATIC` 来注册文件系统，因为这种方式更高效。若无法使用静态分配，则将 `ESP_VFS_FLAG_STATIC` 替换为 `ESP_VFS_FLAG_DEFAULT`，指示 VFS 在 RAM 中对传递的结构体进行深拷贝，并由 VFS 组件来管理这份拷贝。

    // 可能是局部作用域
    {
        esp_vfs_dir_ops_t myfs_dir = {
            .stat = &myfs_stat,
        };

        bool some_condition = false;

        esp_vfs_fs_ops_t myfs = {
            .write = some_condition ? &myfs_special_write : &myfs_write,
            // ... 其他成员
            .dir = &myfs_dir,
        };

        ESP_ERROR_CHECK(esp_vfs_register_fs("/data", &myfs, ESP_VFS_FLAG_DEFAULT, NULL));
    }

### 上下文感知文件系统

在某些情况下，当挂载多个文件系统实例时，向文件系统功能传递一些上下文信息（比如特定挂载点的文件描述符表）是有益的，甚至是必要的。因此，`esp_vfs_fs_ops_t` 中包含了每个成员带有 `_p` 后缀的第二个版本。例如，`read` 函数有其对应的 `read_p` 函数，后者需要额外的第一个参数。在注册文件系统时，必须指定 `ESP_VFS_FLAG_CONTEXT_PTR` 标志，并将上下文指针作为最后一个参数传递。

    ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size);

    // 在 esp_vfs_fs_ops_t 的定义中：
        .write_p = &myfs_write,
    // ... 初始化其他成员

    // 注册文件系统时，将 ESP_VFS_FLAG_CONTEXT_PTR 标志与文件系统上下文指针分别作为第三和第四个参数传递
    // （假设的 myfs_mount 函数用于实例说明）
    myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size);
    ESP_ERROR_CHECK(esp_vfs_register_fs("/data1", &myfs, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, myfs_inst1));

    // 可以注册另一个实例：
    myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
    ESP_ERROR_CHECK(esp_vfs_register_fs("/data2", &myfs, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, myfs_inst2));

## 同步输入/输出多路复用

VFS 组件支持通过 `select()` 进行同步输入/输出多路复用，其实现方式如下：

1. 调用 `select()`，使用时提供的文件描述符可以属于不同的 VFS 驱动。

2. 文件描述符被分为几组，每组属于一个 VFS 驱动。

3. 非套接字 VFS 驱动的文件描述符由 `start_select()` 移交给指定的 VFS 驱动，后文会对此进行详述。该函数代表指定驱动 `select()` 的实现。这是一个非阻塞的调用，意味着在设置好检查与指定文件描述符相关事件的环境后，该函数应该立即返回。

4. 套接字 VFS 驱动的文件描述符由 `socket_select()` 移交给套接字 VFS 驱动，后文会对此进行详述。这是一个阻塞调用，意味着只有当有一个与套接字文件描述符相关的事件或非套接字驱动发出信号让 `socket_select()` 退出时，它才会返回。

5. 从各个 VFS 驱动程序收集结果，并通过对事件检查环境取消初始化来终止所有驱动程序。

6. `select()` 调用结束并返回适当的结果。

### 非套接字 VFS 驱动

如果要使用非套接字 VFS 驱动的文件描述符调用 `select()`，那么需要用函数 `start_select()` 和 `end_select()` 注册该驱动，具体如下：

    // 在 esp_vfs_select_ops_t 的定义中：
        .start_select = &uart_start_select,
        .end_select = &uart_end_select,
    // ... 其他成员已初始化

调用 `start_select()` 函数可以设置环境，检测指定 VFS 驱动的文件描述符读取/写入/错误条件。

调用 `end_select()` 函数可以终止/取消初始化/释放由 `start_select()` 设置的环境。

备注

在少数情况下，在调用 `end_select()` 之前可能并没有调用过 `start_select()`。因此 `end_select()` 的实现必须在该情况下返回错误而不能崩溃。

如需获取更多信息，请参考 esp_driver_uart/src/uart_vfs.c 中 UART 外设的 VFS 驱动，尤其是函数 `uart_vfs_dev_register()`、`uart_start_select()` 和 `uart_end_select()`。

请参考以下示例，查看如何使用 VFS 文件描述符调用 `select()`：

- peripherals/uart/uart_select

- system/select

### 套接字 VFS 驱动

套接字 VFS 驱动会使用自实现的 `socket_select()` 函数，在读取/写入/错误条件时，非套接字 VFS 驱动会通知该函数。

可通过定义以下函数注册套接字 VFS 驱动：

    // 在 esp_vfs_select_ops_t 的定义中：
        .socket_select = &lwip_select,
        .get_socket_select_semaphore = &lwip_get_socket_select_semaphore,
        .stop_socket_select = &lwip_stop_socket_select,
        .stop_socket_select_isr = &lwip_stop_socket_select_isr,
    // ... 其他成员已初始化

函数 `socket_select()` 是套接字驱动对 `select()` 的内部实现。该函数只对套接字 VFS 驱动的文件描述符起作用。

`get_socket_select_semaphore()` 返回信号对象 (semaphore)，用于非套接字驱动程序中，以终止 `socket_select()` 的等待。

`stop_socket_select()` 通过传递 `get_socket_select_semaphore()` 函数返回的对象来终止 `socket_select()` 函数的等待。

`stop_socket_select_isr()` 与 `stop_socket_select()` 的作用相似，但是前者可在 ISR 中使用。

请参考 lwip/port/esp32xx/vfs_lwip.c 以了解使用 LWIP 的套接字驱动参考实现。

备注

如果 `select()` 用于套接字文件描述符，可以禁用 CONFIG_VFS_SUPPORT_SELECT 选项来减少代码量，提高性能。

不要在 `select()` 调用过程中更改套接字驱动，否则会出现一些未定义行为。

## 路径

已注册的 FS 驱动程序均有一个路径前缀与之关联，此路径前缀即为分区的挂载点。

如果挂载点中嵌套了其他挂载点，则在打开文件时使用具有最长匹配路径前缀的挂载点。例如，假设以下文件系统已在 VFS 中注册：

- 在 /data 下注册 FS 驱动程序 1

- 在 /data/static 下注册 FS 驱动程序 2

那么：

- 打开 `/data/log.txt` 会调用驱动程序 FS 1；

- 打开 `/data/static/index.html` 需调用 FS 驱动程序 2；

- 即便 FS 驱动程序 2 中没有 `/index.html`，也不会在 FS 驱动程序 1 中查找 `/static/index.html`。

挂载点名称必须以路径分隔符 (`/`) 开头，且分隔符后至少包含一个字符。但在以下情况中，VFS 同样支持空的挂载点名称：1. 应用程序需要提供一个”最后方案“下使用的文件系统；2. 应用程序需要同时覆盖 VFS 功能。如果没有与路径匹配的前缀，就会使用到这种文件系统。

VFS 不会对路径中的点 (`.`) 进行特殊处理，也不会将 `..` 视为对父目录的引用。在上述示例中，使用 `/data/static/../log.txt` 路径不会调用 FS 驱动程序 1 打开 `/log.txt`。特定的 FS 驱动程序（如 FATFS）可能以不同的方式处理文件名中的点。

执行打开文件操作时，FS 驱动程序仅得到文件的相对路径（挂载点前缀已经被去除）：

1. 以 `/data` 为路径前缀注册 `myfs` 驱动；

2. 应用程序调用 `fopen("/data/config.json", ...)`；

3. VFS 调用 `myfs_open("/config.json", ...)`；

4. `myfs` 驱动打开 `/config.json` 文件。

VFS 对文件路径长度没有限制，但文件系统路径前缀受 `ESP_VFS_PATH_MAX` 限制，即路径前缀上限为 `ESP_VFS_PATH_MAX`。各个文件系统驱动则可能会对自己的文件名长度设置一些限制。

## 文件描述符

文件描述符是一组很小的正整数，从 `0` 到 `FD_SETSIZE - 1`，`FD_SETSIZE` 定义在 `sys/select.h`。最大文件描述符由 `CONFIG_LWIP_MAX_SOCKETS` 定义，且为套接字保留。VFS 中包含一个名为 `s_fd_table` 的查找表，用于将全局文件描述符映射至 `s_vfs` 数组中注册的 VFS 驱动索引。

标准 I/O 流（stdin、stdout、stderr）分别映射到文件描述符 0、1 和 2。有关标准 I/O 的更多信息，请参见 标准 I/O 与控制台输出。

## `eventfd()`

`eventfd()` 是一个很强大的工具，可以循环通知基于 `select()` 的自定义事件。在 ESP-IDF 中， `eventfd()` 的实现大体上与 man(2) eventfd 中的描述相同，主要区别如下：

- 在调用 `eventfd()` 之前必须先调用 `esp_vfs_eventfd_register()`；

- 标志中没有 `EFD_CLOEXEC`、`EFD_NONBLOCK` 和 `EFD_SEMAPHORE` 选项；

- `EFD_SUPPORT_ISR` 选项已经被添加到标志中。在中断处理程序中读取和写入 eventfd 需要这个标志。

注意，用 `EFD_SUPPORT_ISR` 创建 eventfd 将导致在读取、写入文件时，以及在设置这个文件的 `select()` 开始和结束时，暂时禁用中断。

## 常用 VFS 设备

IDF 定义了多个可供应用程序使用的 VFS 设备。这些设备包括：

- `/dev/uart/<UART NUMBER>` - 此文件映射到使用 VFS 驱动程序打开的 UART 中。UART 编号是 UART 外设的编号。

- `/dev/null` - 此文件丢弃所有写入的数据，并在读取时返回 EOF。启用 CONFIG_VFS_INITIALIZE_DEV_NULL 会自动创建此文件。

- `/dev/console` - 此文件连接到在 menuconfig 中由 CONFIG_ESP_CONSOLE_UART 和 CONFIG_ESP_CONSOLE_SECONDARY 指定的主输出和次输出。更多信息请参考 标准 I/O 与控制台输出。

## 应用示例

- system/eventfd 使用了两个任务和一个定时器 ISR 回调函数演示了如何使用 `eventfd()` 在基于 `select()` 的主循环中收集任务和中断服务程序的事件。

- system/select 演示了如何使用 `select()` 函数进行同步 I/O 多路复用，使用 UART 和套接字文件描述符，并将二者配置为回环模式，以接收来自其他任务发送的消息。

- storage/semihost_vfs 演示了如何使用半托管 VFS 驱动程序，包括注册主机目录、将 UART 的 stdout 重定向到主机上的文件，并读取和打印文本文件的内容。

## API 参考

### Header File

- components/vfs/include/esp_vfs.h

- This header file can be included with:

  >     #include "esp_vfs.h"

- This header file is a part of the API provided by the `vfs` component. To declare that your component depends on `vfs`, add the following to your CMakeLists.txt:

  >     REQUIRES vfs

  > or

  >     PRIV_REQUIRES vfs

### Functions

ssize_t esp_vfs_write(struct _reent *r, int fd, const void *data, size_t size)

  These functions are to be used in esp_libc syscall table. They will be called by esp_libc when it needs to use any of the syscalls.

off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode)

ssize_t esp_vfs_read(struct _reent *r, int fd, void *dst, size_t size)

int esp_vfs_open(struct _reent *r, const char *path, int flags, int mode)

int esp_vfs_close(struct _reent *r, int fd)

int esp_vfs_fstat(struct _reent *r, int fd, struct stat *st)

int esp_vfs_stat(struct _reent *r, const char *path, struct stat *st)

int esp_vfs_link(struct _reent *r, const char *n1, const char *n2)

int esp_vfs_unlink(struct _reent *r, const char *path)

int esp_vfs_rename(struct _reent *r, const char *src, const char *dst)

int esp_vfs_utime(const char *path, const struct utimbuf *times)

esp_err_t esp_vfs_register(const char *base_path, const esp_vfs_t *vfs, void *ctx)

  Register a virtual filesystem for given path prefix.

  参数:

    - base_path -- file path prefix associated with the filesystem. Must be a zero-terminated C string, may be empty. If not empty, must be up to ESP_VFS_PATH_MAX characters long, and at least 2 characters long. Name must start with a "/" and must not end with "/". For example, "/data" or "/dev/spi" are valid. These VFSes would then be called to handle file paths such as "/data/myfile.txt" or "/dev/spi/0". In the special case of an empty base_path, a "fallback" VFS is registered. Such VFS will handle paths which are not matched by any other registered VFS.

    - vfs -- Pointer to esp_vfs_t, a structure which maps syscalls to the filesystem driver functions. VFS component doesn't assume ownership of this pointer.

    - ctx -- If vfs->flags has ESP_VFS_FLAG_CONTEXT_PTR set, a pointer which should be passed to VFS functions. Otherwise, NULL.

  返回:

    ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are registered.

esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t *vfs_id)

  Special case function for registering a VFS that uses a method other than open() to open new file descriptors. In comparison with esp_vfs_register_fd_range, this function doesn't pre-registers an interval of file descriptors. File descriptors can be registered later, by using esp_vfs_register_fd.

  参数:

    - vfs -- Pointer to esp_vfs_t. Meaning is the same as for esp_vfs_register().

    - ctx -- Pointer to context structure. Meaning is the same as for esp_vfs_register().

    - vfs_id -- Here will be written the VFS ID which can be passed to esp_vfs_register_fd for registering file descriptors.

  返回:

    ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are registered, ESP_ERR_INVALID_ARG if the file descriptor boundaries are incorrect.

esp_err_t esp_vfs_unregister(const char *base_path)

  Unregister a virtual filesystem for given path prefix

  参数:

    base_path -- file prefix previously used in esp_vfs_register call

  返回:

    ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix hasn't been registered

esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id)

  Unregister a virtual filesystem with the given index

  参数:

    vfs_id -- The VFS ID returned by esp_vfs_register_with_id

  返回:

    ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for the given index hasn't been registered

esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd)

  Special function for registering another file descriptor for a VFS registered by esp_vfs_register_with_id. This function should only be used to register permanent file descriptors (socket fd) that are not removed after being closed.

  参数:

    - vfs_id -- VFS identificator returned by esp_vfs_register_with_id.

    - fd -- The registered file descriptor will be written to this address.

  返回:

    ESP_OK if the registration is successful, ESP_ERR_NO_MEM if too many file descriptors are registered, ESP_ERR_INVALID_ARG if the arguments are incorrect.

esp_err_t esp_vfs_register_fd_with_local_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd)

  Special function for registering another file descriptor with given local_fd for a VFS registered by esp_vfs_register_with_id.

  参数:

    - vfs_id -- VFS identificator returned by esp_vfs_register_with_id.

    - local_fd -- The fd in the local vfs. Passing -1 will set the local fd as the (*fd) value.

    - permanent -- Whether the fd should be treated as permanent (not removed after close())

    - fd -- The registered file descriptor will be written to this address.

  返回:

    ESP_OK if the registration is successful, ESP_ERR_NO_MEM if too many file descriptors are registered, ESP_ERR_INVALID_ARG if the arguments are incorrect.

esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd)

  Special function for unregistering a file descriptor belonging to a VFS registered by esp_vfs_register_with_id.

  参数:

    - vfs_id -- VFS identificator returned by esp_vfs_register_with_id.

    - fd -- File descriptor which should be unregistered.

  返回:

    ESP_OK if the registration is successful, ESP_ERR_INVALID_ARG if the arguments are incorrect.

int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)

  Synchronous I/O multiplexing which implements the functionality of POSIX select() for VFS.

  参数:

    - nfds -- Specifies the range of descriptors which should be checked. The first nfds descriptors will be checked in each set.

    - readfds -- If not NULL, then points to a descriptor set that on input specifies which descriptors should be checked for being ready to read, and on output indicates which descriptors are ready to read.

    - writefds -- If not NULL, then points to a descriptor set that on input specifies which descriptors should be checked for being ready to write, and on output indicates which descriptors are ready to write.

    - errorfds -- If not NULL, then points to a descriptor set that on input specifies which descriptors should be checked for error conditions, and on output indicates which descriptors have error conditions.

    - timeout -- If not NULL, then points to timeval structure which specifies the time period after which the functions should time-out and return. If it is NULL, then the function will not time-out. Note that the timeout period is rounded up to the system tick and incremented by one.

  返回:

    The number of descriptors set in the descriptor sets, or -1 when an error (specified by errno) have occurred.

void esp_vfs_select_triggered(esp_vfs_select_sem_t sem)

  Notification from a VFS driver about a read/write/error condition.

  This function is called when the VFS driver detects a read/write/error condition as it was requested by the previous call to start_select.

  参数:

    sem -- semaphore structure which was passed to the driver by the start_select call

void esp_vfs_select_triggered_isr(esp_vfs_select_sem_t sem, BaseType_t *woken)

  Notification from a VFS driver about a read/write/error condition (ISR version)

  This function is called when the VFS driver detects a read/write/error condition as it was requested by the previous call to start_select.

  参数:

    - sem -- semaphore structure which was passed to the driver by the start_select call

    - woken -- is set to pdTRUE if the function wakes up a task with higher priority

ssize_t esp_vfs_pread(int fd, void *dst, size_t size, off_t offset)

  Implements the VFS layer of POSIX pread()

  参数:

    - fd -- File descriptor used for read

    - dst -- Pointer to the buffer where the output will be written

    - size -- Number of bytes to be read

    - offset -- Starting offset of the read

  返回:

    A positive return value indicates the number of bytes read. -1 is return on failure and errno is set accordingly.

ssize_t esp_vfs_pwrite(int fd, const void *src, size_t size, off_t offset)

  Implements the VFS layer of POSIX pwrite()

  参数:

    - fd -- File descriptor used for write

    - src -- Pointer to the buffer from where the output will be read

    - size -- Number of bytes to write

    - offset -- Starting offset of the write

  返回:

    A positive return value indicates the number of bytes written. -1 is return on failure and errno is set accordingly.

void esp_vfs_dump_fds(FILE *fp)

  Dump the existing VFS FDs data to FILE* fp.

  Dump the FDs in the format:

              <VFS Path Prefix>-<FD seen by App>-<FD seen by driver>

         where:
          VFS Path Prefix   : file prefix used in the esp_vfs_register call
          FD seen by App    : file descriptor returned by the vfs to the application for the path prefix
          FD seen by driver : file descriptor used by the driver for the same file prefix.

  参数:

    fp -- File descriptor where data will be dumped

void esp_vfs_dump_registered_paths(FILE *fp)

  Dump all registered FSs to the provided FILE*.

  Dump the FSs in the format:

             <index>:<VFS Path Prefix> -> <VFS entry ptr>

         where:
             index           : internal index in the table of registered FSs (the same as returned when registering fd with id)
             VFS Path Prefix : file prefix used in the esp_vfs_register call or "NULL"
             VFS entry ptr   : pointer to the esp_vfs_fs_ops_t struct used internally when resolving the calls

  参数:

    fp -- File descriptor where data will be dumped

### Structures

struct esp_vfs_t

  VFS definition structure.

  This structure should be filled with pointers to corresponding FS driver functions.

  VFS component will translate all FDs so that the filesystem implementation sees them starting at zero. The caller sees a global FD which is prefixed with an pre-filesystem-implementation.

  Some FS implementations expect some state (e.g. pointer to some structure) to be passed in as a first argument. For these implementations, populate the members of this structure which have _p suffix, set flags member to ESP_VFS_FLAG_CONTEXT_PTR and provide the context pointer to esp_vfs_register function. If the implementation doesn't use this extra argument, populate the members without _p suffix and set flags member to ESP_VFS_FLAG_DEFAULT.

  If the FS driver doesn't provide some of the functions, set corresponding members to NULL.

  备注

  Prefer using esp_vfs_fs_ops_t with esp_vfs_register_fs*() instead.

  Public Members

  int flags

    ESP_VFS_FLAG_CONTEXT_PTR and/or ESP_VFS_FLAG_READONLY_FS or ESP_VFS_FLAG_DEFAULT

  ssize_t (*write_p)(void *p, int fd, const void *data, size_t size)

    Write with context pointer

  ssize_t (*write)(int fd, const void *data, size_t size)

    Write without context pointer

  off_t (*lseek_p)(void *p, int fd, off_t size, int mode)

    Seek with context pointer

  off_t (*lseek)(int fd, off_t size, int mode)

    Seek without context pointer

  ssize_t (*read_p)(void *ctx, int fd, void *dst, size_t size)

    Read with context pointer

  ssize_t (*read)(int fd, void *dst, size_t size)

    Read without context pointer

  ssize_t (*pread_p)(void *ctx, int fd, void *dst, size_t size, off_t offset)

    pread with context pointer

  ssize_t (*pread)(int fd, void *dst, size_t size, off_t offset)

    pread without context pointer

  ssize_t (*pwrite_p)(void *ctx, int fd, const void *src, size_t size, off_t offset)

    pwrite with context pointer

  ssize_t (*pwrite)(int fd, const void *src, size_t size, off_t offset)

    pwrite without context pointer

  int (*open_p)(void *ctx, const char *path, int flags, int mode)

    open with context pointer

  int (*open)(const char *path, int flags, int mode)

    open without context pointer

  int (*close_p)(void *ctx, int fd)

    close with context pointer

  int (*close)(int fd)

    close without context pointer

  int (*fstat_p)(void *ctx, int fd, struct stat *st)

    fstat with context pointer

  int (*fstat)(int fd, struct stat *st)

    fstat without context pointer

  int (*stat_p)(void *ctx, const char *path, struct stat *st)

    stat with context pointer

  int (*stat)(const char *path, struct stat *st)

    stat without context pointer

  int (*link_p)(void *ctx, const char *n1, const char *n2)

    link with context pointer

  int (*link)(const char *n1, const char *n2)

    link without context pointer

  int (*unlink_p)(void *ctx, const char *path)

    unlink with context pointer

  int (*unlink)(const char *path)

    unlink without context pointer

  int (*rename_p)(void *ctx, const char *src, const char *dst)

    rename with context pointer

  int (*rename)(const char *src, const char *dst)

    rename without context pointer

  DIR *(*opendir_p)(void *ctx, const char *name)

    opendir with context pointer

  DIR *(*opendir)(const char *name)

    opendir without context pointer

  struct dirent *(*readdir_p)(void *ctx, DIR *pdir)

    readdir with context pointer

  struct dirent *(*readdir)(DIR *pdir)

    readdir without context pointer

  int (*readdir_r_p)(void *ctx, DIR *pdir, struct dirent *entry, struct dirent **out_dirent)

    readdir_r with context pointer

  int (*readdir_r)(DIR *pdir, struct dirent *entry, struct dirent **out_dirent)

    readdir_r without context pointer

  long (*telldir_p)(void *ctx, DIR *pdir)

    telldir with context pointer

  long (*telldir)(DIR *pdir)

    telldir without context pointer

  void (*seekdir_p)(void *ctx, DIR *pdir, long offset)

    seekdir with context pointer

  void (*seekdir)(DIR *pdir, long offset)

    seekdir without context pointer

  int (*closedir_p)(void *ctx, DIR *pdir)

    closedir with context pointer

  int (*closedir)(DIR *pdir)

    closedir without context pointer

  int (*mkdir_p)(void *ctx, const char *name, mode_t mode)

    mkdir with context pointer

  int (*mkdir)(const char *name, mode_t mode)

    mkdir without context pointer

  int (*rmdir_p)(void *ctx, const char *name)

    rmdir with context pointer

  int (*rmdir)(const char *name)

    rmdir without context pointer

  int (*fcntl_p)(void *ctx, int fd, int cmd, int arg)

    fcntl with context pointer

  int (*fcntl)(int fd, int cmd, int arg)

    fcntl without context pointer

  int (*ioctl_p)(void *ctx, int fd, int cmd, va_list args)

    ioctl with context pointer

  int (*ioctl)(int fd, int cmd, va_list args)

    ioctl without context pointer

  int (*fsync_p)(void *ctx, int fd)

    fsync with context pointer

  int (*fsync)(int fd)

    fsync without context pointer

  int (*access_p)(void *ctx, const char *path, int amode)

    access with context pointer

  int (*access)(const char *path, int amode)

    access without context pointer

  int (*truncate_p)(void *ctx, const char *path, off_t length)

    truncate with context pointer

  int (*truncate)(const char *path, off_t length)

    truncate without context pointer

  int (*ftruncate_p)(void *ctx, int fd, off_t length)

    ftruncate with context pointer

  int (*ftruncate)(int fd, off_t length)

    ftruncate without context pointer

  int (*utime_p)(void *ctx, const char *path, const struct utimbuf *times)

    utime with context pointer

  int (*utime)(const char *path, const struct utimbuf *times)

    utime without context pointer

  esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, esp_vfs_select_sem_t sem, void **end_select_args)

    start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS

  int (*socket_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)

    socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS

  void (*stop_socket_select)(void *sem)

    called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver

  void (*stop_socket_select_isr)(void *sem, BaseType_t *woken)

    stop_socket_select which can be called from ISR; set only for the socket driver

  void *(*get_socket_select_semaphore)(void)

    end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS

  esp_err_t (*end_select)(void *end_select_args)

    get_socket_select_semaphore returns semaphore allocated in the socket driver; set only for the socket driver

### Macros

MAX_FDS

  Maximum number of (global) file descriptors.

ESP_VFS_PATH_MAX

  Maximum length of path prefix (not including zero terminator)

ESP_VFS_FLAG_DEFAULT

  Default value of flags member in esp_vfs_fs_ops_t structure.

ESP_VFS_FLAG_CONTEXT_PTR

  Flag which indicates that FS needs extra context pointer in syscalls.

ESP_VFS_FLAG_READONLY_FS

  Flag which indicates that FS is located on read-only partition.

ESP_VFS_FLAG_STATIC

  Flag which indicates that VFS structure should be freed upon unregistering.

  备注

  Free if false, do not free if true

### Header File

- components/vfs/include/esp_vfs_ops.h

- This header file can be included with:

  >     #include "esp_vfs_ops.h"

- This header file is a part of the API provided by the `vfs` component. To declare that your component depends on `vfs`, add the following to your CMakeLists.txt:

  >     REQUIRES vfs

  > or

  >     PRIV_REQUIRES vfs

### Functions

esp_err_t esp_vfs_register_fs(const char *base_path, const esp_vfs_fs_ops_t *vfs, int flags, void *ctx)

  Register a virtual filesystem for given path prefix.

  参数:

    - base_path -- file path prefix associated with the filesystem. Must be a zero-terminated C string, may be empty. If not empty, must be up to ESP_VFS_PATH_MAX characters long, and at least 2 characters long. Name must start with a "/" and must not end with "/". For example, "/data" or "/dev/spi" are valid. These VFSes would then be called to handle file paths such as "/data/myfile.txt" or "/dev/spi/0". In the special case of an empty base_path, a "fallback" VFS is registered. Such VFS will handle paths which are not matched by any other registered VFS.

    - vfs -- Pointer to esp_vfs_fs_ops_t, a structure which maps syscalls to the filesystem driver functions. VFS component does not assume ownership of this struct, but see flags for more info

    - flags -- Set of binary flags controlling how the registered FS should be treated

      - ESP_VFS_FLAG_STATIC - if this flag is specified VFS assumes the provided esp_vfs_fs_ops_t and all its subcomponents are statically allocated, if it is not enabled a deep copy of the provided struct will be created, which will be managed by the VFS component

      - ESP_VFS_FLAG_CONTEXT_PTR - If set, the VFS will use the context-aware versions of the filesystem operation functions (suffixed with `_p`) in `esp_vfs_fs_ops_t` and its subcomponents. The `ctx` parameter will be passed as the context argument when these functions are invoked.

    - ctx -- Context pointer for fs operation functions, see the ESP_VFS_FLAG_CONTEXT_PTR. Should be `NULL` if not used.

  返回:

    ESP_OK if successful, ESP_ERR_NO_MEM if too many FSes are registered.

esp_err_t esp_vfs_register_fs_with_id(const esp_vfs_fs_ops_t *vfs, int flags, void *ctx, esp_vfs_id_t *id)

  Analog of esp_vfs_register_with_id which accepts esp_vfs_fs_ops_t instead.

esp_err_t esp_vfs_unregister_fs(const char *base_path)

  Alias for esp_vfs_unregister for naming consistency

esp_err_t esp_vfs_unregister_fs_with_id(esp_vfs_id_t id)

  Alias for esp_vfs_unregister_with_id for naming consistency

### Structures

struct esp_vfs_select_sem_t

  VFS semaphore type for select()

  Public Members

  bool is_sem_local

    type of "sem" is SemaphoreHandle_t when true, defined by socket driver otherwise

  void *sem

    semaphore instance

struct esp_vfs_dir_ops_t

  Struct containing function pointers to directory related functionality.

  Public Members

  const esp_vfs_stat_ctx_op_t stat_p

    stat with context pointer

  const esp_vfs_stat_op_t stat

    stat without context pointer

  const esp_vfs_link_ctx_op_t link_p

    link with context pointer

  const esp_vfs_link_op_t link

    link without context pointer

  const esp_vfs_unlink_ctx_op_t unlink_p

    unlink with context pointer

  const esp_vfs_unlink_op_t unlink

    unlink without context pointer

  const esp_vfs_rename_ctx_op_t rename_p

    rename with context pointer

  const esp_vfs_rename_op_t rename

    rename without context pointer

  const esp_vfs_opendir_ctx_op_t opendir_p

    opendir with context pointer

  const esp_vfs_opendir_op_t opendir

    opendir without context pointer

  const esp_vfs_readdir_ctx_op_t readdir_p

    readdir with context pointer

  const esp_vfs_readdir_op_t readdir

    readdir without context pointer

  const esp_vfs_readdir_r_ctx_op_t readdir_r_p

    readdir_r with context pointer

  const esp_vfs_readdir_r_op_t readdir_r

    readdir_r without context pointer

  const esp_vfs_telldir_ctx_op_t telldir_p

    telldir with context pointer

  const esp_vfs_telldir_op_t telldir

    telldir without context pointer

  const esp_vfs_seekdir_ctx_op_t seekdir_p

    seekdir with context pointer

  const esp_vfs_seekdir_op_t seekdir

    seekdir without context pointer

  const esp_vfs_closedir_ctx_op_t closedir_p

    closedir with context pointer

  const esp_vfs_closedir_op_t closedir

    closedir without context pointer

  const esp_vfs_mkdir_ctx_op_t mkdir_p

    mkdir with context pointer

  const esp_vfs_mkdir_op_t mkdir

    mkdir without context pointer

  const esp_vfs_rmdir_ctx_op_t rmdir_p

    rmdir with context pointer

  const esp_vfs_rmdir_op_t rmdir

    rmdir without context pointer

  const esp_vfs_access_ctx_op_t access_p

    access with context pointer

  const esp_vfs_access_op_t access

    access without context pointer

  const esp_vfs_truncate_ctx_op_t truncate_p

    truncate with context pointer

  const esp_vfs_truncate_op_t truncate

    truncate without context pointer

  const esp_vfs_ftruncate_ctx_op_t ftruncate_p

    ftruncate with context pointer

  const esp_vfs_ftruncate_op_t ftruncate

    ftruncate without context pointer

  const esp_vfs_utime_ctx_op_t utime_p

    utime with context pointer

  const esp_vfs_utime_op_t utime

    utime without context pointer

struct esp_vfs_select_ops_t

  Struct containing function pointers to select related functionality.

  Public Members

  const esp_vfs_start_select_op_t start_select

    start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS

  const esp_vfs_socket_select_op_t socket_select

    socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS

  const esp_vfs_stop_socket_select_op_t stop_socket_select

    called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver

  const esp_vfs_stop_socket_select_isr_op_t stop_socket_select_isr

    stop_socket_select which can be called from ISR; set only for the socket driver

  const esp_vfs_get_socket_select_semaphore_op_t get_socket_select_semaphore

    end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS

  const esp_vfs_end_select_op_t end_select

    get_socket_select_semaphore returns semaphore allocated in the socket driver; set only for the socket driver

struct esp_vfs_fs_ops_t

  Main struct of the minified vfs API, containing basic function pointers as well as pointers to the other subcomponents.

  Public Members

  const esp_vfs_write_ctx_op_t write_p

    Write with context pointer

  const esp_vfs_write_op_t write

    Write without context pointer

  const esp_vfs_lseek_ctx_op_t lseek_p

    Seek with context pointer

  const esp_vfs_lseek_op_t lseek

    Seek without context pointer

  const esp_vfs_read_ctx_op_t read_p

    Read with context pointer

  const esp_vfs_read_op_t read

    Read without context pointer

  const esp_vfs_pread_ctx_op_t pread_p

    pread with context pointer

  const esp_vfs_pread_op_t pread

    pread without context pointer

  const esp_vfs_pwrite_ctx_op_t pwrite_p

    pwrite with context pointer

  const esp_vfs_pwrite_op_t pwrite

    pwrite without context pointer

  const esp_vfs_open_ctx_op_t open_p

    open with context pointer

  const esp_vfs_open_op_t open

    open without context pointer

  const esp_vfs_close_ctx_op_t close_p

    close with context pointer

  const esp_vfs_close_op_t close

    close without context pointer

  const esp_vfs_fstat_ctx_op_t fstat_p

    fstat with context pointer

  const esp_vfs_fstat_op_t fstat

    fstat without context pointer

  const esp_vfs_fcntl_ctx_op_t fcntl_p

    fcntl with context pointer

  const esp_vfs_fcntl_op_t fcntl

    fcntl without context pointer

  const esp_vfs_ioctl_ctx_op_t ioctl_p

    ioctl with context pointer

  const esp_vfs_ioctl_op_t ioctl

    ioctl without context pointer

  const esp_vfs_fsync_ctx_op_t fsync_p

    fsync with context pointer

  const esp_vfs_fsync_op_t fsync

    fsync without context pointer

  const esp_vfs_dir_ops_t *const dir

    pointer to the dir subcomponent

  const esp_vfs_select_ops_t *const select

    pointer to the select subcomponent

### Type Definitions

typedef int esp_vfs_id_t

typedef int (*esp_vfs_stat_ctx_op_t)(void *ctx, const char *path, struct stat *st)

  stat with context pointer

typedef int (*esp_vfs_stat_op_t)(const char *path, struct stat *st)

  stat without context pointer

typedef int (*esp_vfs_link_ctx_op_t)(void *ctx, const char *n1, const char *n2)

  link with context pointer

typedef int (*esp_vfs_link_op_t)(const char *n1, const char *n2)

  link without context pointer

typedef int (*esp_vfs_unlink_ctx_op_t)(void *ctx, const char *path)

  unlink with context pointer

typedef int (*esp_vfs_unlink_op_t)(const char *path)

  unlink without context pointer

typedef int (*esp_vfs_rename_ctx_op_t)(void *ctx, const char *src, const char *dst)

  rename with context pointer

typedef int (*esp_vfs_rename_op_t)(const char *src, const char *dst)

  rename without context pointer

typedef DIR *(*esp_vfs_opendir_ctx_op_t)(void *ctx, const char *name)

  opendir with context pointer

typedef DIR *(*esp_vfs_opendir_op_t)(const char *name)

  opendir without context pointer

typedef struct dirent *(*esp_vfs_readdir_ctx_op_t)(void *ctx, DIR *pdir)

  readdir with context pointer

typedef struct dirent *(*esp_vfs_readdir_op_t)(DIR *pdir)

  readdir without context pointer

typedef int (*esp_vfs_readdir_r_ctx_op_t)(void *ctx, DIR *pdir, struct dirent *entry, struct dirent **out)

  readdir_r with context pointer

typedef int (*esp_vfs_readdir_r_op_t)(DIR *pdir, struct dirent *entry, struct dirent **out)

  readdir_r without context pointer

typedef long (*esp_vfs_telldir_ctx_op_t)(void *ctx, DIR *pdir)

  telldir with context pointer

typedef long (*esp_vfs_telldir_op_t)(DIR *pdir)

  telldir without context pointer

typedef void (*esp_vfs_seekdir_ctx_op_t)(void *ctx, DIR *pdir, long offset)

  seekdir with context pointer

typedef void (*esp_vfs_seekdir_op_t)(DIR *pdir, long offset)

  seekdir without context pointer

typedef int (*esp_vfs_closedir_ctx_op_t)(void *ctx, DIR *pdir)

  closedir with context pointer

typedef int (*esp_vfs_closedir_op_t)(DIR *pdir)

  closedir without context pointer

typedef int (*esp_vfs_mkdir_ctx_op_t)(void *ctx, const char *name, mode_t mode)

  mkdir with context pointer

typedef int (*esp_vfs_mkdir_op_t)(const char *name, mode_t mode)

  mkdir without context pointer

typedef int (*esp_vfs_rmdir_ctx_op_t)(void *ctx, const char *name)

  rmdir with context pointer

typedef int (*esp_vfs_rmdir_op_t)(const char *name)

  rmdir without context pointer

typedef int (*esp_vfs_access_ctx_op_t)(void *ctx, const char *path, int amode)

  access with context pointer

typedef int (*esp_vfs_access_op_t)(const char *path, int amode)

  access without context pointer

typedef int (*esp_vfs_truncate_ctx_op_t)(void *ctx, const char *path, off_t length)

  truncate with context pointer

typedef int (*esp_vfs_truncate_op_t)(const char *path, off_t length)

  truncate without context pointer

typedef int (*esp_vfs_ftruncate_ctx_op_t)(void *ctx, int fd, off_t length)

  ftruncate with context pointer

typedef int (*esp_vfs_ftruncate_op_t)(int fd, off_t length)

  ftruncate without context pointer

typedef int (*esp_vfs_utime_ctx_op_t)(void *ctx, const char *path, const struct utimbuf *times)

  utime with context pointer

typedef int (*esp_vfs_utime_op_t)(const char *path, const struct utimbuf *times)

  utime without context pointer

typedef esp_err_t (*esp_vfs_start_select_op_t)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, esp_vfs_select_sem_t sem, void **end_select_args)

typedef int (*esp_vfs_socket_select_op_t)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout)

typedef void (*esp_vfs_stop_socket_select_op_t)(void *sem)

typedef void (*esp_vfs_stop_socket_select_isr_op_t)(void *sem, BaseType_t *woken)

typedef void *(*esp_vfs_get_socket_select_semaphore_op_t)(void)

typedef esp_err_t (*esp_vfs_end_select_op_t)(void *end_select_args)

typedef ssize_t (*esp_vfs_write_ctx_op_t)(void *ctx, int fd, const void *data, size_t size)

  Write with context pointer

typedef ssize_t (*esp_vfs_write_op_t)(int fd, const void *data, size_t size)

  Write without context pointer

typedef off_t (*esp_vfs_lseek_ctx_op_t)(void *ctx, int fd, off_t size, int mode)

  Seek with context pointer

typedef off_t (*esp_vfs_lseek_op_t)(int fd, off_t size, int mode)

  Seek without context pointer

typedef ssize_t (*esp_vfs_read_ctx_op_t)(void *ctx, int fd, void *dst, size_t size)

  Read with context pointer

typedef ssize_t (*esp_vfs_read_op_t)(int fd, void *dst, size_t size)

  Read without context pointer

typedef ssize_t (*esp_vfs_pread_ctx_op_t)(void *ctx, int fd, void *dst, size_t size, off_t offset)

  pread with context pointer

typedef ssize_t (*esp_vfs_pread_op_t)(int fd, void *dst, size_t size, off_t offset)

  pread without context pointer

typedef ssize_t (*esp_vfs_pwrite_ctx_op_t)(void *ctx, int fd, const void *src, size_t size, off_t offset)

  pwrite with context pointer

typedef ssize_t (*esp_vfs_pwrite_op_t)(int fd, const void *src, size_t size, off_t offset)

  pwrite without context pointer

typedef int (*esp_vfs_open_ctx_op_t)(void *ctx, const char *path, int flags, int mode)

  open with context pointer

typedef int (*esp_vfs_open_op_t)(const char *path, int flags, int mode)

  open without context pointer

typedef int (*esp_vfs_close_ctx_op_t)(void *ctx, int fd)

  close with context pointer

typedef int (*esp_vfs_close_op_t)(int fd)

  close without context pointer

typedef int (*esp_vfs_fstat_ctx_op_t)(void *ctx, int fd, struct stat *st)

  fstat with context pointer

typedef int (*esp_vfs_fstat_op_t)(int fd, struct stat *st)

  fstat without context pointer

typedef int (*esp_vfs_fcntl_ctx_op_t)(void *ctx, int fd, int cmd, int arg)

  fcntl with context pointer

typedef int (*esp_vfs_fcntl_op_t)(int fd, int cmd, int arg)

  fcntl without context pointer

typedef int (*esp_vfs_ioctl_ctx_op_t)(void *ctx, int fd, int cmd, va_list args)

  ioctl with context pointer

typedef int (*esp_vfs_ioctl_op_t)(int fd, int cmd, va_list args)

  ioctl without context pointer

typedef int (*esp_vfs_fsync_ctx_op_t)(void *ctx, int fd)

  fsync with context pointer

typedef int (*esp_vfs_fsync_op_t)(int fd)

  fsync without context pointer

### Header File

- components/vfs/include/esp_vfs_dev.h

- This header file can be included with:

  >     #include "esp_vfs_dev.h"

- This header file is a part of the API provided by the `vfs` component. To declare that your component depends on `vfs`, add the following to your CMakeLists.txt:

  >     REQUIRES vfs

  > or

  >     PRIV_REQUIRES vfs

### Header File

- components/esp_driver_uart/include/driver/uart_vfs.h

- This header file can be included with:

  >     #include "driver/uart_vfs.h"

- This header file is a part of the API provided by the `esp_driver_uart` component. To declare that your component depends on `esp_driver_uart`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_uart

  > or

  >     PRIV_REQUIRES esp_driver_uart

### Functions

void uart_vfs_dev_register(void)

  Add /dev/uart virtual filesystem driver.

  This function is called from startup code to enable serial output

int uart_vfs_dev_port_set_rx_line_endings(int uart_num, esp_line_endings_t mode)

  Set the line endings expected to be received on specified UART.

  This specifies the conversion between line endings received on UART and newlines ('

  ', LF) passed into stdin:

  - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF

  - ESP_LINE_ENDINGS_CR: convert CR to LF

  - ESP_LINE_ENDINGS_LF: no modification

  备注

  this function is not thread safe w.r.t. reading from UART

  参数:

    - uart_num -- the UART number

    - mode -- line endings to send to UART

  返回:

    0 if succeeded, or -1 when an error (specified by errno) have occurred.

int uart_vfs_dev_port_set_tx_line_endings(int uart_num, esp_line_endings_t mode)

  Set the line endings to sent to specified UART.

  This specifies the conversion between newlines ('

  ', LF) on stdout and line endings sent over UART:

  - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF

  - ESP_LINE_ENDINGS_CR: convert LF to CR

  - ESP_LINE_ENDINGS_LF: no modification

  备注

  this function is not thread safe w.r.t. writing to UART

  参数:

    - uart_num -- the UART number

    - mode -- line endings to send to UART

  返回:

    0 if succeeded, or -1 when an error (specified by errno) have occurred.

void uart_vfs_dev_use_nonblocking(int uart_num)

  set VFS to use simple functions for reading and writing UART

  Read is non-blocking, write is busy waiting until TX FIFO has enough space. These functions are used by default.

  参数:

    uart_num -- UART peripheral number

void uart_vfs_dev_use_driver(int uart_num)

  set VFS to use UART driver for reading and writing

  备注

  Application must configure UART driver before calling these functions With these functions, read and write are blocking and interrupt-driven.

  参数:

    uart_num -- UART peripheral number

### Header File

- components/vfs/include/esp_vfs_eventfd.h

- This header file can be included with:

  >     #include "esp_vfs_eventfd.h"

- This header file is a part of the API provided by the `vfs` component. To declare that your component depends on `vfs`, add the following to your CMakeLists.txt:

  >     REQUIRES vfs

  > or

  >     PRIV_REQUIRES vfs

### Functions

esp_err_t esp_vfs_eventfd_register(const esp_vfs_eventfd_config_t *config)

  Registers the event vfs.

  返回:

    ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are registered.

esp_err_t esp_vfs_eventfd_unregister(void)

  Unregisters the event vfs.

  返回:

    ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix hasn't been registered

int eventfd(unsigned int initval, int flags)

### Structures

struct esp_vfs_eventfd_config_t

  Eventfd vfs initialization settings.

  Public Members

  size_t max_fds

    The maximum number of eventfds supported

### Macros

EFD_SUPPORT_ISR

ESP_VFS_EVENTD_CONFIG_DEFAULT()

### Header File

- components/vfs/include/esp_vfs_null.h

- This header file can be included with:

  >     #include "esp_vfs_null.h"

- This header file is a part of the API provided by the `vfs` component. To declare that your component depends on `vfs`, add the following to your CMakeLists.txt:

  >     REQUIRES vfs

  > or

  >     PRIV_REQUIRES vfs

### Functions

esp_err_t esp_vfs_null_register(void)

  Register filesystem for /dev/null.

  返回:

    ESP_OK on success; any other value indicates an error

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。