Ориентация главы
Модуль process инженеризует «запуск внешней программы»: семейство Shell (xrtProcessShell — вся командная строка идёт в системный shell, лёгкий путь вызова утилит) и семейство Spawn (xrtProcessSpawn — прямой запуск из массива argv, без поверхности инъекций, безопасный путь для недоверенного входа); захват вывода (Result.Stdout — владеющий буфер, перехватывающий стандартный вывод потомка); конвейеризация (xrtProcessPipeline — stdout каждой стадии автоматически подключается к stdin следующей, эквивалент shell-ового a | b); трёхзначный статус выхода (нормальный выход с кодом / убит сигналом / отказ запуска — различает xrtProcessResultSuccess). Наследование окружения, рабочий каталог и тайм-аутное убийство (лестница SIGTERM→SIGKILL) объявляются в конфигурации.
52.1引入
Три сцены. Сцена первая: службе обработки изображений нужен внешний ffmpeg — простейший system(): склейка командной строки (пробел в имени входного файла — катастрофа), вывод не захватить, тайм-аут не поставить (ffmpeg завис — и служба зависла с ним). Сцена вторая: инструменту сборки нужно запустить gcc main.c -o app и получить вывод ошибок — нужны захват stderr и код выхода, а system даёт один int. Сцена третья: текстовый конвейер cat | grep | sort — многостадийная цепочка процессов, где ручная склейка труб — управление дескрипторами и двусторонние deadlock-и (верхний поток заблокировал запись, нижний не читает) — одни капканы.
Три сцены соответствуют трём наборам возможностей: семейство Spawn (xrtProcessSpawn(配置) (конфигурация) возвращает дескриптор) плюс настройка тайм-аута (безопасная версия сцены один), двухпоточный захват плюс трёхзначный вердикт (сцена два), автосклейка труб Pipeline-ом (сцена три). Разделительная линия Shell и Spawn — доверие: командная строка из вашего кода (доверенная) — Shell для экономии сил; из пользовательского ввода (недоверенного) — обязательно Spawn+Argv — командная инъекция одного семейства с SQL-инъекцией, и линия обороны одна: «не вклеивать вход в интерпретатор».
52.2概念
Два семейства: Shell и Spawn
| Измерение | Семейство Shell | Семейство Spawn |
|---|---|---|
| Вход | xrtProcessShell(命令串, 结果) (командная строка, результат) | xrtProcessSpawn(配置) (конфигурация) |
| Интерпретация | cmd/sh трактует метасимволы | прямой запуск ядром, ноль интерпретации |
| Применимость | вызов утилит, доверенные команды | недоверенный вход, точное управление |
| Поверхность инъекции | склейка командной строки и есть риск | нет - аргументы передаются как есть |
Конфигурация (xprocessconfig) объявляет среду исполнения: рабочий каталог, переменные окружения (инкрементальное перекрытие окружения процесса из главы 43), обработка стандартных потоков (отбросить/унаследовать/захватить/труба), тайм-аут и лестница убийства (по тайм-ауту сперва SIGTERM, после льготного периода SIGKILL — передающий конец разделения приёма/передачи главы 49).
Трёхзначный статус выхода
Смысл различения трёх состояний: ненулевой код выхода — семантический сигнал потомка (gcc сообщает об ошибке компиляции); убийство сигналом — аварийное прекращение (тайм-аут, крах) — обработка у них совершенно разная (первое — читать вывод, второе — диагностировать). Success — удобный вердикт «код 0 и нормальный выход»; полная тройка состояний — в полях структуры.
Конвейеризация
xrtProcessPipeline(阶段数组, ...) (массив стадий) порождает и сцепляет N стадий за один вызов — stdout→stdin между стадиями это настоящая труба ОС (данные не проходят через родителя — пропускная способность и изоляция вдвойне оптимальны); вывод последней стадии можно захватить по желанию. Контраст с ручной склейкой Spawn-ом: настройка наследования дескрипторов, момент закрытия обоих концов, двусторонний буферный deadlock — три классических капкана полностью забирает реализация (дословно из комментария образца pipeline).
Жизненный цикл захваченных потоков
Result.Stdout/Stderr — владеющие буферы — освобождаются xrtProcessResultUnit-ом (буферы включены); забыть Unit — утечка на каждый процесс. Правило комбинирования захвата с трубой: последняя стадия может захватывать (вывод промежуточных ушёл в трубу — родитель его не видит, и это именно смысл конвейера).
52.3示例
Полная программа: исполнение через Shell и захват вывода
Из образца репозитория examples/process/capture/main.c:
#include <stdio.h> #include <xrt.h> */ /* 执行命令、输出捕获内容,并把子进程退出状态映射为示例退出码。 */ int main(void) { xprocessresult Result; bool bOk; #if defined(_WIN32) || defined(_WIN64) bOk = xrtProcessShell("echo captured output", &Result); #else bOk = xrtProcessShell("printf 'captured output\n'", &Result); #endif if ( !bOk ) { const xerror* pError = xrtGetError(); fprintf( stderr, "process failed: %s\n", pError != NULL ? xrtErrorMessage(pError) : "unknown error" ); return 1; } fwrite(Result.Stdout, 1u, Result.StdoutSize, stdout); bOk = xrtProcessResultSuccess(&Result); xrtProcessResultUnit(&Result); return bOk ? 0 : 2; }
Что только что произошло. (1) xrtProcessShell("echo captured output", &Result) — вся команда идёт в системный shell; платформенные различия (echo у Windows против printf у POSIX) обработаны условной компиляцией образца, поведение в рантайме одинаково. (2) Result.Stdout несёт захваченный владеющий буфер — печатается напрямую для проверки; xrtProcessResultSuccess выносит вердикт «нормальный выход и код 0». (3) xrtProcessResultUnit освобождает результат — буфер захвата следует за результатом, и Unit — его единственный выход (забыли Unit — утечка по блоку за вызов). (4) Эта форма — кратчайший путь «внешняя программа как функция»: на входе командная строка, на выходе пара (буфер + статус).
Полная программа: многостадийный конвейер
Из examples/process/pipeline/main.c:
#include <stdio.h> #include <xrt.h> */ /* 用两个 Shell 阶段演示真实管道连接与末段捕获。 */ int main(void) { xprocesspipelineresult Result; xprocessconfig Stages[2]; bool bOk; #if defined(_WIN32) || defined(_WIN64) if ( !xrtProcessShellConfigInit(&Stages[0], "echo pipeline output") || !xrtProcessShellConfigInit(&Stages[1], "findstr pipeline") ) { return 1; } #else if ( !xrtProcessShellConfigInit(&Stages[0], "printf 'pipeline output\n'") || !xrtProcessShellConfigInit(&Stages[1], "tr a-z A-Z") ) { return 1; } #endif bOk = xrtProcessPipeline(Stages, 2u, NULL, &Result); if ( !bOk ) { const xerror* pError = xrtGetError(); fprintf( stderr, "pipeline failed: %s\n", pError != NULL ? xrtErrorMessage(pError) : "unknown error" ); return 2; } fwrite(Result.Stdout, 1u, Result.StdoutSize, stdout); bOk = xrtProcessPipelineSuccess(&Result); xrtProcessPipelineResultUnit(&Result); return bOk ? 0 : 3; }
Что только что произошло. (1) Массив конфигураций двух стадий (по xprocessconfig на стадию) одной рукой передаётся xrtProcessPipeline — порождение, склейка труб, запуск и сбор — всё его работа. (2) Труба между стадиями уровня ОС (ветка Windows берёт findstr, ветка POSIX — tr; каждая платформа выбирает эквивалентную утилиту); поток данных не проходит через родителя — пропускная способность не страдает от родительской пересылки. (3) Result.Stdout захватывает вывод последней стадии, xrtProcessPipelineSuccess выносит вердикт по всем стадиям — отказ любой стадии проваливает всю цепочку (семантика отказа конвейера: всё или ничего). (4) Контраст с ручной склейкой: наследование дескрипторов, двусторонние deadlock-и — капканы названы поимённо в комментариях образца; то, что их забрала реализация, и есть причина существования Pipeline. Образец file демонстрирует перенаправление вывода в файл (xrt-process-output.txt) — захват и перенаправление суть два пункта назначения обработки стандартных потоков.
52.4契约
- Выбор семейства: доверенная команда — Shell для экономии; недоверенный вход — обязательно Spawn+Argv — линия обороны от инъекций одного семейства с параметризацией SQL.
- Трёхзначная семантика: отказ запуска (ошибка родителя) / нормальный выход (код) / убийство сигналом (сигнал) — Success = код 0 и нормальный выход.
- Балансировка результата:
ResultUnitосвобождает захваченные буферы включённо — каждому Result свой Unit. - Лестница тайм-аута: конфигурация объявляет тайм-аут и убийство (TERM→льгота→KILL) — зависший потомок не утащит родителя.
- Семантика конвейера: между стадиями трубы ОС напрямую, данные минуют родителя; последняя стадия может захватывать; отказ любой стадии проваливает цепочку.
- Управление средой: рабочий каталог и переменные окружения объявлены в конфигурации — среда исполнения потомка это явный контракт, а не лотерея наследования.
От примеров к инженерии: три хозяина дочерних процессов
Клей инструментов (арена семейства Shell): транскодирование, сжатие, скрипты уведомлений — когда зрелые утилиты командной строки уже есть, вызвать их быстрее и надёжнее, чем переписывать библиотеку; командная строка — константа кода (доверенная), захват вывода, лестница тайм-аута — три настройки за один шаг.Граница безопасности (арена семейства Spawn): обработка файлов по запросу пользователя, исполнение плагинов — argv-изация исключает инъекции, рабочий каталог и окружение объявлены явно, ресурсы (тайм-аут + потолок вывода — поле StdoutTruncated сообщает, что обрезали) подконтрольны с трёх сторон.Потоки данных (арена Pipeline): шаги ETL, цепочки сборки — многостадийная сцепка вручается трубам ОС (пропускная способность выше родительской пересылки), вердикт по всем стадиям (семантика сборки). Общая нижняя черта трёх хозяев: потомок — код за границей — тайм-аут, потолок вывода и трёхзначный выход суть три вещи, о которых думают при каждом вызове; сэкономив на любом, оплатите в продакшене другим способом.
Полная бухгалтерия структуры результата
Поля xprocessresult богаче, чем «вывод + код выхода»; стоит проговорить поимённо. Status — трёхзначная основа (нормальный/сигнал/отказ); Wait — исход ожидания (тайм-аутные убийства всплывают здесь); InputWritten — байты, переданные в stdin потомка (запись в трубу бывает частичной); Stdout/Stderr — каждый из двух потоков несёт размер и отметку усечения (StdoutTruncated — взводится, когда потолок захвата задан и превышен: прочитано может быть не всё, перед вердиктом проверять обязательно); Duration — длительность в миллисекундах (мера главы 42 — готовое поле для наблюдения производительности). Читайте поля как бухгалтерию: каждое поле — окаменевшее однажды «здесь могло пойти не так» — отметка усечения бережёт от ошибочного вердикта, длительность — от слепых зон, разделение потоков — от смешанного чтения. Проектируйте собственные обёртки по этой полноте — плотность полей структуры результата суть окаменелость плотности ям, пройденных автором API.
Стык с томом 6: процесс тоже единица параллелизма
Открытие тома 6 этой главой — не совпадение порядка: процесс — древнейшая единица параллелизма. Контраст с потоком: процесс изолирует сильно (адресное пространство своё — крахи не заразны) и стартует дорого (цена fork/exec); поток делит память (связь без копий, но нужна синхронизация — глава 53) и стартует дёшево. Выбор определяется потребностью в изоляции: недоверенный/ломкий код (плагины, сторонние утилиты) — процесс; кооперативные вычисления — поток плюс параллельная система глав после этой. Точки контакта процесса с другими единицами параллелизма: межстадийная конкурентность конвейера (трубы ОС как каналы — процессная редакция Channel); тайм-аутное убийство встречает систему отмены (токен главы 54 с SIGTERM — форма «отменяемого ожидания» для внешних процессов); балансировка Unit у буфера захвата (та же дисциплина, что у деревьев значений и токенов).Процесс, поток и корутина — три гранулярности параллелизма — эта глава грубейшая, глава 55 тоньше всех, пол между ними — в следующей главе.
Типичные ошибки
Симптом: аудит безопасности репортит командную инъекцию — ввод ; rm -rf / или $(curl ...) исполняется после интерпретации.
Причина: семейство Shell отдаёт командную строку shell-у на растолкование — метасимволы (;, |, $, обратный апостроф) все поверхность атаки; пользовательские данные и синтаксис команды живут в одном слое.
char sCmd[512]; snprintf(sCmd, sizeof(sCmd), "convert %s -resize 100 %s", sUserInput, sOut); xrtProcessShell(sCmd, &Result); /* вход с ";" инъецирует произвольные команды */
/* argv объявляется в полях xprocessconfig; Spawn возвращает дескриптор, после ожидания берём результат */ xprocess* P = xrtProcessSpawn(&Config); /* аргументы передаются как есть - ноль интерпретации */
Симптом: долгоживущая служба медленно растёт — по блоку утечки на каждый внешний вызов; лестница активных байтов статистики главы 6 ползёт вверх.
Причина: Result.Stdout — владеющий буфер, ResultUnit — его единственный выход; путь использования Result не сбалансирован.
if ( xrtProcessShell(sCmd, &Result) && xrtProcessResultSuccess(&Result) ) { Use(Result.Stdout, Result.Size); /* использовали и бросили - буфер захвата течёт */ }
if ( xrtProcessShell(sCmd, &Result) ) { if ( xrtProcessResultSuccess(&Result) ) { Use(Result.Stdout, Result.Size); } xrtProcessResultUnit(&Result); /* освобождение и на пути успеха, и на пути отказа */ }
Практические задания
Постройте потомков с тремя исходами (нормальный код 0, нормальный ненулевой код, убит сигналом) — прогоните каждый с xrtProcessShell, печатая трёхзначные поля Result и вердикт Success.
run(命令, 超时秒数) (команда, тайм-аут в секундах): по тайм-ауту лестница TERM→KILL; захват вывода; возврат трёх состояний. Вызовите им команду sleep и проверьте поля сигнала на пути тайм-аута.
Реализуйте интерфейс транскодирования изображений на семействе Spawn: имена файлов и параметры полностью в argv (ноль склейки), захват вывода, лестница тайм-аута, потолок конкурентности (семафор или пул задач главы 22). Критерии приёмки: набор инъекционных тестов (двадцать злобных имён файлов) ничего не исполняет; процессы по тайм-ауту неизменно подчищаются (без зомби); ограничение конкурентности действует; результат транскодирования сходится с кодом выхода.
Итоги
| Тема | Кратко |
|---|---|
| Два семейства | Shell (командная строка + результат за шаг, доверенно) / Spawn (конфигурация → дескриптор, недоверенный вход) |
| Три состояния | отказ запуска / нормальный выход (код) / убийство сигналом (сигнал); Success = код 0 и нормальный выход |
| Балансировка результата | ResultUnit освобождает захваченные буферы - каждому Result свой Unit |
| Конвейер | Pipeline берёт массив стадий и сцепляет за один вызов; трубы ОС напрямую; последняя стадия может захватывать |
| Тайм-аут | конфигурация объявляет лестницу TERM→льгота→KILL - передающий конец напротив главы 49 |
| Среда | рабочий каталог/переменные окружения в конфигурации - среда исполнения это контракт, а не лотерея |
📖 Справочники API в этой главе:process (56 записей)signal (26 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки