程序地址空间

1.进程的地址空间

我们看一段代码

复制代码
#include <stdio.h>
#include <unistd.h>

int g_val = 100;
int main()
{
    printf("father is running, pid: %d, ppid: %d\n", getpid(), getppid());
    pid_t id = fork();
    if(id == 0)
    {
        //child
        int cnt = 0;
        while(1)
        {
            printf("I am child process, 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 = 300;
                printf("I am child process, change %d -> %d\n", 100, 300);
            }
        }
    }
    else
    {
        //father
        while(1)
        {
            printf("I am father process, pid: %d, ppid: %d. g_val: %d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }
    }
}

我们可以看到,我们创建的父进程和子进程,都指向g_val,当子进程改变g_val的值,但是父进程指向的g_val的值还是没有变的,而且二者的地址都是一样的,这是为什么呢?

1.地址空间

由于父子进程输出变量的内容不一样,而且进程具有独立性,所以我们可以推断,这绝对不是同一个变量,那么相同的地址又是怎么回事呢?这就有了地址空间的概念了

在操作系统内部,会有虚拟地址空间,通过页表的映射,访问物理内存空间,由于子进程拷贝的是父进程的数据,所以同一份代码,父子进程是共享的,当子进程要修改g_val的值时,又为了不影响父进程对g_val的使用,就有了写时拷贝,子进程会把父进程的很多数据结构都拷贝一份,包括虚拟地址空间,本质上虚拟地址空间也是一个结构体,当操作系统发现子进程的要更改的数据和父进程指向的数据冲突了,才会进行写时拷贝,在物理内存上开辟g_val的空间,子进程通过页表映射的物理内存地址也就和父进程页表映射的g_val的地址不一样了,

写时拷贝的意义?

因为每次创建子进程,都会拷贝父进程的代码和数据,消耗比较大,所以对于一些能够共享的代码,就尽量不拷贝,父子进程共享代码,当子进程需要修改代码而且影响父进程时,才会发生写时拷贝,达到有效节省空间的目的

2.地址空间的本质

地址空间其实时一个结构体,在linux中以mm_struct命名,地址空间其实是一个虚拟的空间,他通过比较有序的管理地址的方式,创造了遍历,因为在真实物理地址空间里,数据的分布是杂乱的,堆空间,栈空间,还有代码段等等,不好管理,所以通过虚拟地址空间,然后经过页表的映射,将无序变成有序,让进程以统一的视角看待物理内存以及自己运行的各个区域,

还可以拦截非法请求,当我i们访问的虚拟地址空间中的一个地址,在页表中没有对应的物理内存地址,就会拦截请求,所以地址空间还是比较好的

2.linux O(1)算法

linux系统中每一个cpu都有一个运行队列,运行队列中有array0和array1两个数组,

nr_active表示有多少个运行状态的进程,

bitmap5是一个位图,在32位平台下,32*5能表示160个数据单位,而我们的优先级只有140个,对于优先级,有实时优先级:0-99,我们先不考虑,100-139是普通优先级,所以bitmap 5可以快速查找非空队列,queue140:一个元素就是一个进程队列,相同优先级的队列,按照FIFO(先进先出)规则进行排队调度,不同优先级按照下标对号入座,

我们一般把array0叫做活跃队列,array1叫做过期队列

active指针指向活动队列,expired指针指向过期队列,活动队列包含了当前时间片尚未运行完的进程,当cpu执行进程时,会先把活动队列上的进程执行完,执行完的进程转移到过期队列中,当active队列通过biemap5发现队列执行完了,就会交换active和expired指针,然后过期队列就变成了活跃队列,这个操作时O(1)的,效率很高,所以叫做进程调度O(1)算法

相关推荐
GuWenyue1 小时前
传统Agent工具两大痛点!300行代码落地MCP跨语言工具,彻底解耦LLM与工具
前端·人工智能·算法
我叫洋洋3 小时前
C ++ [ hello world ]
c语言·c++·算法
一叶龙洲4 小时前
wslg打开Ubuntu24.04默认打开图形界面
linux·服务器·数据库·ubuntu
奋发向前wcx4 小时前
y1,y2总复习笔记5 2026.7.19
数据结构·笔记·算法
敖行客 Allthinker5 小时前
Parallels Ubuntu虚拟机项目如何让手机访问?完整解决方案
linux·运维·ubuntu
keyipatience7 小时前
线程栈与TLS和线程互斥
java·linux·服务器·开发语言·ubuntu
元Y亨H7 小时前
生产环境监控与故障应急处理(5-5-5 标准)指南
运维
j7~7 小时前
【Linux】二十二.《Linux 信号机制完全指南:表示、捕捉与处理》
linux·信号处理·volatile·可入重函数·保存信号
战族狼魂7 小时前
高频面试题精选:分治与AI Agent架构
人工智能·算法·大模型·大语言模型
李剑一7 小时前
你用过网易的将军令嘛?它底层实现账号保护的原理相当简单粗暴
算法