下面用程序实现这样的功能:有一个文件的内容需要拷贝到另一个文件中,以前的做法是用一个进程来完成,下面要做的是,是两个进程同时做这件事,一个进程负责一半,如父进程负责拷贝文件的前半段,子进程负责拷贝文件的后半段。下面看一下程序的流程以及需要注意的问题:
其中 要注意的是当父进程调用fork函数创建子进程后,子进程同样也把父进程所打开的文件描述符fd拷贝过来,但是,在内核当中的struct file结构体只有一份。即:
这不是我们期望的,这样会造成两个进程之间的干扰,比如文件的当前位置就不好控制。对于文件描述符符可以这么理解:
在每个进程的进程控制块PCB当中有一个结构体指针数组 struct file *a[] = {0xa0,0xb0,……};子进程创建时,也拷贝了父进程的这个指针数组。每当一个进程p1调用open函数都返回一个整型的文件描述符fd,同时内核空间就会产生一个相应的struct file 类型的结构体,这个结构体的的地址就存放在该进程p1的PCB中的a[fd]中。而进程p1也是通过这个数组来找到相应的文件的,当进程p1执行close(fd)后,描述符a中的a[fd]不再保存相应的结构体的地址,注意:内核中那个结构体可能还存在,原因是虽然p1不再使用该文件,但是其父进程或者子进程还使用该文件,即只要有还指向该结构体的文件描述符存在,他就不会释放。还应该知道,同一个文件可以有多个struct file,即一个文件可以被打开多次,每次的fd可能不同,但是打开一次内核中就创建一个关于这个文件的struct file。
所以,在子进程中应该先将源文件a和目标文件b关闭,然后再打开,这样就不会相互干扰了。
即:
下面是程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#define N 64
int main(int argc, char *argv[])
{
int fdr, fdw; //定义文件描述符
off_t len; //文件长度
pid_t pid; //
char buf[N] = {0};
ssize_t n, sum = 0;
if (argc != 3) //检查输入参数是否合法
{
printf("usage:%s srcfile destfile\n", argv[0]);
return 0;
}
if ((fdr = open(argv[1], O_RDONLY)) == -1) //打开源文件
{
perror("open for reading");
exit(-1);
}
if ((fdw = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) //打开要写的文件,即目标文件
{
perror("open for writing");
exit(-1);
}
if ((len = lseek(fdr, 0, SEEK_END)) == (off_t)-1) //求源文件的长度len
{
perror("lseek");
exit(-1);
}
printf("len=%ld\n", len);
if (-1 == ftruncate(fdw, len)) //将目标文件变成空洞文件,大小根源文件相同。
{
perror("ftruncate");
exit(-1);
}
if ((pid = fork()) == -1) //创建子进程
{
perror("fork");
exit(-1);
}
if (pid == 0)//child last 1/2
{
close(fdr); //将源文件先关闭
close(fdw); //将目标文件先关闭
fdr = open(argv[1], O_RDONLY); //将源文件打开
fdw = open(argv[2], O_WRONLY); //将目标问价打开
lseek(fdr, len/2, SEEK_SET); //将源文件当前位置定位到中间
lseek(fdw, len/2, SEEK_SET); //将目标文件当前位置定位到中间
while ((n = read(fdr, buf, N)) > 0) //拷贝,等于0意味着到了问价末尾
write(fdw, buf, n);
}
else//parent first 1/2
{
lseek(fdr, 0, SEEK_SET); //父进程重新将源文件当前位置定位到中间
lseek(fdw, 0, SEEK_SET); //父进程重新将目标文件当前位置定位到中间
while ((n = read(fdr, buf, N)) > 0)
{
write(fdw, buf, n);
sum += n;
if (sum >= len / 2) //是否读够,注意:即使父进程写多了,也没关系,因为子进程会以同样的内容将其覆盖
break;
}
}
close(fdr);
close(fdw);
return 0;
}
拷贝完成了后,用 “diff 源文件 目标文件”进行检查。