抢占的含义
抢占指的是强制使一个任务让出 CPU 给其他任务。
抢占是调度器做的,每次执行schedule()
就可能发生一次抢占,所以
抢占发生的地点是内核,也就是schedule()
的执行环境。
用户抢占
与内核抢占相对应的是用户抢占,用户抢占不是指抢占发生的地点,因为 上面说了抢占发生的地点一定是内核。
所以用户抢占的含义是:抢占的时机是用户态,换句话说就是抢占发生之前, 系统正处于用户态。
用户抢占的经典场景是时钟中断,用户进程 1 执行的好好地,被时钟中断打断 然后中断返回时执行重调度,选择了新的用户进程 2。其他的可能用户抢占的场景 还有系统调用返回时, 总之是内核返回用户态时都会发生用户抢占。
内核抢占
启用内核抢占增加了系统中发生抢占的点,即抢占前系统正处于内核。
当一个进程正处于内核态执行任务时,比如执行mmap()
系统调用的任务,在
未开启内核抢占的情况下,中断返回时只可能继续执行当前进程的任务,不会
发生调度。
当启用内核抢占时,上述情况下若发生中断,系统在退出中断后,即使此时不是
返回用户态,也可以执行schedule()
,即可以发生抢占。此之谓内核抢占。
抢占发生的条件
启用内核抢占之后,其实抢占的过程也不区分用户态和内核态,只要满足条件都会
执行schedule()
。
执行重调度的条件有两个:
- 是否需要重调度?
- 是否可以重调度?
是否需要重调度也就是何时执行schedule()
的问题,大概包含以下的场景:
- 时钟中断
- 新进程创建
- 修改进程的 nice 值
- 中断返回内核态
- 内核恢复为可抢占(下面会介绍)
然而有一些情况不可以重新调度,比如内核中一些关键的步骤,那些不能被打断的 原子操作。
在关键步骤之前,需要调用preempt_disable()
,此时 linux 会在 tcb 中会改变
preempt_count
的值,这个操作不是关闭中断,而是在中断返回时即使有更高优先级的其他进程,
只要该值不符合要求,重调度也不会发生。
关键步骤执行完,调用preempt_enable()
,此时为了去满足关键区域内可能
有新加入的高优先级进程,会调用一次重调度,这也正是上面所说需要重调度的场景之一。