Linux 的 Spinlock 在 MIPS 多核处理器中的设计与实现
linux内核调度与spinlock的相互关系

linux内核调度与spinlock的相互关系
嵌入式linux中文站关于自旋锁用法介绍的文章,已经有很多,但有些细节的地方点的还不够透,因此我们在这里将着重介绍自旋锁相关的知识。
一、自旋锁(spinlock)简介
自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。
这点可以应用在多处理机器、或运行在单处理器上的抢占式内核中需要的锁定服务。
二、信号量简介
这里也介绍下信号量的概念,因为它的用法和自旋锁有相似的地方。
Linux中的信号量是一种睡眠锁。
如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。
这时处理器获得自由去执行其它代码。
当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
三、自旋锁和信号量对比
在很多地方自旋锁和信号量可以选择任何一个使用,但也有一些地方只能选择某一种。
下面对比一些两者的用法。
表1-1自旋锁和信号量对比
应用场合
信号量or自旋锁
低开销加锁(临界区执行时间较快)
优先选择自旋锁
低开销加锁(临界区执行时间较长)
优先选择信号量
临界区可能包含引起睡眠的代码
不能选自旋锁,可以选择信号量。
介绍一下Linux内核的排队自旋锁

介绍一下Linux内核的排队自旋锁
排队自旋锁(FIFO Ticket Spinlock)是Linux 内核2.6.25 版本引入的一种新型自旋锁,它通过保存执行线程申请锁的顺序信息解决了传统自旋锁的“不公平”问题。
排队自旋锁的代码由Linux 内核开发者Nick Piggin 实现,目前只针对x86 体系结构(包括IA32 和x86_64),相信很快就会被移植到其它平台。
自旋锁(Spinlock)是在Linux 内核中广泛运用的底层同步机制。
它是一种工作于多处理器环境的特殊的锁,在单处理环境中自旋锁的操作被替换为空操作。
当某个处理器上的内核执行线程申请自旋锁时,如果锁可用,则获得锁,然后执行临界区操作,最后释放锁;如果锁已被占用,线程并不会转入睡眠状态,而是忙等待该锁,一旦锁被释放,则第一个感知此信息的线程将获得锁。
传统的自旋锁本质上用一个整数来表示,值为1代表锁未被占用。
这种无序竞争导致执行线程无法保证何时能取到锁,某些线程可能需要等待很长时间。
随着计算机处理器个数的不断增长,这种“不公平”问题将会日益严重。
1。
MIPS指令五级流水CPU设计剖析

MIPS指令五级流水CPU设计剖析MIPS指令五级流水CPU设计是一种高性能的处理器设计架构,它将指令的执行过程拆分为五个阶段,并且在每个阶段中可以同时处理多条指令,实现了指令级并行处理。
在这种设计中,分别是取指(Instruction Fetch)、译码(Instruction Decode)、执行(Execute)、访存(Memory Access)和写回(Write Back)这五个阶段。
在MIPS指令五级流水CPU设计中,首先是取指(Instruction Fetch)阶段,这是整个处理器开始处理一条指令的阶段。
在这个阶段,处理器从指令存储器中读取即将执行的指令,并将其送入流水线中。
这个阶段还会包括对指令地址的计算和异常处理的操作。
当一条指令流入流水线后,处理器就会进行下一个阶段的操作。
接下来是译码(Instruction Decode)阶段,这个阶段主要是将取到的指令进行解码,确定指令的操作类型和需要操作的寄存器等信息。
在这个阶段,会根据指令的不同分支到不同的功能单元中处理。
同时还会识别指令之间的数据相关性,以便在后续阶段进行相应的控制。
第三个阶段是执行(Execute)阶段,在这个阶段,CPU执行指令的操作,进行算数运算或逻辑运算,或者进行跳转等控制操作。
在这个阶段,CPU还将从寄存器文件中读取数据,并进行相应的运算。
这个阶段涉及到的计算量比较大,所以需要比较多的时钟周期来完成。
接下来是访存(Memory Access)阶段,这个阶段主要是处理访问数据内存的操作,比如从数据内存中读取数据,或将结果写入数据内存等。
在这个阶段,处理器还会涉及到访存相关的操作,比如缓存机制的处理等。
这个阶段的操作通常是比较高速的存储器操作。
最后是写回(Write Back)阶段,这个阶段是处理器的最后一个阶段,处理器将执行结果写回到寄存器文件中,或进行其他相关的操作。
这个阶段可以认为是指令执行的结束阶段,对前面四个阶段产生的结果进行最终的处理。
linux内核的自旋锁

通用自旋锁
自旋锁的状态值为 1 表示解锁状态,说明有 1 个资源可用;0 或负值 表示加锁状态,0 说明可用资源数为 0。Linux 内核为通用自旋锁提供 了 API 函数初始化、测试和设置自旋锁。API 函数功能说明如表 5。 表 5 通用自旋锁 API 函数功能说明 宏定义 spin_lock_init(lock) spin_is_locked(lock) spin_unlock_wait(lock) 功能说明 初始化自旋锁,将自旋锁设置为 1,表示有一个资源可用。 如果自旋锁被置为 1(未锁),返 回 0,否则返回 1。 等待直到自旋锁解锁(为 1),返 回 0;否则返回 1。 尝试锁上自旋锁(置 0),如果原 来锁的值为 1,返回 1,否则返回 0。 循环等待直到自旋锁解锁(置为 1),然后,将自旋锁锁上(置为 0)。 将自旋锁解锁(置为 1)。
#endif } spinlock_t;
由于自旋锁的性能严重地影响着操作系统的性能,Linux 内核提供了 Lock-class 和 Lockdep 跟踪自旋锁的使用对象和锁的状态,并可从 /proc 文件系统查询自旋锁的状态信息。自旋锁的调试通过配置项 CONFIG_DEBUG_*项打开。 对于对称多处理器系统(SMP),slock 为一个 int 数据类型,对于单 个处理器系统,slock 定义为空。SMP 的 slock 定义列出如下(在 include/linux/spinlock_types.h):
#define spin_lock_irqsave(lock, flags) flags = _spin_lock_irqsave(lock)
函数 spin_lock_irqsave 分析如下(在 kernel/spinlock.c 中):
spinlock实现原理

spinlock实现原理
spinlock是一种轻量级的线程同步机制,该机制通常用于最小范围的线程互斥
的场景。
它的核心目的是阻止其他线程访问一个被某个线程排他性占有的关键资源,使被占用的资源可供该线程独占。
它的基本原理是:一个变量作为标志位,同时只能够由一个线程获得(通过给其上锁),此时此线程可以进行资源的处理,当它释放标志位之后,即可允许其他线程来获得(上锁)这个标志位,从而独占这个资源。
spinlock原理就是上述描述,它解决了线程互斥的问题,但是它也存在一些缺点。
其一,当各个线程在不同处理器上运行时,某个线程持有标志位,其他线程将一直被挂起,导致停止循环的消耗过高的开销,尤其是在多处理器场景下,如果一个处理器上的线程占用某资源过久,将会对多处理器上其他正常运行的线程产生影响。
其二,spinlock在处理器上实际上是占用CPU资源,如果一个线程占有spinlock过久,后将会影响其他需要竞争spinlock的线程执行。
因此,对于简单的线程互斥的场景,可以利用spinlock的思路来解决,它显著
减少了信号量和其他互斥机制带来的开销。
但是spinlock要求每个线程只有在短暂的时间里占有锁,只有这样,才能有效的减少消耗,才能更好的实现线程互斥。
MIPS指令多周期CPU设计

MIPS指令多周期CPU设计MIPS(Microprocessor without Interlocked Pipeline Stages)是一种经典的指令集架构,也是一种常用的计算机体系结构之一、在本文中,将介绍如何设计一个多周期CPU来执行MIPS指令。
多周期CPU是一种在各个阶段使用不同时钟周期数的中央处理器设计。
其核心思想是将指令处理过程划分为若干个阶段,每个阶段由单独的硬件电路来执行。
通过这种方式,可以提高CPU的效率和性能。
下面将逐步介绍多周期CPU的设计步骤:1. 指令存储器(Instruction Memory):首先,需要设计一个指令存储器,用于存储MIPS指令。
指令存储器通常使用随机存取存储器(Random Access Memory, RAM),可以通过指令地址来读取指令。
2. 指令解码(Instruction Decode):在该阶段中,需要将从指令存储器中读取的指令进行解码。
解码的目的是确定指令的类型以及操作数的位置。
根据指令的类型,还需要通过控制信号来决定执行的下一步操作。
3. 寄存器读取(Register Read):在这个阶段中,需要从寄存器文件中读取操作数。
MIPS架构中有32个通用寄存器,它们存储着变量和数据。
4. 执行(Execute):在这个阶段中,需要执行指令的操作。
具体的操作取决于指令的类型。
例如,加法操作需要将操作数相加,存储结果。
5. 存储器访问(Memory Access):在这个阶段中,需要进行内存访问操作。
MIPS架构中,可以使用lw(load word)指令将数据从内存中加载到寄存器中,使用sw(store word)指令将数据从寄存器中存储到内存中。
6. 寄存器写入(Register Write):在这个阶段中,需要将执行阶段的结果写入到寄存器文件中。
以上是多周期CPU的基本设计流程。
在设计过程中,还需要考虑异常处理和分支跳转等特殊情况。
linux锁机制分析

Linux 锁机制分析batoom1.加锁的原因是因为在对共享数据或者共享资源进行并发访问的时候,会使数据错乱。
例如:very_important_count++期望的结果这种结果依赖于多个任务的相对执行顺序,叫做竞态条件(race ondition)。
包含并发问题的代码叫做临界区。
这种情况在SMP机器上更加明显,上面的例子是单CPU由于抢占造成的。
2.在linux内核中加锁linux内核中主要有两种类型的锁:1)自旋锁spinlock当获取不成功时,不会睡眠,会一直循环查找锁是否被释放,必须在不能睡眠的代码中使用。
在单cpu没有打开抢占的情况下,自选锁相当于不存在,在打开抢占的情况下,自选锁的作用是禁止抢占。
#define spin_lock(lock) _spin_lock(lock) #define _spin_lock(lock) __LOCK(lock)#define __LOCK(lock) \do { preempt_disable(); __acquire(lock); (void)(lock); } while (0) 注意到“preempt_disable()”,这个调用的功能是“关抢占”(在spin_unlock中会重新开启抢占功能)。
从中可以看出,使用自旋锁保护的区域是工作在非抢占的状态;即使获取不到锁,在“自旋”状态也是禁止抢占的。
使用方法:Static spinlock_t xxx_lock = SPIN_LOCK_UNLOCKED;spin_lock(&xxx_lock);…spin_unlock(&xxx_lock);2)信号量semaphone只有一个持有者的信号量 叫mutex,当获取不成功时,任务会把自身放到一个队列中睡眠,一直等到信号量被释放时才唤醒。
必须在能睡眠的代码中使用。
void down(struct semaphore *sem){unsigned long flags;spin_lock_irqsave(&sem->lock, flags);if (likely(sem->count > 0))sem->count--;else__down(sem);spin_unlock_irqrestore(&sem->lock, flags);}for (;;) {if (signal_pending_state(state, task))goto interrupted;if (timeout <= 0)goto timed_out;__set_task_state(task, state);spin_unlock_irq(&sem->lock);timeout = schedule_timeout(timeout);spin_lock_irq(&sem->lock);if (waiter.up)return 0;}schedule_timeout函数是把当前进程推入等待列表,并在timeout时间后唤醒。
linux自旋锁使用实例

linux自旋锁使用实例Linux内核中的自旋锁是一种轻量级的同步机制,用于保护临界区,以避免多个线程同时访问共享资源。
下面是一个简单的使用自旋锁的实例:```c#include <stdio.h>#include <pthread.h>pthread_spinlock_t lock;int shared_variable = 0;void* thread_function(void* arg) {pthread_spin_lock(&lock); // 获取自旋锁// 临界区shared_variable++;printf("Thread %d: shared_variable = %d\n", *(int*)arg, shared_variable);pthread_spin_unlock(&lock); // 释放自旋锁return NULL;}int main() {pthread_t thread1, thread2;int thread1_arg = 1;int thread2_arg = 2;pthread_spin_init(&lock, 0); // 初始化自旋锁pthread_create(&thread1, NULL, thread_function, &thread1_arg);pthread_create(&thread2, NULL, thread_function, &thread2_arg);pthread_join(thread1, NULL);pthread_join(thread2, NULL);pthread_spin_destroy(&lock); // 销毁自旋锁return 0;}```在上面的示例中,我们创建了两个线程,它们会同时访问一个共享变量`shared_variable`。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Spinlock 在Linux 中被广泛应用于解决多核处理器之间访问共享资源的互斥问题,本文以MIPS 多核处理器为例,介绍了Spinlock 的设计与实现,以及Spinlock 的不足与扩展。
引言随着科技的发展,尤其是在嵌入式领域,高性能、低功耗的处理器成为众多厂商追逐的目标,但是由于技术和工艺的瓶颈,试图在单核处理器上达到这样的目标变得越发困难,于是人们提出了多核处理器的概念。
多核处理器的核心思想是一个处理器中包含若干个核(或线程),所有核(或线程)之间共享IO、Cache、内存等资源,对于这些资源的使用和分配由硬件来完成,用户无需关注细节,因此每个核(或线程)对于用户来说就好像一个独立的虚拟CPU,从用户角度来看,这个虚拟CPU 独占所有的外设资源。
目前比较流行的多核处理器的架构有下面几种:(1)SMP(Symmetric Multi-P rocessor)这种架构的处理器由多个核组成,每个核有自己独立的Cache,所有核共享内存和IO。
(2)SMT(Symmetric Multi-Thread)这种架构的处理器的每个核由多个线程组成(此处的线程指的硬件线程,而不是我们所说的操作系统的线程概念),每个核下的所有线程共享寄存器、ALU(CPU 运算单元)、Cache、内存、IO 等资源,线程之于用户也像一个虚拟的CP U。
这种架构的最大优势在于线程和线程之间的切换很快,通常一个时钟周期内就能完成。
(3)NUMA(Non-Uniform Memory Access)这种架构和前面两种的区别在于它不是简单的一个处理器,而是一个由多个处理器组成的系统,每个处理器作为一个结点在该系统中存在。
对于内存、IO 等资源所有结点也是共享的。
目前比较流行的处理器架构大多都推出了多核处理器的产品,比如Intel 的X86 双核处理器、Freescale 的PPC 多核处理器、SUN 的SP ARC 多核处理器、RMI 和Cavium 的MIP S 多核处理器等。
MIP S 这种架构诞生于Standford 大学,它的名字含义有双重意义,一是“Microcomputer without Inter-locked P ipeline Stages”,另一种是“Millions of Instructions P er Second”,它设计思想的核心是采用多级指令流水线技术达到高效的处理速度,目前比较常用的MIP S 采用5级流水线的技术。
因其性能高效、结构简单,MIP S 被誉为RISC(Reduced Instruction Set Computing)家族中最优美的一款处理器架构。
基于MIPS 的多核处理器因为其低功耗、高性能从而在嵌入式市场得到了广泛的应用。
针对上述这些多核处理器的特点,Linux 提出了Multi-P rocessing 的概念,它的调度器可以将操作系统的线程均分到各个核(或硬件线程)上去执行,以此达到并行计算的目的,从而也可以极大地提高系统的性能。
回页首Spinlock 的思想Linux 在推出了Multi-P rocessing 之后,多核处理器的并行处理的能力得到了极大的发挥,但是这同时也带来了一个问题,并行执行势必就存在多个核同时访问共享资源的情况,如何能够保证一个核在访问共享资源时,该共享资源不会被其它核所改写呢?我们没有方法去控制对共享资源访问的有序性,但是我们有能力对共享资源采用锁的保护机制,当某个共享资源被锁住时,只有获取该锁的CPU 核能够操作共享资源,其余试图访问共享资源的CPU 核只能够等待这个锁的释放,这就是在Linux 中被称为Spinlock 的自旋锁保护机制。
Spinlock 的设计思想是基于一种被称为Test-and-Set 的机制,它的操作分为三部分:(1)INIT初始化lock值。
示例如下:(2)LOCK这个流程包括两部分:首先CPU 核反复轮询lock值直到它处于空闲状态为止,然后利用Test-and-Set 方式尝试设置该lock值,Test-and-Set 的操作可以描述成三步:(a)读取lock值;(b)设置lock值;(c)检查lock值是否设置成功,如果在步骤(a)之后还存在别的对lock值的操作,说明存在有并发访问lock 值的情况,则步骤(b)的lock值将不能设置成功,还需要回到步骤(a)重新执行这个流程。
示例如下:(3)UNLOCK释放lock值。
示例如下:MIP S 中 Spinlock 的实现传统的 C 语言是无法实现 Test-and-Set 的机制的,因为它无法在多核之间建立一个交互的机制,因此 Test-and-Set 需要处理器给以相应的支持。
以 MIP S 为例,它提供了 LL (Load Linked Word ) 和 SC (Store Conditional Word ) 这两个汇编指令来实现对共享资源的保护。
LL 指令的功能是从内存中读取一个字,以实现接下来的 RMW (Read-Modify-Write ) 操作;SC 指令的功能是向内存中写入一个字,以完成前面的 RMW 操作。
LL/SC 指令的独特之处在于,它们不是一个简单的内存读取/写入的函数,当使用 LL 指令从内存中读取一个字之后,比如 LL d, off(b),处理器会记住 LL 指令的这次操作(会在 CPU 的寄存器中设置一个不可见的 bit 位),同时 LL 指令读取的地址 off(b) 也会保存在处理器的寄存器中。
接下来的 SC 指令,比如 SC t, off(b),会检查上次 LL 指令执行后的 RMW 操作是否是原子操作(即不存在其它对这个地址的操作),如果是原子操作,则 t 的值将会被更新至内存中,同时 t 的值也会变为1,表示操作成功;反之,如果 RMW 的操作不是原子操作(即存在其它对这个地址的访问冲突),则 t 的值不会被更新至内存中,且 t 的值也会变为0,表示操作失败。
SC 指令执行失败的原因有两种:(1)在 LL/SC 操作序列的过程中,发生了一个异常(或中断),这些异常(或中断)可能会打乱 RMW 操作的原子性。
(2)在多核处理器中,一个核在进行 RMW 操作时,别的核试图对同样的地址也进行操作,这会导致 SC 指令执行的失败。
采用 LL/SC 指令实现 Spin_Lock/Spin_Unlock 的范例如下。
MIP S 的 Spin_Lock对于 MIP S 的 Spin_Lock 操作,可以用如下的方式实现:表1. Spin_Lock回页首Line 1:lockKey 是共享资源锁,这是块能被多核所共享的内存地址,该值为0表示锁处于空闲状态,为1表示锁已经被某个核所获取,其余核若想获取它只能等待,以下对于lockKey 的定义相同。
Line 3:将lockKey 读入t0寄存器中。
Line 4:比较lockKey 是否空闲,如果该锁不可用的话则跳转到Line 1。
Line 5:给t0 寄存器赋值为1。
Line 6:将t0 寄存器的值保存入lockKey 中,并返回操作结果于t0 寄存器中。
Line 7:判断t0 寄存器的值是否为0,如果为0表示Line 5 中的操作失败,则返回Line 1 重新开始;如果为1表示Line 5 中的操作成功。
Line 8:Sync 是内存操作同步指令,用来保证sync 之前对于内存的操作能够在sync 之后的指令开始之前完成。
MIP S 的Spin_Unlock对于MIP S 的Spin_Unlock操作,可以用如下的方式实现:表 2. Spin_UnlockLine 2:给 lockKey 赋值为 0,表示这个锁被释放。
Spinlock 的不足和改进Spinlock 在多核处理器中得到了广泛的运用,但是它也存在一些不足,特别是在对共享资源竞争很激烈的情况下,这种不足会带来很严重的后果。
(1)Spin_Lock 操作是不可重入的,在同一个核上如果在释放锁之前调用了多次 Spin_Lock ,这将导致当前 CPU 核处于死锁状态,严重情况下会引起系统的崩溃。
(2)Spinlock 不能保证进程对共享资源访问的公平性和有序性,先申请锁的进程有可能反而在后申请锁的进程之后获取锁,严重时甚至可能导致某个进程长时间无法获取锁而处于饥饿状态。
(3)当 Spinlock 被用于保护共享内存时,对于并发读操作的保护是多余的,反而会降低系统性能。
为了解决 Spinlock 的上述不足,我们提出了以下的改进措施,有些措施已经在 Linux 中有所体现。
Spinlock 的可重入性Spinlock 的设计初衷是为多核处理器所提供的,如果在一个核上调用多次 Spin_Lock ,会发生什么样的事情呢?这个问题在 Linux 的多线程编程中是个值得考虑的问题。
比如 Thread A 在用 Spin_Lock 取得共享资源操作权限后,在 Spin_Unlock 之前,它的操作由于某种原因被打断了(比如说其它高优先级的线程 Thread B 抢占,中断或者异常的发生等),在新的线程、中断(或异常)的处理程序中如果再次调用了 Spin_Lock ,这种后果可能是灾难性的。
因为 Spin_Lock 采用的是如果无法取得锁就会无限等待的策略,因此一个核在没有释放锁的情况下多次调用 Spin_Lock 会引起当前 CPU 核性能低下,甚至导致系统崩溃。
针对这种情况,Linux 提供了对于共享资源保护的原则:回页首(1)如果是单核中的共享资源保护,采用Semphore 的机制;(2)如果是多核中的共享资源保护,采用Spinlock 的机制。
同时需要注意的是,因为Semphore 和Spinlock 的策略都是循环等待锁释放,因此对于它们的使用需要小心,比如不能在中断处理函数中使用等。
针对上述原则,我们可以提供一种更安全的Spinlock ——可重入的Spinlock 机制,它的思想是,在入参中同时提供一个标志核ID 的Owner 字段,当某个CPU 核获取锁后,Owner 字段就等于当前核的ID。
在申请锁时,首先判断O w ner 字段是否等于申请者的核ID,如果相等,则跳过后面的检查流程直接操作共享资源,否则再进行Spin_Lock 的操作。
改进后的可重入的Reent_Spin_Lock实现如下:表3. Reent_S pin_Lock改进后的可重入的Reent_Spin_Unlock实现如下:表4. Reent_S pin_UnlockSpinlock 的排队策略Linux 在提出Spinlock 的概念时,是没有考虑到如何保证等待中的进程有序地获取共享资源锁的问题的。