(入门自用)--Linux--程序地址空间--程序的创建--0907-0913

 程序地址空间

问题引入

#include <stdio.h>
#include <unistd.h>
int g_val=100;
int main()
{
    pid_t id=fork();
    if(id==0)
    {    
        int cnt=0;
        //子进程
        while(1)
        { 
            printf("child pid:%d,ppid %d,g_val: %d,&g_val: %p\n",
                getpid(),getppid(),g_val.&g_val;)
            sleep(1);   
            cnt++;
            if(cnt==5)
            {
                g_val=200;
            }
                
        }
    }
    else
    {
        while(1)
        { 
            printf("father pid:%d,ppid %d,g_val: %d,&g_val: %p\n",
                getpid(),getppid(),g_val.&g_val;)
            sleep(1);       
        }

    }


}

 结果: 发现地址相同而值不同。结论:地址不是真实的物理内存地址,而是虚拟地址(线性地址)。内核中的地址空间,本质将来也一定是一种数据结构。

地址空间分布

命令行参数环境变量
共享区
未初始化数据
初始化数据
正文代码

在Linux环境下发现虚拟地址符合上述空间分布规则。

注意:

  1. 堆区的申请不连续,原因是在申请的空间之后会有一些空间被用来记录申请空间的属性。
  2. 正文代码中包含了字符常量区。

static变量

static修饰局部变量的本质:将该变量开辟在全局区域。

static的意思就是把局部变量变成全局变量。

地址空间(数据结构)

地址空间是一种内核数据结构,它里面至少要有:各个区域的划分。

stuct addr_room
{
    int code_start;
    int code_end;
    
    int init_start;
    int init_end;

    int uninit_start;
    int uninit_end;

    int heap_start;
    int heap_end;

    int stack_start;
    int stack_end;
    
    //...其他属性

}

程序的地址

程序在编译的时候,形成可执行程序的时候,没有被加载到内存中的时候,程序就已经有地址了。其实可执行程序编译的时候,内部就已经有地址了。

地址空间不仅操作系统内部遵守,编译器也遵守。即在编译时,就已经给我们形成了各个区域。并且和Linux内核中一样的编址方式,给每一个变量,每一行代码都进行了编址。所以在程序编译时,每个字段早已经具有了一个虚拟地址。

为什么要有程序地址空间

更安全

地址空间和页表是操作系统创建并维护的。所以使用地址空间和页表(单位为页框。页和页框之间通过MMU调度)进行映射,也一定要在操作系统的监管之下。也便保护了物理内存中的所有的合法数据,包括各个进程,以及内核的相关有效数据。

解耦合

因为有地址空间和页表的存在,未来的数据可以加载到物理内存的任意位置。所以内存管理模块(物理内存的分配)和进程管理模块就完成了解耦合。

如果我申请了物理空间,但是如果我不立马使用,这就构成了空间的浪费。采用延时分配的策略,来提高整机的效率。所以我们在使用malloc和new申请空间的时候,本质上是在虚拟地址空间上申请的。因为有地址空间的存在,物理内存可以一个字节都不分配。而当真正进行对物理地址空间访问的时候,才会执行内存的相关管理算法,申请内存,构建页表映射关系,再进行内存的访问。这个过程由操作系统自动完成,用户,进程完全不知道。

内存分布有序化

因为理论上数据可以在物理内存的任意位置加载,所以物理内存中的几乎所有的数据和代码在内存中是乱序的。但是因为有页表存在,在进程的视角所有的内存分布都可以是有序的。

进程的独立性

地址空间是操作系统给进程画的大饼,因为有地址空间的存在,每一个进程都认为自己拥有4GB空间(32位)。进程要访问的物理内存中的数据和代码,可能目前并没有在物理内存中。同样的,也可以让不同的进程一映射到不同的物理内存。因为有地址空间的存在,进而可以通过页表映射到不同的区域来实现进程的独立性。每一个进程都不知道,也不需要知道其他进程的存在。

什么是挂起

加载本质就是创建进程。进程本质上就是对应的代码和数据(从磁盘中来)+内核数据结构(操作系统)。而加载并不是必须非得立马把所有的程序的代码和数据加载到内存里,并创建内核数据结构建立映射关系。在极端情况下,程序可能只有内核结构被创建出来了(即pcb和页表),并没有被分配物理内存。此时该程序处于新建状态。由此可以发现,理论上可以实现对程序的分批加载(唤入),那也就可以分批唤出。当一个进程短时间不会再被执行了,比如进入了阻塞状态,进程的数据和代码被唤出了,就叫做挂起了。

程序的创建

fork

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

创建子进程,给子进程分配对应的内核结构,必须子进程自己独有,因为进程具有独立性,理论上子进程也要有自己的代码和数据。一般而言,子进程没有加载的过程,子进程只能“使用”父进程的代码和数据。

代码:都是不可被写的,只能读取。父子进程共享没有问题。

数据:可能被修改,所以必须分离。但有些数据可能不会用到,或者用到了也可能仅是读取。所以采用了写时拷贝的操作。

因为有写时拷贝技术的存在,所以父子进程可以彻底分离.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值