feat(storage/vfs): update docs to new version of API

This commit is contained in:
Tomáš Rohlínek
2024-10-31 13:25:36 +01:00
parent 6b07039281
commit e5ab487e3c
2 changed files with 63 additions and 43 deletions

View File

@ -137,7 +137,7 @@ To send application output to a custom channel (for example, a WebSocket connect
- ``fstat()`` — recommended, to provide correct buffering behavior for the I/O streams
- ``fcntl()`` — only if non-blocking I/O has to be supported
Once you have created a custom VFS driver, use ``esp_vfs_register()`` to register it with VFS. Then, use ``fopen()`` to redirect ``stdout`` and ``stderr`` to the custom channel. For example:
Once you have created a custom VFS driver, use :cpp:func:`esp_vfs_register_fs()` to register it with VFS. Then, use ``fopen()`` to redirect ``stdout`` and ``stderr`` to the custom channel. For example:
.. code-block:: c

View File

@ -3,70 +3,96 @@ Virtual Filesystem Component
:link_to_translation:`zh_CN:[中文]`
Overview
--------
Virtual filesystem (VFS) component provides a unified interface for drivers which can perform operations on file-like objects. These can be real filesystems (FAT, SPIFFS, etc.) or device drivers which provide a file-like interface.
This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At a high level, each FS driver is associated with some path prefix. When one of C library functions needs to open a file, the VFS component searches for the FS driver associated with the file path and forwards the call to that driver. VFS also forwards read, write, and other calls for the given file to the same FS driver.
This component allows C library functions, such as fopen and fprintf, to work with FS drivers. At a high level, each FS driver is mounted at some path prefix. When one of C library functions needs to open a file, the VFS component searches for the FS driver associated with the file path and forwards the call to that driver. VFS also forwards read, write, and other calls for the given file to the same FS driver.
For example, one can register a FAT filesystem driver with the ``/fat`` prefix and call ``fopen("/fat/file.txt", "w")``. Then the VFS component calls the function ``open`` of the FAT driver and pass the argument ``/file.txt`` to it together with appropriate mode flags. All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver.
For example, one can mount a FAT filesystem driver at the ``/fat`` prefix and call ``fopen("/fat/file.txt", "w")``. Then the VFS component calls the function ``open`` of the FAT driver and pass the argument ``/file.txt`` to it together with appropriate mode flags. All subsequent calls to C library functions for the returned ``FILE*`` stream will also be forwarded to the FAT driver.
FS Registration
---------------
To register an FS driver, an application needs to define an instance of the :cpp:type:`esp_vfs_t` structure and populate it with function pointers to FS APIs:
.. note:: For previous version of the API (using :cpp:type:`esp_vfs_t`) see documentation for previous release.
To register an FS driver, an application needs to define an instance of the :cpp:type:`esp_vfs_fs_ops_t` structure and populate it with function pointers to FS APIs:
.. highlight:: c
::
esp_vfs_t myfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
.write = &myfs_write,
.open = &myfs_open,
// Both esp_vfs_fs_ops_t and its subcomponents have to have static storage
static const esp_vfs_dir_ops_t myfs_dir = {
.fstat = &myfs_fstat,
.close = &myfs_close,
.read = &myfs_read,
};
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));
Depending on the way how the FS driver declares its API functions, either ``read``, ``write``, etc., or ``read_p``, ``write_p``, etc., should be used.
Case 1: API functions are declared without an extra context pointer (the FS driver is a singleton)::
ssize_t myfs_write(int fd, const void * data, size_t size);
// In definition of esp_vfs_t:
.flags = ESP_VFS_FLAG_DEFAULT,
static const esp_vfs_fs_ops_t myfs = {
.write = &myfs_write,
// ... other members initialized
.open = &myfs_open,
.close = &myfs_close,
.read = &myfs_read,
.dir = &myfs_dir,
};
// When registering FS, context pointer (the third argument) is NULL:
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));
ESP_ERROR_CHECK(esp_vfs_register_fs("/data", &myfs, ESP_VFS_FLAG_STATIC, NULL));
Case 2: API functions are declared with an extra context pointer (the FS driver supports multiple instances)::
Non-static :cpp:type:`esp_vfs_fs_ops_t`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The recommended approach for registering filesystem is to use statically allocated :cpp:type:`esp_vfs_fs_ops_t` alongside ``ESP_VFS_FLAG_STATIC``, as it is more memory efficient. In cases where using static allocation is not possible, ``ESP_VFS_FLAG_STATIC`` can replaced with ``ESP_VFS_FLAG_DEFAULT``. This tells VFS to make a deep copy of the passed structure in RAM, this copy will be managed by VFS component.
.. highlight:: c
::
// Possibly local scope
{
esp_vfs_dir_ops_t myfs_dir = {
.fstat = &myfs_fstat,
};
bool some_condition = false;
esp_vfs_fs_ops_t myfs = {
.write = some_condition ? &myfs_special_write : &myfs_write,
// ... other members
.dir = &myfs_dir,
};
ESP_ERROR_CHECK(esp_vfs_register_fs("/data", &myfs, ESP_VFS_FLAG_DEFAULT, NULL));
}
Context aware filesystem
^^^^^^^^^^^^^^^^^^^^^^^^
In some cases it might be beneficial, or even necessary to pass some context to the filesystem functions, such as mountpoint specific file descriptor table, when multiple instances of FS mounted. For this reason the :cpp:type:`esp_vfs_fs_ops_t` contains second version of each member with ``_p`` suffix, for example for ``read`` there is ``read_p``, these functions have additional first argument. When registering the FS, ``ESP_VFS_FLAG_CONTEXT_PTR`` needs to be specified and the pointer passed as the last argument.
::
ssize_t myfs_write(myfs_t* fs, int fd, const void * data, size_t size);
// In definition of esp_vfs_t:
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
.write_p = &myfs_write,
// ... other members initialized
// When registering FS, pass the FS context pointer into the third argument
// When registering FS, pass the ESP_VFS_FLAG_CONTEXT_PTR flag, alongside FS context pointer into the third argument
// (hypothetical myfs_mount function is used for illustrative purposes)
myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size);
ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1));
ESP_ERROR_CHECK(esp_vfs_register_fs("/data1", &myfs, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, myfs_inst1));
// Can register another instance:
myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2));
ESP_ERROR_CHECK(esp_vfs_register_fs("/data2", &myfs, ESP_VFS_FLAG_STATIC | ESP_VFS_FLAG_CONTEXT_PTR, myfs_inst2));
Synchronous Input/Output Multiplexing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-------------------------------------
Synchronous input/output multiplexing by :cpp:func:`select` is supported in the VFS component. The implementation works in the following way.
@ -82,8 +108,9 @@ Synchronous input/output multiplexing by :cpp:func:`select` is supported in the
6. The :cpp:func:`select` call ends and returns the appropriate results.
Non-Socket VFS Drivers
""""""""""""""""""""""
^^^^^^^^^^^^^^^^^^^^^^
If you want to use :cpp:func:`select` with a file descriptor belonging to a non-socket VFS driver, then you need to register the driver with functions :cpp:func:`start_select` and :cpp:func:`end_select` similarly to the following example:
@ -91,7 +118,7 @@ If you want to use :cpp:func:`select` with a file descriptor belonging to a non-
::
// In definition of esp_vfs_t:
// In definition of esp_vfs_select_ops_t:
.start_select = &uart_start_select,
.end_select = &uart_end_select,
// ... other members initialized
@ -113,7 +140,7 @@ Please check the following examples that demonstrate the use of :cpp:func:`selec
Socket VFS Drivers
""""""""""""""""""
^^^^^^^^^^^^^^^^^^
A socket VFS driver is using its own internal implementation of :cpp:func:`select` and non-socket VFS drivers notify it upon read/write/error conditions.
@ -123,7 +150,7 @@ A socket VFS driver needs to be registered with the following functions defined:
::
// In definition of esp_vfs_t:
// In definition of 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,
@ -146,6 +173,7 @@ Please see :component_file:`lwip/port/esp32xx/vfs_lwip.c` for a reference socket
You should not change the socket driver during an active :cpp:func:`select` call or you might experience some undefined behavior.
Paths
-----
@ -195,16 +223,6 @@ Standard I/O streams (``stdin``, ``stdout``, ``stderr``) are mapped to file desc
Note that creating an eventfd with ``EFD_SUPPORT_ISR`` will cause interrupts to be temporarily disabled when reading, writing the file and during the beginning and the ending of the ``select()`` when this file is set.
Minified VFS
------------
To minimize RAM usage, an alternative version of :cpp:func:`esp_vfs_register` function, :cpp:func:`esp_vfs_register_fs` is provided. This version accepts :cpp:class:`esp_vfs_fs_ops_t` instead of :cpp:class:`esp_vfs_t` alongside separate argument for OR-ed flags. Unlike :cpp:func:`esp_vfs_register`, it can handle statically allocated struct, as long as the ``ESP_VFS_FLAG_STATIC`` is provided.
The :cpp:class:`esp_vfs_fs_ops_t` is split into separate structs based on features (directory operations, select support, termios support, ...). The main struct contains the basic functions (``read``, ``write``, ...), alongside pointers to the feature-specific structs. These pointers can be ``NULL`` indicating lack of support for all the functions provided by that struct, which decreases the required memory.
Internally the VFS component uses this version of API, with additional steps to convert the :cpp:class:`esp_vfs_t` to :cpp:class:`esp_vfs_fs_ops_t` upon registration.
Well Known VFS Devices
----------------------
@ -214,6 +232,7 @@ IDF defines several VFS devices that can be used by applications. These devices
* ``/dev/null`` - file that discards all data written to it and returns EOF when read. It is automatically created if :ref:`CONFIG_VFS_INITIALIZE_DEV_NULL` is enabled.
* ``/dev/console`` - file that is connected to the primary and secondary outputs specified in the menuconfig by :ref:`CONFIG_ESP_CONSOLE_UART` and :ref:`CONFIG_ESP_CONSOLE_SECONDARY` respectively. More information can be found here :doc:`../../api-guides/stdio`.
Application Examples
--------------------
@ -223,6 +242,7 @@ Application Examples
- :example:`storage/semihost_vfs` demonstrates how to use the semihosting VFS driver, including registering a host directory, redirecting stdout from UART to a file on the host, and reading and printing the content of a text file.
API Reference
-------------