Ориентация главы
«Встраивание» — первоклассная точка зрения в дизайне XRT — API каждой главы живут в предположении «быть частью программы-хозяина». Эта глава собирает разбросанные по главам точки интеграции в полное руководство: память (xrtSetAllocator захватывает на входе в main — дальше все выделения библиотеки идут хозяину; блокировка по первому выделению хранит стабильность); ошибки (отображение потокового слота ошибки в систему исключений хозяина — каждое «возвращаемое значение объявляет отказ» есть кандидат-источник исключения хозяина; объекты ошибки переносятся между слоями); обрезка (хозяин выбирает только нужные корневые модули — семантика обрезки главы 131 на стороне хозяина; минимальная интеграция не растёт незаметно); наблюдаемость (почти нулевая по стоимости всегда включённая счётная memory_stats — панель здоровья хозяина читает напрямую); выбор формы (одиночный заголовок/статическая/динамическая — три вида потребителей главы 132 как решение хозяина). Высшая цель встраивания: библиотека — деталь хозяина, не параллельное королевство — память под вашим управлением, ошибки переводите вы, размер определяете вы.
135.1引入
Самая частая форма провала интеграции хозяина — «библиотека хочет быть хозяином»: malloc по собственной инициативе (памятный бюджет хозяина уходит из-под контроля), ошибки только печатаются (система ошибок хозяина не может их поймать), полный объём втащен (встраиваемое не помещается), глобальное состояние решает само (сосуществование многих экземпляров/версий падает). Каждая точка интеграции XRT — прямой ответ на эти провалы — и каждая закреплена тестами (замена распределителя — двухтрековая проверка allocator_tour, отображение ошибок — проверка изоляции слота _threads, обрезка — утверждения профиля размера, stats — базовая линия накладных расходов).
Стоит сперва построить тайминг-картину захвата: первый акт main меняет распределитель (до блокировки) → инициализация инфраструктуры хозяина → включение stats по надобности → в период бизнеса слот ошибки ходит с потоком → на финале снимок stats + Drain всех объектов библиотеки. Неправильный порядок (смена после первого выделения) — ровно сценарий отказа главы 5 — эта глава нанизывает их на таймлайн.
135.2概念
Захват памяти: распределитель и блокировка
Частые формы распределителя хозяина: arena (bump-выделение на запрос — весь блок освобождается по концу запроса, прикладное применение главы 7), счётчик бюджета (квотный распределитель — потолок памяти библиотеки принадлежит политике хозяина), прокси-учёт (пересылка системному распределителю + бухгалтерия — панель хозяина читает потребление библиотеки напрямую). Смысл блокировки ещё раз: два модуля библиотеки (или два места кода хозяина) оба пытаются сменить распределитель — первый пришёл, второй отвергнут — стабильность интеграции прежде гибкости.
Отображение ошибок: из слота в систему хозяина
Отчёт об отказе в XRT — «возвращаемое значение + потоковый слот ошибки» (глава 4) — отображение на стороне хозяина задаёт три вопроса: где перехватывать (проверять возвращаемое значение в каждой точке вызова XRT — макрос/обёрточная функция перехватывает единообразно); как переводить (xrtErrorKind → тип исключения/код ошибки хозяина; xrtErrorMessage → человекочитаемое сообщение; цепочка причин → разворачивание в журнал); как переносить (между потоками сперва TakeError — семантика переноса главы 4, исполненная в очереди задач хозяина). Изоляция слота: потоки хозяина держат каждый свой слот — изоляция, проверенная тестами _threads, и есть гарантия конкурентной безопасности хозяина.
Обрезка и размер: право выбора хозяина
Хозяин объявляет корневые модули (глава 131) — артефакт хозяина «только контейнеры + строки» не содержит ни одного сетевого символа (утверждение forbid_symbols). Безопасность эволюции: без выбора = Core — будущие новые модули XRT не увеличат незаметно артефакт хозяина (контракт прямо называет цель этого правила). Согласованность многих TU: все TU хозяина используют одинаковое множество модулей (при модульной линковке) — сборочная система определяет в одном месте (target_compile_definitions CMake объявляется однажды).
Наблюдаемость: stats всегда включён в продакшне
Роль memory_stats (представлена в главе 6) на стороне хозяина — источник данных панели здоровья: xrtMemStatsEnable(true) один раз, периодическое чтение Snapshot — malloc_calls (число бизнес-выделений)/pooled (попадания в пул)/direct (напрямую)/backing (число обращений к системе) — четыре чтения образуют минимальный набор метрик здоровья памяти. Почти нулевые накладные расходы — предпосылка постоянного включения (только счётчики, без записей — против высокозатратной полной записи memory_debug); обнаружение утечек: утечка видна как live не возвращается к нулю идёт в пороги тревог долго работающих сервисов.
Решение хозяина о трёх формах
Однозаголовочный (скрипты/прототипы — один include)/статическая библиотека (настоящие проекты — линковка есть заморозка)/динамическая (хозяева плагинов — независимое обновление) — выбор потребителя главы 132 в контексте хозяина повторяется как интеграционное решение: стоимость стыковки со сборочной системой хозяина и форма распространения решают; согласованность поведения (утверждение verify) делает переключение незаметным.
135.3示例
Первая полная программа: панель здоровья с всегда включёнными stats
Программа ниже из examples/memory/stats — образец наблюдения в продакшн-форме:
*/ #include <stdio.h> #include <xrt.h> int main(void) { xmemstats tStats; ptr pSmall; ptr pLarge; /* 开关可查询:先确认当前状态,再打开并清零。 */ printf("before=%d\n", xrtMemStatsEnabled() ? 1 : 0); xrtMemStatsEnable(true); printf("after=%d\n", xrtMemStatsEnabled() ? 1 : 0); xrtMemStatsReset(); /* 一次小块(走池)+ 一次大块(直通)。 */ pSmall = xrtMalloc(48); pLarge = xrtMalloc(4096); if ( (pSmall == NULL) || (pLarge == NULL) ) { xrtFree(pSmall); xrtFree(pLarge); return 1; } xrtFree(pSmall); xrtFree(pLarge); /* 读取窗口内计数(字段语义见文件头读数解释)。 */ xrtMemStatsGet(&tStats); printf("malloc_calls=%llu pooled=%llu direct=%llu backing=%llu\n", (unsigned long long)tStats.MallocCalls, (unsigned long long)tStats.PooledAllocCalls, (unsigned long long)tStats.DirectAllocCalls, (unsigned long long)tStats.BackingAllocCalls); /* 用完关闭,回到零开销模式。 */ xrtMemStatsEnable(false); return 0; }
Что только что произошло. (1) xrtMemStatsEnable(true) + Reset — опрос переключателя (before/after), затем обнуление: дисциплина наблюдения сперва убедиться, потом действовать. (2) После двух бизнес-выделений (малый блок 48Б + большой 4096Б) чтение снимка: pooled=1 (малый попал в пул — ноль системных вызовов), direct=1 (большой сверх порога прошёл напрямую), backing=3 (по-настоящему на стороне системы 3 — прямой 1 + внутренний прогрев пула 2) — сверка бизнес-взгляда с системным и есть сырьё планирования ёмкости. (3) Периодическое «снимок + чтение» панелью хозяина — всё мониторинг здоровья памяти библиотеки — постоянное включение стоит почти ничего (инкременты счётчиков).
Вторая полная программа: полная проверка захвата распределителя
Вторая программа из examples/core/allocator_tour — приёмка захвата памяти хозяином:
*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <xrt.h> /* 计数分配器:仍路由系统堆,便于验证替换生效。 */ typedef struct examplecount { size_t iAllocs; size_t iFrees; } examplecount; static ptr exampleAlloc(ptr pContext, size_t iSize) { ++((examplecount*)pContext)->iAllocs; return (ptr)malloc(iSize); } static ptr exampleRealloc(ptr pContext, ptr pMemory, size_t iSize) { (void)pContext; return (ptr)realloc(pMemory, iSize); } static void exampleFree(ptr pContext, ptr pMemory) { ++((examplecount*)pContext)->iFrees; free(pMemory); } int main(void) { xallocator Default; xallocator Custom; examplecount Count; uint8* pBlock; uint8* pDup; int iResult = 1; /* ---- 必须在任何 XRT 分配前完成替换。 ---- */ xrtGetAllocator(&Default); memset(&Count, 0, sizeof(Count)); Custom.Context = (ptr)&Count; Custom.Alloc = exampleAlloc; Custom.Realloc = exampleRealloc; Custom.Free = exampleFree; if ( !xrtSetAllocator(&Custom) ) { goto Cleanup; } /* ---- At 族五接口:位置参数不影响堆行为。 ---- */ pBlock = (uint8*)xrtMallocAt(4u, __FILE__, __LINE__); if ( (pBlock == NULL) || ((pDup = (uint8*)xrtMemDupAt("hi", 2u, __FILE__, __LINE__)) == NULL) || (memcmp(pDup, "hi", 2u) != 0) || ((pBlock = (uint8*)xrtReallocAt(pBlock, 16u, __FILE__, __LINE__)) == NULL) || (xrtCallocAt(2u, 4u, __FILE__, __LINE__) == NULL) ) { goto Cleanup; } /* Calloc 清零验证单独做——保留指针用于释放。 */ { uint8* pZero = (uint8*)xrtCallocAt(2u, 4u, __FILE__, __LINE__); if ( (pZero == NULL) || (pZero[0] != 0u) || (pZero[7] != 0u) ) { goto Cleanup; } xrtFreeAt(pZero, __FILE__, __LINE__); } xrtFreeAt(pDup, __FILE__, __LINE__); xrtFreeAt(pBlock, __FILE__, __LINE__); printf("allocator: swap -> at-family alloc/calloc/dup/realloc ok\n"); * 且 GetAllocator 仍报告已安装的计数分配器。 ---- */ * 分配才经过自定义 Alloc;计数只需 >= 1 即证明替换生效。 */ if ( xrtSetAllocator(&Default) || (Count.iAllocs < 1u) ) { goto Cleanup; } { xallocator Current; xrtGetAllocator(&Current); if ( (Current.Alloc != Custom.Alloc) || (Current.Free != Custom.Free) ) { goto Cleanup; } } printf("allocator: locked after first alloc -> swap rejected ok\n"); iResult = 0; Cleanup: return iResult; }
Что только что произошло. (1) После замены проверены все четыре входа at-семейства (Alloc/Calloc/Dup/Realloc) — полная грань, которую обязан реализовать распределитель хозяина (не только Alloc — семантику Realloc часто забывают). (2) Проверка блокировки второй строкой: второй SetAllocator отвергнут — тайминг-контракт интеграции хозяина (обязан предшествовать первому выделению) из ошибки превращён в проверенный факт. (3) Эти два образца вместе — «Hello World+» интеграции хозяина: захват (этот) + наблюдение (предыдущий) — суверенитет памяти и видимость прибывают одновременно.
Чек-лист хозяина: порядок приёмки интеграции
Сверните главу в чек-лист перед запуском — отмечайте пункты в порядке зависимостей: (1) распределитель захвачен в первой строке main (все четыре входа at-семейства реализованы, семантика блокировки известна); (2) обрезка модулей объявлена (список корневых модулей в сборочной системе, запрещённые символы профиля размера проверены); (3) слой отображения ошибок на месте (таблица перевода Kind, цепочка причин в журнале, путь TakeError между потоками); (4) наблюдение включено постоянно (stats enable + периодические снимки + пороги тревог по четырём чтениям); (5) форма диагностики определена (в проде применена деградация до EXCLUDE, отладочные сборки переключаемы); (6) форма артефакта выбрана (одиночный заголовок/статическая/динамическая — стыковка со сборочной системой хозяина проверена); (7) путь финала отрепетирован (все объекты библиотеки Drained, финальный снимок stats, слоты ошибки очищены). Все семь отмечены — XRT вливается в хозяина как «деталь»: память видна, ошибки переводимы, размер управляем, поведение проверяемо — полное состояние встраивания не «компилируется», а «суверенитет ясен».
135.4契约
- Тайминг захвата: SetAllocator до любого выделения XRT (уровень первой строки main); блокировка после первого выделения; все четыре входа at-семейства реализованы.
- Суверенитет памяти: после захвата все выделения библиотеки (включая внутренности пулов) идут хозяину — бюджет/статистика/arena принадлежат хозяину.
- Отображение ошибок: проверка возвращаемого значения в точках вызова → перевод Kind → показ Message → цепочка причин в журнал; между потоками сперва TakeError.
- Изоляция слота: потоки хозяина держат каждый свой слот ошибки — конкурентная безопасность закреплена тестами _threads.
- Суверенитет обрезки: хозяин объявляет корневые модули; без выбора = Core без роста; множества многих TU согласованы (одно определение в сборочной системе).
- Наблюдение всегда: stats только считает, почти нулевая стоимость; четыре чтения (calls/pooled/direct/backing) — минимальный набор здоровья.
- Согласованность форм: поведение однозаголовочного/статической/динамической утверждается verify — переключение хозяина бесплатно.
- Диагностика съёмная: семантика исключения MEMORY_DEBUG (глава 131) — полная продакшн-функциональность минус диагностика.
Типичные ошибки
Симптом: SetAllocator возвращает отказ — внутренние структуры библиотеки (пулы/первые кэши) уже построены распределителем по умолчанию.
Причина: контракт блокировки. Первый акт main хозяина — смена; любой порядок «сперва что-то инициализировать» может пересечь черту.
int main(void) { init_something_using_xrt(); /* внутри уже выделялось */ xrtSetAllocator(&MyAlloc); /* отвергнуто: первое выделение произошло */ }
int main(void) { xrtSetAllocator(&MyAlloc); /* первый акт */ init_something_using_xrt(); /* всё через распределитель хозяина */ }
Симптом: сторона хозяина видит только «отказ» — категория/домен/цепочка причин потеряны — стратегия повторов может лишь гадать.
Причина: ленивый слой отображения (передаёт только bool). Kind — машинно-читаемое основание решений (глава 4) — TIMEOUT повторить, AGAIN подождать, ARGUMENT завести баг — проглотить его значит разжаловать структурированную ошибку в булево.
bool my_json_parse(str s, xvalue** out) { if ( !xrtJsonParse(...) ) { return false; } /* все детали потеряны */ }
bool my_json_parse(str s, xvalue** out, my_error* e) { if ( !xrtJsonParse(...) ) { fill_host_error(e, xrtTakeError()); /* Kind/домен/цепочка переведены полностью */ return false; } }
Симптом: продакшн работает с включённым memory_debug (каждое выделение записывается подробно) — производительность падает, память удваивается.
Причина: роли разные: stats считает (всегда), debug пишет детали (кратко при разборе). Продакшн-умолчание комбинации: stats включён + debug исключён (XRT_EXCLUDE_MEMORY_DEBUG — комбинированная семантика главы 131).
#define XRT_MODULE_ALL /* с memory_debug - полная запись деталей в проде */#define XRT_MODULE_ALL #define XRT_EXCLUDE_MEMORY_DEBUG /* прод: stats остаётся, детали убираются */ /* отладочная сборка добавит XRT_MODULE_MEMORY_DEBUG явно */
Практические задания
Arena хозяина (bump-выделение + освобождение всего блока по концу запроса) как распределитель XRT: внутри одного запроса контейнерно-строковый бизнес. Критерии приёмки: ноль системных выделений внутри библиотеки (backing=0 или только большой блок arena); одно освобождение по концу запроса; stats сходится.
Написать функции отображения хозяина (Kind → коды ошибок хозяина, Message + цепочка причин → структуры журнала) + обернуть три частых вызова. Критерии приёмки: шесть значений Kind каждое отображается верно; межпоточная задача несёт ошибку (путь TakeError) без потерь.
Два «тенанта» (независимые бизнес-модули) в одном процессе, каждый со своей обрезкой, деля один экземпляр XRT: единый распределитель (бюджеты по тенантам — контекст различает), ошибки изолированы по потокам, stats читается по тенантам. Критерии приёмки: OOM тенанта A не влияет на B; ошибки не пересекают слоты; объём есть объединение замыканий обоих тенантов.
Итоги
| Тема | Кратко |
|---|---|
| Высшая цель | библиотека — деталь хозяина — суверенитет памяти/ошибок/размера у хозяина |
| Тайминг захвата | SetAllocator до любого выделения XRT; все четыре входа at-семейства |
| Блокировка | после первого выделения не меняется — стабильность интеграции прежде |
| Отображение ошибок | точки возвращаемого значения → перевод Kind → цепочка в журнал; TakeError между потоками |
| Изоляция слота | потоки держат каждый свой — гарантия конкурентности хозяина, закреплённая _threads |
| Суверенитет обрезки | объявить корневые модули; без выбора = Core; эволюция не растит незаметно |
| stats всегда | почти нулевая стоимость, четыре чтения: calls/pooled/direct/backing |
| Диагностика съёмная | ALL+EXCLUDE_MEMORY_DEBUG — полная прод-функциональность минус детали |
| Решение о форме | однозаголовочный/статическая/динамическая — поведение согласовано, переключение бесплатно |
📖 Справочники API в этой главе:core (25 записей)memory_stats (5 записей)error (34 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки