莫小帅|Linux kernel同步机制(上篇)

在现代操作系统里 , 同一时间可能有多个内核执行流在执行 , 因此内核其实像多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问 , 尤其是在多处理器系统上 , 更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问 。 在主流的Linux内核中包含了如下这些同步机制包括:

  • 原子操作
  • 信号量(semaphore)
  • 读写信号量(rw_semaphore)
  • Spinlock
  • Mutex
  • BKL(Big Kernel Lock , 只包含在2.4内核中 , 不讲)
  • Rwlock
  • brlock(只包含在2.4内核中 , 不讲)
  • RCU(只包含在2.6内核及以后的版本中)
  • seqlock(只包含在2.6内核及以后的版本中)
本文章分为两部分 , 这一章我们主要讨论原子操作 , 自旋锁 , 信号量和互斥锁 。
一、原子操作原子操作的概念来源于物理概念中的原子定义 , 指执行结束前不可分割(即不可打断)的操作 , 是最小的执行单位 。
原子操作与硬件架构强相关 , 其API具体的定义均位于对应arch目录下的include/asm/atomic.h文件中 , 通过汇编语言实现 , 内核源码根目录下的include/asm-generic/atomic.h则抽象封装了API , 该API最后分派的实现来自于arch目录下对应的代码 。
Structure Definition
typedefstruct{intcounter;}atomic_t;
原子操作主要用于实现资源计数 ,许多引用计数(refcnt)就是通过原子操作实现 , 例如TCP/IP协议栈的IP碎片中 , struct ipq中的refcnt字段 , 类型即为atomic_t 。
atomic_add
原子操作的实现比较简单 , 以下为例 。
莫小帅|Linux kernel同步机制(上篇)
原子操作的原子性依赖于ldrex与strex实现 , ldrex读取数据时会进行独占标记 , 防止其他内核路径访问 , 直至调用strex完成写入后清除标记 。 自然strex也不能写入被别的内核路径独占的内存 , 若是写入失败则循环至成功写入 。
API
原子操作的API包括如下, 以arm平台为例:
莫小帅|Linux kernel同步机制(上篇)二 、自旋锁(spinlock)自旋锁是这样一种同步机制:若自旋锁已被别的执行者保持 , 调用者就会原地循环等待并检查该锁的持有者是否已经释放锁(即进入自旋状态) , 若释放则调用者开始持有该锁 。 自旋锁持有期间不可被抢占 。
Structure Definition
莫小帅|Linux kernel同步机制(上篇)从定义出发 ,spinlock根本的实现依赖于具体架构实现中slock这个变量 , 由于spin_lock是大多locking机制的基础 , 我们看一看它的实现 。
Lock & Unlock
莫小帅|Linux kernel同步机制(上篇)核心unlock函数 , 使owner自增 , 保持数据同步 。
莫小帅|Linux kernel同步机制(上篇)
核心lock函数 , 使slock +2^16, 当next==owner时 , 释放锁 , 否则进入循环等待 。 Prefetchw用于cache预加载数据 。
由于slock与tickets共享同一块内存(union) , slock 占32位4字节 , tickets内部变量next与owner各16位2字节 。 以大端序为例 , slock 高2字节与next共享 , 低2字节与owner共享 , 因此arch_spin_lock实际上是将tickets.next+1 。 假设初始时next与owner皆为0 , 此时next与owner不等 , 通过wfe指令进入一小段时间等待状态 , 而后读取新的owner值检查与next是否相等 , 不等则继续等待 , 相等则结束等待 。


推荐阅读