Ориентация главы

Модуль env тонок, но критичен (пять функций во всём API: час на изучение — вся жизнь на применение): это внешний слой конфигурации (переменные окружения — первый конфигурационный вход эпохи контейнеров: конфигурация приложений по 12-Factor живёт на них) и первый справочный стол информации о хосте (имя системы, версия, идентификатор машины). У чтения два входа, семантики разные: xrtEnvGet возвращает владеющую копию (NULL = не существует — копия изолирует от последующих изменений окружения; безопаснее голого getenv); xrtEnvLookup — двухканальный (возвращает bool + выносит владеющую копию через выходной параметр), и в нём ключевая тонкость: ложь — это только настоящий отказ (скажем, проблема кодировки), а «переменной нет» — состояние успеха с пустым выходом; NULL же у Get-а не различает эти два случая. Оба входа отвечают на вопрос трёх состояний: есть со значением / есть пустое / нет — «нет» не ошибка. Кроме API, глава делает упор на границы применимости переменных окружения как слоя конфигурации — это часть архитектурного решения: что класть в переменные, а что решительно нет.

43.1引入

Разворачивается один и тот же образ в dev/staging/production — как различить конфигурацию? Пути файлов в каждой среде свои; зашить при сборке — выпускать по пакеты на среду. Ответ — переменные окружения: DATABASE_URL, LOG_LEVEL, LISTEN_PORT инъекцируются слоем оркестрации (compose/k8s) по средам, сам образ несёт нулевую разницу сред — конфигурационный столп «собрал один раз — запустил где угодно».

Но и границы надо видеть трезво: это плоское строковое отображение — структурированную конфигурацию (вложенные объекты, массивы) в переменные приходится запихивать с изобретением соглашений кодирования (уплощение вида A_B_C=1), а разросшиеся соглашения — катастрофа; ограничения длины (исторический багаж части платформ) и всестроковая типизация тоже мешают сложной конфигурации. Консенсус отрасли (точка встречи 12-Factor и большинства практик): немного переключателей различий сред (адреса, уровни, вкл/выкл) — в переменные; основную массу бизнес-конфигурации (сложные структуры, частые изменения) — в конфигурационные файлы (JSON/XLON главы 32); стандартная форма — «двухслойная конфигурация», где на старте переменные окружения перекрывают умолчания файла (чтение «уровня журнала из окружения» в главе 37 — ровно этот шаблон).

43.2概念

Чтение и семантика трёх состояний

Ключевой вопрос чтения переменных — три состояния: есть со значением / есть, но пустое / нет. NULL у Get-а и «успех + пустой выход» у Lookup — оба явных ответа «отсутствует»; «нет» никогда не маскируется пустой строкой (пустая строка может быть легальным значением конфигурации). Утверждение missing=ok образца variants демонстрирует: отсутствие идёт путём умолчания, и это второй из двух проверенных путей наряду с «прочитано значение».

Запись и окружение процесса

xrtEnvSet модифицирует блок окружения этого процесса — запись реально действует (не внутренняя таблица-имитация), поэтому создаваемые позже дочерние процессы её унаследуют (механизм наследования окружения при создании процессов — глава 53); xrtEnvRemove удаляет. Запись переменных — операция, влияющая глобально: операции с блоком окружения при многопоточности не потокобезопасны (исторический багаж POSIX); дисциплина — писать только в однопоточную фазу старта, в рантайме только чтение. Set/Remove возвращают bool с отчётом об успехе — семантические различия putenv между Windows и POSIX сглажены реализацией.

Запросы сведений о системе (платформенный канал рантайма)

Семейство информации о хосте: имя и версия ОС (диагностические шапки журналов, решения платформенных ветвей на ходу — ветвление на этапе компиляции через макросы XRT_FEATURE_ главы 8, а то, что известно только в рантайме, — здесь), обзор CPU/памяти (самоотчёт для планирования ёмкости), идентификатор машины (сообщение «кто я» при регистрации распределённого узла). Общая форма таких запросов: только чтение, на старте спросил один раз и кешируй (кроме динамических данных вроде остатка памяти — те по надобности).

Место в слоистости конфигурации

1Слой 1 переменные окруженияпереключатели различий сред (адрес/уровень/порт) - инъекция оркестрацией, читаются процессом раньше всего
2Слой 2 конфигурационные файлыосновная масса бизнеса (структурированно, версионированно) - разбор в дерево значений в главе 32
3Политика слиянияумолчания файла + перекрытие окружением (частный случай плоских ключей ObjectMerge главы 31)
4Слой 3 аргументы командной строкиразовое перекрытие текущего запуска - высший приоритет из трёх

Три слоя, каждый на своём посту: переменные ведают «какова эта среда развёртывания», файлы — «как настроен этот бизнес», командная строка — «что временно поменять в этом прогоне». Приоритет от низшего к высшему: файл < окружение < командная строка — код сборки на старте перекрывает слой за слоем в этом порядке.

43.3示例

Полная программа: чтение трёх состояний и умолчания

Из образца репозитория examples/environment/variants/main.c:

examples/environment/variants/main.c源码 ↗
















 */

#include <stdio.h>
#include <xrt.h>

int main(void)
{
	str sValue = NULL;

	/* 不存在的变量:成功返回 + 空输出。 */
	if ( xrtEnvLookup("XRT_DEFINITELY_MISSING", &sValue) ) {
		printf("missing=ok\n");
	}
	xrtFree(sValue);
	sValue = NULL;

	/* 存在的变量:拥有式副本。 */
	(void)xrtEnvSet("XRT_LOOKUP_EXAMPLE", "hello");
	if ( xrtEnvLookup("XRT_LOOKUP_EXAMPLE", &sValue) && sValue != NULL ) {
		printf("present=%s\n", sValue);
	}
	xrtFree(sValue);
	(void)xrtEnvRemove("XRT_LOOKUP_EXAMPLE");
	return 0;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/environment/variants/main.c -lws2_32 -liphlpapi$ ./a.exemissing=okpresent=hello

Что только что произошло. (1) Чтение неустановленного имени: возврат ложи → программа идёт путём умолчания и выводит missing=ok — отсутствие не ошибка, а одно из трёх состояний. (2) Чтение установленной переменной: представление получено и разобрано — present=hello. (3) Образец демонстрирует владеющий путь: Get возвращает копию, по завершении xrtFree — даже если окружение затем изменится, эта строка не пострадает; когда нужен принцип «сперва существование» — берите Lookup: два входа выбираются позой вызова. (4) Без инъекции тоже всё ок: программа устойчива и к «настроено», и к «не настроено» — в этом смысл стратегии умолчаний.

Полная программа: отчёт о системе

Из examples/system/environment/main.c:

examples/system/environment/main.c源码 ↗


















 */

#include <stdio.h>

#include <xrt.h>



int main(void)
{
	str sValue;

	/* 设置:真实进程环境(子进程会继承)。 */
	if ( !xrtEnvSet("XRT_ENVIRONMENT_EXAMPLE", "hello") ) {
		return 1;
	}

	


	 */
	sValue = xrtEnvGet("XRT_ENVIRONMENT_EXAMPLE");
	if ( sValue == NULL ) {
		return 2;
	}
	printf("value=%s\n", sValue);
	xrtFree(sValue);

	/* 收尾删除,范例不留下全局副作用。 */
	return xrtEnvRemove("XRT_ENVIRONMENT_EXAMPLE") ? 0 : 3;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/system/environment/main.c -lws2_32 -liphlpapi$ ./a.exevalue=hello

Что только что произошло. (1) xrtEnvSet пишет в реальное окружение процесса — дочерние процессы унаследуют (глава 53); семантические различия putenv между Windows и POSIX сглажены реализацией (bool единообразно сообщает успех). (2) xrtEnvGet читает назад владеющую копию — даже при последующем изменении окружения эта строка не пострадает; по завершении xrtFree. (3) xrtEnvRemove удаляет — трёхшаговый замкнутый цикл (Set/Get/Remove) и есть минимальный полный набор управления окружением. Запросы сведений о системе (имя/версия/идентификатор машины из зоны понятий) живут в том же семействе заголовков и служат диагностическим шапкам и отчётам при регистрации узлов.

43.4契约

  • Семантика трёх состояний: есть со значением / есть пустое / нет — API чтения двухканальные; отсутствие идёт по явным умолчаниям.
  • Момент записи: запись окружения — только в однопоточную фазу старта; в рантайме только чтение (двойное требование потокобезопасности и стабильности представлений).
  • Наследование дочерними: изменение окружения этого процесса действует на создаваемые позже дочерние процессы (семантика наследования главы 53).
  • Слоистость конфигурации: умолчания файла < перекрытие окружением < командная строка высшая — в переменных только «переключатели различий сред».
  • Два платформенных канала: различия на этапе компиляции — макросы возможностей (глава 8), различия в рантайме — запросы этого модуля; не смешивать.

От примеров к инженерии: три хозяина переменных окружения

Контейнерное развёртывание (домашняя арена): образ с нулевой разницей сред; DATABASE_URL/LOG_LEVEL/PORT инъекцирует оркестрация по средам — приземлённый принцип конфигурации 12-Factor; адреса проверок здоровья и пути ссылок на секреты тоже часто идут здесь. Разработка и отладка: запуская сервис локально, подменяйте переменной (LOG_LEVEL=DEBUG ./app) — быстрее правки конфигурационного файла и не оставляет следов; синтаксис временного префикса shell-а рождён именно для этого. Конструирование окружения потомка: перед созданием дочернего процесса (глава 53) xrtEnvSet кастомизирует его окружение (даёт ему собственный вид конфигурации) — родитель меняет окружение, ребёнок наследует эффект. Три хозяина сообща подтверждают позиционирование «переменные окружения = переключатели различий сред» — потребности конфигурации за этими рамками должны уходить в файловый слой.

Эксплуатационный взгляд: окружение — это интерфейс

Описание развёртывания (секция env у compose/k8s) — внешний интерфейс приложения: оно объявляет, «какие входы различий сред этому сервису нужны». У интерфейса должны быть документация и валидация: на старте перечислите все ожидаемые переменные и напечатайте результат сборки (LOG_LEVEL=INFO (env) или (default)) — «журнал сборки конфигурации» — первая остановка отладки; отсутствующая обязательная переменная (в продакшене не дали DATABASE_URL) — быстрый отказ с ясной ошибкой, а не полёт на пустых значениях с взрывом на первом запросе. Эти два пункта плюс «не писать в рантайме» из зоны ловушек — три ступени лестницы от «работает» до «эксплуатируемо».

Полная картина сборки конфигурации

Поместим главу в общую панораму: сборка на старте читает три слоя (умолчания файла < перекрытие окружением < командная строка высшая), результат сборки входит в дерево значений (глава 31) для потребления всей программой; журнал сборки печатает итоговые значения и их источники; обязательные пункты валидируются быстрым отказом. Этот сборщик — полная форма «конфигурационной станции» конвейера главы 36, и продвинутая версия упражнения два — это он. Собрали один раз — глобально только чтение: горячее обновление конфигурации (правки в рантайме) идёт путём «новое дерево значений атомарно подменяется + уведомление» (три хозяина главы 31), а не записью в окружение на ходу — дисциплины двух слоёв перекликаются вновь.

Типичные ошибки

Ловушка 1: бизнес-конфигурация, запихнутая в переменные окружения:Ловушка 1: бизнес-конфигурация, запихнутая в переменные окружения

Симптом: список переменных идёт на десятки и продолжает расти; SERVICE_A_TIMEOUT_MS, SERVICE_A_RETRY_COUNT... соглашения об именовании образуют собственную систему; каждая правка конфигурации требует править описание развёртывания в системе оркестрации.

Причина: плоскую строковую форму приняли за универсальный слой конфигурации — структурированную конфигурацию силой расплющили, сложность соглашений растёт экспоненциально.

错误写法
/* с десяток бизнес-параметров все через окружение - оркестрация стала деградировавшим конфиг-файлом */
int timeout = env_int("SVC_TIMEOUT_MS", 3000);
int retry = env_int("SVC_RETRY", 3);
str endpoint = env_str("SVC_ENDPOINT", "...");
/* ... ещё двадцать ... */
正确写法
/* основная масса конфигурации в файле (структурированное дерево значений), в окружении - только переключатели сред */
xvalue* pCfg = xrtJsonParse(read_file("config.json"), NULL);
int timeout = cfg_int(pCfg, "timeout", 3000);           /* умолчание файла */
if ( env_has("SVC_TIMEOUT_MS") ) {                       /* перекрытие окружением */
	timeout = env_int_override("SVC_TIMEOUT_MS");
}
Ловушка 2: запись переменных окружения в рантайме:Ловушка 2: запись переменных окружения в рантайме

Симптом: изредка падения в постороннем коде; гонка операций с блоком окружения под POSIX (unsetenv одновременно с getenv — UB) всплывает под нагрузкой.

Причина: блок окружения — глобальное изменяемое состояние, исторические API без синхронизации — запись в рантайме выдёргивает ковёр из-под ног всех читающих потоков.

错误写法
void handle_config_reload(void)
{
	xrtEnvSet(XRT_STR_LITERAL("APP_MODE"), ...);   /* запись в рантайме - для параллельных читателей UB */
}
正确写法
/* записи окружения сконцентрированы в начале main (однопоточная фаза) */
int main(void)
{
	apply_env_overrides();          /* единственная точка записи */
	/* дальше - только чтение */
}

Практические задания

Базовое: проверка трёх состояний (сперва утверждение на бумаге, затем прогон со сверкой)

Для переменной неустановленной, установленной пустой и установленной со значением прочтите обоими входами Get и Lookup — напечатайте шесть результатов; различие подачи трёх состояний двумя входами зафиксируйте в выводах.

Продвинутое: двухслойная сборка конфигурации (трёхслойный приоритет на практике)

Реализуйте загрузку конфигурации «умолчания JSON-файла + перекрытие окружением»: в файле 5 ключей, из них 2 поддерживают перекрытие (отображение префикса APP_); после перекрытий напечатайте итоговую конфигурацию. Третий слой приоритета (файл < окружение < командная строка) достройте простым разбором argv.

Вызов: сведения для регистрации узла (тело отчёта на сцепке модулей)

Реализуйте node_info(): имя/версия системы (этот модуль) + идентификатор машины + время старта процесса (глава 42) + номер версии (глава 8), упакованные в JSON-дерево значений (глава 31) — тело отчёта при регистрации распределённого узла. Критерии приёмки: два прогона — статические поля стабильны, поле времени старта меняется; JSON разбирается стандартным парсером; поля всех типов (время — типом time, глава 33).

Итоги

ТемаКратко
Два входаGet-копия (NULL = нет; два отказа неразличимы) / Lookup (нет = успех + пустой выход; ложь — только настоящий отказ)
Дисциплина записиписать только в однопоточную фазу старта; в рантайме только чтение; Set/Remove возвращают bool
Слоистостьконфиг-файл < окружение < командная строка; в окружении — только переключатели различий, не основная масса бизнеса
Наследованиезапись в окружение этого процесса реально действует; создаваемые позже потомки наследуют
Два платформенных каналаразличия компиляции — макросы возможностей (глава 8) / различия рантайма — запросы этого модуля - не смешивать
Отчёт узласведения о системе + идентификатор машины + время старта + версия - стандартная четвёрка тела регистрации
Три эксплуатационные ступенижурнал сборки с источниками / обязательные пункты - быстрый отказ / в рантайме только чтение - от «работает» до «эксплуатируемо»
Место в панорамеглава - фундамент конфигурационной станции конвейера главы 36; горячее обновление - через подмену дерева значений, не через окружение

📖 Справочники API в этой главе:environment (5 записей)

Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки