[Z] Linux 内核同步机制

Linux内核同步机制,挺复杂的一个东西,经常使用的有自旋锁,信号量,互斥体,原子操做,顺序锁,RCU,内存屏障等。这里就说说它们的特色和基本用法。html

自旋锁 :通用的 和读写的linux

特色:
1. 处理的时间很短。
2. 尝试获取锁时,不能睡眠,可是有trylock接口能够直接退出。
3. 多用在中断中。
4. 任什么时候候只有一个保持者可以访问临界区。
5. 能够被中断打断的(硬件和软件的)
6. 获取自旋锁后首先就是关闭了抢占安全

spin_lock使用接口:数据结构

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个线程同时读,在写时须要得到写锁(不能有读和写锁)。多线程

在读操做时,写操做必须等待;写操做时,读操做也须要的等待。这样虽然避免了数据的不一致,可是某些操做要等待,后面还会出现顺序锁,是对读写锁的优化,把写的优先级调高了架构

使用接口:并发

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
26
rwlock_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):通用的 和读写的优化

相对于自旋锁,它最大的特色就是容许调用它的线程进入睡眠ui

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 它和读写自旋锁相似 除了线程能够睡眠

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的信号量几乎没有区别,固然拥有互斥锁的进程老是尽量的在短期内释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct 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。

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
Seqlock:
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

1
2
3
4
5
struct __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机制是线程间通讯的一种轻量级机制:容许一个线程告诉另外一个线程工做已经完成

1
2
3
4
5
6
7
8
9
10
11
12
struct 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资源),而上述全部这些机制都是为了防止并发。

 

参考书籍《linux内核设计与实现》 ,《深刻linux设备驱动内核机制》等。 参考代码 linux3.18.3

相关文章
相关标签/搜索