测试程序
我们来看到下面的这个代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void fun(int *num)
{
return;
}
int main()
{
int i=10;
int pid=fork();
if(pid>0)
{
signal(SIGUSR1,fun);
printf("父进程:修改变量i之前,变量i地址:%X\t变量i的值:%d\n",&i,i);
kill(pid,SIGUSR1);
pause();
printf("父进程:修改变量i之后,变量i地址:%X\t变量i的值:%d\n",&i,i);
wait(NULL);
}
else if(pid==0)
{
signal(SIGUSR1,fun);
pause();
printf("子进程:修改变量i之前,变量i地址:%X\t变量i的值:%d\n",&i,i);
i=20;
printf("子进程:修改变量i之后,变量i地址:%X\t变量i的值:%d\n",&i,i);
kill(getppid(),SIGUSR1);
}
return 0;
}
我们可以看到的运行结果是这样子的:
我们可以看到,在子进程修改这个变量i的值之后,我们变量i的值的确被改变了(父进程i=10,子进程i=20),但是这个父进程和子进程的这个变量i的地址还是一样的。但是我们知道的fork()函数采用的是写时复制的。也就是说,在父子进程共有的变量都没有改变的时候,父子进程的这个变量的地址应该是相同的。的确,这个在我们上面的测试程序的表现出来的也是这样子的。但是如果子进程改变了共有变量的值,那么按照写时复制的说法,此时子进程应该开辟一块新的内存来存放这个变量的值。这样子,父进程和子进程对于这个共有变量的指向就会改变了。但是我们上面的程序表明,在子进程改变了这个共有变量的值之后,父进程和子进程对于变量 i 的地址还是一样的。这点和我当初理解的写时复制是不同的,所以到底是那里出现了问题呢?
原因在于:写时复制指的是物理内存的分配。就是进程A和进程B一开始都是指向的同一块物理内存mem中变量val,但是进程A试图改变这个mem中的变量val的时候,写时复制就会重新开辟一块内存mem1,来存放这个变量val。此时我们的进程A指向的就是mem1中的val。
这里的原因:写时复制我们说的是重新分配一块物理内存,改变原来的这个虚拟地址的指向,指向我们新分配的这个物理内存上。所以此时子进程中变量 i 的虚拟地址还是0x1AF0CAD0,但是这个虚拟地址所指向的物理地址却和父进程中变量的 i 的物理地址不同了。从操作系统中的学习可以直到,不同进程的同一虚拟地址肯定是可以映射到不同的物理地址上的。
fork函数对于文件描述符的处理
如果在调用fork()函数之前,父进程打开了一个文件A,然后开始执行了fork()函数。那么现在的问题就是,父进程和子进程都获得了文件A描述符,那么现在父进程和子进程都往文件里面写入数据的时候,是否会触发写时复制。这个文件A是否会被复制两份?
首先给出答案就是:文件A不会被复制两份。父进程和子进程都会对这个文件写入操作。并且由于父进程和子进程的文件偏移量也是一样的,所以写入的时候不会出现覆盖,而是写入的数据相互混合。
我们看看下面这个测试程序代码:
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<dirent.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
int main ()
{
pid_t fpid; //fpid表示fork函数返回的值
//打开一个文件
int fd=open("./test.c",O_WRONLY|O_CREAT);
fpid=fork();
if (fpid < 0)
printf("error in fork!");
if (fpid == 0)
{
printf("i am child\n");
int cnt=0;
char buf[128];
for(int i=0;i<65535;i++)
{
memset(buf,0,sizeof(buf));
sprintf(buf,"%d [i am child]\n",cnt);
write(fd,buf,sizeof(buf));
cnt++;
}
close(fd);
}
else
{
printf("i am parent\n");
char buf[128];
int cnt=0;
for(int i=0;i<65535;i++)
{
memset(buf,0,sizeof(buf));
sprintf(buf,"%d [i am parent]\n",cnt);
write(fd,buf,sizeof(buf));
cnt++;
}
waitpid(fpid);
close(fd);
}
return 0;
}
程序中之所以循环65535次,就是因为如果仅仅只循环10次,那么可能就无法看到交叉写入文件数据的情况。这个是因为可能父进程或者是子进程一旦得到了CPU的执行权,可能10次循环在一次时间片中就执行完毕了。所以我们这里选择采用65535次循环就是为了避免这个问题出现。
我们看这个写入的文件test.c,看看里面的内容:
可以看到这个父子进程是交叉写入的。
我之所以提到上面这个文件 描述符的例子,就是想要说明:这里文件描述符的操作是没有写时复制的,所以我们采用进程间通信的时候也是没有写时复制的,大家就是直接操作这个内核通信对象的。
参考文章
http://bbs.chinaunix.net/thread-1987648-1-1.html
https://blog.csdn.net/u012317833/article/details/39160219
https://blog.csdn.net/Holy_666/article/details/85336387