Linux內(nèi)核同步機(jī)制,挺復(fù)雜的一個(gè)東西,常用的有自旋鎖,信號(hào)量,互斥體,原子操作,順序鎖,RCU,內(nèi)存屏障等。這里就說(shuō)說(shuō)它們的特點(diǎn)和基本用法。
自旋鎖 :通用的 和讀寫的
特點(diǎn):
1. 處理的時(shí)間很短。
2. 嘗試獲取鎖時(shí),不能睡眠,但是有trylock接口可以直接退出。
3. 多用在中斷中。
4. 任何時(shí)候只有一個(gè)保持者能夠訪問(wèn)臨界區(qū)。
5. 可以被中斷打斷的(硬件和軟件的)
6. 獲取自旋鎖后首先就是關(guān)閉了搶占
spin_lock使用接口:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 void spin_lock_init(spinlock_t *lock); //init
void spin_lock(spinlock_t *lock); // 獲取鎖
void spin_unlock(spinlock_t *lock); //釋放鎖
其他變體
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
Rwlock: 讀寫自旋鎖基本特點(diǎn)和通用自旋鎖一樣,但是有時(shí)候多線程頻繁讀取臨界區(qū)如果同時(shí)只能一個(gè)那么效率會(huì)很低,它的特點(diǎn)就是在讀的時(shí)候獲取讀鎖,可以同時(shí)有N個(gè)線程同時(shí)讀,在寫時(shí)需要獲得寫鎖(不能有讀和寫鎖)。
在讀操作時(shí),寫操作必須等待;寫操作時(shí),讀操作也需要的等待。這樣雖然避免了數(shù)據(jù)的不一致,但是某些操作要等待,后面還會(huì)出現(xiàn)順序鎖,是對(duì)讀寫鎖的優(yōu)化,把寫的優(yōu)先級(jí)調(diào)高了
使用接口:
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26rwlock_init(lock); //init
read_lock(lock); //獲取讀鎖
read_unlock(lock) ;
write_lock (lock); //獲取寫鎖
write_unlock(lock);
/*
* include/linux/rwlock_types.h - generic rwlock type definitions
* and initializers
*
* portions Copyright 2005, Red Hat, Inc., Ingo Molnar
* Released under the General Public License (GPL)。
*/
typedef struct {
arch_rwlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} rwlock_t;
而關(guān)于自旋鎖的缺點(diǎn)?這里找到ibm一個(gè)文章
信號(hào)量(semaphore):通用的 和讀寫的
相對(duì)于自旋鎖,它最大的特點(diǎn)就是允許調(diào)用它的線程進(jìn)入睡眠
C
1
2
3
4
5
6/* Please don‘t access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
void sema_init(struct semaphore *sem, int val); // val值代表了同時(shí)多少個(gè)線程可以進(jìn)入臨界區(qū),一般為1 即作為互斥體使用;當(dāng)然》1 時(shí),并發(fā)操作同一資源會(huì)引發(fā)什么呢?
down_interruptible(struct semaphore *sem); // 獲取信號(hào)量 ,它是可以中斷的。
up(struct semaphore *sem); // 釋放信號(hào)量,一般配對(duì)使用,當(dāng)然也可以在別的線程里釋放它。
讀寫信號(hào)量:rwsem 它和讀寫自旋鎖類似 除了線程可以睡眠
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/* the rw-semaphore definition
* - if activity is 0 then there are no active readers or writers
* - if activity is +ve then that is the number of active readers
* - if activity is -1 then there is one active writer
* - if wait_list is not empty, then there are processes waiting for the semaphore
*/
struct rw_semaphore {
__s32 activity;
raw_spinlock_t wait_lock;
struct list_head wait_list;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
init_rwsem(sem) ; // 初始化
down_read(struct rw_semaphore *sem); // 獲取讀信號(hào)量
up_read(struct rw_semaphore *sem); //釋放讀信號(hào)量
down_write(struct rw_semaphore *sem); //獲取寫信號(hào)量
up_write(struct rw_semaphore *sem); // 釋放寫信號(hào)量
互斥體(mutex):和count=1的信號(hào)量幾乎沒(méi)有區(qū)別,當(dāng)然擁有互斥鎖的進(jìn)程總是盡可能的在短時(shí)間內(nèi)釋放
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
struct list_head wait_list;
#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)
struct task_struct *owner;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
const char *name;
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
mutex_init(mutex); // init 互斥鎖
mutex_lock(); //獲取互斥鎖,幾乎都能獲取
mutex_unlock(); //釋放互斥鎖
原子操作(atomic)(和架構(gòu)相關(guān),就是多條指令相當(dāng)于一條指令執(zhí)行,多用于計(jì)數(shù))
組要是在smp上有意義,防止多條指令被多cpu執(zhí)行。也是為了實(shí)現(xiàn)互斥。
順序鎖(sequence)
特點(diǎn):
和讀寫自旋鎖鎖類似,但是它的寫不會(huì)等待。寫的時(shí)候持有自旋鎖。首先讀者的代碼應(yīng)該盡可能短且寫者不能頻繁獲得鎖,其次被保護(hù)的數(shù)據(jù)結(jié)構(gòu)不包括被寫修改的指針或被讀間接引用的指針。當(dāng)要保護(hù)的資源很小很簡(jiǎn)單,會(huì)很頻繁被訪問(wèn)并且寫入操作很少發(fā)生且必須快速時(shí),就可以用seqlock。
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25Seqlock:
typedef struct {
unsigned sequence;
spinlock_t lock;
} seqlock_t;
seqlock_init(x) / DEFINE_SEQLOCK(x) // init
write_seqlock(seqlock_t *sl) ; // 獲取寫鎖
write_sequnlock(seqlock_t *sl);
read_seqbegin 和 read_seqretry 結(jié)合使用 //讀時(shí)如果有寫鎖,則循環(huán)等待直到鎖釋放。
應(yīng)用實(shí)例drivers/md/md.c
retry:
seq = read_seqbegin(&bb-》lock);
memset(bbp, 0xff, PAGE_SIZE);
for (i = 0 ; i 《 bb-》count ; i++) {
u64 internal_bb = p[i];
u64 store_bb = ((BB_OFFSET(internal_bb) 《《 10)
| BB_LEN(internal_bb));
bbp[i] = cpu_to_le64(store_bb);
}
bb-》changed = 0;
if (read_seqretry(&bb-》lock, seq))
goto retry;
RCU:read-copy-update
在linux提供的所有內(nèi)核互斥設(shè)施當(dāng)中屬于一種免鎖機(jī)制。Rcu無(wú)需考慮讀和寫的互斥問(wèn)題。
它實(shí)際上是rwlock的一種優(yōu)化。讀取者不必關(guān)心寫入者。所以RCU可以讓多個(gè)讀取者與寫入者同時(shí)工作。寫入者的操作比例在10%以上,需要考慮其他互斥方法。并且必須要以指針的方式來(lái)訪問(wèn)被保護(hù)資源。
Rcu_read_lock //僅僅是關(guān)閉搶占
Rcu_read_unlock //打開(kāi)搶占
Rcu_assign_pointer(ptr,new_ptr)
//等待隊(duì)列:它并不是一種互斥機(jī)制。它輔助comletion。
//它主要用來(lái)實(shí)現(xiàn)進(jìn)程的睡眠等待。
//操作接口:wait/ wake_up
C
1
2
3
4
5struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
完成接口(completion) :該機(jī)制被用來(lái)在多個(gè)執(zhí)行路徑間作同步使用,即協(xié)調(diào)多個(gè)執(zhí)行路徑的執(zhí)行順序。如果沒(méi)有完成體,則睡眠在wait_list上。這里usb 在提交urb時(shí)會(huì)用到。
如果驅(qū)動(dòng)程序要在執(zhí)行后面操作之前等待某個(gè)過(guò)程的完成,它可以調(diào)用wait_for_completion,以要完成的事件為參數(shù):
Completion機(jī)制是線程間通信的一種輕量級(jí)機(jī)制:允許一個(gè)線程告訴另一個(gè)線程工作已經(jīng)完成
C
1
2
3
4
5
6
7
8
9
10
11
12struct completion {
unsigned int done;
wait_queue_head_t wait;
};
接口:
DECLARE_COMPLETION(x) // 靜態(tài)定義completion
init_completion(struct completion *x); // 動(dòng)態(tài)init
INIT_COMPLETION(x); // 初始化一個(gè)已經(jīng)使用過(guò)的completion
Wait_for_completion(struct completion *x);
complete(struct completion *); //done +1,喚醒等待的一個(gè)。
Complete_all // 喚醒所有的,一般不會(huì)用。
內(nèi)存屏障
內(nèi)存屏障主要有:讀屏障、寫屏障、通用屏障、優(yōu)化屏障
內(nèi)存屏障主要解決了兩個(gè)問(wèn)題:?jiǎn)?a target="_blank">處理器下的亂序問(wèn)題和多處理器下的內(nèi)存同步問(wèn)題
編譯器優(yōu)化以保證程序上下文因果關(guān)系為前提。
以 讀屏障為例,它用于保證讀操作有序。屏障之前的讀操作一定會(huì)先于屏障之后的讀操作完成,寫操作不受影響,同屬于屏障的某一側(cè)的讀操作也不受影響。類似的, 寫屏障用于限制寫操作。而通用屏障則對(duì)讀寫操作都有作用。而優(yōu)化屏障則用于限制編譯器的指令重排,不區(qū)分讀寫。前三種屏障都隱含了優(yōu)化屏障的功能。比如:
tmp = ttt; *addr = 5; mb(); val = *data;
有了內(nèi)存屏障就了確保先設(shè)置地址端口,再讀數(shù)據(jù)端口。而至于設(shè)置地址端口與tmp的賦值孰先孰后,屏障則不做干預(yù)。有了內(nèi)存屏障,就可以在隱式因果關(guān)系的場(chǎng)景中,保證因果關(guān)系邏輯正確。
在Linux中,優(yōu)化屏障就是barrier()宏,它展開(kāi)為asm volatile(“”:::”memory”)
smp_rmb(); // 讀屏障
smp_wmb(); //寫屏障
smp_mb(); // 通用屏障
Blk:大內(nèi)核鎖
BKL(大內(nèi)核鎖)是一個(gè)全局自旋鎖,使用它主要是為了方便實(shí)現(xiàn)從Linux最初的SMP過(guò)度到細(xì)粒度加鎖機(jī)制。它終將退出歷史舞臺(tái)。
BKL的特性:
持有BKL的任務(wù)仍然可以睡眠 。因?yàn)楫?dāng)任務(wù)無(wú)法調(diào)度時(shí),所加的鎖會(huì)自動(dòng)被拋棄;當(dāng)任務(wù)被調(diào)度時(shí),鎖又會(huì)被重新獲得。當(dāng)然,并不是說(shuō),當(dāng)任務(wù)持有BKL時(shí),睡眠是安全的,緊急是可以這樣做,因?yàn)樗卟粫?huì)造成任務(wù)死鎖。
BKL是一種遞歸鎖。一個(gè)進(jìn)程可以多次請(qǐng)求一個(gè)鎖,并不會(huì)像自旋鎖那么產(chǎn)生死鎖。BKL可以在進(jìn)程上下文中。
BKL是有害的:
在內(nèi)核中不鼓勵(lì)使用BKL。一個(gè)執(zhí)行線程可以遞歸的請(qǐng)求鎖lock_kernel(),但是釋放鎖時(shí)也必須調(diào)用同樣次數(shù)的unlock_kernel()操作,在最后一個(gè)解鎖操作完成之后,鎖才會(huì)被釋放。BKL在被持有時(shí)同樣會(huì)禁止內(nèi)核搶占。多數(shù)情況下,BKL更像是保護(hù)代碼而不是保護(hù)數(shù)據(jù)。
備注:?jiǎn)魏瞬豢蓳屨純?nèi)核 唯一的異步事件就是硬件中斷 ,所以想要同步即關(guān)閉中斷即可。對(duì)于單核可搶占和多核可搶占的 ,除了中斷 還有進(jìn)程調(diào)度(即優(yōu)先級(jí)高的進(jìn)程搶占cpu資源),而上述所有這些機(jī)制都是為了防止并發(fā)。
評(píng)論