linux进程地址空间

进程地址空间

进程的地址空间是进程可用于寻址内存的地址集合, 包括进程的物理地址空间和虚拟地址空间。操作系统有虚拟内存和物理内存的概念, 但是在很久之前, 操作系统只有物理内存的概念, 程序寻址都是用的物理地址, 寻址空间的大小取决于 cpu 地址线条数, 在 32 位的操作系统上, 寻址的范围是固定的(最多 4G).。也就是说, 每次运行一个程序, 都会给进程分配 4G 的物理内存, 这样就带来了很多麻烦:

  1. 内存浪费: 每个进程固定分配 4G 物理内存, 对内存要求太高; 并且物理内存的分配是连续的且随机的, 不能充分利用物理内存, 进而造成内存浪费
  2. 效率低下: 内存得不到充分利用, 就会产生过多的进程等待, 等待其他进程 结束后再被载入内存, 操作频繁, 效率低下
  3. 安全性低: 进程中的指令都是直接访问物理内存的, 这就可能影响其他进程 甚至破坏内核空间, 安全性低
虚拟地址空间
  • 虚拟地址空间概念:

    虚拟地址空间就是操作系统为进程所描述的一个假的地址空间,目的是为了让进程认为自己拥有一块连续的线性的完整的地址空间。但是实际上一个进程使用的内存并不是连续存储的,而是通过页表映射了虚拟地址与物理地址之间的关系。让进程通过页表获取物理地址,进而实现数据的离散式存储。

  • 虚拟地址空间组成:

    虚拟内存空间包括内核空间和用户空间, 在 32 位的 Linux 操作系统上, 1G(高地址)是内核 空间, 3G 是用户空间。
    在这里插入图片描述

  • 虚拟地址空间作用:

    ①:提高内存利用率(物理内存的离散存储)
    ②:保证了进程的独立性(每个进程都只能访问自己虚拟地址映射的物理内存)
    ③:页表可以进行内存访问控制(页表可以对每个虚拟地址进行权限标记)

虚拟内存工作流程
  • 1.创建一个进程得到PCB(进程控制块(即task_struct这个结构体))
  • 2.在 task_struct 中可以找到 内存描述符( mm_struct )。mm_struct 是用来描述进程地址空间的, 每一个进程都有唯一的进程地址空间, 即每个进程拥有唯一的 mm_struct 结构体,通过 mm_struct 将可执行程序映射到虚拟内存空间。
  • 3.建立虚拟内存和物理内存的映射
写时拷贝技术
  • 父进程创建一个子进程,父子进程分别打印自己的pid、value值、value地址。
#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
int main() {    
  int var = 1;    
  pid_t pid = fork();    
  if (pid < 0) {                                                                                                                        
    perror("fork error");    
    exit(-1);    
  } else if (pid == 0) {    
    printf("child &var = %p\n", &var);    
    printf("child var = %d\n", var);    
  } else {    
    printf("parent &var = %p\n", &var);    
    printf("parent var = %d\n", var);    
  }    
  return 0;    
}    

程序运行如下:
在这里插入图片描述
结果发现父子进程打印的变量值和地址都相同,因为子进程并未对变量进行任何修改,我们在修改上述代码,在子进程中将 var 值改为 2,再次运行:

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
int main()
{    
  int var = 1;    
  pid_t pid = fork();    
  if (pid < 0) {                                                                                                                        
    perror("fork error");    
    exit(-1);    
  } else if (pid == 0) {
  	var = 2;    
    printf("child &var = %p\n", &var);    
    printf("child var = %d\n", var);    
  } else {    
    printf("parent &var = %p\n", &var);    
    printf("parent var = %d\n", var);    
  }    
  return 0;    
}  

程序运行结果:
在这里插入图片描述
这时候,父子进程输出地址是一致的,但是变量内容不一样,可以说:

变量内容不一样,所以父子进程输出的变量绝对不是同一个变量 ,但地址值是一样的,说明该地址绝对不是物理地址! 在Linux地址下,这种地址叫做 虚拟地址 ,我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理

所以有父子进程”代码共享,数据独有“:

  • 代码共享:由于代码是只读的,不能被修改(只读的访问控制由页表来控制),子进程和父进程代码会一直映射同一块物理内存,所以这就是代码共享。
  • 数据独有:但是在子进程中修改变量的值, 却不影响父进程中变量的值,子进程中变量发生改变时,此时才会新开辟物理空间给子进程使用,并且改变页表到物理地址的映射关系。

为了理解 “代码共享,数据独有” ,需要理解 写时拷贝技术

写时拷贝故名思意:是在写的时候(即改变字符串的时候)才会真正的开辟空间拷贝(深拷贝),如果只是对数据的读时,只会对数据进行浅拷贝。
写时拷贝:引用计数器的浅拷贝,又称延时拷贝。
:写时拷贝技术是通过"引用计数"实现的,在分配空间的时候多分配4个字节,用来记录有多少个指针指向块空间,当有新的指针指向这块空间时,引用计数加一,当要释放这块空间时,引用计数减一(假装释放),直到引用计数减为0时才真的释放掉这块空间。当有的指针要改变这块空间的值时,再为这个指针分配自己的空间(注意这时引用计数的变化,旧的空间的引用计数减一,新分配的空间引用计数加一)。

写时拷贝是一种可以推迟甚至避免拷贝数据的技术。子进程拷贝了父进程的PCB和页表,当子进程中变量发生改变时,此时才会新开辟物理空间给子进程使用,并且改变页表到物理地址的映射关系(虚拟地址没变,但映射的物理地址已经变化)

当子进程中改变变量 var 的值的时候,通过写实拷贝技术开辟一块新的物理空间给子进程使用,并将变量a的值拷贝到新的物理空间并改变其值为2,并且改变页表到物理地址的映射关系:

创建子进程的过程:
①:fork( )通过复制调用进程(就是复制了PCB),创建一个新的进程(子进程)
②:子进程拷贝父进程PCB中的数据(拷贝后子进程与父进程有相同的虚拟地址空间,相同的页表)
③:父子进程一开始映射同一块物理内存
④:等到物理内存内容修改的时候,系统才会为子进程重新开辟一块物理内存,拷贝数据过来。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值