使用mmap实现大文件的复制(单进程和多进程)

使用mmap实现大文件的复制,供大家参考,具体内容如下

典型的文件复制的流程是:

1、读取(fread)被复制的文件的内容。
2、写入(fwrite)到新的文件中去。

使用mmap进行文件复制的流程则是:

1、为被复制的文件已经新文件分别进行mmap映射。
2、将被复制的文件映射的内存的内容复制到新文件映射的内存。

在知道了基本原理之后,让我们看看具体的做法,本文只分析使用mmap进行大文件复制的方法

具体的做法

先了解一些使用mmap时,应当注意的细节:

  • 文件的大小必须要大于等于内存映射区的大小,因此对于创建的新文件,可以使用文件截断函数(ftruncate)来改变文件的大小和被复制的文件大小一样,这样子才能在映射一个和被复制的文件的映射区一样大的内存映射区。
  • 下面我将使用单进程和多进程两种方式来进行mmap文件复制

单进程mmap文件复制

#include<stdio.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
/* 首先规定一下该命令的参数 :
 * mycopy 源文件地址 目标文件地址
 *
 * 可能需要的函数:open\read\write\close
 * 基本流程:打开源文件,读取内容,写到另一个文件,关闭文件
 * 核心方法:通过mmap(最快的进程间通信方法)
 * 如何获取文件的大小:
 *
 */
unsigned long get_file_byte_num(const char * filename)
{
 FILE *fp = fopen(filename, "r");
 fseek(fp, 0, SEEK_END);
 return ftell(fp);
}
int main(int argc, char ** argv)
{
 // 首先解析输入的参数得到源文件和目标文件
 if (argc < 3)
 {
  perror("参数输入不足");
 }
 int slen = strlen(argv[1]); // 这个是源文件的名的长度
 int tlen = strlen(argv[2]); // 这个是目标文件名的长度
 char *sname = (char *)malloc(sizeof(char)*(slen + 1));
 char *tname = (char*)malloc(sizeof(char)*(tlen + 1));
 strcpy(sname, argv[1]);
 strcpy(tname, argv[2]);
 // 打开目标文件
 // 计算源文件的大小(字节数)
 unsigned long byte_num = get_file_byte_num(sname);
 printf("文件的长度为%ld字节\n", byte_num);
 //--------建立 mmap 映射区 --------------
 // 获取被复制文件的文件描述符
 int fd = open(sname, O_RDWR|O_CREAT, 0644);
 int tfd = open(tname, O_RDWR|O_CREAT, 0644);
 ftruncate(tfd, byte_num);

 char *mem =(char*) mmap(NULL, byte_num, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
 if (mem == MAP_FAILED)
  perror("mmap err");
 char *tmem =(char*) mmap(NULL, byte_num, PROT_WRITE|PROT_READ, MAP_SHARED, tfd, 0);
 if (tmem == MAP_FAILED)
  perror("mmap err");

 close(fd); // 内存映射区建立之后,就可以关闭文件描述符
 close(tfd);

 memcpy(tmem, mem, byte_num);

 // 回收子进程,等待拷贝结束
 munmap(mem, byte_num);
 munmap(tmem, byte_num);
 free(sname);
 free(tname);

多进程mmap复制文件

所谓的多进程mmap复制文件,只是做了将复制的任务分给多个进程去完成,核心思想不变。

#include<stdio.h>
#include <string.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <signal.h>
#include <sys/wait.h>
/* 首先规定一下该命令的参数 :
* mycopy 源文件地址 目标文件地址
*
* 可能需要的函数:open\read\write\close
* 基本流程:打开源文件,读取内容,写到另一个文件,关闭文件
* 核心方法:通过mmap(最快的进程间通信方法)
* 如何获取文件的大小:
*
*/
// 获取文件的字节数
unsigned long get_file_byte_num(const char * filename)
{
 FILE *fp = fopen(filename, "r");
 fseek(fp, 0, SEEK_END);
 return ftell(fp);
}

// sigchld信号处理函数
void sigchld_handle(int a)
{
 pid_t pid;
 while ((pid = waitpid(0,NULL, WNOHANG)) > 0)
 {
  printf("回收到一个子进程%d\n", pid);
 }
}

int main(int argc, char ** argv)
{
 // 首先解析输入的参数得到源文件和目标文件
 if (argc < 3)
 {
  perror("参数输入不足");
 }
 int slen = strlen(argv[1]); // 这个是源文件的名的长度
 int tlen = strlen(argv[2]); // 这个是目标文件名的长度
 char *sname = (char *)malloc(sizeof(char)*(slen + 1));
 char *tname = (char*)malloc(sizeof(char)*(tlen + 1));
 strcpy(sname, argv[1]);
 strcpy(tname, argv[2]);
 // 打开目标文件
 FILE * tfp = fopen(tname, "w"); // 不存在则创建
 // 计算源文件的大小(字节数)
 unsigned long byte_num = get_file_byte_num(sname);
 printf("文件的长度为%ld字节\n", byte_num);
 //--------建立 mmap 映射区 --------------
 // 获取被复制文件的文件描述符
 int fd = open(sname, O_RDWR|O_CREAT, 0644);
 int tfd = open(tname, O_RDWR|O_CREAT, 0644);
 ftruncate(tfd, byte_num); // 将tfd指向的文件的大小改变为byte_num

 char *mem =(char*) mmap(NULL, byte_num, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0);
 char * mem_tmp = mem; // 备份共享内存入口地址
 if (mem == MAP_FAILED)
 perror("MAP_FAILED");
 char *tmem = (char*)mmap(NULL, byte_num, PROT_WRITE, MAP_SHARED,tfd, 0);
 char * tmem_tmp = tmem;
 if (tmem == MAP_FAILED)
 perror("mmap err");

 close(fd); // 内存映射区建立之后,就可以关闭文件描述符
 close(tfd);

 // 父子进程之间就可以通过这个mem指针进行通信了
 // 规定进程数量为5mZ const int num_proc = 5;
 // 根据进程数量,计算每个进程需要拷贝的字节数,
 // 最后一个进程拷贝剩余的字节数。
 const int num_proc = 5;
 const unsigned long each_proc_byte = byte_num/num_proc;
 // 由于可能无法整除,因此最后一个进程,需要多复制一些
 const unsigned long last_proc_byte = byte_num - each_proc_byte*(num_proc - 1); 

 // 屏蔽sigchld信号的系统默认处理方式
 sigset_t set;
 sigemptyset(&set); // 初始化一下set
 sigaddset(&set, SIGCHLD);
 sigprocmask(SIG_BLOCK, &set, NULL); // 屏蔽

 // 循环创建子进程
int i;
pid_t pid;
for(i = 0; i < num_proc - 1; ++i)
{
 if ((pid = fork())==-1)
 perror("fork error");
 if (pid == 0)
 break;
}

 // -------具体的拷贝过程---------
 if (i == num_proc - 1) // 父进程
 {
  // 设置信号捕捉和解除屏蔽
  struct sigaction act;
  act.sa_handler = sigchld_handle;
  sigemptyset(&act.sa_mask); // 处理期间不屏蔽其他信号
  sigaction(SIGCHLD, &act, NULL); // 启动信号捕获
  sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除SIGCHLD的屏蔽

  memcpy(tmem_tmp + each_proc_byte*i, mem_tmp + each_proc_byte*i, last_proc_byte); 

 }else
 {
  memcpy(tmem + each_proc_byte*i, mem_tmp + each_proc_byte*i, each_proc_byte);
 }
 //
 // 回收子进程,等待拷贝结束
 //

  munmap(mem, byte_num);
  munmap(tmem, byte_num);
  free(sname);
  free(tname);
  return 0;
}

几个问题

1、主进程什么时候进行munmap才不会影响其他进程使用mmap进行复制。

父子进程之间的用户空间遵循“读时共享、写时复制”的原则,mmap肯定是采用的用户空间的内存,因此我认为最好的处理方法,就是每个进程都进行munmap。

2、sleep()函数的注意点:

sleep在manpage上的原文解释为:sleep() makes the calling process sleep until seconds seconds have elapsed or a signal arrives which is not ignored.

也就是说,结束sleep的情况又两种:1.休眠时间到。2.调用sleep的进程收到一个信号,且该信号未被屏蔽,在该信号处理完成后,理解结束sleep。

3、子进程可能会比主进程结束的慢,而且不能让主进程阻塞等待子进程(通过信号捕捉来回收子进程),这种情况下,该如何回收子进程?

首先,主进程准备好信号捕获回收子进程的代码。其次,能不能回收就随缘,如果主进程先退出了就把子进程交给init进程回收。所以,不需要担心能不能回收的问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 使用mmap实现多进程对大文件拷贝

    本文实例为大家分享了mmap实现文件多进程拷贝,供大家参考,具体内容如下 假设有一个超大文件,需对其完成拷贝工作.为提高效率,可采用多进程并行拷贝的方法来实现.假设文件大小为len,共有n个进程对该文件进行拷贝.那每个进程拷贝的字节数应为len/n.但未必一定能整除,我们可以选择让最后一个进程负责剩余部分拷贝工作.可使用len % (len/n)将剩余部分大小求出. 为降低实现复杂度,可选用mmap来实现源.目标文件的映射,通过指针操作内存地址,设置每个进程拷贝的起始.结束位置.借助MAP_SH

  • 利用mmap实现文件拷贝功能

    利用mmap实现的一个文件拷贝例子,供大家参考,具体内容如下 /* * gcc -Wall -O3 -o copy_mmap copy_mmap.c */ #include < stdio.h > #include < stdlib.h > #include < string .h > /* for memcpy */ #include < strings.h > #include < sys / mman.h > #include < s

  • 使用mmap实现大文件的复制(单进程和多进程)

    使用mmap实现大文件的复制,供大家参考,具体内容如下 典型的文件复制的流程是: 1.读取(fread)被复制的文件的内容. 2.写入(fwrite)到新的文件中去. 使用mmap进行文件复制的流程则是: 1.为被复制的文件已经新文件分别进行mmap映射. 2.将被复制的文件映射的内存的内容复制到新文件映射的内存. 在知道了基本原理之后,让我们看看具体的做法,本文只分析使用mmap进行大文件复制的方法 具体的做法 先了解一些使用mmap时,应当注意的细节: 文件的大小必须要大于等于内存映射区的大

  • C# FileStream实现大文件复制

    FileStream缓冲读取和写入可以提高性能.FileStream读取文件的时候,是先将流放入内存,经Flush()方法后将内存中(缓冲中)的数据写入文件.如果文件非常大,势必消耗性能.特封装在FileHelper中以备不时之需. 参考文章:C# FileStream复制大文件.将该文章中提供的代码稍作修改,原文中进行了强制类型转换,如果文件很大,比如4G,就会出现溢出的情况,复制的结果字节丢失严重,导致复制文件和源文件大小不一样.这里修改的代码如下: public static class

  • C# FileStream复制大文件

    本文实例为大家分享了C# FileStream复制大文件的具体代码,供大家参考,具体内容如下 即每次复制文件的一小段,以节省总内存开销.当然,本机复制也可以采用.NET内部的System.IO.File.Copy方法. /// <summary> /// 复制文件 /// </summary> /// <param name="fromFile">要复制的文件</param> /// <param name="toFile

  • C# FileStream复制大文件功能

    FileStream缓冲读取和写入可以提高性能.每次复制文件的一小段,以节省总内存开销.当然,本机复制也可以采用.NET内部的System.IO.File.Copy方法. FileStream读取文件的时候,是先讲流放入内存,经Flash()方法后将内存中(缓冲中)的数据写入文件.如果文件非常大,势必消耗性能.特封装在FileHelper中以备不时之需.强制类型转换,如果文件很大,比如4G,就会出现溢出的情况,复制的结果字节丢失严重,导致复制文件和源文件大小不一样.这里修改的代码如下: publ

  • php 使用file_get_contents读取大文件的方法

    当我们遇到文本文件体积很大时,比如超过几十M甚至几百M几G的大文件,用记事本或者其它编辑器打开往往不能成功,因为他们都需要把文件内容全部放到内存里面,这时就会发生内存溢出而打开错误,遇到这种情况我们可以使用PHP的文件读取函数file_get_contents()进行分段读取. 函数说明 string file_get_contents ( string $filename [, bool $use_include_path [, resource $context [, int $offset

  • Linux 中清空或删除大文件内容的五种方法

    在 Linux 终端下处理文件时,有时我们想直接清空文件的内容但又不必使用任何Linux命令行编辑器 去打开这些文件.那怎样才能达到这个目的呢?在这篇文章中,我们将介绍几种借助一些实用的命令来清空文件内容的方法. 注意: 由于再Linux中一切皆文件,你需要时刻注意,确保你将要清空的文件不是重要的用户文件或者系统文件.清空重要的系统文件或者配置文件可能会引发严重的应用失败或者系统错误. 提示:在下面的示例中,我们将使用名为 access.log 的文件来作为示例样本. 1. 通过重定向到 Nul

  • Java 使用IO流实现大文件的分割与合并实例详解

    Java 使用IO流实现大文件的分割与合并 文件分割应该算一个比较实用的功能,举例子说明吧比如说:你有一个3G的文件要从一台电脑Copy到另一台电脑, 但是你的存储设备(比如SD卡)只有1G ,这个时候就可以把这个文件切割成3个1G的文件 ,分开复制, 最后把三个文件合并, 这样就解决问题了 :再比如说, 你有一个上百M的文件要上传到FTP ,但是这个FTP限制你单个文件不能超过10M 这时候也可以用文件分割的办法解决问题.既然分割了,那么在我们再次使用的时候就需要合并了,今天我们就通过Java

  • ASP.NET对大文件上传的解决方案

    首先,我们需要下载这个名为 RanUpLoad 的组件. 下载完成之后,两个 dll 文件添加到项目的引用中区,xml 文件也要复制在项目中的 bin 文件夹下,也就是最后三个文件都要存在于 bin 文件夹中. 接着,上传控件还是用 ASP.NET 中自带的 FileUpload 控件,需要添加的就是在 FileUpload 控件旁边加入标签: <radU:RadProgressManager ID="Radprogressmanager1" Width="100%&q

  • ASP.Net下载大文件的实现方法

    本文实例讲述了ASP.Net下载大文件的实现方法.分享给大家供大家参考.具体分析如下: 当我们的网站需要支持下载大文件时,如果不做控制可能会导致用户在访问下载页面时发生无响应,使得浏览器崩溃.可以参考如下代码来避免这个问题. 关于此代码的几点说明: 1. 将数据分成较小的部分,然后将其移动到输出流以供下载,从而获取这些数据. 2. 根据下载的文件类型来指定 Response.ContentType .(参考OSChina的这个网址可以找到大部分文件类型的对照表:http://tool.oschi

  • ASP.NET解决上传大文件问题的方法

    上传文件的控件为:FileUpload Asp.Net对上传文件大小有限制.默认情况下用户只能上传4MB大小的文件,这会给用户带来不便.所以如果要上传40MB大小的文件.只能修改配置文件 关键代码如下 复制代码 代码如下: protected void btnSend_Click(object sender, EventArgs e) { try { //上传文件的思路: //获取上传文件的名称,此时为一个全路径的地址 string upFileName = fulFileName.FileNa

随机推荐