【问题标题】:Copying files in a directory using memcpy and mmap forcing all files to be 1Byte使用 memcpy 和 mmap 复制目录中的文件,强制所有文件为 1Byte
【发布时间】:2021-12-17 03:48:26
【问题描述】:
#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>

void* copyFile(void* arg) {

    char *filename = ((char*)arg);

    char *destname = strcat(filename, "copy");

    int in = 0;
    in = open(filename, O_RDONLY);

    int out = 0;
    out = open(destname, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

    struct stat statbuf;
    fstat(in, &statbuf);   

    off_t insize = statbuf.st_size;
    lseek(out, insize - 1, SEEK_SET);
    ssize_t wrote = write(out, "", 1);

    char* inmap = mmap(0, insize, PROT_READ, MAP_FILE | MAP_PRIVATE, in, 0);

    char* outmap = mmap(0, insize, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, out, 0);

    memcpy(outmap, inmap, insize);

    close(out);
}


int main(int argc, char *argv[]) {

  DIR* d;
  struct dirent* e;

  d = opendir(argv[1]);

  int num_entries = 0;

  int i = 0;

  pthread_t threads[50];

  while((e = readdir(d)) != NULL) {

    char *filename = e->d_name;

    printf("%s\n", filename);

    pthread_create(&threads[i], NULL, copyFile, filename);    

    i++;
  }

  for(int j = 0; j < i; j++) {

    pthread_join(threads[j], NULL);

  }

}

您好,我正在尝试使用 mmap 和 memcpy 使用多线程复制整个文件目录。我有复制目录中文件的代码,但是它强制所有复制的文件大小为 1 字节。我很好奇为什么会发生这种情况以及如何解决这个错误。

我是 C 的新手,所以请原谅我明显的语义错误。感谢您的帮助!

【问题讨论】:

  • 您是否需要执行“先阅读”然后“再阅读”之类的操作?当您从中读取条目时,您是否也在修改目录?
  • 我的印象是 while 循环正在遍历目录流,因此不需要“先读”或“下一个读”。此外,据我所知,我不会修改目录。
  • readdir 返回一个指向可以静态分配的结构的指针。这意味着您可能会将相同的 filename 值传递给 copyFile 的所有线程实例。
  • @G.M.例如,如果我们有 3 个文件 f1 f2 f3 根据您提出的错误,我们是否会期望输出是 f1copy 的 3 个实例?如果是这样的话,不幸的是,这不是我所经历的,名字都是正确的(尽管我希望我只是理解你的解释是错误的,而你是正确的!)。
  • strcat 修改它传递的字符串。所以如果filename"foo",那么char *destname = strcat(filename, "copy"); 导致filenamefoocopydestname 是指向同一字符串的另一个指针。无论如何,filename 是指向由readdir 返回的缓冲区的指针,您不应该修改它(尤其是因为被其他线程持有)。您需要创建一个临时缓冲区来保存destname

标签: c multithreading


【解决方案1】:

有很多错误。

  1. main 中,filename 将指向所有文件的相同 地址。我们必须提供一个唯一地址/副本。一种方法是使用strdup。并且,我们应该在copyFile 的底部添加一个free 调用来释放strdup 分配的存储空间。

  2. 复制时我们必须跳过...。否则,代码段错误。

  3. 对输入文件和副本使用相同的目录可能会导致相当于无限循环。也就是说,给定一个复制到xyzcopy 的文件xyzreaddir 可能能够看到xyzcopy 作为输入并尝试创建xyzcopycopy。 最好使用单独的输出目录。那将需要进行重大改写。但是,更简单的解决方法是跳过任何包含 copy 的文件。

  4. 程序将[尝试]复制目录条目,即使它们不是普通文件(例如,它会尝试在目录上执行open)。我们应该检查文件类型并跳过非文件类型。

  5. copyFile 中,即使在main 中使用strdup,字符串filename 确实没有有足够的空间来容纳"copy"strcat。我们需要一个单独的缓冲区来存放输出文件名。

  6. lseek/write 在输出文件的末尾添加了一个多余的二进制零。这是为了扩展输出文件。最好使用ftruncate

  7. 这是因为将writemmap 混合可能会出现问题,并且通常不是最佳做法。在您的用例中,它可能没问题(因为write 是在mmap 之前完成),但如果我们麻烦使用mmap ,最好使用mmap缓冲区指针和memcpy

  8. copyFile 缺少 return。而且,main 还缺少一个 return

  9. copyFile 中的in 没有close

  10. openmmapopendir 调用没有错误检查。

  11. close 调用之前应该有munmap 调用。


错误已修复:

  1. 程序限制目录可以有多少条目。

  2. 使用pthread_t threads[50];,我们可以在目录中有 50 个条目。 没有检查这个数组是否溢出。

  3. 并行处理所有个文件没有实际意义。这缩放。

  4. 如果目录中有(例如)1,000,000 个条目,我们可能会耗尽资源。我们可能会用完未使用的进程槽,并且pthread_create 将在达到系统范围限制后开始失败。我们可能会用完文件描述符,并且在打开大约 1024 个文件后,open 调用将开始失败。

  5. 在创建了一定数量的线程(例如,实际上是 4-5 个)后,复制过程实际上会更慢,因为系统会花费大部分时间在线程之间切换而不是做有用的工作。

  6. 对于小文件,线程创建/拆除的开销相对于线程的运行时间变得很重要。

最好有一个有限的“工作”线程“池”,并将pthread_create 调用与pthread_join 调用穿插,在线程完成时回收/重用现在未使用/空闲的“槽”。

一种更有效的方法是创建线程池(通过pthread_create 在开始时调用一次。每个线程都有一个邮箱/队列要做。main 可以将条目排入线程队列,当它看到线程空闲时添加一个新条目。pthread_join 调用可以在最后完成一次


以下是更正后的代码。

我使用预处理器条件来表示旧代码与新代码(例如):

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

无论如何,这是更正后的代码。我已经用 cmets 注释了错误/修复:

#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>

void *
copyFile(void *arg)
{

// NOTE/BUG: no cast is needed from a void *
#if 0
    char *filename = ((char *) arg);
#else
    char *filename = arg;
#endif

// NOTE/BUG: there is _not_ enough space in filename to add "copy" -- this is
// UB (undefined behavior)
#if 0
    char *destname = strcat(filename, "copy");
#else
    char destname[1024];
    strcpy(destname,filename);
    strcat(destname,"copy");
#endif

    int in = 0;

    in = open(filename, O_RDONLY);

// NOTE/FIX: check for error
#if 1
    if (in < 0) {
        perror(filename);
        exit(1);
    }
#endif

    int out = 0;

    out = open(destname, O_RDWR | O_CREAT | O_TRUNC,
        S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

// NOTE/FIX: check for error
#if 1
    if (out < 0) {
        perror(destname);
        exit(1);
    }
#endif

    struct stat statbuf;

    fstat(in, &statbuf);

    off_t insize = statbuf.st_size;

// NOTE/BUG: this will add a binary zero at the end of the output file
#if 0
    lseek(out, insize - 1, SEEK_SET);
    ssize_t wrote = write(out, "", 1);
#else
    ftruncate(out,insize);
#endif

    char *inmap = mmap(0, insize, PROT_READ, MAP_FILE | MAP_PRIVATE, in, 0);

// NOTE/FIX: check for error
#if 1
    if (inmap == MAP_FAILED) {
        perror("mmap/in");
        exit(1);
    }
#endif

    char *outmap = mmap(0, insize, PROT_READ | PROT_WRITE,
        MAP_FILE | MAP_SHARED, out, 0);

// NOTE/FIX: check for error
#if 1
    if (inmap == MAP_FAILED) {
        perror("mmap/in");
        exit(1);
    }
#endif

    memcpy(outmap, inmap, insize);

// NOTE/FIX: we should unamp the output area
#if 1
    munmap(outmap,insize);
#endif

    close(out);

// NOTE/FIX: we should unmap and close the input descriptor
#if 1
    munmap(inmap,insize);
    close(in);
#endif

// NOTE/FIX: free the string allocated by strdup in main
#if 1
    free(filename);
#endif

// NOTE/FIX: needs return value -- would be flagged by compiler if compiled
// with -Wall
#if 1
    return (void *) 0;
#endif
}

int
main(int argc, char *argv[])
{

    DIR *d;
    struct dirent *e;

// NOTE/BUG: no checks for missing argument or bad directory
#if 0
    d = opendir(argv[1]);
#else
    if (argv[1] == NULL) {
        fprintf(stderr,"missing argument\n");
        exit(1);
    }
    d = opendir(argv[1]);
    if (d == NULL) {
        perror(argv[1]);
        exit(1);
    }
#endif

// NOTE/BUG: unused variable
#if 0
    int num_entries = 0;
#endif

    int i = 0;

    pthread_t threads[50];

    while ((e = readdir(d)) != NULL) {
// NOTE/FIX: must skip "." and ".."
#if 1
        if (strcmp(e->d_name,".") == 0)
            continue;
        if (strcmp(e->d_name,"..") == 0)
            continue;
#endif

// NOTE/FIX: only copy simple files (i.e. do _not_ copy subdirectories, etc.)
#if 1
        if (e->d_type != DT_REG)
            continue;
#endif

// NOTE/FIX: skip "copy" files
#if 1
        if (strstr(e->d_name,"copy") != NULL)
            continue;
#endif

// NOTE/BUG: this will present the _same_ memory address to all threads so there
// is a "race condition"
#if 0
        char *filename = e->d_name;
#else
        char *filename = strdup(e->d_name);
#endif

        printf("%s\n", filename);

        pthread_create(&threads[i], NULL, copyFile, filename);

        i++;
    }

    for (int j = 0; j < i; j++) {
        pthread_join(threads[j], NULL);
    }

// NOTE/FIX: needs return
#if 1
    return 0;
#endif
}

【讨论】:

  • 感谢您非常详细的回答!这有效!
猜你喜欢
  • 1970-01-01
  • 2015-01-03
  • 1970-01-01
  • 1970-01-01
  • 2016-06-27
  • 1970-01-01
  • 2011-01-16
  • 1970-01-01
相关资源
最近更新 更多