作者:王姗姗,华清远见嵌入式学院讲师。
如何利用多进程,来实现文件的拷贝?
在我们学习IO的时候,曾经利用文件IO函数,标准IO函数都实现了对文件的拷贝,那么在我们学习过进程间通信后,就可以创建多个进程来完成对同一个文件的读写。例如让父进程写文件的前半部分,子进程来写文件的后半部分,因为两个进程间是可以并发执行的,所以将会节约一部分时间,提高执行的效率。那么怎样才能实现这个功能?
我们以文件IO为例,边讲述如何实现的同时,也给大家说下为什么这样写的原因,希望能给大家得到些启发。
首先来看下用文件IO函数实现拷贝文件的程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define maxsize 256
int main(int argc,char *argv[])
{
if(argc!=3) //如果命令格式不正确
{
printf("command error!/n");
return -1;
}
int fd1,fd2;
if ((fd1= open(argv[1],O_RDONLY))< 0)
{
perror("error");
return -1;
}
if ((fd2 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0644))< 0)
{
perror("error");
return -1;
}
char s[maxsize ];
int t=0;
while ((t=read(fd1,s,maxsize ))==maxsize )
{
write(fd2,s,t);
}
write(fd2,s,t);
close(fd1);
close(fd2);
return 0;
}
这样就实现了文件的拷贝。那么在我们学习完fork函数之后是不是只要父进程和子进程分别写一部分就可以了?
if((src=open(argv[1],O_RDONLY))<0)
{
……
}
if((des=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0644))<0)
{
……
}
len=lseek(src,0,SEEK_END);
int pid;
if((pid=fork())<0)
{
fprintf(stderr,"fork error!:%s/n",strerror(errno));
return -1;
}
else if(pid ==0)
{
lseek(src,len/2,SEEK_SET);
lseek(des,len/2,SEEK_SET);
while((n=read(src,buf,sizeof(buf)))!=0)
{
write(des,buf,n);
}
exit(0);
}
else
{
lseek(src,0,SEEK_SET);
lseek(des,0,SEEK_SET);
while((n=read(src,buf,sizeof(buf)))!=0)
{
write(des,buf,n);
if(lseek(src,0,SEEK_CUR) > len/2)
break;
}
wait(NULL);
exit(0);
}
是不是只要这样写就可以实现功能?实践证明这样写不行的,那么为什么这样写不可以呢?
子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。子进程获得父进程数据空间、堆栈的复制品。注意,这是子进程所拥有的拷贝,父、子进程并不共享这些存储空间部分。那么跟本例相关的一条fork特性就是由父进程打开的文件描述符也被复制到子程序中。父、子进程的每个相同的打开描述符共享一个文件表项。
在unix高级环境编程中有这样一幅图
这种共享文件的方式使父、子进程对同一个文件使用了一个文件位移量。当父进程是其标准输出重新定向,那么子进程写到该标准输出时,它将更新与父进程共享的该文件的位移量。当程序中,父、子进程写到同一个描述符文件,因为没有任何形式的同步,因为它们的输出都混在一起,所以复制后的文件就是错的。那么为了解决该问题,我们应该对所写的程序加以改进。
if((src=open(argv[1],O_RDONLY))<0)
{
……
}
if((des=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0644))<0)
{
……
}
len=lseek(src,0,SEEK_END);
ftruncate(des,len);//创建空洞文件
printf("len=%ld/n",len);
int pid;
if((pid=fork())<0)
{
……
}
else if(pid ==0)
{
close(src);
close(des);
if((src=open(argv[1],O_RDONLY))<0)
{
fprintf(stderr,"open %s failed!:%s/n",argv[1],strerror(errno));
return -1;
}
if((des=open(argv[2],O_WRONLY))<0)
{
fprintf(stderr,"open %s failed!:%s/n",argv[2],strerror(errno));
return -1;
}
lseek(src,len/2,SEEK_SET);
lseek(des,len/2,SEEK_SET);
while((n=read(src,buf,sizeof(buf)))!=0)
{
write(des,buf,n);
}
close(src);
close(des);
exit(0);
}
else
{
lseek(src,0,SEEK_SET);
lseek(des,0,SEEK_SET);
while((n=read(src,buf,sizeof(buf)))!=0)
{
write(des,buf,n);
if(lseek(src,0,SEEK_CUR) > len/2)
break;
}
wait(NULL);
close(src);
close(des);
exit(0);
}
这样父、子进程就不共享同一个文件位移量,虽然打开的是同一个文件,写的也是同一个文件,但是再实际操作中,就不会让父子进程使用同一张文件表了。