[Из песочницы] Интеграция FATFS библиотеки для организации чтения дискового устройства на iOS
Введение
Статья посвящена внедрению open source библиотеки на iOS для чтения/записи данных с MFI дискового устройства на основе FAT12/FAT16/FAT32/Exfat. Представлен способ построения архитектуры приложения, на основе FATFS библиотеки, а также методы отладки и тестирования проводных MFI-устройств. Статья практически не содержит кода из-за соблюдения NDA.
Постановка задачи
Главной целью было создание некого универсального SDK (.framework), способного работать с производителями различных устройств дискового доступа по протоколу MFI, использующих единый стандарт по API.
На рисунке представлена обобщенная структурная схема такого фреймворка. В зависимости от протокола устройства выбирается та или иная библиотека взаимодействия с устройством. В зависимости от того, какая файловая система (FS) используется на диске, выбирается соответствующий алгоритм работы с диском: FAT/exFat/Other FS. При этом, к устройству могут иметь доступ несколько приложений (использующих данный SDK), находящихся в памяти устройства однако запись/чтение единовременно может выполнять только одно. Данный SDK подразумевает использование его во многопоточном приложении c конкурентными задачами чтения и записи.
Поиск решения
Первоначально в качестве базовой библиотеки для реализации FS FAT32 была выбрана реализация FAT32, созданная Apple. Однако, сложность интеграции стандартного API для устройства доступа вследствие привязки к конкретной платформе заставила пойти в направлении поиска готового решения с раздельным абстрактным API для физического уровня доступа c MFI устройством через библиотеку производителя. Были рассмотрены также EFSL и FATFS open source реализации FAT32. FATFS была выбрана вследствие нескольких причин:
• текущая поддержка библиотеки
• независимость платформы и простота портирования
• широкий спектр параметров конфигурации
• поддержка exFAT
• абстрактный уровень доступа для медиа драйверов
Вследствие проблем получения логов работы приложения, учитывая, что физически разъем был занят и не было возможности подключения к XCode, была подключена библиотека NSLogger, позволяющая передавать логи по Wi-Fi почти в real time режиме. Тем не менее, проблема отладки такой библиотеки имела место быть. Было решено разработать симулятор MFI устройства, который обращался к некой области на диске iOS/MAC устройства и эмулировал чтение/запись по тому же API, что и реальное устройство.
Проблемы интеграции
Портирование на Obj-C
FATFS написан на чистом С, а интегрировать приходилось в библиотеку написанной на ObjC. Несмотря на то, что ObjC напрямую поддерживает интеграцию C функций, возникали проблемы с сигнатурами функций и с использованием некоторых типов переменных.
Поддержка Unicode
Опция Unicode адекватно работала только с параметром кодировки Obj-C NSUTF16LittleEndianStringEncoding, несмотря на то, что согласно заявленным требованиям поддерживалось дополнительно UTF-16BE и UTF-8. Все методы, работающие с именами файлов необходимо было сделать потокобезопасными для корректной работы FAT32.
Thread safe
Опция потокобезопасности, активизируемая по параметру FS_REENTRANT и реализуемая через функции ff_req_grant, ff_rel_grant, ff_del_syncobj потребовала дополнительной имплементации через POSIX. Без включенной опции потокобезопасности наблюдалось повреждение таблицы файловой системы и как следствие — потеря данных. Файловый дескриптор pthread_t не сразу удалось корректно имплементировать вследствие того, что функции ff_ для синхронизации потоков передавали данные по значению, а не по ссылке. После замены на ссылочные значения — проблем с потокобезопасностью выполнения операций не возникало.
Кеширование
Во избежание потери данных при копировании и повреждении таблицы файлов, применялось синхронизация кэша данных записываемого файла с помощью функции f_sync. Учитывая, что размер дискового пространства мог быть более 16 Гб, некоторые базовые типы переменных пришлось поменять на «long log». FATFS изначально проектировалась с расчетом на встраиваемые устройства, характеризующиеся ограниченным размером памяти и низкой производительностью. Как следствие функции чтения/записи выполнялись по одному кластеру дискретно в единицу времени, что существенно ниже непрерывного чтения/записи. Подобная же проблема возникла при чтении структуры каталогов при наличии в директории более 100 файлов. Чтение/запись одного файла линейно росла с увеличением количества файлов, как показано на рисунке. По оси «x«отражено количество файлов в директории, а по оси «y» — время чтения в секундах. Типы кривых на графике для: V2 Sim Fat –симулятор дискового устройства, V1 Fat файловая система FAT32, для которой отсутствует данная проблема (не FATFS), V2 Fat64/128 FATFS c размером диска 64 и 128 Гб, соответственно.
Структурная схема работы кэша в таком SDK представлена на рисунке ниже. Таким образом кэшировалась не только файловая структура, но и сектора для низкоуровневых функций чтения/записи медиа устройства, вследствие отсутствия аппаратного кэша.
Уникальный доступ приложения к устройству
Приложения с интегрированным SDK не должны одновременно иметь доступ к внешнему дисковому устройству. Правила которым должно подчиняться приложение с используемым SDK должно выглядеть как показано ниже на диаграмме состояний.
Для установления правил доступа к устройству был имплементирован механизм основанный на Darwin Notification Center позволяющий с помощью уведомлений разрешать или блокировать работу с устройством приложения когда с устройством работает еще одно приложение с данным SDK. При первом запуске приложения отправляется запрос всем приложениям, подписанным на получение определенных уведомлений. Каждое приложение публикует свое состояние для остальных приложений, чтобы новое приложение могло перейти в адекватное состояние. В случае, если устройство свободно, приложение переходит в состояние USING. Состояние PENDING является промежуточным для BUSY и WAIT на тот случай, когда устройство не доступно или занято. В случае сбоя в коммуникациях используется состояние INVALID. После завершения использования устройства приложение переходит в состояние INACTIVE.
Тестирование Фреймворка
Написание Unit Tests для всех функций библиотеки было критически необходимо вследствие жёстких детерминированных требований к поведению API для конечных пользователей. Первоначально все тесты выполнялись на симуляторе дискового устройства. Однако, симулятор и библиотеку для низкоуровневого доступа диска писали разные люди, не имеющие возможности взаимодействия, в связи с чем поведение симулятора не всегда совпадало с поведением драйвера устройства. Для этого была разработана схема тестирования на реальном устройстве, представленная ниже. Тестируемый framework интегрировался в приложение, написанное с использованием Private API, а установка производилась через Xcode Server Over-the-Air installation. Private API позволяло выводить приложение из Background состояния приложения по Push Notification и запускать необходимые тесты. Результаты тестов отправлялись по REST протоколу на сервер статистики, где впоследствии они представлялись в виде графиков и таблиц. В библиотеку также включены Activity Tracing функции для получения более подробного crash лога.
Заключение
Несмотря на серьезные недостатки FATFS opensourse библиотеки, требующие доработки, другой альтернативы с возможностью использования Fat32 и ExFat, а также с абстрактным уровнем поддержки media drivers — найти не удалось. При корректной модификации библиотеки с подключенными дополнительными функциями кеширования и буферного чтения/записи, функции копирования и чтения файлов FAT32 API ниже скорости копирования LBA блоков медиа драйвера производителя железа всего на 10–15%. Для новичков в области разработки и интеграции файловых систем — FATFS может являться вполне достойным выбором.
Полезные ссылки
→ FatFs — Generic FAT File System Module
→ Microsoft EFI FAT32 File System Specification
→ Darwin Notification Concepts
→ About Continuous Integration in Xcode