是什么导致互斥对象行为不端

What could cause a mutex to misbehave?

本文关键字:对象 行为不端 是什么      更新时间:2023-10-16

在过去的几个月里,我一直忙于调试一个非常大的专有C++图像处理库中的某个地方引起的罕见崩溃,该库使用GCC 4.7.2为ARM Cortex-A9 Linux目标编译。由于一个常见的症状是油嘴滑舌地抱怨堆损坏,所以第一步是使用堆损坏检查器来捕获oob内存写入。我使用了中描述的技术https://stackoverflow.com/a/17850402/3779334为了将所有对free/malloc的调用转移到我自己的函数,用一定数量的已知数据填充每个分配的内存块,以捕获越界写入,但一无所获,即使在每个分配的块前后填充多达1 KB(由于STL容器的大量使用,分配了数十万个块,所以我不能进一步扩大填充,而且我认为任何超过1KB的越界写入最终都会触发segfault(。这个边界检查器在过去发现了其他问题,所以我不怀疑它的功能。

(在有人说"Valgrind"之前,是的,我也试过,但也没有结果。(

现在,我的内存边界检查器还有一个功能,它在每个分配的块前面都准备了一个数据结构。这些结构都链接在一个长的链表中,以便我偶尔查看所有分配并测试内存完整性。出于某种原因,即使该列表的所有操作都受到互斥体保护,但该列表已损坏。在调查这个问题时,互斥体本身似乎偶尔会失败。这是伪代码:

pthread_mutex_t alloc_mutex;
static bool boolmutex; // set to false during init. volatile has no effect.
void malloc_wrapper() {
  // ...
  pthread_mutex_lock(&alloc_mutex);
  if (boolmutex) {
    printf("mutex misbehavingn");
    __THROW_ERROR__; // this happens!
  }
  boolmutex = true;
  // manipulate linked list here
  boolmutex = false;
  pthread_mutex_unlock(&alloc_mutex);
  // ...
}

偶尔会出现注释为"this occures!"的代码,尽管这似乎是不可能的。我的第一个理论是互斥数据结构被覆盖了。我把互斥锁放在一个结构中,前后都有大数组,但当这个问题发生时,数组没有受到影响,所以似乎什么都没有被覆盖。

所以。。什么样的腐败可能导致这种情况发生,我将如何找到并解决原因?

还有几条注释。测试程序使用3-4个线程进行处理。用更少的线程运行似乎可以减少损坏的发生,但并没有消失。测试每次运行约20秒,在绝大多数情况下都能成功完成(我可以有10个单元重复测试,第一次失败发生在5分钟到几个小时后(。当问题发生时,测试已经很晚了(比如说,15秒(,所以这不是一个糟糕的初始化问题。内存边界检查器从不捕获实际的越界写入,但glibc偶尔仍会因损坏的堆错误而失败(这样的错误会是由oob写入以外的原因引起的吗?(。每次故障都会生成一个包含大量跟踪信息的核心转储;我在这些转储中看不到任何模式,也看不到比其他部分显示得更多的特定代码部分。这个问题似乎非常特定于一个特定的算法家族,在其他算法中不会发生,所以我很确定这不是偶然的硬件或内存错误。我已经做了更多的测试来检查oob堆访问,我不想列出这些访问来阻止这篇文章继续下去。

提前感谢您的帮助!

感谢所有的评论者。当我最终决定写一个简单的内存分配压力测试时,我尝试了几乎所有的建议,但都没有结果。这个测试将在每个CPU内核上运行一个线程(我的单元是Freescale I.MX6四核SoC(,每个内核都以随机顺序高速分配和释放内存。测试在几分钟或几小时内因glibc内存损坏错误而崩溃。

将内核从3.0.35更新到3.0.101解决了这个问题;压力测试和图像处理算法现在都运行了一夜,没有失败。该问题不会在具有相同内核版本的英特尔机器上重现,因此该问题通常特定于ARM,或者可能特定于包含内核3.0.35的特定BSP版本中包含的某些补丁Freescale。

对于那些好奇的人来说,附件是压力测试源代码。将NUM_THREADS设置为CPU核数,并使用构建

<cross-compiler-prefix>g++ -O3 test_heap.cpp -lpthread -o test_heap

我希望这些信息能帮助到别人。干杯:(

// Multithreaded heap stress test. By Itay Chamiel 20151012.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#define NUM_THREADS 4 // set to number of CPU cores
#define ALIVE_INDICATOR NUM_THREADS
// Each thread constantly allocates and frees memory. In each iteration of the infinite loop, decide at random whether to
// allocate or free a block of memory. A list of 500-1000 allocated blocks is maintained by each thread. When memory is allocated
// it is added to this list; when freeing, a random block is selected from this list, freed and removed from the list.
void* thr(void* arg) {
    int* alive_flag = (int*)arg;
    int thread_id = *alive_flag; // this is a number between 0 and (NUM_THREADS-1) given by main()
    int cnt = 0;
    timeval t_pre, t_post;
    gettimeofday(&t_pre, NULL);
    const int ALLOCATE=1, FREE=0;
    const unsigned int MINSIZE=500, MAXSIZE=1000;
    const int MAX_ALLOC=10000;
    char* membufs[MAXSIZE];
    unsigned int membufs_size = 0;
    int num_allocs = 0, num_frees = 0;
    while(1)
    {
        int action;
        // Decide whether to allocate or free a memory block.
        // if we have less than MINSIZE buffers, allocate.
        if (membufs_size < MINSIZE) action = ALLOCATE;
        // if we have MAXSIZE, free.
        else if (membufs_size >= MAXSIZE) action = FREE;
        // else, decide randomly.
        else {
            action = ((rand() & 0x1)? ALLOCATE : FREE);
        }
        if (action == ALLOCATE) {
            // choose size to allocate, from 1 to MAX_ALLOC bytes
            size_t size = (rand() % MAX_ALLOC) + 1;
            // allocate and fill memory
            char* buf = (char*)malloc(size);
            memset(buf, 0x77, size);
            // add buffer to list
            membufs[membufs_size] = buf;
            membufs_size++;
            assert(membufs_size <= MAXSIZE);
            num_allocs++;
        }
        else { // action == FREE
            // choose a random buffer to free
            size_t pos = rand() % membufs_size;
            assert (pos < membufs_size);
            // free and remove from list by replacing entry with last member
            free(membufs[pos]);
            membufs[pos] = membufs[membufs_size-1];
            membufs_size--;
            assert(membufs_size >= 0);
            num_frees++;
        }
        // once in 10 seconds print a status update
        gettimeofday(&t_post, NULL);
        if (t_post.tv_sec - t_pre.tv_sec >= 10) {
            printf("Thread %d [%d] - %d allocs %d frees. Alloced blocks %u.n", thread_id, cnt++, num_allocs, num_frees, membufs_size);
            gettimeofday(&t_pre, NULL);
        }
        // indicate alive to main thread
        *alive_flag = ALIVE_INDICATOR;
    }
    return NULL;
}
int main()
{
    int alive_flag[NUM_THREADS];
    printf("Memory allocation stress test running on %d threads.n", NUM_THREADS);
    // start a thread for each core
    for (int i=0; i<NUM_THREADS; i++) {
        alive_flag[i] = i; // tell each thread its ID.
        pthread_t th;
        int ret = pthread_create(&th, NULL, thr, &alive_flag[i]);
        assert(ret == 0);
    }
    while(1) {
        sleep(10);
        // check that all threads are alive
        bool ok = true;
        for (int i=0; i<NUM_THREADS; i++) {
            if (alive_flag[i] != ALIVE_INDICATOR)
            {
                printf("Thread %d is not respondingn", i);
                ok = false;
            }
        }
        assert(ok);
        for (int i=0; i<NUM_THREADS; i++)
            alive_flag[i] = 0;
    }
    return 0;
}