LINUX.ORG.RU

Доступ к разделяемой памяти из различных процессов

 , ,


1

4

Снова извиняюсь за нубский вопрос. У меня есть несколько программ, взаимодействие между которыми я собираюсь наладить через разделяемую память. В каждой программе я вначале вставляю следующую процедуру инициализации

sem_t *sem;
byte * sharedaddr;

int initshared()
{
  if ( (shm = shm_open("SHARED", O_CREAT|O_RDWR, 0777)) == -1 ) {
        ;
        return 1;
  }
  sharedaddr = mmap(0, 100, PROT_WRITE|PROT_READ, MAP_SHARED, shm, 0);
  
  if ( sharedaddr == (char*)-1 ) {
        
        return 1;
    }
  else return 0;
}
Достаточно ли этого, чтобы при запуске нескольких программ изменения в области sharedaddr, проделываемые каждой из них синхронно производились бы в аналогичных областях каждой из запущенных? И насколько быстро?


Изменения будут видны в других процессах, но без гарантии немедленного отображения. Нужна доп.синхронизация, чтобы читать данные сразу после записи другим процессом.

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

клиент-серверная архитектура

anonymous
()

У меня немного по другому (работает уже более 10 лет) связь между процессами - константа SHM_KEY)


#include <sys/ipc.h>
#include <sys/shm.h>

struct SHMEM_INFO *shmem = NULL;
static int shm_fd;

//========================================
int sh_mem_attach( void )
{
  shm_fd = shmget( SHM_KEY, SHM_SIZE, IPC_CREAT | 0666 );

  if( shm_fd < 0 ) return( 1 );

  shmem = (struct SHMEM_INFO *) shmat( shm_fd, NULL, 0 );

  if( shmem == (struct SHMEM_INFO *) -1 ) return( 1 );

  return( 0 );
}
//========================================
void sh_mem_kill( void )
{
  shmdt( (char *) shmem );
  shmctl( shm_fd, IPC_RMID, NULL );
}
//========================================

sigurd ★★★★★
()

Это ж классика из Стивенса. Во-первых, отсутствует задание размера сегмента общей памяти. Где-то после вызова shm_open должен быть ftruncate. Тут возможна гонка — ftruncate вызовут сразу два процесса. Опасно ли это — надо разбираться. После вызова mmap надо создать в общей памяти какие-то примитивы синхронизации. Здесь тоже возможна гонка и с ней тоже надо разбираться.

iliyap ★★★★★
()
Ответ на: комментарий от iliyap

Во-первых, отсутствует задание размера сегмента общей памяти. Где-то после вызова shm_open должен быть ftruncate.

А разве не достаточно в mmap задать размер?

Тут возможна гонка — ftruncate вызовут сразу два процесса.

Ну, если функция инициализации вызывается один раз при загрузке процесса, попасть на одновременную инициализацию практически нереально - или я чего то недопонимаю?

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

Вот этот момент тоже поподробнее. Я сперва так понял, что буквально выделяется кусок реальной памяти, к которой идет одновременный доступ из нескольких процессов - тогда изменения должны срабатывать мгновенно, а иначе вообще зачем было огород городить?

MBK
() автор топика
Ответ на: комментарий от MBK

попасть на одновременную инициализацию практически нереально - или я чего то недопонимаю?

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

программирование параллельных процессов - это не теория вероятностей «упадёт - не упадёт». все обращения к общей памяти обязательно синхронизируются.

Ну так я и собираюсь синхронизировать через разделяемую память. Но, получается, что еще и обращения к самой памяти как то синхронизировать надо?

MBK
() автор топика
Ответ на: комментарий от MBK

разделяемая память - всего лишь мап куска памяти на несколько процессов. всю синхронизацию ты должен делать сам.
читай про синхронизацию в pthread. для быстрого просвещения, хотя бы тут посмотри пример:
http://stackoverflow.com/questions/2584678/how-do-i-synchronize-access-to-sha...
а то ты очень близок к написанию опасного и падучего говнокода.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от leave

Но почему нельзя сделать просто кусок физической памяти, в который процессы смогут писать и читать одновременно? Ладно, как я уже понял из ответов, «SHARED» в моем примере это некий виртуальный файл, навроде семафора, который обновляется непонятно когда по запросу, но если, например, атрибут O_FIXED поставить - это будет действительно кусок физической памяти с одновременным отображением или опять какой то непонятный костыль?

MBK
() автор топика
Ответ на: комментарий от Sorcerer

Работа процессоров с памятью устроена сложнее, чем вы, возможно, себе представляете.

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

MBK
() автор топика
Ответ на: комментарий от MBK

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

Вы, наверное, хотите сказать, потоки? Если да, то так же «прекрасно» можно и в разделяемой памяти работать.

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

В смысле, не совсем понял, что значит одинаково, то есть они действительно будут работать с выделенным куском разделяемой памяти как с одним и тем же и изменения вносимые одним процессом мгновенно будет видны другим?

MBK
() автор топика
Ответ на: комментарий от MBK

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

roof ★★
()
Ответ на: комментарий от roof

Разные потоки одного процесса работают с глобальными переменными совсем не прекрасно.

Можно поподробнее об этом? У меня, тьфу-тьфу-тьфу до этого потоки синхронизировались по глобальным переменным, вроде, проблем не было. Захотел подобное сделать с процессами, попробовал через семафоры, получилось громоздко, глючно и медленно, решил переделать на раздеяемую память - а тут выходит, что нельзя.

MBK
() автор топика
Ответ на: комментарий от MBK

Может, я неправильно понимаю что вы имеете в виду, говоря «синхронизировались по глобальным переменным»? Что-то вроде

#include <thread>
#include <iostream>

using namespace std;

int i;

int main()
{
  thread t1 = thread([](){ i = 1; });
  thread t2 = thread([](){ cout << i << endl;});
  t1.join();
  t2.join();
  return 0;
}

?

roof ★★
()
Последнее исправление: roof (всего исправлений: 2)
Ответ на: комментарий от leave

https://en.wikipedia.org/wiki/Race_condition#Example

Я понимаю, но мне нужно примерно такое:


bool camLock=true;

static void *camthreadFunc(void *arg)
{ 
  while (true)
  {
   if (!camLock)
   {
// как только в соседнем потоке флаг camLock установлен эта ветка блокируется 	
   }

   }
  return NULL;
}

static void *mainthreadFunc(void *arg)
{ 
  ...
   camLock=true; // блокируем второй поток
  ...
   camLock=false; // разблокируем второй поток
  ...
}


При таком подходе конфликтов и гонок не бывает и срабатывает мгновенно. Хочется так же просто и быстро сделать из другого процесса - получится?
MBK
() автор топика
Ответ на: комментарий от MBK

Как это не бывает конфликтов и гонок? Даже оставляя за скобками всякие синхронизации кешей, у вас тут конфликт на конфликте и гонкой погоняет. =) Если вас устраивает результат работы кода, который вы привели выше, то и разделяемую память смело используйте без всяких синхронизаций, хуже-то уже некуда :)

roof ★★
()
Ответ на: комментарий от roof

Но где тут конфликт то? При выставлении флага в true следующий цикл в первом потоке мгновенно блокируется, при false - мгновенно разблокируется - все элементарно, какие могут быть подводные камни? Допустим, флаг camLock находится в разделяемой памяти - он будет так же мгновенно реагировать или возможны нюансы?

MBK
() автор топика
Ответ на: комментарий от MBK

Первый очевидный подводный камень - блокирующий поток не ждет следующего цикла в блокируемом, он сразу начинает что-то делать. Т.е. возможен такой сценарий:

  1. Пишущий поток начал что-то писать в буфер. Записал, скажем, половину. Тут его квант времени закончился, и он отправился в очередь ждать следующего.
  2. Второй поток выставил флаг и начал радостно из буфера читать. Прочитал буфер, записанный наполовину, сбросил флаг обратно и радостный пошел что-то с прочитанным мусором делать. Но тут его квант времени тоже кончился.
  3. Пишущий поток проснулся, дописал в буфер, проверил флаг, он уже сброшен читающим потоком обратно, поэтому для пишущего потока его состояние не изменилось. Он, пока время есть, еще раз наполовину успел буфер обновить. Тут опять квант кончился. И.т.д.
roof ★★
()
Последнее исправление: roof (всего исправлений: 1)
Ответ на: комментарий от roof

Если вас это не смущает, то вот хороший цикл статей о том, как работает память в современных системах: http://scrutator.me/post/2015/05/16/memory_barriers.aspx Если и это для вас не аргумент, тогда, как я уже говорил, смело делайте флаг в разделяемой памяти, оно даже иногда (а может и почти всегда) будет работать так, как вы ожидаете, т.е. действительно как будто все потоки всех процессов исполняются одновременно и все видят изменения памяти мгновенно и именно в том порядке, в котором они происходили.

roof ★★
()
Ответ на: комментарий от roof

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

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

MBK
() автор топика
Ответ на: комментарий от MBK

я проверяю, что цикл докрутился до конца другим флагом.

А пока ты проверял он опять начался.

Vovanano
()
Ответ на: комментарий от MBK

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

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

И, если не секрет, зачем два флага, когда достаточно одного mutex? Без него ваш код выглядит так, как будто вы вообще про синхронизацию не слышали никогда %)

roof ★★
()
Ответ на: комментарий от roof

изменения попадают в кеши процессоров не мгновенно

Во всяком случае, достаточно быстрее, чем очередной цикл опроса до конца докрутится

И, если не секрет, зачем два флага, когда достаточно одного mutex? Без него ваш код выглядит так, как будто вы вообще про синхронизацию не слышали никогда %)

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

MBK
() автор топика
Ответ на: комментарий от MBK

Во всяком случае, достаточно быстрее, чем очередной цикл опроса до конца докрутится.

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

я, повторяю, в линуксе нуб

Линукс тут практически ни при чем, эта проблема возникнет на любой современной ОС и архитектуре, если вам нужно взаимодействие между потоками.

хочется обходиться какими то простыми низкоуровневыми средствами

Примитивы синхронизации и есть такие простые, низкоуровневые средства :)

проверять на существование, открывать, закрывать

ну, shared memory же тоже надо, почему тут это вас не смущает? :)

roof ★★
()
Ответ на: комментарий от roof

ну, shared memory же тоже надо, почему тут это вас не смущает? :)

Снова я недопонимаю. Я так понимаю, при работе с shared memory достаточно просто при запуске каждого процесса адрес сегмента получить и уже внутри всего процесса работать с этим адресом как с обычным указателем, или нет? А в случае семафоров их каждый раз открывать надо, вдобавок, непонятно как они вообще реализованы, если через файл, как я подозреваю, то получается разница во времени между чтением из файла и просто указателя на память.Где я туплю?

MBK
() автор топика
Ответ на: комментарий от MBK

файлы тоже каждый раз открывать нужно — давай не будем файлами пользоваться

семафоры не реализованы через файл, а просто работа с именованными семафорами похожа на работу с файлом, да они работают через системные вызовы, и если это медленно, то значит просто этот способ синхронизации не подходит, и надо использовать другие, например, spin-lock

anonymous
()
Ответ на: комментарий от anonymous

файлы тоже каждый раз открывать нужно — давай не будем файлами пользоваться

Если есть возможность перезавать значения через пямять - то наверное, ж прямее делать так, чем через запись-чтение в файл?

и если это медленно, то значит просто этот способ синхронизации не подходит, и надо использовать другие, например, spin-lock

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

MBK
() автор топика
Ответ на: комментарий от roof

Мьютекс тоже достаточно инициализировать один раз, а потом только блокировать и разблокировать

К мьютексу тоже доступ наверняка внутри сложнее, чем к простому адресу. Да и мьютексы бинарны, а мне значениями обмениваться надо

MBK
() автор топика
Ответ на: комментарий от MBK

Если есть возможность перезавать значения через пямять - то наверное, ж прямее делать так, чем через запись-чтение в файл?

ты какой-то неадекват; я тебе говорю про открытие/закрытие, а ты — про запись через файл; семафоры и мьютексы работают через память, ни в какой файл никто не пишет/читает

Да не то что медленнее, просто на мой чайниковский взгляд, работа с прямыми указателями на память прямее, проще и быстрее, чем непрерывная возня с открытием-закрытием семафоров

тебе еще раз говорят: с семафорами и мьютексами работают точно так как с памятью — один раз готовят/инициализируют, потом много раз опрашивают/ждут/пишут, потом один раз закрывают/удаляют

anonymous
()
Ответ на: комментарий от MBK

К мьютексу тоже доступ наверняка внутри сложнее, чем к простому адресу. Да и мьютексы бинарны, а мне значениями обмениваться надо

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

anonymous
()
Ответ на: комментарий от MBK

Размер сегмента надо задать. mmap не меняет размер сегмента, он лишь отображает часть сегмента в адресное пространство процесса.

Если к сегменту памяти цепляются только родственные процессы (один предок и несколько его потомков, сделанные вызовом fork), то можно инициализировать сегмент до создания потомков, и гонки не будет. Если же процессы не родственные, например, рожденные запуском одной и той же программы из шелла несколько раз, гонка возможна. Для первого варианта вообще именованный сегмент не нужен, можно использовать анонимный. Дочерние процессы и так унаследуют и файловый дескриптор сегмента (если fork вызван после shm_open), и маппинг (если fork вызван после mmap).

Обращаться к общей памяти из нескольких потоков или процессов без синхронизации — это безобразие. Но тебя это похоже не смущает.

iliyap ★★★★★
()
Ответ на: комментарий от anonymous

ты какой-то неадекват; я тебе говорю про открытие/закрытие, а ты — про запись через файл; семафоры и мьютексы работают через память, ни в какой файл никто не пишет/читает

Нифига, я вполне обучаем, ok, через память, тем лучше ;)

один раз готовят/инициализируют, потом много раз опрашивают/ждут/пишут

Ага, значит я снова поначалу недопонял - т.е. семафор тоже совсем необязательно открывать-закрывать для синхронизации, достаточно при запуске процесса сделать sem = sem_open(SEM_NAME, O_RDWR, 0777, 1) а потом в течение всего процесса обращаться к ним на чтение и запись sem_getvalue(sem, &var) и sem_init(sem,1,val) как к обычным переменным, изменения синхронизируются мгновенно? Но ведь, все рано медленнее получается чем в случае с разделяемой памятью, в чем преимущество то? Или семафорами сильно надежнее?

MBK
() автор топика
Ответ на: комментарий от MBK

изменения синхронизируются мгновенно?

следующий получатель ресурса получит его в том состоянии, в котором его освободил предыдущий владелец ресурса, если все правильно сделать

Но ведь, все рано медленнее получается чем в случае с разделяемой памятью, в чем преимущество то? Или семафорами сильно надежнее?

семафоры/мьютексы — это просто стандартные механизмы синхронизации, которые уже реализованы и отлажены (сложный системный код написали бородатые программисты и там ошибок нет), а ты, конечно, можешь это все повторить руками, но зачем?

anonymous
()
Ответ на: комментарий от MBK

Обращение к мьютексу - системный вызов. Да, он сложнее, но и работа, которую он выполняет, непроста. В любом случае, это экономия на спичках, большинству программистов до задач, когда блокировки начинают мешать производительности и/или масштабируемости, никогда не дорасти. Ну, это ИМХО.

мьютексы бинарны

Мьютекс нужен для синхронизации доступа к ресурсам, в том числе он содержит full memory barrier, т.е. захватил мьютекс и пиши/читай разделяемый ресурс (переменную, shared memory и т.д.) в свое удовольствие. Закончил - отпусти мьютекс, другие смогут с этим ресурсом работать.

roof ★★
()
Ответ на: комментарий от MBK

Вся инфа у вас сейчас есть. На все ваши вопросы ответы в топике даны, на некоторые - по нескольку раз. То, что вы называете «подводными камнями» - очевидные для любого, кто хоть немного в теме, проблемы многопоточного окружения.

roof ★★
()
Ответ на: комментарий от roof

Да, спасибо, буду разбираться и экспериментировать. Я и есть тот самый бородатый программист старой закалки, который не любит сторонние библиотеки и готовые решения, а предпочитает низкоуровневый хардкор и танцы на граблях :-D))

MBK
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.