• 进程地址空间


    1.进程地址空间

    在这里插入图片描述

    1.进程地址空间认证

    代码

    #include 
    #include 
    #include 
    
    
    int un_g_val;
    int g_val=100;
    
    int main(int argc, char *argv[], char *env[])
    {
        printf("code addr         : %p\n", main);
        printf("init global addr  : %p\n", &g_val);
        printf("uninit global addr: %p\n", &un_g_val);
        char *m1 = (char*)malloc(100);
        char *m2 = (char*)malloc(100);
        char *m3 = (char*)malloc(100);
        char *m4 = (char*)malloc(100);
        static int s = 100;
        printf("heap addr         : %p\n", m1);
        printf("heap addr         : %p\n", m2);
        printf("heap addr         : %p\n", m3);
        printf("heap addr         : %p\n", m4);
    
        printf("stack addr        : %p\n", &m1);
        printf("stack addr        : %p\n", &m2);
        printf("stack addr        : %p\n", &m3);
        printf("stack addr        : %p\n", &m4);
        printf("s stack addr        : %p\n", &s);
        int i=0;
        for( i = 0; i < argc; i++)
        {
            printf("argv addr         : %p\n", argv[i]); //argv/&argc?
        }
        
        for( i =0 ; env[i];i++)
        {
            printf("env addr          : %p\n", env[i]);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这里插入图片描述
    可以看到上面的代码和前面我的发的图分布是
    堆区向地址增大方向增长,栈区向地址减少方向增长
    栈,我们一般在c函数定义的变量,通常都在栈上保存,那么先定义的一定是地址比较高的

    2.如何理解static变量

    c语言中我们了解到:如果一个变量在函数里定义声明为static,它的作用域不变,但是它的生命周期会随着程序一直存在
    眼尖的上面的图片就已经看到了
    在这里插入图片描述
    s被修饰成了static是时候他的地址也发生了改变

    3.当父子进程没有人修改全局数据的时候父子是共享数据的,但是如果尝试写入了?

    代码

    #include 
    #include 
    #include 
    
    int g_val=100;
    
    int main()
    {
        pid_t id = fork();
        if(id == 0)
        {
            //child
            int flag = 0;
            while(1)
            {
                printf("我是子进程:%d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);
                sleep(1);
                flag++;
                if(flag == 5)
                {
                    g_val=200;
                    printf("我是子进程,全局数据我已经改了,用户你注意查看!\n");
                }
            }
        }
        else 
        {
            //parent
            while(1)
            {
                printf("我是父进程:%d, ppid: %d, g_val: %d, &g_val: %p\n\n", getpid(), getppid(), g_val, &g_val);
                sleep(2);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    运行结果
    在这里插入图片描述
    可以看到父子进程读取同一个变量(因为地址一样),但是后续没有人修改的情况父子进程读取的内容却不一样
    如果是物理地址是不可能出现这种情况的,所以上面的不是物理地址,而是虚拟地址,也被称为线性地址

    上面那种情况相当于
    独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰

    比如有个操作系统是个富豪他有10亿家产
    而他有三个进程是他的儿子
    富豪就给三个儿子画饼,说给每个儿子10个亿
    但是因为不是马上拿10个亿的,因为它们根本用不了10个亿那么多,而是一点一点拿,而进程地址空间的作用就是让每个进程都拥有独立的资源

    4.为什么操作系统不给我们直接看到物理地址

    内存是一个硬件,不能阻拦你访问,只能被动的进行读取和写入,如果你能访问的话,那你就能随便的用指针来访问了,这样子就有可能一个进程越界改变了其他进程,甚至改变了操作系统的数据,导致整个系统都崩了

    每一个进程在启动的时候,都会让操作系统给他创建一个地址空间,这个地址空间是进程地址空间,操作系统是要管理这些进程地址空间的,先描述,在组织,进程地址空间,其实是内核的一个数据结构,stuct mm_struct

    5.进程地址空间到底是什么?

    所谓的地址空间:其实就是操作系统通过软件的方式,给进行提供一个软件视角,认为自己会独占系统的所有资源

    在这里插入图片描述

    大概简略是这样的有个页表区分区域来分别存放虚拟地址和物理地址,建立映射关系,可以通过虚拟地址来访问到物理地址
    而页表的产生是:将程序加载到内存有程序变成进程之后,由操作系统会给没一个进程构建一个页表结构

    6.程序被编译的时候,没有被加载的时候,由地址吗?有区域吗?

    答案:是有地址的,还记得链接吗?链接是把我们的程序和库产生关联,但是你没有地址怎么链接啊?
    而区域也是有的
    readelf -S 文件
    在这里插入图片描述
    那么可能有点懵了,因为没有被加载的时候都有地址了,那么加载的时候在干什么,这二个地址有什么关系
    比如 我在一个跑道里,左边是0,右边是100米,我现在是在30米的地方,这个是绝对地址,我傍边有棵树离我5米,这是相对地址,那么不管我左边或者右边的刻度怎么改变,变成100也好200也好,我和树的距离都不会发生改变,而这种相对地址可以理解成和程序开始的相对地址

    这个时候我们就能回答之前的问题了,为什么地址一样,但是父子进程打印不同的值,是因为它们的虚拟地址相同,但是物理地址不同了,因为进程具有独立性
    当我们子进程想要发生修改,那么操作系统就会给子进程重新开辟一段空间,并且把他的数据写实拷贝下来,重新给子进程建立新的映射关系,也就是改页表的右侧,不改左侧虚拟地址,所以才看到地址相同,但是数据不同

    7.为什么要有虚拟内存空间

    访问内存添加了一层软硬件层,可以对转化过程进行审核,非法的直接被拦截
    1.可以保护内存
    2.进程管理
    通过地址空间,进程功能模块的解耦
    3.让进程或者程序可以以一种统一的视角来看待内存
    方便以统一的方式来编译或加载所有的可执行文件
    简化进程本身的设计和实现

  • 相关阅读:
    基于Python的超市管理系统毕业设计源码111042
    2.3_6用信号量实现进程互斥、同步、前驱关系
    stdout stderr 重定向到文件
    Spring IoC源码:createBean
    ECMAScript 6 入门教程
    商标注册初审公告后可以使用吗?
    现在的各类解释非常混乱,到底什么是智慧城市?
    解密Docker容器网络
    关于我对线程安全问题中死锁的理解
    前馈-反馈控制系统设计(过程控制课程设计matlab/simulink)
  • 原文地址:https://blog.csdn.net/li1829146612/article/details/126686250