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的核心原因有两点:
- DIR结构体的缓存累积:
opendir()创建的DIR结构体内部会维护一个缓冲区,用于批量从磁盘读取目录条目(减少系统调用次数)。遍历大目录时,这个缓冲区会随着读取的条目增多被自动扩容(比如从初始4KB逐步扩大到几MB),且这部分内存会一直被DIR持有,直到closedir()调用。 - 内存碎片问题:在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 } }
正常运行的原因
- 固定的目录遍历内存占用:读取所有路径到
std::vector的过程中,DIR的缓存会一次性扩容到足够容纳所有目录条目的大小,之后不再变化,内存占用固定。 - 更优的内存分配模式:批量处理时,内存碎片的产生模式更稳定。
std::vector的连续内存分配相比边遍历边处理的零散分配,内存利用率更高,减少了碎片累积的影响。 - 内存占用阶段分离:目录读取和图片处理的内存占用峰值不会叠加(读取目录时的内存占用在处理图片时已稳定,不会再增长),避免了内存占用的叠加峰值触发OOM。
优化建议
- 提前释放DIR缓存:在读取完所有目录项后,手动调用
closedir()(无需等到函数退出),及时释放DIR结构体占用的缓存内存。 - 使用内存池:处理图片时使用内存池分配内存,减少频繁分配/释放导致的内存碎片。
- 分批处理:将目录条目分批读取、处理,每处理完一批就关闭目录、释放内存,再重新打开目录继续遍历。
内容的提问来源于stack exchange,提问作者Figura
相关产品推荐
相关产品推荐

