Дамп глобальных данных на диск в ассемблерном коде

Эксперимент проводится на Linux, x86 32-bit.

Итак, предположим, что в моей программе сборки мне нужно периодически (например, каждый раз после выполнения 100000 базовых блоков) сбрасывать массив в секции .bss из памяти на диск. Начальный адрес и размер массива фиксированы. Массив записывает адрес исполняемого базового блока, сейчас его размер составляет 16M.

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

Итак, вот мой вопрос, как я могу эффективно сбрасывать память из разделов глобальных данных? Я достаточно ясен?


person lllllllllllll    schedule 16.07.2015    source источник
comment
Почему бы не вызвать _fwrite и не указать ему данные в вашем .BSS с помощью указателя в качестве вызывающего аргумента в стеке?   -  person David Hoelzer    schedule 16.07.2015
comment
Почему ассемблерный код? Похоже на контрольную точку приложения. Рассмотрите возможность использования BLCR.   -  person Basile Starynkevitch    schedule 16.07.2015
comment
Копировать в стек нет смысла. Вы можете сделать write(2) системных вызовов для чего угодно. Копирование себя не будет быстрее, чем позволить ядру сделать это во время write(2). На самом деле медленнее, поскольку ядру все еще приходится копировать ваши данные для write(2), если только вы не используете ввод-вывод с нулевым копированием для копии.   -  person Peter Cordes    schedule 18.07.2015


Ответы (2)


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

Очевидно, что вы можете написать что угодно на ассемблере, поскольку выполнение системных вызовов не является чем-то особенным по сравнению с C. MFENCE вокруг замка.

Во всяком случае, я рассмотрел три варианта того, что именно вы хотите сделать с вашим буфером:

  1. Перезаписать тот же буфер на место (mmap(2)/msync(2))
  2. Добавить снимок буфера в файл (либо с идеей write(2), либо с вероятно неработающей нулевой копией vmsplice(2) + splice(2)).
  3. Запустить новый (обнуленный) буфер после записи старого. mmap(2) последовательных фрагментов выходного файла.

На месте перезаписывает

Если вы просто хотите каждый раз перезаписывать одну и ту же область диска, mmap(2) файл и используйте его в качестве массива. (Периодически вызывайте msync(2), чтобы принудительно записать данные на диск.) Однако метод mmapped не гарантирует согласованного состояния файла. Записи могут быть сброшены на диск иначе, чем по запросу. IDK, если есть способ избежать этого с какой-либо гарантией (т. е. не просто выбирать таймеры сброса буфера и т. д., чтобы ваши страницы обычно не записывались, кроме как msync(2).)

Добавить снимки

Самый простой способ добавить буфер в файл — это просто вызвать write(2), когда вы хотите, чтобы он был записан. write(2) делает все, что вам нужно. Если ваша программа является многопоточной, вам может потребоваться заблокировать данные перед системным вызовом, а затем снять блокировку. Я не уверен, как быстро вернется системный вызов записи. Он может вернуться только после того, как ядро ​​скопирует ваши данные в кеш страницы.

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

IDK, если write(2) возвращается медленнее или быстрее при прямом вводе-выводе (нулевое копирование, обход кэша страниц). open(2) ваш файл с O_DIRECT, write(2) нормально.

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

Добавление моментальных снимков с нулевым копированием

Существует API для записи без копирования пользовательских страниц в файлы на диске. Linux vmsplice(2) и splice(2) в таком порядке позволит вам указать ядру отображать ваши страницы в кеш страниц. Я предполагаю, что без SPLICE_F_GIFT они настраиваются как копирование при записи. (упс, на самом деле справочная страница говорит, что без SPLICE_F_GIFT придется копировать следующие splice(2). Итак, IDK, если есть механизм для получения семантики копирования при записи.)

Предполагая, что существует способ получить семантику копирования при записи для ваших страниц, пока ядро ​​не закончит запись их на диск и не сможет их освободить:

Для дальнейших операций записи может потребоваться, чтобы ядро ​​копировало в память одну или две страницы до того, как данные попадут на диск, но не копируйте весь буфер целиком. Программные ошибки страниц и накладные расходы на манипуляции с таблицами страниц в любом случае могут не стоить того, если ваш шаблон доступа к данным не очень пространственно локализован в течение коротких периодов времени, пока запись не попадет на диск и не будут освобождены страницы для записи. (Я думаю, что API, который работает таким образом, не существует, потому что нет механизма для выпуска страниц сразу после их попадания на диск. Linux хочет забрать их и сохранить в кэше страниц.)

Я никогда не использовал vmsplice, поэтому могу ошибаться в некоторых деталях.

Если есть способ создать новое сопоставление копирования при записи для той же памяти, возможно, путем mmap создания нового сопоставления временного файла (в файловой системе tmpfs, вероятно, /dev/shm), это даст вам моментальные снимки, не удерживая блокировку в течение длинная. Затем вы можете просто передать моментальный снимок в write(2) и отменить сопоставление как можно скорее, прежде чем произойдет слишком много ошибок копирования при записи страниц.

Новый буфер для каждого чанка

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

  • (необязательно) fallocate(2) немного места в выходном файле, чтобы предотвратить фрагментацию, если ваш шаблон записи не является последовательным.
  • mmap(2) вашего буфера до первых 16 МБ вашего выходного файла.
  • работать нормально
  • When you want to move on to the next 16MiB:
    1. take a lock to prevent other threads from using the buffer
    2. munmap(2) ваш буфер
    3. mmap(2) следующие 16 МиБ файла по тому же адресу, поэтому вам не нужно передавать новый адрес авторам. Эти страницы будут предварительно обнулены, как того требует POSIX (ядро не может открывать доступ к памяти).
    4. открыть замок

Возможно, mmap(buf, 16MiB, ... MAP_FIXED, fd, new_offset) мог бы заменить пару munmap/mmap. MAP_FIXED отбрасывает старые mmapings, которые перекрываются. Я предполагаю, что это не означает, что изменения в файле/разделяемой памяти отбрасываются, а скорее фактические изменения отображения, даже без munmap.

person Peter Cordes    schedule 18.07.2015
comment
Спасибо, Питер! Я очень ценю ваш ответ!! - person lllllllllllll; 23.07.2015

Два пояснения к случаю Добавить снимки из ответа Питера.

<сильный>1. Добавление без O_DIRECT

Как сказал Питер, если вы не используете O_DIRECT, write() вернется, как только данные будут скопированы в кеш страницы. Если кеш страниц заполнен, он будет блокироваться до тех пор, пока какая-либо устаревшая страница не будет сброшена на диск.

Если вы только добавляете данные, не читая их (скоро), вы можете периодически вызывать sync_file_range(2) для планирования очистки ранее написанных страниц и posix_fadvise(2)< /a> с флагом POSIX_FADV_DONTNEED для удаления уже очищенных страниц из кэша страниц. Это может значительно снизить вероятность блокировки write().

<сильный>2. Добавление с помощью O_DIRECT

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

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

person gavv    schedule 23.07.2015