作者:爱写代码的刚子
时间:2023.10.20
前言:本篇博客是对【Linux进程篇】的收尾,介绍进程地址空间的相关内容。本篇博客研究的背景:kernel 2.6.32 32位平台
再次认识空间布局
其中:未初始化数据和初始化数据属于静态全局区。
命令行参数比环境变量更靠近高地址
注意:不同芯片平台栈的增长方向可能不同,栈顶指针用esp寄存器存储,同时不管数组在栈区还是堆区,数组的生长方向都是从下往上的(也就是从低地址到高地址的)。
- 验证一(同时不管数组在栈区还是堆区,数组的生长方向都是从低地址到高地址的):
观察一个现象:
在父子进程中分别对a进行修改,但是我们发现a的地址都为同一个,说明同一个变量在同样的地址有不同的值,如何解释?
-
变量内容不一样,所以父子进程输出的变量绝对不是同一个变量
-
但地址值是一样的,说明该地址绝对不是物理地址!
-
在Linux地址下,这种地址叫做虚拟地址
-
我们在用C/C++语言(或者其他语言)所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理,OS必须负责将虚拟地址转化为物理地址。
进程地址空间
什么叫做地址空间?
所谓的进程地址空间,本质是一个描述进程可视范围的大小,地址空间内一定要存在各种区域划分,对线性地址进行start,和end即可,在32位计算机中,有32位的地址和数据总线,所有地址总线排列组合形成地址范围[0,232],232 * 1byte也就是4GB大小
- cpu内存在地址寄存器(AR),保存当前CPU所访问内存单元地址
- 虚拟地址和物理地址通过页表进行映射(页表是一种软件结构)
- 当子进程创建时,子进程会继承父进程的虚拟地址空间,也就是说子进程和父进程指向同一块物理空间,当子进程需要对数据进行修改时,物理空间会为子进程新开辟一块空间,存储子进程修改的数据(类似于写时拷贝)
进程地址空间图示及笔记
从以上图可以知道,同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址。
- 所以,什么叫进程?
进程 = 内核数据结构(task_struct && mm_struct && 页表)+ 程序的代码和数据
所以进程在切换时task_struct && mm_struct && 页表都要进行切换
- 内核数据结构上,进程之间是互相独立的
- 不同进程通过页表进行映射,将虚拟地址空间和物理地址空间进行解耦合,即便是父子进程关系只需要在页表层面上,代码区指向一样,数据区指向不一样,在数据层面上解耦合。进程若触发异常,只需要处理各自页表映射的数据即可
- 因为有页表的存在,数据加载在物理内存的位置我们并不关心,我们只关心页表的映射关系即可。以统一的视角看待内存,让无序变有序
- 环境变量是全局属性,因为子进程会根据父进程页表的映射关系,建立自己的页表
Linux2.6内核进程调度队列(Linxu进程篇2有涉及)
【Linux进程篇】进程概念2
如果有多个CPU就要考虑进程个数的负载均衡问题 优先级
一个CPU拥有一个runqueue(运行队列)
普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!) 实时优先级:0~99(不关心)
活动队列
- 时间片还没有结束的所有进程都按照优先级放在该队列
- nr_active: 总共有多少个运行状态的进程
- queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
- 从该结构中,选择一个最合适的进程,过程是怎么的呢?
- 从0下表开始遍历queue[140]
- 找到第一个非空队列,该队列必定为优先级最高的队列 3. 拿到选中队列的第一个进程,开始运行,调度完成!
- 遍历queue[140]时间复杂度是常数!但还是太低效了!
bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个 比特位表示队列是否为空,这样,便可以大大提高查找效率!
过期队列
- 过期队列和活动队列结构一模一样
- 过期队列上放置的进程,都是时间片耗尽的进程
- 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
active指针和expired指针
- active指针永远指向活动队列
- expired指针永远指向过期队列
- 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。
- 在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程!
总结
- 在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法。