1. 程式人生 > >Linux核心同步方法——讀寫鎖

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或更大的數,核心就不會搶佔。這個計數是一種對原子操作和睡眠有效的除錯方法。