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

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

  1. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    В описанной тобой схеме, когда один процесс только пишет, а второй только читает, достаточно двух эвентов без мьютексов.
    Одним эвентом писатель сигналит читателю, что данные записаны, и встаёт на ожидание на втором эвенте.
    Читатель встаёт на ожидание первого эвента, и, как только писатель им просигналит, просыпается, читает общую память и сигналит писателю вторым эвентом, мол "я прочитал, можешь писать снова".
    В этом случае синхронизировать дополнительно мьютексами ничего не надо, т.к. ты регулируешь доступ к данным ожиданием на эвентах (пока писатель пишет - читатель ждёт; пока читатель читает - писатель ждёт).

    Но если второй процесс тоже что-то пишет (оба процесса могут читать или писать в произвольные моменты), то синхронизировать надо мьютексом, и под мьютексом каждый процесс должен проверить, появились ли в буфере новые данные, чтобы предварительно их прочесть, и только после этого записывать свою порцию.
    Но именно эта ситуация (когда общий буфер и оба процесса случайно туда что-то пишут и оттуда же читают) - это довольно странная и не очень эффективная архитектура, поскольку можно сделать два независимых буфера и 4 эвента, по два на буфер (как в схеме с одним читателем и одним писателем):
    Процесс1 → Буфер1 → Процесс2
    Процесс1 ← Буфер2 ← Процесс2
     
    Mikl___ нравится это.
  2. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Я это понимаю, я имею ввиду, что mutex и file mapping это объекты ядра и что объект ядра один, а потоков/проекций файла может быть сколько угодно(в пределах разумного разумеется, память не резиновая).

    Как он это делает, что собой представляет объект ядра Mutex, я пытаюсь добиться этого ответа.

    Т.е. что даже с Mutex поток могут прервать в то время, когда он что пишет в общую память? Тогда какой смысл в этом... я понимаю, что windows ОС с вытесняющей многозадачностью и тот факт, что если она не будет передавать управление другому потоку по истечении кванта времени, то смысл многозадачности теряется, но всё же, если данные не будут обработаны должным образом, то это тоже не есть хорошо, не так ли? Или я ошибаюсь по этому поводу или что-то не так понимаю...

    Что за нах...:derisive:, то переключает, то оказывается "призваны обеспечить монопольный доступ к общим данным" и, получается, не отключает поток, пока тот не выполнит свои задачи и не освободит Mutex:) Меня терзают смутные сомнения...

    handle тоже не так прост, из прочитанного мной следует, что он является ссылкой на структуру в ОС в которой много чего содержится, включая указатели на другие данные(или структуры), поправь меня, если я не прав, но, если начать разбираться, то там много чего связано со всякого рода handle, handle gdi, hwnd...

    Это всё же псевдо параллелизм или реальный параллелизм ядра же логические "процессоры", а не физические? И можно ли как-то в обход системы установить один поток на одно ядро, а второй на другое ядро ну и так далее?
    --- Сообщение объединено, 17 янв 2024 ---
    Т.е. процес1 пишет в буфер1, процесс2 ждёт, в момент, когда данные готовы процес2 читает данные из буфера1, а изменённые данные помещает в буфер2, и даёт сигнал о том, что данные обработаны и готовы к чтению процсу1, процес1 читает данные из буфера2 и так до бесконечности. Я правильно понял твою мысль?
     
  3. MaKsIm

    MaKsIm Active Member

    Публикаций:
    0
    Регистрация:
    11 фев 2008
    Сообщения:
    106
    В данном случае резиновая. Т.к. памяти проекции занимают сущие копейки, а вот для доступа к данным проекции файла используются виртуальные адреса. И даже тут - для проекций доступно (257 адресов)×(количество процессов).

    Хотя согласен, что создать проекцию больше чем есть памяти это будет сложно, но даже такую проекцию можно будет спокойно разделить между многими процессами и не один раз. Это из-за страничного механизма формирования виртуального адресного пространства.


    Просто некоторая переменная в ядре, которая записывает id потока её заблокировавшего и, возможно, дополнительные флаги на ветвях, которых поставили на его ожидание.


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


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


    Так и есть. Просто то число, которое вы сохраняете как handle на самом деле это идентификатор для получения доступа к ресурсам. Если можно обойтись только локальной информацией, то он сохранен только в пределах процесса. Но если нельзя, то он дополняется другими структурами на стороне системы. Например, объекты GDI типа кисти в целом не нуждаются в системных структурах, но они тоже создают handle. Хотя объекты типа thread или file существуют и в процессе и в системе т.к. они позволяют получать доступ к общим ресурсам (thread - к процессору, а file - к постоянному хранилищу данных).


    Псевдо параллелизм это многозадачность на одном процессоре с одним ядром. А тут у вас вполне себе физические устройства способные к самостоятельным вычислениям. Да, ядро это часть процессора. Но ядро это тоже физическое устройство. Хотя если говорить о HyperThreading - то там есть и логические ядра, которые удваивают количество параллельно готовых к выполнению потоков, но каждые два потока используют одно физическое ядро (устройство для исполнения команд).
     
    Mikl___ и Andrey_59 нравится это.
  4. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Структурка в ядре _KMUTANT, она же KMUTEX, содержащая список ждунов, владельца и состояние (залочен или нет). Каждый раз, когда вызываешь CreateMutex, ядро создаёт такую структурку и хэндл на неё.
    Могут, но проблемы в этом нет, так как никто другой не сможет начать читать/писать память, пока мьютекс залочен. А разлочится он только когда захвативший его поток доработает с памятью до конца и освободит его.
    Не важно, сколько времени это займёт и сколько раз его прервут - однажды он всё-таки закончит, а все другие всё это время будут просто ждать.
    Ожидание в ядре значит, что планировщик не назначает потоки на исполнение, пока не произойдёт какое-то условие.
    Отключает. Планировщик ничего не знает про залоченные мьютексы (если потоки не ждут на них). Потоки прерываются как обычно.
    Наверно ты имел в виду не в обход системы, а принудительно установить, независимо от того, что хотел бы планировщик: можно функцией SetThreadAffinityMask.
    Да, правильно, в итоге буферы не пересекаются, каждый процесс пишет в один, читает из другого. Это позволит читать и писать одновременно, как дуплексный режим в радиостанциях.
     
    Mikl___ и Andrey_59 нравится это.
  5. MaKsIm

    MaKsIm Active Member

    Публикаций:
    0
    Регистрация:
    11 фев 2008
    Сообщения:
    106
    Когда один поток вызывает WaitForSingleObject(hMutex, ...) для свободного hMutex, то он выполняет его блокировку и квант времени для этого потока не прерывается. Но другой поток, вызвавший WaitForSingleObject(hMutex, ...) для блокированного hMutex, вернет системе обратно часть кванта времени этого потока и планировщик перераспределяет его на свободный поток. Такой остановленный поток сможет получить свой квант времени только после освобождения объекта синхронизации. И даже тут объект синхронизации не освободится и после возобновления потока будет вновь захвачен (что может породить проблемы, если на нескольких ядрах будет возобновлены потоки блокированные одним освободившимся MUTEX), а просто изменит владельца (id потока его захватившего) сразу на уровне ядра до переключения на выполнение этого потока. Таким образом будет предотвращено возобновление более одного ожидающего потока.
     
    Mikl___ и Andrey_59 нравится это.
  6. Andrey_59

    Andrey_59 Member

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


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

    Становиться яснее.
    Да, именно это я и имел ввиду, но Рихтер пишет, что это влечёт за собой определённые проблемы, нужно опять же разбираться в неких тонкостях, но не могу точно вспомнить, что он имел ввиду. А так да это и хотел узнать, хотелось бы уточнить, но сейчас это не суть.
     
  7. MaKsIm

    MaKsIm Active Member

    Публикаций:
    0
    Регистрация:
    11 фев 2008
    Сообщения:
    106
    Ну тут тоже хитрая ситуация. Вот небольшой пример. У вас есть данные длиной 256 Мб, которые должны быть размещены в общей области памяти. Для описания занимаемых страниц памяти будут использованы блоки либо по 4 Кб (мало вероятно) или по 2 Мб. Таких блоков понадобится 128 штук и каждый будет занимать 8 байт (итого 1 Кб). Таким образом, чтобы разместить этот блок в 256 Мб в адресном пространстве процесса, надо дописать в специальные структуры 1 Кб данных. Если надо разместить этот блок еще раз, то надо дописать еще 1 Кб данных к процессу.

    А что будет, если блок будет длиной в 255 Мб. Тогда будет записано 127 блоков по 8 байт для описания 254 Мб и еще 1 Мб будет описан блоками по 4 Кб (256 штук по 8 байт). Тогда для размещения маппинга в адресном пространстве процесса надо будет использовать уже 3 Кб. А для повторного размещения еще 1 Кб (т.к. блоки описания по 4 Кб могут быть использованы повторно).

    А что будет если блок будет занимать 1536 Мб. Тогда будет использован еще больший объём описываемой памяти в 1 Гб и 512 Мб будут описываться блоками по 2 Мб (256 шт * 8 = 2 Кб). Но тогда для размещения понадобится еще 8 б + 2Кб = 2056. Для повторного размещения понадобится еще 2056 байт.

    Вот как-то так это работает. Подробнее про страничный механизм лучше читать в документации к процессору (Indel Software Developer Manual или AMD x64 Architecture Manual).

    Коротко об этом рассказать сложно. Но в целом для каждого процесса составляется структура описания его адресного пространства расширяемая по мере необходимости. Эта структура представляет из себя выравненные на 4 Кб массивы физических указателей на блоки описания (тоже длиной 4 Кб такие же массивы указателей) или блоки памяти + эти указатели в массивах смешаны с некоторым набором флагов для управления работой процессора (из-за этих флагов возможно часть памяти ограничить на доступ к записи или исполнению и еще некоторые функции). Всего уровней вложенности в этой структуре может быть от 2 до 5. Каждый уровень описания индексируется битами из виртуального адреса. Для x64 скорее всего используется 5 уровневая структура и в каждом уровне по 9 бит виртуального адресного пространства обращаются к следующему уровню.

    6666666555555555444444444333333333222222222111111111000000000000 - биты виртуального адреса
    первые 12 = 0 - это смещение на физической странице в 4 Кб (они не расшифровываются)
    далее идут группы по 9 бит. Группы с 1 по 5 (если нет флагов указывающих обратное) описывают смещение 8 байтового указателя предыдущего уровня на физической странице (т.е. 4 Кб / 8 = 512 описателей - как раз 0 .. 511 умещается в 9 бит без остатка). Группы 1 и 2 могут быть тоже как и 0 смещением на более длиной странице (размером 2 Мб и 1 Гб соответственно, зависит от флагов в описании на уровне выше: для 2 на уровне 3 и для 1 на уровне 2). Группа 6 принимает значение старшего бита из группы 5 (т.е. адресное пространство теперь описывается знаковыми 64-битными числами, а разрыв в адресном пространстве начинается выше 55 бита).

    Таким образом, чтобы описать все 257 адресного пространства надо 4 Кб для 5 уровня, 512 * 4 Кб для 4 уровня и 512 * 512 * 4 Кб для 3 уровня = 2Гб + 2Мб + 4 Кб. Но где вы видели компьютер с таким объемом оперативной памяти (257). Для описания же, скажем 128 Гб понадобится 4 Кб для 5 уровня + 4 Кб для 4 уровня + 4 Кб для 3 уровня = 12 Кб. Для описания адресного пространства рядового процесса как правило используется около 128-512 Кб (т.к. в адресном пространстве много прерывистых кусочков с различным доступом).
     
    Andrey_59 и Mikl___ нравится это.
  8. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Дополню MaKsIm.
    Виртуальные адреса транслируются в физические через механизмы страничной трансляции. Виртуальный адрес имеет смысл только в контексте процесса, у каждого процесса виртуальные адреса свои.
    Каждый выделенный виртуальный адрес имеет сопоставление на конкретную физическую страничку в оперативке. "Выделение памяти" означает, что мы резервируем некоторый диапазон виртуальных адресов и сопоставляем их с физической памятью в RAM.
    Чтобы превратить виртуальные адреса в физические, процессор транслирует их через набор таблиц трансляций: PML5, PML4, PDPT, PD и PT.
    Виртуальный адрес фактически представляет собой набор индексов в каждой из этих таблиц.
    В зависимости от режимов процессора, виртуальный адрес может смотреть на странички в 4Кб (самые распространённые, мы всегда работаем с ними), в 2Мб (Large page), в 4Мб (тоже Large Page для x32 без PAE) и в 1Гб (Huge page).
    В x64 используется четырёхуровневая трансляция, начиная с PML4. Для серверных машин начинает появляться поддержка пятиуровневой трансляции через PML5.
    Здесь можно посмотреть схемки всех возможных режимов трансляций: https://github.com/HoShiMin/Arch/blob/main/Arch/include/Arch/x86/Pte.h
    А детально это описано в спецификация от Intel и AMD. Например, в Intel Software Developer Manual, Volume 3, Chapter 4 Paging.
     
    Последнее редактирование: 18 янв 2024
    Andrey_59 и Mikl___ нравится это.
  9. Andrey_59

    Andrey_59 Member

    Публикаций:
    0
    Регистрация:
    20 фев 2021
    Сообщения:
    81
    Согласен, пожалуй будет лучше, если я буду обращаться для уточнений тех мест, которые будут непонятны. Посмотрел я некоторые таблицы из intel software developer manual и как-то мир сразу померк) там сам чёрт ногу сломит.

    Короче говоря тут ещё нужно найти то, за что зацепиться, чтобы начать разматывать эти дебри. И да, я на буржуинском читать не умею, так что здесь добавляется ещё одна проблема..., увы и ах.
     
  10. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    458
    Кто и где использует пейджинг с 1 ГБ страницами?
     
  11. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
    Предполагается, что их будут использовать там, где требуются огромные объёмы памяти.
    Например, если софту нужно несколько сотен гигабайт большими блоками, разумно выделять большими страницами. Или поверх этих страниц организовать аллокатор.
    То же самое можно делать и обычными страницами, но к большим быстрее доступ (т.к. процессору надо меньше уровней трансляций, чтобы получить физический адрес) и меньше накладные расходы (не надо выделять сами таблицы трансляций для маленьких страниц).
    На практике ни разу не видел использования даже 2Мб-страниц, не говоря уже о гигабайтных, но скорей всего, их юзают где-то в узкоспециализированном вычислительном софте на каких-нибудь мейнфреймах с терабайтами оперативки.
     
  12. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    458
    Хм хм
    с теребайтами оперативки говорите...
    допускаю такое только в клаудах причем не в простой EC2 для бедных а в дорогих типав инстансов, но
    какой же надо чипсет чтоб он поддерживал пару терабайт РАМ-а?
     
  13. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.460
    Адрес:
    Россия, Нижний Новгород
  14. alex_dz

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    458
    во дела! фотка 8 летней давности
    счас уже можно купить 1 плашку памяти 256 ГБ
    получается итого 48 слотов * 256 = 12 Террабайт
    йоксель моксель!
    --- Сообщение объединено, 22 янв 2024 ---
    Нашел еще более крутой сервак
    https://www.supermicro.com/en/products/motherboard/x13qeh+

    64 слота по 256 ГБ =========> 16 Террабайт памяти

    тут уж можно и по 1 ГБ пейджинг завести!
     
  15. HoShiMin

    HoShiMin Well-Known Member

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

    alex_dz Active Member

    Публикаций:
    0
    Регистрация:
    26 июл 2006
    Сообщения:
    458
    Пишут что модная венда уже тоже научилась (стырила у Линукса)
    upload_2024-1-25_20-21-22.png