Linux内核24-内核同步理解

简介: Linux内核24-内核同步理解

1 引言


我们可以把内核想象成一个服务器,专门响应各种请求。这些请求可以是CPU上正在运行的进程发起的请求,也可以是外部的设备发起的中断请求。所以说,内核并不是串行运行,而是交错执行。既然是交错执行,就会产生竞态条件,我们可以采用同步技术消除这种竞态条件。

我们首先了解一下如何向内核请求服务。然后,看一下这些请求如何实现同步。Linux内核又是采用了哪些同步技术。


2 如何请求内核服务


为了更好地理解内核是如何工作的,我们把内核比喻成一个酒吧服务员,他响应两种请求服务:一种是来自顾客,另外一种来自多个老板。这个服务员采用的策略是:

  1. 如果老板呼叫服务员,而服务员恰巧空闲,则立即服务老板;
  2. 如果老板呼叫服务员,而服务员恰巧正在服务一名顾客。则服务员停止为顾客服务,而是去服务老板。
  3. 如果老板呼叫服务员,而服务员恰巧在服务另一个老板,则服务员停止服务第一个老板,转而服务第二个。当他服务完第二个老板,再回去服务第一个老板。
  4. 老板让服务员停止为顾客服务转而为自己服务。在处理完老板的最后一个请求后,服务员也可能会决定是临时性地放弃之前的顾客,而迎接新顾客。

上面的服务员就非常类似于处于内核态的代码执行。如果CPU被用户态程序占用,服务员被认为是空闲的。老板的请求就类似于中断请求,而顾客请求就对应于用户进程发出的系统调用或异常。后面描述中,异常处理程序指的是系统调用和常规异常的处理程序。

仔细研究,就会发现,前3条规则其实与内核中的异常和中断嵌套执行的规则是一样的。第4条规则就对应于内核抢占。


3 内核抢占


给内核抢占下一个完美定义很难。在这儿,我们只是尝试着给其下一个定义:如果一个进程正运行在内核态,此时,发生了进程切换我们就称其为抢占式内核。当然了,Linux内核不可能这么简单:

  • 不论是抢占式内核还是非抢占式内核,进程都有可能放弃CPU的使用权而休眠等待某些资源。我们称这类进程切换是有计划的进程切换。但是抢占式内核和非抢占式的区别就在于对于异步事件的响应方式不同-比如,抢占式内核的中断处理程序可以唤醒更高优先级的进程,而非抢占式内核不会。我们称这类进程切换为强迫性的进程切换。
  • 我们已经知道所有的进程切换动作都由switch_to宏完成。不论是抢占式还是非抢占式,当进程完成内核活动的某个线程并调用调度器时就会发生进程切换。但是,在非抢占式内核中,除非即将切换到用户态时,否则不会发生进程替换。

因此,抢占式内核主要的特性就是运行在内核态的进程可以被其它进程打断而发生替换。让我们举例说明抢占式内核和非抢占式内核的区别:

假设进程A正在执行异常处理程序(内核态),这时候中断请求IRQ发生,相应的处理程序唤醒高优先级的进程B。如果内核是可抢占式的,就会发生进程A到进程B的替换。异常处理程序还没有执行完,只有当调度器再一次选择进程A执行的时候才会继续。相反,如果内核是非抢占式的,除非进程A完成异常处理或者自愿放弃CPU的使用权,否则不会发生进程切换。

再比如,考虑正在执行异常处理程序的进程,它的CPU使用时间已经超时。如果内核是抢占式的,进程被立即切换;但是,如果内核是非抢占式的,进程会继续执行,知道进程完成异常处理或自动放弃CPU的使用权。

实施内核抢占的动机就是减少用户态进程的调度延时,也就是减少可运行状态真正运行时的延时。需要实时调度的任务(比如外部的硬件控制器等)需要内核具有抢占性,因为减少了被其它进程延时的风险。

Linux内核是从2.6版本开始的,相比那些旧版本的非抢占性内核而言,没有什么显著的变化。当thread_info描述符中的preempt_count成员的值大于0,内核抢占就被禁止。这个值分为3部分,也就是说可能有3种情况导致该值大于0:

  1. 内核正在执行中断服务例程(ISR);
  2. 延时函数被禁止(当内核执行软中断或tasklet时总是使能状态);
  3. 内核抢占被禁止。

通过上面的规则可以看出,内核只有在执行异常处理程序(尤其是系统调用)的时候才能够被抢占,而且内核抢占也没有被禁止。所以,CPU必须使能中断,内核抢占才能被执行。

下表是操作prempt_count数据成员的一些宏:


Macro 描述
preempt_count() 选择preempt_count
preempt_disable() 抢占计数加1
preempt_enable_no_resched() 抢占计数减1
preempt_enable()

抢占计数减1,如果需要调度

,调用preempt_schedule()

get_cpu()

preempt_disable()相似

,但是返回CPU的数量

put_cpu() preempt_enable()相似
put_cpu_no_resched() preempt_enable_no_resched()相似


preempt_enable()使能抢占,还会检查TIF_NEED_RESCHED标志是否设置。如果设置,说明需要进行进程切换,就会调用函数preempt_schedule(),其代码片段如下所示:

if (!current_thread_info->preempt_count && !irqs_disabled()) {
    current_thread_info->preempt_count = PREEMPT_ACTIVE;
    schedule();
    current_thread_info->preempt_count = 0;
}

可以看出,这个函数首先检查中断是否使能,以及抢占计数是否为0。如果条件为真,调用schedule()切换到其它进程运行。因此,内核抢占既可以发生在中断处理程序结束时,也可以发生在异常处理程序重新使能内核抢占时(调用preempt_enable()也就是说,对于抢占式内核来说,进程切换发生的时机有,中断、系统调用、异常处理,还有一种特殊情况就是内核线程,它们直接调用schedule()进行主动进程切换。

内核抢占不可避免地引入了更多的开销。基于这个原因,Linux2.6内核允许用户在编译内核代码的时候,通过配置,可以使能和禁止内核抢占。


4 什么时候需要同步技术?


我们先了解一下内核进程的竞态条件和临界区的概念。当计算结果依赖于两个嵌套的内核控制路径时就会发生竞态条件。而临界区就是每次只能一个内核控制路径可以进入的代码段。

内核控制路径的交错执行给内核开发者带来很大的麻烦:必须小心地在异常处理程序、中断处理程序、可延时处理函数和内核线程中确定临界区。一旦确定了哪些代码是临界区,就需要为这个临界区代码提供合适的保护,确保至多有一个内核控制路径可以访问它。

假设两个不同的中断处理程序需要访问相同的数据结构。所有影响数据结构的语句都必须放到一个临界区中。如果是单核处理系统,临界区的保护只需要关闭中断即可,因为内核控制路径的嵌套只有在中断使能的情况下会发生。

另一方面,如果不同的系统调用服务程序访问相同的数据,系统也是单核处理系统,临界区的保护只需要禁止内核抢占即可。

但是,在多核系统中事情就比较复杂了。因为除了内核抢占,中断、异常或软中断之外,多个CPU也可能会同时访问某个相同的数据

后面我们会看一下内核提供了哪些内核同步手段?每种同步手段最合适的使用场景是什么?通过这些问题,我们掌握内核同步技术,为自己的内核程序设计最好的同步方法。


5 都有哪些同步技术?


表5-2,列举了Linux内核使用的一些同步技术。范围一栏表明同步技术应用到所有的CPU还是单个CPU。比如局部中断禁止就是针对一个CPU(系统中的其它CPU不受影响);相反,原子操作影响所有的CPU。

表5-2 Linux内核使用的一些同步技术

技术 描述 范围
Per-CPU变量 用于在CPU之间拷贝数据 所有CPU
原子操作 针对计数器的原子RMW指令 所有CPU
内存屏障 避免指令乱序

本地CPU

或所有CPU

自旋锁 忙等待 所有CPU
信号量 阻塞等待(休眠) 所有CPU
Seqlock 根据计数器进行加锁 所有CPU
中断禁止 禁止响应中断 本地CPU
软中断禁止 禁止处理可延时函数 本地CPU

读-拷贝-更新

(RCU)

通过指针实现无锁

访问共享资源

所有CPU


后面我们会针对每种同步技术进行详细阐述。

相关文章
|
3天前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
21 4
|
7天前
|
缓存 算法 Linux
深入理解Linux内核调度器:公平性与性能的平衡####
真知灼见 本文将带你深入了解Linux操作系统的核心组件之一——完全公平调度器(CFS),通过剖析其设计原理、工作机制以及在实际系统中的应用效果,揭示它是如何在众多进程间实现资源分配的公平性与高效性的。不同于传统的摘要概述,本文旨在通过直观且富有洞察力的视角,让读者仿佛亲身体验到CFS在复杂系统环境中游刃有余地进行任务调度的过程。 ####
28 6
|
6天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
27 9
|
5天前
|
缓存 负载均衡 Linux
深入理解Linux内核调度器
本文探讨了Linux操作系统核心组件之一——内核调度器的工作原理和设计哲学。不同于常规的技术文章,本摘要旨在提供一种全新的视角来审视Linux内核的调度机制,通过分析其对系统性能的影响以及在多核处理器环境下的表现,揭示调度器如何平衡公平性和效率。文章进一步讨论了完全公平调度器(CFS)的设计细节,包括它如何处理不同优先级的任务、如何进行负载均衡以及它是如何适应现代多核架构的挑战。此外,本文还简要概述了Linux调度器的未来发展方向,包括对实时任务支持的改进和对异构计算环境的适应性。
23 6
|
6天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
23 5
|
6天前
|
算法 Unix Linux
深入理解Linux内核调度器:原理与优化
本文探讨了Linux操作系统的心脏——内核调度器(Scheduler)的工作原理,以及如何通过参数调整和代码优化来提高系统性能。不同于常规摘要仅概述内容,本摘要旨在激发读者对Linux内核调度机制深层次运作的兴趣,并简要介绍文章将覆盖的关键话题,如调度算法、实时性增强及节能策略等。
|
7天前
|
存储 监控 安全
Linux内核调优的艺术:从基础到高级###
本文深入探讨了Linux操作系统的心脏——内核的调优方法。文章首先概述了Linux内核的基本结构与工作原理,随后详细阐述了内核调优的重要性及基本原则。通过具体的参数调整示例(如sysctl、/proc/sys目录中的设置),文章展示了如何根据实际应用场景优化系统性能,包括提升CPU利用率、内存管理效率以及I/O性能等关键方面。最后,介绍了一些高级工具和技术,如perf、eBPF和SystemTap,用于更深层次的性能分析和问题定位。本文旨在为系统管理员和高级用户提供实用的内核调优策略,以最大化Linux系统的效率和稳定性。 ###
|
6天前
|
Java Linux Android开发
深入探索Android系统架构:从Linux内核到应用层
本文将带领读者深入了解Android操作系统的复杂架构,从其基于Linux的内核到丰富多彩的应用层。我们将探讨Android的各个关键组件,包括硬件抽象层(HAL)、运行时环境、以及核心库等,揭示它们如何协同工作以支持广泛的设备和应用。通过本文,您将对Android系统的工作原理有一个全面的认识,理解其如何平衡开放性与安全性,以及如何在多样化的设备上提供一致的用户体验。
|
6天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
22 2
|
8天前
|
监控 网络协议 算法
Linux内核优化:提升系统性能与稳定性的策略####
本文深入探讨了Linux操作系统内核的优化策略,旨在通过一系列技术手段和最佳实践,显著提升系统的性能、响应速度及稳定性。文章首先概述了Linux内核的核心组件及其在系统中的作用,随后详细阐述了内存管理、进程调度、文件系统优化、网络栈调整及并发控制等关键领域的优化方法。通过实际案例分析,展示了这些优化措施如何有效减少延迟、提高吞吐量,并增强系统的整体健壮性。最终,文章强调了持续监控、定期更新及合理配置对于维持Linux系统长期高效运行的重要性。 ####