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

Средняя глава о файлах разбирает три высшие способности. Отображение в память (xrtFileMap): файл проецируется в адресное пространство, и указатель есть содержимое файла — произвольный доступ к большим файлам без копирований и системных вызовов. Файловые блокировки (xrtFileLock на весь файл + xrtFileLockRange на диапазон): режимы совместный/монопольный, согласующие чтение и запись нескольких процессов в один файл — две ступени зернистости, от «взаимоисключения на весь файл» до «тонкого зерна байтовых диапазонов». Атомарная доставка (xrtFileTemp + переименование): временный файл записан + атомарное переименование — читатель всегда видит целый файл, состояние «наполовину записано» исчезает структурно. Каждая из трёх решает одну настоящую проблему параллельности или производительности.

46.1引入

Три сцены. Сцена первая: файлу индексов в 2 ГБ нужен произвольный доступ — цикл Read/Seek делает каждое чтение системным вызовом (ядро копирует в пользовательский буфер), и на горячем пути с миллионом обращений расходы на syscall становятся главной статьёй; отобразив файл в память, вы обращаетесь обычным разыменованием указателя — ядро подгружает страницы лениво, ноль копий. Сцена вторая: два процесса обновляют один файл данных — без согласования они перезаписывают друг друга; блокировка на весь файл слишком груба (в сценариях «много чтения, мало записи» читатели полностью сериализуются), а диапазонные блокировки дают читателям совместный доступ, писателю — монопольный, и непересекающиеся диапазоны работают параллельно. Сцена третья: во время обновления файла конфигурации читатель как раз читает — он видит наполовину записанное, и разбор конфигурации рушится; запись во временный файл с последующим атомарным переименованием даёт читателю либо старый, либо новый файл — промежуточное состояние не увидит никто.

46.2概念

Отображение в память: указатель есть файл (арена произвольного доступа)

1УстановитьxrtFileMap(дескриптор, смещение, длина, флаги) - длина 0 = отобразить до конца файла; Data/Size представления и есть поверхность доступа
2Доступразыменование указателя читает файл; при флаге записи запись синхронизируется в файл
3Жизненный циклUnmap возвращает отображение; момент сброса записи на диск определяют ядро и flush
4Применимостьбольшие файлы с произвольным доступом (индексы/базы/ресурсы только для чтения) - последовательным потокам лучше Read

Выгода отображения расщепляется по режиму доступа: произвольный доступ — каждое обращение это страничный сбой (первый раз) или чтение памяти (дальше); после оседания горячей зоны скорость приближается к памяти; последовательный поток — у отображения нет преимущества предзагрузки, поблочный Read проще и управляемее. Так что «отображение быстрее» — полуправда: быстро для произвольного, без преимущества для последовательного; выбор начинается с режима доступа (продолжение трёх хозяев главы 45).

Диапазонные блокировки: согласование с зерном до байта

Две ступени зернистости: xrtFileLock(句柄, 共享/独占, 是否等待) (дескриптор, совместный/монопольный, ждать ли) — блокировка на весь файл (стандартная поза стража единственного экземпляра и взаимоисключения очередей задач); xrtFileLockRange(句柄, 模式, 偏移, 长度, 是否等待) (дескриптор, режим, смещение, длина, ждать ли) — блокировка байтового диапазона (согласование на уровне записей); Unlock/UnlockRange освобождают зеркально, и параметры снятия диапазонной блокировки обязаны в точности совпадать с параметрами установки. Выбор между ступенями смотрит на форму данных: целофайловая блокировка подходит взаимоисключению «файл как единица» (классическое употребление стража единственного экземпляра: держится, пока процесс жив, и освобождается при выходе); диапазонная — параллелизму на уровне записей (читатель берёт совместную блокировку на диапазон A, писатель — монопольную на диапазон B; без пересечения работа идёт параллельно). Дисциплина семантики блокировок: блокировка — протокол согласования, а не магия — смысл она имеет, только когда все стороны доступа соблюдают один протокол (процесс, пишущий без блокировки, не защищён ничьей блокировкой); диапазон блокировки выравнивается с границами структуры данных (блокировки на уровне записей — по диапазонам записей).

Атомарная доставка: временный файл + атомарное переименование (уничтожитель промежуточных состояний)

Промышленная полная форма «трёхшагового завершения» главы 45: (1) запись во временный файл (в том же каталоге — переименование через файловые системы провалится); (2) Flush-семантика гарантирует попадание данных на диск; (3) атомарное переименование в окончательное имя — rename внутри одной файловой системы атомарен, и читатель в любой момент видит целый файл. Помимо образца lock, образцы temp и dir_temp демонстрируют согласование именования и зачистки временных файлов (xrtFileTemp создаёт временный файл с уникальным именем (каталог/префикс параметризуются), по завершении он удаляется или переименовывается).

Комбинации тройки (смешивать по сценарию)

Отображение плюс блокировки: пишущая сторона отображения согласуется диапазонными блокировками — каждый писатель блокирует свой диапазон записей и пишет через отображение. Атомарная доставка плюс блокировки: часто обновляемые файлы используют «версионный файл + атомарное переключение» (записали временный v2, rename на главное имя), не держа блокировку долго. Тройка не предписывает обязательных сочетаний — берите по сценариям параллельности, и именно поэтому каждая из них живёт в собственном разделе.

46.3示例

Полная программа: чтение через отображение

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

examples/file/map/main.c源码 ↗
#include <stdio.h>

#include <xrt.h>





















 */


/* 展示使用只读映射检查文件内容。 */
int main(void)
{
	static const char sPath[] = "xrt-file-map-example.tmp";
	xfile File;
	xfilemap Map;
	int iResult = 1;

	File = xrtOpen(sPath, XFILE_READ | XFILE_WRITE |
		XFILE_CREATE | XFILE_TRUNCATE);
	if ( (File == NULL) ||
		 !xrtWriteFull(File, "mapped", 6u, NULL) ) {
		(void)xrtClose(File);
		return 1;
	}
	Map = xrtFileMap(File, 0u, 0u, XFILE_MAP_READ);
	if ( Map != NULL ) {
		printf("%.*s\n", (int)xrtFileMapSize(Map),
			(const char*)xrtFileMapData(Map));
		if ( xrtFileUnmap(Map) && xrtClose(File) &&
			 xrtFileDelete(sPath) ) {
			iResult = 0;
		}
	}
	return iResult;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/file/map/main.c -lws2_32 -liphlpapimapped

Что только что произошло. (1) Файл сперва записывается, затем отображается — параметры (file, offset=0, size=0, READ) с size 0 означают «отобразить до конца файла»; отображение не создаёт файл, оно отображает текущие байты файла. (2) После установления отображения содержимое читается через указатель и выводится mapped — это чтение обошлось без вызова Read, чистый доступ к памяти. (3) xrtFileMapData/xrtFileMapSize дают указатель и длину представления, xrtFileUnmap снимает отображение — дисциплина порядка: сперва Unmap, потом Close (порядок не обратим). Дополнительный дивиденд отображения только для чтения: несколько процессов делят одни и те же физические страницы — способ загрузки словарей/индексов/ресурсных файлов. Записи отображения с флагом записи синхронизируются в файл — грязные страницы ядро сбрасывает по своему выбору; явная потребность в долговечности идёт с flush-семантикой.

Полная программа: файловые блокировки

Из examples/file/lock/main.c:

examples/file/lock/main.c源码 ↗
#include <stdio.h>

#include <xrt.h>




















 */


/* 展示对整个文件使用非阻塞排他锁。 */
int main(void)
{
	static const char sPath[] = "xrt-file-lock-example.tmp";
	xfile File = xrtOpen(sPath, XFILE_READ | XFILE_WRITE |
		XFILE_CREATE);

	if ( File == NULL ) {
		return 1;
	}
	if ( !xrtFileLock(File, XFILE_LOCK_EXCLUSIVE, false) ) {
		(void)xrtClose(File);
		return 1;
	}
	printf("locked\n");
	if ( !xrtFileUnlock(File) || !xrtClose(File) ) {
		return 1;
	}
	return xrtFileDelete(sPath) ? 0 : 1;
}
$ gcc -O1 -DXRT_MODULE_ALL -I single impl.c examples/file/lock/main.c -lws2_32 -liphlpapilocked

Что только что произошло. (1) На целевой диапазон берётся монопольная блокировка — при успехе печатается locked; пока она держится, запросы других процессов на тот же диапазон будут ждать или отказывать (по флагу). (2) Образец — однопроцессная демонстрация формы API; настоящее межпроцессное согласование требует, чтобы все участники ходили через протокол блокировок, — практическая сторона самого понятия «протокол согласования». (3) Unlock освобождает — жизненный цикл блокировки можно повесить и на закрытие дескриптора (автоосвобождение при выходе процесса), но явный Unlock — дисциплинарное написание. Образцы link и report оставлены продвинутым чтением: операции с симлинками и отчёт о состоянии файла (размер/времена/атрибуты) принадлежат семейству файловых метаданных и дополняют тройку этой главы.

46.4契约

  • Дисциплина отображения: сперва установить, потом пользоваться; сперва Unmap, потом Close; произвольный доступ — его арена, последовательный поток — Read.
  • Протокол блокировок: защита существует, только когда все стороны соблюдают один протокол; диапазоны блокировок выровнены с границами данных; явный Unlock.
  • Атомарная доставка: временный файл в том же каталоге, после записи — на диск, rename переключает атомарно; состояние «наполовину записано» исчезает структурно.
  • Временные файлы: уникальное имя (xrtFileTemp); на пути успеха — переименование, на пути отказа — удаление; ни один финал не оставляет мусора.
  • Свобода комбинаций: тройка сочетается по сценариям параллельности; обязательных пар нет.

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

Загрузка индексов и ресурсов (арена отображения): словари, большие файлы конфигурации только для чтения, индексы баз данных — отображение на старте, доступ только для чтения на всём пути, Unmap на выходе; деля несколькими процессами одни физические страницы, «память — по копии на процесс» превращается в «представление — по одному на процесс».Межпроцессное согласование (арена блокировок): страж единственного экземпляра — целофайловой блокировкой (обслуживает только тот, кто её взял; второй экземпляр выходит); файлы очередей задач и книг учёта обновляются на уровне записей диапазонными блокировками — протокол блокировок фиксируется в «инструкции по применению» файла (какие диапазоны полей, какой режим, потолок длительности удержания).Доставка конфигурации и состояния (арена атомарной доставки): все файлы, которые «читатель может прочесть в любой момент», — конфигурации, манифесты, контрольные точки. Хозяева и сочетаются: файл книги учёта = диапазонные блокировки (обновление записей) + атомарная доставка (ротация целиком).

Отказоустойчивость: слепое пятно атомарной доставки

Атомарная доставка обещает «читатель не видит промежуточных состояний», но одно слепое пятно надо признать: успешный rename не означает, что данные уже на диске — rename атомарно переключает запись каталога, а блоки данных могут ещё сидеть в страничном кеше; обрыв питания в этот момент — и каталог указывает на новое имя с неполными данными. Для сценариев с высокими требованиями к отказоустойчивости (книги учёта, контрольные точки) полная цепь — «временный файл → семантика fsync (данные на диск) → rename → fsync каталога (современные файловые системы)»: файловый слой XRT предоставляет соответствующие возможности на flush-семантике, а fsync каталога после rename — деталь хостовой файловой системы (в большинстве сцен опускается, в сценариях уровня книги учёта добавляется). Запишите эту цепь в свой «список долговечности»: промежуточные состояния уничтожает rename, окно обрыва питания уничтожает fsync — это два гаранта на разных слоях, и без любого остаётся окно.

Напоминание о выборе: когда блокировки не нужны

Файловые блокировки — не единственный способ согласования параллельности; перед выбором задайте три вопроса. Несколько процессов на одной машине? Да → файловые блокировки уместны; между машинами → блокировки бессильны (семантика блокировок на сетевых файловых системах ненадёжна), берите протокол прикладного уровня. Зернистость доступа? Уровень файла → целофайловая блокировка (проще всего); уровень записи → диапазонная; ещё тоньше → рассмотрите модель единственного писателя (один процесс единолично ведает файлом, остальные ходят через него — сообщения очередью главы 21), обходя блокировки. Частота конфликтов? Редкие обновления → атомарная доставка напрямую заменяет блокировки (переключение версий без блокировок); горячие конфликты → ожидание на блокировках становится заметно, рассмотрите общую память + единственного писателя. Блокировка — один из последних доводов согласования, а не первая реакция; тот же корень, что и встречный вопрос главы 23 «сперва блокировка — и порядок».

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

Ловушка 1: отображение вместо последовательного чтения:Ловушка 1: отображение вместо последовательного чтения

Симптом: большой файл сканируется через отображение байт за байтом — производительность не выросла, а то и упала (накладные расходы страничных сбоев + отсутствие предзагрузки), код стал сложнее.

Причина: модель выгоды отображения — «произвольный доступ с оседанием горячей зоны»; у последовательного потока его страничные сбои не дают никакого преимущества перед поблочным Read.

错误写法
xfilemap Map = xrtFileMap(File, 0, Size, XFILE_MAP_READ);
for ( uint64 i = 0; i < Size; i++ ) {
	Consume(Map.Data[i]);   /* последовательный скан 2 ГБ - страничный сбой на каждую страницу, без предзагрузки */
}
正确写法
/* последовательный поток: вернуться к поблочному Read (потоковый хозяин главы 45) */
char Buffer[65536];
uint64 Done = 0;
while ( Done < Size ) {
	uint64 Chunk = Min(sizeof(Buffer), Size - Done);
	xrtReadFull(File, Buffer, Chunk);
	Consume(Buffer, Chunk);
	Done += Chunk;
}
Ловушка 2: вы блокируете, а остальные нет:Ловушка 2: вы блокируете, а остальные нет

Симптом: при блокировках всё равно гонки — испорченные записи, чтение наполовину старого наполовину нового; эпизодично и плохо воспроизводится.

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

错误写法
/* процесс A (законопослушный): сперва блокировка, потом запись */
xrtFileLockRange(File, XFILE_LOCK_EXCLUSIVE, Off, Len, true);
WriteRecord(File, Off, Data);
xrtFileUnlockRange(File, Off, Len);

/* процесс B (старый код, без протокола): пишет напрямую */
WriteRecord(File, Off, OtherData);   /* блокировка A не сковывает незаблокированного B - гонка осталась */
正确写法
/* все стороны доступа идут по одному протоколу - B исправлен в согласии с A */
xrtFileLockRange(File, XFILE_LOCK_EXCLUSIVE, Off, Len, true);
WriteRecord(File, Off, OtherData);
xrtFileUnlockRange(File, Off, Len);
/* протокол фиксируется в документации модуля: любая запись в этот файл - только под блокировкой */

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

Базовое: отображение против чтения (согласованность двух путей доступа)

Прочитайте один и тот же отрезок через отображение и через ReadFull — байты совпадают; затем измените через отображение (с флагом записи) и прочитайте назад, проверив синхронизацию.

Продвинутое: эксперимент с блокировкой на два процесса

Напишите две программы — «держатель блокировки» и «ждущий»: первый берёт блокировку и спит несколько секунд, второй пытается заблокировать и репортит ожидание/успех — прочувствуйте семантику согласования (ждущий пусть попробует и совместную, и монопольную).

Вызов: атомарный обновлятор конфигурации (проверка нуля промежуточных состояний при параллельных читателях)

Реализуйте save_config(路径, 内容) (путь, содержимое): запись в одноимённый временный файл уникального имени в том же каталоге → сброс на диск → атомарное переименование; параллельные читатели в цикле читают и разбирают конфигурацию, проверяя, что в любой момент читается только целое легальное содержимое. Критерии приёмки: за 100 обновлений у читателей ноль сбоев разбора; после инъекции отказа (убить процесс до переименования) исходный файл цел; временных файлов не осталось.

Итоги

ТемаКратко
ОтображениеxrtFileMap — указатель есть файл; арена произвольного доступа, последовательному потоку преимущества нет; Unmap прежде Close
Две ступени блокировокLock на весь файл (взаимоисключение единственного экземпляра) / LockRange на диапазон (параллелизм на уровне записей); протокол связывает все стороны
Атомарная доставкавременный файл в том же каталоге (уникальное имя) → сброс на диск → атомарное переименование; промежуточные состояния исчезают структурно
Временные файлыxrtFileTemp(目录, 前缀, ...) (каталог, префикс) уникальное имя; успех — переименование, отказ — удаление - два финала, ноль мусора
Комбинацииотображение + блокировки (согласование пишущих) / атомарное переключение (без долгих блокировок) - брать по сценарию
Семейство метаданныхобразцы link/report: симлинки и отчёт о состоянии файла - попутчики тройки
Три хозяиназагрузка индексов и ресурсов (отображение) / межпроцессное согласование (блокировки) / доставка читаемых в любой момент файлов (атомарное переименование)
Слепое пятно отказовпромежуточные состояния уничтожает rename, окно обрыва питания - fsync - гаранты двух слоёв; без любого остаётся окно
Границы блокировокмежду машинами бессильны, при редких обновлениях заменимы атомарной доставкой, при горячих конфликтах — единственный писатель - последний довод, не первая реакция

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

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