[转帖]内核同步原语

内核,同步,原语 · 浏览次数 : 0

小编点评

**同步原语**是一种在内核中用于实现并发编程的技术。它允许多个进程或线程在内核中共享内存中的数据。 同步原语共享内存的一个关键是 **原子操作**。原子操作是操作内核内存单元的最高级操作,它们确保数据在内核中被处理以一个单一的值。 **同步原语的类型** * **Per-CPU 变量**:每个 CPU 拥有专用的实例所有CPU原子操作。 * **Thread Local**:在同一个 CPU 上运行的线程可以共享数据。 **同步原语的基本原理** 1. **共享内存**:内核为每个 CPU 设置一个共享内存区域。 2. **原子操作**:每个进程或线程使用原子操作来读取或写入共享内存中的数据。 3. **屏障**:在内核访问共享内存期间,使用屏障来确保数据可见性。屏障可以分为 **忙等待锁** 和 **挂起-等待调度锁**。 4. **信号量**:当进程等待数据时,它使用信号量来通知内核。内核根据信号量将数据传递给等待进程。 **同步原语的性能** 同步原语通常比使用开发语言或类库函数库提供的同步方法快。然而,当内核在访问共享内存期间遇到屏障或信号量时,性能可能会下降。

正文

https://blog.mygraphql.com/zh/notes/low-tec/kernel/5-sync/synchronizeation-primitives/

 

什么是同步原语

共享内存,多进程/线程的运行期设计模式已成主流的今天,你有好奇一下,进程/线程间的怎么同步的吗?大部分人知道,我们用的开发语言,或类库函数库,已经提供了看起来很漂亮的封装。然而在漂亮的面子工程后面,大部分归根到底是要内核 或/和 CPU 硬件去完成这些同步的。而反过来,只要我们理解了内部原理,你就可以快速理解那些漂亮的面子工程,和他们可能的性能问题,进而选择一个适合你的“面子工程”。而这些内部原理,就是同步原语。

技术说明适用范围
CPU 本地变量(Per-CPU variables) 对于一个相同的数据类型,每个 CPU 拥有专用的实例 所有CPU
原子操作(Atomic operation) 原子化的读取-修改-写入一个计数器 所有CPU
内存栏栅(barrier) 避免程序的指令被 编译器 或 CPU 自动重排顺序 本地CPU 或 所有CPU
自旋锁(Spin lock) 让 CPU 不停忙着读内存标记位的忙等待锁 所有CPU
信号量(Semaphore) 挂起等待 所有CPU
写优先锁(Seqlocks)   所有CPU
禁用本地中断   本地CPU
禁用本地软中断   本地CPU
读-复制-更新(Read-copy-update (RCU)) 无锁地并发读写基于指针的数据结构 所有CPU

CPU 本地变量(Per-CPU variables)

基于原理就是一个数组,数组的大小就是CPU的个数。每个元素专用于一个CPU。数组元素实际上是一个指针,指向了实际的数据结构内存块。

需要注意的是,为了避免读写数据结构时的CPU Cache丢失和CPU间的干扰,数据结构内存块(上图红色部分)在分配内存时,内存物理地址需要对齐 cache line。

Per-CPU 和我们平时用的 Thread Local(Per-Thread) 不尽相同。不同的 Thread ,是可以运行于相同的CPU的。因为线程间可以抢占,而这个抢占足以引起数据不一致。这就是为什么,内核在访问 Per-CPU 期间,暂停了本 CPU 的抢占,即在这期间,我大晒,我玩晒。

原子操作(Atomic Operations)

每一种硬件架构,都有其原子操作的指令,对于 x86,在多CPU环境下,最少有以下情况下是原子操作:

  1. CPU指令操作的内存地址是数据长度的倍数
  2. CPU指令带有 0xf0前缀(称为 lock 前缀)。内存访问总线在执行指令期间被执行指令的CPU独占

对于 Linux 内核。为方便内核开发者使用原子操作,定义了一个atomic_type数据结构,和一系列的函数与宏。

1
2
3
typedef struct {
	int counter;
} atomic_t;

来源:bootlin.com

下面以atomic_inc(v)函数为例,简单看看实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/**
 * atomic_inc - increment atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically increments @v by 1.
 */
static inline void atomic_inc(atomic_t *v)
{
	asm volatile(LOCK_PREFIX "incl %0"
		     : "+m" (v->counter));
}

来源:elixir.bootlin.com

上面的LOCK_PREFIX即为前面提到的0xf0内存总线锁指令前缀。

如果想校真,看 c 中inline汇编。其中volatile即告诉编译器,汇编不要和 c 代码生成的指令混合乱序优化。

编译器优化栏栅与运行期内存栏栅(barrier)

优化栏栅(optimization barrier)

我们知道,编译器出于优化目的,生成的CPU指令的顺序,不一定和源码的顺序相同。但对于同步原语来说,这种乱序行为是不允许的。所以需要在代码中划一条线,告诉编译器,线前的代码,必须在线后执行前执行。这条线,就叫优化栏栅(optimization barrier)。内核中用 barrier() 宏,展开成asm volatile("":::"memory")

内存栏栅(barrier)

你也许知道,CPU 同样会乱序执行指令。同理,也需要划一条线告诉CPU,线后的指令不能在线前指令前执行。内存栏栅一般同时作为优化栏栅出现。定义这条线的方法有:

描述x86汇编
smp_mb() 内存栏栅  
smp_rmb() 读内存栏栅 volatile("lfence") 或 volatile("lock;addl $0,0(%%esp)":::"memory")
smp_wmb() 写内存栏栅 由于x86不会乱序执行写相关指令,本宏转成优化栏栅barrier(),跳过编译器乱序。

自旋锁(Spin lock)

根据是否在等待时使用CPU,锁可以分为两种,一种是忙等待锁,一种是挂起和等待调度锁。对于每次拥有,均极短时间占用的锁,忙等待可能比挂起-等待调度更节省CPU资源。自旋锁就是忙等待锁,即 on-cpu 等待的锁。

内核以 spinlock_t数据结构保存,在 x86 中最后定义为以下结构。

1
2
3
typedef struct arch_spinlock {
	unsigned int slock;
} arch_spinlock_t;

来源: bootlin.com

在最近版本的内核中,简单的 spin lock 已经修改为一个队列化的实现

https://events.static.linuxfound.org/sites/events/files/slides/Optimizing%20Application%20Performance%20in%20Large%20Multi-core%20Systems_0.pdf

  • Ticket spinlock is the spinlock implementation used in the Linux kernel prior to 4.2. A lock waiter gets a ticket number and spin on the lock cacheline until it sees its ticket number. By then, it becomes the lock owner and enters the critical section.

  • Queued spinlock is the new spinlock implementation used in 4.2 Linux kernel and beyond. A lock waiter goes into a queue and spins in its own cacheline until it becomes the queue head. By then, it can spin on the lock cacheline and attempt to get the lock.

For ticket spinlocks, all the lock waiters will spin on the lock cacheline (mostly read). For queued spinlocks, only the queue head will spin on the lock cacheline.

写优先锁(Seqlocks)

有时,我们关注写的性能,而可以让读的性能慢一点。如 Linux 中系统时间处理程序。基本原理就是读方使用乐观版本锁+失败重试的方法。写方每次写入均更新版本号。

读-复制-更新(Read-copy-update (RCU))

这可能是最复杂的内核同步原语,我觉得自己没明了,不乱写了,有兴趣的看原文吧:https://en.wikipedia.org/wiki/Read-copy-update

信号量(Semaphore)

根据是否在等待时使用CPU,锁可以分为两种,一种是忙等待锁,一种是挂起和等待调度锁。信号量属于挂起和等待调度锁。

1
2
3
4
5
struct semaphore {
	atomic_t count; //负数:有进程在等待; 0:在进程占用,但无等待;正数:无占用(可用数)
	int sleepers;
	wait_queue_head_t wait;//等待进程的队列
};

参考

  1. Better per-CPU variables
  2. https://en.wikipedia.org/wiki/Read-copy-update
  3. Understanding The Linux Kernel
 

与[转帖]内核同步原语相似的内容:

[转帖]内核同步原语

https://blog.mygraphql.com/zh/notes/low-tec/kernel/5-sync/synchronizeation-primitives/ 什么是同步原语 共享内存,多进程/线程的运行期设计模式已成主流的今天,你有好奇一下,进程/线程间的怎么同步的吗?大部分人知道,

[转帖]深入理解同步机制---内核自旋锁

https://switch-router.gitee.io/blog/spinlock/ 进程(线程)间的同步机制是面试时的常见问题,所以准备用一个系列来好好整理下用户态与内核态的各种同步机制。本文就以内核空间的一种基础同步机制—自旋锁开始好了 自旋锁是什么 自旋锁就是一个二状态的原子(atomi

[转帖]VLAN与三层交换机

目录 一、VLAN概述与优势二、Trunk的作用三、IEEE 802.1q四、VLAN转发五、Trunk的配置六、单臂路由概述七、三层交换机实现VLAN之间通信的原理八、实验一九、实验二十、实验三 一、VLAN概述与优势 在使用交换机互联的以太网中,同一区域内的主机在相互通信时可能会产生广播报文,此

[转帖]架构真经

1 大道至简 1.1 规则1 避免过度设计 【内容】在设计中警惕复杂的解决方案 【应用场景】适用于任何项目,应用所有大型项目和复杂系统或项目设计过程中 【用法】通过测试同事是否轻松的理解解决方案,来验证是否存在过度设计 【原因】复杂的解决方案实时成本过高,而且长期维护费用昂贵 【要点】过于复杂的系统

[转帖]Redis中Key的过期策略和淘汰机制

Key的过期策略 Redis的Key有3种过期删除策略,具体如下: 1. 定时删除 原理:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作优点:能够很及时的删除过期的Key,能够最大限度的节约内存缺点:对CPU时间不友好,如果过期的Key比

[转帖]Linux 生产内核网络参数调优分析

https://www.jianshu.com/p/634ea67ac23a Linux 生产内核网络参数调优分析 本文总结了常见的 Linux 内核参数及相关问题。修改内核参数前,您需要: 从实际需要出发,最好有相关数据的支撑,不建议随意调整内核参数。 了解参数的具体作用,且注意同类型或版本环境的

[转帖]datax安装+配置+使用文档

1 DataX离线同步工具DataX3.0介绍 DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、Oracle、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS

[转帖]使用Red Hat Enterprise Linux的实时内核

运行实时内核并评估其对应用程序的潜力和性能优势是值得的。 https://www.redhat.com/sysadmin/real-time-kernel 目录 什么是实时内核? 实时安装RHEL Wrap up 实时内核功能在开源生态系统中已经存在了十多年。同样,红帽企业Linux(RHEL)对实

[转帖]【文章导读】什么是旁道攻击?Meltdown Redux英特尔漏洞(MDS攻击);KAISER:从用户空间隐藏内核(KAISER);Meltdown/Spectre分析

Table of Contents 黑客词典:什么是旁道攻击? Meltdown Redux:Intel缺陷使黑客窃取了数百万台PC的秘密 三重熔毁:有多少研究人员同时发现了20年的芯片缺陷 KAISER:从用户空间隐藏内核 迟到的Meltdown/Spectre分析 黑客词典:什么是旁道攻击? h

【转帖】isolcpus功能与使用

isolcpus功能存在已久,笔者追溯v2.6.11(2005年)那时内核就已经存在了isolcpus功能。根据kernel-parameters.txt 上的解释,”isolcpus功能用于在SMP均衡调度算法中将一个或多个CPU孤立出来。同时可通过亲和性设置将进程置于 “孤立CPU”运行,iso