抢占的概念
linux内核支持抢占, 可以通过抢占相关的编译宏打开或者禁止抢占。
一个线程在一个cpu上运行, 不是这个线程主动让出cpu导致的其他线程跑到这个cpu上
的情况都是抢占。关抢占就是禁止这样的行为发生。
线程主动让出cpu的行为有,比如一个线程发送了一个I/O任务,然后调用
wait_for_completion睡眠等待completion。其他的各种行为我们都认为是内核抢占行为,
比如,周期性的tick中断中调度器把另外一个线程调度到cpu上跑;各种中断程序里,
触发了调度把另一个线程调度到cpu上跑。
所以,内核里一段程序之前关了内核抢占之后又把内核抢占打开,如果这块代码中没有
主动让出cpu的行为,我们可以认为这段代码在一个cpu上会持续的执行完,中间不会被
打断。但是,这里说的是单个cpu上的情况, 当这段代码里有访问全局的数据结构的时候,
对于那个全局的数据结构,还是可能和其他cpu上的程序并发访问的。
开关抢占的实现
可以看到禁止抢占这个宏就是给task_struct结构里的preempt count加引用计数。
1 | #define preempt_disable() \ |
开启抢占是先减引用计数, 然后执行调度。
1 | #define preempt_enable() \ |
是否可以抢占的判断标准除了如上preempt的计数外,还有中断的情况。如果中断是关闭的
同样认为是不能抢占的。
1 | #define preemptible() (preempt_count() == 0 && !irqs_disabled()) |
但是,从上面可以看出来,禁止抢占并没有关中断,在禁止抢占的上下文里,中断依然可以
进来,中断处理完后内核会进行调度,在禁止抢占时,内核依然调度被中断打断的线程运行,
这样从总体上看之前的线程没有被其它线程抢占。
关抢占的用途
关抢占的目的就是叫一段代码不被内核调度打断下执行完,因为有的时候换一个程序进来
跑可能把原来程序的数据破坏掉。比如内核zswap里,在每一个cpu上分配了压缩解压缩
的tfm,然后用per-cpu变量保存相应的内容,他在使用这些per-cpu变量的时候就需要
关闭内核抢占, 不然中途换另一个线程在cpu上跑就会有数据不一致的问题。
可以看到在使用per-cpu变量的宏里,一进来就把抢占关了:
1 | #define get_cpu_ptr(var) \ |