OpenWiki

Palm Record Operation Semantic

Palm Notes | Recent Changes | Title Index | User Preferences | Random Page | Help
Difference from prior major revision.
minor diff author diff hide diff

<TableOfContents/>
  
== Введение == 
  
Статья 
занудно 
описывает 
семантику (ну 
да, смысл, 
суть) 
операций над 
базами в PalmOS Data Manager. 
Сие творение 
было 
написано как 
возмущенная 
реакция на 
изменения в PalmOS 
5.4.9 (начало в 
этом треде: 
http://forum.sources.ru/index.php?showtopic=119558 ) . 
Читать с 
интонациями 
писателя 
Коклюшкина. 
  
== 
Классический Data Manager ==
  
В 
классической PalmOS Data Manager располагает свои базы в кучах памяти и реализован как надстройка над Memory Manager. Это приводит к тому, что хэндлы записей можно рассматривать как обычные хэндлы блоков в динамической куче, которые можно лочить, данные которых можно читать итд. Различия между хэндлами из динамической памяти и из хранилища следующие:
  * хэндлы data manager не 
стоит 
освобождать 
и изменять их 
размер. 
  * не стоит 
писать в 
память, на 
которую 
указывает 
хэндл. Даже 
если 
отсутствует 
защита куч по 
записи.
  
Чем плохи эти 
действия? 
Тем, что они 
идут в обход Data 
Manager и не дают 
ему 
возможности 
изменить, 
например, 
размер базы 
при resize записи с 
помощью MemHandleResize. В 
принципе 
изменить 
размер можно, 
но где и 
когда это 
аукнется - 
неизвестно.
  
Какие 
операции над 
записями и 
ресурсами 
предлагаются программисту?
  
=== Записи ===
У записей в 
базе 
существуют 
два признака: 
busy и dirty. Операции 
доступа к 
записям 
изменяют 
состояние 
этих битов.
||**операция**||**смысл**||**действия с флагом busy**||**действия с флагом dirty**||**действия, если флаг busy выставлен**||
||~DmGetRecord||Возвращает хэндл записи для модификации||выставляет||не трогает||возвращает ошибку||
||~DmQueryRecord||Возвращает хэндл записи для чтения||не изменяет||не изменяет||не влияет на функцию||
||~DmReleaseRecord||Освобождает запись||сбрасывает||выставляет по параметру||требует выставленного флага||
  
Некоторые 
неверные 
действия с 
этими 
функциями 
ловится, а 
некоторые 
нет.
  * Если забыть 
вызвать DmReleaseResource 
после DmGetRecord, то 
запись 
останется busy и 
последующие 
DmGetRecord не смогут 
получить эту 
запись
  * Вызов DmReleaseResource 
после DmQueryRecord 
ловится в 
отладочном ROM 
по 
отсутствию 
бита busy
  * Ничто не 
мешает 
сделать DmWrite для 
записи, 
полученной 
по DmQueryRecord. У этой 
записи не 
будет 
выставлен 
бит dirty и не 
изменится modification 
number в заголовке 
базы.
  * 
Неосвобожденные записи отлавливаются в отладочном ROM во время DmCloseDatabase.
  
=== Ресурсы ===
Базы 
ресурсов не 
содержат 
признаков 
записей, 
поэтому 
существует 
только две 
функции 
доступа к 
записям
||**операция**||**смысл**||
||DmGet(1)Resource||Возвращает||~DmGet(1)Resource||Возвращает
хэндл записи||
||DmReleaseResource||Осовобождает хэндл записи||
  
Никакой 
проверки 
правильного 
использования пары функций нет.
  * Функция DmReleaseResource 
ничего не 
делает. 
Неудивительно, что ее отсутствия в коде можно не замечать очень долго.
  
=== Локинг 
записей и 
ресурсов ===
Функции DmGet* 
возвращают 
хэндлы. Эти 
хэндлы можно 
и нужно 
лочить для 
работы с 
данными. 
Важно понять, 
что 
залоченность этих хэндлов абсолютно безразлична Data Manager. Единственное исключение: после любого сброса функция DmInit разлочивает все записи во всех кучах storage. Все остальное время записи могут быть залочены или разлочены. Освобождение записи или закрытие базы на это не влияет.
  
Лок ресурса 
часто 
применяется 
программами-резидентами для регистрации процедурного аларма или нотификации. Такой код лочит ресурс code#1 и передает указатель в функцию регистрации. Даже после выхода из программы указатель на обработчик останется валидным.
  
=== Защита базы ===
К сожалению 
лок записи не 
спасает от 
удаления 
базы. В этом 
случае у 
залочившей 
ресурс 
программы 
нет никакой 
возможности 
обнаружить, 
что 
указатель на 
ресурс 
больше не 
валиден. Для 
предотвращения такой ситуации в API есть функция DmProtectDatabase, которая защищает базу и не позволяет удалять ее. Эта функция не запрещает модифицировать записи базы, но это уже не так принципиально, вряд ли кто-то полезет изменять записи в чужой базе. А вот удалить незащищенную базу можно из любого файл менеджера.
  
Защита базы 
напрямую 
никак не 
связана с 
локом 
записей, ее 
можно 
применять и 
на базе без 
залоченных 
записей. 
Связь между 
локом и 
защитой 
существует 
на уровне 
настоятельных рекомендаций
  
== NVFS. Первая 
попытка ===
Реализация Data 
Manager на базе NVFS 
отличается 
от классики 
тем, что базы 
больше не 
хранятся в 
адресуемой 
памяти. С 
первого 
взгляда 
кажется, что 
существующее DM API позволяет сделать прозрачную подзагрузку записей в кэш. Не получилось. Почему?
  
=== Записи ===
  
Попробуем 
составить 
таблицу 
состояний 
записи в 
кэше. 
Состояния 
гипотетические и составлены из соображений логики, нежели из реального состояния дел.
  
||**статус**||**описание**||**может ли быть выгружена при очистке кэша**||
||загружена 
для 
модификации||запись загружена функцией DmGetRecord||не может||
||загружена 
для 
чтения||запись загружена функцией DmQueryRecord или DmRecordInfo||может. остается хэндл, указывающий на блок размера 0||
||загружена 
для 
повторного 
использования||запись после вызова DmReleaseRecord с dirty==false||может||
||грязная||запись после вызова DmReleaseRecord с dirty==false||не может||
||сирота||запись после вызова DmQueryRecord и ужатая до блока размером 0||не может||
  
  
Отметим 
следующие 
нюансы.
  * DmSyncDatabase 
переводит 
грязную 
запись в 
запись для 
повторного 
использования.
  * Хэндл, 
полученный 
от DmQueryRecord 
требуется 
перед 
использованием проверять на длину. Если она нулевая, то запись стоит перезапросить.
  * Не очень 
понятно 
время жизни 
записи, 
загруженной 
для чтения с 
помощью DmQueryRecord. До 
закрытия 
файла (скорее 
всего)? До 
закрытия 
программы? 
Вообще, 
создается 
впечатление, 
что такие 
записи лучше 
не 
использовать.
  
Как мы видим, 
в целом все 
хорошо и 
предсказуемо. По крайней мере пока. 
  
=== Ресурсы ===
  
Переходя к 
ресурсам, 
хочется 
грязно 
выругаться. API 
Resource Manager не 
позволяет 
программисту выразить свои намерения также явно, как и в Record API. Наличие опциональной DmReleaseRecord не дает никакой информации о том, была ли запись модифицирована или нет. 
  
Как это 
делает NVFS? 
Скорее всего 
этот уровень 
отслеживает 
вызовы DmWrite и 
неявно 
выставляет 
внутренний 
бит dirty для 
таких 
ресурсов.
  
||**статус**||**описание**||**может ли быть выгружена при очистке кэша**||
||загружена||ресурс загружен функцией DmGet(1)Resource||не может||
||загружена 
для 
повторного 
использования||запись после вызова DmReleaseResource||может||
||грязная||измененная запись после вызова DmReleaseResource||не может||
  
Отметим 
следующие 
нюансы.
  * DmSyncDatabase 
переводит 
грязную 
запись в 
запись для 
повторного 
использования.
  * Для 
освобождения кэша требуется явно вызвать DmReleaseResource. А все ли и всегда ли его вызывают? Или же **иногда** ресурсы могут сбрасываться как и записи имени DmQueryRecord?
  
=== Локинг ===
Похоже, что 
залоченные 
ресурсы и 
записи кэш не 
очищает. Что 
он с ними 
делает - 
неизвестно. 
На следующие 
вопросы 
ответ 
неизвестен:
  * Сохраняет 
ли NVFS связь 
между 
залоченным 
ресурсом и 
базой после 
закрытия 
оной? Будет 
ли при 
повторном 
открытии 
базы 
возвращен 
тот же хэндл, 
А если да, то 
всегда ли?
  * Как NVFS 
обрабатывает release для залоченного ресурса? Скорее всего никак, release обрабатывает только незалоченные ресурсы.
  * А может для 
залоченного 
ресурса release 
вообще 
вреден?
  
Эти вопросы 
подводят к 
тому, что 
надежность 
программы-резидента на системе с NVFS под большим вопросом.
  
=== Защита ===
Факт 
защищенности базы никак не влияет на логику кэша. Точка.
  
  
== NVFS в PalmOS 5.4.9 ==
  
К чему 
привела 
вышеописанная логика?
  
  * Первыми 
были побиты 
разработчики резидентов, которые не лочили ресурсы кода в памяти. Те, которые не поддавались были излечены программой RescoLocker, которая лично лочила ресурсы указанных программ в кэше для надежности.
  * Вторыми 
были побиты 
авторы 
резидентов, 
которые 
подписывались не процедурами, а через sublaunch. Это было медленно. Для избавления был написан SharkCache, который лочил указанные программы в кэше для ускорения повторных запусков.
  * Самые умные 
посмотрели 
на результат 
действия 
двух 
предыдущих 
программ и 
охнули. Ибо 
кэш оказался 
забит всякой 
ерундой. 
Программа DBCacheTool 
пыталась 
очистить кэш 
путем 
создания 
временной 
базы нужного 
размера. 
Предполагалось, что создание новых записей вытолкнет из кэша "ненужные" записи. Это привело к тому, что процедура очистки кэша стала вызываться чаще и нашлись все ошибки, допущенные при ее создании. С завидной регулярностью эта операция висла или перегружала устройство.
  * 
Естественно, 
что 
проверять 
записи перед 
каждым 
использованием DmQueryRecord никто не стал. Поэтому программы регулярно напарывались на пустую запись, будучи морально к этому неготовыми.
  
Горе-разработчики NVFS посмотрели на это безобразие и решили переделать. Все и сразу.
  
И сделали 
следующее:
  * DmQueryRecord 
перестала 
возвращать 
пустую 
запись. 
Теперь NVFS влез 
в MemoryManager так, что 
лок такой 
пустой 
записи 
пересчитывает ее из флеша.
  * После 
закрытия 
незащищенной базы все записи или ресурсы переводятся в состояние "загружена для повторного использования" независимо от лока и разлочиваются. Это решает проблему "забыл сделать release" или "забыл сделать unlock". но возникает проблема "забыл защитить". Наверняка в Resco Locker появится опция "защитить базу".
  
Это 
кардинальная переделка и плоха и хороша. Плоха тем, что полностью поменяла логику кэша, хороша тем, что сделала его более контролируемым.
  
=== Заключение ===
  
Как же 
надежно 
защитить 
свой 
резидент от 
превратностей NVFS? Рекомендуется следующий вариант. Он был проверен на программах YAHM и RusHack и показал высокую стабильность:
  
Инсталляция 
резидента:
  * DmOpenDatabase
  * DmGet1Resource
  * MemHandleLock
  * SysRegisterNotification
  * DmReleaseResource (спорный 
шаг. похоже, 
что он 
опционален)
  * DmProtectDatabase(true) (лучше 
делать до 
закрытия 
базы, а то 
вдруг кэш 
успеет ее 
убить?)
  * DmCloseDatabase
  
Запрещение 
резидента:
  * DmOpenDatabase
  * DmGet1Resource
  * MemHandleLock
  * SysUnregisterNotification
  * MemHandleUnlock (за 
текущий лок)
  * MemHandleUnlock (за лок при 
инсталляции)
  * DmReleaseResource
  * DmProtectDatabase(false)
  * DmCloseDatabase
  

Palm Notes | Recent Changes | Title Index | User Preferences | Random Page | Help
Edit this page | View other revisions
Print this page | View XML
Find page by browsing, searching or an index
Edited February 1, 2007 (hide diff)