2014-04-01 58 views
0

我想知道如何做到这一点(使用C++ 98)。这里是我的场景:我有进程A和进程B.进程A在共享内存中分配一个大缓冲区,并将其分成固定数量的块。然后,它使用了一系列这样的结构来表示每个组块:如何在共享内存的同一区域上工作的两个进程之间共享锁定?

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 
}; 

的锁定构造时执行此:

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 

称为当锁定方法做到这一点:

pthread_mutex_lock(&lock); 

当创建上述“块”到像这样的共享内存缓冲区的开始时,它使用放置新:

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

然后它分配偏移并继续写入缓冲区的剩余部分。每次在与上述其中一个对应的块中写入时,它会抓取块锁并在完成时释放它。

现在进程B出现,并且在同一共享存储器缓冲区映射到存储器和试图检索这样的数据块:

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

然后它试图通过扫描周围的不同的块上的共享存储器操作和如果需要也试图使用锁。

我遇到奇怪的崩溃时,我运行这个块**是垃圾,我想知道如果锁将工作跨过程,以及如果有任何其他的警告我忽略在上面的简单步骤?有足够的SHARED pthread属性,还是需要使用完全不同的锁品种?

+0

“它试图通过扫描不同的块来操作共享内存,并在需要时尝试使用锁。”你需要展示你如何使用锁,因为你可能做错了。 – msw

+0

我在锁上增加了细节 –

+0

请注意,您不需要为每个锁创建单独的'pthread_mutexattr_t' - 您可以创建一个attr(一次)并重新使用它来初始化每个锁。初始化之后,锁不会引用attr(所以你不需要保留它); attr只是一个方便的方法,用于批量创建锁时读取的很多选项,而不是提供'pthread_mutex_init'几十个参数。 –

回答

0

将共享内存区域拉入进程时,它通常不会位于与访问共享内存的其他进程相同的虚拟地址。所以你不能只将原始指针存储到共享内存中,并期望它们有效地工作。

因此,在您的情况下,即使Chunk在共享内存中,每个块中的offset指针在任何其他进程中都没有意义。

一种解决方案是使用偏移量从共享内存块

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? 
} 

现在B的开始,你可以做

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

但在任何过程中,要访问的数据大块,您需要base + chunks[i].offset

或者,您可以使用package that manages the shared memory allocation for you and ensures that it gets mapped at the same address in every process

+0

我试图存储实际结构(通过将它们分配到新的位置),然后重新解释转换,无论它在另一个进程中被映射为指向该类型结构的指针。 –

+1

@PalaceChan问题是,看起来你正在共享内存区域存储指针(比如'Chunk :: offset')。在共享内存区域中,应该只存储相对于共享内存区域内的某些偏移量(例如共享内存的开始处)的偏移量。虽然我不是一个推广粉丝,但有一套针对IPC和共享内存管理的boost命名空间/类,例如boost :: interprocess和即使在共享内存区域也可以使用的指针(因为它将指针存储为相对偏移量)是'boost :: interprocess :: offset_ptr'。 – pasztorpisti

+0

@pasztorpisti哦,我看到了,是的,我认为我只是将偏移量更改为相对ptrdiff_t,然后我不想为类似上面那样的简单东西拉动升压库。感谢您指出了这一点。 –

0

除了我的意见,我还提供了一个非常基本的offset_ptr实现。我同意做一个简单的项目依赖于像boost这样的东西可能是一种矫枉过正的做法,但即使在达到某个项目大小时,如果您决定切换到更严重的库集合,它也值得包装像偏移指针这样的重要事情。包装可以帮助您集中您的偏移量处理程序代码,并保护自己的断言。不能处理所有的情况下,简单offset_ptr模板仍然可以大大优于手工编码的被到处复制粘贴偏移+指针操纵代码和它的基本实现,而不试图完整是:

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; 
} 

它可能包含一些运算符函数,如果你不习惯实现指针的话,那么这些函数会很麻烦,但是与一个像boost这样的复杂的lib完全成熟的指针实现相比,这非常简单,不是吗?它使指针(偏移量)操纵器代码更好!没有理由不使用至少这样的包装!

即使你自己没有外部库,包装锁和其他东西也是富有成效的,因为在用2-3-4次的时间写入/使用原生api的锁之后,你的代码看起来会很多如果你使用简单的ctor/destructor/lock()/ unlock()封装,更不用说可以用断言保护的集中式锁定/解锁代码,偶尔你可以将调试信息放入锁类中最后一个锁定器线程的ID来调试死锁更容易...)。

所以即使你没有使用std或boost来编码,也不要忽略包装。

+0

这是非常酷的感谢,不幸的是,因为我怀疑我的pthreads在锁定调用时出现了segfaulting ..其余的块状态在gdb中看起来都很好,两个进程甚至gdb中的锁结构看起来都很相似。 –

+0

@PalaceChan仔细检查你的指针。你原来的文章包含了一些完全错误的代码,例如:'chunk ** chunks = reinterpret_cast pasztorpisti

相关问题