Отображение файла в память (File mapping ) и разделяемая в DLL память.

Тема в разделе "WASM.WIN32", создана пользователем Andrey_59, 8 янв 2024.

  1. vitokop

    vitokop Member

    Публикаций:
    0
    Регистрация:
    20 май 2006
    Сообщения:
    48
    как установлено у секты мелкомягких ОСНОВОЙ является файл и его ИМЯ ( A или W ) - NameFileMapping
    как-то так:
    надо знать ИМЯ файла Mapping в память и задать ЕГО один лишь раз при создании
    остальные попытки при создании будут возвращать УЖЕ СУЩЕСТВУЕТ
    ОДИН РАЗ В ГОД САДЫ ЦВЕТУТ....

    CrateFileMapping ((HANDLE) -1, NULL, PAGE_READWRITE,
    0, 1 + iLength, NameFileMapping) ; <<<<==== NameFileMapping

    Любой процесс (32 or 64) может обращаться к FileMapping по ИМЕНИ, которое он должен знать

    MyHandle = OpenFileMapping(FILE_MAP_WRITE, False, NameMapFile) и получать СВОЙ Handle для
    дальнейшей работы
    pMem = MapViewOfFile(MyHandle ,FILE_MAP_WRITE, 0,0,0); //<<<<===== указатель на память файла

    А уж там воротите, что хотите...
     
  2. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Может быть, при открытии копии, когда iTotal уже не нуль, но во второй копии ещё не было добавлено ни одного элемента и, соответственно, при перемещении возникает исключение т.к. предыдущее значение, которого нет пытается быть перемещённым на текущее значение, которого нет.

    Фуу.., вроде бы стало доходить.) Если в расшаренной области я объявляю массив содержащий какие-то данные, то второй процесс видит лишь адрес указывающий на начало этого массива, а что там сохранено и какой смысл имеют сохранённые в массиве данные, это для второго процесса "филькина грамота" и интерпретировать это должен сам разработчик, в данном случае, вызовом функций OpenFileMapping и MapViewOfFile.

    Не знаю, как по мне, не всё так ясно с ними, с мьютексами. Для работы с мьютексом предусмотрено всего четыре функции что ли, CreateMutex/CreateMutexEx, OpenMutex и ReleaseMutex?! Ну тут можно поразбираться, но желательно на примерах, а так голая теория она мне ничего не даст, уже пробовал.
     

    Вложения:

    • FileMapping.jpg
      FileMapping.jpg
      Размер файла:
      48,7 КБ
      Просмотров:
      111
  3. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Верно, именно так оно и работает!


    Итак, мьютекс - это тоже объект ядра, предназначенный для синхронизации.
    Он может быть только в двух состояниях: занят (блокировка кем-то захвачена) и освобождён (блокировка снята).
    Если мьютекс захвачен (ещё говорят «залочен»), все, кто попытаются его захватить, будут ждать, пока владелец блокировки (тот, кто её захватил), не снимет её.
    Как только владелец снимает блокировку, один из потоков, который ждал, просыпается и захватывает мьютекс, становясь новым владельцем блокировки. Если не ждал никто, то мьютекс остаётся незанятым, и первый, кто попытается его занять, займёт его сразу, без ожидания.

    Чтобы разделять мьютекс между процессами, мы можем при создании задать ему имя, и другие процессы, зная это имя, смогут тоже его открыть.
    CreateMutex создаёт мьютекс, если мьютекса с таким именем ещё нет в системе, или открывает его, если он уже создан.
    OpenMutex только открывает, если мьютекс с таким именем уже есть, но не пытается создать его, если он не существует.
    На выходе CreateMutex и OpenMutex возвращают хэндл - валидный, как мы помним, только в контексте процесса, в котором мы его открыли/создали. То есть, если мы хотим юзать общий мьютекс, в каждом процессе вызываем CreateMutex с одинаковым именем: первый CreateMutex в первом процессе его создаст, все последующие CreateMutex’ы в других процессах откроют уже созданный.
    Таким образом, в каждом процессе будут разные хэндлы мьютекса, ведущие на один и тот же объект ядра.

    Чтобы залочить мьютекс, используем функцию WaitForSingleObject(hMutex, INFINITE).
    Если мьютекс никем не занят, она его займёт и сразу вернёт управление.
    Если мьютекс уже кем-то занят (кто-то вызвал WaitForSingleObject раньше и ещё не сделал ReleaseMutex) - WaitForSingleObject будет ждать, пока мьютекс не освободится, и только после этого займёт его (сделает вызвавший поток владельцем блокировки) и вернёт управление.

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

    Поработал - надо снять блокировку. Для этого делаешь ReleaseMutex.

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

    Проще говоря, схема выглядит так:

    Процесс1:
    hMutex = CreateMutex("SampleMutex");
    WaitForSingleObject(hMutex, INFINITE);
    // Работаем с общими данными
    ReleaseMutex(hMutex);
    CloseHandle(hMutex);

    Процесс2:
    hMutex = CreateMutex("SampleMutex");
    WaitForSingleObject(hMutex, INFINITE);
    // Работаем с общими данными
    ReleaseMutex(hMutex);
    WaitForSingleObject(hMutex, INFINITE);
    // Снова работаем с общими данными
    ReleaseMutex(hMutex);
    CloseHandle(hMutex);

    И убедись, что нигде не обращаешься к общим данным без мьютекса, напрямую - это приведёт к гонке потоков, и кто-то скорей всего упадёт.

    Также нельзя рекурсивно захватывать мьютекс, уже захваченный этим же потоком:
    hMutex = CreateMutex(…);
    WaitForSingleObject(hMutex, INFINITE);
    WaitForSingleObject(hMutex, INFINITE); // Зависнет навсегда
    ReleaseMutex(hMutex);
    ReleaseMutex(hMutex);
    CloseHandle(hMutex);

    ^ Эта ситуация называется «дедлок», когда поток или потоки создают ситуацию или самоблокировки (как выше), или взаимоблокировки (такое возможно, если потоки синхронизируются несколькими мьютексами).

    Есть примитивы синхронизации, у которых разрешён рекурсивный захват (например, критические секции), но у мьютексов запрещён.
     
    MaKsIm, Mikl___ и Andrey_59 нравится это.
  4. Thetrik

    Thetrik UA6527P

    Публикаций:
    0
    Регистрация:
    25 июл 2011
    Сообщения:
    875
    Для относительно быстрых операций с общей памятью можно заюзать спин блокировку. Они намного более легковесные и быстрые. В определённых случаях можно организовать структуру данных так (и нужно стремиться к этому) чтобы вообще обеспечить доступ к памяти из разных потоков без ожидания через interlocked функции/инструкции.
     
  5. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    450
  6. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Надо двигаться от простого к сложному. ТС ещё только учится, и разбираться в лок-фри сейчас - точно не лучшая идея.
    Там такая гора нюансов, что можно утонуть даже подготовленному человеку.
     
  7. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    HoShiMin
    Да об этом я читал, а ещё в книге было написано, что для переключения в режим ядра требуется 1000 (тиков - не уверен в точности единиц) и что это "дорогостоящая" операция, впрочем, как и все остальные объекты синхронизации уровня ядра... Так ли это? Действительно ли это снижает производительность?

    Короче говоря я всё же своего добился, программка заработала, по крайней мере на тех данных, что я вводил, и без мьютексов работает, хотя интереса ради я их всё же включил в функции AddString, DeleteString в функцию вывода, насколько я понял, мьютексы, как и другие объекты синхронизации не нужны. Только тот самый цикл, где производится сортировка, почему-то не поддаётся) Где лучше устанавливать мьютекс, в теле функции или в исполняемом файле, например так:
    Код (C++):
    1. HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("MUTEXFILEMAPPING"));
    2.     WaitForSingleObject(hMutex, INFINITI);
    3.     BOOL bOk = AddString(szString);
    4.     ReleaseMutex(hMutex);
    5.     if(bOk)
    6.     {
    7.         EnumWindows(&EnumCallBack, 0);
    8.     }
    9.     else
    10.         MessageBeep(0);
    В функции масса мест, где функция будет прерываться из-за "некорректного поведения" везде в таких местах придётся прописывать
    Код (Text):
    1. ReleaseMutex(hMutex);
    , а, если это в .*cpp файле, то код становиться не таким изящным, но зато ReleaseMutex(hMutex); вызывается только один раз. Хотя это очень похоже на вкусовщину, но всё же других причин я не вижу.

    Вот это засада самая настоящая я вообще не понял эту тему в книги давались пояснения и даже картинки, но, если я столкнусь с такой ситуацией я точно могу сказать, что я её не увижу(.
     
  8. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Да, любая синхронизация скорости не добавляет. Но нельзя оперировать категориями «дорогостоящая» и «легковесная»: производительность или достаточная, или нет.
    Если недостаточная - надо профилировать и смотреть, что именно тормозит. Возможно, получится обойтись без синхронизаций или уменьшить их количество (например, использовать стратегию Read-Copy-Update); а возможно, требуется поменять примитив синхронизации (например, мьютекс на RW-лок); а если изменение данных происходит очень быстро (скажем, поменять пару указателей) - можно использовать спинлок или написать лок-фри-алгоритм.
    Всё идёт от задачи: нельзя сказать, что спинлок «легче» мьютекса - один под одни задачи, другой под другие.

    Насчёт тысячи тактов: может и тысяча, может и миллион - разве эти цифры о чём-то говорят? Тысяча - это много или мало?
    Современные процессоры обрабатывают миллиарды операций в секунду, операционные системы постоянно переключают потоки между ядрами, прерывая их в случайные моменты, на это тратятся десятки тысяч инструкций, сбрасываются кэши, переключаются таблицы трансляций, процессор ошибается в предсказании ветвлений - и многое, многое другое сотни раз в секунду.
    На фоне всего этого выполнить syscall, дойти там до планировщика и сказать ему «я жду, разбуди меня позже» - это капля в море.

    UbIvItS, вот тебе пример, как не даёт жить отсутствие деструкторов.
    По сути вопроса: есть концепция RAII, которой ты должен следовать: для каждого объекта, требующего очистки (хэндлы, выделенная память и т.д.) заводится класс, принимающий сырой объект, сохраняющий его у себя и уничтожающий его в деструкторе.
    Это позволит не писать каждый раз очистку ресурсов на выходе: ты сделаешь return, и все деструкторы локальных объектов вызовутся автоматически, очистив все ресурсы.
    Нужны везде, где ты обращаешься к общим данным - не важно, на чтение или запись.
    В документации на msdn явно написано для каждого примитива синхронизации, поддерживает ли он рекурсивный вход. Если явно не сказано, что поддерживает - значит, входить рекурсивно нельзя.
    В остальных случаях если что-то зависло на попытке захватить примитив синхронизации - значит, его кто-то захватил и или забыл отпустить, или получилась ситуация, что потоки ждут освобождения мьютексов друг друга, но сами при этом их держат, не давая каждому пройти дальше.
     
  9. UbIvItS

    UbIvItS Well-Known Member

    Публикаций:
    0
    Регистрация:
    5 янв 2007
    Сообщения:
    6.243
    ну - ёклмн.. :) substitute("return", "close_handles()\nreturn") :grin: Ты лучше задумайся почему во ржаку впиндюрили borrow checker.. а всё просто - ржака по сути своей отрицает глобальные переменные и однопоточку вообще. прям дико сейфово:laugh1::laugh2::laugh3::crazy:
     
  10. algent

    algent Member

    Публикаций:
    0
    Регистрация:
    11 апр 2018
    Сообщения:
    101
    cmpxchg забыли
     
  11. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Вот из таких "капель в море" и складывается торможение приложений). Что ж, пусть остаётся так я же говорю я со всеми этими инструментами знаком очень поверхностно, так что инструменты, которые ты перечислил многие мне вообще не знакомы, а уж выбирать, какой из них эффективнее и уместнее, ну, как минимум смелое допущение. :derisive:

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

    Насколько я помню, то нужно синхронизировать функции, которые изменяются данные, соответственно, если я их просто читаю, то зачем...
    Я кстати написал класс file_mapping не знаю как он в деле, не пробовал, но всё же "навоял" пока что скелет, но всё же)
     
  12. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Если синхронизируешь только записи - в единицу времени может быть только один писатель и сколько угодно читателей. Без синхронизации на чтение возможна ситуация, когда писатель меняет данные, а читатель читает наполовину изменённые.
    Например, синхронно меняются два указателя: писатель успел поменять первый, но не успел поменять второй, как пришёл читатель и прочитал первый новый и старый второй.
    Чтобы такого не было, если есть хотя бы один писатель, синхронизировать нужно и чтения тоже.
    Однако, если данные только читают, но гарантированно никто никогда в них не пишет, синхронизировать их не надо.
     
  13. MaKsIm

    MaKsIm Member

    Публикаций:
    0
    Регистрация:
    11 фев 2008
    Сообщения:
    96
    На случай, когда при чтении больше одного примитива данных (один или несколько байт умещающихся в одну инструкцию) происходит переключение и следующий процесс/ветвь начинает их изменять. Так вы получите прочитанное начало до изменения и конец - после.
     
  14. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    450
  15. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Ну, чисто теоретически это понятно, но только теоретически, я пытаюсь донести то, что ясного видения того как всё это дело работает у меня нет, а без этого я просто-напросто не смогу понять, где нужно применять эти инструменты, а где и без них можно обойтись. Как бы это пояснить, например, когда ты отвечаешь на вопрос: "сколько будет дважды два:?" ты ведь точно знаешь ответ, а не наугад его даёшь "авось попаду в точку", а откуда ты его знаешь?! ну, смею предположить, что когда-то тебя научили считать: складывая, отнимая спички, палочки или что-то подобное, затем ты выучил таблицу умножения и благодаря этому ты твёрдо уверен в ответе. Это я к тому, что желательно понять, как всё это дело работает, а уж потом, возможно, и понимание придёт, куда нужно вставлять эти инструменты и какие из них более предпочтительны к данному конкретному случаю, а где и без них всё будет работать прекрасно, и, возможно, даже быстрее. Но за ответы, всё равно, огромное спасибо!

    Насколько я понимаю, это касается приложений, где я явно создаю несколько потоков или процессов, правильно? Если я создам в программе два и более потока, то в случае, если один из них или несколько меняю общие данные, то нужно применять методы синхронизации, если эти данные простые вроде int, flot, double, то тут можно применить интерлокет функции для увеличения или уменьшения значений, обмена значений... Так же если это потоки, то тут можно применить для синхронизации критические секции. Если же данные разделяются между двумя и.т.д. процессами, тот тут уже нужно применять инструменты вроде событий, мьютексов, семафоров и прочих подобных инструментов.
    Вот такое у меня понимание данных инструментов на данный момент.
     
  16. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    Ну в целом, верно.
    События, мьютексы и семафоры применяются и в рамках одного процесса. Например, если рекурсивный захват не нужен - юзаешь мьютекс. Если нужен - критическую секцию.
    Эвенты - когда из одного потока надо просигналить в другие.
    Просто межпроцессных примитивов не много - или винапишные, которым можно задать имя, или те, память которых можно разделять (например, спинлоки).
    Считай, что изначально все примитивы задуманы для синхронизации в одном процессе, но некоторые из них умеют и в разных.
     
  17. aa_dav

    aa_dav Active Member

    Публикаций:
    0
    Регистрация:
    24 дек 2008
    Сообщения:
    457
    Напоминаю, что в винде критические секции это легковесный аналог мьютексов и не являются объектами ядра. Они работают только внутри одного процесса. Мьютексы могут учавствовать в межпроцессном взаимодействии, но тяжеловеснее поэтому. При этом std::mutex из плюсов это скорее критическая секция, нежели виндовый мьютекс. В терминологии можно запутаться если не осторожничать.
    А вообще вопросами на форумах в многопоточном программировании хрен разберешся. Лучше взять книгу.
     
  18. MaKsIm

    MaKsIm Member

    Публикаций:
    0
    Регистрация:
    11 фев 2008
    Сообщения:
    96
    В целом почти верно. Тут вся сложность в том как осуществляется доступ к данным. Процессор выполняет одну или несколько инструкций, чтобы считать или записать данные. В случае, если инструкция одна, тогда синхронизация может быть выполнена на уровне машинных команд (префикс lock - тогда другие ядра точно не помешают). Но когда инструкций для доступа к данным несколько (а это любые сложносочиненные структуры данных), то переключение ветвей/процессов может произойти во время выполнения последовательности этих команд. Чтобы этого не происходило (вернее, чтобы при таком переключении диспетчер процессов не возобновил поток, который обращается к тем же самым данным) и применяются объекты синхронизации. Объекты синхронизации подсказывают операционной системе какие потоки могут быть возобновлены, а какие нет. Т.е. переключение потоков случится, но этот поток уже точно не будет обращаться к данным, доступ к которым был прерван.
     
  19. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    А зачем вообще нужен рекурсивный захват?
    Эвент - это события? Если я правильно понимаю.
    Создаётся объект ядра, например, мьютекс, что он из себя представляет этого объект ядра, какая-то структура?..., если я не задаю имя, то по сути это неименованная секция о которой говорилось касаемо "проецируемых в паять файлов", если же имя указывается, то, видимо, именуется некое поле структуры, возможно, меняется какой-то статус в этой структуре или что происходит? Запущенных процессов с этим объектом синхронизации может быть сколь угодно много, но объект ядра у них будет один. Интересно.
    У потоков есть некий квант времени через который система отключает один поток и включает другой, но процессов, насколько я знаю, такого нет, и, если я запускаю два процесса и один пишет что-то в общую память, а другой считывает, то, видимо, изходя из моего предположения о том, что ОС не переключает процессы, как потоки, они выполняются как?! последовательно, если их не синхронизировать?
    Если я задаю объект синхронизации, то, видимо, для описанной мной ситуации подойдёт event-объект, как только я записал в память я сигнализирую второму процессу, что данные готовы, читай. Если второй процесс ничего не исправляет, то он читает данные, например, выводит их и что, он тоже в свою очередь должен дать сигнал первому процессу, что данные прочитаны или это не обязательно, если ты их не меняешь? А если второй процесс что-то в данных меняет, то он должен заблокировать на время изменения данных себя, так? Тут, видимо, нужно использовать не только event-ы.

    Читал, но вопросов по прочтении книги не уменьшилось, теория она и в Африке теория, а по параллельному программированию в winapi литературы, мягко говоря, не много, в той же, которую я читал нет упражнений, хотя это книги не совсем о многопоточном программировании скорее в них этой темы касаются.

    Ну, это то я как раз понимаю, но тут масса мелочей о которых можно споткнуться, ещё проблема описания функций для этих объектов, возможно, это касается русского перевода, а может быть они туманно описаны и в оригинале, не знаю.
     
  20. MaKsIm

    MaKsIm Member

    Публикаций:
    0
    Регистрация:
    11 фев 2008
    Сообщения:
    96
    Не совсем. Mutex это объект синхронизации. Он не имеет ничего общего с проецируемыми в память файлами. Этот объект должен пометить блок команд, который работает с данными, разделяемыми разными потоками. Причем, опираясь на функционал этого объекта, диспетчер переключения задач может видеть какие потоки можно включать, а какие нет. Например, у нас в процессе 2 потока. Оба используют mutex для доступа к данным. Тогда дойдя до WaitFor...Object будет производиться проверка доступности mutex. Если он доступен, то он будет занят и выполнение продолжится. Но система будет знать, что поток вошел в пространство синхронизации. Затем, на втором ядре выполнявшийся, второй поток тоже вызовет WaitFor...Object и проверки покажут, что mutex занят. Тогда произойдет переключение задач. И вот тут вот начинаются нюансы.
    1) Первый поток во время проверок тоже попал на стоп в другом ядре и не освободил mutex (допустим из-за окончания кванта - не суть). Тогда приоритетно второе ядро будет проверено на переключение на поток в том же процессе и второе ядро переключится на него. Так диспетчер задач быстрее разблокирует выполнение второго потока.
    2) Первый поток все еще выполняет доступ к данным. Тогда переключение будет выполнено на другой поток в другом процессе. Это потребует больше манипуляций с данными, но выполнение продолжится без проблем.
    В любом случае все эти манипуляции призваны обеспечить монопольный доступ к общим данным на все время доступа к ним на уровне планирования задач.

    Не совсем. Процесс это набор потоков и виртуальное адресное пространство памяти, в котором они функционируют. Плюс в процессе есть свои наборы объектов, которые существуют только в его адресном пространстве (например, handle). Но хотя handle и существует в рамках адресного пространства процесса, он все равно предоставляет доступ к общим ресурсам в системе (описывает данные для доступа к ним).

    Разумеется, если через некоторое количество квантов времени, когда все потоки одного процесса будут заморожены, то произойдет переключение процессов. Но, скажем в разных ядрах, могут параллельно работать потоки из разных процессов. Не обязательно один процесс будет приоритетно занимать все ядра процессора. На каждом ядре в процессоре может исполняться любой системный поток. Так, например, происходит при поступлении прерывания (от клавиатуры или мыши, скажем).