Как сделать так, чтобы изменения в базовом файле после mmap() не отображались для моей программы?

Согласно справочной странице mmap():

КАРТА_ЧАСТНАЯ

Создайте частное сопоставление копирования при записи. Обновления сопоставления невидимы для других процессов, отображающих тот же файл, и не переносятся в базовый файл. Не указано, видны ли изменения, сделанные в файле после вызова mmap(), в отображаемой области.

Вопрос: как сделать, чтобы изменения в базовом файле после mmap() не были видны моей программе?


Предыстория. Я разрабатываю структуру данных для текстового редактора, позволяющего эффективно редактировать огромные текстовые файлы. Структура данных похожа на веревку на диске, но фактические строки указатель на mmap()-ed находится в диапазоне от исходного файла.

Поскольку файл может быть очень большим, есть несколько ограничений по дизайну:

  1. Не следует загружать весь файл в ОЗУ, так как файл может быть больше, чем доступная физическая ОЗУ.

  2. Не следует копировать файлы при открытии, так как это сделает открытие новых файлов очень медленным

  3. Должен работать с файловыми системами, такими как ext4, которые не поддерживают копирование при записи (cp --reflink/ioctl_ficlone)

  4. Не следует полагаться на обязательную блокировку файлов, поскольку она устарела и требует специальной опции монтирования -o mand в файловой системе.

  5. Пока изменения не видны в моем mmap(), базовый файл может измениться в файловой системе.

  6. Нужно только поддерживать последнюю версию Linux, и использование системных API для Linux допустимо.

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

Когда пользователь сохраняет файл, структура данных будет использовать copy_file_range соединить файл подкачки и исходный файл, чтобы собрать новый файл. Чтобы это соединение работало, исходный файл, видимый моей программе, должен оставаться неизменным на протяжении всего сеанса редактирования.

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

В такой ситуации редактор может обнаружить такое внешнее изменение с помощью inotify, а затем я хочу дать пользователю два варианта, как продолжить:

  1. отменить все несохраненные изменения и перечитать файл с диска, реализация этой опции довольно проста

  2. позволить пользователю продолжить редактирование файла, а позже пользователь сможет сохранить несохраненные изменения в новом месте или перезаписать изменения, внесенные другой программой, но реализация этого кажется сложной

Поскольку мой редактор не сделал копию файла при открытии файла, когда другая программа перезаписывает файл, диапазоны текста, которые отслеживает моя структура данных, могут стать недействительными, поскольку данные на диске изменились, и эти изменения теперь виден через мой mmap(). Это означает, что если мой редактор попытается записать несохраненные изменения после того, как файл был изменен другим процессом, он может объединять текстовые диапазоны в старом файле, используя данные из данных из нового файла, что может означать, что мой редактор может создавать поврежденный файл при сохранении несохраненных изменений.

Я не думаю, что рекомендательные блокировки спасут ситуацию во всех случаях, так как другие программы могут не соблюдать рекомендательные блокировки.

Моим идеальным решением было бы сделать так, чтобы, когда другие программы перезаписывают файл, система должна была прозрачно копировать файл, чтобы позволить моей программе продолжать видеть старую версию, в то время как другая программа заканчивает запись на диск и делает свою версию видимой в файловая система. Я думаю, что ioctl_ficlone мог бы сделать это возможным, но, насколько я понимаю, это работает только с файловой системой копирования при записи, такой как btrfs.

Возможно ли такое?


Любые другие предложения по решению этой проблемы также будут приветствоваться.


person Lie Ryan    schedule 07.06.2019    source источник
comment
Если нет возможности mmap() сделать это, я не думаю, что есть хорошее решение.   -  person Barmar    schedule 07.06.2019
comment
Это интересный вопрос, но я бы посоветовал не прыгать через обручи, пытаясь защитить пользователя от самого себя. Консультативных замков и предупреждений о модификации предостаточно. Если пользователь хочет уничтожить файл, Unix предлагает сотню более удобных способов, чем игнорировать блокировки и предупреждения в большом файловом редакторе.   -  person that other guy    schedule 07.06.2019


Ответы (1)


То, что вы хотите сделать, невозможно с mmap, и я не уверен, что это вообще возможно с вашими ограничениями.

Когда вы сопоставляете регион, ядро ​​может фактически загрузить или не загрузить его полностью в память. Область памяти, в которой отсутствуют данные, на самом деле будет содержать недопустимую страницу, поэтому при доступе к ней ядро ​​принимает ошибку страницы и отображает эту область в памяти. Этот регион, вероятно, будет содержать все, что находится в этой части файла в момент возникновения ошибки страницы. Существует вариант MAP_LOCKED, который пытается выполнить предварительную настройку всех страниц, но не гарантирует этого, поэтому вы не можете полагаться на его работу.

В общем, вы не можете запретить другим процессам изменять файл из-под вас. Некоторые инструменты (включая редакторы) запишут новый файл в сторону, вызвав rename для перезаписи файла, а некоторые перезапишут файл на месте. Первое — это то, что вам нужно, но многие редакторы предпочитают второе, поскольку оно сохраняет такие характеристики, как списки управления доступом и разрешения, которые вы не можете восстановить.

Кроме того, вы действительно не хотите использовать mmap для любого файла, который вы не можете полностью контролировать, потому что, если другой процесс обрезает файл, и вы попытаетесь получить доступ к этой части буфера, ваш процесс умрет с SIGBUS. Поймать этот сигнал — поведение неопределенное, и единственно разумный выход — умереть. (Кроме того, он может быть отправлен в других ситуациях, таких как невыровненный доступ, и вам будет трудно различить их.)

В конечном счете, если вы не заинтересованы в копировании файла, вы не можете гарантировать, что кто-то не изменится под вами, и вам нужно быть готовым к тому, что это произойдет.

person bk2204    schedule 07.06.2019