好好学习,天天向上
分类: LINUX
2011-04-08 12:12:11
锁
内核将加锁的情况分为两种,一种是可以睡眠的情况,一种是不能睡眠的情况。对于可以睡眠的情况,内核提供了旗标(semaphore,又称互斥锁),而对于另一种情况,内核提供了自旋锁。
asm/semaphore
1.静态初始化
DECLARE_MUTEX(name); //定义并初始化为1
DECLARE_MUTEX_LOCKED(name);//定义并初始化为0
2.运行时初始化:
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
3.直接定义一个semaphore结构体,然后初始化:
void sema_init(struct semaphore *sem, int val);
void down(struct semaphore *sem);//不可中断,不推荐
int down_interruptible(struct semaphore *sem);//如果它返回非零, 操作被打断了. 在这个情况下通常要做的是返回 -ERESTARTSYS.
int down_trylock(struct semaphore *sem);//立即返回
void up(struct semaphore *sem); //解锁
记得读者和写者的经典问题吗,为了提高并发度,区分读者和写者,内核也提供了读者写者semaphore
linux/rwsem.h
初始化方式只有一种:
void init_rwsem(struct rw_semaphore *sem);
void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);//注意,成功返回非零
void up_read(struct rw_semaphore *sem);
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);
写者有优先权, 当一个写者试图进入临界区, 就不会允许读者进入直到所有的写者完成了它们的工作. 这个实现可能导致读者饥饿 -- 读者被长时间拒绝存取 -- 如果你有大量的写者来竞争旗标. 由于这个原因, rwsem 最好用在很少请求写的时候, 并且写者只占用短时间。
等待semaphore是幸福的,可以睡觉,而等待spinlock--自旋锁无疑是痛苦的,因为不能睡觉还要一直自旋。在中断上下文这种不能睡觉的地方,只能使用spinlock进行互斥访问临界资源。显然只有在多核系统上,中断才需要加锁访问资源,而对于一个单核的系统,自旋锁是杯具的,永远的自旋,因此, 自旋锁在没有打开抢占的单处理器系统上的操作被优化为什么不作。
使用自旋锁的原则:
1.持有锁的时候不能睡眠,如果睡眠,别的线程可能要等很长时间,也可能导致系统死锁
2.持有锁的时候,要禁止本地CPU中断,试想不禁止中断的情况,线程刚拿到锁,就被中断了,而中断服务程序中也请求这个锁,死锁就这样发生了。
3.持有锁的时候,请尽可能短,不要让别人等很久。
linux/spinlock.h
静态初始化:
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
运行时初始化:
void spin_lock_init(spinlock_t *lock);
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock); //禁止中断(只在本地处理器)在获得自旋锁之前,之前的中断状态保存在 flags 里。
void spin_lock_bh(spinlock_t *lock);//只禁止软中断,开放硬中断
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
这些函数成功时返回非零
同样也有读者写者自旋锁:
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* Dynamic way */
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);
加锁是件比较容易出错的事情,所幸有前人的经验可以参考:
1. 一个正确的加锁机制需要清晰和明确的规则
2.不论旗标还是自旋锁都不允许一个持锁者第 2 次请求锁; 如果你试图这样做, 事情就简单地完了。
3.在假设持有锁的函数边注明,是否持有锁。
4.要请求多个锁时,确定加锁的顺序,所有请求锁的操作必须遵循此顺序。semaphore应当在spinlock前获取,如果需要的话。最重要的, 尽力避免需要多于一个锁的情况。
completions机制
这种机制出现的背景是,一个进程等待它的一个线程完成一个任务,然后继续执行,进程并不知道线程什么时候可以完成,内核提供了一个机制,这种机制允许线程完成任务后通知进程。
linux/completion.h
静态初始化:
DECLARE_COMPLETION(my_completion);
运行时初始化:
struct completion my_completion;
init_completion(&my_completion);
void wait_for_completion(struct completion *c); //等待complete,如果没有线程的complete,它将永远等待下去。
void complete(struct completion *c);//通知线程任务完成了,释放complete
void complete_all(struct completion *c);//通知所有等待的线程任务完成了
一个 completion 正常地是一个单发设备; 使用一次就放弃,在使用了complete_all的情况下,如果要继续使用它,必须重新初始化:
INIT_COMPLETION(struct completion c);
当然如果你使用的是complete通知函数,就不必初始化了。
一个经典的使用方法是,模块退出时,通知工作线程可以退出了,然后调用wait_for_completion等待工作线程的退出消息,工作线程收到退出通知后调用void complete_and_exit(struct completion *c, long retval);退出线程并通知模块退出函数,模块退出函数收到后开始结束。
实践:
为内存设备加上semaphore:
struct dev_info_struct
{
char* data;
int size;
struct semaphore sem;
struct cdev cdev;
}dev_info;
ssize_t base_char_read(struct file *fp, char __user *buf, size_t size, loff_t *ff)
{
struct dev_info_struct *dev_info_ptr;
if(size<0)
return -1;
if(down_interruptible(&dev_info.sem)!=0)
return -ERESTARTSYS;
dev_info_ptr=(struct dev_info_struct*)fp->private_data;
if(size>dev_info_ptr->size)
size=dev_info_ptr->size;
if(copy_to_user(buf,dev_info_ptr->data,size)!=0)
size=-1;
printk("base char read data,size %d!\n",size);
up(&dev_info.sem);
return size;
}
在base_char_init中初始化:
sema_init(&dev_info.sem,1);
base_char_write和base_char_read差不多,不再贴出。
有时候只需要对一个整数进行原子操作,使用加锁就有些大材小用了,内核为此提供了整数的原子变量。
asm/atomic.h
atomic_t v = ATOMIC_INIT(value);
void atomic_set(atomic_t *v, int i);
int atomic_read(atomic_t *v);
void atomic_add(int i, atomic_t *v);
void atomic_sub(int i, atomic_t *v);
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
//操作后如果为0,返回真
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
//操作后如果为负,返回真
int atomic_add_negative(int i, atomic_t *v);
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
注意:两个原子变量函数之间是非原子的,如:
atomic_set(&v,1);
atomic_read(&v);
它们之间可能v已经被改变了。
位操作:
asm/bitops.h
void set_bit(nr, void *addr); //设置第 nr 位在 addr 指向的数据项中.
void clear_bit(nr, void *addr); //清除指定位在 addr 处的无符号长型数据. 它的语义与 set_bit 的相反.
void change_bit(nr, void *addr); //翻转这个位.
test_bit(nr, void *addr); //这个函数是唯一一个不需要是原子的位操作; 它简单地返回这个位的当前值.
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);