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

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

  1. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Здравствуйте! Вопрос касается темы Отображение файла в память (File mapping ), если кому-нибудь будет интересно, первая проблема касается того факта, что после создания объекта "проекция файла" и Представление данных файла (file view), когда я задаю какие-то данные, то они выводятся не так как нужно, после того как я ввожу второе и последующее значения в памяти(и при отображении) значения последнего введённого элемента т.е., если введу one, two, то будет выведено two, two, а не one, two как предполагалось.

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

    Код (C++):
    1. #include <windows.h>
    2. #include "strlib.h"
    3.  
    4. #pragma data_seg ("shared")
    5.  
    6. PSTR pszStrings[MAX_STRINGS] = { NULL } ;
    7. int  iTotal = 0 ;
    8.  
    9. #pragma data_seg ()
    10.  
    11. #pragma comment(linker, "/SECTION:shared,RWS")
    12.  
    13.  
    14. int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
    15.      {
    16.      int i ;
    17.  
    18.      switch (fdwReason)
    19.           {
    20.           // Nothing to do when process (or thread) begins
    21.  
    22.           case DLL_PROCESS_ATTACH :
    23.           case DLL_THREAD_ATTACH :
    24.           case DLL_THREAD_DETACH :
    25.                break ;
    26.  
    27.           // When process terminates, free any remaining blocks
    28.  
    29.           case DLL_PROCESS_DETACH :
    30.                for (i = 0 ; i < iTotal ; i++)
    31.                     UnmapViewOfFile (pszStrings[i]) ;
    32.                break ;
    33.           }
    34.  
    35.      return TRUE ;
    36.      }
    37.    
    38.  
    39. EXPORT BOOL CALLBACK AddString (PSTR pStringIn)
    40. {
    41.      HANDLE hString ;
    42.      PSTR   pString ;
    43.      int    i, iLength, iCompare ;
    44.  
    45.      if (iTotal == MAX_STRINGS - 1)
    46.           return FALSE ;
    47.  
    48.      iLength = strlen (pStringIn) ;
    49.      if (iLength == 0)
    50.           return FALSE ;
    51.      //
    52.      hString = CreateFileMapping ((HANDLE) -1, NULL, PAGE_READWRITE,
    53.                                   0, 1 + iLength, NULL) ;
    54.      if (hString == NULL)
    55.           return FALSE ;
    56.      //Адреса получаются разные...
    57.      pString = (PSTR) MapViewOfFile (hString, FILE_MAP_WRITE, 0,0,0);
    58.      //Мне кажется, что после того, как скопировал значение в pString
    59.      //все значения в разделяемой памяти стали равные именно этому
    60.      //значению, почему?
    61.      strcpy (pString, pStringIn);
    62.      AnsiUpper (pString) ;
    63.  
    64.      for (i = iTotal ; i > 0 ; i--)
    65.           {
    66.           iCompare = strcmpi (pStringIn, pszStrings[i - 1]) ;
    67.  
    68.           if (iCompare >= 0)
    69.                break ;
    70.  
    71.           pszStrings[i] = pszStrings[i - 1] ;
    72.           }
    73.  
    74.      pszStrings[i] = pString ;
    75.  
    76.      iTotal++ ;
    77.      return TRUE ;
    78. }
     
  2. galenkane

    galenkane Active Member

    Публикаций:
    0
    Регистрация:
    13 янв 2017
    Сообщения:
    302
    мб поможет
    upload_2024-1-8_15-59-56.png upload_2024-1-8_16-0-10.png
     
  3. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    А что вообще надо сделать?
    У тебя есть массив указателей, который разделяется между всеми процессами, которые грузят эту дллку. Но разделяется только сам массив, но не данные под указателями.
    Тебе надо, чтобы строки в этом массиве тоже были общими у разных процессов?
     
  4. Andrey_59

    Andrey_59 Member

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

    Второе, думаю, тут вообще не должно быть вопросов, что, если вы запускаете вторую копию приложения, то она должна работать, а не падать или не отображать информацию вообще или отображать коряво и.т.д...
    --- Сообщение объединено, 8 янв 2024 ---
    hString создаётся при каждом вызове AddString();, если я правильно понял то, что написано в том материале, что вы выложили.
    --- Сообщение объединено, 8 янв 2024 ---
    Я не понимаю почему так происходит, что ранее введённые значения заменяются последним введённым, функция MapViewOfFile возвращает разные адреса для представлений данных файла(file view) эти адреса должны присваиваться массиву pszStrings, который в-общем то и является свободной памятью зачем ещё и File mapping не ясно, ну да ладно, если адреса разные то почему, когда я создаю новое представление данных файла и копирую туда строку-аргумент, данные "портятся" адреса то разные... Я уже голову сломал пытаясь понять что там внутри происходит, нужна помощь.:dash1:
     
  5. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Andrey_59, опиши исходную задачу. Что ты пытаешься сделать и зачем тебе нужен расшаренный между процессами массив строк?
    Когда ты делаешь CreateFileMapping -> MapViewOfFile, ты получаешь адрес буфера, который валиден только в процессе, для которого ты сделал маппинг.
    У тебя сейчас работает следующим образом: Процесс1 вызывает AddString, в нём делает MapViewOfFile, получает адрес, в него пишет строку, и адрес этой строки добавляет в массив.
    Затем Процесс2 делает AddString, создаёт свою строку и начинает сравнивать её со строками из массива. Но адрес, записанный в массив в Процессе1, невалиден в Процессе2, и на попытке обратиться к этой строке Процесс2 падает.

    Ты не можешь просто записывать в расшаренный массив адреса: тебе требуется, чтобы память по этим адресам тоже была расшарена между процессами, а MapViewOfFile это не делает.

    Возможное решение: для каждой строки заводить отдельную секцию через CreateFileMapping с уникальным именем: например, "String_N", где N будет неким уникальным числовым идентификатором (1, 2, 3 и т.д.), и в массив класть не указатели, а идентификаторы, по которым в другом процессе ты сможешь открыть секцию и отмапить её через MapViewOfFile.

    Здесь возникает другая проблема, связанная с синхронизацией между процессами.
    Например, Процесс1 создал секцию с именем "String_123", записал идентификатор 123 в массив, затем ты запускаешь Процесс2, ещё ничего там не делаешь, а Процесс1 по каким-то причинам падает, а вместе с ним закрывается и его секция "String_123". Процесс2 об этом ничего не знает: он видит, что в массиве лежит идентификатор 123, пытается открыть секцию с именем "String_123", а её уже нет. Твои процессы должны быть к этому готовы.

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

    Но выглядит так, будто ты изначально пытаешься сделать что-то, что решается по-другому, без необходимости вводить общую память.
     
  6. Andrey_59

    Andrey_59 Member

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

    Итак, что делает CreateFileMapping: она выделяет блок памяти, даёт команду ОС зарезервировать память, на которую ссылается дескриптор? Или что она вообще делает, если человеческим языком...
    Получили дескриптор hString = CreateFileMapping далее вызывая функцию MapViewOfFile я получаю "Представление данных файла (file view)" т.е. некий адрес в виртуальной памяти процесса, а по сути, что делает эта функция, если простым языком, она проецирует участок памяти, если параметры dwFileOffsetHigh = 0, dwFileOffsetLow = 0, dwNumberOfBytesToMap = 0, то весь участок памяти, который зарезервировала функция CreateFileMapping, либо, если задать dwFileOffsetHigh = 0, dwFileOffsetLow = offset, dwNumberOfBytesToMap = sizeByte, то участок памяти начиная со смещения offset и размером sizeByte. Грубо говоря, если зарезервировать буфер, скажем, 10 символов, то вызывая
    MapViewOfFile(hString, FILE_MAP_ALL_ACCESS, 0, 4, 5); я проецирую не всю память, а только её часть со смещения 4 и длиной 5 символов, так? Не знаю, очень, коряво описал, но так я понимаю данный механизм.
    Для начала я бы хотел разобраться с первой проблемой, с той, которая не выводит(не сохраняет) корректно данные, пока пусть это будет одна запущенная копия процесса.
    Они портятся после копирования символов из параметра функции в pString- представление данных файла. Но как связан pString со всеми адресами, которые были помещены в массив pszStrings до этого?
     
  7. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    В первом сообщении ты писал, что если ввести "one", "two", то при чтении ты видишь "two", "two" - у меня не воспроизводится, в массиве лежат "ONE", "TWO".
    Напиши, на каких тестовых данных ты проверял и где гарантированно воспроизводится.

    Также опиши словами, как должна работать вставка: для чего там strcmpi и для чего эта строчка pszStrings = pszStrings[i - 1] ;? Это какая-то сортировка?

    Кроме того, у тебя в функции утекают хэндлы (не делаешь CloseHandle для hString) и отображённая память (когда заменяешь указатели, не делаешь UnmapViewOfFile).
    --- Сообщение объединено, 9 янв 2024 ---
    CreateFileMapping создаёт секцию (это такой объект ядра) с заданным именем - и резервирует под него физическую память.
    Процессор не умеет работать с физической памятью напрямую, поэтому нужно сопоставить этой физической памяти один или несколько виртуальных адресов (сделать отображение физической памяти на виртуальное адресное пространство).
    Отображение делается функцией MapViewOfFile: она создаёт «окно», через которое процесс может работать с физической памятью, ассоциированной с хэндлом секции.
    В адресном пространстве процесса резервируется непрерывный диапазон по случайному адресу, который тебе возвращает MapViewOfFile, и этот диапазон ведёт на физическую память секции.
    Ты можешь делать множество отображений одной секции и в одном процессе, и в разных, получая разные виртуальные адреса «окон», но все они будут вести на одну и ту же физическую память.

    Выглядит это так:

    Процесс1:
    CreateFileMapping("MappingName") => hSection => Phys = 0x123000
    MapViewOfFile(hSection) => Virt = 0x7ff1000 => Phys = 0x123000
    MapViewOfFile(hSection) => Virt = 0x7ff2000 => Phys = 0x123000
    … мапить можно сколько угодно, все виртуальные адреса будут вести на одну физическую память …

    Процесс2
    OpenFileMapping("MappingName") => hSection => Phys = 0x123000
    MapViewOfFile(hSection) => Virt = 0xabcd1000 => Phys = 0x123000
    MapViewOfFile(hSection) => Virt = 0xabcd2000 => Phys = 0x123000

    То есть, адреса отображений разные, и сами отображения не разделяются между процессами, но физическая память под ними общая, и разделяется именно она.
     
    MaKsIm и Mikl___ нравится это.
  8. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Да, если я делаю как советуют т.е. первоначально вызываю CreateFileMapping, а затем OpenFileMapping, это, якобы, для того чтобы запущенные копии приложения получали доступ к расширенным данным. Если делаю так, то да я при вводе данных получаю на выходе последнее введённое значение, ввожу "one", "two", получаю при выводе "two", "two". В памяти тоже самое все значения заменяются на последнее введённое. В массиве, конечно же, тоже лежат "two", "two". Адреса 0x00380000 = "one", при первом MapView, после второго ввода данных, вызова функции AddString, OpenFileMapping, MapViewOfFile 0x00390000 = "two", но и 0x00380000 = "two".
    Если же делать так как в изначальном примере т.е. при каждом вызове функции AddString вызывать CreateFileMapping, MapViewOfFile, то по тем же адресам уже будут записаны 0x00390000 = "two", но уже 0x00380000 = "one", но тут возникает другая проблема при запуске копии приложения либо окно закрывается, либо закрывается после следующего ввода в первое окно, либо, если ввести данные через второе окно, то в первом окне в функции вывода выскакивает ошибка доступа к памяти...
    Для чего сравнение без учёта регистра символов я точно сказать не могу, а вот pszStrings = pszStrings[i - 1], это сортировка, видимо, для более интересного написания кода к основной теме это никак не относится.
    Да! О том, что дескриптор не освобождается я знаю, я уже этот момент поправил. По поводу UnmapViewOfFile там в коде автора есть функция DeleteString(LPCTSTR), где строка освобождается с помощью UnmapViewOfFile, затем массив упорядочивается, все данные сдвигаются. По завершении приложения, когда библиотека отключается все оставшиеся строки в массиве удаляются UnmapViewOfFile. Я не выложил весь код чтобы не загромождать вывод, если нужно, то выложу. В DLL-библиотеке три функции AddString, DeleteString, GetString.

    Код (C++):
    1. EXPORT BOOL CALLBACK DeleteString (PSTR pStringIn)
    2.      {
    3.      int i, j, iCompare ;
    4.  
    5.      if (0 == strlen (pStringIn))
    6.           return FALSE ;
    7.  
    8.      for (i = 0 ; i < iTotal ; i++)
    9.           {
    10.           iCompare = lstrcmpi (pszStrings[i], pStringIn) ;
    11.  
    12.           if (iCompare == 0)
    13.                break ;
    14.           }
    15.  
    16.      // If given string not in list, return without taking action
    17.  
    18.      if (i == iTotal)
    19.           return FALSE ;
    20.  
    21.      // Else free memory occupied by the string and adjust list downward
    22.  
    23.      UnmapViewOfFile (pszStrings[i]) ;
    24.  
    25.      for (j = i ; j < iTotal ; j++)
    26.           pszStrings[j] = pszStrings[j + 1] ;
    27.  
    28.      pszStrings[iTotal--] = NULL ;    // Destroy unused pointer
    29.      return TRUE ;
    30.      }
    //pfnGetStrCallBack - функция обратного вывода, которая реализована в основном приложении.
    Код (C++):
    1. EXPORT int CALLBACK GetStrings (PSTRCB pfnGetStrCallBack, PVOID pParam)
    2.      {
    3.      BOOL bReturn ;
    4.      int  i ;
    5.  
    6.      for (i = 0 ; i < iTotal ; i++)
    7.           {
    8.           bReturn = pfnGetStrCallBack (pszStrings[i], pParam) ;
    9.  
    10.           if (bReturn == FALSE)
    11.                return i + 1 ;
    12.           }
    13.      return iTotal ;
    14.      }
    Код (C++):
    1. #include <windows.h>
    2. #include <string.h>
    3. #include "strprog.h"
    4. #include "strlib.h"
    5.  
    6. #define MAXLEN 32
    7. #define WM_DATACHANGE WM_USER
    8.  
    9. typedef struct
    10.      {
    11.      HDC  hdc ;
    12.      int  xText ;
    13.      int  yText ;
    14.      int  xStart ;
    15.      int  yStart ;
    16.      int  xIncr ;
    17.      int  yIncr ;
    18.      int  xMax ;
    19.      int  yMax ;
    20.      }
    21.      CBPARAM ;
    22.  
    23. LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
    24.  
    25. char szAppName[] = "StrProg" ;
    26. char szString[MAXLEN] ;
    27.  
    28. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
    29.                     PSTR szCmdLine, int iCmdShow)
    30.      {
    31.      HWND        hwnd ;
    32.      MSG         msg ;
    33.      WNDCLASSEX  wndclass ;
    34.  
    35.      wndclass.cbSize        = sizeof (wndclass) ;
    36.      wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
    37.      wndclass.lpfnWndProc   = WndProc ;
    38.      wndclass.cbClsExtra    = 0 ;
    39.      wndclass.cbWndExtra    = 0 ;
    40.      wndclass.hInstance     = hInstance ;
    41.      wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
    42.      wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
    43.      wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
    44.      wndclass.lpszMenuName  = szAppName ;
    45.      wndclass.lpszClassName = szAppName ;
    46.      wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;
    47.  
    48.      RegisterClassEx (&wndclass) ;
    49.  
    50.      hwnd = CreateWindow (szAppName, "DLL Demonstration Program",
    51.                           WS_OVERLAPPEDWINDOW,
    52.                           CW_USEDEFAULT, CW_USEDEFAULT,
    53.                           CW_USEDEFAULT, CW_USEDEFAULT,
    54.                           NULL, NULL, hInstance, NULL) ;
    55.  
    56.      ShowWindow (hwnd, iCmdShow) ;
    57.      UpdateWindow (hwnd) ;
    58.  
    59.      while (GetMessage (&msg, NULL, 0, 0))
    60.           {
    61.           TranslateMessage (&msg) ;
    62.           DispatchMessage (&msg) ;
    63.           }
    64.      return msg.wParam ;
    65.      }
    66.  
    67. BOOL CALLBACK DlgProc (HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
    68.      {
    69.      switch (iMsg)
    70.           {
    71.           case WM_INITDIALOG :
    72.                SendDlgItemMessage (hDlg, IDD_STRING, EM_LIMITTEXT,
    73.                                    MAXLEN - 1, 0) ;
    74.                return TRUE ;
    75.  
    76.           case WM_COMMAND :
    77.                switch (wParam)
    78.                     {
    79.                     case IDOK :
    80.                          GetDlgItemText (hDlg, IDD_STRING, szString, MAXLEN) ;
    81.                          EndDialog (hDlg, TRUE) ;
    82.                          return TRUE ;
    83.  
    84.                     case IDCANCEL :
    85.                          EndDialog (hDlg, FALSE) ;
    86.                          return TRUE ;
    87.                     }
    88.           }
    89.      return FALSE ;
    90.      }
    91.  
    92. BOOL CALLBACK EnumCallBack (HWND hwnd, LPARAM lParam)
    93.      {
    94.      char szClassName[16] ;
    95.  
    96.      GetClassName (hwnd, szClassName, sizeof (szClassName)) ;
    97.  
    98.      if (0 == strcmp (szClassName, szAppName))
    99.           SendMessage (hwnd, WM_DATACHANGE, 0, 0) ;
    100.  
    101.      return TRUE ;
    102.      }
    103.  
    104. BOOL CALLBACK GetStrCallBack (PSTR pString, CBPARAM *pcbp)
    105.      {
    106.      TextOut (pcbp->hdc, pcbp->xText, pcbp->yText,
    107.               pString, strlen (pString)) ;
    108.  
    109.      if ((pcbp->yText += pcbp->yIncr) > pcbp->yMax)
    110.           {
    111.           pcbp->yText = pcbp->yStart ;
    112.           if ((pcbp->xText += pcbp->xIncr) > pcbp->xMax)
    113.                return FALSE ;
    114.           }
    115.      return TRUE ;
    116.      }
    117.  
    118. LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
    119.      {
    120.      static HINSTANCE  hInst ;
    121.      static int        cxChar, cyChar, cxClient, cyClient ;
    122.      CBPARAM           cbparam ;
    123.      HDC               hdc ;
    124.      PAINTSTRUCT       ps ;
    125.      TEXTMETRIC        tm ;
    126.  
    127.      switch (iMsg)
    128.           {
    129.           case WM_CREATE :
    130.                hInst = ((LPCREATESTRUCT) lParam)->hInstance ;
    131.                hdc   = GetDC (hwnd) ;
    132.                GetTextMetrics (hdc, &tm) ;
    133.                cxChar = (int) tm.tmAveCharWidth ;
    134.                cyChar = (int) (tm.tmHeight + tm.tmExternalLeading) ;
    135.                ReleaseDC (hwnd, hdc) ;
    136.  
    137.                return 0 ;
    138.  
    139.           case WM_COMMAND :
    140.                switch (wParam)
    141.                     {
    142.                     case IDM_ENTER :
    143.                          if (DialogBox (hInst, "EnterDlg", hwnd, &DlgProc))
    144.                               {
    145.                               if (AddString (szString))
    146.                                    EnumWindows (&EnumCallBack, 0) ;
    147.                               else
    148.                                    MessageBeep (0) ;
    149.                               }
    150.                          break ;
    151.  
    152.                     case IDM_DELETE :
    153.                          if (DialogBox (hInst, "DeleteDlg", hwnd, &DlgProc))
    154.                               {
    155.                               if (DeleteString (szString))
    156.                                    EnumWindows (&EnumCallBack, 0) ;
    157.                               else
    158.                                    MessageBeep (0) ;
    159.                               }
    160.                          break ;
    161.                     }
    162.                return 0 ;
    163.  
    164.           case WM_SIZE :
    165.                cxClient = (int) LOWORD (lParam) ;
    166.                cyClient = (int) HIWORD (lParam) ;
    167.                return 0 ;
    168.  
    169.           case WM_DATACHANGE :
    170.                InvalidateRect (hwnd, NULL, TRUE) ;
    171.                return 0 ;
    172.  
    173.           case WM_PAINT :
    174.                hdc = BeginPaint (hwnd, &ps) ;
    175.  
    176.                cbparam.hdc   = hdc ;
    177.                cbparam.xText = cbparam.xStart = cxChar ;
    178.                cbparam.yText = cbparam.yStart = cyChar ;
    179.                cbparam.xIncr = cxChar * MAXLEN ;
    180.                cbparam.yIncr = cyChar ;
    181.                cbparam.xMax  = cbparam.xIncr * (1 + cxClient / cbparam.xIncr) ;
    182.                cbparam.yMax  = cyChar * (cyClient / cyChar - 1) ;
    183.  
    184.                GetStrings ((PSTRCB) GetStrCallBack, (PVOID) &cbparam) ;
    185.  
    186.                EndPaint (hwnd, &ps) ;
    187.                return 0 ;
    188.  
    189.           case WM_DESTROY :
    190.                PostQuitMessage (0) ;
    191.                return 0 ;
    192.           }
    193.      return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
    194.      }
     
  9. R81...

    R81... Active Member

    Публикаций:
    0
    Регистрация:
    1 фев 2020
    Сообщения:
    153
    HoShiMin, как программе в UM WinXP32 получить эти физические адреса памяти "Phys = 0x123000" и далее, возможно разбросанные по разным страницам, соответственно?
     
  10. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Скорей всего никак. На Vista и выше можно было бы через недокументированные API от Superfetch, но на XP их не было.
    Наверно проще всего написать драйвер, в UM сделать MapViewOfFile, отдать драйверу полученный буфер, а тот сделает MmGetPhysicalAddress и вернёт физические адреса.

    А зачем их вообще получать?
     
  11. R81...

    R81... Active Member

    Публикаций:
    0
    Регистрация:
    1 фев 2020
    Сообщения:
    153
    Чтобы в/в сделать не через процессор, а контроллером:
    побыстрее, там же только через физ. адреса.
    С драйверами - WinRing0 изучать, пока не связывался,
    было что-то с porttalk-ом, но ни в Win98, ни в XP
    мне он не пригодился.
    P.S. Это только для внутренних нужд -
    ни заказчиков, ни распространения не предполагается.
     
  12. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Да, если надо гонять данные между оперативкой и своим девайсом, можно заюзать DMA.
    DMA Programming Techniques - Windows drivers | Microsoft Learn
     
  13. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Объект ядра секция я о таком и не слышал.

    На windows 10 тот же код, не отрабатывает как надо, на windows xp, если не запускать копий приложения, выводит(сохраняет) введённые данные корректно. Это я про код функции добавления сейчас...

    Код (C++):
    1. EXPORT BOOL CALLBACK AddString (PSTR pStringIn)
    2.      {
    3.      HANDLE hString ;
    4.      PSTR   pString ;
    5.      int    i, iLength, iCompare ;
    6.  
    7.      if (iTotal == MAX_STRINGS - 1)
    8.           return FALSE ;
    9.  
    10.      iLength = strlen (pStringIn) ;
    11.      if (iLength == 0)
    12.           return FALSE ;
    13.  
    14.      hString = CreateFileMapping ((HANDLE) -1, NULL, PAGE_READWRITE,
    15.                                   0, 1 + iLength, NULL) ;
    16.      if (hString == NULL)
    17.           return FALSE ;
    18.  
    19.      pString = (PSTR) MapViewOfFile (hString, FILE_MAP_WRITE, 0, 0, 0) ;
    20.      strcpy (pString, pStringIn) ;
    21.      AnsiUpper (pString) ;
    22.  
    23.      for (i = iTotal ; i > 0 ; i--)
    24.           {
    25.           iCompare = strcmpi (pStringIn, pszStrings[i - 1]) ;
    26.  
    27.           if (iCompare >= 0)
    28.                break ;
    29.  
    30.           pszStrings[i] = pszStrings[i - 1] ;
    31.           }
    32.  
    33.      pszStrings[i] = pString ;
    34.  
    35.      iTotal++ ;
    36.      return TRUE ;
    37.      }
    --- Сообщение объединено, 10 янв 2024 ---
    Т.е., если я вызываю CreateFileMapping многократно, то это всё равно будет одна и та же "секция", которая будет указывать на один и тот же участок памяти? Зачем же тогда автор книги при вызове функции AddString каждый раз вызывает CreateFileMapping, не ясно.
    Я в windows xp получал одни и те же адреса объекта "проекция файла" и разные адреса отображений-"окон", как вы и показали, но при этом вводимые данные корректно отображались, но, если включить сюда же вызов OpenFileMapping, то адреса объекта "проекция файла" уже будут разными, а вводимые данные будут выводиться не корректно, хотя адреса те же самые(по крайней мере на windows xp), почему так?
    --- Сообщение объединено, 10 янв 2024 ---
    Такой вариант, если память выделяется в DLL-библиотеке, то почему бы не вызвать CreateFileMapping в функции DllMain при получении сообщения DLL_PROCESS_ATTACH, но результат тот же, вводимые значения выводятся некорректно. Но адреса той самой секции, которую я получаю при вызове CreateFileMapping почему они разные я же вызываю функцию CreateFileMapping только при проецировании библиотеки на адресное пространство процесса т.е. когда библиотека подключается к процессу то в функцию DllMain приходит сообщение DLL_PROCESS_ATTACH или я в чём-то не прав?

    Код (C++):
    1. #pragma data_seg("shared")
    2. LPTSTR pszStrings[MAX_STRINGS] = { NULL };
    3. HANDLE hString = NULL;
    4. const TCHAR szNameMap[] = TEXT("_STRLIBMAPFILE_");
    5. int iTotal = 0;
    6. #pragma data_seg()
    7.  
    8. #pragma comment(linker, "/section:shared,RWS")
    9.  
    10.  
    11. int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
    12. {
    13.     switch (fdwReason)
    14.     {
    15.         //Nothing to do when process(or thread) begins
    16.  
    17.     case DLL_PROCESS_ATTACH:
    18.     {
    19.         hString = CreateFileMapping((HANDLE)-1, NULL, PAGE_READWRITE,
    20.             0, /*sizeof(TCHAR) * (iLength + 1)*/ 1024, szNameMap);
    21.         if (hString == NULL)
    22.         {
    23.             return FALSE;
    24.         }
    25.         //break;
    26.     }
    27.     case DLL_THREAD_ATTACH:
    28.     case DLL_THREAD_DETACH:
    29.         break;
    30.  
    31.         //When process terminates, tree any remaining blocks
    32.     case DLL_PROCESS_DETACH:
    33.     {
    34.         for (int i = 0; i < iTotal; ++i)
    35.             UnmapViewOfFile(pszStrings[i]);
    36.        
    37.         CloseHandle(hString);
    38.         break;
    39.     }
    40.     }
    41.     return TRUE;
    42. }
     
  14. HoShiMin

    HoShiMin Well-Known Member

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

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

    По алгоритму: для начала убери весь этот странный код с strcmpi, AnsiUpper и перетасовыванием указателей, потому что непонятно, что хотел сделать автор.
    Чтобы прочитать строки из другого процесса, для каждой строки тебе нужно создать секцию с неким уникальным именем.
    Пишущий процесс создаёт секции с уникальными именами для каждой строки, а читающий процесс должен как-то узнать эти имена, чтобы у себя открыть эти секции через OpenFileMapping, отмапить их к себе в процесс через MapViewOfFile, и в полученных буферах будут лежать строки.
    Основная проблема в том, что читающий процесс должен узнать имена секций, которые им дал пишущий процесс.
    Чтобы это решить, я предлагаю в массиве хранить не указатели на непонятно что, а числовые айдишники секций.

    Получится вот такой код:

    SampleDll.cpp:
    Код (C++):
    1.  
    2. #define WIN32_LEAN_AND_MEAN
    3. #include <Windows.h>
    4. #include <string>
    5.  
    6. struct SharedSection
    7. {
    8.     HANDLE hSection; // Хэндл секции, чтобы владелец мог её закрыть при завершении работы
    9.     DWORD ownerPid; // ProcessID владельца секции (того, кто её создал)
    10.     unsigned int sectionUid; // Уникальный ID секции
    11. };
    12.  
    13. constexpr unsigned int k_maxOfSections = 10; // Размер разделяемого массива
    14.  
    15. HANDLE g_mutex = nullptr;
    16.  
    17. // Создаём секцию с именем ".shared", где будут лежать разделяемые данные:
    18. #pragma data_seg(".shared")
    19. volatile SharedSection g_sections[k_maxOfSections]{}; // Массив UID'ов
    20. volatile unsigned int g_actualSize = 0; // Сколько уже записано в массив
    21. volatile unsigned int g_currentUid = 0; // Счётчик UID'ов, общий для всех процессов, необходимый для генерации уникальных имён
    22. #pragma data_seg()
    23.  
    24. // Говорим линкеру, что секция ".shared" должна иметь атрибуты RWS (Read, Write, Shared):
    25. #pragma comment(linker, "/section:.shared,RWS")
    26.  
    27. // Генерирует имя секции на основе UID'а:
    28. std::wstring generateSectionName(unsigned int uid)
    29. {
    30.     return L"SampleSection" + std::to_wstring(uid);
    31. }
    32.  
    33. extern "C" __declspec(dllexport) void __stdcall lockSharedData();
    34. extern "C" __declspec(dllexport) void __stdcall unlockSharedData();
    35.  
    36. // Инициализация мьютекса - вызывать до первого обращения к разделяемым данным:
    37. extern "C"
    38. __declspec(dllexport)
    39. bool __stdcall initShared()
    40. {
    41.     g_mutex = CreateMutexW(nullptr, false, L"MutexForSharedData");
    42.     return g_mutex != nullptr;
    43. }
    44.  
    45. // Деинициализация - вызывать после последнего обращения к разделяемым данным:
    46. extern "C"
    47. __declspec(dllexport)
    48. void __stdcall deinitShared()
    49. {
    50.     // Закрываем секции, созданные нашим процессом:
    51.     lockSharedData();
    52.     for (unsigned int i = 0; i < g_actualSize;)
    53.     {
    54.         auto& entry = g_sections[i];
    55.         if (entry.ownerPid == GetCurrentProcessId())
    56.         {
    57.             // Секция создана нашим процессом, закрываем её:
    58.             CloseHandle(entry.hSection);
    59.  
    60.             // Удаляем запись из списка:
    61.             if ((g_actualSize == 1) || (i == (k_maxOfSections - 1)))
    62.             {
    63.                 // Это последняя секция в списке или единственная оставшаяся, просто уменьшаем общее количество и выходим из цикла:
    64.                 --g_actualSize;
    65.                 break;
    66.             }
    67.             else
    68.             {
    69.                 // Перемещаем последнюю секцию в массиве на наше место:
    70.                 memcpy(const_cast<SharedSection*>(&g_sections[i]), const_cast<const SharedSection*>(&g_sections[g_actualSize - 1]), sizeof(SharedSection));
    71.                 --g_actualSize;
    72.                 continue; // Продолжаем со сдвинутой на наше место, не увеличивая `i`
    73.             }
    74.  
    75.             ++i;
    76.         }
    77.     }
    78.     unlockSharedData();
    79.  
    80.     if (g_mutex)
    81.     {
    82.         CloseHandle(g_mutex);
    83.         g_mutex = nullptr;
    84.     }
    85. }
    86.  
    87. extern "C"
    88. __declspec(dllexport)
    89. void __stdcall lockSharedData()
    90. {
    91.     WaitForSingleObject(g_mutex, INFINITE);
    92. }
    93.  
    94. extern "C"
    95. __declspec(dllexport)
    96. void __stdcall unlockSharedData()
    97. {
    98.     ReleaseMutex(g_mutex);
    99. }
    100.  
    101. // Создаёт новую секцию, записываем в неё строку и кладёт информацию о секции в разделяемый массив:
    102. extern "C"
    103. __declspec(dllexport)
    104. bool __stdcall addSharedString(const char* str)
    105. {
    106.     // Захватываем межпроцессную блокировку:
    107.     lockSharedData();
    108.  
    109.     if (g_actualSize == k_maxOfSections)
    110.     {
    111.         // В массиве больше нет места - снимаем блокировку и выходим:
    112.         unlockSharedData();
    113.         return false;
    114.     }
    115.  
    116.     // Генерируем новый UID для строки:
    117.     ++g_currentUid;
    118.  
    119.     // Создаём имя секции:
    120.     const std::wstring secName = generateSectionName(g_currentUid);
    121.  
    122.     // Создаём секцию под хранение строки с размером, равным длине строки:
    123.     const size_t len = strlen(str) + sizeof('\0');
    124.     const HANDLE hSection = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, static_cast<DWORD>(len), secName.c_str());
    125.     if (!hSection)
    126.     {
    127.         // Не получилось создать секцию - снимаем блокировку и выходим:
    128.         unlockSharedData();
    129.         return false;
    130.     }
    131.  
    132.     // Отображаем секцию в наш процесс:
    133.     void* const mapping = MapViewOfFile(hSection, FILE_MAP_WRITE, 0, 0, 0);
    134.     if (!mapping)
    135.     {
    136.         // Не получилось создать отображение секции - снимаем блокировку, закрываем саму секцию и выходим:
    137.         unlockSharedData();
    138.         CloseHandle(hSection);
    139.         return false;
    140.     }
    141.  
    142.     // Копируем строку в отображение вместе с нуль-терминатором:
    143.     memcpy(mapping, str, len);
    144.  
    145.     // Закрываем отображение (но не саму секцию!):
    146.     UnmapViewOfFile(mapping);
    147.  
    148.     // Записываем описание секции в массив:
    149.     auto& entry = g_sections[g_actualSize];
    150.     entry.hSection = hSection;
    151.     entry.ownerPid = GetCurrentProcessId();
    152.     entry.sectionUid = g_currentUid;
    153.     ++g_actualSize; // Увеличиваем количество элементов в массиве
    154.  
    155.     // Снимаем межпроцессную блокировку:
    156.     unlockSharedData();
    157.  
    158.     return true; // Успешно выходим
    159. }
    160.  
    161. extern "C"
    162. __declspec(dllexport)
    163. void __stdcall printStrings()
    164. {
    165.     // Захватываем межпроцессную блокировку:
    166.     lockSharedData();
    167.  
    168.     for (unsigned int i = 0; i < k_maxOfSections; ++i)
    169.     {
    170.         auto& entry = g_sections[i];
    171.  
    172.         // Формируем имя секции на основе её UID'а:
    173.         const std::wstring secName = generateSectionName(entry.sectionUid);
    174.  
    175.         // Открываем секцию:
    176.         const HANDLE hSection = OpenFileMappingW(FILE_MAP_READ, false, secName.c_str());
    177.         if (!hSection)
    178.         {
    179.             // Не получилось открыть секцию, пропускаем эту запись:
    180.             continue;
    181.         }
    182.  
    183.         // Отображаем секцию в наш процесс:
    184.         const char* const mapping = static_cast<const char*>(MapViewOfFile(hSection, FILE_MAP_READ, 0, 0, 0));
    185.         if (!mapping)
    186.         {
    187.             // Не получилось отобразить секцию в наш процесс, закрываем секцию и пропускаем эту запись:
    188.             CloseHandle(hSection);
    189.             continue;
    190.         }
    191.  
    192.         // Печатаем строку:
    193.         printf("UID = %u, Owner PID = %u, '%s'\n", entry.sectionUid, entry.ownerPid, mapping);
    194.  
    195.         UnmapViewOfFile(mapping);
    196.         CloseHandle(hSection);
    197.     }
    198.  
    199.     // Снимаем межпроцессную блокировку:
    200.     unlockSharedData();
    201. }
    202.  
    203. BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD /*dwReason*/, LPCONTEXT /*lpContext*/)
    204. {
    205.     return true;
    206. }
    207.  
    SampleDll.h:
    Код (C++):
    1.  
    2. #pragma once
    3.  
    4. extern "C"
    5. __declspec(dllimport)
    6. bool __stdcall initShared();
    7.  
    8. extern "C"
    9. __declspec(dllimport)
    10. void __stdcall deinitShared();
    11.  
    12. extern "C"
    13. __declspec(dllimport)
    14. void __stdcall lockSharedData();
    15.  
    16. extern "C"
    17. __declspec(dllimport)
    18. void __stdcall unlockSharedData();
    19.  
    20. extern "C"
    21. __declspec(dllimport)
    22. bool __stdcall addSharedString(const char* str);
    23.  
    24. extern "C"
    25. __declspec(dllimport)
    26. void __stdcall printStrings();
    27.  
    SampleApp.cpp:
    Код (C++):
    1.  
    2. #define WIN32_LEAN_AND_MEAN
    3. #include <Windows.h>
    4.  
    5. #include "../SampleDll/SampleDll.h"
    6.  
    7. int main()
    8. {
    9.     initShared();
    10.  
    11.     addSharedString("Sample text");
    12.     addSharedString("Yet another text");
    13.     addSharedString("Do some shi~ you kn0w");
    14.  
    15.     printStrings();
    16.  
    17.     return getchar();
    18. }
    19.  
     
  15. Andrey_59

    Andrey_59 Member

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

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

    По твоему коду, вроде бы всё понятно, кроме мьютексов.
     
  16. HoShiMin

    HoShiMin Well-Known Member

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

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

    Мьютекс - это примитив синхронизации, позволяющий упорядочить доступ к разделяемым данным, чтобы в единицу времени с ними мог работать только один поток или процесс.
    Это нужно, чтобы избежать гонок, когда один процесс пишет данные, а другой в этот момент пытается их прочитать. Если такая ситуация возникнет - есть риск, что читатель прочитает недописанные данные. Например, структурку, заполненную лишь наполовину.
    Поэтому все обращения к этим данным мы пускаем через мьютекс: кто первый через него пройдёт - тот и будет работать с данными, а остальные будут ждать, пока поток, захвативший мьютекс, не освободит его.
    Как только мьютекс освобождается - его захватывает следующий поток в очереди (а все остальные продолжают ждать).
     
  17. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Ну, удалить или закомментировать не так уж и сложно...

    Я пока что про один открытый процесс и говорю. Разве DLL-ка не становится частью процесса на который она спроецирована?
    Не понимаю, зачем тогда массив в расшаренной памяти, если им делиться нельзя?! Т.е. если я сохраню хендлы для объектов "проекция файла", то во второй копии их не будет видно, но ведь так не может быть, а что тогда отображается во второй копии в массиве хендло на тех местах на которых в первой копии были сохранены объекты хендлов... Ты же сам писал выше, что адреса проекций разные, но все они ссылаются на один блок памяти(объект "проекция файла"), который и является общим для нескольких процессов, разве не так...? Т.е. я создал объект "проекция файла", который создал "секцию" в памяти, положил его в массив, и он тут же потерял свои свойства, перестал быть видимым для других процессов.

    Вот это поворот, зачем тогда их(HANDLE) вообще сохранять, если можно сохранить только какие-то абстрактные числа?:dash1:

    Я читал и о них и о событиях и о семафорах, но врубиться, как с ними работать так и не смог только критические секции мне оказались близки. Хотя, на данный момент, моё знакомство с этими примитивами было исключительно теоретическим.
     
  18. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Верно, становится. Там было что-то не так с перетасовыванием указателей в сортировке.
    Ты делишься только тем, что у тебя в разделяемой секции. Магии не происходит, система просто отображает содержимое байт в байт, ничего не зная о семантике разделяемых объектов.
    Хэндлы и адреса - это просто некие абстрактные числа, которые имеют смысл только в процессе, который их создал или открыл.
    Например, ты сделал CreateFileMapping, он тебе вернул хэндл, численно равный 1234. Это абстрактное число имеет смысл только в твоём конкретном процессе. Если ты его любым способом передашь в другой процесс (не важно, через общую память, через сокеты, пайпы или как-то ещё) - другой процесс всё равно не сможет его использовать как хэндл, поскольку у каждого процесса своя собственная таблица хэндлов, и хэндлы, валидные в одном процессе, не валидны в других.
    То же самое с адресами: у каждого процесса своё собственное адресное пространство, и если ты передашь адрес строки в другой процесс, он не сможет с ним работать как со своим, ведь в памяти второго процесса буквально нет этого адреса.
    Хэндл не теряет свойств "видимости", у него их не было изначально.
    Когда ты создаёшь секцию в ядре и получаешь хэндл (фактически ссылку на ядерный объект), этот хэндл валиден и имеет смысл только в твоём процессе. Но у секции есть имя, зная которое, другие процессы тоже могут её открыть и тоже получить хэндлы.
    Хэндлы в разных процессах будут разные, но ссылаться будут на одну и ту же секцию (на один и тот же объект в ядре).
    Дальше процессы делают MapViewOfFile для своих хэндлов и получают свои экземпляры отображений, каждый со своим уникальным адресом, но которые тоже ведут на одну и ту же физическую память.
    В своём примере я их сохраняю, чтобы каждый процесс мог закрыть секции, которые он создал - именно поэтому вместе с хэндлами я сохраняю и PID'ы владельцев, чтобы каждый процесс перед завершением работы мог пробежаться по массиву и сказать: "Ага, вот это моя секция, я её могу закрыть, потому что этот хэндл валиден в моём процессе, т.к. его создавал я сам; а это не моя секция, я с ней ничего делать не буду, поскольку этот хэндл в моём процессе невалиден".
    Вот теперь выпал шанс познакомиться с мьютексами на практике.
    Критические секции нельзя разделять между процессами, а мьютексы (винапишные мьютексы, которые создаются через CreateMutex) - можно, поскольку у этих мьютексов можно задать имя, и по имени их может открыть каждый процесс, который хочет через них что-то синхронизировать.
    Также отличие от критических секций в том, что поток, захвативший критическую секцию, может рекурсивно захватить её ещё раз, а с мьютексом так нельзя: попытка рекурсивно зайти в него снова приведёт к самоблокировке - ты повиснешь на нём бесконечно в ожидании, когда мьютекс освободится, а он не освободится никогда, поскольку ты сам его и захватил.
    Using Mutex Objects - Win32 apps | Microsoft Learn
     
    q2e74 нравится это.
  19. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    458
    почему не использовать стандарт - std::mutex?
     
  20. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Нам надо синхронизироваться между потоками разных процессов через общий для них всех мьютекс, а std::mutex разделять нельзя.