【Linux系统】地址空间 && Linux内核进程调度队列

发布于:2024-04-20 ⋅ 阅读:(25) ⋅ 点赞:(0)

1.进程的地址空间

1.1 直接写代码,看现象

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int g_val = 100;
  5 
  6 int main()
  7 {
  8     int cnt = 0;
  9     pid_t id = fork();
 10     if(id == 0)
 11     {
 12         while(1)
 13         {
 14             printf("I am child process,pid:%d,ppid:%d, g_val:%d,&g_val:%p\n",getpid(),getppid(), g_val, &g_val);
 15             sleep(1);
 16             cnt++;
 17             if(cnt == 5)
 18             {
 19                 g_val = 300;
 20                 printf("I am child process, change:%d->%d\n", 100, 300);
 21             }
 22         }
 23     }
 24     else 
 25     {
 26         //father
 27         while(1)
 28         {
 29             printf("I am child process,pid:%d,ppid:%d, g_val:%d,&g_val:%p\n",getpid(),getppid(), g_val, &g_val);
 30             sleep(1);
 31         }
 32     }
 33     return 0;
 34 } 

 4cf5f3f48d1b4887a559d1b6eac793ae.png

 看到上面是执行结果,我们观察到,为什么g_val的数据被子进程改变,但父进程与子进程的地址仍然相同???

300,100说明父子进程是具有独立性的!!!

没错我们已经知道进程具有独立性这个概念!

那上面执行结果怎么理解呢?

进程=内核数据结构(struct stask)+代码和数据 我们之前已经知道代码是只读的,数据有w权限吗???

父子进程共用的全局变量地址一定不是物理地址,是虚拟地址!!!

1.2 引入最基本的理解(快速)

画图讲解

 

1.3 细节问题 -- 理解

独立性:多进程运行,需要独享各种资源,多进程运行之间互不干扰。

如果父子进程不写的?未来一个全局变量,默认是被父子共享的,代码是共享(只读的)

为什么要这么干??

可不可以把数据在创建子进程的时候,全部给子进程拷贝一份?

 

1.3.1 如何理解地址空间

a.什么是划分区域

其实 我们小学时可能就有划分区域这个概念了,小学时我们在分配座位坐好后,你可能与你的同桌共用一张桌子,那么你们必然要划分工作区域咯,我们当时叫它为38线,那么此时的划分区域也就类似这样,用代码怎么表述?

其实就是一个结构体!!!

来记录你的开始位置到结束位置!

对于区域的扩张与缩小其实就是对数据的+-了!!!

 

b.理解地址空间

 地址空间就像是操作系统给进程画的大饼似的,每次都给进程说想要多少空间我都能满足,但如果真的要求得到操作系统给不了的空间大小就无法分配相应空间了!!!

1.3.2 为什么要有地址空间

 

 实际的物理内存中, 代码区, 数据区, 栈区, 堆区, 共享区, 命令行参数和环境变量。

  1. 将无序变成有序,让进程以统一的视角看待物理内存以及自己运行的各个区域。
  2. 将进程管理模块和内存管理模块进行解耦
  3. 拦截非法请求 --- 对物理内存进行保护

1.3.3 如何进一步理解页表和写时拷贝

 

 当进程访问某个变量的地址时,经地址空间、页表OS会自动识别错误,

OS识别到错误

  1. 是不是数据不在物理内存(缺页中断)
  2. 是不是数据需要写实拷贝(发生写实拷贝)
  3. 如果都不是才进行异常处理
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6     pid_t id = fork();
  7     if(id == 0)
  8     {
  9         //child
 10         while(1)
 11         {
 12             printf("I am child process, id:%d, &id: %p\n", id, &id);
 13             sleep(1);
 14         }
 15     }
 16     else if(id > 0)
 17     {
 18         //father  
 19         while(1)
 20         {
 21             printf("I am father process, id:%d, &id: %p\n", id, &id);                                                                                                                   
 22             sleep(1);
 23         }
 24     }
 25     return 0;
 26 }

 

2.Linux真正的是怎么调度的?O(1)算法

nice并不能让你任意调整,而是有范围的![-20,19)40个数字

Linux系统中每一个CPU都有一个运行队列!

这个运行队列中包含活动队列和过期队列!

 

活动队列(只出不进)

时间片还没有结束的所有进程都按照优先级放在该队列

nr_active: 总共有多少个运行状态的进程
queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级

从该结构中,选择一个最合适的进程,过程是怎么的呢?
1. 从0下表开始遍历queue[140]
2. 找到第一个非空队列,该队列必定为优先级最高的队列
3. 拿到选中队列的第一个进程,开始运行,调度完成!
4. 遍历queue[140]时间复杂度是常数!但还是太低效了!
bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个
比特位表示队列是否为空,这样,便可以大大提高查找效率!
 


过期队列(只进不出)

过期队列活动队列结构一模一样
过期队列上放置的进程,都是时间片耗尽的进程
当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算 

active指针和expired指针
 

active指针永远指向活动队列
expired指针永远指向过期队列
可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。
没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!