Ранние встраиваемые системы на микроконтроллерах (MCU) обладали очень малыми ресурсами памяти, и обычно работали с простыми типами данных. Сохраняемые данные записывались непосредственно по указанному адресу в устройство хранения. Однако с расширением количества поддерживаемых функций понадобилось сохранять все больше и больше данных, форматы хранения данных усложнились, потребовалась поддержка стандартных файловых систем.
Файловая система это набор абстрактных типов данных, поддерживающий хранение, иерархическую организицию и стандартизованный доступ к данным. Файловая система обеспечивает для пользователей высокоуровневый механизм доступа к данным, где единицей хранения данных является файл. Если файлов становится слишком много, то они могут быть распределены по категориям, для чего реализованы каталоги (директории, папки). В этом документе (перевод документации [1]), описывается архитектура виртуальной файловой системы RT-Thread (Virtual File System, VFS), её возможности и использование.
Компонент DFS представляет в RT-Thread файловую систему устройства (DFS это сокращение от Device File System). Принятая система именования файлов аналогична файлам и папкам UNIX. Пример структуры директорий:
Начало всех путей до файлов это корневая директория (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) и слой абстракции устройства.
[Слой интерфейса 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 находит определенную файловую систему и способ манипуляции фалами на устройстве хранения. Эта функция будет вызвана автоматически, если включена система автоинициализации (что разрешено по умолчанию).
intdfs_init(void);
В случае успешной инициализации dfs_init вернет 1 (RT_TRUE), иначе, если инициализация уже имела место, будет возвращен 0 (RT_FALSE).
Регистрация файловой системы. После того, как компонент DFS инициализирован, для него необходимо инициализировать определенный тип файловой системы, это делается вызовом функции dfs_register.
/* Операции для файла */conststruct dfs_file_ops *fops;
/* Монтирование и демонтирование файловой системы */int (*mount) (struct dfs_filesystem *fs, unsignedlong rwflag, constvoid*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, constchar*pathname);
int (*stat) (struct dfs_filesystem *fs, constchar*filename, struct stat *buf);
int (*rename) (struct dfs_filesystem *fs, constchar*oldpath, constchar*newpath);
};
В случае успеха dfs_register вернет 0, в случае ошибки регистрации вернет -1.
Эта функция не требует вызова пользователем, она вызывается из функции инициализации различных файловых систем, например из функции elm_init() файловой системы elm-FAT. После того, как соответствующая файловая система разрешена, если автоматическая инициализация разрешена (что разрешено по умолчанию), функция файловой системы также будет вызвана автоматически.
Диаграмма вызова функций для инициализации файловой системы elm-FAT:
Регистрация устройства хранения (Storage Device) в как блочного (Block Device). Для файловой системы могут быть смонтированы только блочные устойства, поэтому на устройстве хранения должны быть созданы блочные устройства. Если устройством хранения является микросхема SPI Flash, то вы можете использовать компонент "Serial Flash Universal Driver Library SFUD", который поддерживает различные драйверы SPI Flash, создавая для монтирования абстракцию блочного устройства поверх микросхем SPI Flash. Процесс регистрации блочного устройства показан на следующей картинке:
Форматирование файловой системы. После того, как блочное устройство зарегистрировано, вам на нем также надо создать файловую систему определенного типа. Для форматирования устройства хранения и создания на нем файловой системы вы можете использовать функцию dfs_mkfs().
Файловая система журналирования jffs2 поверх flash
nfs
NFS, network file system
ram
Файловая система RamFS
rom
Файловая система RomFS (read-only)
uffs
Файловая система uffs
Для примера рассмотрим процесс форматирования блочного устройства elm-FAT:
Вы также можете отформатировать устройство командой mkfs консоли FinSH. Ниже показан пример создания файловой системы на блочном устройстве с именем sd0, команда mkfs по умолчанию будет его форматировать. Параметр -t указывает имя типа файловой системы для elm-FAT.
msh />mkfs sd0
sd0 is elm-FAT file system
msh />mkfs -t elm sd0
Монтирование файловой системы. В RT-Thread под монтированием подразумевается отображение устройства хранения на существующий путь в корневой файловой системе. Чтобы обратиться к файлу на устройстве хранения, мы должны смонтировать раздел, где находится файл, на существующий путь, и затем обращаться к файлу по полному имени, включающему этот путь.
Имя блочного устройства, которое было отформатировано.
path
Путь монтирования.
filesystemtype
Тип монтируемой файловой системы. Здесь возможны значения, указанные в таблице параметра имени файловой системы для функции dfs_mkfs (fs_name, см. выше).
rwflag
Бит флага чтения и записи (read, write).
data
Приватные данные для определенной файловой системы.
Возвращаемое значение
0
Успешное монтирование файловой системы.
-1
Монтирование было неудачным.
Если в системе только одно устройство хранения, то оно может быть напрямую смонтировано в корневой каталог /.
Демонтирование файловой системы. Когда файловая система больше не нужна, она может быть размонтирована (unmount).
intdfs_unmount(constchar*specialfile);
Параметр
Описание
specialfile
Путь монтирования.
Возвращаемое значение
0
Успешное демонтирование.
-1
Неудача демонтирования.
[Работа с файлами]
Операции над файлами обычно основываются на дескрипторе файла fd (file descriptor). Стандартные операции включают в себя открытие и закрытие файла (open, close), чтение и запись файла (read, write).
Открытие и закрытие файла. Чтобы открыть существующий файл или создать новый, используется функция open():
intopen(constchar*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(). Тогда содержимое файла будет записано на физический носитель данных, и ресурсы, связанные с поддержкой файла, будут освобождены.
intclose(int fd);
Параметр
Описание
fd
Дескриптор файла (file descriptor).
Возвращаемое значение
0
Файл был успешно закрыт.
-1
Ошибка закрытия файла.
Чтение и запись файла. Для чтения содержимого файла используется функция read():
intread(int fd, void*buf, size_t len);
Параметр
Описание
fd
Дескриптор файла (file descriptor).
buf
Указатель на буфер в памяти.
len
Количество считываемых байт из файла.
Возвращаемое значение
>0
Сколько байт было реально прочитано.
0
Чтение достигло конца файла, в файле больше нет данных.
-1
Ошибка чтения, код ошибки можно узнать анализом текущего значения ошибки потока (thread errno).
По мере считывания len байт текущая позиция чтения в файле перемещается вперед на значение, которое было возвращено из функции read.
Для записи данных в файл используется функция write():
intwrite(int fd, constvoid*buf, size_t len);
Параметр
Описание
fd
Дескриптор файла (file descriptor).
buf
Указатель на буфер в памяти.
len
Количество записываемых байт.
Возвращаемое значение
>0
Сколько байт было реально записано.
-1
Ошибка записи, код ошибки можно узнать анализом текущего значения ошибки потока (thread errno).
Функция запишет len байт по текущей позиции в файле, эта позиция перемещается вперед на значение, которое было возвращено из функции write.
Переименование файла. Чтобы переименовать файл, используется функция rename():
intrename(constchar*old, constchar*new);
Параметр
Описание
old
Старое имя файла.
new
Новое имя файла.
Возвращаемое значение
0
Успешное изменение имени файла.
-1
Ошибка изменения имени.
Функция rename меняет имя файла old на новое имя new, и если файл с именем new существует, то он будет перезаписан содержимым файла с именем old.
Получение состояния файла. Чтобы узнать статус файла, используйте функцию stat():
intstat(constchar*file, struct stat *buf);
Параметр
Описание
file
Имя файла.
buf
Указатель на структуру, куда будет записана информация о текущем состоянии файла.
Возвращаемое значение
0
Информация о состоянии файла успешно получена.
-1
Ошибка доступа к статусу файла.
Удаление файлов. Чтобы удалить файл в указанной директории, используйте функцию unlink():
intunlink(constchar*pathname);
Параметр
Описание
pathname
Строка, указывающая абсолютный путь до удаляемого файла.
Возвращаемое значение
0
Файл удален успешно.
-1
Ошибка удаления файла.
Синхронизация данных файла с хранилищем. Чтобы сбросить модифицированные данные файла на диск (устройство хранения), используется функция fsync():
intfsync(int fd);
Параметр
Описание
fd
Дескриптор файла (file descriptor).
Возвращаемое значение
0
Синхронизация прошла успешно.
-1
Ошибка синхронизации содержимого файла с устройством хранения.
Запрос системной информации, связанной с файловой системой. Для этого используется функция statfs().
intstatfs(constchar*path, struct statfs *buf);
Параметр
Описание
path
Путь монтирования файловой системы.
buf
Указатель на структуру, куда сохраняется информация о файловой системе.
Возвращаемое значение
0
Информация о файловой системе успешно получена.
-1
Ошибка получения информации о файловой системе.
Мониторинг статуса устройства ввода/вывода. Чтобы отслеживать события устройства хранения (I/O device), используйте функцию select():
Диапазон всех дескрипторов файлов в коллекции. Т. е. параметру nfds может быть присвоено максимальное значение дескриптора всех открытых файлов плюс 1.
readfds
Коллекция файловых дескрипторов, которые должны отслеживаться на чтении.
writefds
Коллекция файловых дескрипторов, которые должны отслеживаться на записи.
exceptfds
Коллекция файловых дескрипторов, которые должны отслеживаться на возникновение исключений.
timeout
Таймаут для select.
Возвращаемое значение
> 0
Произошло событие чтения/записи, или произошла ошибка на отслеживаемой коллекции файлов.
0
Таймаут ожидания, не было событий на чтение, запись или ошибки.
< 0
Ошибка.
Функция select() используется для блокировки и детектирования событий на неблокируемых устройствах (non-blocking I/O devices), к таким событиям относятся чтение, запись, высокоприоритетный вывод ошибок, и т. д.).
[Управление директориями]
Для управления каталогами файлов используются следующие функции:
Создание и удаление директорий. Чтобы создать директорию, используйте функцию mkdir():
intmkdir(constchar*path, mode_t mode);
Параметр
Описание
path
Абсолютное имя директории.
mode
Маска создания.
Возвращаемое значение
0
Директория успешно создана.
-1
Ошибка создания директории.
В параметр mode в текущей версии не используется, передайте в нем значение по умолчанию 0x777.
Для удаления директории используйте функцию rmdir():
intrmdir(constchar*pathname);
Параметр
Описание
pathname
Абсолютное имя удаляемой директории.
Возвращаемое значение
0
Директория успешно удалена.
-1
Ошибка удаления директории.
Открытие и закрытие директории. Чтобы открыть директорию, используйте функцию opendir():
DIR*opendir(constchar* name);
Параметр
Описание
pathname
Абсолютное имя директории.
Возвращаемое значение
!=NULL
Директория успешно открыта, возвращен указатель на поток (итератор содержимого) директории.
NULL
Ошибка открытия директории.
Чтобы закрыть директорию, используйте функцию closedir():
intclosedir(DIR* d);
Параметр
Описание
d
Указатель на поток директории.
Возвращаемое значение
0
Директория успешно закрыта.
-1
Ошибка закрытия директории.
Эта функция используется для закрытия директории, которая была предварительно открыта вызовом opendir().
Чтение директории. Чтобы прочитать элементы директории, используйте функцию readdir():
struct dirent*readdir(DIR*d);
Параметр
Описание
d
Указатель на поток директории.
Возвращаемое значение
> 0
Указатель на структуру dirent, в которой находится информация по прочитанному элементу содержимого директории.
NULL
Достигнут конец чтения директории (все элементы директории были прочитаны).
При каждом вызове readdir итератор d автоматически перемещается на одну позицию.
Получение текущей позиции чтения директории. Для этого используется функция telldir():
longtelldir(DIR*d);
Параметр
Описание
d
Указатель на поток директории.
Возвращаемое значение
long
Смещение позиции чтения.
В возвращаемом значении записана текущая позиция потока (итератора) директории. Это значение представляет смещение от начала файла директории. Вы можете использовать это значение для последующего вызова seekdir(), чтобы сбросить итератор директории на определенную позицию.
voidseekdir(DIR*d, off_t offset);
Параметр
Описание
d
Указатель на поток директории.
offset
Значение смещения относительно начала файла этой директории.
Функция seekdir используется для установки позиции чтения директории d. Чтение директории функцией readdir() начнется с установленного смещения offset.
Чтобы сбросить позицию чтения потока директории в начало, используйте функцию rewinddir():
voidrewinddir(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}.
Опции конфигурации 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 консоль 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:
Как только файловая система заработала, можно попробовать её работу на чтение и запись данных файла, что показано в следующем примере. Здесь сначала создается файл text.txt с помощью функции open(), и в него записывается строка "RT-Thread Programmer!\n" функцией write(), после чего файл закрывается. Чтобы открыть файл и прочитать его, снова используется функция open(), и с помощью функции read содержимое файла считывается и печатается в stdout, после чего файл закрывается.
#include < rtthread.h>
// Этот заголовочный файл нужно подключить для работы с файлами:
#include < dfs_posix.h>
staticvoidreadwrite_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.
Этот пример показывает, как получить информацию статуса файла text.txt. Это делается вызовом функции stat(), информация статуса записывается в структуру stat buf, после чего в stdout выводится информация о размере файла.
#include < rtthread.h>
#include < dfs_posix.h>
staticvoidstat_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);
Запуск примера в консоли покажет следующий результат:
Следующий пример кода показывает, как считывать список файлов и папок в указанном каталоге. С помощью вызова функции readdir() код получает доступ к списку содержимого в папке dir_test, и выводит эту информацию в консоль:
/* Экспорт команды в консоль 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>
/* Предполагается, что операции с файлами выполняются одновременно
только в этом потоке */
staticvoidtelldir_sample(void)
{
DIR*dirp;
int save3 =0;
int cur;
int i =0;
struct dirent *dp;
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.