Программирование ARM ESP VFS: виртуальная файловая система Tue, October 08 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

ESP VFS: виртуальная файловая система Печать
Добавил(а) microsin   

В этой статье приведен перевод документации по компоненту виртуальной файловой системы (VFS). Он предоставляет унифицированный интерфейс для драйверов, которые могут работать с объектами наподобие файлов. Это могут быть реальные файловые системы (FAT, SPIFFS [3], и т. п.), либо драйверы устройств, которые предоставляют интерфейс для работы с файлами (см. описание модуля SPP [2]).

Примечание: незнакомые термины и сокращения см. в Словарике, в конце статьи.

Компонент VFS позволяет использовать библиотечные функции языка C, такие как fopen и fprintf, чтобы работать с драйверами файловой системы (FS). На верхнем уровне каждый драйвер FS связывается с некоторым префиксом пути. Когда одной из библиотечных C-функций нужно открыть файл, компонент VFS ищет драйвер FS, связанный с путем до файла, и перенаправляет этот вызов открытия файла соответствующему драйверу FS. VFS также перенаправляет операции read, write, и другие вызовы операций для указанного файла к тому же драйверу FS.

Например, может быть зарегистрирован драйвер файловой системы FAT с префиксом пути /fat, и произошел вызов fopen("/fat/file.txt", "w"). Компонент VFS тогда вызовет функцию open драйвера FAT, и передаст ему аргумент /file.txt вместе с соответствующим флагом режима открытия файла (w). Все последующие вызовы к библиотечным функциям C для возвращенного потока файла (FILE* stream) будут также перенаправлены драйверу FAT.

[Регистрация файловой системы]

Чтобы зарегистрировать драйвер FS, приложению надо определить экземпляр структуры esp_vfs_t, и заполнить её поля указателями на API-функции этого драйвера:

esp_vfs_t myfs = {
   .flags = ESP_VFS_FLAG_DEFAULT,
   .write = &myfs_write,
   .open = &myfs_open,
   .fstat = &myfs_fstat,
   .close = &myfs_close,
   .read = &myfs_read,
};
 
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));

В зависимости от того, как драйвер FS декларирует свои API-функции, используются имена либо read, write, и т. д., либо read_p, write_p, и т. д.

Вариант 1: API-функции декларируются без дополнительного указателя контекста (например, используется одиночный драйвер FS):

ssize_t myfs_write (int fd, const void * data, size_t size);
 
// В определении esp_vfs_t:
   .flags = ESP_VFS_FLAG_DEFAULT,
   .write = &myfs_write,
   // ... инициализация других указателей на функции драйвера
 
// При регистрации FS указатель на контекст будет NULL:
ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL));

Вариант 2: API-функции декларируются с дополнительным указателем на контекст (т. е. драйвер FS поддерживает несколько своих экземпляров, например несколько отдельных устройств хранения данных):

ssize_t myfs_write (myfs_t* fs, int fd, const void * data, size_t size);
 
// В определении esp_vfs_t:
   .flags = ESP_VFS_FLAG_CONTEXT_PTR,
   .write_p = &myfs_write,
   // ... инициализация других указателей на функции драйвера
 
// При регистрации FS в третьем аргументе передается указатель на контекст FS
// (используется гипотетическая функция myfs_mount в целях иллюстрации):
myfs_t* myfs_inst1 = myfs_mount(partition1->offset, partition1->size);
ESP_ERROR_CHECK(esp_vfs_register("/data1", &myfs, myfs_inst1));
 
// Можно зарегистрировать другой экземпляр файловой системы:
myfs_t* myfs_inst2 = myfs_mount(partition2->offset, partition2->size);
ESP_ERROR_CHECK(esp_vfs_register("/data2", &myfs, myfs_inst2));

[Синхронное мультиплексирование ввода/вывода]

Компонент VFS поддерживает синхронное мультиплексирование ввода/вывода (synchronous input/output multiplexing) с помощью select(). Эта реализация работает следующим образом.

1. Вызывается select() с дескрипторами файлов, которые могут принадлежать различным драйверам VFS.
2. Дескрипторы файлов делятся на группы, каждая группа принадлежит к одному драйверу VFS.
3. Дескрипторы файлов, принадлежащие драйверам VFS без сокетов (non-socket VFS driver) обрабатываются через имеющиеся драйверы VFS с помощью start_select() (см. далее). Эта функция представляет специфическую для драйвера реализацию select(). Это должен быть не блокирующий вызов, что означает немедленный возврат после настройки окружения для проверки событий для имеющихся дескрипторов файлов.
4. Дескрипторы файлов, принадлежащие драйверам VFS на основе сокетов (socket VFS driver) обрабатываются через socket-драйвер с помощью  socket_select() (см. далее). Это блокирующий вызов, т. е. возврат из функции произойдет только если имеется событие, связанное с дескрипторами socket-файла, или сигнал non-socket драйвера socket_select() для выхода.
5. Собираются результаты от каждого драйвера VFS, и все драйверы останавливаются путем деинициализации программного окружения для проверки событий.
6. Вызов select() завершается, и возвращает соответствующие результаты.

Non-socket драйверы VFS. Если нужно использовать select() с дескриптором файла, принадлежащим non-socket VFS-драйверу, то необходимо зарегистрировать драйвер с функциями start_select() и end_select(), примерно так, как в следующем примере:

// В определении esp_vfs_t:
   .start_select = &uart_start_select,
   .end_select = &uart_end_select,
// ... инициализация других указателей на функции драйвера

Функция start_select() вызывается для настройки окружения детектирования событий чтения, записи, ошибки (read/write/error) на файловых дескрипторах, принадлежащих имеющемуся драйверу VFS.

Функция end_select() вызывается для остановки, отмены инициализации, освобождения ресурсов (stop/deinitialize/free) рабочего окружения, которое было настроено вызовом start_select().

Примечание: функция end_select() может вызываться без предварительного вызова start_select() в некоторых редких обстоятельствах. Для такого случая необходимо корректно обрабатывать ошибку вызова функции end_select() (т. е. не было полного сбоя приложения, просто возврат по ошибке).

Для периферийного устройства UART дополнительную информацию см. в vfs/vfs_uart.c, в частности функции esp_vfs_dev_uart_register(), uart_start_select() и uart_end_select(). Также см. следующие примеры для использования select() с файловыми дескрипторами VFS:

peripherals/uart/uart_select
system/select

Socket драйверы VFS. Этот драйвер VFS использует свою собственную реализацию select(), non-socket драйвер VFS уведомляет его в случаях read/write/error.

Socket драйвер VFS должен быть зарегистрирован со следующими определенными функциями:

// В определении esp_vfs_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() для socket-драйвера. Это работает только когда дескрипторы файла принадлежать socket VFS.

Функция get_socket_select_semaphore() возвратит объект сигнализации (семафор), который будет использоваться в non-socket драйверах для прекращения ожидания в функции socket_select().

Вызов stop_socket_select() используется для остановки ожидания в socket_select() путем передачи объекта, который возвратил вызов get_socket_select_semaphore().

У функции stop_socket_select_isr() такая же функциональность, как и у stop_socket_select(), однако она должна использоваться в теле ISR.

Реализацию socket-драйвера на основе LWIP см. в lwip/port/esp32/vfs_lwip.c.

Примечание: если Вы используете select() только для socket-дескрипторов файла, то можете запретить опцию CONFIG_VFS_SUPPORT_SELECT, чтобы уменьшить размер кода и повысить производительность. Не следует изменять socket-драйвер во время активного вызова select(), иначе можете столкнуться с непредсказуемым поведением кода.

[Пути файловой системы]

У каждой FS есть привязанный к ней префикс для файлового пути (path prefix). Этот префикс можно считать точкой монтирования (mount point) файлового раздела дискового хранилища (partition).

В случае, когда точки монтирования вложены друг в друга, при открытии файла используется точка монтирования с самым длинным подходящим префиксом пути. Для примера представим, что в VFS зарегистрированы следующие файловые системы:

FS1 на /data
FS2 на /data/static

Тогда: 

FS1 будет использоваться при открытии файла /data/log.txt
FS2 будет использоваться при открытии файла /data/static/index.html

Даже если /index.html не существует в FS2, все равно поиск /static/index.html не будет производится по файловой системе FS1.

Как основное правило, имена точек монтирования должны начинаться с разделителя пути (/), и должны содержать как минимум 1 символ после этого  разделителя. Однако также поддерживаются пустые имена точки монтирования, и они могут использоваться в случае, когда приложению нужно предоставить файловую систему с резервным доступом (fallback), или чтобы вообще переопределить функционал VFS. Такая файловая система будет использоваться, если в предоставленном пути нет ни одного совпадающего префикса.

VFS не поддерживает каким-либо специальным образом точки (.) в именах пути. VFS не обрабатывает .. как ссылку на родительскую директорию. В примере выше использование пути наподобие /data/static/../log.txt не приведет для FS1 к открытию файла /log.txt. Специальные драйверы FS (такие как FATFS) могут по-разному поддерживать точки в именах файлов.

При открытии файлов драйвер FS принимает только относительные пути до файлов. Например:

1. Регистрируется драйвер myfs с префиксом пути /data.
2. Приложение вызывает fopen("/data/config.json", ...).
3. Компонент VFS вызывает myfs_open("/config.json", ...).
4. Драйвер myfs открывает файл /config.json.

VFS не накладывает никакое ограничение на общую длину пути до файла, однако сделано ограничение для префикса пути FS на значении ESP_VFS_PATH_MAX. Индивидуальные драйверы FS могут иметь свои собственные ограничения длины имен файлов.

[Дескрипторы файлов]

Дескрипторы файлов это небольшие положительные целые числа от 0 до FD_SETSIZE-1, где FD_SETSIZE определен в файле sys/types.h. Самые большие дескрипторы файлов (сконфигурированные опцией CONFIG_LWIP_MAX_SOCKETS) зарезервированы для сокетов. Компонент VFS содержит таблицу поиска с именем s_fd_table для отображения глобальных дескрипторов файла в индексы VFS драйвера, зарегистрированных в массиве s_vfs.

[Стандартные потоки ввода/вывода (stdin, stdout, stderr)]

Если в menuconfig опция "UART for console output" не установлена в None, то stdin, stdout и stderr сконфигурированы для чтения/записи через UART (standard IO, стандартный ввод/вывод). Для стандартного ввода/вывода можно использовать UART0 или UART1. По умолчанию используется UART0 на скорости 115200 бод; для ножки TX используется GPIO1, для ножки RX GPIO3. Эти параметры могут быть изменены с помощью menuconfig.

Запись в stdout или stderr отправит символы в UART transmit FIFO. Чтение из stdin будет извлекать символы и UART receive FIFO.

По умолчанию VFS использует простые функции для чтения и записи UART. Запись происходит с ожиданием освобождения буфера (busy-wait), пока все данные не будут переданы в UART FIFO, и чтение не блокирующее, возвращающее только данные, которые присутствуют в FIFO. Из-за такого не блокирующего поведения вызовы таких высокоуровневых библиотечных C-функция, как fscanf("%d\n", &var);, могут не иметь желаемых результатов.

Приложения, которые используют драйвер UART, могут инструктировать VFS использовать вместо этого функции чтения и записи, работающие по прерываниям. Это делается вызовом функции esp_vfs_dev_uart_use_driver. Также можно вернуться к базовым не блокирующим функциям путем вызова esp_vfs_dev_uart_use_nonblocking.

VFS также предоставляет функционал опционального преобразования новой строки для ввода и вывода. Внутри себя большинство приложений отправляют и принимают строки терминала, заканчивающиеся на символ LF ('\n', 0x0A, Line Feed, перевод строки). Различные программы терминала могут требовать различных вариантов завершения строки, таких как CR ('\r', 0x0D, Carriage Return, возврат каретки) или CRLF ("\r\n", 0x0D 0x0A). Для приложений можно индивидуально сконфигурировать окончания строк для ввода и для вывода либо через menuconfig, либо путем вызова функций esp_vfs_dev_uart_port_set_rx_line_endings и esp_vfs_dev_uart_port_set_tx_line_endings.

Стандартные потоки и задачи FreeRTOS. Объекты FILE для stdin, stdout и stderr совместно используются всеми задачами FreeRTOS, однако указатели на эти объекты сохраняются индивидуально для каждого потока в структуре _reent.

Следующий код будет препроцессором преобразован в fprintf(__getreent()->_stderr, "42\n"):

fprintf(stderr, "42\n");

Здесь функция __getreent() возвратит индивидуальный для каждой задачи указатель на структуру _reent, которая находится в newlib libc. Эта структура выделяется для каждой задачи из области TCB. Когда задача инициализируется, поля _stdin, _stdout и _stderr в структуре _reent устанавливаются в значения _stdin, _stdout и _stderr из _GLOBAL_REENT (например, структура, которая используется перед запуском FreeRTOS).

Такой дизайн имеет следующие последствия:

● Можно установить stdin, stdout и stderr для любой отдельной задачи, не влияя на другие, например путем stdin = fopen("/dev/uart/1", "r").

● Закрытие потоков по умолчанию stdin, stdout или stderr с использованием fclose закроет объект потока FILE, не влияя на другие задачи.
● Чтобы поменять поток по умолчанию для stdin, stdout, stderr для новых задач, измените _GLOBAL_REENT->_stdin (_stdout, _stderr) перед созданием задачи.

[Event fds]

Вызов eventfd() мощный инструмент для оповещения цикла, основанного на select(), о возникновении пользовательских событий. Реализация eventfd() в ESP-IDF в основном такая же, как это описано в man(2) eventfd, за исключением:

● Функция esp_vfs_eventfd_register() должна быть вызвана перед вызовом eventfd().
● В флагах не поддерживаются опции EFD_CLOEXEC, EFD_NONBLOCK и EFD_SEMAPHORE.
● В флаги добавлена опция EFD_SUPPORT_ISR. Этот флаг требуется для чтения и записи eventfd в обработчике прерывания.

Обратите внимание, что создание eventfd с флагом EFD_SUPPORT_ISR приведет к тому, что будут временно запрещены прерывания при чтении, записи файла, и во время начала и завершения select(), когда этот файл установлен.

[API VFS]

Заголовочный файл:

components/vfs/include/esp_vfs.h

Функция Описание
esp_vfs_write Запись блока данных в файл по его дескриптору.
esp_vfs_lseek Позиционирование по файлу.
esp_vfs_read Чтение блока данных из файла по его дескриптору.
esp_vfs_open Открытие файла по имени (пути) и возврат его дескриптора.
esp_vfs_close Закрытие файла по его дескриптору.
esp_vfs_fstat Получить состояние файла по его дескриптору.
esp_vfs_stat Получить состояние файла по его имени (пути).
esp_vfs_link Создает ссылку на файл по его имени (пути).
esp_vfs_unlink Уничтожает ссылку на файл по его имени (пути).
esp_vfs_rename Переименовывает файл.
esp_vfs_utime Получает информацию о времени файла по его имени (пути).
esp_vfs_register Регистрирует виртуальную файловую систему (драйвер VFS).
esp_vfs_register_fd_range Специальный случай регистрации VFS, который использует метод открытия новых файловых дескрипторов, в заданном интервале, отличающийся от open.
esp_vfs_register_with_id Специальный случай регистрации VFS, который использует метод открытия новых файловых дескрипторов, отличающийся от open. В сравнении с esp_vfs_register_fd_range, эта функция не делает предварительную регистрацию интервала файловых дескрипторов. Файловые дескрипторы могут быть зарегистрированы позже, путем использования esp_vfs_register_fd.
esp_vfs_unregister Отменяет регистрацию виртуальной файловой системы.
esp_vfs_unregister_with_id Отменяет регистрацию виртуальной файловой системы с указанным индексом.
esp_vfs_register_fd Специальная функция для регистрации другого файлового дескриптора для VFS, зарегистрированной через esp_vfs_register_with_id.
esp_vfs_register_fd_with_local_fd Специальная функция для регистрации другого файлового дескриптора с указанным local_fd для VFS, зарегистрированной через esp_vfs_register_with_id.
esp_vfs_unregister_fd Специальная функция для отмены регистрации файлового дескриптора, принадлежащего VFS, зарегистрированной через esp_vfs_register_with_id.
esp_vfs_select Синхронное мультиплексирование ввода/вывода, которое реализует функциональность POSIX select() для VFS.
esp_vfs_select_triggered Оповещение от драйвера VFS о событиях read/write/error.
esp_vfs_select_triggered_isr Оповещение от драйвера VFS о событиях read/write/error (версия для вызова из обработчика прерывания).
esp_vfs_pread Реализует слой VFS для функции POSIX pread().
esp_vfs_pwrite Реализует слой VFS для функции POSIX pwrite().

Подробную документацию по функциям VFS см. заголовочном файле esp_vfs.h и в документации [1].

[Словарик]

FDS Flash Data Storage.

FS File System, файловая система.

TCB Tack Control Block, системные данные для управления задачей.

VFS Virtual FileSystem.

[Ссылки]

1. ESP Virtual filesystem component site:docs.espressif.com.
2. ESP SPP API.
3ESP-IDF: файловая система SPIFFS.

 

Добавить комментарий


Защитный код
Обновить

Top of Page