【Linux学习笔记】进程概念(下)

进程地址空间

1. 虚拟地址

来看这样一段代码。

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

int global_value = 100;

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        printf("fork error\n");
        return 1;
    }
    else if(id == 0)
    {
        int cnt = 0;
        while(1)
        {
            printf("我是子进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value);
            sleep(1);
            cnt++;
            if(cnt == 10)
            {
                global_value = 300;
                printf("子进程已经更改了全局的变量啦..........\n");
            }
        }
    }
    else
    {
        while(1)
        {
            printf("我是父进程, pid: %d, ppid: %d | global_value: %d, &global_value: %p\n", getpid(), getppid(), global_value, &global_value);
            sleep(2);
        }
    }
    sleep(1);
}

运行结果:

img

上面的这种现象,是因为这里所打印出来的地址并非物理地址,而是虚拟地址。所以这里就引出了进程地址空间。下面用个例子来描述进程地址空间。

img

img

其中所画的大饼是需要管理起来的,好比老板昨天让员工好好干,一个月后升经理。但是今天老板见员工,却说一个月升总监。大饼画的多了,自然就需要管理起来,否则容易混乱。对于软件来说,管理的本质就是先描述,再组织。所以这里的一个个大饼,其实可以理解为是一个个的结构体。img

2. 什么是进程地址空间

  1. 进程地址空间的概念

上面讲过操作系统给进程画的大饼可以认为是进程地址空间,具体来说就是一个结构体。那结构体里面有什么呢?

首先,先做好规定,这里的背景是32位机器背景。

  1. 32位机器的数据可以有2^32个,约42亿,以字节为单位。
  2. 所以表示的空间大小约为4GB的空间范围。
  3. 地址确保唯一性,所以有2^32个地址。

所以,进程地址空间整体上应该是这样的,如下图:

img

对于上面的这个图,你可以想象成一把尺子。尺子是有刻度,所以就可以用刻度来划分区域。可以用一个结构体描述出上面的进程地址空间。如下结构体:

struct mm_struct
{
	unit_32t code_start;
	unit_32t code_end;
	unit_32t data_start;
	unit_32t data_end;
	unit_32t heap_start;
	unit_32t heap_end;
	//......
}

所以可以认为有这么一个进程地址空间对应着这么一个结构体,其中结构体的变量就是地址,这些地址就如同尺子上的刻度,划分好了区域。

img

其中堆栈空间是动态开辟的,所以当你写代码定义变量或者new变量的时候,其实就是在更改对应区域的start or end。

3. 进程地址空间的映射。

程序当加载到内存的时候,确实是加载到了物理内存里面,但是操作系统并不允许进程直接访问物理内存,而是在进程PCB里面存放一个进程地址空间,让进程地址空间通过页表和物理地址进行映射,从而让进程可以访问到物理内存。如图所示:img

所以进程是无法直接访问到物理地址的,是操作系统在管理进程的时候,同时给进程画了个大饼,让进程可以通过进程地址空间,再通过页表的映射,从而访问到物理地址。

4. 地址空间存在的意义

  1. 为了保护物理地址

设想一下,如果一个进程可以随意访问物理地址,然后这个进程将数据恶意写入到物理地址,将会破坏物理地址。操作系统为了保证物理地址的安全,就有了地址空间。通过地址空间的虚拟地址,再通过页表映射访问到物理地址,保证了物理地址的安全。

用压岁钱的例子来解释上述内容。你的压岁钱实际就是物理地址,但是中间有父母(页表)的存在,所以你确实是知道有那么多钱,但是当你用的时候,要通过父母的同意,如果父母觉得你的要求合理,那么就通过你的要求,让你拿到钱去买东西。如果要求不合理,那么父母将会拒绝你的要求,拒绝给你拿钱。

img

  1. 为了保证进程间的独立性

具体可看下面写时拷贝的内容。

5. 写时拷贝

回到我们的第一个代码打印结果的问题,可以看到两个值的地址明明一样,但是值却不一样。这是为什么呢?

是由于进程具有独立性,虽然两个进程共享数据,但是每个进程都是有独立的进程地址空间和独立的页表。

img

因为进程有独立性,所以先有一个进程改变了global_value的值,也不会影响另一个进程的值。这是因为在有一个进程改变值的时候,OS会先进行数据拷贝,在物理地址上开辟空间,拷贝进去,然后更改另一个进程页表的映射。

img

所以这也就是打印结果的时候,为什么地址明明一样,但是两个进程的数值却不相同,是因为操作系统帮进程做了写时拷贝的操作,写时拷贝对虚拟地址无影响。所以这也证明了,打印出来的并非物理地址,而是虚拟地址。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值