通过atomic_t共享内存实现IPC;它适合x86吗?

IPC through shared memory with atomic_t; is it good for x86?

本文关键字:x86 IPC atomic 共享 实现 内存 通过      更新时间:2023-10-16

我有以下代码通过共享内存进行进程间通信。一个进程写日志,另一个进程读日志。一种方法是使用信号量,但是这里我使用的是类型为atomic_t的原子标志(log_flag),它位于共享内存中。日志(log_data)也是共享的。

现在的问题是,这是否适用于x86架构,或者我是否需要信号量或互斥体?如果我使log_flag非原子的呢?鉴于x86具有严格的内存模型和主动缓存一致性,并且没有对指针应用优化,我认为它仍然有效?

EDIT:请注意,我有一个8核的多核处理器,所以我在这里没有任何忙碌等待的问题!

// Process 1 calls this function
void write_log( void * data, size_t size )
{
    while( *log_flag )
           ;
    memcpy( log_data, data, size );
    *log_flag = 1;
}
// Process 2 calls this function
void read_log( void * data, size_t size )
{
    while( !( *log_flag ) )
       ;
    memcpy( data, log_data, size );
    *log_flag = 0;
}

您可能希望在循环中使用以下宏,以避免对内存总线造成压力:

#if defined(__x86_64) || defined(__i386)
#define cpu_relax() __asm__("pause":::"memory")
#else
#define cpu_relax() __asm__("":::"memory")
#endif

同时,它作为一个内存屏障("memory"参数),所以不需要将log_flag声明为volatile

但我认为这是过度的,它应该只用于硬实时的东西。你用futex应该没问题。也许您可以简单地使用管道,它对于几乎所有用途来说都足够快。

我不推荐这样做,原因有两个:首先,虽然编译器可能没有优化指针访问,但这并不意味着指向的值不会被处理器缓存。其次,它是原子的这一事实不会阻止在while循环的末尾和执行*log_flag=0的行之间的读访问。互斥锁更安全,尽管速度要慢得多。

如果你使用pthreads,考虑使用RW互斥锁来保护整个缓冲区,这样你就不需要一个标志来控制它,互斥锁本身就是一个标志,当你频繁读取时,你会有更好的性能。

我也不建议使用空的while()循环,那样会占用所有的处理器空间。在循环中放置一个ussleep(1000),给处理器一个喘息的机会。

应该使用信号量而不是依赖标志的原因有很多。

  1. 你的读日志while循环不必要地旋转。这会不必要地消耗系统资源,比如电力。这也意味着CPU不能用于其他任务。
  2. 如果x86完全保证读写顺序,我会感到惊讶。输入数据可以将日志标志设置为1,而输出数据可以将其设置为0。这可能意味着你最终会丢失数据。
  3. 我不知道你从哪里得到的优化不应用于指针作为一般用途。优化可以应用于任何与外部变化没有区别的地方。编译器可能不知道log_flag可以被并发进程改变。

问题2可能很可能很少出现,并且很难追踪到问题。所以帮你自己一个忙,使用正确的操作系统原语。他们会保证事情按预期进行。

只要log_flag是原子,你就会没事的。

如果log_flag只是一个普通bool,你不能保证它会工作。

编译器可以重新排序你的指令

*log_flag = 1;
memcpy( log_data, data, size );

在单处理器系统上,只要log_flag不在memcpy中被访问,这在语义上是相同的。您唯一的可取之处可能是一个较差的优化器,它不能推断出memcpy中访问了哪些变量。

cpu可以重新排序你的指令
它可以选择在循环之前加载log_flag来优化管道。

缓存可能会重新排序内存写入。
包含log_flag的缓存行可能会在包含data的缓存行之前同步到其他处理器。

你需要的是一种告诉编译器、cpu和缓存"放手"的方法,这样它们就不会对顺序做出假设。这只能通过记忆栅栏来实现。std::atomicstd::mutex和信号量都在它们的代码中嵌入了正确的内存栅栏指令。