【问题标题】:Linked list with a memory leak存在内存泄漏的链表
【发布时间】:2019-12-18 06:14:42
【问题描述】:

我正在努力解决这个问题。

如果我在 Linux 中使用 top 命令检查我的应用程序,我会发现 VIRT 始终相同(运行几天),而 RES 正在增加一点(在 4 字节和 32 字节之间)手术后。我每 60 分钟执行一次手术。

一个操作包括通过 SPI 读取一些帧,将它们添加到几个链表中,然后在另一个线程中提取它们。

我使用以下选项执行 Valgrind:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -v ./application

一旦我关闭它(如果一切顺利,我的应用程序将永远运行)我检查泄漏,除了我没有关闭的线程之外我什么也看不到。仅可能在线程中丢失字节。绝对没有,也没有间接。

我不知道这是否正常。我过去曾使用 Valgrind 来发现一些漏洞,它一直运行良好,我很确定它现在也运行良好,但我无法解释 RES 问题。

我已经检查了链表,看看我是否留下了一些没有空闲的节点,但对我来说,这似乎是正确的。

我在一个数组中有 32 个链表。我这样做是为了使推送/弹出操作更容易,而无需 32 个单独的列表。我不知道这是否会导致问题。如果是这种情况,我将它们分开:

typedef struct PR_LL_M_node {
    uint8_t                 val[60];
    struct PR_LL_M_node     *next;
} PR_LL_M_node_t;

pthread_mutex_t         PR_LL_M_lock[32];
PR_LL_M_node_t          *PR_LL_M_head[32];
uint16_t                PR_LL_M_counter[32];

int16_t LL_M_InitLinkedList(uint8_t LL_M_number) {
    if (pthread_mutex_init(&PR_LL_M_lock[LL_M_number], NULL) != 0) {
        printf("Mutex LL M %d init failed\n", LL_M_number);
        return -1;
    }
    PR_LL_M_ready[LL_M_number]      = 0;
    PR_LL_M_counter[LL_M_number]    = 0;
    PR_LL_M_head[LL_M_number]       = NULL;
    pthread_mutex_unlock(&PR_LL_M_lock[LL_M_number]);

    return PR_LL_M_counter[LL_M_number];
}

int16_t LL_M_Push(uint8_t LL_M_number, uint8_t *LL_M_frame, uint16_t LL_M_size) {

    pthread_mutex_lock(&PR_LL_M_lock[LL_M_number]);
    PR_LL_M_node_t *current = PR_LL_M_head[LL_M_number];
    if (current != NULL) {
        while (current->next != NULL) {
            current = current->next;
        }

        /* now we can add a new variable */
        current->next = malloc(sizeof(PR_LL_M_node_t));
        memset(current->next->val, 0x00, 60);                                                                                
        /* Clean buffer before using it */
        memcpy(current->next->val, LL_M_frame, LL_M_size);
        current->next->next = NULL;
    } else {
        PR_LL_M_head[LL_M_number] = malloc(sizeof(PR_LL_M_node_t));
        memcpy(PR_LL_M_head[LL_M_number]->val, LL_M_frame, LL_M_size);
        PR_LL_M_head[LL_M_number]->next = NULL;
    }
    PR_LL_M_counter[LL_M_number]++;
    pthread_mutex_unlock(&PR_LL_M_lock[LL_M_number]);

    return PR_LL_M_counter[LL_M_number];
}

int16_t LL_M_Pop(uint8_t LL_M_number, uint8_t *LL_M_frame) {
    PR_LL_M_node_t  *next_node  = NULL;

    pthread_mutex_lock(&PR_LL_M_lock[LL_M_number]);
    if ((PR_LL_M_head[LL_M_number] == NULL)) {
       pthread_mutex_unlock(&PR_LL_M_lock[LL_M_number]);
        return -1;
    }

    if ((PR_LL_M_counter[LL_M_number] == 0)) {
        pthread_mutex_unlock(&PR_LL_M_lock[LL_M_number]);
        return -1;
    }

    next_node = PR_LL_M_head[LL_M_number]->next;
    memcpy(LL_M_frame, PR_LL_M_head[LL_M_number]->val, 60);
    free(PR_LL_M_head[LL_M_number]);
    PR_LL_M_counter[LL_M_number]--;
    PR_LL_M_head[LL_M_number] = next_node;

    pthread_mutex_unlock(&PR_LL_M_lock[LL_M_number]);

    return PR_LL_M_counter[LL_M_number];
}

这样我传递了我要管理的链表的编号并对其进行操作。你怎么看? RES 是一个真正的问题吗?我认为它可能与应用程序的其他部分有关,但我已经注释掉了大部分,如果使用 push/pop 操作,它总是会发生。如果我离开推送/弹出,RES 会保留它的号码。

当我提取值时,我使用do/while,直到我得到-1 作为弹出操作的响应。

【问题讨论】:

  • 在 init 函数中调用 pthread_mutex_unlock 会导致未定义的行为,因为此时互斥锁未锁定。
  • 我删除了编辑。我将删除解锁并再次测试
  • 尚不清楚问题出在哪里,但return PR_LL_M_counter[LL_M_number]; 访问了临界区之外的共享资源,这可能导致竞争条件和 UB。
  • @n.m 我的问题是,在推送一些数据并弹出它们之后,我在 RES(顶部)中的值比以前更高。我是否也应该传递一个变量来读取关键部分内的该参数?
  • 我在这里没有发现问题。 RES 是调入的内存量。您的操作系统决定何时调入或调出什么。 VIRT是内存总量,如果不改变,就没有内存泄漏。

标签: c memory-leaks linked-list


【解决方案1】:

您的观察似乎表明没有问题:

  • VIRTRES 以 KiB(1024 字节为单位)表示。根据您系统上虚拟内存的工作方式,这些数字应始终是页面大小的倍数,最有可能是 4KiB。
  • RES 是常驻内存量,即在给定时间为您的程序实际映射的 RAM 量。
  • 如果程序一次进入睡眠状态 60 分钟,系统 (Linux) 可能会确定它的某些页面适合丢弃或交换,以便为其他进程映射内存。 RES 会相应减少。顺便提一下,top 就是这样一个需要记忆的过程,因此在观察过程时会干扰你的过程,它是海森堡原理的一种计算变体。
  • 当进程唤醒时,正在运行的线程访问的任何内存都将映射回内存,无论是从可执行文件(如果它被丢弃)或从交换文件,或者从无处(如果丢弃的页面是全部)空字节或堆栈的未使用部分。这会导致RES 再次增加。

代码中可能存在其他问题,尤其是在您没有发布的代码中,但如果VIRT 没有更改,则您似乎没有内存泄漏。

【讨论】:

  • 谢谢。我很确定还有其他一些问题。我仍在调试它,但我担心 RES 值。我不知道你解释了什么。非常详细。我将执行其他用户在这部分中建议的更改,然后我将继续工作。
  • @Biribu:我建议阅读man top 页面。此实用程序有很好的文档记录,并提供了许多有用的选项。
猜你喜欢
  • 2017-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-13
  • 2020-05-11
  • 2019-07-11
  • 2012-12-13
  • 2021-05-19
相关资源
最近更新 更多