Как выбрать фиксированный адрес для сопоставления общей памяти

Я хотел бы использовать разделяемую память между несколькими процессами и хотел бы иметь возможность использовать raw-указатели (и stl-контейнеры).

Для этой цели я использую общую память, отображаемую по фиксированному адресу:

segment = new boost::interprocess::managed_shared_memory(
    boost::interprocess::open_or_create,
    "MySegmentName",
    1048576, // alloc size
    (void *)0x400000000LL // fixed address
);

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

Ответ 1

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

Если вы этого не сделаете, то первое, на что стоит обратить внимание, - это действительно нужно использовать необработанные контейнеры STL вместо контейнеров для промежуточных процессов. То, что вы уже используете boost interprocess для выделения сегмента разделяемой памяти, предполагает, что у вас нет проблем с использованием boost, поэтому единственным преимуществом, которое я могу придумать для использования STL-контейнеров, является то, что вам не нужно будет переносить существующий код. Имейте в виду, что для работы с фиксированными адресами контейнеры и то, что они содержат указатели (при условии, что вы работаете с контейнерами указателей), должны храниться в общем пространстве памяти.

Если вы уверены, что это то, что вам нужно, вам придется выяснить какой-то метод для их согласования. Имейте в виду, что ОС может отклонить желаемый адрес фиксированной памяти. Он отклонит адрес, если страница на этом адресе уже была отображена в память или выделена. Поскольку разные программы будут распределять разные объемы памяти в разное время, какие страницы доступны и недоступны, они будут отличаться в разных программах.

Таким образом, вам необходимо, чтобы программы получали консенсус по адресу памяти. Это означает, что несколько адресов, возможно, придется попробовать и отклонить. Если возможно, что когда-нибудь после запуска будет интересна новая программа, поиск консенсуса должен будет начаться снова. Алгоритм будет выглядеть примерно так:

  • Программа A предлагает адрес X памяти для всех других программ.
  • Другие программы отвечают с помощью true или false, чтобы указать, удалось ли сопоставить память по адресу X.
  • Если программа A получает любые ложные ответы, перейдите к # 1.
  • Программа A отправляет сообщение другим программам, позволяющим им знать, что адрес был проверен и, возможно, использован.
  • Если новое приложение заинтересовано в данных, оно должно уведомить программу A, что ей нужен адрес.
  • Затем программа A должна сказать всем остальным программам прекратить использование данных и перейти к # 1.

Чтобы найти то, что должны предложить A, вы могли бы отобразить на карте нефиксированный сегмент памяти, посмотреть, на какой адрес он отображается, и предложить этот адрес. Если это неудовлетворительно, сопоставьте другой сегмент и предложите его. Вам нужно будет отформатировать сегменты в какой-то момент, но вы не можете их сразу распаковать, потому что, если вы отмените переназначение сегмента с одинаковым размером, OS будет возвращать вам тот же адрес снова и снова. Имейте в виду, что вы никогда не достигнете консенсуса; нет никакой гарантии, что там будет достаточно большой сегмент в общем месте по всем процессам. Это может произойти, если ваши программы независимо используют почти всю память, скажем, если они подкреплены тонной суммой (хотя, если вы достаточно заботитесь о производительности, чтобы использовать общую память, вы избегаете обмена).

Все вышесказанное предполагает, что вы находитесь в относительно ограниченном адресном пространстве. Если вы на 64-битном, это может работать. Большинство обменов RAM + swap будет намного меньше, чем то, что разрешено 64-битными, поэтому вы можете поместить карту памяти на очень далекий фиксированный адрес, который все процессы вряд ли будут отображаться уже. Я предлагаю хотя бы 2 ^ 48, так как текущие 64-разрядные процессоры x86 не каждый за пределами этого диапазона (несмотря на то, что указатели являются 64-битными, вы можете подключать столько же RAM, сколько разрешено 48 бит, время написания). Хотя нет никакой причины, чтобы интеллектуальный распределитель кучи не мог воспользоваться обширностью адресного пространства, чтобы уменьшить свою бухгалтерскую работу, поэтому, чтобы быть по-настоящему надежным, вам все равно нужно будет создавать консенсус. Имейте в виду, что вы, по крайней мере, хотите, чтобы адрес настраивался - даже если у нас не так много памяти в ближайшее время, между вами и сейчас кто-то может иметь ту же идею и выбрать ваш адрес.

Для двунаправленной связи вы можете использовать любой из сокетов, труб или другого сегмента разделяемой памяти. Ваша ОС может предоставлять другие формы IPC. Но решительно считайте, что вы, вероятно, сейчас вводите больше сложностей, чем вам пришлось бы иметь дело, если бы вы просто использовали контейнеры для межпроцессного форсирования;)

Ответ 2

Прочитайте адрес из файла конфигурации. Это позволит легко экспериментировать и упростить изменение адреса при изменении обстоятельств.

Ответ 3

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

Вы выбрали http://reversingonwindows.blogspot.sg/2013/12/hardcoded-pointers.html как пример того, как сделать программное обеспечение менее безопасным, минуя ASLR. 2-й неудачный пример находится в boost library.

Адресное пространство должно быть согласовано между общими сторонами во время выполнения.

Ответ 4

Мое решение:

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

Предостережения: Я использую 64-bit fedora 21 с Kdevelop 4.7 и обнаруживаю, что "void *" имеет длину 64 бит. Запись на диск адреса главы сегмента включает sprintf (bu, "% p", указатель); и написание текстового файла:

Recovery считывает этот файл и декодирует шестнадцатеричный номер как "длинное длинное" значение. Это возвращается вызывающему абоненту, где он отображается как (void *)

Я также обнаружил, что группировка всех процедур доступа в одну папку выше уровня отдельных процессов (каждый как самостоятельный проект) помог сохранить мое рассудок за счет одного аберрантного "#include" в файлах процессов

Дэвид Нлэйн