You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

readdir()是否会引发OOM?Linux嵌入式目录遍历异常排查

低内存环境(25MB)下目录遍历与OOM问题分析

背景

运行在仅25MB内存的Linux嵌入式芯片上,目录内存储单张约3MB的图片,程序需遍历目录读取并处理图片,但处理到约4000张时触发OOM(内存不足)错误。以下是三次尝试的情况及问题解析:


第一次尝试:scandir()遍历

代码实现

void MyFunc(const std::string &fileDir)
{
    struct dirent **entry_list;
    int32_t count = scandir(fileDir.c_str(), &entry_list, nullptr, alphasort);
    if (count == -1) {
        printf("open dir:%s failed.\n", fileDir.c_str());
        return;
    }

    for (int32_t i = 0; i < count; i++) {
        struct dirent *ptr = entry_list[i];
        if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0) {
            continue;
        }

        // read image
        // do process
        // release image
    }
}

问题原因

scandir()会一次性遍历整个目录,将所有目录项的dirent结构体分配到堆内存中,同时会调用alphasort对所有条目排序。当目录包含3万+图片时,所有条目占用的内存(包括结构体本身和文件名)加上排序过程的临时内存,会直接耗尽25MB系统内存,导致崩溃。


第二次尝试:readdir()边遍历边处理

代码实现

void MyFunc(const std::string &fileDir)
{
    DIR *dir = opendir(fileDir.c_str());
    if (dir == nullptr) {
        printf("open dir:%s failed.\n", fileDir.c_str());
        return;
    }
    ScopeGuard dirGuard([&] { (void)closedir(dir); }); //RAII

    struct dirent *entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        // read image, the same scandir version
        // do process, the same scandir version
        // release image, the same scandir version
    }
}

问题解析

readdir()确实是逐个返回目录项,但仍触发OOM的核心原因有两点:

  1. DIR结构体的缓存累积:opendir()创建的DIR结构体内部会维护一个缓冲区,用于批量从磁盘读取目录条目(减少系统调用次数)。遍历大目录时,这个缓冲区会随着读取的条目增多被自动扩容(比如从初始4KB逐步扩大到几MB),且这部分内存会一直被DIR持有,直到closedir()调用。
  2. 内存碎片问题:在25MB的小内存环境中,反复分配/释放3MB的图片内存会产生大量内存碎片。当碎片累积到一定程度,后续无法分配到连续的3MB内存块,直接触发OOM。4000次的分配释放循环后,碎片已足够导致分配失败。

第三次尝试:先存路径再批量处理

代码实现

void MyFunc(const std::string &fileDir)
{
    DIR *dir = opendir(fileDir.c_str());
    if (dir == nullptr) {
        printf("open dir:%s failed.\n", fileDir.c_str());
        return;
    }
    ScopeGuard dirGuard([&] { (void)closedir(dir); }); //RAII

    std::vector<std::string> tmp;
    struct dirent *entry;
    while ((entry = readdir(dir)) != nullptr) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        tmp.emplace_back(fileDir + "/" + entry->d_name);
    }
    printf("process img total %zu.\n", tmp.size());

    for (const auto& fileName: tmp) {
        // read image, the same scandir version
        // do process, the same scandir version
        // release image, the same scandir version
    }
}

正常运行的原因

  1. 固定的目录遍历内存占用:读取所有路径到std::vector的过程中,DIR的缓存会一次性扩容到足够容纳所有目录条目的大小,之后不再变化,内存占用固定。
  2. 更优的内存分配模式:批量处理时,内存碎片的产生模式更稳定。std::vector的连续内存分配相比边遍历边处理的零散分配,内存利用率更高,减少了碎片累积的影响。
  3. 内存占用阶段分离:目录读取和图片处理的内存占用峰值不会叠加(读取目录时的内存占用在处理图片时已稳定,不会再增长),避免了内存占用的叠加峰值触发OOM。

优化建议

  1. 提前释放DIR缓存:在读取完所有目录项后,手动调用closedir()(无需等到函数退出),及时释放DIR结构体占用的缓存内存。
  2. 使用内存池:处理图片时使用内存池分配内存,减少频繁分配/释放导致的内存碎片。
  3. 分批处理:将目录条目分批读取、处理,每处理完一批就关闭目录、释放内存,再重新打开目录继续遍历。

内容的提问来源于stack exchange,提问作者Figura

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 16:05:16