Есть ли смысл в handles вместо сырых указателей для аллокации памяти в embedded устройствах?
Начну с небольшой предыстории, я хочу сделать КПК-подобное устройство на базе esp32s3 с поддержкой загрузки внешних приложений и многозадачностью, Т.к. в моей esp32s3 всего 8мб озу и нет виртуальной памяти + многозадачность + внешние приложения остро встает проблема фрагментации кучи. В теории можно использовать статическую память в приложениях но тогда ОЗУ может забиться очень быстро, поэтому появилась идея использовать handles вместо сырых указателей для работы с кучей как это делали классическая macOS, palmOS и 16 битные версии windows, т.к таким образом система имеет возможность передвинуть память в куче даже без поддержки виртуальной памяти. Но имеет ли смысл такой подход на esp32s3 с 8мб озу? Я знаю про сложность программирования с хэндлами вместо традиционного malloc/calloc (лично для меня это не будет большой проблемой, и вряд-ли это будет устройство под которое найдутся другие разработчики). И я вижу еще 1 плюс в использование handles, в них можно хранить метаданные об объекте, например его размер и тому подобное
А где будете выделять память под handles? Судя по описанию это должен быть какой-то статический массив фиксированного размера не в общей куче.
Для борьбы с фрагментацией идея выглядит здравой. Но с другой стороны, возможно, лучше использовать какой-то аллокатор, в котором уже реализована логика дефрагментации.
res2001, Можно использовать под handles статический массив и если его не будет хватать то выделять новый но уже из кучи, и насчет аллокатора, я планировал заранее выделить условно 4 мегабайта памяти и уже из нее выделять память через хэндлы, т.к куча понадобится для загрузки приложений, потоков freeRTOS, сети и т.п.
res2001, Логика дефрагментации есть и в стандартном аллокаторе из esp-idf но от маленькой долго живущей аллокации в середине кучи он не защитит, потому что без полноценной виртуальной памяти такое сделать невозможно(или крайне сложно)
Catmengi, Если массив хэндлов выделять динамически в той же куче, то он сам будет попадать под операцию дефрагментации и может быть перемещен дефрагментатором и тогда все хэндлы станут не действительны.
В таком случае вам, видимо, надо будет делать 2 кучи. Одну по меньше для разных "системных" нужд, где память не будет принудительно дефрагментироваться и где аллокатор будет выдавать прямые указатели. И вторую на которой будет работать дефрагментатор и память будет выдаваться через хэндлы.
Ну или как-то по другому это обходить.
Думаю, что массив хэндлов, который нельзя перемещать, окажется не единственным подобным объектом в вашей системе.
res2001, поэтому я про хэндлы и вспомнил, такая дефрагментация у меня будет происходить когда в куче свободного места больше или равно размеру необходимой аллокации а свободного места "подряд" меньше размера необходимой аллокации. (надеюсь я смогу написать нормальную и удобную имплементацию этих хэндлов на Си с поддержкой метаданных). И сразу вопрос про метаданные, тут есть 2 варианта их адресации: через enum значения что крайне быстро и сразу видно что поддерживается а что нет, или через строки (можно сделать define на строку чтобы было удобнее писать) где можно добавлять какие-то свойства к хэндлу которые не известны для ОС в compile time, например приложение добавляет какие-то свои метаданные, но тогда для каждого свойства в метаданных необходимо будет делать free callback (что бы не городить костыли для очистки) и придется использовать структуру данных по типу хэштаблицы, которая может неплохо вызывать фрагментацию а она вероятно будет хранится в системной куче или массива но поиск в нем будет выполнятся медленнее
Catmengi, Какие например метаданные могут быть не известны в compile time?
Если пользователю понадобятся добавить к хэндлу свои данные, то он просто объявит свою структуру, хэндл и метаданные положит в эту структуру. В общем как обычно делаются обертки вокруг системных функций.
Вообще в хэндле в качестве метаданных будет некая информация, которая будет необходима вашему дефрагментатору для работы. С их набором вы определитесь, когда будете его писать.
Пока же можно просто положить в структуру хэндла указатель на реальные данные.
А вы планируете вытесняемую многозадачность реализовывать или кооперативную? Ядер сколько на вашей железке?
При вытесняемой надо будет блокировать хэндл на мьютексе при попытке доступа к памяти по нему и освобождать после доступа.
В кооперативном варианте на одном ядре этого, видимо, можно избежать. На нескольких ядрах уже начнется конкуренция.
res2001, 2 ядра по 240мгц, вытесняющая многозадачность, думаю использовать рекурсивный мьютекс и автоматическую разблокировку через __attribute__ cleanup. чтобы получить указатель надо будет сначала заблокировать хэндл
res2001, кстати, насчет многозадачности в GUI я не знаю, т.к планировал делать его на LVGL, есть вариант ограничить количество одновременно работающих GUI приложений до 1, а вот количество приложений без GUI (которые скорее всего будут общаться через RPC, который должен неплохо работать в условиях одного адресного пространства) ограниченно не будет (ограничения будут только по памяти и от самой rtos под капотом)
P.S. или есть вариант написать свой рендерер для этой библиотеки
Т.к. в моей esp32s3 всего 8мб озу и нет виртуальной памяти
Вообще в нём есть MMU с 64-килобайтными страницами который может отображать до гигабайта флэша или PSRAM в 32-МБ окошки 0x3c000000..0x3e000000 и 0x42000000..0x44000000.
Вот мои заметки о формате и расположении записей этого MMU: wiki.osll.ru/doku.php/etc:users:jcmvbkbc:linux-xte...
jcmvbkbc, он есть, да, но заставить кучу работать как на том же самом линуксе с mmu я считаю будет очень тяжело и больно, и наверное не только из-за отсутствия документации, но и из-за размера страницы (я думаю это будет очень затратно для кучи, где большая часть аллокаций будет меньше 64кб)
res2001, сейчас начал работать над имплементацией, вот первая версия структуру данных для хэндлов
#include <stdatomic.h>
#include <pthread.h>
#include <stdint.h>
#define auto_unlock __attribute__((cleanup(NULL)));
enum{
MM_HANDLE_REFCOUNT_METADATA,
MM_HANDLE_SERIALIZE_FUNC,
MM_HANDLE_DESERIALIZE_FUNC,
MM_HANDLE_META_MAX,
}mm_metadata_blocks;
typedef struct{
uint64_t huid; //handle unique ID
void* metadata[MM_HANDLE_META_MAX]; //userdefined metadata;
size_t alloc_size; //size of memory controlled by handle
void* ptr; //pointer to chunk of memory
atomic_int refcount;
}mm_handle_metablock;
typedef struct{
uint64_t huid; //after mm_handle invalidation it would be changed in metablock and all handles with huid != info->huid will cause assert!
mm_handle_metablock* info;
}mm_handle;
res2001, если вам интересно, вот финальная имплементация, там есть несколько "оссобенностей", например то что 2 потока не могут одновременно блокировать handle, но я считаю что это будет лучше, ведь меньше смогу накосячить с этим в будущем
Это лишнее, на мой взгляд. Это должно решать приложение. С таким подходом вы заранее объявляете все данные в куче разделяемым ресурсом, даже если они не являются такими (вышестоящее приложение однопоточное).
Ваш мьютекс должен использоваться только для переноса памяти в другое место. Тут больше подойдет rwlock, где read блокировка будет захватываться потоками при доступе к данным, а write блокировка - дефрагментатором.