程序地址空间

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都有一个运行队列,运行队列中有array[0]和array[1]两个数组,

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

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

我们一般把array[0]叫做活跃队列,array[1]叫做过期队列

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

相关推荐
302wanger8 分钟前
ARTS-算法-长度最小的子数组
算法
_Djhhh12 分钟前
权限系统设计方案实践(Spring Security + RBAC 模型)
java·linux·数据库·spring
烤鱼骑不快20 分钟前
dockerfile
运维·docker·容器
天上掉下来个程小白31 分钟前
开发环境搭建-06.后端环境搭建-前后端联调-Nginx反向代理和负载均衡概念
java·运维·spring boot·后端·nginx·负载均衡·苍穹外卖
KoiC31 分钟前
内网环境部署Deepseek+Dify,构建企业私有化AI应用
linux·人工智能·ubuntu·docker·大模型·ai应用·deepseek
lizz3133 分钟前
机器学习中的线性代数:奇异值分解 SVD
线性代数·算法·机器学习
MSTcheng.38 分钟前
【C语言】动态内存管理
c语言·开发语言·算法
不去幼儿园42 分钟前
【启发式算法】Dijkstra算法详细介绍(Python)
人工智能·python·算法·机器学习·启发式算法·图搜索算法
BestRivenCHN44 分钟前
vscode离线配置远程服务器
linux·ide·vscode
serve the people1 小时前
神经网络中梯度计算求和公式求导问题
神经网络·算法·机器学习