Linux内核同步机制,挺杂乱的一个东西,常用的有自旋锁,信号量,互斥体,原子操作,次序锁,RCU,内存屏障等。这儿就说说它们的特色和根本用法。
自旋锁 :通用的 和读写的
特色:
1. 处理的时刻很短。
2. 测验获取锁时,不能睡觉,可是有trylock接口能够直接退出。
3. 多用在中止中。
4. 任何时分只要一个保持者能够拜访临界区。
5. 能够被中止打断的(硬件和软件的)
6. 获取自旋锁后首要便是封闭了抢占
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: 读写自旋锁根本特色和通用自旋锁相同,可是有时分多线程频频读取临界区假如一起只能一个那么功率会很低,它的特色便是在读的时分获取读锁,能够一起有N个线程一起读,在写时需求取得写锁(不能有读和写锁)。
在读操作时,写操作有必要等候;写操作时,读操作也需求的等候。这样尽管避免了数据的不一致,可是某些操作要等候,后边还会呈现次序锁,是对读写锁的优化,把写的优先级调高了
运用接口:
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;
而关于自旋锁的缺陷?这儿找到ibm一个文章
信号量(semaphore):通用的 和读写的
相关于自旋锁,它最大的特色便是答应调用它的线程进入睡觉
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值代表了一起多少个线程能够进入临界区,一般为1 即作为互斥体运用;当然》1 时,并发操作同一资源会引发什么呢?
down_interrupTIble(struct semaphore *sem); // 获取信号量 ,它是能够中止的。
up(struct semaphore *sem); // 开释信号量,一般配对运用,当然也能够在其他线程里开释它。
读写信号量: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); // 获取读信号量
up_read(struct rw_semaphore *sem); //开释读信号量
down_write(struct rw_semaphore *sem); //获取写信号量
up_write(struct rw_semaphore *sem); // 开释写信号量
互斥体(mutex):和count=1的信号量简直没有差异,当然具有互斥锁的进程总是尽可能的在短时刻内开释
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)(和架构相关,便是多条指令相当于一条指令履行,多用于计数)
组要是在smp上有含义,避免多条指令被多cpu履行。也是为了完结互斥。
次序锁(sequence)
特色:
和读写自旋锁锁相似,可是它的写不会等候。写的时分持有自旋锁。首要读者的代码应该尽可能短且写者不能频频取得锁,其次被维护的数据结构不包括被写修正的指针或被读直接引证的指针。当要维护的资源很小很简单,会很频频被拜访而且写入操作很少发生且有必要快速时,就能够用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 结合运用 //读时假如有写锁,则循环等候直到锁开释。
运用实例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供给的一切内核互斥设备傍边归于一种免锁机制。Rcu无需考虑读和写的互斥问题。
它实际上是rwlock的一种优化。读取者不用关怀写入者。所以RCU能够让多个读取者与写入者一起作业。写入者的操作份额在10%以上,需求考虑其他互斥办法。而且有必要要以指针的方法来拜访被维护资源。
Rcu_read_lock //仅仅是封闭抢占
Rcu_read_unlock //翻开抢占
Rcu_assign_pointer(ptr,new_ptr)
//等候行列:它并不是一种互斥机制。它辅佐comletion。
//它首要用来完结进程的睡觉等候。
//操作接口: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) :该机制被用来在多个履行途径间作同步运用,即和谐多个履行途径的履行次序。假如没有完结体,则睡觉在wait_list上。这儿usb 在提交urb时会用到。
假如驱动程序要在履行后边操作之前等候某个进程的完结,它能够调用wait_for_completion,以要完结的事情为参数:
Completion机制是线程间通讯的一种轻量级机制:答应一个线程告知另一个线程作业现已完结
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) // 静态界说completion
init_completion(struct completion *x); // 动态init
INIT_COMPLETION(x); // 初始化一个现已运用过的completion
Wait_for_completion(struct completion *x);
complete(struct completion *); //done +1,唤醒等候的一个。
Complete_all // 唤醒一切的,一般不会用。
内存屏障
内存屏障首要有:读屏障、写屏障、通用屏障、优化屏障
内存屏障首要处理了两个问题:单处理器下的乱序问题和多处理器下的内存同步问题
编译器优化以确保程序上下文因果关系为条件。
以 读屏障为例,它用于确保读操作有序。屏障之前的读操作一定会先于屏障之后的读操作完结,写操作不受影响,同归于屏障的某一侧的读操作也不受影响。相似的, 写屏障用于约束写操作。而通用屏障则对读写操作都有效果。而优化屏障则用于约束编译器的指令重排,不区别读写。前三种屏障都隐含了优化屏障的功用。比方:
tmp = ttt; *addr = 5; mb(); val = *data;
有了内存屏障就了确保先设置地址端口,再读数据端口。而至于设置地址端口与tmp的赋值孰先孰后,屏障则不做干涉。有了内存屏障,就能够在隐式因果关系的场景中,确保因果关系逻辑正确。
在Linux中,优化屏障便是barrier()宏,它打开为asm volatile(“”:::”memory”)
smp_rmb(); // 读屏障
smp_wmb(); //写屏障
smp_mb(); // 通用屏障
Blk:大内核锁
BKL(大内核锁)是一个大局自旋锁,运用它首要是为了便利完结从Linux开始的SMP过度到细粒度加锁机制。它终将退出历史舞台。
BKL的特性:
持有BKL的使命依然能够睡觉 。由于当使命无法调度时,所加的锁会主动被扔掉;当使命被调度时,锁又会被从头取得。当然,并不是说,当使命持有BKL时,睡觉是安全的,紧迫是能够这样做,由于睡觉不会形成使命死锁。
BKL是一种递归锁。一个进程能够屡次恳求一个锁,并不会像自旋锁那么发生死锁。BKL能够在进程上下文中。
BKL是有害的:
在内核中不鼓舞运用BKL。一个履行线程能够递归的恳求锁lock_kernel(),可是开释锁时也有必要调用相同次数的unlock_kernel()操作,在最终一个解锁操作完结之后,锁才会被开释。BKL在被持有时相同会制止内核抢占。大都情况下,BKL更像是维护代码而不是维护数据。
补白:单核不行抢占内核 仅有的异步事情便是硬件中止 ,所以想要同步即封闭中止即可。关于单核可抢占和多核可抢占的 ,除了中止 还有进程调度(即优先级高的进程抢占cpu资源),而上述一切这些机制都是为了避免并发。