fork()函数与虚拟地址

测试程序

        我们来看到下面的这个代码:

#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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值