使用memcpy与mmap多线程复制目录文件时输出仅1Byte问题求助
问题产生原因
- 文件名被意外篡改:代码中
strcat(filename, "copy")直接修改了指向源文件名的指针,后续打开源文件时实际尝试打开的是拼接后的目标文件名,此时目标文件还未创建,源文件打开失败,fstat读取到的文件大小为无效值。 - 目录项缓冲区复用问题:
readdir返回的struct dirent内存是C库内部复用的,直接将d_name指针传给子线程时,主循环后续的readdir调用会覆盖原有内容,子线程拿到的文件名大概率是错误的。 - 无错误检查逻辑:
open、fstat等系统调用失败后没有做拦截,后续逻辑基于无效的文件描述符执行,获取到的文件大小异常,最终写入后只有你主动填充的1字节内容,和你遇到的现象完全吻合。 - 未过滤特殊目录项:
readdir会返回.和..两个特殊目录,尝试打开目录作为普通文件读取必然失败。 - 缓冲区溢出风险:直接向
d_name指向的固定大小缓冲区拼接字符串,会触发缓冲区溢出,属于未定义行为,可能引发各种奇怪问题。 - 路径拼接缺失:
opendir打开的是你传入的指定路径的目录,直接打开d_name只会在当前工作目录查找文件,若运行程序的目录和你要处理的目录不一致,会直接找不到文件。 - 资源泄漏:打开的源文件描述符未关闭,
mmap映射的内存未释放,会逐渐耗尽系统资源,多线程运行时尤其明显。
修复方案
- 单独分配内存存储源文件完整路径和目标文件名,不修改原始
d_name内容。 - 主循环中过滤
.和..特殊目录项,仅处理普通文件。 - 拼接目录路径和文件名,保证能正确找到目标目录下的文件。
- 为每个线程独立拷贝文件名,避免
readdir缓冲区复用导致的内容覆盖。 - 增加所有系统调用的错误检查,异常时直接打印错误并返回。
- 补充资源释放逻辑,关闭所有打开的文件描述符,释放
mmap映射和动态分配的内存。 - 调整目标文件名生成逻辑,单独拼接不修改源文件名变量。
修复后完整代码
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <dirent.h> #include <pthread.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/mman.h> #include <string.h> #include <errno.h> void* copyFile(void* arg) { char *srcpath = (char*)arg; // 单独分配目标文件名内存,不修改源路径 int path_len = strlen(srcpath); char *destpath = malloc(path_len + 10); // 留足够空间存.copy后缀 if (!destpath) { perror("malloc destpath failed"); free(srcpath); return NULL; } snprintf(destpath, path_len + 10, "%s.copy", srcpath); // 打开源文件,增加错误检查 int in = open(srcpath, O_RDONLY); if (in < 0) { perror("open src file failed"); free(srcpath); free(destpath); return NULL; } // 打开目标文件 int out = open(destpath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (out < 0) { perror("open dest file failed"); close(in); free(srcpath); free(destpath); return NULL; } // 获取源文件大小 struct stat statbuf; if (fstat(in, &statbuf) < 0) { perror("fstat failed"); close(in); close(out); free(srcpath); free(destpath); return NULL; } off_t insize = statbuf.st_size; // 空文件不需要后续处理 if (insize == 0) { close(in); close(out); free(srcpath); free(destpath); return NULL; } // 扩展目标文件大小 if (lseek(out, insize - 1, SEEK_SET) < 0) { perror("lseek failed"); close(in); close(out); free(srcpath); free(destpath); return NULL; } if (write(out, "", 1) < 0) { perror("write failed"); close(in); close(out); free(srcpath); free(destpath); return NULL; } // 映射源文件 char* inmap = mmap(0, insize, PROT_READ, MAP_FILE | MAP_PRIVATE, in, 0); if (inmap == MAP_FAILED) { perror("mmap infile failed"); close(in); close(out); free(srcpath); free(destpath); return NULL; } // 映射目标文件 char* outmap = mmap(0, insize, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, out, 0); if (outmap == MAP_FAILED) { perror("mmap outfile failed"); munmap(inmap, insize); close(in); close(out); free(srcpath); free(destpath); return NULL; } // 拷贝内容 memcpy(outmap, inmap, insize); // 释放资源 munmap(inmap, insize); munmap(outmap, insize); close(in); close(out); free(srcpath); free(destpath); return NULL; } int main(int argc, char *argv[]) { if (argc != 2) { printf("usage: %s <target_dir>\n", argv[0]); return 1; } DIR* d = opendir(argv[1]); if (!d) { perror("opendir failed"); return 1; } int i = 0; pthread_t threads[50]; struct dirent* e; while((e = readdir(d)) != NULL) { // 过滤.和..,只处理普通文件 if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0 || e->d_type != DT_REG) { continue; } // 拼接完整路径,避免当前工作目录不对找不到文件 int dir_len = strlen(argv[1]); int name_len = strlen(e->d_name); char *srcpath = malloc(dir_len + name_len + 2); // 留位置给/和结束符 if (!srcpath) { perror("malloc srcpath failed"); continue; } snprintf(srcpath, dir_len + name_len + 2, "%s/%s", argv[1], e->d_name); printf("processing %s\n", srcpath); // 传入单独分配的路径,避免缓冲区复用问题 if (pthread_create(&threads[i], NULL, copyFile, srcpath) != 0) { perror("pthread_create failed"); free(srcpath); continue; } i++; // 避免超过线程数组上限 if (i >= 50) break; } for(int j = 0; j < i; j++) { pthread_join(threads[j], NULL); } closedir(d); return 0; }
内容的提问来源于stack exchange,提问作者Shiloh
相关产品推荐
相关产品推荐

