大家好,我是王鸽,今天分享一篇文章,主要讲解一下什么是Linux 非抢占式内核和抢占式内核。在Linux中,内核的抢占性是衡量内核任务在执行过程中是否可以被其他高优先级任务打断并抢占的特性。根据内核是否支持抢占,可以分为抢占式内核和非抢占式内核。非抢占式内核(Non-Preemptive Kernel)
在非抢占式内核中,一旦一个任务(进程或线程)进入内核态(例如通过系统调用),那么在这个任务主动让出CPU(如调用schedule()函数)或者完成内核态的工作返回到用户态之前,它不会被其他任务抢占。也就是说,内核代码会一直执行直到完成或主动放弃CPU。
特点:
内核代码的执行是串行的,不会在内核态发生任务切换(除非任务主动放弃CPU或中断处理程序使某个任务就绪,但也要等当前任务离开内核态才可能发生调度)。
进程在内核态执行时不会被强制中断
只有以下情况才会发生调度:
进程主动调用 schedule() 让出CPU
从内核态返回用户态时
阻塞等待资源(如I/O)时
被中断打断,且中断处理程序唤醒了更高优先级任务
优点
简化了内核同步,因为在内核态执行时不会被打断,所以很多临界区不需要额外的锁保护(但中断仍可能发生,所以中断处理程序与内核代码之间仍需同步)。
缺点
响应时间可能较长,因为高优先级的任务必须等待当前在内核态运行的任务退出内核态才能被调度。
工作流程:
用户态 → 系统调用 → 内核态执行 → 返回用户态 → 可能发生调度
抢占式内核(Preemptive Kernel)
在抢占式内核中,即使任务处于内核态,也允许在内核态的某些点上被更高优先级的任务抢占。这意味着内核代码的执行可能被中断,并且发生任务切换。
特点:
高优先级的任务可以抢占正在内核态运行的低优先级任务,从而减少响应时间,提高系统的实时性。
但是,由于内核代码可能被抢占,因此必须使用锁等同步机制来保护共享资源,防止竞争条件。
抢占式内核更适用于需要高响应性的系统,如实时系统或桌面系统,以减少用户交互的延迟。
工作流程:
用户态 → 系统调用 → 内核态执行 → 可能被抢占 → 恢复执行 → 返回用户态
区别
响应时间:抢占式内核的响应时间更短,因为高优先级任务可以立即抢占低优先级任务,而非抢占式内核则必须等待当前任务离开内核态。
同步机制:抢占式内核需要更复杂的同步机制来保护内核数据,因为内核代码可能在任何一点被抢占;非抢占式内核则相对简单,因为内核代码不会被打断(除了中断,但中断处理程序通常不会主动调度)。
适用场景:抢占式内核适用于对响应时间要求高的系统,如实时系统;非抢占式内核适用于对吞吐量要求高,而响应时间要求不高的系统。
Linux内核的抢占模式
Linux内核从2.6版本开始支持可选的抢占模式,并逐渐加强了对抢占的支持。在Linux内核中,可以配置三种抢占模式:
无抢占(No Forced Preemption):相当于非抢占式内核,主要用于服务器等对吞吐量要求高的场景。
自愿抢占(Voluntary Kernel Preemption):在内核代码中主动插入调度点,允许在某些安全点进行任务切换,但不能完全抢占。这种模式是一种折中,旨在提高响应性而不引入复杂的同步问题。
完全抢占(Preemptible Kernel):允许在内核的大多数地方进行抢占,类似于抢占式内核,适用于桌面和实时系统。
三种模式详解:
a) 无抢占模式(CONFIG_PREEMPT_NONE)
// 类似这样执行,不会被打断: syscall() { // 内核代码执行...// 不会被高优先级任务中断// 除非主动调用schedule()或返回用户空间 }
适用于:服务器、计算密集型应用
优点:吞吐量高,上下文切换少
b) 自愿抢占模式(CONFIG_PREEMPT_VOLUNTARY)
syscall() { // 内核代码执行...might_resched(); // 主动检查是否需要调度// 继续执行...}
内核在安全位置主动检查是否需要调度
平衡了响应性和吞吐量
c) 完全抢占模式(CONFIG_PREEMPT)
syscall() { // 内核代码执行...// 任何位置都可能被高优先级任务中断// 除非在临界区中使用preempt_disable()}
主要区别
特性 | 非抢占式内核 | 抢占式内核 |
|---|
响应时间 | 较长,需等待内核操作完成 | 较短,可立即响应高优先级任务 |
实时性 | 较差 | 较好,适合实时系统 |
内核复杂性 | 相对简单 | 更复杂,需要处理更多竞争条件 |
同步机制 | 需求较少 | 需要更多锁和同步机制 |
调度延迟 | 可能较大 | 较小 |
典型应用 | 早期Linux内核,服务器系统 | 桌面系统,实时系统,嵌入式系统 |
此外,Linux还提供了实时补丁(如PREEMPT_RT),使得内核可以支持硬实时。
如何查看和配置Linux内核的抢占模式
在编译内核时,可以通过配置选项选择抢占模式。在运行时,可以通过查看内核配置文件或使用命令查看当前内核的抢占模式。例如,在已经编译的内核中,可以通过查看/boot/config-$(uname -r)文件中的相关配置项:
CONFIG_PREEMPT_NONE=y # 无抢占CONFIG_PREEMPT_VOLUNTARY=y # 自愿抢占 CONFIG_PREEMPT=y # 完全抢占或者,在实时内核中,可能会看到:CONFIG_PREEMPT_RT=y # 实时抢占
实际示例
#include<linux/module.h>#include<linux/kthread.h>#include<linux/delay.h> static struct task_struct *task1, *task2; // 低优先级任务 staticintlow_prio_thread(void *data){ while (!kthread_should_stop()) { printk(KERN_INFO "Low priority task in kernel\n"); // 模拟长时间内核操作 mdelay(5000); // 睡眠5秒 } return 0; } // 高优先级任务 staticinthigh_prio_thread(void *data){ while (!kthread_should_stop()) { printk(KERN_INFO "High priority task ready\n"); // 在抢占式内核中,这个任务可以中断低优先级任务 msleep(1000); } return 0; }
// 禁用抢占(进入临界区) preempt_disable(); // 临界区代码 // ... // 启用抢占(离开临界区) preempt_enable(); // 检查是否可抢占 int preemptible(void);
# 查看抢占配置 $ uname -a Linux hostname 5.15.0-xx-generic # SMP PREEMPT ...# 查看内核编译选项 $ cat /boot/config-$(uname -r) | grep -i preempt # 测量调度延迟(需要rt-tests)$ sudo cyclictest -t1 -p 80 -n -i 10000 -l 10000
选择建议
服务器/数据中心:使用 CONFIG_PREEMPT_NONE,最大化吞吐量
桌面系统:使用 CONFIG_PREEMPT_VOLUNTARY,平衡响应和性能
实时系统/嵌入式:使用 CONFIG_PREEMPT,最小化延迟
硬实时需求:使用 CONFIG_PREEMPT_RT 补丁
总结
抢占式内核和非抢占式内核的主要区别在于任务在内核态执行时是否可以被抢占。抢占式内核提供了更好的响应性,但需要更复杂的同步机制;非抢占式内核则相反。Linux内核提供了多种抢占模式,可以根据应用场景进行选择。另外抢占式内核在SMP架构中尤其难以设计,因为在这些环境中,可能会有两个内核模式下的进程同时在不同处理器上运行。 抢占式内核更适合实时编程,因为它允许实时进程抢占当前在内核中运行的进程。此外,抢占式内核可能更具响应性,因为在等待进程之前,内核模式下的进程运行的时间不会任意延长。