Linux内核同步方法——读写锁

读 - 写自旋锁

    一个或多个任务可以并发地持有读者锁;相反,用于写的锁最多只能被一个写任务持有,而且此时不能有并发地读操作。

   读/写锁也叫做共享/排斥锁,或者并发/排斥锁,因为这种锁对读者而言是共享地,对写者以排斥形式获取地。


基本数据结构

    在内核代码中,读-写自旋锁用rwlock_t类型表示,

typedef struct {
	/**
	 * 这个锁标志与自旋锁不一样,自旋锁的lock标志只能取0和1两种值。
	 * 读写自旋锁的lock分两部分:
	 *     0-23位:表示并发读的数量。数据以补码的形式存放。
	 *     24位:未锁标志。如果没有读或写时设置该,否则清0
	 * 注意:如果自旋锁为空(设置了未锁标志并且无读者),则lock字段为0x01000000
	 *     如果写者获得了锁,则lock为0x00000000(未锁标志清0,表示已经锁,但是无读者)
	 *     如果一个或者多个进程获得了读锁,那么lock的值为0x00ffffff,0x00fffffe等(未锁标志清0,后面跟读者数量的补码)
	 */
	volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned magic;
#endif
#ifdef CONFIG_PREEMPT
	/**
	 * 表示进程正在忙等待自旋锁。
	 * 只有内核支持SMP和内核抢占时才使用本标志。
	 */
	unsigned int break_lock;
#endif
} rwlock_t;

rwlock_t中的锁标志与自旋锁不同,

    (注:如果自旋锁为空(设置了未锁标志并且无读者),则锁字节位0x0100 0000)(自锁锁的锁标志只能取0和1两种值。

读写自旋锁的锁分为两部分:

    · 0-23位:表示并发读的数量;

    ·第24位:未锁标志如果没有读或写时会设置,否则清0。

如果写者获得了锁,则锁为0x0000 0000(未锁标志清0,表示已经锁,但无读者);如果一个或多个进程获得了读锁,那么锁的值为0x00ff ffff,0x00ff fffe锁标志清0)。



初始化

    rwlock_init(),初始化指定的rwlock_t。

#define rwlock_init(x)	do { *(x) = RW_LOCK_UNLOCKED; } while(0)

read_lock

    获得指定的读锁。

    在没有配置内核抢占时,read_lock的实现如下,

/**
 * 在没有配置内核抢占时,read_lock的实现。
 */
void __lockfunc _read_lock(rwlock_t *lock)
{
	preempt_disable();
	_raw_read_lock(lock);
}
EXPORT_SYMBOL(_read_lock);

    接下来,read_lock调用_raw_read_lock(),其中第一个参数为读写锁指针,第二个为获取读锁失败时的处理函数的函数指针。

/**
 * 在没有配置内核抢占时,read_lock调用它。
 */
static inline void _raw_read_lock(rwlock_t *rw)
{
#ifdef CONFIG_DEBUG_SPINLOCK
	BUG_ON(rw->magic != RWLOCK_MAGIC);
#endif
	__build_read_lock(rw, "__read_lock_failed");
}

__build_read_lock

    函数raw_read_lock调用宏函数__build_read_lock,

这里的__builtin_constant_p()是编译器的GCC的内置函数,用于判断一个值是否为编译时常量,如果是,函数返回1,否则返回0。

#define __build_read_lock(rw, helper)	do { \
						if (__builtin_constant_p(rw)) \
							__build_read_lock_const(rw, helper); \
						else \
							__build_read_lock_ptr(rw, helper); \
					} while (0)

__build_read_lock_ptr和__build_read_lock_const

    在没有内核抢占时,助手为__read_lock_failed。

/**
 * 在没有内核抢占时,read_lock会调到这里来。
 * 在那种情况下,helper为__read_lock_failed
 */
#define __build_read_lock_ptr(rw, helper)   \
	/**
	 * 将lock减1,变相是将读者数加1
	 */
	asm volatile(LOCK "subl $1,(%0)\n\t" \
			 /**
			  * 如果减1后,lock值>=0。就说明此时未锁,或者只有读者,申请读锁成功。
			  */
		     "jns 1f\n" \
		     /**
		      * 此时有写者,申请不成功,转到__read_lock_failed
		      */
		     "call " helper "\n\t" \
		     "1:\n" \
		     ::"a" (rw) : "memory")

#define __build_read_lock_const(rw, helper)   \
	asm volatile(LOCK "subl $1,%0\n\t" \
		     "jns 1f\n" \
		     "pushl %%eax\n\t" \
		     "leal %0,%%eax\n\t" \
		     "call " helper "\n\t" \
		     "popl %%eax\n\t" \
		     "1:\n" \
		     :"=m" (*(volatile int *)rw) : : "memory")


read_unlock()

    释放指定的读锁。

void __lockfunc _read_unlock(rwlock_t *lock)
{
	_raw_read_unlock(lock);
	preempt_enable();
}
EXPORT_SYMBOL(_read_unlock);
#define _raw_read_unlock(lock)	do { (void)(lock); } while(0)


write_lock()

    获得指定的写锁。

void __lockfunc _write_lock(rwlock_t *lock)
{
	preempt_disable();
	_raw_write_lock(lock);
}
static inline void _raw_write_lock(rwlock_t *rw)
{
#ifdef CONFIG_DEBUG_SPINLOCK
	BUG_ON(rw->magic != RWLOCK_MAGIC);
#endif
	__build_write_lock(rw, "__write_lock_failed");
}

__build_write_lock()源代码

    和__build_read_lock()代码类似,

#define __build_write_lock(rw, helper)	do { \
						if (__builtin_constant_p(rw)) \
							__build_write_lock_const(rw, helper); \
						else \
							__build_write_lock_ptr(rw, helper); \
					} while (0)

__build_write_lock_ptr()和__build_write_lock_const

#define __build_write_lock_ptr(rw, helper) \
	//锁总线,将rw减RW_LOCK_BIAS_STR,即rw减0x01000000,判断结果是否为0,若为0则获取写锁成功并返回
	asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",(%0)\n\t" \
		     "jz 1f\n" \
		      //若结果不为0则获取写锁失败,调用失败处理函数helper
		     "call " helper "\n\t" \
		     "1:\n" \
		     ::"a" (rw) : "memory")

#define __build_write_lock_const(rw, helper) \
	asm volatile(LOCK "subl $" RW_LOCK_BIAS_STR ",%0\n\t" \
		     "jz 1f\n" \
		     "pushl %%eax\n\t" \
		     "leal %0,%%eax\n\t" \
		     "call " helper "\n\t" \
		     "popl %%eax\n\t" \
		     "1:\n" \
		     :"=m" (*(volatile int *)rw) : : "memory")


write_unlock()

    释放指定的写锁。

    在获得锁时是禁止抢占的,此时要把抢占打开。顺序与锁定相反。


void __lockfunc _write_unlock(rwlock_t *lock)
{
	/**
	 * 调用汇编lock ; addl $0x01000000, rwlp把字段中的未锁标志置位。
	 */
	_raw_write_unlock(lock);
	/**
	 * 当然了,在获得锁时是禁用抢占的,此时要把抢占打开。
	 * 另外,注意它的顺序,是与lock时相反。
	 */
	preempt_enable();
}
EXPORT_SYMBOL(_write_unlock);

调用汇编lock;加$ 0x01000000,把rw字段中的未锁标志置位。

#define _raw_write_unlock(rw)	asm volatile("lock ; addl $" RW_LOCK_BIAS_STR ",%0":"=m" ((rw)->lock) : : "memory")

read_unlock()

    释放指定的读锁。
__build_write_lock_ptr()和__build_write_lock_const

内核中的读 - 写自旋锁具体应用的类型

获取读写锁的操作

    read_lock_irqsave()存储本地中断的当前状态,禁止本地中断并获得指定读锁。

unsigned long __lockfunc _read_lock_irqsave(rwlock_t *lock)
{
	unsigned long flags;

	local_irq_save(flags);
	preempt_disable();
	_raw_read_lock(lock);
	return flags;
}
EXPORT_SYMBOL(_read_lock_irqsave);

   read_lock_irq()禁止本地中断并获得指定读锁。

void __lockfunc _read_lock_irq(rwlock_t *lock)
{
	local_irq_disable();
	preempt_disable();
	_raw_read_lock(lock);
}
EXPORT_SYMBOL(_read_lock_irq);

      __write_lock_irqsave()存储本地中断的当前状态,禁止本地中断并获得指定写锁。

unsigned long __lockfunc _write_lock_irqsave(rwlock_t *lock)
{
	unsigned long flags;

	local_irq_save(flags);
	preempt_disable();
	_raw_write_lock(lock);
	return flags;
}
EXPORT_SYMBOL(_write_lock_irqsave);

    _write_lock_irq()禁止本地中断并获得指定写锁。

void __lockfunc _write_lock_irq(rwlock_t *lock)
{
	local_irq_disable();
	preempt_disable();
	_raw_write_lock(lock);
}
EXPORT_SYMBOL(_write_lock_irq);


释放读写锁的操作

    _read_unlock_irqrestore()释放指定的读锁并将本地中断恢复到指定的前状态(标志)。
void __lockfunc _spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
{
	_raw_spin_unlock(lock);
	local_irq_restore(flags);
	preempt_enable();
}
    _read_unlock_irq()释放指定的读锁并激活本地中断。
void __lockfunc _read_unlock_irq(rwlock_t *lock)
{
	_raw_read_unlock(lock);
	local_irq_enable();
	preempt_enable();
}

read_unlock()

    释放指定的读锁。
__build_write_lock_ptr()和__build_write_lock_const
    _write_unlock_irqrestore()和_read_unlock_irqrestore()函数功能类似,释放指定的写锁并将本地中断恢复到指定的前状态。
void __lockfunc _read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
{
	_raw_read_unlock(lock);
	local_irq_restore(flags);
	preempt_enable();
}
    _write_unlock_irq()释放指定的写锁,并激活本地中断。


void __lockfunc _write_unlock_irq(rwlock_t *lock)
{
	_raw_write_unlock(lock);
	local_irq_enable();
	preempt_enable();
}


write_trylock()

    试图获得指定的写锁;如果写锁不可用,返回非0值。

int __lockfunc _write_trylock(rwlock_t *lock)
{
	preempt_disable();
	if (_raw_write_trylock(lock))
		return 1;

	preempt_enable();
	return 0;
}
#define _raw_write_trylock(lock) ({ (void)(lock); (1); })



总结:

(1)或一个多个读任务可以并发地持有同一个伦敦报道读者锁 ;相反,用于写的锁最多只能被一个写任务持有

(2)一个线程递归地获得同一读锁也是安全的。

(3)如果在中断处理程序中只有读操作而没有写操作,那么就可以混合使用“中断禁止”锁,即使用read_lock()而不是read_lock_irqsave()对锁进行保护。

(4)读写锁对于读更好一些。当读锁被持有时,写操作是为了互斥访问只能等待,但读者却可以继续成功地占用锁。而自旋锁等待地写者在所有读者释放锁之前是无法获得锁的。所以大量的读者必定会使挂起写者处于饥饿状态,消耗处理器资源。

(5)如果加锁时间不长御姐代码不会睡眠(比如中断处理程序),利用自旋锁是最佳选择;

        如果加锁时间可能很长或代码在持有锁时有可能睡眠,那么适合使用信号量。




补充:

禁止抢占

    。内核中的抢占代码使用自旋锁作为非抢占区域的标记如果一个自旋锁被持有,内核便不能进行抢占。但是,自旋锁对于单处理器(或者是数据对每个处理器是唯一的)的情况,是不需要锁保护的。

    由于内核是抢占性地,内核中的进程在任何时刻都肯停下来以便另一个具有更高优先级的进程运行。这意味着一个任务与被强占的任务可能会在同一个临界区内运行。

preempt_disable()

    增加抢占计数值,从而禁止内核抢占。这是一个嵌套调用函数。每次调用都需要有一个对应的preempt_enable()调用。

preemp_enable()

    减少抢占计数,并当该值降为0时检查和执行挂起的需调度的任务。也就是说,当是什么意思?求最后一次preempt_enable()被调用后,内核抢占才重新启用。

preempt_count()

    返回抢占计数。

    抢占计数是被持有锁的数量和preempt_disable()的调用次数。如果计数是0,那么内核可以抢占;如果为1或更大的数,内核就不会抢占。这个计数是一种对原子操作和睡眠有效的调试方法。












read_unlock()

    释放指定的读锁。
__build_write_lock_ptr()和__build_write_lock_const


总结:

(1)或一个多个读任务可以并发地持有同一个伦敦报道读者锁 ;相反,用于写的锁最多只能被一个写任务持有

(2)一个线程递归地获得同一读锁也是安全的。

(3)如果在中断处理程序中只有读操作而没有写操作,那么就可以混合使用“中断禁止”锁,即使用read_lock()而不是read_lock_irqsave()对锁进行保护。

(4)读写锁对于读更好一些。当读锁被持有时,写操作是为了互斥访问只能等待,但读者却可以继续成功地占用锁。而自旋锁等待地写者在所有读者释放锁之前是无法获得锁的。所以大量的读者必定会使挂起写者处于饥饿状态,消耗处理器资源。

(5)如果加锁时间不长御姐代码不会睡眠(比如中断处理程序),利用自旋锁是最佳选择;

        如果加锁时间可能很长或代码在持有锁时有可能睡眠,那么适合使用信号量。




补充:

禁止抢占

    。内核中的抢占代码使用自旋锁作为非抢占区域的标记如果一个自旋锁被持有,内核便不能进行抢占。但是,自旋锁对于单处理器(或者是数据对每个处理器是唯一的)的情况,是不需要锁保护的。

    由于内核是抢占性地,内核中的进程在任何时刻都肯停下来以便另一个具有更高优先级的进程运行。这意味着一个任务与被强占的任务可能会在同一个临界区内运行。

preempt_disable()

    增加抢占计数值,从而禁止内核抢占。这是一个嵌套调用函数。每次调用都需要有一个对应的preempt_enable()调用。

preemp_enable()

    减少抢占计数,并当该值降为0时检查和执行挂起的需调度的任务。也就是说,当是什么意思?求最后一次preempt_enable()被调用后,内核抢占才重新启用。

preempt_count()

    返回抢占计数。

    抢占计数是被持有锁的数量和preempt_disable()的调用次数。如果计数是0,那么内核可以抢占;如果为1或更大的数,内核就不会抢占。这个计数是一种对原子操作和睡眠有效的调试方法。


你可能感兴趣的:(Linux)