Программирование ARM RT-Thread Virtual File System Wed, July 24 2024  

Поделиться

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

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

RT-Thread Virtual File System Печать
Добавил(а) microsin   

Ранние встраиваемые системы на микроконтроллерах (MCU) обладали очень малыми ресурсами памяти, и обычно работали с простыми типами данных. Сохраняемые данные записывались непосредственно по указанному адресу в устройство хранения. Однако с расширением количества поддерживаемых функций понадобилось сохранять все больше и больше данных, форматы хранения данных усложнились, потребовалась поддержка стандартных файловых систем.

Файловая система это набор абстрактных типов данных, поддерживающий хранение, иерархическую организицию и стандартизованный доступ к данным. Файловая система обеспечивает для пользователей высокоуровневый механизм доступа к данным, где единицей хранения данных является файл. Если файлов становится слишком много, то они могут быть распределены по категориям, для чего реализованы каталоги (директории, папки). В этом документе (перевод документации [1]), описывается архитектура виртуальной файловой системы RT-Thread (Virtual File System, VFS), её возможности и использование.

Компонент DFS представляет в RT-Thread файловую систему устройства (DFS это сокращение от Device File System). Принятая система именования файлов аналогична файлам и папкам UNIX. Пример структуры директорий:

RT Thread DFS file names fig01

Начало всех путей до файлов это корневая директория (root), обозначаемая символом /. Например файл f1.bin, находящийся в корневой директории, имеет путь /f1.bin, и файл f1.bin, находящийся в директории /data/2019, представлен полным именем /data/2019/f1.bin. Такая система именования используется в UNIX/Linux, что отличается от Windows, где в качестве разделителя имен используется символ \, и отдельные диски начинаются с буквы и двоеточия (C:, D: и т. д.).

[Архитектура DFS]

Основные функции компонента RT-Thread DFS:

- Унифицированный POSIX-интерфейс операций над файлами и директориями для приложений: read, write, poll/select, и т. п.
- Поддержка нескольких типов файловых систем, таких как FatFS, RomFS, DevFS, и т. д., с предоставлением доступа к обычным файлам, файлам устройств, сетевым дескрипторам файлов.
- Поддержка нескольких типов устройств храненеия, таких как карты SD, SPI Flash, Nand Flash, и т. д.

Иерархическая структура DFS показана на следующем рисунке. Основные слои здесь интерфейс POSIX, virtual file system (VFS) и слой абстракции устройства.

RT Thread DFS layers fig02

[Слой интерфейса POSIX]

POSIX означает Portable Operating System Interface of UNIX. POSIX определяет стандартный интерфейс операционной системы, предоставляемый для приложений. Это общий термин для набора стандартных API, определенных IEEE для программного обеспечения операционных систем UNIX.

Стандарт POSIX предназначался для обеспечения переносимости ПО на уровне исходного кода. Другими словами, программа, написанная для POSIX-совместимой операционной системы, должна без особых трудностей компилироваться и запускаться на любой другой POSIX-системе (даже от другого производителя). RT-Thread поддерживает стандартный интерфейс POSIX, что упрощает портирование программ Linux/Unix на операционную систему RT-Thread.

На операционных системах семейства UNIX обычные файлы, файлы устройств и сетевые дескрипторы файлов для программ являются одинаковыми. В операционной системе RT-Thread компонент DFS используется для поддержки аналогичного принципа доступа к файлам. Такая универсальность файловых дескрипторов позволяет использовать интерфейс poll/select для удобного доступа к данным из кода программ.

Использование интерфейса poll/select обеспечивает блкировку и детектирование попыток одновременного доступа к устройствам ввода/вывода (таким как устройства чтения и записи, устройства высокоприоритетного вывода сообщенй об ошибках и т. д.) с поддержкой событий. Если устройство недоступно в течение заданного периода таймаута, то генерируется событие ошибки. Этот механизм помогает вызывающему API-функции коду находить устройства, находящиеся в состоянии готовности, что уменьшает сложность программирования.

[Слой Virtual File System]

Пользователи могут зарегистрировать для DFS несколько различных файловых систем: FatFS, RomFS, DevFS, и т. д. Ниже перечислены несколько таких типов систем:

- FatFS поддерживает Microsoft FAT. Эта библиотека была разработана для небольших встраиваемых систем. Она написана на ANSI C, и обеспечивает хорошую переносимость на разное оборудование. FatFS чаще всего используется для файловой системы в RT-Thread.
- Традиционная файловая система RomFS это простая, компактная, работающая только на чтение файловая система. Она не поддерживает динамическое стирание и сохранение данных. RomFS поддерживает для приложений метод запуска по месту, XIP (execute In Place), что экономит RAM работающей системы.
- Jffs2 это файловая система лога. Она в основном используется для памяти NOR flash, базируясь на слое драйвера MTD. Jffs2 предоставляет чтение и запись, компрессию данных, таблицу хеша, защиту от отказов системы и пропадания питания, поддержку равномерного износа памяти и т. д.
- DevFS это файловвая система устройств. После того, как функция DevFS разрешена в RT-Thread, устройства в системе могут быть виртуализированы как файлы в папке /dev, так что устройства могут использовать файловый интерфейс read и write для приема и передачи данных.
- NFS (Network File System) это технология общего доступа к файлам по сети между разными машинами и операционными системами. Сетевые ресурсы могут быть смонтированы и доступны как файлы в корневую файловую систему встраиваемого устройства.
- UFFS это сокращение от Ultra-low-cost Flash File System, файловая система с открытым исходным кодом. Она разработана китайцами, используется поверх Nand Flash встраиваемых устройств. В сравнении с YAFFS она чаще используется на встраиваемых устройствах, потому что требует очень мало ресурсов, обладает быстрым запуском и бесплатная.

Device Abstraction Layer. Слой абстракции устройства предоставляет интерфейс к разным типам устройств хранения: SD Card, микросхемы SPI Flash и Nand Flash, так что они становятся доступными для использования в файловой системе. Например, файловая система FAT требует устройства хранения блочного типа, чтобы можно было сохранять данными по секторам (обычно 512 байт).

Различные типы файловых систем реализованы независимо от драйвера хранилища (storage device driver). Благодаря этому файловая система может работать корректно после того, как для неё был определен драйвер хранилища.

[Управление монтированием]

Процесс инициализации файловой системы обычно распределен на следующие шаги:

1. Инициализация компонента DFS.
2. Инициализация определенного типа файловой системы.
3. Создание в памяти блочного устройства.
4. Форматирование блочного устройства.
5. Монтирование блочного устройства в директорию DFS.

Когда файловая система больше не используется, она может быть деинсталлирована.

Инициализация компонента DFS. Dызов функции dfs_init() инициализирует соответствующие ресурсы, требуемые для DFS, и создает ключевые структуры данных, по которым DFS находит определенную файловую систему и способ манипуляции фалами на устройстве хранения. Эта функция будет вызвана автоматически, если включена система автоинициализации (что разрешено по умолчанию).

int dfs_init(void);

В случае успешной инициализации dfs_init вернет 1 (RT_TRUE), иначе, если инициализация уже имела место, будет возвращен 0 (RT_FALSE).

Регистрация файловой системы. После того, как компонент DFS инициализирован, для него необходимо инициализировать определенный тип файловой системы, это делается вызовом функции dfs_register.

int dfs_register(const struct dfs_filesystem_ops *ops);

Параметр ops передает указатель на структуру, заполненную рабочими функциями файловой системы:

struct dfs_filesystem_ops
{
   char *name;      // произвольный текст, описывающий файловую систему
   uint32_t flags;  // флаги поддерживаемых операций
 
   /* Операции для файла */
   const struct dfs_file_ops *fops;
 
   /* Монтирование и демонтирование файловой системы */
   int (*mount)   (struct dfs_filesystem *fs, unsigned long rwflag, const void *data);
   int (*unmount) (struct dfs_filesystem *fs);
 
   /* Создание файловой системы */
   int (*mkfs)    (rt_device_t devid);
   int (*statfs)  (struct dfs_filesystem *fs, struct statfs *buf);
   int (*unlink)  (struct dfs_filesystem *fs, const char *pathname);
   int (*stat)    (struct dfs_filesystem *fs, const char *filename, struct stat *buf);
   int (*rename)  (struct dfs_filesystem *fs, const char *oldpath, const char *newpath);
};

В случае успеха dfs_register вернет 0, в случае ошибки регистрации вернет -1.

Эта функция не требует вызова пользователем, она вызывается из функции инициализации различных файловых систем, например из функции elm_init() файловой системы elm-FAT. После того, как соответствующая файловая система разрешена, если автоматическая инициализация разрешена (что разрешено по умолчанию), функция файловой системы также будет вызвана автоматически.

Диаграмма вызова функций для инициализации файловой системы elm-FAT:

RT Thread file system registration process

Регистрация устройства хранения (Storage Device) в как блочного (Block Device). Для файловой системы могут быть смонтированы только блочные устойства, поэтому на устройстве хранения должны быть созданы блочные устройства. Если устройством хранения является микросхема SPI Flash, то вы можете использовать компонент "Serial Flash Universal Driver Library SFUD", который поддерживает различные драйверы SPI Flash, создавая для монтирования абстракцию блочного устройства поверх микросхем SPI Flash. Процесс регистрации блочного устройства показан на следующей картинке:

RT Thread registering block device

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

int dfs_mkfs(const char *fs_name, const char *device_name);

 

Параметр Описание
fs_name Имя файловой системы.
device_name Имя блочного устройства.
Возвращаемое значение
0 Успешное создание файловой системы.
-1 Форматирование было неудачным.

Имя типа файловой системы (fs_name) может быть:

fs_name Тип файловой системы
elm elm-FAT
jffs2 Файловая система журналирования jffs2 поверх flash
nfs NFS, network file system
ram Файловая система RamFS
rom Файловая система RomFS (read-only)
uffs Файловая система uffs

Для примера рассмотрим процесс форматирования блочного устройства elm-FAT:

RT Thread formatting process

Вы также можете отформатировать устройство командой mkfs консоли FinSH. Ниже показан пример создания файловой системы на блочном устройстве с именем sd0, команда mkfs по умолчанию будет его форматировать. Параметр -t указывает имя типа файловой системы для elm-FAT.

msh />mkfs sd0
sd0 is elm-FAT file system
msh />mkfs -t elm sd0

Монтирование файловой системы. В RT-Thread под монтированием подразумевается отображение устройства хранения на существующий путь в корневой файловой системе. Чтобы обратиться к файлу на устройстве хранения, мы должны смонтировать раздел, где находится файл, на существующий путь, и затем обращаться к файлу по полному имени, включающему этот путь.

int dfs_mount(const char   *device_name,
              const char   *path,
              const char   *filesystemtype,
              unsigned long rwflag,
              const void   *data);

 

Параметр Описание
device_name Имя блочного устройства, которое было отформатировано.
path Путь монтирования.
filesystemtype Тип монтируемой файловой системы. Здесь возможны значения, указанные в таблице параметра имени файловой системы для функции dfs_mkfs (fs_name, см. выше).
rwflag Бит флага чтения и записи (read, write).
data Приватные данные для определенной файловой системы.
Возвращаемое значение
0 Успешное монтирование файловой системы.
-1 Монтирование было неудачным.

Если в системе только одно устройство хранения, то оно может быть напрямую смонтировано в корневой каталог /.

Демонтирование файловой системы. Когда файловая система больше не нужна, она может быть размонтирована (unmount).

int dfs_unmount(const char *specialfile);

 

Параметр Описание
specialfile Путь монтирования.
Возвращаемое значение
0 Успешное демонтирование.
-1 Неудача демонтирования.

[Работа с файлами]

Операции над файлами обычно основываются на дескрипторе файла fd (file descriptor). Стандартные операции включают в себя открытие и закрытие файла (open, close), чтение и запись файла (read, write).

RT Thread DFS file management

Открытие и закрытие файла. Чтобы открыть существующий файл или создать новый, используется функция open():

int open(const char *file, int flags, ...);

 

Параметр Описание
file Имена файлов, которые открываются или создаются.
flags Указывается способ открытия файла, значения этого параметра указаны в таблице ниже.
Возвращаемое значение
fd Дескиптор файла (file descriptor).
-1 Ошибка открытия файла.

Файл может быть открыт разными способами, и несколько вариантов его открытия могут быть указаны одновременно OR-комбинацией флагов в параметре flags. Например, если файл открыт в режиме O_WRONLY и O_CREAT, то когда файл с указанным именем не существует, он будет сначала создан (O_CREAT), и затем открыт в режиме только для записи (O_WRONLY). Методы открытия файла показаны в следующей таблице:

Бит для flags Описание
O_RDONLY Открыть файл в режиме только для чтения (read-only).
O_WRONLY Открыть файл в режиме только для записи (write-only).
O_RDWR O_RDWR Открыть файл и для чтения, и для записи.
O_CREAT Если файл не существует, то он будет предварительно создан.
O_APPEND Когда файл окрывается для чтения или записи, то указатель его содержимого будет перемещен в конец файла. Т. е. при записи данные будут добавляться с конец файла, добавляя уже существующие данные файла.
O_TRUNC Обнуление содержимого файла, если он уже существует.

Если файл больше не нужен, то он может быть закрыт функцией close(). Тогда содержимое файла будет записано на физический носитель данных, и ресурсы, связанные с поддержкой файла, будут освобождены.

int close(int fd);

 

Параметр Описание
fd Дескриптор файла (file descriptor).
Возвращаемое значение
0 Файл был успешно закрыт.
-1 Ошибка закрытия файла.

Чтение и запись файла. Для чтения содержимого файла используется функция read():

int read(int fd, void *buf, size_t len);

 

Параметр Описание
fd Дескриптор файла (file descriptor).
buf Указатель на буфер в памяти.
len Количество считываемых байт из файла.
Возвращаемое значение
>0 Сколько байт было реально прочитано.
0 Чтение достигло конца файла, в файле больше нет данных.
-1 Ошибка чтения, код ошибки можно узнать анализом текущего значения ошибки потока (thread errno).

По мере считывания len байт текущая позиция чтения в файле перемещается вперед на значение, которое было возвращено из функции read.

Для записи данных в файл используется функция write():

int write(int fd, const void *buf, size_t len);

 

Параметр Описание
fd Дескриптор файла (file descriptor).
buf Указатель на буфер в памяти.
len Количество записываемых байт.
Возвращаемое значение
>0 Сколько байт было реально записано.
-1 Ошибка записи, код ошибки можно узнать анализом текущего значения ошибки потока (thread errno).

Функция запишет len байт по текущей позиции в файле, эта позиция перемещается вперед на значение, которое было возвращено из функции write.

Переименование файла. Чтобы переименовать файл, используется функция rename():

int rename(const char *old, const char *new);

 

Параметр Описание
old Старое имя файла.
new Новое имя файла.
Возвращаемое значение
0 Успешное изменение имени файла.
-1 Ошибка изменения имени.

Функция rename меняет имя файла old на новое имя new, и если файл с именем new существует, то он будет перезаписан содержимым файла с именем old.

Получение состояния файла. Чтобы узнать статус файла, используйте функцию stat():

int stat(const char *file, struct stat *buf);

 

Параметр Описание
file Имя файла.
buf Указатель на структуру, куда будет записана информация о текущем состоянии файла.
Возвращаемое значение
0 Информация о состоянии файла успешно получена.
-1 Ошибка доступа к статусу файла.

Удаление файлов. Чтобы удалить файл в указанной директории, используйте функцию unlink():

int unlink(const char *pathname);

 

Параметр Описание
pathname Строка, указывающая абсолютный путь до удаляемого файла.
Возвращаемое значение
0 Файл удален успешно.
-1 Ошибка удаления файла.

Синхронизация данных файла с хранилищем. Чтобы сбросить модифицированные данные файла на диск (устройство хранения), используется функция fsync():

int fsync(int fd);

 

Параметр Описание
fd Дескриптор файла (file descriptor).
Возвращаемое значение
0 Синхронизация прошла успешно.
-1 Ошибка синхронизации содержимого файла с устройством хранения.

Запрос системной информации, связанной с файловой системой. Для этого используется функция statfs().

int statfs(const char *path, struct statfs *buf);

 

Параметр Описание
path Путь монтирования файловой системы.
buf Указатель на структуру, куда сохраняется информация о файловой системе.
Возвращаемое значение
0 Информация о файловой системе успешно получена.
-1 Ошибка получения информации о файловой системе.

Мониторинг статуса устройства ввода/вывода. Чтобы отслеживать события устройства хранения (I/O device), используйте функцию select():

int select(int nfds,
           fd_set *readfds,
           fd_set *writefds,
           fd_set *exceptfds,
           struct timeval *timeout);

 

Параметр Описание
nfds Диапазон всех дескрипторов файлов в коллекции. Т. е. параметру nfds может быть присвоено максимальное значение дескриптора всех открытых файлов плюс 1.
readfds Коллекция файловых дескрипторов, которые должны отслеживаться на чтении.
writefds Коллекция файловых дескрипторов, которые должны отслеживаться на записи.
exceptfds Коллекция файловых дескрипторов, которые должны отслеживаться на возникновение исключений.
timeout Таймаут для select.
Возвращаемое значение
> 0 Произошло событие чтения/записи, или произошла ошибка на отслеживаемой коллекции файлов.
0 Таймаут ожидания, не было событий на чтение, запись или ошибки.
< 0 Ошибка.

Функция select() используется для блокировки и детектирования событий на неблокируемых устройствах (non-blocking I/O devices), к таким событиям относятся чтение, запись, высокоприоритетный вывод ошибок, и т. д.).

[Управление директориями]

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

RT Thread DFS directory management

Создание и удаление директорий. Чтобы создать директорию, используйте функцию mkdir():

int mkdir(const char *path, mode_t mode);

 

Параметр Описание
path Абсолютное имя директории.
mode Маска создания.
Возвращаемое значение
0 Директория успешно создана.
-1 Ошибка создания директории.

В параметр mode в текущей версии не используется, передайте в нем значение по умолчанию 0x777.

Для удаления директории используйте функцию rmdir():

int rmdir(const char *pathname);

 

Параметр Описание
pathname Абсолютное имя удаляемой директории.
Возвращаемое значение
0 Директория успешно удалена.
-1 Ошибка удаления директории.

Открытие и закрытие директории. Чтобы открыть директорию, используйте функцию opendir():

DIR* opendir(const char* name);

 

Параметр Описание
pathname Абсолютное имя директории.
Возвращаемое значение
!=NULL Директория успешно открыта, возвращен указатель на поток (итератор содержимого) директории.
NULL Ошибка открытия директории.

Чтобы закрыть директорию, используйте функцию closedir():

int closedir(DIR* d);

 

Параметр Описание
d Указатель на поток директории.
Возвращаемое значение
0 Директория успешно закрыта.
-1 Ошибка закрытия директории.

Эта функция используется для закрытия директории, которая была предварительно открыта вызовом opendir().

Чтение директории. Чтобы прочитать элементы директории, используйте функцию readdir():

struct dirent* readdir(DIR *d);

 

Параметр Описание
d Указатель на поток директории.
Возвращаемое значение
> 0 Указатель на структуру dirent, в которой находится информация по прочитанному элементу содержимого директории.
NULL Достигнут конец чтения директории (все элементы директории были прочитаны).

При каждом вызове readdir итератор d автоматически перемещается на одну позицию.

Получение текущей позиции чтения директории. Для этого используется функция telldir():

long telldir(DIR *d);

 

Параметр Описание
d Указатель на поток директории.
Возвращаемое значение
long Смещение позиции чтения.

В возвращаемом значении записана текущая позиция потока (итератора) директории. Это значение представляет смещение от начала файла директории. Вы можете использовать это значение для последующего вызова seekdir(), чтобы сбросить итератор директории на определенную позицию.

void seekdir(DIR *d, off_t offset);

 

Параметр Описание
d Указатель на поток директории.
offset Значение смещения относительно начала файла этой директории.

Функция seekdir используется для установки позиции чтения директории d. Чтение директории функцией readdir() начнется с установленного смещения offset.

Чтобы сбросить позицию чтения потока директории в начало, используйте функцию rewinddir():

void rewinddir(DIR *d);

 

Параметр Описание
d Указатель на поток директории.

[Опции конфигурации DFS]

Опции конфигурации файловой системы настраиваются командой scons menuconfig.

RT-Thread Components  --->
    Device virtual file system  --->

После сохранения конфигурации menuconfig в файле rtconfig.h устанавливаются опции конфигурации DFS:

Опция Определение макроса Описание
[*] Using device virtual file system RT_USING_DFS Открывает DFS virtual file system.
[*] Using working directory DFS_USING_WORKDIR Открывает относительный путь для рабочей директории.
(2) The maximal number of mounted file system DFS_FILESYSTEMS_MAX Максимальное количество монтируемых файловых систем.
(2) The maximal number of file system type DFS_FILESYSTEM_TYPES_MAX Максимальное количество поддерживаемых типов файловых систем.
(4) The maximal number of opened files DFS_FD_MAX Максимальное количество открытых файлов.
[ ] Using mount table for file system RT_USING_DFS_MNTTABLE Автоматически монтируемая таблица файловой системы.
[*] Enable elm-chan fatfs RT_USING_DFS_ELMFAT Открыть файловую систему elm-FatFs.
[*] Using devfs for device objects RT_USING_DFS_DEVFS Откроет файловую систему устройств DevFS.
[ ] Enable ReadOnly file system on flash RT_USING_DFS_ROMFS Откроет файловую систему RomFS.
[ ] Enable RAM file system RT_USING_DFS_RAMFS Откроет файловую систему RamFS.
[ ] Enable UFFS file system: Ultra-low-cost Flash File System RT_USING_DFS_UFFS Откроет файловую систему UFFS.
[ ] Enable JFFS2 file system RT_USING_DFS_JFFS2 Откроет файловую систему JFFS2.
[ ] Using NFS v3 client file system RT_USING_DFS_NFS Откроет файловую систему NFS.

По умолчанию RT-Thread не включает поддержку относительных путей файлов, чтобы уменьшить расход памяти. В этом случае необходимо для использовать абсолютные пути для файлов и папок (поскольку не задана текущая директория для файловой системы). Если же необходимо использовать и текущую рабочую директорию, и относительную директорию, то вы можете разрешить функцию относительного пути в конфигурации DFS.

Когда выбрана опция [*] Using mount table for file system is selected, будет разрешен макрос RT_USING_DFS_MNTTABLE, чтобы включить автоматическое монтирование. В коде приложения предоставляется таблица автоматического монтирования mount_table[]. В таблице нужно указать имя устройства, путь монтирования, тип файловой системы, флаг чтения и записи и указатель на приватные данные. После этого система выполнит итерацию по таблице mount_table, и автоматически смонтирует указанные устройства и файловые системы. Таблица mount_table должна заканчиваться маркером {0}.

Ниже показан пример mount_table[], где в первом элементе таблицы mount_table[0] указаны 5 параметров для функции dfs_mount(). Задано монтировать файловую систему elm в путь / на устройстве хранения flash 0, rwflag 0, data 0. Элемент mount_table [1] содержит маркер окончания таблицы {0}.

const struct dfs_mount_tbl mount_table[] =
{
    {"flash0", "/", "elm", 0, 0},
    {0}
};

Опции конфигурации elm-FatFs. Файловая система Elm-FatFs может быть дополнительно сконфигурирована следующими опциями в menuconfig:

Опция Определение макроса Описание
(437) OEM code page RT_DFS_ELM_CODE_PAGE Режим кодирования.
[*] Using RT_DFS_ELM_WORD_ACCESS RT_DFS_ELM_WORD_ACCESS Настройка доступа к памяти с выравниванием на размер слова.
Support long file name (0: LFN disable) ---> RT_DFS_ELM_USE_LFN Откроет субменю длинных имен файлов.
(255) Maximal size of file name length RT_DFS_ELM_MAX_LFN Максимальная длина имени файла.
(2) Number of volumes (logical drives) to be used RT_DFS_ELM_DRIVES Количество устройств, на которых монтируется FatFs.
(4096) Maximum sector size to be handled RT_DFS_ELM_MAX_SECTOR_SIZE Максимальный обрабатываемый размер сектора.
[ ] Enable sector erase feature RT_DFS_ELM_USE_ERASE Разрешить функцию стирания сектора.
[*] Enable the reentrancy (thread safe) of the FatFs module RT_DFS_ELM_REENTRANT Разрешить потокобезопасный (реэнтрантный) код.

Длинное имя файла. По умолчанию у системы именования файлов FatFs есть следующие недостатки:

- Длина имени файла (без суффикса) может иметь длину не более 8 символов, и длина суффикса (расширения) не может быть больше 3 символов. Если это ограничение превышено, то элементы имени будут обрезаны до 8 и 3 символов соответственно.
- Имена файлов не поддерживают регистр символов (всегда отображаются в верхнем регистре).

Если необходима поддержка длинных имен, то нужно включить соответствующую опцию (support long filenames). Субменю настройки длинных имен:

Опция Определение макроса Описание
( ) 0: LFN disable RT_DFS_ELM_USE_LFN_0 Запрет использования длинных имен.
( ) 1: LFN with static LFN working buffer RT_DFS_ELM_USE_LFN_1 Поддерживать длинные имена с использованием статических буферов, без поддержки реентерабельности (не обеспечивается потокобезопасный режим работы файловой системы).
( ) 2: LFN with dynamic LFN working buffer on the stack RT_DFS_ELM_USE_LFN_2 Поддерживаются длинные имена с временными буферами, организованными в стеке. Имена большой длины приведут к увеличенному расходу пространства стека.
(X) 3: LFN with dynamic LFN working buffer on the heap RT_DFS_ELM_USE_LFN_3 Использовать кучу (запрос malloc) под буферы длинных имен, это самый безопасный режим (используется по умолчанию).

Encoding Mode. Когда включена поддержка длинных имен файлов, можно установить режим кодирования (encoding mode) имени файла. RT-Thread/FatFs по умолчанию использует кодирование 437 (American English). Если нужно использовать китайскую кодировку (Chinese) для имен файлов, то установите 936 encoding (GBK encoding). 936 encoding требует библиотеки шрифта размером около 180 килобайт. Если в используете для имен файлов только символы English, то рекомендуется установить 437 encoding (American English), это позволит съэкономить 180KB памяти Flash.

FatFs поддерживает следующие кодировки имен файлов:

#define _CODE_PAGE 437
/* Эта опция задает кодовую страницу OEM, используемую на целевой системе.
/  Неправильная установка может привести к ошибке открытия файла.
/
/   1   - ASCII (нет поддержки расширенных символов. Только для конфигурации без LFN)
/   437 - U.S.
/   720 - Arabic
/   737 - Greek
/   771 - KBL
/   775 - Baltic
/   850 - Latin 1
/   852 - Latin 2
/   855 - Cyrillic
/   857 - Turkish
/   860 - Portuguese
/   861 - Icelandic
/   862 - Hebrew
/   863 - Canadian French
/   864 - Arabic
/   865 - Nordic
/   866 - Russian
/   869 - Greek 2
/   932 - Japanese (DBCS)
/   936 - Simplified Chinese (DBCS)
/   949 - Korean (DBCS)
/   950 - Traditional Chinese (DBCS)
*/

File System Sector Size. Указывает внутренний размер сектора FatFs, который должен быть больше или равен размеру сектора актуального драйвера аппаратуры хранилища. Например, если размер сектора микросхемы SPI Flash равен 4096 байтам, то макрос RT_DFS_ELM_MAX_SECTOR_SIZE должен быть установлен в значение 4096. Иначе, когда FatFs прочитает данные из драйвера, произойдет переполнение массива буфера сектора, что приведет к краху системы (новая версия выдаст предупреждающее сообщение при запуске системы).

Обычно устройство Flash требует размера сектора 4096, и обычные карты TF и карты SD поддерживают размер сектора 512.

Reentrant. FatFs полностью поддерживает многопоточную среду выполнения кода, обеспечивая безопасные операции чтения и записи. При чтении и записи FafFs в многопоточной среде для устранения проблем с одновременным доступом из разных потоков необходимо сконфигурировать макрос RT_DFS_ELM_REENTRANT, а также выбрать режим выделения памяти для длинных имен файлов RT_DFS_ELM_USE_LFN_2 или RT_DFS_ELM_USE_LFN_3 (в случае использования длинных имен). Если доступ к файлам происходит только из одного потока, то определение макроса реентерабельности RT_DFS_ELM_REENTRANT для экономии памяти можно удалить (закомментировать).

Другие опции конфигурации. FatFs поддерживает множество опций, что делает эту библиотеку файловой системы очень гибкой в использовании. Опции конфигурации FatFs находятся в заголовочном файле:

rt-thread/components/dfs/filesystems/elmfat/ffconf.h

[Команды FinSH]

После того, как файловая система успешно смонтирована, можно работать с файлами и директориями. Обычно в примере приложения RT-Thread консоль FinSH [4] предоставляет команды:

Команда FinSH Описание
ls Показывает информацию по файлам и директориям.
cd Вход в указанную директорию.
cp Копирование файла.
rm Удалить файл или директорию.
mv Переместить или переименовать файл.
echo Записать указанное содержимое в указанный файл, запись файла когда он существует, и создание нового файла и запись в него, если он не существует.
cat Показать содержимое файла.
pwd Напечатать имя текущей директории.
mkdir Создать папку.
mkfs Создать файловую систему на устройстве хранения (форматирование).

Используйте команду ls, чтобы посмотреть текущую директорию, результат будет примерно такой:

msh />ls
Directory /:
dummy               < DIR >
dummy.txt           17

DIR означает, что dummy это директория, а число 17 говорит о том, что размер файла dummy.txt равен 17 байтам.

Для создания папки используйте команду mkdir:

msh />mkdir rt-thread    # создание папки rt-thread в текущем каталоге
msh />ls                 # просмотр информации
Directory /:
rt-thread           < DIR >
dummy               < DIR >
dummy.txt           17

Используйте команду echo, чтобы вывести строку в файл, находящийся в определенном каталоге. Для простоты файл запишем в текущий каталог /:

msh />echo "hello rt-thread!!!"                # вывод строки в stdout
hello rt-thread!!!
msh />echo "hello rt-thread!!!" hello.txt      # вывод строки в файл hello.txt
msh />ls
Directory /:
rt-thread           < DIR >
dummy               < DIR >
dummy.txt           17
hello.txt           18

Для просмотра содержимого файла используйте команду cat:

msh />cat hello.txt
hello rt-thread!!!

Для удаления файла или папки используйте команду rm:

msh />ls                        # просмотр содержимого текущей директории
Directory /:
rt-thread           < DIR >
hello.txt           18
msh />rm rt-thread              # удаление папки rt-thread
msh />ls
Directory /:
hello.txt           18
msh />rm hello.txt              # удаление папки hello.txt
msh />ls
Directory /:

[Чтение и запись файла]

Как только файловая система заработала, можно попробовать её работу на чтение и запись данных файла, что показано в следующем примере. Здесь сначала создается файл text.txt с помощью функции open(), и в него записывается строка "RT-Thread Programmer!\n" функцией write(), после чего файл закрывается. Чтобы открыть файл и прочитать его, снова используется функция open(), и с помощью функции read содержимое файла считывается и печатается в stdout, после чего файл закрывается.

#include < rtthread.h>
// Этот заголовочный файл нужно подключить для работы с файлами:
#include < dfs_posix.h>
 
static void readwrite_sample(void)
{
   int fd, size;
   char s[] = "RT-Thread Programmer!", buffer[80];
 
   rt_kprintf("Write string %s to test.txt.\n", s);
 
   /* Открыть файл /text.txt с флагами создания (O_CREAT),
      в режиме записи (O_WRONLY). Если файл еще не существует,
      то он будет создан. */
   fd = open("/text.txt", O_WRONLY | O_CREAT);
   if (fd>= 0)
   {
      write(fd, s, sizeof(s));
      close(fd);
      rt_kprintf("Write done.\n");
   }
 
   /* Открыть файл /text.txt в режиме только для чтения. */
   fd = open("/text.txt", O_RDONLY);
   if (fd>= 0)
   {
      size = read(fd, buffer, sizeof(buffer));
      close(fd);
      rt_kprintf("Read from file test.txt : %s \n", buffer);
      if (size < 0)
         return ;
   }
}
 
/* Экспорт команды в консоль FinSH */
MSH_CMD_EXPORT(readwrite_sample, readwrite sample);

[Пример изменения имени файла]

В функции rename_sample() показан простой код, который вызовом rename() переименовывает файл text.txt в text1.txt.

#include < rtthread.h>
#include < dfs_posix.h>
 
static void rename_sample(void)
{
   rt_kprintf("%s => %s", "/text.txt", "/text1.txt");
 
   if (rename("/text.txt", "/text1.txt") < 0)
      rt_kprintf("[error!]\n");
   else
      rt_kprintf("[ok!]\n");
}
 
/* Экспорт команды в консоль FinSH */
MSH_CMD_EXPORT(rename_sample, rename sample);

Пример запуска команды rename_sample, которая переименует файл text.txt в text1.txt:

msh />echo "hello" text.txt
msh />ls
Directory /:
text.txt           5
msh />rename_sample
/text.txt => /text1.txt [ok!]
msh />ls
Directory /:
text1.txt           5

[Получение состояния файла]

Этот пример показывает, как получить информацию статуса файла text.txt. Это делается вызовом функции stat(), информация статуса записывается в структуру stat buf, после чего в stdout выводится информация о размере файла.

#include < rtthread.h>
#include < dfs_posix.h>
 
static void stat_sample(void)
{
   int ret;
   struct stat buf;
   
   ret = stat("/text.txt", &buf);
   if(ret == 0)
      rt_kprintf("text.txt file size = %d\n", buf.st_size);
   else
      rt_kprintf("text.txt file not found\n");
}
 
/* Экспорт команды в консоль FinSH */
MSH_CMD_EXPORT(stat_sample, show text.txt stat sample);

Запуск примера в консоли покажет следующий результат:

msh />echo "hello" text.txt
msh />stat_sample
text.txt file size = 5

[Создание директории]

Следующий пример создаст папку dir_test в корневой (root) директории /.

#include < rtthread.h>
#include < dfs_posix.h>
 
static void mkdir_sample(void)
{
   int ret;
 
   /* Вызов mkdir создаст каталог /dir_test: */
   ret = mkdir("/dir_test", 0x777);
   if (ret < 0)
   {
      /* Ошибка создания директории */
      rt_kprintf("dir error!\n");
   }
   else
   {
      /* Директория создана успешно */
      rt_kprintf("mkdir ok!\n");
   }
}
 
/* Экспорт команды в консоль FinSH */
MSH_CMD_EXPORT(mkdir_sample, mkdir sample);

Запуск примера в консоли покажет следующий результат:

msh />mkdir_sample
mkdir ok!
msh />ls
Directory /:
dir_test                 < DIR>

[Чтение директории]

Следующий пример кода показывает, как считывать список файлов и папок в указанном каталоге. С помощью вызова функции readdir() код получает доступ к списку содержимого в папке dir_test, и выводит эту информацию в консоль:

#include < rtthread.h>
#include < dfs_posix.h>
 
static void readdir_sample(void)
{
   DIR *dirp;
   struct dirent *d;
 
   /* Открыть директорию /dir_test */
   dirp = opendir("/dir_test");
   if (dirp == RT_NULL)
   {
      rt_kprintf("open directory error!\n");
   }
   else
   {
      /* Чтение элементов в директории */
      while ((d = readdir(dirp)) != RT_NULL)
      {
         rt_kprintf("found %s\n", d->d_name);
      }
 
      /* Закрыть директорию */
      closedir(dirp);
   }
}
 
/* Экспорт команды в консоль FinSH */
MSH_CMD_EXPORT(readdir_sample, readdir sample);

Запуск примера в консоли покажет следующий результат:

msh />ls
Directory /:
dir_test                 < DIR>
msh />cd dir_test                       # переход в папку dir_test
msh /dir_test>echo "hello" hello.txt    # создание файла hello.txt
msh /dir_test>cd ..                     # переход в родительскую папку
msh />readdir_sample
found hello.txt

В этом примере мы сначала создадим файл hello.txt в каталоге dir_test, после чего вернемся обратно в корневую директорию, где находится сама папка dir_test. После этого мы запустим командой readdir_sample код нашего примера, он прочитает каталог dir_test и выведет в консоль его содержимое.

[Навигация по списку директории]

Этот код показывает, как программно указать текущий каталог. Функция команды telldir_sample() сначала откроет корневой каталог, затем напечатает его содержимое в консоль. Кроме того, используется функция telldir() для записи информации о третьем элементе директории. Перед чтением информации о корневой директории второй раз, вызов функции seekdir() используется для установки позиции чтения третьего элемента директории, которая была предварительно записана в переменную. После этого опять запускается итерация чтения директории.

#include < rtthread.h>
#include < dfs_posix.h>
 
/* Предполагается, что операции с файлами выполняются одновременно
   только в этом потоке */
static void telldir_sample(void)
{
   DIR *dirp;
   int save3 = 0;
   int cur;
   int i = 0;
   struct dirent *dp;
 
   /* Открыть папку root */
   rt_kprintf("the directory is:\n");
   dirp = opendir("/");
 
   for (dp = readdir(dirp); dp != RT_NULL; dp = readdir(dirp))
   {
      /* Сохранение указателя на третий элемент списка директории / */
      i++;
      if (i == 3)
         save3 = telldir(dirp);
      rt_kprintf("%s\n", dp->d_name);
   }
 
   /* Возвращаемся обратно по списку каталога на сохраненную позицию */
   seekdir(dirp, save3);
 
   /* Проверка: совпадает ли текущий указатель директории с сохраненным
      третьим элементом директории */
   cur = telldir(dirp);
   if (cur != save3)
   {
      rt_kprintf("seekdir (d, %ld); telldir (d) == %ld\n", save3, cur);
   }
 
   /* Начинаем итерацию по списку директории с третьего элемента */
   rt_kprintf("the result of tell_seek_dir is:\n");
   for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))
   {
      rt_kprintf("%s\n", dp->d_name);
   }
 
   /* Закрыть директорию */
   closedir(dirp);
}
 
/* Экспорт команды в консоль FinSH */
MSH_CMD_EXPORT(telldir_sample, telldir sample);

В этом примере мы должны сначала вручную создать 5 папок hello_1 .. hello_5 в корневой директории / с помощью команды mkdir. После этого запуск примера команды покажет следующий результат:

msh />ls
Directory /:
hello_1             < DIR>
hello_2             < DIR>
hello_3             < DIR>
hello_4             < DIR>
hello_5             < DIR>
msh />telldir_sample
the directory is:
hello_1
hello_2
hello_3
hello_4
hello_5
the result of tell_seek_dir is:
hello_3
hello_4
hello_5

[VFS/DFS FAQ]

Q01: Почему неправильно отображаются имена файлов или папок?
A01: Проверьте, разрешена ли опция поддержки длинных имен файлов, см. выше описание конфигурации DFS.

Q02: Что делать, если происходит ошибка при попытке инициализации файловой системы?
A02: Проверьте настройку типа файловой системы и настроенное количество поддерживаемых типов файловых систем.

Q03: Почему не работает команда mkfs?
A03: Проверьте, существует ли устройство хранения (storage device). Если оно существует, то проверьте, проходит ли драйвер устройства функциональный тест. Если тест не проходит, проанализируйте код ошибки драйвера. Также проверьте, разрешена ли функция libc.

Q04: Что делать, если файловая система устройства хранения не монтируется?
A04: Выполните следующие проверки:

- Проверьте, существует ли указываемый вами путь монтирования. Файловая система может быть смонтирована напрямую в корневой каталог, точка монтирования "/". Но если нужно смонтировать еще одно устройство, то необходимо указать другую точку (путь) монтирования, такую как например "/sdcard". Проверьте перед монтированием, существует ли путь "/sdcard". Если не существует, то нужно создать папку sdcard в корневом каталоге перед монтированием нового устройства в точку монтирования "/sdcard".
- Проверьте, создана ли на устройстве хранения файловая система. Если нет, то нужно сначала отформатировать устройство хранения командой mkfs.

Q05: Что делать, если SFUD не может определить Flash определенной модели?
A05: Выполните аппаратные настройки:

- Зарегистрировано ли устройство SPI.
- Правильно ли подключено и смонтировано устройство SPI.
- Проверьте параметры "Using auto probe flash JEDEC SFDP" и "Using defined supported flash chip information table" в разделе настроек menuconfig "RT-Thread Components -> Device Drivers -> Using SPI Bus/Device device drivers -> Using Serial Flash Universal Driver" чтобы убедиться, что выбрана правильная конфигурация. Эти две опции должны быть разрешены.
- Если устройство хранения все еще не распознается после того, как эти две опции включены, то причина может быть в коде проекта SFUD.

Q06: Почему тест производительности устройства хранения идет слишком долго?
A06: Проверьте следующее:

- Сравните данные эталонного теста, когда системный тик достигнет 1000, с временем, необходимым для этого теста. Если лаг времени слишком большой, то вероятно тест работает некорректно.
- Проверьте настройки системных тиков. Поскольку некоторые операции задержки будут реализованы на базе интервала системного тика, необходимо обеспечить правильные интервалы тиков. Если значение системных тиков не меньше 1000, то нужно использользовать логический анализатор для проверки скорости доступа к устройству хранения.

Q07: На SPI Flash реализована файловая система elmfat, как сделать так, чтобы некоторые секторы не использовались для файловой системы?
A07: Вы можете создать несколько блочных устройств (block devices) на одном устройстве хранения, используя утилиту разделов (partition tool package), предоставляемую RT-Thread. И разным блочным устройствам можно назначить разные функции.

Q08: Что делать, если программа зависает на тесте файловой системы?
A08: Попробуйте использовать отладчик, чтобы определить место зависания. Если отладчика нет, то выведите в консоль некоторую дополнительную отладочную информацию, позволяющую обнаружить место и причину зависания.

Q09: Как пошагово разобраться с проблемой в работе файловой системы?
A09: Выполните следующее:

- Сначала проверьте, успешно ли зарегистрировано и нормально ли работает устройство хранения (storage device).
- Проверьте, создана ли файловая система на устройстве хранения.
- Проверьте, поддерживается ли настройками DFS тип вашей файловой системы, а также достаточно ли количество регистрируемых типов файловых систем.
- Проверьте, прошла ли успешно инициализация DFS. На этом шаге инициализация происходит полностью программно, так что вероятность возникновения ошибки невысока. Следует отметить, что если включена автоинициализация компонента (component auto-initialization), то не требуется проводить вручную инициализацию DFS.

[Ссылки]

1. RT-Thread Virtual File System site:rt-thread.io.
2. Библиотека FatFS: модуль файловой системы FAT.
3. YAFFS site:wikipedia.org.
4. Консоль FinSH.
5. RT-Thread RTOS и её компоненты.

 

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


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

Top of Page