找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

搜索
查看: 4339|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

Android 深度探索(卷1)-學(xué)習(xí)筆記2(讀寫、順序鎖、信號(hào)量)

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:82083 發(fā)表于 2015-6-6 02:50 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
一、讀寫鎖
使用自旋鎖時(shí),無論什么時(shí)候都只有一個(gè)可執(zhí)行單元進(jìn)入臨界區(qū),無論該執(zhí)行單元是讀操作還是寫操作,大多數(shù)情況下我們讀操作會(huì)多于寫操作,
多個(gè)執(zhí)行單元進(jìn)入臨界區(qū)進(jìn)行讀操作是不會(huì)有問題的,如果仍采用自旋鎖就會(huì)有效率低下的問題,而采用讀寫鎖的話就會(huì)大大的提升效率。


讀寫鎖,從自旋鎖中衍生而出,分讀自旋鎖和寫自旋鎖。
1、多個(gè)執(zhí)行單元可以同時(shí)獲取到讀鎖并訪問臨界區(qū)的資源(或代碼),但只能獲取一個(gè)寫自旋鎖。
2、如果某些執(zhí)行單元已經(jīng)獲取到讀鎖仍未釋放該鎖,這時(shí)B執(zhí)行單元去獲取寫鎖,B執(zhí)行單元就會(huì)阻塞,直到所有讀鎖被釋放。
3、如果有個(gè)A執(zhí)行單元獲取到寫鎖仍未釋放該鎖,這時(shí)B執(zhí)行單元去獲取寫鎖,B執(zhí)行單元就會(huì)阻塞,直到A寫鎖被釋放。
4、如果有個(gè)A執(zhí)行單元獲取到寫鎖仍未釋放該鎖,這時(shí)B執(zhí)行單元去獲取讀鎖,B執(zhí)行單元就會(huì)阻塞,直到A寫鎖釋放。


無論是讀鎖還是寫鎖,都會(huì)對(duì)臨界區(qū)加鎖:
讀鎖:讀鎖不會(huì)互斥讀鎖,但會(huì)互斥寫鎖,也就是說可以重復(fù)獲取讀鎖(讀鎖上鎖期間不應(yīng)修改共享資源)
寫鎖:寫鎖會(huì)互斥寫鎖和讀鎖,也就是說在上了寫鎖期間,其他執(zhí)行單元無法獲取到寫鎖和讀鎖
(讀鎖上鎖期間不允許修改共享資源,寫鎖上鎖期間只允許一個(gè)執(zhí)行單元修改共享資源。)



使用示例:
// 定義并初始化讀寫自旋鎖
static DEFINE_RWLOCK(rwlock);


static ssize_t demo_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
// ......

read_lock(&rwlock);// 加了讀鎖,為了互斥寫鎖
// ..
// 讀臨界區(qū)這里可以并發(fā)進(jìn)入臨界區(qū)不會(huì)阻塞,提高效率
// ..
read_unlock(&rwlock);

// ......
}


static ssize_t demo_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
// .......

write_lock(&rwlock); //加了寫鎖,為了互斥讀鎖和寫鎖
// ..
// 修改臨界區(qū) 這里只允許有一個(gè)執(zhí)行單元在所有讀鎖釋放后進(jìn)入
// ..
write_unlock(&rwlock);

// .......
}


優(yōu)點(diǎn):在沒有寫操作時(shí)可以并發(fā)讀操作,不會(huì)被阻塞。
缺點(diǎn):當(dāng)執(zhí)行寫操作時(shí),所有讀操作都會(huì)被阻塞。


讀寫鎖僅適合在讀多寫少并且臨界區(qū)很短的情況下。


一些接口:
DEFINE_RWLOCK(lock)
void rwlock_init(rwlock_t *lock)


int read_trylock(rwlock_t *lock)
void read_lock(rwlock_t *lock)
void read_unlock(rwlock_t *lock)
void read_lock_irq(rwlock_t *lock)
void read_unlock_irq(rwlock_t *lock)
void read_lock_bh(rwlock_t *lock)
void read_unlock_bh(rwlock_t *lock)


int write_trylock(rwlock_t *lock)
void write_lock(rwlock_t *lock)
void write_unlock(rwlock_t *lock)
void write_lock_irq(rwlock_t *lock)
void write_unlock_irq(rwlock_t *lock)
void write_lock_bh(rwlock_t *lock)
void write_unlock_bh(rwlock_t *lock)




二 順序鎖
順序鎖則是自旋鎖的另外一個(gè)升級(jí)版,和讀寫鎖有些相似。順序鎖主要是圍繞順序鎖和順序號(hào)來設(shè)計(jì)的。


typedef struct{
unsigned sequence;// 順序鎖的順序計(jì)數(shù)器-順序號(hào)
spinlock_t lock;// 自旋鎖
}seqlock_t;


順序鎖:順序鎖未被釋放時(shí),獲取順序鎖的執(zhí)行單元會(huì)阻塞(自旋)
在需要修改共享資源(臨界區(qū))的時(shí)候獲取順序鎖,成功獲取順序號(hào)+1
當(dāng)完成共享資源的操作后釋放順序鎖,順序號(hào)+1


順序號(hào):順序鎖未被釋放時(shí),獲取順序號(hào)的執(zhí)行單元會(huì)阻塞(自旋)
在讀取共享資源時(shí)需要先取得順序號(hào),只有在順序鎖釋放的情況下才會(huì)得到順序號(hào),該順序號(hào)一定是偶數(shù)。
完成共享資源的讀取后再次取得順序號(hào)并對(duì)比之前獲取的順序號(hào),若不一致則需要重新讀取共享資源。

成功獲取順序鎖時(shí)順序號(hào)一定是奇數(shù),釋放順序鎖時(shí)一定是偶數(shù)。之所以有這種規(guī)律跟順序鎖實(shí)現(xiàn)有關(guān)。


// 實(shí)現(xiàn)源碼,獲取順序號(hào)的過程中并沒有上鎖操作
static _always_inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret;
repeat:
ret = sl->sequence; // 讀取順序號(hào)
smp_rmb(); // 讀內(nèi)存屏障

// 如果順序號(hào)是奇數(shù)則循環(huán)檢測(cè)(自旋)否則返回順序號(hào)
if(unlikely(ret & 1))
{
cpu_relax();
goto repeat;
}
return ret;
}



使用示例:
// 定義并初始化讀寫自旋鎖
static DEFINE_SEQLOCK(seqlock);


static ssize_t demo_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
// ......
// -------------------- 關(guān)鍵代碼 ----------------------------
unsigned seq;
do
{// 獲取順序號(hào),如果順序鎖未被釋放 將會(huì)被阻塞(自旋)
seq = read_seqbegin(&seqlock); // 成功返回的順序號(hào)一定是偶數(shù)
// ...
// 臨界區(qū)
// ...
//如果當(dāng)前順序號(hào)和seq一致,則退出循環(huán),否則重新讀取共享資源
}while(read_seqretry(&seqlock, seq));
// -------------------- 關(guān)鍵代碼 ----------------------------
// ......
}


static ssize_t demo_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
// .......
// 獲取順序鎖  seqlock.sequence 會(huì)被+1  這時(shí) seqlock.sequence 會(huì)是奇數(shù)
write_seqlock(&seqlock);
// ..
// 修改臨界區(qū) 這里只允許有一個(gè)執(zhí)行單元在所有讀鎖釋放后進(jìn)入
// ..
// 釋放順序鎖 seqlock.sequence 會(huì)被+1 這時(shí) seqlock.sequence 會(huì)是偶數(shù)
write_sequnlock(&seqlock);

// .......
}


順序鎖的讀臨界區(qū)操作并沒有鎖定臨界區(qū),只是簡(jiǎn)單讀取臨界區(qū)中的共享資源,所以在讀取共享數(shù)據(jù)時(shí)
是可以修改共享數(shù)據(jù)的。


優(yōu)點(diǎn):讀取到的數(shù)據(jù)一定是最新的數(shù)據(jù),在沒有寫操作時(shí)讀操作不會(huì)阻塞并且可以并發(fā)讀。
缺點(diǎn):如果在讀共享數(shù)據(jù)的過程中發(fā)生了寫操作,就會(huì)使得系統(tǒng)不斷的地循環(huán)等待(自旋)和重新執(zhí)行讀臨界區(qū)數(shù)據(jù)。


順序鎖僅適合在讀多寫少、臨界區(qū)很短并要求數(shù)據(jù)實(shí)時(shí)更新情況下。


一些接口:
DEFINE_SEQLOCK(lock)
void seqlock_init(seqlock_t *lock)
// 以下接口在成功執(zhí)行后順序號(hào)會(huì)+1
int write_tryseqlock(seqlock_t *lock)
void write_seqlock(seqlock_t *lock)
void write_seqlock_irqsave(seqlock_t *lock)
void write_seqlock_irq(lock)
void write_seqlock_bh(lock)
void wrtie_sequnlock(seqlock_t *lock)
void write_sequnlock_irqrestore(lock, flags)
void write_sequnlock_irq(lock)
void write_sequnlock_bh(lock)
// 以下接口不會(huì)修改順序號(hào)
unsigned read_seqbegin(const seqlock_t *lock)
void read_seqbegin_irqsave(lock, flags)
int read_seqretry(const seqlock_t *lock, unsigned iv)
void read_seqretry_irqrestore(lock, iv, flags)


三、信號(hào)量


信號(hào)量和鎖機(jī)制最大的區(qū)別就是實(shí)現(xiàn)阻塞的方式不一樣。
自旋鎖、讀寫鎖、順序鎖都是通過不斷循環(huán)檢測(cè)實(shí)現(xiàn)阻塞,當(dāng)臨界區(qū)很短時(shí)效率很高,
當(dāng)臨界區(qū)很長(zhǎng)的時(shí)候性能就會(huì)急劇下降。而信號(hào)量則是通過休眠方式實(shí)現(xiàn)阻塞,適合
臨界區(qū)比較長(zhǎng)的情況,休眠不會(huì)占用CPU資源,所以不會(huì)影響系統(tǒng)性能更不會(huì)出現(xiàn)死機(jī)現(xiàn)象。


信號(hào)量比鎖機(jī)制要靈活很多,它可以指定可以有幾個(gè)執(zhí)行單元進(jìn)入臨界區(qū)。


1、信號(hào)量數(shù)據(jù)結(jié)構(gòu)

struct semaphore sem;


struct semaphore {
raw_spinlock_tlock;// 鎖
// 資源數(shù) 決定可以有幾個(gè)執(zhí)行單元進(jìn)入臨界區(qū)
// >0,資源空閑. ==0,資源忙  
// <0 資源不可用,并至少有一個(gè)進(jìn)程等待資源
unsigned intcount;
struct list_headwait_list;// 鏈表
};


2、初始化

sema_init(&sem, 1);// 初始化信號(hào)量并指定 sem.count 的初始值(即資源數(shù))


#define __SEMAPHORE_INITIALIZER(name, n)\
{\
.lock= __RAW_SPIN_LOCK_UNLOCKED((name).lock),\
.count= n,\  // <--- 這里設(shè)置了可用資源數(shù)目
.wait_list= LIST_HEAD_INIT((name).wait_list),\
}


#define DEFINE_SEMAPHORE(name)\
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)


static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}


3、獲取信號(hào)量

中斷無法喚醒:
down(&sem); // 獲取信號(hào)量,若計(jì)數(shù)器的值小于或等于0則進(jìn)入休眠,若大于0則遞減。
void down(struct semaphore *sem)
{
unsigned long flags;
// 上鎖
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;// 計(jì)數(shù)器大于 0 表示資源可用則將資源數(shù)自減
else
__down(sem);// 計(jì)數(shù)器小于或等于0 表示資源忙則進(jìn)入休眠狀態(tài)
// 解鎖
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(down);

static noinline void __sched __down(struct semaphore *sem)
{
__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}

中斷可喚醒:返回非0值表示是由中斷喚醒,返回0值則表示成功獲取到信號(hào)量
int down_interruptible(struct semaphore *sem)
{
unsigned long flags;
int result = 0;
// 上鎖
raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(sem->count > 0))
sem->count--;
else
result = __down_interruptible(sem);// 這里不一樣
// 解鎖
raw_spin_unlock_irqrestore(&sem->lock, flags);


return result;
}
EXPORT_SYMBOL(down_interruptible);


static noinline int __sched __down_interruptible(struct semaphore *sem)
{// 只是傳遞的參數(shù)不一樣 TASK_INTERRUPTIBLE
// MAX_SCHEDULE_TIMEOUT 表示永不超時(shí)
return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}


static inline int __sched __down_common(struct semaphore *sem, long state, long timeout)
{
struct task_struct *task = current;
struct semaphore_waiter waiter;
// 將當(dāng)前進(jìn)程加入到等待鏈表
list_add_tail(&waiter.list, &sem->wait_list);
waiter.task = task;
waiter.up = 0;// 將當(dāng)前進(jìn)程標(biāo)記為休眠


for (;;) {
// 這里是為 down_interruptible() 準(zhǔn)備
if (signal_pending_state(state, task))
goto interrupted;

// 這里是為 down_timeout() 準(zhǔn)備
// 若超時(shí)被喚醒則跳出
if (timeout <= 0)
goto timed_out;

// 設(shè)置進(jìn)程狀態(tài)
__set_task_state(task, state);

// 解鎖
raw_spin_unlock_irq(&sem->lock);
// 讓出CPU,此時(shí)執(zhí)行到這里停止往下執(zhí)行
timeout = schedule_timeout(timeout);
// 進(jìn)程被喚醒時(shí) 再上鎖
raw_spin_lock_irq(&sem->lock);
// 判斷是否由  __up() 喚醒,如果是則表示資源可用返回0。
if (waiter.up)
return 0;
}


// 超時(shí)喚醒 將該進(jìn)程從等待鏈表中刪除。返回非0值
timed_out:
list_del(&waiter.list);
return -ETIME;
// 中斷喚醒 將該進(jìn)程從等待鏈表中刪除。返回非0值
interrupted:
list_del(&waiter.list);
return -EINTR;
}


4、釋放信號(hào)量

up(&sem); // 釋放信號(hào)量 若有進(jìn)程在等待則喚醒,直到?jīng)]有進(jìn)程再等待才將計(jì)數(shù)器自增


static noinline void __sched __up(struct semaphore *sem)
{
// 獲取等待鏈表中第一個(gè)進(jìn)程
struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list, struct semaphore_waiter, list);
list_del(&waiter->list);    // 從等待鏈表中移除該進(jìn)程
waiter->up = 1;    // 喚醒標(biāo)志
wake_up_process(waiter->task);    // 喚醒該進(jìn)程
}


void up(struct semaphore *sem)
{
unsigned long flags;


raw_spin_lock_irqsave(&sem->lock, flags);
if (likely(list_empty(&sem->wait_list)))
sem->count++;        // 如果沒有等待鏈表中沒有進(jìn)程在等待 才自增 表示資源空閑
else
__up(sem);// 如果有進(jìn)程等待則喚醒
raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(up);


使用示例:
struct semaphore sem;
static ssize_t demo_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
// ......
// 獲取信號(hào)量
down(&sem);
// ...
// 臨界區(qū)
// ...
// 釋放信號(hào)量
up(&sem);
// ......
}


static ssize_t demo_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
// ......
// 獲取信號(hào)量
down(&sem);
// ...
// 臨界區(qū)
// ...
// 釋放信號(hào)量
up(&sem);
// ......
}


static int __init demo_init(void)
{
// 將信號(hào)量值初始化為 1 表示只允許一個(gè)執(zhí)行單元進(jìn)入臨界區(qū)
sema_init(&sem, 1);
}


接口:略


四、讀寫信號(hào)量


讀寫信號(hào)量與信號(hào)量之間的關(guān)系和讀寫自旋鎖與自旋鎖一樣。
讀寫信號(hào)量可以有多個(gè)執(zhí)行單元獲得讀信號(hào)量從而并發(fā)讀操作,但只能有一個(gè)執(zhí)行單元獲取到寫信號(hào)量。


使用示例:


struct rw_semaphore rw_sem;
static ssize_t demo_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
// ......
// 獲取讀信號(hào)量 如果有寫信號(hào)量未釋放則進(jìn)入休眠
down_read(&rw_sem);
// ...
// 臨界區(qū)
// ...
// 釋放讀信號(hào)量
up_read(&rw_sem);
// ......
}


static ssize_t demo_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
// ......
// 獲取寫信號(hào)量 如果有寫信號(hào)量或所有讀信號(hào)量未全部釋放則進(jìn)入休眠
down_write(&rw_sem);
// ...
// 臨界區(qū)
// ...
// 釋放寫信號(hào)量
up_write(&rw_sem);
// ......
}


static int __init demo_init(void)
{
// 將信號(hào)量值初始化為 1 表示只允許一個(gè)執(zhí)行單元進(jìn)入臨界區(qū)
init_rwsem(&rw_sem, 1);
}

接口:略


鎖機(jī)制適合在臨界區(qū)執(zhí)行時(shí)間短的情況下。
信號(hào)量適合在臨界區(qū)執(zhí)行時(shí)間較長(zhǎng)的情況下。
鎖機(jī)制是采用不斷循環(huán)檢測(cè),所以實(shí)時(shí)性、速度快。
信號(hào)量是采用休眠喚醒的方式,休眠喚醒需要時(shí)間,實(shí)時(shí)性和速度較慢。

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表