linux内核自旋锁总结

1、自旋锁实现:

自旋锁是一个互斥设备,它只有两个值:“锁定”和“解锁”。它通常实现为某个整数值中的某个位。希望获得某个特定锁得代码测试相关的位。如果锁可用,则“锁定”被设置,而代码继续进入临界区;相反,如果锁被其他人获得,则代码进入忙循环(而不是休眠,这也是自旋锁和一般锁的区别)并重复检查这个锁,直到该锁可用为止,这就是自旋的过程。

“测试并设置位”的操作必须是原子的,多线程并发获取锁时所做的机制 ,这样,即使多个线程在给定时间自旋,也只有一个线程可获得该锁。

 

  关于自旋锁实现还有这些问题:

   1)你如何保证在SMP下其他处理器不会同时访问同一个的标志呢?(也就是标志的独占访问)

(2)必须保证每个处理器都不会去读取高速缓存而是真正的内存中的标志(可以实现,编程上可以用volitale

       要根本解决这个问题,需要在芯片底层实现物理上的内存地址独占访问,并且在实现上使用特殊的汇编指令访问。请看参考资料中对于自旋锁的实现分析。以arm为例,从存在SMP的ARM构架指令集开始(V6、V7),采用LDREX和STREX指令实现真正的自旋等待。


2、自旋锁设计目的:

自旋锁最初是为了在多处理器系统(SMP)使用而设计的,但是只要考虑到并发问题,单处理器在运行可抢占内核时其行为就类似于SMP。因此,自旋锁对于SMP和单处理器可抢占内核都适用。可以想象,当一个处理器处于自旋状态时,它做不了任何有用的工作,因此自旋锁对于单处理器不可抢占内核没有意义,实际上,非抢占式的单处理器系统上自旋锁被实现为空操作,不做任何事情。

由此可见自旋锁就是为了smp并发操作以及单处理器抢占内核实现的,所以一般进入临界区后不会禁用内核抢占。


3.自旋锁有几个重要的特性:

1)、被自旋锁保护的临界区代码执行时不能进入休眠。仅限于应用在实时进程调度策略的进程。自旋锁的含义就是保护临界区原子操作,并没有说不能进入休眠。

2)、被自旋锁保护的临界区代码执行时是不能被被其他中断中断。仅仅如果这个自旋锁会被中断使用。

3)、被自旋锁保护的临界区代码执行时,内核不能被抢占。仅限于应用在实时进程调度策略的进程,自旋锁的含义就是保护临界区原子操作,就是为抢占内核设计的,所以不能说不能被抢占。


下面就是上面考虑的几种极端情况:

考虑上面第一种情况,前提是获取锁的进程是实时进程并且优先级高于持锁进程。想象你的内核代码请求到一个自旋锁并且在它的临界区里做它的事情,在中间某处,你的代码失去了处理器。或许它已调用了一个函数(copy_from_user,假设)使进程进入睡眠。也或许,内核抢占发威,一个更高优先级的进程将你的代码推到了一边。此时,正好某个别的线程想获取同一个锁,如果这个线程运行在和你的内核代码不同的处理器上(幸运的情况),那么它可能要自旋等待一段时间(可能很长),当你的代码从休眠中唤醒或者重新得到处理器并释放锁,它就能得到锁。而最坏的情况是,那个想获取锁得线程刚好和你的代码运行在同一个处理器上,这时它将一直持有CPU进行自旋操作,而你的代码是永远不可能有任何机会来获得CPU释放这个锁了,这就是悲催的死锁前提是获取锁的进程是实时进程并且优先级高于持锁进程。

考虑上面第二种情况,前提中断中会获取该锁,和上面第一种情况类似。假设我们的驱动程序正在运行,并且已经获取了一个自旋锁,这个锁控制着对设备的访问。在拥有这个锁得时候,设备产生了一个中断,它导致中断处理例程被调用,而中断处理例程在访问设备之前,也要获得这个锁。当中断处理例程和我们的驱动程序代码在同一个处理器上运行时,由于中断处理例程持有CPU不断自旋,我们的代码将得不到机会释放锁,这也将导致死锁。中断程序中不允许进程调度,进程调度在中断返回中。

因此,如果我们有一个自旋锁,它可以被运行在(硬件或软件)中断上下文中的代码获得,则必须使用某个禁用中断的spin_lock形式的锁来禁用本地中断(注意,只是禁用本地CPU的中断,不能禁用别的处理器的中断),使用其他的锁定函数迟早会导致系统死锁(导致死锁的时间可能不定,但是发生上述死锁情况的概率肯定是有的,看处理器怎么调度了)。如果我们不会在硬中断处理例程中访问自旋锁,但可能在软中断(例如,以tasklet的形式运行的代码)中访问,则应该使用spin_lock_bh,以便在安全避免死锁的同时还能服务硬件中断。


4、使用自旋锁方法:

锁定一个自旋锁的函数有四个:

void spin_lock(spinlock_t *lock);      

最基本得自旋锁函数,它不失效本地中断。

void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);

在获得自旋锁之前禁用硬中断(只在本地处理器上),而先前的中断状态保存在flags中

void spin_lockirq(spinlock_t *lock);

在获得自旋锁之前禁用硬中断(只在本地处理器上),不保存中断状态

void spin_lock_bh(spinlock_t *lock);

在获得锁前禁用软中断,保持硬中断打开状态


  • 自旋锁变体的使用规则

       自旋锁只是实现锁的基本功能,至于是否会引起死锁得靠程序员判断:中断是否获取锁访问临界区,其他高优先级实时进程是否获取锁


  1. 不会在任何中断例程中操作临界区: 

  2. static inline void spin_lock(spinlock_t *lock)

  3.  static inline void spin_unlock(spinlock_t *lock)

  4. 如果在软件中断中操作临界区: 

  5.  static inline void spin_lock_bh(spinlock_t *lock)

  6.  static inline void spin_unlock_bh(spinlock_t *lock)

  7. bh代表bottom half,也就是中断中的底半部,因内核中断的底半部一般通过软件中断(tasklet等)来处理而得名。

  8. 如果在硬件中断中操作临界区: 

  9. static inline void spin_lock_irq(spinlock_t *lock)

  10. static inline void spin_unlock_irq(spinlock_t *lock)

  11. 如果在控制硬件中断的时候需要同时保存中断状态:

  12. spin_lock_irqsave(lock, flags)

  13. static inline void spin_unlock_irqres






通用自旋锁

自旋锁的状态值为1表示解锁状态,说明有1个资源可用;0或负值表示加锁状态,0说明可用资源数为0。Linux内核为通用自旋锁提供了API函数初始化、测试和设置自旋锁。API函数功能说明如表5。

表5 通用自旋锁API函数功能说明
宏定义功能说明
spin_lock_init(lock)初始化自旋锁,将自旋锁设置为1,表示有一个资源可用。
spin_is_locked(lock)如果自旋锁被置为1(未锁),返回0,否则返回1。
spin_unlock_wait(lock)等待直到自旋锁解锁(为1),返回0;否则返回1。
spin_trylock(lock)尝试锁上自旋锁(置0),如果原来锁的值为1,返回1,否则返回0。
spin_lock(lock)循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。
spin_unlock(lock)将自旋锁解锁(置为1)。
spin_lock_irqsave(lock, flags)循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断,将状态寄存器值存入flags。
spin_unlock_irqrestore(lock, flags)将自旋锁解锁(置为1)。开中断,将状态寄存器值从flags存入状态寄存器。
spin_lock_irq(lock)循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断。
spin_unlock_irq(lock)将自旋锁解锁(置为1)。开中断。
spin_unlock_bh(lock)将自旋锁解锁(置为1)。开启底半部的执行。
spin_lock_bh(lock)循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。阻止软中断的底半部的执行。

下面用一个使用自旋锁锁住链表的样例,代码列出如下(在arch/x386/mm/pgtable.c中):

spinlock_t pgd_lock <span class="sy0"><span style="COLOR: #339933">=</span></span> SPIN_LOCK_UNLOCKED<span class="sy0"><span style="COLOR: #339933">;</span></span> <span class="co1"><em><span style="COLOR: #666666">//锁初始化</span></em></span>
<span class="kw4"><span style="COLOR: #993333">void</span></span> pgd_dtor<span class="br0"><span style="COLOR: #099000">(</span></span><span class="kw4"><span style="COLOR: #993333">void</span></span> <span class="sy0"><span style="COLOR: #339933">*</span></span>pgd<span class="sy0"><span style="COLOR: #339933">,</span></span> kmem_cache_t <span class="sy0"><span style="COLOR: #339933">*</span></span>cache<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">long</span></span> unused<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">long</span></span> flags<span class="sy0"><span style="COLOR: #339933">;</span></span> <span class="co1"><em><span style="COLOR: #666666">//能从中断上下文中被调用</span></em></span>
 
	spin_lock_irqsave<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>pgd_lock<span class="sy0"><span style="COLOR: #339933">,</span></span> flags<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span><span class="co1"><em><span style="COLOR: #666666">//加锁</span></em></span>
	pgd_list_del<span class="br0"><span style="COLOR: #099000">(</span></span>pgd<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
	spin_unlock_irqrestore<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>pgd_lock<span class="sy0"><span style="COLOR: #339933">,</span></span> flags<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span><span class="co1"><em><span style="COLOR: #666666">//解锁</span></em></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

自旋锁用结构spinlock_t描述,在include/linux/spinlock.h中有类型 spinlock_t定义,列出如下:

<span class="kw4"><span style="COLOR: #993333">typedef</span></span> <span class="kw4"><span style="COLOR: #993333">struct</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span>
	raw_spinlock_t raw_lock<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="co2"><span style="COLOR: #339933">#ifdef CONFIG_GENERIC_LOCKBREAK    /*引入另一个自旋锁*/</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">int</span></span> break_lock<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="co2"><span style="COLOR: #339933">#endif</span></span>
<span class="co2"><span style="COLOR: #339933">#ifdef CONFIG_DEBUG_SPINLOCK   /*用于调试自旋锁*/</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">int</span></span> magic<span class="sy0"><span style="COLOR: #339933">,</span></span> owner_cpu<span class="sy0"><span style="COLOR: #339933">;</span></span>
	<span class="kw4"><span style="COLOR: #993333">void</span></span> <span class="sy0"><span style="COLOR: #339933">*</span></span>owner<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="co2"><span style="COLOR: #339933">#endif</span></span>
<span class="co2"><span style="COLOR: #339933">#ifdef CONFIG_DEBUG_LOCK_ALLOC</span></span>
	<span class="kw4"><span style="COLOR: #993333">struct</span></span> lockdep_map dep_map<span class="sy0"><span style="COLOR: #339933">;</span></span>  <span class="coMULTI"><em><span style="COLOR: #808080">/*映射lock实例到lock-class对象
#endif
} spinlock_t;</span></em></span>

由于自旋锁的性能严重地影响着操作系统的性能,Linux内核提供了Lock-class和Lockdep跟踪自旋锁的使用对象和锁的状态,并可从/proc文件系统查询自旋锁的状态信息。自旋锁的调试通过配置项CONFIG_DEBUG_*项打开。

对于对称多处理器系统(SMP),slock为一个int数据类型,对于单个处理器系统,slock定义为空。SMP的slock定义列出如下(在include/linux/spinlock_types.h):

<span class="kw4"><span style="COLOR: #993333">typedef</span></span> <span class="kw4"><span style="COLOR: #993333">struct</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">volatile</span></span> <span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">int</span></span> slock<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span> raw_spinlock_t<span class="sy0"><span style="COLOR: #339933">;</span></span>

自旋锁的实现机制类型,下面仅分析自旋锁API函数spin_lock_init、spin_lock_irqsave和spin_unlock_irqrestore。

(1)spin_lock_init

函数spin_lock_init将自旋锁状态值设置为1,表示未锁状态。其列出如下(在include/linux/spinlock.h中):

<span class="co2"><span style="COLOR: #339933"># define spin_lock_init(lock)					/
	do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)</span></span>

宏__SPIN_LOCK_UNLOCKED列出如下(在include/linux/spinlock_types.h中):

<span class="co2"><span style="COLOR: #339933"># define __SPIN_LOCK_UNLOCKED(lockname) /
	(spinlock_t)	{	.raw_lock = __RAW_SPIN_LOCK_UNLOCKED,	/
				SPIN_DEP_MAP_INIT(lockname) }</span></span>
 
<span class="co2"><span style="COLOR: #339933">#define __RAW_SPIN_LOCK_UNLOCKED	{ 1 }</span></span>

(2)函数spin_lock_irqsave

  函数spin_lock_irqsave等待直到自旋锁解锁,即自旋锁值为1,它还关闭本地处理器上的中断。其列出如下(在include/linux/spinlock.h中):

<span class="co2"><span style="COLOR: #339933">#define spin_lock_irqsave(lock, flags)	flags = _spin_lock_irqsave(lock)</span></span>

函数spin_lock_irqsave分析如下(在kernel/spinlock.c中):

<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">long</span></span> __lockfunc _spin_lock_irqsave<span class="br0"><span style="COLOR: #099000">(</span></span>spinlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">long</span></span> flags<span class="sy0"><span style="COLOR: #339933">;</span></span>
 
	local_irq_save<span class="br0"><span style="COLOR: #099000">(</span></span>flags<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>  <span class="co1"><em><span style="COLOR: #666666">//将状态寄存器的值写入flags保存</span></em></span>
	preempt_disable<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>      <span class="co1"><em><span style="COLOR: #666666">//关闭内核抢占,内核抢占锁加1</span></em></span>
	spin_acquire<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>lock<span class="sy0"><span style="COLOR: #339933">-></span></span>dep_map<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">0</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">0</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> _RET_IP_<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
 
<span class="co2"><span style="COLOR: #339933">#ifdef CONFIG_LOCKDEP</span></span>
	LOCK_CONTENDED<span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="sy0"><span style="COLOR: #339933">,</span></span> _raw_spin_trylock<span class="sy0"><span style="COLOR: #339933">,</span></span> _raw_spin_lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="co2"><span style="COLOR: #339933">#else</span></span>
	_raw_spin_lock_flags<span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="sy0"><span style="COLOR: #339933">&</span></span>flags<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="co2"><span style="COLOR: #339933">#endif</span></span>
	<span class="kw1"><span style="COLOR: #b1b100">return</span></span> flags<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

宏定义local_irq_save保存了状态寄存器的内容到x中,同时关中断。这个宏定义列出如下:

<span class="co2"><span style="COLOR: #339933">#define local_irq_save(x)	__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x): /* no input */ :"memory")</span></span>

上述语句中,指令pushfl将当前处理器的状态寄存器的内容压入堆栈保护。指令popl %0 将状态寄存器的内容存入x中,其中%0这里指x。

函数_raw_spin_lock_flags空操作等待直到自旋锁的值为1,表示有资源可用,就跳出循环等待,准备执行本函数后面的操作。其列出如下:

<span class="co2"><span style="COLOR: #339933"># define _raw_spin_lock_flags(lock, flags) /
		__raw_spin_lock_flags(&(lock)->raw_lock, *(flags�</span></span>

函数__raw_spin_lock_flags列出如下(在include/asm-x86/spinlock.h中):

<span class="co2"><span style="COLOR: #339933">#define __raw_spin_lock_flags(lock, flags) __raw_spin_lock(lock)</span></span>
<span class="kw4"><span style="COLOR: #993333">static</span></span> __always_inline <span class="kw4"><span style="COLOR: #993333">void</span></span> __raw_spin_lock<span class="br0"><span style="COLOR: #099000">(</span></span>raw_spinlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">int</span></span> inc <span class="sy0"><span style="COLOR: #339933">=</span></span> <span class="nu12"><span style="COLOR: #208080">0x00010000</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
	<span class="kw4"><span style="COLOR: #993333">int</span></span> tmp<span class="sy0"><span style="COLOR: #339933">;</span></span>
    <span class="coMULTI"><em><span style="COLOR: #808080">/*指令前缀lock用来锁住内存控制器,不让其他处理器访问,保证指令执行的原子性*/</span></em></span>
	asm <span class="kw4"><span style="COLOR: #993333">volatile</span></span><span class="br0"><span style="COLOR: #099000">(</span></span><span class="st0"><span style="COLOR: #ff0000">"lock ; xaddl %0, %1</span><span class="es1"><strong><span style="COLOR: #009900">/n</span></strong></span><span style="COLOR: #ff0000">"</span></span>   <span class="co1"><em><span style="COLOR: #666666">// lock->slock=lock->slock+inc</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"movzwl %w0, %2</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>         <span class="co1"><em><span style="COLOR: #666666">//tmp=inc</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"shrl $16, %0</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>           <span class="co1"><em><span style="COLOR: #666666">//inc >> 16 后,inc=1</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"1:</span><span class="es1"><strong><span style="COLOR: #009900">/t</span></strong></span><span style="COLOR: #ff0000">"</span></span>
		     <span class="st0"><span style="COLOR: #ff0000">"cmpl %0, %2</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>           <span class="co1"><em><span style="COLOR: #666666">//比较inc与lock->slock</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"je 2f</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>                 <span class="co1"><em><span style="COLOR: #666666">//如果inc与lock->slock相等,跳转到2</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"rep ; nop</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>           <span class="co1"><em><span style="COLOR: #666666">//空操作</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"movzwl %1, %2</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>       <span class="co1"><em><span style="COLOR: #666666">//tmp=lock->slock</span></em></span>
		     <span class="coMULTI"><em><span style="COLOR: #808080">/* 这里不需要读内存屏障指令lfence,因为装载是排序的*/</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"jmp 1b</span><span class="es1"><strong><span style="COLOR: #009900">/n</span></strong></span><span style="COLOR: #ff0000">"</span></span>               <span class="co1"><em><span style="COLOR: #666666">//跳转到1</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"2:"</span></span>
		     <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"+Q"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>inc<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="st0"><span style="COLOR: #ff0000">"+m"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="sy0"><span style="COLOR: #339933">-></span></span>slock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="st0"><span style="COLOR: #ff0000">"=r"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>tmp<span class="br0"><span style="COLOR: #099000">)</span></span>
		     <span class="sy0"><span style="COLOR: #339933">:</span></span>
		     <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"memory"</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="st0"><span style="COLOR: #ff0000">"cc"</span></span><span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

(3)函数spin_unlock_irqrestore

宏定义spin_unlock_irqrestore是解锁,开中断,并把flags值存入到状态寄存器中,这个宏定义分析如下:

<span class="co2"><span style="COLOR: #339933">#define spin_unlock_irqrestore(lock, flags)	_spin_unlock_irqrestore(lock, flags)</span></span>

函数_spin_unlock_irqrestore列出如下(在kernel/spinlock.c中):

<span class="kw4"><span style="COLOR: #993333">void</span></span> __lockfunc _spin_unlock_irqrestore<span class="br0"><span style="COLOR: #099000">(</span></span>spinlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>lock<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">long</span></span> flags<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	spin_release<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>lock<span class="sy0"><span style="COLOR: #339933">-></span></span>dep_map<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">1</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> _RET_IP_<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
	_raw_spin_unlock<span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>    <span class="co1"><em><span style="COLOR: #666666">//解锁</span></em></span>
	local_irq_restore<span class="br0"><span style="COLOR: #099000">(</span></span>flags<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>  <span class="co1"><em><span style="COLOR: #666666">//开中断,将flag的值存入状态寄存器</span></em></span>
	preempt_enable<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>          <span class="co1"><em><span style="COLOR: #666666">//开启内核抢占</span></em></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>
 
<span class="co2"><span style="COLOR: #339933"># define _raw_spin_unlock(lock)		__raw_spin_unlock(&(lock)->raw_lock)</span></span>

函数__raw_spin_unlock将自旋锁状态值加1,表示有1个资源可用,从而释放自旋锁,其列出如下(在include/asm-x86/spinlock.h中):

<span class="kw4"><span style="COLOR: #993333">static</span></span> __always_inline <span class="kw4"><span style="COLOR: #993333">void</span></span> __raw_spin_unlock<span class="br0"><span style="COLOR: #099000">(</span></span>raw_spinlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	asm <span class="kw4"><span style="COLOR: #993333">volatile</span></span><span class="br0"><span style="COLOR: #099000">(</span></span>UNLOCK_LOCK_PREFIX <span class="st0"><span style="COLOR: #ff0000">"incw %0"</span></span>       <span class="co1"><em><span style="COLOR: #666666">// lock->slock= lock->slock +1</span></em></span>
		     <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"+m"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="sy0"><span style="COLOR: #339933">-></span></span>slock<span class="br0"><span style="COLOR: #099000">)</span></span>
		     <span class="sy0"><span style="COLOR: #339933">:</span></span>
		     <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"memory"</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="st0"><span style="COLOR: #ff0000">"cc"</span></span><span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

读/写自旋锁

"读/写自旋锁"用来解决读者/写者问题。如果有多个线程(进程、中断处理程序、底半部例程)以只读的方式访问一个临界区数据,读/写自旋锁允许多个线程同时读取数据。如果一个线程需要对临界区数据进行写操作,它必须获取写锁,只有在没有读者或写者进行操作时,写者才独占临界区数据进行写操作。读操作时需要获取读锁,写操作时需要获取写锁。

Linux内核为读/写自旋锁提供了操作API函数初始化、测试和设置自旋锁。API函数功能说明如表5。

表5 读/写自旋锁API函数功能说明
宏定义功能说明
rwlock_init(lock)初始化自旋锁值为0x01000000(未锁)。
read_lock(lock)加读者锁,即将读者计数加1。
read_lock_irqsave(lock, flags)加读者锁,即将读者计数加1。并且关中断,存储状态标识到flags中。
read_lock_irq(lock)加读者锁,即将读者计数加1。并且关中断。
read_unlock(lock)解读者锁,即将读者计数减1。
read_unlock_irqrestore(lock, flags)解读者锁,即将读者计数减1。并且开中断,将状态标识从flags读到状态寄存器中。
read_unlock_irq(lock)解读者锁,即将读者计数减1。并且开中断。
write_lock(lock)加写者锁,即将写者锁置0。
write_lock_irqrestore(lock, flags)加写者锁,即将写者锁置0。并且关中断,存储状态标识到flags中。
write_lock_irq(lock)加写者锁,即将写者锁置0。并且关中断。
write_unlock(lock)解写者锁,即将写者锁置1。
write_unlock_irqrestore(lock, flags)解写者锁,即将写者锁置1。并且开中断,将状态标识从flags读到状态寄存器中。
write_unlock_irq(lock)解写者锁,即将写者锁置1。并且开中断。

用户使用读/写自旋锁,应先自旋锁的状态值初始化为锁初始化为RW_LOCK_BIAS,即0x01000000,表示为未锁状态。

读/写自旋锁用结构rwlock_t描述,它的主要成员为锁状态值变量lock,结构rwlock_t列出如下(在include/linux/spinlock_types.h中):

<span class="kw4"><span style="COLOR: #993333">typedef</span></span> <span class="kw4"><span style="COLOR: #993333">struct</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span>
	raw_rwlock_t raw_lock<span class="sy0"><span style="COLOR: #339933">;</span></span>
    ……
<span class="br0"><span style="COLOR: #099000">}</span></span> rwlock_t<span class="sy0"><span style="COLOR: #339933">;</span></span>
 
<span class="kw4"><span style="COLOR: #993333">typedef</span></span> <span class="kw4"><span style="COLOR: #993333">struct</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">int</span></span> lock<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span> raw_rwlock_t<span class="sy0"><span style="COLOR: #339933">;</span></span>

在结构raw_rwlock_t中,读/写自旋锁状态变量lock为32位,它分为2个部分,0~23位是一个24位计数器,表示对临界数据区进行并发读操作的线程数,线程数以补码形式存入计数器;第24位为表示"未锁"的状态位,在没有线程读或写临界区时,设置为1,否则,设置为0。

如果自旋锁设置了"未锁"状态且无读者,那么lock值为0x01000000;如果写者已获得自旋锁且无读者,则未锁状态位清0,lock值为0x00000000。如果有一个、2个或多个线程获取锁对临界数据区进行读操作,则lock值为0x00ffffff、0x00fffffe等(第24位清0表示未锁,第0~23位为读者个数的补码)。

下面说明读/写自旋锁API函数的实现:

(1)函数rwlock_init

函数rwlock_init将读/写自旋锁状态值设为0x01000000,其列出如下(在include/linux/spinlock.h中):

<span class="co2"><span style="COLOR: #339933"># define rwlock_init(lock)					/
	do { *(lock) = RW_LOCK_UNLOCKED; } while (0)</span></span>
 
<span class="co2"><span style="COLOR: #339933">#define RW_LOCK_UNLOCKED	__RW_LOCK_UNLOCKED(old_style_rw_init)</span></span>
<span class="co2"><span style="COLOR: #339933">#define __RW_LOCK_UNLOCKED(lockname) /
	(rwlock_t)	{	.raw_lock = __RAW_RW_LOCK_UNLOCKED,	/
				RW_DEP_MAP_INIT(lockname) }</span></span>
 
<span class="co2"><span style="COLOR: #339933">#define __RAW_RW_LOCK_UNLOCKED		{ RW_LOCK_BIAS }</span></span>
<span class="co2"><span style="COLOR: #339933">#define RW_LOCK_BIAS		 0x01000000</span></span>

(2)函数read_lock和read_unlock

函数read_lock用于加读者锁,函数read_unlock用于解读者锁,两函数需要配对使用。下面分别进行说明:

函数read_lock

读/写自旋锁lock空闲值为0x01000000,当有一个读者进行读操作时,它加读者锁,执行运算lock=lock-1,lock值为0x00ffffff;当接着有第二个读者进行读操作时,可以进行并发的读,再执行运算lock=lock-1,lock值为0x00fffffe;依此类推,可支持多个读者同时读操作。

如果在读操作正进行(如:有2个读者正进行操作,lock值为0x00fffffe)时,有一个写者请求写操作时,写操作必须等待读者全部完成操作,每个读者完成操作时,执行运算lock=lock+1,当2个读者的操作完成后,lock值为0x01000000,表示写锁空闲,可以进行写操作或并发的读操作。

如果一个写操作正进行时,执行运算lock=lock-0x01000000,lock值为0x00000000,表示写者锁已加锁,另一个写者无法对临界区数据进行访问。此时,如果有一个读者进行读操作请求时,执行运算lock=lock-1,结果为负数,则状态寄存器符号位置为1,加读者锁失败,将lock还原(lock=lock+1),读者循环等待,直到写操作完成(即lock值为0x01000000)时。

写操作完成时,lock值为0x01000000,表示写锁空闲,可以进行写操作或并发的读操作。这时,正等待的读者执行运算lock=lock-1,结果为0x00ffffff,则状态寄存器符号位置为0,跳出加读者锁的等待循环,加锁成功,读者进行读操作。

函数read_lock关闭内核抢占,加读者锁,即将读者数增加1,其列出如下(在include/linux/spinlock.h中):

<span class="co2"><span style="COLOR: #339933">#define read_lock(lock)			_read_lock(lock)</span></span>

函数_read_lock列出如下(在kernel/spinlock.c中):

<span class="kw4"><span style="COLOR: #993333">void</span></span> __lockfunc _read_lock<span class="br0"><span style="COLOR: #099000">(</span></span>rwlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	preempt_disable<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>      <span class="co1"><em><span style="COLOR: #666666">//关闭内核抢占</span></em></span>
	rwlock_acquire_read<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>lock<span class="sy0"><span style="COLOR: #339933">-></span></span>dep_map<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">0</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">0</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> _RET_IP_<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>  <span class="coMULTI"><em><span style="COLOR: #808080">/*用于自旋锁调试*/</span></em></span>
    <span class="coMULTI"><em><span style="COLOR: #808080">/*下面语句相当于_raw_read_lock(lock)*/</span></em></span>
	LOCK_CONTENDED<span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="sy0"><span style="COLOR: #339933">,</span></span> _raw_read_trylock<span class="sy0"><span style="COLOR: #339933">,</span></span> _raw_read_lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>
<span class="co2"><span style="COLOR: #339933"># define _raw_read_lock(rwlock)		__raw_read_lock(&(rwlock)->raw_lock)</span></span>

函数__raw_read_lock增加读锁,即锁状态值rw减1,由于读者计数以补码形式存放在锁状态值中,因此,减1表示读者计数增加1。其列出如下(在include/asm-x86/spinglock.h中):

<span class="kw4"><span style="COLOR: #993333">static</span></span> <span class="kw2"><strong>inline</strong></span> <span class="kw4"><span style="COLOR: #993333">void</span></span> __raw_read_lock<span class="br0"><span style="COLOR: #099000">(</span></span>raw_rwlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	asm <span class="kw4"><span style="COLOR: #993333">volatile</span></span><span class="br0"><span style="COLOR: #099000">(</span></span>LOCK_PREFIX <span class="st0"><span style="COLOR: #ff0000">" subl $1,(%0)</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>     <span class="co1"><em><span style="COLOR: #666666">//*rw=*rw-1</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"jns 1f</span><span class="es1"><strong><span style="COLOR: #009900">/n</span></strong></span><span style="COLOR: #ff0000">"</span></span>                             <span class="co1"><em><span style="COLOR: #666666">//如果符号位为0,跳转到1</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"call __read_lock_failed</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>
		     <span class="st0"><span style="COLOR: #ff0000">"1:</span><span class="es1"><strong><span style="COLOR: #009900">/n</span></strong></span><span style="COLOR: #ff0000">"</span></span>
		     <span class="sy0"><span style="COLOR: #339933">::</span></span><span class="me2"><span style="COLOR: #202020">LOCK_PTR_REG</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>rw<span class="br0"><span style="COLOR: #099000">)</span></span> <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"memory"</span></span><span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

函数__read_lock_failed进行加读者锁失败后的循环等待操作。加读者锁失败,说明有一个写者正在写操作,因此,锁状态值为*rw=0x00000000,函数__raw_read_lock在执行*rw=*rw-1后,rw值为0xffffffff,即传入函数__read_lock_failed的rw值为0xffffffff。

函数__read_lock_failed执行*rw=*rw+1后,锁状态值为*rw=0x00000000,然后,进入循环等待状态,直到,写者完成写操作后将锁状态值*rw置为0x01000000。这时,函数__read_lock_failed才跳出循环等待状态,加读者锁成功。

函数__read_lock_failed列出如下(在include/asm-x86/lib/rwlock_64.h中):

<span class="sy0"><span style="COLOR: #339933">/*</span></span> rdi指向rwlock_t <span class="sy0"><span style="COLOR: #339933">*/</span></span>
ENTRY<span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span>__read_lock_failed<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>
	CFI_STARTPROC           <span class="sy0"><span style="COLOR: #339933">//</span></span>即:#define CFI_STARTPROC <span class="sy0"><span style="COLOR: #339933">.</span></span>cfi_startproc
	LOCK_PREFIX
	incl <span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span><span class="sy0"><span style="COLOR: #339933">%</span></span>rdi<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>           <span class="sy0"><span style="COLOR: #339933">//</span></span> <span class="sy0"><span style="COLOR: #339933">*</span></span>rw=<span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="sy0"><span style="COLOR: #339933">+</span></span><span class="nu0"><span style="COLOR: #00ff00">1</span></span>,值为<span class="nu0"><span style="COLOR: #00ff00">0x00000000</span></span>
<span class="nu0"><span style="COLOR: #00ff00">1</span></span><span class="sy0"><span style="COLOR: #339933">:</span></span>	<span class="kw1"><strong><span style="COLOR: #007f00">rep</span></strong></span>                   <span class="sy0"><span style="COLOR: #339933">//</span></span>循环等待<span class="sy0"><span style="COLOR: #339933">*</span></span>rw值被写者修改为<span class="nu0"><span style="COLOR: #00ff00">0x01000000</span></span>
	<span class="kw1"><strong><span style="COLOR: #007f00">nop</span></strong></span>
	cmpl $<span class="nu0"><span style="COLOR: #00ff00">1</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span><span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span><span class="sy0"><span style="COLOR: #339933">%</span></span>rdi<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>     <span class="sy0"><span style="COLOR: #339933">//</span></span> <span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="sy0"><span style="COLOR: #339933">-</span></span><span class="nu0"><span style="COLOR: #00ff00">1</span></span>
	<span class="kw1"><strong><span style="COLOR: #007f00">js</span></strong></span> <span class="nu0"><span style="COLOR: #00ff00">1b</span></span>              <span class="sy0"><span style="COLOR: #339933">//</span></span>如果符号位为<span class="nu0"><span style="COLOR: #00ff00">1</span></span>,表明<span class="sy0"><span style="COLOR: #339933">*</span></span>rw值还为<span class="nu0"><span style="COLOR: #00ff00">0x00000000</span></span>,跳转到<span class="nu0"><span style="COLOR: #00ff00">1</span></span>进行循环等待
	LOCK_PREFIX
    <span class="sy0"><span style="COLOR: #339933">/*</span></span> 运行到这里,说明写者操作完成,<span class="sy0"><span style="COLOR: #339933">*</span></span>rw值为<span class="nu0"><span style="COLOR: #00ff00">0x01000000</span></span> <span class="sy0"><span style="COLOR: #339933">*/</span></span>
	decl <span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span><span class="sy0"><span style="COLOR: #339933">%</span></span>rdi<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>       <span class="sy0"><span style="COLOR: #339933">//</span></span>执行加读者锁操作<span class="sy0"><span style="COLOR: #339933">*</span></span>rw=<span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="sy0"><span style="COLOR: #339933">-</span></span><span class="nu0"><span style="COLOR: #00ff00">1</span></span>
	<span class="kw1"><strong><span style="COLOR: #007f00">js</span></strong></span> __read_lock_failed<span class="sy0"><span style="COLOR: #339933">//</span></span>如果符号位为<span class="nu0"><span style="COLOR: #00ff00">1</span></span>,表明<span class="sy0"><span style="COLOR: #339933">*</span></span>rw值为<span class="nu0"><span style="COLOR: #00ff00">0x00000000</span></span>,跳转到函数开头进行循环等待
	<span class="kw1"><strong><span style="COLOR: #007f00">ret</span></strong></span>
	CFI_ENDPROC             <span class="sy0"><span style="COLOR: #339933">//</span></span>即:#define CFI_ENDPROC <span class="sy0"><span style="COLOR: #339933">.</span></span>cfi_endproc
<strong><span class="kw4">END</span><span class="br0"><span style="COLOR: #099000">(</span></span></strong>__read_lock_failed<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>

由于汇编语言程序无法产生帧信息,由用户手动添加指示语句。上述代码中,指示语句.cfi_startproc用于调试时的调用帧信息处理,在每个函数的开始处使用,它在.eh_frame中生成一个条目,初始化一些内部数据结构,并发出构架依赖的初始CFI(Call Frame Information)指令。在函数结束处使用.cfi_endproc关闭该功能。

函数read_unlock

函数read_unlock开读者锁,即将锁状态值减1,由于读者计数以补码形式存放在锁状态值中,因此,加1表示读者计数减1。其列出如下

<span class="co2"><span style="COLOR: #339933"># define read_unlock(lock) /
do {__raw_read_unlock(&(lock)->raw_lock); __release(lock); } while (0)</span></span>
<span class="co2"><span style="COLOR: #339933"># define __release(x)	__context__(x,-1)</span></span>
<span class="kw4"><span style="COLOR: #993333">static</span></span> <span class="kw2"><strong>inline</strong></span> <span class="kw4"><span style="COLOR: #993333">void</span></span> __raw_read_unlock<span class="br0"><span style="COLOR: #099000">(</span></span>raw_rwlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>   <span class="coMULTI"><em><span style="COLOR: #808080">/* rw->lock= rw->lock +1*/</span></em></span>
	asm <span class="kw4"><span style="COLOR: #993333">volatile</span></span><span class="br0"><span style="COLOR: #099000">(</span></span>LOCK_PREFIX <span class="st0"><span style="COLOR: #ff0000">"incl %0"</span></span> <span class="sy0"><span style="COLOR: #339933">:</span></span><span class="st0"><span style="COLOR: #ff0000">"+m"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>rw<span class="sy0"><span style="COLOR: #339933">-></span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span> <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"memory"</span></span><span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

(3)函数write_lock和write_unlock

函数write_lock和write_unlock分别加写者锁和解写者锁,分别说明如下:

函数write_lock

只有在没有读者或写者对临界区数据进行操作时,加写者锁才会成功,即:只有锁状态值lock值为0x01000000时,写者加锁才能成功,执行运行lock=lock-0x01000000运算。

当有读者或写者操作临界区数据时,lock值为比0x01000000小的正数,如果值为0x00000000表示有一个写者正在写操作,如果值为0x00ffffff,表示有1个读者在进行读操作,如果值为0x00fffffe,表示有2个读者在进行读操作,依此类推。此时,写者只能循环等待,直到lock值为0x01000000。

函数write_lock关闭内核抢占,加写者锁,其列出如下(在include/linux/spinlock.h中):

<span class="co2"><span style="COLOR: #339933">#define write_lock(lock)		_write_lock(lock)</span></span>

函数_write_lock列出如下(在kernel/spinlock.c中):

<span class="kw4"><span style="COLOR: #993333">void</span></span> __lockfunc _write_lock<span class="br0"><span style="COLOR: #099000">(</span></span>rwlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	preempt_disable<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>           <span class="coMULTI"><em><span style="COLOR: #808080">/*关闭内核抢占*/</span></em></span>
	rwlock_acquire<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>lock<span class="sy0"><span style="COLOR: #339933">-></span></span>dep_map<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">0</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">0</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> _RET_IP_<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>  <span class="coMULTI"><em><span style="COLOR: #808080">/*用于自旋锁调试*/</span></em></span>
     <span class="coMULTI"><em><span style="COLOR: #808080">/*下面语句相当于_raw_write_lock(lock)*/</span></em></span>
	LOCK_CONTENDED<span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="sy0"><span style="COLOR: #339933">,</span></span> _raw_write_trylock<span class="sy0"><span style="COLOR: #339933">,</span></span> _raw_write_lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>
 
<span class="co2"><span style="COLOR: #339933"># define _raw_write_lock(rwlock)	__raw_write_lock(&(rwlock)->raw_lock)</span></span>
 
<span class="kw4"><span style="COLOR: #993333">static</span></span> <span class="kw2"><strong>inline</strong></span> <span class="kw4"><span style="COLOR: #993333">void</span></span> __raw_write_lock<span class="br0"><span style="COLOR: #099000">(</span></span>raw_rwlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	asm <span class="kw4"><span style="COLOR: #993333">volatile</span></span><span class="br0"><span style="COLOR: #099000">(</span></span>LOCK_PREFIX <span class="st0"><span style="COLOR: #ff0000">" subl %1,(%0)</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>     <span class="coMULTI"><em><span style="COLOR: #808080">/* RW_LOCK_BIAS-rw*/</span></em></span>
              <span class="coMULTI"><em><span style="COLOR: #808080">/* 如果没有读者或写者,*rw为0x01000000,RW_LOCK_BIAS-rw为0  */</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"jz 1f</span><span class="es1"><strong><span style="COLOR: #009900">/n</span></strong></span><span style="COLOR: #ff0000">"</span></span>      <span class="coMULTI"><em><span style="COLOR: #808080">/*值为0,跳转到1*/</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"call __write_lock_failed</span><strong><span style="COLOR: #009900"><span class="es1">/n</span><span class="es1">/t</span></span></strong><span style="COLOR: #ff0000">"</span></span>    <span class="coMULTI"><em><span style="COLOR: #808080">/*加写者锁失败*/</span></em></span>
		     <span class="st0"><span style="COLOR: #ff0000">"1:</span><span class="es1"><strong><span style="COLOR: #009900">/n</span></strong></span><span style="COLOR: #ff0000">"</span></span>
             <span class="coMULTI"><em><span style="COLOR: #808080">/* RW_LOCK_BIAS定义为0x01000000*/</span></em></span>
		     <span class="sy0"><span style="COLOR: #339933">::</span></span><span class="me2"><span style="COLOR: #202020">LOCK_PTR_REG</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>rw<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="st0"><span style="COLOR: #ff0000">"i"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>RW_LOCK_BIAS<span class="br0"><span style="COLOR: #099000">)</span></span> <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"memory"</span></span><span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span> 
<span class="br0"><span style="COLOR: #099000">}</span></span>

运行函数__write_lock_failed时,说明加写者锁失败。如果加写者锁失败,说明有读者或写者正在访问临界区数据,*rw值为一个小于0x01000000的正数。此时,函数__write_lock_failed循环等待直到,读者或写者完成操作,锁变为空闲,即*rw值为0x01000000。

函数__write_lock_failed列出如下(在include/asm-x86/lib/rwlock_64.h中):

<span class="sy0"><span style="COLOR: #339933">/*</span></span> rdi<span class="sy0"><span style="COLOR: #339933">:</span></span>	pointer to rwlock_t <span class="sy0"><span style="COLOR: #339933">*/</span></span>
ENTRY<span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span>__write_lock_failed<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>
	CFI_STARTPROC         <span class="sy0"><span style="COLOR: #339933">/*</span></span>用于调试时将调用帧信息写入
	LOCK_PREFIX
	addl $RW_LOCK_BIAS<span class="sy0"><span style="COLOR: #339933">,</span></span><span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span><span class="sy0"><span style="COLOR: #339933">%</span></span>rdi<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span> <span class="sy0"><span style="COLOR: #339933">//</span></span> <span class="sy0"><span style="COLOR: #339933">*</span></span>rw=<span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="sy0"><span style="COLOR: #339933">+</span></span>$RW_LOCK_BIAS,还原为尝试加锁前的状态值
<span class="nu0"><span style="COLOR: #00ff00">1</span></span><span class="sy0"><span style="COLOR: #339933">:</span></span>	<span class="kw1"><strong><span style="COLOR: #007f00">rep</span></strong></span>
	<span class="kw1"><strong><span style="COLOR: #007f00">nop</span></strong></span>
	cmpl $RW_LOCK_BIAS<span class="sy0"><span style="COLOR: #339933">,</span></span><span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span><span class="sy0"><span style="COLOR: #339933">%</span></span>rdi<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>   <span class="sy0"><span style="COLOR: #339933">//</span></span>比较结果 = <span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="sy0"><span style="COLOR: #339933">-</span></span>$RW_LOCK_BIAS
	<span class="kw1"><strong><span style="COLOR: #007f00">jne</span></strong></span> <span class="nu0"><span style="COLOR: #00ff00">1b</span></span>     <span class="sy0"><span style="COLOR: #339933">//</span></span>比较结果不为<span class="nu0"><span style="COLOR: #00ff00">0</span></span>,说明有写者或读者在访问临界区,跳转到<span class="nu0"><span style="COLOR: #00ff00">1</span></span>进行循环等待
	LOCK_PREFIX  <span class="sy0"><span style="COLOR: #339933">//</span></span>锁内存管理器,确保原子操作
     <span class="sy0"><span style="COLOR: #339933">/*</span></span>运行到这里,说明锁空闲,<span class="sy0"><span style="COLOR: #339933">*</span></span>rw值为<span class="nu0"><span style="COLOR: #00ff00">0x010000</span></span>,执行加写者锁操作<span class="sy0"><span style="COLOR: #339933">*/</span></span>
	subl $RW_LOCK_BIAS<span class="sy0"><span style="COLOR: #339933">,</span></span><span class="br0"><strong><span style="COLOR: #099000">(</span></strong></span><span class="sy0"><span style="COLOR: #339933">%</span></span>rdi<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>  <span class="sy0"><span style="COLOR: #339933">//*</span></span>rw=<span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="sy0"><span style="COLOR: #339933">-</span></span>RW_LOCK_BIAS
	<span class="kw1"><strong><span style="COLOR: #007f00">jnz</span></strong></span>  __write_lock_failed  <span class="sy0"><span style="COLOR: #339933">/*</span></span>如果<span class="sy0"><span style="COLOR: #339933">*</span></span>rw不为<span class="nu0"><span style="COLOR: #00ff00">0</span></span>,说明加写者锁失败,跳转到函数头循环等待<span class="sy0"><span style="COLOR: #339933">*/</span></span>
	<span class="kw1"><strong><span style="COLOR: #007f00">ret</span></strong></span>
	CFI_ENDPROC
<strong><span class="kw4">END</span><span class="br0"><span style="COLOR: #099000">(</span></span></strong>__write_lock_failed<span class="br0"><strong><span style="COLOR: #099000">)</span></strong></span>


函数write_unlock

函数write_unlock在写者操作完后解写者锁,读/写自旋锁变为空闲,锁状态值lock变为: 0x00000000+0x01000000。以后,读者或写者可以访问临界区数据了。

函数write_unlock列出如下:

<span class="co2"><span style="COLOR: #339933"># define write_unlock(lock) /
    do {__raw_write_unlock(&(lock)->raw_lock); __release(lock); } while (0)</span></span>

函数_write_unlock列出如下(在kernel/spinlock.c中):

<span class="kw4"><span style="COLOR: #993333">void</span></span> __lockfunc _write_unlock<span class="br0"><span style="COLOR: #099000">(</span></span>rwlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	rwlock_release<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>lock<span class="sy0"><span style="COLOR: #339933">-></span></span>dep_map<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="nu0"><span style="COLOR: #00dd00">1</span></span><span class="sy0"><span style="COLOR: #339933">,</span></span> _RET_IP_<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
	_raw_write_unlock<span class="br0"><span style="COLOR: #099000">(</span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
	preempt_enable<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>        <span class="coMULTI"><em><span style="COLOR: #808080">/*打开内核抢占*/</span></em></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>
 
<span class="co2"><span style="COLOR: #339933"># define _raw_write_unlock(rwlock)	__raw_write_unlock(&(rwlock)->raw_lock)</span></span>

函数__raw_write_unlock开写者锁,即将锁状态值加上RW_LOCK_BIAS,其列出如下(在include/asm-x86/spinlock.h中):

<span class="kw4"><span style="COLOR: #993333">static</span></span> <span class="kw2"><strong>inline</strong></span> <span class="kw4"><span style="COLOR: #993333">void</span></span> __raw_write_unlock<span class="br0"><span style="COLOR: #099000">(</span></span>raw_rwlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>rw<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	asm <span class="kw4"><span style="COLOR: #993333">volatile</span></span><span class="br0"><span style="COLOR: #099000">(</span></span>LOCK_PREFIX <span class="st0"><span style="COLOR: #ff0000">"addl %1, %0"</span></span>   <span class="coMULTI"><em><span style="COLOR: #808080">/* RW_LOCK_BIAS+rw*/</span></em></span>
		     <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"+m"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>rw<span class="sy0"><span style="COLOR: #339933">-></span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span> <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"i"</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>RW_LOCK_BIAS<span class="br0"><span style="COLOR: #099000">)</span></span> <span class="sy0"><span style="COLOR: #339933">:</span></span> <span class="st0"><span style="COLOR: #ff0000">"memory"</span></span><span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

顺序锁

当使用读/写锁时,读者必须等待写者完成时才能读,写者必须等待读者完成时才能写,两者的优先权是平等的。顺序锁是对读/写锁的优化,它允许读写同时进行,提高了并发性,读写操作同时进行的概率较小时,其性能很好。顺序锁对读/写锁进行了下面的改进:

  • 写者不会阻塞读者,即写操作时,读者仍可以进行读操作。
  • 写者不需要等待所有读者完成读操作后才进行写操作。
  • 写者与写者之间互斥,即如果有写者在写操作时,其他写者必须自旋等待。
  • 如果在读者进行读操作期间,有写者进行写操作,那么读者必须重新读取数据,确保读取正确的数据。
  • 要求临界区的共享资源不含指针,因为如果写者使指针失效,读者访问该指针,将导致崩溃。

顺序锁实际上由一个自旋锁和一个顺序计数器组成,有的应用已包括自旋锁,只需要一个顺序计数器配合就可以实现顺序锁。针对这两种情况,Linux内核给顺序锁提供了两套API函数。一套API函数为*seq*,完整地实现了顺序锁;另一套API函数为*seqcount*,只包含了顺序计数器,需要与用户的自旋锁配套实现顺序锁。顺序锁API函数的功能说明如表5所示。

表5 顺序锁API函数功能说明
函数名功能说明
seqlock_init(x)初始化顺序锁,将顺序计数器置0。
write_seqlock(seqlock_t *sl)加顺序锁,将顺序号加1。写者获取顺序锁s1访问临界区,它使用了函数spin_lock。
write_sequnlock(seqlock_t *sl)解顺序锁,使用了函数spin_unlock,顺序号加1。
write_tryseqlock(seqlock_t *sl)功能上等同于spin_trylock,顺序号加1。
read_seqbegin(const seqlock_t *sl)返回顺序锁s1的当前顺序号,读者没有开锁和释放锁的开销。
read_seqretry(const seqlock_t *sl, unsigned start)检查读操作期间是否有写者访问了共享资源,如果是,读者就需要重新进行读操作,否则,读者成功完成了读操作。
seqcount_init(x)初始化顺序号。
read_seqcount_begin(const seqcount_t *s)读者在读操作前用此函数获取当前的顺序号。
read_seqcount_retry(const seqcount_t *s, unsigned start)读者在访问完后调用此函数检查在读期间是否有写者访问临界区。如果有,读者需要重新进行读操作,否则,完成读操作。
write_seqcount_begin(seqcount_t *s)写者在访问临界区前调用此函数将顺序号加1,以便读者检查是否在读期间有写者访问过。
write_seqcount_end(seqcount_t *s)写者写完成后调用此函数将顺序号加1,以便读者能检查出是否在读期间有写者访问过。

用户使用顺序锁时,写操作加锁方法与自旋锁一样,但读操作需要使用循环查询,使用顺序锁的读操作样例列出如下(在kernel/time.c中):

u64 get_jiffies_64<span class="br0"><span style="COLOR: #099000">(</span></span><span class="kw4"><span style="COLOR: #993333">void</span></span><span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> <span class="kw4"><span style="COLOR: #993333">long</span></span> seq<span class="sy0"><span style="COLOR: #339933">;</span></span>
	u64 ret<span class="sy0"><span style="COLOR: #339933">;</span></span>
 
	<span class="kw1"><span style="COLOR: #b1b100">do</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span>
		seq <span class="sy0"><span style="COLOR: #339933">=</span></span> read_seqbegin<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>xtime_lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>   <span class="co1"><em><span style="COLOR: #666666">//获取当前的顺序号</span></em></span>
		ret <span class="sy0"><span style="COLOR: #339933">=</span></span> jiffies_64<span class="sy0"><span style="COLOR: #339933">;</span></span>        <span class="co1"><em><span style="COLOR: #666666">//读取临界区数据</span></em></span>
          <span class="coMULTI"><em><span style="COLOR: #808080">/*检查seq值与当前顺序号是否相等,若不等,说明有写者开始工作,函数read_seqretry返回1,继续循环*/</span></em></span>
	<span class="br0"><span style="COLOR: #099000">}</span></span> <span class="kw1"><span style="COLOR: #b1b100">while</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>read_seqretry<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>xtime_lock<span class="sy0"><span style="COLOR: #339933">,</span></span> seq<span style="COLOR: #099000"><span class="br0">)</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span> 
	<span class="kw1"><span style="COLOR: #b1b100">return</span></span> ret<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

在非SMP系统上,自旋锁消失,但写者还必须递增顺序变量,因为中断例程可能改变数据的状态。

下面分析顺序锁的数据结构及API函数:

(1)顺序锁结构seqlock_t

顺序锁用结构seqlock_t描述,它包括顺序计数器sequence和自旋锁lock。结构seqlock_t列出如下(在include/linux/seqlock.h中):

<span class="kw4"><span style="COLOR: #993333">typedef</span></span> <span class="kw4"><span style="COLOR: #993333">struct</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> sequence<span class="sy0"><span style="COLOR: #339933">;</span></span>
	spinlock_t lock<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span> seqlock_t<span class="sy0"><span style="COLOR: #339933">;</span></span>

在结构seqlock_t中,顺序计数器sequence存放顺序号,每个读者在读数据前后两次读顺序计数器,并检查两次读到的顺序号是否相同。如果不相同,说明新的写者已经开始写并增加了顺序计数器,表明刚读到的数据无效。

写者通过调用函数write_seqlock获取顺序锁,将顺序号加1,调用函数write_sequnlock释放顺序锁,再将顺序号加1。这样,写者正在写操作时,顺序号为奇数,写完临界区数据后,顺序号为偶数。

读者应以循环查询方法读取临界区数据,读者执行的临界区代码的方法列出如下:

<span class="kw1"><span style="COLOR: #b1b100">do</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span>
	    seq <span class="sy0"><span style="COLOR: #339933">=</span></span> read_seqbegin<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>foo<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>   <span class="co1"><em><span style="COLOR: #666666">//返回当前的顺序号</span></em></span>
 	...    <span class="co1"><em><span style="COLOR: #666666">//临界区数据操作</span></em></span>
    <span class="br0"><span style="COLOR: #099000">}</span></span> <span class="kw1"><span style="COLOR: #b1b100">while</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>read_seqretry<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>foo<span class="sy0"><span style="COLOR: #339933">,</span></span> seq<span style="COLOR: #099000"><span class="br0">)</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>

在上述代码中,读者在读临界区数据之前,先调用函数read_seqbegin获致当前的顺序号,如果顺序号seq为奇数,说明写者正写临界区数据,或者seq值与顺序号当前值不等,表明读者正读时,写者开始写,函数read_seqretry返回1,读者继续循环等待写者完成。

(2)顺序锁初始化函数seqlock_init

函数seqlock_init初始化顺序锁,顺序锁实际上由一个自旋锁和一个顺序计数器组成。其列出如下:

<span class="co2"><span style="COLOR: #339933">#define seqlock_init(x)					/
	do {						/
		(x)->sequence = 0;			/
		spin_lock_init(&(x)->lock);		/
	} while (0)</span></span>

(3)写者加锁函数write_seqlock

函数write_seqlock加顺序锁。方法是:它先加自旋锁,然后将顺序号加1,此时,顺序号值为奇数。此函数不需要关闭内核抢占,因为自旋锁加锁时已关闭了内核抢占。其列出如下:

<span class="kw4"><span style="COLOR: #993333">static</span></span> <span class="kw2"><strong>inline</strong></span> <span class="kw4"><span style="COLOR: #993333">void</span></span> write_seqlock<span class="br0"><span style="COLOR: #099000">(</span></span>seqlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>sl<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	spin_lock<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>sl<span class="sy0"><span style="COLOR: #339933">-></span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
	<span class="sy0"><span style="COLOR: #339933">++</span></span>sl<span class="sy0"><span style="COLOR: #339933">-></span></span>sequence<span class="sy0"><span style="COLOR: #339933">;</span></span>
	smp_wmb<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

(4)写者解锁函数write_sequnlock

函数write_sequnlock表示写者解顺序锁,它将顺序号加1,然后解开自旋锁。此时,顺序号应为偶数。其列出如下(在include/linux/seqlock.h中):

<span class="kw4"><span style="COLOR: #993333">static</span></span> <span class="kw2"><strong>inline</strong></span> <span class="kw4"><span style="COLOR: #993333">void</span></span> write_sequnlock<span class="br0"><span style="COLOR: #099000">(</span></span>seqlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>sl<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	smp_wmb<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>   <span class="co1"><em><span style="COLOR: #666666">//加上SMP写内存屏障</span></em></span>
	sl<span class="sy0"><span style="COLOR: #339933">-></span></span>sequence<span class="sy0"><span style="COLOR: #339933">++;</span></span>   <span class="co1"><em><span style="COLOR: #666666">//顺序号加1</span></em></span>
	spin_unlock<span class="br0"><span style="COLOR: #099000">(</span></span><span class="sy0"><span style="COLOR: #339933">&</span></span>sl<span class="sy0"><span style="COLOR: #339933">-></span></span>lock<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>  <span class="co1"><em><span style="COLOR: #666666">//解开自旋锁</span></em></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

(5)读操作开始时读顺序号函数read_seqbegin

函数read_seqbegin读取顺序号,如果顺序号为奇数,说明写者正在写操作,处理器执行空操作,进行循环等待,否则,函数返回读取的顺序号值。其列出如下:

<span class="kw4"><span style="COLOR: #993333">static</span></span> __always_inline <span class="kw4"><span style="COLOR: #993333">unsigned</span></span> read_seqbegin<span class="br0"><span style="COLOR: #099000">(</span></span><span class="kw4"><span style="COLOR: #993333">const</span></span> seqlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>sl<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	<span class="kw4"><span style="COLOR: #993333">unsigned</span></span> ret<span class="sy0"><span style="COLOR: #339933">;</span></span>
 
repeat<span class="sy0"><span style="COLOR: #339933">:</span></span>
	ret <span class="sy0"><span style="COLOR: #339933">=</span></span> sl<span class="sy0"><span style="COLOR: #339933">-></span></span>sequence<span class="sy0"><span style="COLOR: #339933">;</span></span>
	smp_rmb<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>      <span class="co1"><em><span style="COLOR: #666666">//加上SMP读内存屏障</span></em></span>
	<span class="kw1"><span style="COLOR: #b1b100">if</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>unlikely<span class="br0"><span style="COLOR: #099000">(</span></span>ret <span class="sy0"><span style="COLOR: #339933">&</span></span> <span class="nu0"><span style="COLOR: #00dd00">1</span></span><span style="COLOR: #099000"><span class="br0">)</span><span class="br0">)</span></span> <span class="br0"><span style="COLOR: #099000">{</span></span> <span class="co1"><em><span style="COLOR: #666666">//如果ret & 1为true,表示顺序号为奇数,写者正在写操作</span></em></span>
		cpu_relax<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>       <span class="co1"><em><span style="COLOR: #666666">//空操作</span></em></span>
		<span class="kw1"><span style="COLOR: #b1b100">goto</span></span> repeat<span class="sy0"><span style="COLOR: #339933">;</span></span> 
	<span class="br0"><span style="COLOR: #099000">}</span></span>
 
	<span class="kw1"><span style="COLOR: #b1b100">return</span></span> ret<span class="sy0"><span style="COLOR: #339933">;</span></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>

(6)读操作完成时顺序号检查函数read_seqretry

函数read_seqretry用于读操作完成后检测读的数据是否有效。如果读操作完成后的顺序号与读操作开始前的顺序号不一致,函数返回1,说明有写者更改了临界区数据,因此,调用者必须重新读临界者数据。

函数read_seqretry列出如下:

<span class="kw4"><span style="COLOR: #993333">static</span></span> __always_inline <span class="kw4"><span style="COLOR: #993333">int</span></span> read_seqretry<span class="br0"><span style="COLOR: #099000">(</span></span><span class="kw4"><span style="COLOR: #993333">const</span></span> seqlock_t <span class="sy0"><span style="COLOR: #339933">*</span></span>sl<span class="sy0"><span style="COLOR: #339933">,</span></span> <span class="kw4"><span style="COLOR: #993333">unsigned</span></span> start<span class="br0"><span style="COLOR: #099000">)</span></span>
<span class="br0"><span style="COLOR: #099000">{</span></span>
	smp_rmb<span style="COLOR: #099000"><span class="br0">(</span><span class="br0">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span><span class="co1"><em><span style="COLOR: #666666">//加上SMP读内存屏障</span></em></span>
 
	<span class="kw1"><span style="COLOR: #b1b100">return</span></span> <span class="br0"><span style="COLOR: #099000">(</span></span>sl<span class="sy0"><span style="COLOR: #339933">-></span></span>sequence <span class="sy0"><span style="COLOR: #339933">!=</span></span> start<span class="br0"><span style="COLOR: #099000">)</span></span><span class="sy0"><span style="COLOR: #339933">;</span></span>  <span class="co1"><em><span style="COLOR: #666666">//顺序锁的顺序号值与读操作开始时的顺序号值start不一致</span></em></span>
<span class="br0"><span style="COLOR: #099000">}</span></span>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值