简体   繁体   English

如何在共享内存的同一区域上工作的两个进程之间共享锁?

[英]How do I share locks between two processes working on the same region of shared memory?

I'm wondering how to do this (using C++98). 我想知道如何做到这一点(使用C ++ 98)。 Here's my scenario: I have process A and process B. Process A allocates a large buffer in shared memory and splits it into a fixed number of chunks. 这是我的场景:我有进程A和进程B。进程A在共享内存中分配一个大缓冲区,并将其拆分为固定数量的块。 It then uses a series of structs like these to represent each chunk: 然后,它使用一系列类似的结构来表示每个块:

struct Chunk
{
    Lock lock; //wrapper for pthread_attr_t and pthread_mutex_t
    char* offset; //address of the beginning of this chunk in the shared memory buffer
};

The Lock when constructed does this: 构造锁时,它会执行以下操作:

pthread_mutexattr_init(&attrs);
pthread_mutexattr_setpshared(&attrs, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&lock,&attrs); //lock is pthread_mutex_t and attrs is pthread_mutexattr_t

the lock method when called does this: 调用lock方法时会执行以下操作:

pthread_mutex_lock(&lock);

It uses placement new when creating the above "Chunk"s into the beginning of the shared memory buffer like this: 在共享内存缓冲区的开头创建上述“块”时,它将使用new放置,如下所示:

char* mem; //pointer to the shared memory
Chunks[i] = new (mem) Chunk; //for i = {1..num chunks}
mem += sizeof(Chunk);

Then it assigns offsets and proceeds to write into the rest of the buffer through it's lifetime. 然后,它分配偏移量,并在其生命周期内继续写入缓冲区的其余部分。 Each time it is writing in the chunk corresponding to one of the aboves it grabs the chunks lock and releases it when done. 每次它写入与上述其中之一相对应的块时,它都会抓住块锁并在完成后释放它。

Now process B comes up and maps the same shared memory buffer into memory and attempts to retrieve the chunks like this: 现在,进程B出现并将相同的共享内存缓冲区映射到内存中,并尝试像这样检索块:

Chunk** chunks = reinterpret_cast<Chunk**)(mem); //mem being the pointer into the shared memory

then it tries to operate on the shared memory by scanning around the different chunks and also trying to make use of the lock if needed. 然后它尝试通过扫描不同的块并在需要时尝试利用锁来对共享内存进行操作。

I am getting strange crashes when I run this in which the chunks** is garbage and am wondering if the Lock would work across the processes as well or if there are any other caveats I'm ignoring in the simple steps above? 当我在其中运行大块**的垃圾时,我遇到了奇怪的崩溃,并且想知道Lock是否也可以在整个进程中正常工作,或者在上述简单步骤中是否有我忽略的其他警告? Is having the SHARED pthread attr enough or do I need to use an entirely different breed of lock? 拥有共享的pthread attr是否足够?还是需要使用一种完全不同的锁?

When you pull a region of shared memory into a process, it generally will NOT be located at the same virtual address as in other processes that access the shared memory. 当您将共享内存区域拉入进程时,通常不会将其与访问共享内存的其他进程位于相同的虚拟地址。 So you can't just store the raw pointers into the shared memory and expect them to work meaningfully. 因此,您不能只将原始指针存储到共享内存中并期望它们有意义地工作。

So in your case, even though the Chunk is in shared memory, the offset pointer within each chunk is not meaningful in any other process. 因此,在您的情况下,即使Chunk位于共享内存中,每个块内的offset指针在任何其他进程中也没有意义。

One solution is to use offsets from the beginning of the shared memory chunk 一种解决方案是使用共享内存块开头的偏移量

struct Chunk {
    pthread_mutex_t  lock;
    size_t           offset;
};

char *base; // base address of shared memory
char *mem;  // end of in-use shared memory

Chunk *chunks = reinterpret_cast<Chunk *>(mem);  // pointer to array in shared memory
for (int i = 0; i < num_chunks; i++) {
    // initialize the chunks
    new(mem) Chunk;
    mem += sizeof(Chunk); }
// set the offsets to point at some memory
for (int i = 0; i < num_chunks; i++) {
    chunks[i].offset = mem - base;
    mem += CHUNK_SIZE; // how much memory to allocate for each chunk?
}

Now in B you can just do 现在在B中,您可以

Chunk *chunks = reinterpret_cast<Chunk *>(base);

but in either process, to access the data of a chunk, you need base + chunks[i].offset 但是在任一过程中,要访问块的数据,您都需要base + chunks[i].offset

Alternately, you can use a package that manages the shared memory allocation for you and ensures that it gets mapped at the same address in every process . 或者,您可以使用一个包来管理您的共享内存分配,并确保在每个进程中将其映射到相同的地址

In addition to my comments I also provide a very basic offset_ptr implementation. 除了我的评论外,我还提供了一个非常基本的offset_ptr实现。 I agree that making a simple project dependent on something like boost may be an overkill but even until reaching a certain project size where you decide to switch to a more serious set of libraries it is worth wrapping critical things like offset pointers. 我同意,使一个简单的项目依赖于诸如boost之类的东西可能是一个过大的杀伤力,但是即使在达到某个特定项目规模之前,您决定切换到更严格的库集,也值得包装诸如偏移指针之类的关键内容。 Wrapping helps you to centralize your offset handler code and also to guard yourself with asserts. 包装可以帮助您集中化偏移量处理程序代码,并使用断言保护自己。 A simple offset_ptr template that doesn't handle all cases can still be much better than hand-coded offset+pointer manipulator code that is copy pasted everywhere and its basic implementation without attempting to be comprehensive is: 一个不能处理所有情况的简单offset_ptr模板仍然比手动编码的offset + pointer操纵器代码要好得多,后者可以粘贴到各处,并且其基本实现无需尝试全面:

template <typename T, typename OffsetInt=ptrdiff_t>
class offset_ptr
{
    template <typename U, typename OI> friend class offset_ptr;
public:
    offset_ptr() : m_Offset(0) {}
    offset_ptr(T* p)
    {
        set_ptr(p);
    }
    offset_ptr(offset_ptr& other)
    {
        set_ptr(other.get_ptr());
    }
    template <typename U, typename OI>
    offset_ptr(offset_ptr<U,OI>& other)
    {
        set_ptr(static_cast<T*>(other.get_ptr()));
    }
    offset_ptr& operator=(T* p)
    {
        set_ptr(p);
        return *this;
    }
    offset_ptr& operator=(offset_ptr& other)
    {
        set_ptr(other.get_ptr());
        return *this;
    }
    template <typename U, typename OI>
    offset_ptr& operator=(offset_ptr<U,OI>& other)
    {
        set_ptr(static_cast<T*>(other.get_ptr()));
        return *this;
    }
    T* operator->()
    {
        assert(m_Offset);
        return get_ptr();
    }
    const T* operator->() const
    {
        assert(m_Offset);
        return get_ptr();
    }
    T& operator*()
    {
        assert(m_Offset);
        return *get_ptr();
    }
    const T& operator*() const
    {
        assert(m_Offset);
        return *get_ptr();
    }
    operator T* ()
    {
        return get_ptr();
    }
    operator const T* () const
    {
        return get_ptr();
    }

private:
    void set_ptr(const T* p)
    {
        m_Offset = p ? OffsetInt((char*)p - (char*)this) : OffsetInt(0);
    }
    T* get_ptr() const
    {
        return m_Offset ? (T*)((char*)this + m_Offset) : (T*)nullptr;
    }

private:
    OffsetInt m_Offset;
};

offset_ptr<int> p;
int x = 5;

struct TestStruct
{
    int member;
    void func()
    {
        printf("%s(%d)\n", __FUNCTION__, member);
    }
};

TestStruct ts;
offset_ptr<TestStruct> pts;

int main()
{
    p = &x;
    *p = 6;
    printf("%d\n", x);

    ts.member = 11;
    if (!pts)
        printf("pts is null\n");
    pts = &ts;
    if (pts)
        pts->func();
    pts = nullptr;
    if (!pts)
        printf("pts is null again\n");

    // this will cause an assert because pts is null
    pts->func();
    return 0;
}

It may contain some operator functions that are pain in the ass to write if you are not used to implement pointer stuff but this is really simple compared to a full fledged pointer implementation of a complex lib like boost, isn't it? 它可能包含一些操作员函数,如果您不习惯于实现指针的话,会很费劲,但是与像boost这样的复杂lib的完整指针实现相比,这真的很简单,不是吗? And it makes the pointer (offset) manipulator code much nicer! 而且它使指针(偏移)操纵器代码变得更好! There is no excuse for not using at least a wrapper like this! 没有任何理由不使用至少一个这样的包装器!

Wrapping the locks and other stuff is also fruitful even if you do it for yourself without external libs because after writing/using the locks with bare native api for 2-3-4 times it truns out that your code will look much nicer if you use a simple wrapper with a ctor/destructor/lock()/unlock() not to mention the centralized lock/unlock code that can be guared with asserts and occasionally you can put in debug info to the lock class (like the id of the last locker thread to debug deadlocks easier...). 即使没有外部库就自己做,也可以包装锁和其他东西,因为在使用裸本机api编写/使用锁2至3-4次后,它会断掉,如果您使用,代码看起来会更好一个带有ctor / destructor / lock()/ unlock()的简单包装器,更不用说可以由断言保证的集中式锁定/解锁代码,有时您可以将调试信息放入锁定类(例如最后一个的id)锁线程更容易调试死锁...)。

So don't omit wrappers even if you are rolling your code without std or boost. 因此,即使您在没有std或boost的情况下滚动代码,也不要忽略包装器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何保护两个进程之间共享 memory 中的字符串? - How do I protect a character string in shared memory between two processes? Ubuntu在两个进程之间共享内存,代码不起作用 - Ubuntu Shared Memory Between 2 Processes, Code Not Working 进程之间不共享共享内存 - shared memory is not shared between processes 可以增加不同CPU上的进程之间的共享内存 - Can boost do shared memory between processes that are on different CPUs 如何使用运行时大小参数构造 boost spsc_queue 以使用共享内存在两个进程之间交换 cv::Mat 对象? - How to construct boost spsc_queue with runtime size parameter to exchange cv::Mat objects between two processes using shared memory? C ++和Java进程之间的共享内存 - Shared memory between C++ and Java processes 内核和用户模式之间的共享内存。 如何分享处理? - Shared memory between kernel and user mode. How to share handle? 如果两个不同的进程使用 mmap 到 map 相同的文件区域(一个使用 MAP_SHARED,另一个使用 MAP_PRIVATE),会发生什么情况? - What happens if two distinct processes use mmap to map the same region of file (one with MAP_SHARED, another with MAP_PRIVATE)? 在两个进程(C和C ++)之间提升Boost的managed_shared_memory用法 - Boost's managed_shared_memory usage in between two processes (C and C++) 尝试写入 memory 映射文件时出现问题,在两个进程之间共享(32 位 -> 64 位) - Issue when trying to write to memory mapped file, shared between two processes (32 bit -> 64 bit)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM