把握linux内核设计(十):内核同步

#define DEFINE_SPINLOCK(x) spinlock_t x = __SPIN_LOCK_UNLOCKED(x)# define spin_lock_init(lock)\do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)#endifspin_lock:#define spin_lock(lock)_spin_lock(lock)void __lockfunc _spin_lock(spinlock_t *lock){__spin_lock(lock);}static inline void __spin_lock(spinlock_t *lock){preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);}spin_lock_irqsave:#define spin_lock_irqsave(lock, flags)\do {\typecheck(unsigned long, flags); \flags = _spin_lock_irqsave(lock); \} while (0)unsigned long __lockfunc _spin_lock_irqsave(spinlock_t *lock){return __spin_lock_irqsave(lock);}static inline unsigned long __spin_lock_irqsave(spinlock_t *lock){unsigned long flags;local_irq_save(flags); //spin_lock的实现没有禁止本地中断这一步preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);#ifdef CONFIG_LOCKDEPLOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);#else_raw_spin_lock_flags(lock, &flags);#endifreturn flags;}

读写自旋锁一种比自旋锁粒度更小的锁机制,它保留了“自旋”的概念,但是在写操作方面,只能最多有1个写进程,在读操作方面,同时可以有多个读执行单元。当然,读和写也不能同时进行。读者写者锁有一个类型 rwlock_t, 在 <linux/spinlokc.h> 中定义. 它们可以以 2 种方式被声明和被初始化:静态方式:rwlock_t my_rwlock = RW_LOCK_UNLOCKED;动态方式:rwlock_t my_rwlock;rwlock_init(&my_rwlock);可用函数的列表现在应当看来相当类似。 对于读者, 有下列函数可用:void read_lock(rwlock_t *lock);void read_lock_irqsave(rwlock_t *lock, unsigned long flags);void read_lock_irq(rwlock_t *lock);void read_lock_bh(rwlock_t *lock);void read_unlock(rwlock_t *lock);void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);void read_unlock_irq(rwlock_t *lock);void read_unlock_bh(rwlock_t *lock);对于写存取的函数是类似的:void write_lock(rwlock_t *lock);void write_lock_irqsave(rwlock_t *lock, unsigned long flags);void write_lock_irq(rwlock_t *lock);void write_lock_bh(rwlock_t *lock);int write_trylock(rwlock_t *lock);void write_unlock(rwlock_t *lock);void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);void write_unlock_irq(rwlock_t *lock);void write_unlock_bh(rwlock_t *lock);

在与下半部配合使用时,锁机制必须要小心使用。由于下半部可以抢占进程上下文的代码,所以当下半部和进程上下文共享数据时,必须对进程上下文的共享数据进行保护,所以需要加锁的同时还要禁止下半部执行。同样的,由于中断处理器可以抢占下半部,所以如果中断处理器程序和下半部共享数据,,那么就必须在获取恰当的锁的同时还要禁止中断。 同类的tasklet不可能同时运行,所以对于同类tasklet中的共享数据不需要保护,但是当数据被两个不同种类的tasklet共享时,就需要在访问下半部中的数据前先获得一个普通的自旋锁。由于同种类型的两个软中断也可以同时运行在一个系统的多个处理器上,所以被软中断共享的数据必须得到锁的保护。

3. 信号量

一个被占有的自旋锁使得请求它的线程循环等待而不会睡眠,这很浪费处理器时间,所以自旋锁使用段时间占有的情况。linux提供另外的同步方式可以在锁争用时让请求线程睡眠,直到锁重新可用时在唤醒它,这样处理器就不必循环等待,可以去执行其它代码。这种方式就是即将讨论的信号量。

信号量是一种睡眠锁,如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其放入一个等待队列,然后睡眠。当持有的信号量被释放后,处于等待队列中的那个任务将被唤醒,并获得信号量。信号量比自旋锁提供了更好的处理器利用率,因为没有把时间花费在忙等带上。但是信号量也会有一定的开销,被阻塞的线程换入换出有两次明显的上下文切换,这样的开销比自旋锁要大的多。

信号量的实现与体系结构相关,信号量使用struct semaphore类型用来表示信号量,定义于文件<include/linux/semaphore.h>中:struct semaphore {spinlock_tlock;unsigned intcount;struct list_head wait_list;};

信号量初始化方法有如下:方法一:

struct semaphore sem; void sema_init(struct semaphore *sem, int val);//初始化信号量,并设置信号量 sem 的值为 val。 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);}

方法二:

DECLARE_MUTEX(name);定义一个名为 name 的信号量并初始化为1。

其实现为:

#define DECLARE_MUTEX(name) \struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)方法三:

#define init_MUTEX(sem)sema_init(sem, 1) //以不加锁状态动态创建信号量#define init_MUTEX_LOCKED(sem) sema_init(sem, 0) //以加锁状态动态创建信号量 信号量初始化后就可以使用了,使用信号量主要有如下方法:

背起简单的行攘,沐浴自由的风。

把握linux内核设计(十):内核同步

相关文章:

你感兴趣的文章:

标签云: