Linux内核30-读写自旋锁

简介: Linux内核30-读写自旋锁

1 读/写自旋锁概念


自旋锁解决了多核系统在内核抢占模式下的数据共享问题。但是,这样的自旋锁一次只能一个内核控制路径使用,这严重影响了系统的并发性能。根据我们以往的开发经验,大部分的程序都是读取共享的数据,并不更改;只有少数时候会修改数据。为此,Linux内核提出了读/写自旋锁的概念。也就是说,没有内核控制路径修改共享数据的时候,多个内核控制路径可以同时读取它。如果有内核控制路径想要修改这个数据结构,它就请求读/写自旋锁的写自旋锁,独占访问这个资源。这大大提高了系统的并发性能。


2 读写自旋锁的数据结构


读/写自旋锁的数据结构是rwlock_t,其定义如下:

typedef struct {
    arch_rwlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
    unsigned int break_lock;
#endif
    ......
} rwlock_t;

从上面的代码可以看出,读/写自旋锁的实现还是依赖于具体的架构体系。下面我们先以ARM体系解析一遍:

arch_rwlock_t的定义:
typedef struct {
    u32 lock;
} arch_rwlock_t;


3 读写自旋锁API实现


  1. 请求写自旋锁arch_write_lock的实现:
static inline void arch_write_lock(arch_rwlock_t *rw)
{
    unsigned long tmp;
    prefetchw(&rw->lock);   // ----------(0)
    __asm__ __volatile__(
"1: ldrex   %0, [%1]\n"     // ----------(1)
"   teq %0, #0\n"           // ----------(2)
    WFE("ne")               // ----------(3)
"   strexeq %0, %2, [%1]\n" // ----------(4)
"   teq %0, #0\n"           // ----------(5)
"   bne 1b"                 // ----------(6)
    : "=&r" (tmp)
    : "r" (&rw->lock), "r" (0x80000000)
    : "cc");
    smp_mb();               // ----------(7)
}
  • (0)通知硬件提前将rw->lock的值加载到cache中,缩短等待预取指令的时延。
  • (1)使用独占指令ldrex标记相应的内存位置已经被独占,并将其值存储到tmp变量中。
  • (2)判断tmp是否等于0。
  • (3)如果tmp不等于0,则说明rw->lock正在被占用,所以进入低功耗待机模式。
  • (4)如果tmp等于0,则向rw->lock的内存地址处写入0x80000000,然后清除独占标记。
  • (5)测试tmp是否等于0,相当于验证第4步是否成功。
  • (6)如果加锁失败,则重新(0)->(5)的过程。
  • (7)现在只是把指令写入到数据总线上,还没有完全成功。所以smp_mb()内存屏障保证加锁成功。
  1. 写自旋锁的释放过程,arch_write_unlock函数实现,代码如下:
static inline void arch_write_unlock(arch_rwlock_t *rw)
{
    smp_mb();           // ----------(0)
    __asm__ __volatile__(
    "str    %1, [%0]\n" // ----------(1)
    :
    : "r" (&rw->lock), "r" (0)
    : "cc");
    dsb_sev();          // ----------(2)
}
  • (0)保证释放锁之前的操作都完成。
  • (1)将rw->lock的值赋值为0。
  • (2)调用sev指令,唤醒正在执行WFE指令的内核控制路径。
  1. 读自旋锁的释放过程(低功耗版)由arch_read_lock函数实现,代码如下:
static inline void arch_read_lock(arch_rwlock_t *rw)
{
    unsigned long tmp, tmp2;
    prefetchw(&rw->lock);
    __asm__ __volatile__(
"1: ldrex   %0, [%2]\n"     // ----------(0)
"   adds    %0, %0, #1\n"   // ----------(1)
"   strexpl %1, %0, [%2]\n" // ----------(2)
    WFE("mi")               // ----------(3)
"   rsbpls  %0, %1, #0\n"   // ----------(4)
"   bmi 1b"                 // ----------(5)
    : "=&r" (tmp), "=&r" (tmp2)
    : "r" (&rw->lock)
    : "cc");
    smp_mb();
}
  • (0)读取rw->lock地址处的内容,然后标记为独占。
  • (1)tmp=tmp+1。
  • (2)将这条指令的执行结果写入到tmp2变量中,将tmp的值写入到rw->lock地址处。
  • (3)如果tmp是负值,说明锁已经被占有,则执行wfe指令,进入低功耗待机模式。
  • (4)执行0减去tmp2,将结果写入tmp。因为tmp2的值有2个:0-更新成功;1-更新失败。所以正常情况,此时tmp的结果应该为0,也就是释放加锁成功。
  • (5)如果加锁失败,则重新进行(0)->(4)的操作。失败的可能就是,独占标记被其它加锁操作破坏。
  1. 读自旋锁的释放过程(不断尝试版)由arch_read_trylock函数实现,代码如下:
static inline int arch_read_trylock(arch_rwlock_t *rw)
{
    unsigned long contended, res;
    prefetchw(&rw->lock);
    do {
        __asm__ __volatile__(
        "   ldrex   %0, [%2]\n"     // ----------(0)
        "   mov %1, #0\n"           // ----------(1)
        "   adds    %0, %0, #1\n"   // ----------(2)
        "   strexpl %1, %0, [%2]"   // ----------(3)
        : "=&r" (contended), "=&r" (res)
        : "r" (&rw->lock)
        : "cc");
    } while (res);                  // ----------(4)
    /* 如果lock为负,则已经处于write状态 */
    if (contended < 0x80000000) {   // ----------(5)
        smp_mb();
        return 1;
    } else {
        return 0;
    }
}
  1. 根据4和5两个释放锁的过程分析,可以看出除了是否根据需要进入低功耗状态之外,其它没有区别。
  • (0)读取rw->lock地址处的内容,然后标记为独占。
  • (1)res = 0。
  • (2)contended = contended + 1。
  • (3)将contended的值写入rw->lock地址处,操作结果写入res。
  • (4)如果res等于0,操作成功;否则重新前面的操作。
  • (5)如果此时处于write状态,则释放锁失败,返回1;否则,成功返回0。
  1. 读自旋锁的释放过程由arch_read_unlock函数实现,代码如下:
static inline void arch_read_unlock(arch_rwlock_t *rw)
{
    unsigned long tmp, tmp2;
    smp_mb();
    prefetchw(&rw->lock);
    __asm__ __volatile__(
"1: ldrex   %0, [%2]\n"     // ----------(0)
"   sub %0, %0, #1\n"       // ----------(1)
"   strex   %1, %0, [%2]\n" // ----------(2)
"   teq %1, #0\n"           // ----------(3)
"   bne 1b"                 // ----------(4)
    : "=&r" (tmp), "=&r" (tmp2)
    : "r" (&rw->lock)
    : "cc");
    if (tmp == 0)
        dsb_sev();
}
  • (0)读取rw->lock地址处的内容,然后标记为独占。
  • (1)要退出临界区,所以,tmp = tmp - 1。
  • (2)tmp写入到rw->lock地址处,操作结果写入tmp2。
  • (3)判断tmp2是否等于0。
  • (4)等于0成功,不等于0,则跳转到标签1处继续执行。

通过上面的分析可以看出,读写自旋锁使用bit31表示写自旋锁,bit30-0表示读自旋锁,对于读自旋锁而言,绰绰有余了。

  1. 成员break_lock

对于另一个成员break_lock来说,同自旋锁数据结构中的成员一样,标志锁的状态。

rwlock_init宏初始化读写锁的lock成员。

对于X86系统来说,处理的流程跟ARM差不多。但是,因为与ARM架构体系不同,所以具体的加锁和释放锁的实现是不一样的。在此,就不一一细分析了。

相关文章
|
1月前
|
安全 Linux 测试技术
Intel Linux 内核测试套件-LKVS介绍 | 龙蜥大讲堂104期
《Intel Linux内核测试套件-LKVS介绍》(龙蜥大讲堂104期)主要介绍了LKVS的定义、使用方法、测试范围、典型案例及其优势。LKVS是轻量级、低耦合且高代码覆盖率的测试工具,涵盖20多个硬件和内核属性,已开源并集成到多个社区CICD系统中。课程详细讲解了如何使用LKVS进行CPU、电源管理和安全特性(如TDX、CET)的测试,并展示了其在实际应用中的价值。
|
1月前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
95 15
|
2月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
2月前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
2月前
|
缓存 监控 网络协议
Linux操作系统的内核优化与实践####
本文旨在探讨Linux操作系统内核的优化策略与实际应用案例,深入分析内核参数调优、编译选项配置及实时性能监控的方法。通过具体实例讲解如何根据不同应用场景调整内核设置,以提升系统性能和稳定性,为系统管理员和技术爱好者提供实用的优化指南。 ####
|
3月前
|
算法 Linux 调度
深入理解Linux内核调度器:从基础到优化####
本文旨在通过剖析Linux操作系统的心脏——内核调度器,为读者揭开其高效管理CPU资源的神秘面纱。不同于传统的摘要概述,本文将直接以一段精简代码片段作为引子,展示一个简化版的任务调度逻辑,随后逐步深入,详细探讨Linux内核调度器的工作原理、关键数据结构、调度算法演变以及性能调优策略,旨在为开发者与系统管理员提供一份实用的技术指南。 ####
118 4
|
3月前
|
缓存 并行计算 Linux
深入解析Linux操作系统的内核优化策略
本文旨在探讨Linux操作系统内核的优化策略,包括内核参数调整、内存管理、CPU调度以及文件系统性能提升等方面。通过对这些关键领域的分析,我们可以理解如何有效地提高Linux系统的性能和稳定性,从而为用户提供更加流畅和高效的计算体验。
98 17
|
2月前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
3月前
|
缓存 网络协议 Linux
深入探索Linux操作系统的内核优化策略####
本文旨在探讨Linux操作系统内核的优化方法,通过分析当前主流的几种内核优化技术,结合具体案例,阐述如何有效提升系统性能与稳定性。文章首先概述了Linux内核的基本结构,随后详细解析了内核优化的必要性及常用手段,包括编译优化、内核参数调整、内存管理优化等,最后通过实例展示了这些优化技巧在实际场景中的应用效果,为读者提供了一套实用的Linux内核优化指南。 ####
92 1
|
3月前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
101 9