Копирование файлов в каталог с использованием memcpy и mmap, чтобы все файлы были размером 1 байт

#c #multithreading

Вопрос:

 #include lt;stdio.hgt; #include lt;stdlib.hgt; #include lt;sys/types.hgt; #include lt;dirent.hgt; #include lt;pthread.hgt; #include lt;fcntl.hgt; #include lt;unistd.hgt; #include lt;sys/stat.hgt; #include lt;sys/mman.hgt; #include lt;string.hgt;  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, amp;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-gt;d_name;   printf("%sn", filename);   pthread_create(amp;threads[i], NULL, copyFile, filename);    i  ;  }   for(int j = 0; j lt; i; j  ) {   pthread_join(threads[j], NULL);   }  }  

Здравствуйте, я пытаюсь использовать mmap и memcpy для копирования всего каталога файлов с использованием многопоточности. У меня есть код для копирования файлов в каталог, однако он заставляет все СКОПИРОВАННЫЕ файлы иметь размер 1 байт. Мне любопытно, почему это происходит и что можно сделать для устранения этой ошибки.

Я очень новичок в C, поэтому, пожалуйста, простите мои очевидные семантические ошибки. Я ценю вашу помощь!

Комментарии:

1. Вам нужно сделать что-то вроде «сначала прочитайте», а затем «читайте дальше»? Кроме того, вы изменяете каталог по мере чтения записей из него?

2. У меня сложилось впечатление, что цикл while повторяется в потоке каталогов и, следовательно, не потребуется «сначала прочитать» или «читать дальше». Кроме того, я не изменяю каталог, насколько мне известно.

3. readdir возвращает указатель на структуру, которая может быть статически выделена. Это означает, что вы можете передавать одно и то же значение filename во все потоковые экземпляры copyFile .

4.@G. M. Например, если бы у нас было 3 файла f1 f2 f3 , ожидали бы мы, что на выходе будет 3 экземпляра f1copy в соответствии с предложенной вами ошибкой? Если это так, то, к сожалению, это не то, что я испытываю, все имена указаны правильно (хотя я надеюсь, что просто неправильно понимаю ваше объяснение, и вы правы!).

5. Также strcat изменяет передаваемую строку. Так что если filename было "foo" , то char *destname = strcat(filename, "copy"); это приводит к filename тому, что вы являетесь foocopy и destname являетесь еще одним указателем на ту же строку. И в любом случае, filename это указатель на возвращаемый буфер readdir , который вы не должны изменять (особенно потому, что он хранится в других потоках). Вам нужно создать временный буфер для хранения destname .

Ответ №1:

Есть ряд ошибок.

  1. В main , filename будет указывать на один и тот же адрес для всех файлов. Мы должны предоставить уникальный адрес/копию. Один из способов-использовать strdup . И мы должны добавить free вызов в нижней части copyFile , чтобы освободить хранилище, выделенное strdup .
  2. Мы должны пропустить . и .. при копировании. В противном случае код сегментируется.
  3. Использование одного и того же каталога для входных файлов и копии может привести к эквивалентному бесконечному циклу. То есть, учитывая файл xyz , в который копируется xyzcopy , readdir он может быть виден xyzcopy в качестве входных данных и попытаться создать xyzcopycopy . Лучше использовать отдельный выходной каталог. Это потребовало бы серьезной переписки. Но более простое решение-пропустить любой файл, который содержит copy .
  4. Программа [попытается] скопировать записи каталога, даже если они не являются обычными файлами (например, она попытается сделать open это в каталоге). Мы должны проверить тип файла и пропустить типы, отличные от файлов.
  5. В copyFile , даже с strdup помощью in main , в строке filename недостаточно места, чтобы разрешить strcat из "copy" . Нам нужен отдельный буфер для выходного имени файла.
  6. lseek/write Добавляет лишний двоичный ноль в конце выходного файла. Это делается для расширения выходного файла. Лучше использовать ftruncate для этого.
  7. Это потому, что смешивание write с mmap может быть проблематичным и обычно не является лучшей практикой. В вашем случае использования это может быть нормально (потому write что это делается до mmap ), но если мы возьмем на себя труд использовать mmap , лучше просто использовать указатель mmap буфера и memcpy .
  8. copyFile отсутствует кот return . И, main также отсутствует return
  9. Здесь нет close in никакого «за copyFile «.
  10. При open mmap вызовах , , проверка ошибок не opendir выполняется.
  11. До звонков должны быть munmap close звонки.

Ошибки не исправлены:

  1. Программа ограничена количеством записей, которые могут содержаться в каталоге.
  2. С pthread_t threads[50]; помощью мы можем иметь только 50 записей в каталоге. Проверка переполнения этого массива отсутствует.
  3. Нет практического смысла делать все файлы параллельно. Это не масштабируется.
  4. Если бы у нас было (например) 1 000 000 записей в каталоге, у нас могли бы закончиться ресурсы. У нас могут закончиться неиспользуемые слоты для процессов, и pthread_create они начнут выходить из строя после достижения общесистемного предела. У нас могут закончиться файловые дескрипторы, и open вызовы начнут завершаться сбоем примерно после 1024 открытых файлов.
  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  

В любом случае, вот исправленный код. Я прокомментировал ошибки/исправления комментариями:

 #include lt;stdio.hgt; #include lt;stdlib.hgt; #include lt;sys/types.hgt; #include lt;dirent.hgt; #include lt;pthread.hgt; #include lt;fcntl.hgt; #include lt;unistd.hgt; #include lt;sys/stat.hgt; #include lt;sys/mman.hgt; #include lt;string.hgt;  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 lt; 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 lt; 0) {  perror(destname);  exit(1);  } #endif   struct stat statbuf;   fstat(in, amp;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 argumentn");  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-gt;d_name,".") == 0)  continue;  if (strcmp(e-gt;d_name,"..") == 0)  continue; #endif  // NOTE/FIX: only copy simple files (i.e. do _not_ copy subdirectories, etc.) #if 1  if (e-gt;d_type != DT_REG)  continue; #endif  // NOTE/FIX: skip "copy" files #if 1  if (strstr(e-gt;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-gt;d_name; #else  char *filename = strdup(e-gt;d_name); #endif   printf("%sn", filename);   pthread_create(amp;threads[i], NULL, copyFile, filename);   i  ;  }   for (int j = 0; j lt; i; j  ) {  pthread_join(threads[j], NULL);  }  // NOTE/FIX: needs return #if 1  return 0; #endif }  

Комментарии:

1. Спасибо вам за очень подробный ответ! Это сработало!