Linux学习记录——십삼 程序地址空间


1、了解程序地址测试代码

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

int g_value = 100;
int main()
{
    pid_t id = fork();
    assert(id >= 0);
    if(id == 0)
    {
        //child
        while(1)
        {
            printf("我是子进程, 我的id是: %d, 我的父进程是: %d, g_value: %d, &g_value: %p\n", getpid(), getppid(), g_value, &g_value);
            sleep(1);
        }
    }
    else
    {
        //father
        while(1)
        {
            printf("我是父进程, 我的id是: %d, 我的父进程是: %d, g_value: %d, &g_value: %p\n", getpid(), getppid(), g_value, &g_value);
            sleep(2);
        }
    }
}

在这里插入图片描述

如果在子进程循环里写上g_value++,虽然它是全局变量,但对父进程没有影响。

在这里插入图片描述

这就是进程具有独立性。之前写过进程 = 内核数据结构 + 代码和数据,两个各自也都有独立性,保证相互独立的做法就是写时拷贝。

再看最后的地址。全都一样。g_value经过写时拷贝会变成两个变量,打印出来的地址应当是不一样的。如果是物理地址,读取同一个变量的地址,那么不会出现不同。子进程对变量进行了修改,但是父进程没有读取到,所以这个地址并不是物理地址。那么现在用的这个地址叫虚拟地址或者线性地址。

2、理解程序地址空间

操作系统会给进程分配好该有的内存量,比如有10g内存,3个进程,分别给他们仨3 3 4g内存。系统要管理进程地址空间,空间本质是一个内核数据结构,struct mm_struct{}。进程在申请空间时,不会直接把所有分配给的空间全要上,只要够这次程序用的空间,如果要得太多,系统也会拒绝。

地址空间会被分成很多区,比如栈区堆区等。所以存入地址空间的数据最终都会进入物理内存中。程序在访问数据时会访问地址空间,用的就是虚拟地址。

程序地址空间的各个区域如何理解?

区域的划分就是对线性区域进行指定start和end即可完成区域划分。程序地址空间是一个线性区域,从低地址到高地址分布,32位下每个地址是1个字节。这和真实内存一样,32位机器下,CPU访问内存中某个地址或者数据时,会先编址,编成只有0和1,而CPU与内存之间有2的32次方种连接,也就是2的32次方个地址,差不多4G,CPU就需要在这里面寻址,CPU寻址的最小内单位是1字节。程序地址空间也是如此,地址是连续的,每个都保证了唯一性,所以是线性区域,每个地址所能存储的最大空间是1字节。程序地址空间的地址是从0x全0到0x全F。

一个整数存进去时,占连续的4个字节,而整数的地址就是4个字节中最低的那个地址,数据类型则确定了从这个地址往后读几位。

程序地址空间是一个线性空间,系统对它的大小是早就计算好的,32位和64位的源代码不一样,32位的结构体就直接是4G。空间中每个区域会这样定义:

long code_start
long code_end
long init_start
long init_end

每个区域对应各自的变量名,init_start就是初始化区域的起始位置。

如果限定了区域,区域之间的数据就是虚拟地址或者线性地址。

区域的扩大缩小就是修改边界值start和end。

虽然有程序地址空间,但实际上数据只能存在内存中。虚拟地址在系统中会通过页表转为物理地址。

也就是说,程序声明一个变量,这个变量会在内存中有物理地址,在地址空间有对应的虚拟地址,当程序接下来使用这个变量时,CPU会把这个变量编址,编成一个地址,拿着这个地址去地址空间的划分好的各个区域去找,找到了,就通过页表链接到物理地址,也就真正找到了这个变量,然后对它进行操作。

-------------------------------------------------------------

回到刚才的问题,子进程修改数值不影响父进程。父进程和子进程都有各自的地址空间,通过页表连接到物理空间,当子进程想修改数据,内存会给他再申请另一块空间,数据也拷贝过去,子进程就连接到这个新的空间,所以他们的虚拟地址一样,但是连接到物理空间后数值不一样,因为在物理空间中访问的空间不一样,物理空间的地址不一样。

-------------------------------------------------------------

代码中还有一个问题,为什么if和else都能运行?这是因为fork在返回的时候,父子进程都有了,所以会return两次,id是pid_t类型定义的局部变量,而return的本质,就是在写入,所以谁先返回,谁就会让操作系统发生写时拷贝。

3、程序地址空间存在的意义

如果只有物理内存,那么一个个程序在写入数据时,如果存在越界,或者野指针,那么其他区域的数据就可能被改变了,所以引入地址空间和页表,通过地址空间和页表向物理空间的映射,系统检测映射成功后就可以找到地址和数据。

malloc的本质

malloc申请的时候,返回的也是物理空间上一个堆区的地址,也要通过地址空间和页表。申请并不一定会马上就给空间,而是系统认为你需要才会给你,系统一定要以高效为主。malloc申请空间时,系统开辟空间后,会先进入闲置状态,这时候别人无法用,并且页表也不会去映射,物理内存也没有开辟空间,页表倒是有虚拟地址。当用户开始写入时,系统就会重新做一遍,把映射加上,物理上开辟空间,用户才能正常用,这就是缺页中断。

地址空间的调整,和页表的连接属于进程管理,页表和物理空间的映射,物理空间的管理属于内存管理。

重新理解地址空间

源代码被编译的时候,会按照虚拟地址空间的方式对代码和数据进行编制,地址空间不仅会影响系统,也会让编译器遵守它的规则。

Linux中可执行程序的格式一般是ELF格式。

读取程序的时候,数据中包含的地址都是虚拟地址,用来更便利地找到相应的物理地址。程序中的地址是虚拟地址,内存中是物理地址。

地址空间和页表存在的意义:
1、防止地址随意访问,保护物理内存与其他进程
2、进程管理和内存管理进行解耦合
3、可以让进程以统一的视角看待自己的代码和数据

结束。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值