fork之后父子进程虚拟地址(逻辑地址)相同的问题

背景:突然脑残,搞不明白单例在多进程中的使用,所以想看下单例在多进程中到底是不是同一份,所以下了以下测试程序

#include <unistd.h>
#include <errno.h>
#include <stdio.h>

template <typename T>
class CSingleton
{
public:
	static T *getInstance()
	{
		static T t;
		return &t;
		/*
		static T * t = NULL;
		if(t == NULL)
		{
			printf("init t\n");
			t = new T();
		}
		return t;
		*/
	}
};

class CData
{
public:
	CData()
	{
		printf("pid:%d, init cdata, a:%d\n", getpid(), a);
	}

	~CData()
	{

	}

public:
	static int a;
};

int CData::a = 1;

int main()
{
	int iCount = 0;
	int iRet = fork();
	if(iRet < 0)
	{
		printf("fork error:%d\n", errno);
	}
	else if(iRet == 0) //子进程
	{
		printf("pid:%d, before write, child address is %p, a:%d, a's address:%p\n", getpid(), CSingleton<CData>::getInstance(),
				CSingleton<CData>::getInstance()->a, &(CSingleton<CData>::getInstance()->a));
		sleep(3);
		CSingleton<CData>::getInstance()->a++;
		printf("pid:%d, after write, child address is %p, a:%d, a's address:%p\n", getpid(), CSingleton<CData>::getInstance(),
				CSingleton<CData>::getInstance()->a, &(CSingleton<CData>::getInstance()->a));
		iCount++;
	}
	else
	{
		sleep(1);
		printf("pid:%d, before write, father address is %p, a:%d, a's address:%p\n", getpid(), CSingleton<CData>::getInstance(),
				CSingleton<CData>::getInstance()->a, &(CSingleton<CData>::getInstance()->a));
		CSingleton<CData>::getInstance()->a++;
		printf("pid:%d, after write, father address is %p, a:%d, a's address:%p\n", getpid(), CSingleton<CData>::getInstance(),
				CSingleton<CData>::getInstance()->a, &(CSingleton<CData>::getInstance()->a));
		iCount++;
		sleep(5);
	}
	printf("iCount is %d\n", iCount);
	return 0;
}

 执行结果是这样的:

可以看到,两个进程中实例的虚拟地址都是一样的,但是实际上指向的是两个不同的变量(物理地址不同),这点可以从a的值上看出。

但是我没明白的是,我多次运行并且更换系统,父子进程中这个虚拟地址都是一模一样的,但是实例是在fork之后才建立的,所以子进程拷贝父进程的数据跟这个貌似无关啊,可是为什么虚拟地址一直是一样的呢? 

附:在fork前建立实例

int main()
{
	int iCount = 0;
	CData *pCData = CSingleton<CData>::getInstance();
	pCData->a++;
	int iRet = fork();
	if(iRet < 0)
	{
		printf("fork error:%d\n", errno);
	}
	else if(iRet == 0) //子进程
	{
		printf("pid:%d, child address is %p\n", getpid(), pCData);
		pCData->a++;
	}
	else
	{
		sleep(2);
		printf("pid:%d, father address is %p\n", getpid(),pCData);
		pCData->a++;
	}
	printf("iCount is %d, a:%d\n", iCount, pCData->a);
	return 0;
}

运行结果:

这个还是比较好理解的,在fork后,子进程“继承”父进程的变量,其地址总是一样的,因为在fork时整个虚拟地址空间被复制,但是虚拟地址空间所对应的物理内存却没有复制(这个时候父子进程中变量 x对应的虚拟地址和物理地址都相同)。等到虚拟地址空间被写时,对应的物理内存空间被复制(这个时候父子进程中变量 i 对应的虚拟地址还是相同的,但是物理地址不同),这就是"写时复制"。这个一样的地址是线性地址,每个进程的相同的线性地址都可以映射到不同的物理地址上。在fork的时候,子进程从父进程了copy了task_struct结构,其中task_struct里的mm就是线性地址的使用情况,mm也会被copy给子进程,所以在fork之前声明的变量,在fork后在父进程和子进程里的线性地址是一样的。 

再附:这种情况下,虚拟地址是不一样的:

int main()
{
	int iRet = fork();
	if(iRet < 0)
	{
		printf("fork error:%d\n", errno);
	}
	else if(iRet == 0) //子进程
	{
		static CData a;
		printf("i:%p\n", &a);
	}
	else
	{
		sleep(1);
		static CData a;
		printf("i:%p\n", &a);
		sleep(5);
	}
	return 0;
}

运行结果:

附一张图:(http://blog.chinaunix.net/uid-24774106-id-3361500.html

    我们都知道fork创建进程的时候,并没有真正的copy内存,因为我们知道,对于fork来讲,有一个很讨厌的东西叫exec系列的系统调用,它会勾引子进程另起炉灶。如果创建子进程就要内存拷贝的的话,一执行exec,辛辛苦苦拷贝的内存又被完全放弃了。
    内核采用的策略是写时拷贝,换言之,先把页表映射关系建立起来,并不真正将内存拷贝。如果进程读访问,什么都不许要做,如果进程写访问,情况就不同了,因为父子进程的内存空间是独立的,不应该互相干扰。所以这时候不能在公用同一块内存了,否则子进程的改动会被父进程觉察到。 
    下图是linux toolbox里面的一张图,比较好,我就拷贝出来了(如果有侵权通知立删),很好的解释了COW的原理。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值