📝前言:
这篇文章我们来讲讲进程优先级和进程切换:
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
这里写目录标题
- 一,进程优先级
- 实时操作系统和分时操作系统
- 二,进程切换
- 1 保护上下文
- 2 Linux2.6内核进程O(1)调度队列
- Linux2.6内核中进程队列的数据结构
- queue[140]
- bitmap[5]
- nr_active
- a 队列和 e 队列
一,进程优先级
进程的优先级就是指:进程获取CPU资源的先后顺序。
在Linux中,优先级用task_struct
里面的一个整型表示,值越低,优先级越高。
我们运行起来一个进程,然后查看它的信息
- UID:用户id
- PRI:优先级(默认为80)
- NI:优先级的修正值(nice)
1,对UID的理解:
进程会通过UID来记录用户。
我们都知道一切命令和程序都是进程,那么访问文件的本质就是进程在访问。进程会用记录的UID来判断此ID拥有的权限,识别权限是识别进程和文件之间的权限。【一切行为实质上都是进程,用户用进程来和系统交互】
2,对PRI和NI的理解:
进程的优先级PRI = 默认值(80) + nice值
且nice值的取值范围是:[-20, 19]
,一共40个级别(这是有讲究的)
如果我们要修改一个进程的优先级,可以通过renice修改nice值:
renice [nice值] + 进程PID
但是进程的优先级不建议频繁修改。
实时操作系统和分时操作系统
- 实时操作系统:对外部事件做出快速响应,并且严格要求在一定时间内完成。优先级高的进程可以中断优先级低的进程。
- 分时操作系统:有时间片,轮流为进程提供服务。(即:尽管进程的优先级高,但是每次只能在CPU上运行一个时间片那么长的时间,运行完以后就被拿下来,然后运行其他进程)
举个形象的例子理解两种操作系统的运用场景:
1,汽车进行刹车操作:运用实时操作系统,即:一旦有刹车指令,刹车指令放在第一位,把刹车指令执行完才能运行别的
2,边运行代码边听音乐:运用的是分时操作系统,即:没有要求跑代码的时候音乐就暂停,而是两个进程交替在CPU上运行。(只是速度太快,让我们感觉两个在一起)
理论上,单个CPU在某一时刻,只能有一个进程在运行。
但某一时段,可以通过并发的方式,让我们感觉在运行多个进程。
比如只有两个进程:运行代码和听音乐,两个进程是交替获取CPU资源的,一个进程运行完一个时间片以后,就立马换成另一个,只是切换的速度太快了。
进程的特点:
- 竞争性(竞争CPU资源)
- 独立性(多进程运行间互相不干扰)
- 并行性(多个进程在多个CPU下分别,同时进行运行)
- 并发性(多个进程在⼀个CPU下采⽤进程切换的⽅式,在⼀段时间之内,让多个进程都得以推进)
二,进程切换
那么,一个进程被拿下来以后,下次再上CPU时,为什么能够延续上一次的位置继续运行呢?这是因为,数据会被记录。
1 保护上下文
CPU在运行进程时,主要关注数据和代码。CPU中的各种寄存器会用来记录当前进程的临时数据。(CPU的寄存器是一块空间,是数据临时存储的地方)
假如现在有一个进程A和一个进程B,保护上下文的流程:
- 进程A先运行,此时CPU中的寄存器就会记录进程A的各种数据,比如:运算(1 + 2) * 3,加数 2 和被加数 1 被存储到寄存器中,+这个指令也被存储到寄存器中,1+2得到的结果3也被储存到寄存器中
- 然后,A进程的时间片到了,这时候,B进程要上来
- 则A就会将CPU中寄存器的数据保存到
task_struct
中一个叫tss
的结构体(即硬件上下文数据,保存到task_struct
中的tss
),比如运算的临时结果3
,还有后续的*
操作等 - B上去以后,就会用自己的数据直接覆盖寄存器里面的数据,然后运行B进程
- 等到B的时间片运行完了,B像A一样自己记录自己的数据
- 然后到A运行,A又重新把上一次记录的数据放到寄存器里,然后接着运行
Linux内核0.11的task_struct
中的tss_struct
:
2 Linux2.6内核进程O(1)调度队列
⼀个CPU拥有⼀个runqueue
(调度队列),那调度队列的数据结构到底是怎么样?如何管理这些进程的?
进程A的时间片运行完以后,进程A又去了哪里,下一个要运行的进程B是怎么找到的,进程A又是怎么回来重新运行的?
Linux2.6内核中进程队列的数据结构
一个CPU一个runqueue
queue[140]
queue[140]
的底层是哈希表(用的是哈系统)
[0, 99]
:是实时优先级(我们不关心)[100, 139]
:是普通优先级,刚好对应 nice 值的40个优先级级别- 同一级别中的不同进程,是通过哈希桶的方式链起来的
找运行队列时:全局上从上到下看优先级,局部上(同一优先级),进程队列FIFO。
bitmap[5]
哈希桶的头插效率是O(1),因为queue[140]
大小固定,查找效率也是O(1),但是每次找进程都要从上到下遍历整个queue[140]
还是太慢了,于是有了位图unsigned int bitmap[5]
unsigned int
是 4
个字节,即:32
个bit位,长度为 5
的话就是 160
个比特位。于是从 0
开始 0 - 139
位就与queue[140]
的下标一一对应。想要知道哪个下标还有进程,就只需要查bitmap
,0
代表无进程,1
代表还有进程。
nr_active
nr_active
用来指明当前queue[140]
里的进程数量
a 队列和 e 队列
一个进程的时间片运行完以后被放到哪里呢?
这就要谈谈 e 队列,在上面的结构图中我们可以看到两份:
这两份一份是活跃队列(a 队列),一份是过期队列(e 队列),分别被*active
和 *expired
指针指向
进程的运行顺序以及交替:
- 首先,我们在 a 队列里面挑进程:全局上从上到下(利用bitmap),局部上,FIFO(直接拿链表第一个)
- 当一个进程的时间片运行完以后,这个进程会从 a 队列移动到 e 队列
- 直到所有 a 队列里面的进程都被执行过了一遍(即:
nr_active == 0
) - 然后,会交换
*active
和*expired
指针,再运行 a 队列,重复上面的过程
(这就是O(1)调度算法:在系统当中查找⼀个最合适调度的进程的时间复杂度是⼀个常数,不随着进程增多⽽导致时间成本增加)
其实,上述过程中进程在 e 队列时的状态就是操作系统中的就绪状态,不过Linux中不对就绪状态和R状态进行区分。
还有两个小细节:
1,当一个进程的优先级被改了,这个进程怎么移动位置呢?
答:当一个进程的优先级被改,本次的PRI是不变的,等到本次从 a 队列中运行完以后到了 e 队列,下次从 e 队列回到 a 的时候,在重新计算,改变位置
2,当有新进程加入时,新进程进 a 还是 e
- 一般情况:新进程进 e
- 内核抢占情况:新进程进 a
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!