读写自旋锁(防写不防读)
和读取写入者信号量很相似,一些任务只需要读取受保护的数据结构,而其他的则必须做出修改。允许多个并发的读取是可能的,只要它们中没有那个做出修改。这样可以极大地提高性能。
它保留了自锁的概念,但是它规定在读方面同时可以有多个读单元,在写方面,只能最多有一个写进程。当然,读和写也不能同时进行。
使用方法:1)初始化读写锁的方法。
rwlock_t x;//动态初始化 rwlock_t x=RW_LOCK_UNLOCKED;//动态初始化
rwlock_init(&x);
2)最基本的读写函数。
void read_lock(rwlock_t *lock);//使用该宏获得读写锁,如果不能获得,它将自旋,直到获得该读写锁
void read_unlock(rwlock_t *lock);//使用该宏来释放读写锁lock
void write_lock(rwlock_t *lock);//使用该宏获得获得读写锁,如果不能获得,它将自旋,直到获得该读写锁
void write_unlock(rwlock_t *lock);//使用该宏来释放读写锁lock
3)和自旋锁中的spin_trylock(lock),读写锁中分别为读写提供了尝试获取锁,并立即返回的函数,如果获得,就立即返回真,否则返回假:
read_trylock(lock)和write_lock(lock);
4)硬中断安全的读写锁函数:
read_lock_irq(lock);//读者获取读写锁,并禁止本地中断
read_unlock_irq(lock);//读者释放读写锁,并使能本地中断
write_lock_irq(lock);//写者获取读写锁,并禁止本地中断
write_unlock_irq(lock);//写者释放读写锁,并使能本地中断
read_lock_irqsave(lock, flags);//读者获取读写锁,同时保存中断标志,并禁止本地中断
read_unlock_irqrestores(lock,flags);//读者释放读写锁,同时恢复中断标志,并使能本地中断
write_lock_irqsave(lock,flags);//写者获取读写锁,同时保存中断标志,并禁止本地中断
write_unlock_irqstore(lock,flags);写者释放读写锁,同时恢复中断标志,并使能本地中断
5)软中断安全的读写函数:
read_lock_bh(lock);//读者获取读写锁,并禁止本地软中断
read_unlock_bh(lock);//读者释放读写锁,并使能本地软中断
write_lock_bh(lock);//写者获取读写锁,并禁止本地软中断
write_unlock_bh(lock);//写者释放读写锁,并使能本地软中断
一个典型应用:
rwlock_t lock; //定义rwlock
rwlock_init(&lock);//初始化rwlock
//读时获取锁
read_lock(&lock);
….//临界资源
read_unlock(&lock);
//写时获取锁
write_lock_irqsave(&lock, flags);
….//临界资源
write_unlock_irqrestore(&lock, flags);
信号量
信号量(信号量其实和自旋锁是一样的,就是有一点不同:当获取不到信号量时,进程不会原地打转而是进入休眠等待状态)
Linux系统中与信号量相关的操作主要有一下4种:
1)定义信号量 struct semaphore sem;
2)初始化信号量
void sema_init (struct semphore *sem, int val); //设置sem为val
void init_MUTEX(struct semaphore *sem); //初始化一个用户互斥的信号量sem设置为1
void init_MUTEX_LOCKED(struct semaphore *sem); //初始化一个用户互斥的信号量sem设置为0
DECLARE_MUTEX(name); //该宏定义信号量name并初始化1
DECLARE_MUTEX_LOCKED(name); //该宏定义信号量name并初始化0
3)获得信号量
void down(struct semaphore *sem); //该函数用于获取信号量sem,会导致睡眠,不能被信号打断,所以不能在中断上下文使用。
int down_interruptible(struct semaphore *sem); //因其进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这是返回非0。
int down_trylock(struct semaphore *sem);//尝试获得信号量sem,如果能够获得,就获得并返回0,否则返回非0,不会导致调用者睡眠,可以在中断上下文使用
一般这样使用
if(down_interruptible(&sem))
{
return - ERESTARTSYS;
}
4)释放信号量
void up(struct semaphore *sem); //释放信号量sem,唤醒等待者
信号量一般这样被使用,如下所示:
//定义信号量
DECLARE_MUTEX(mount_sem);
down(&mount_sem);//获取信号量,保护临界区
…
critical section //临界区
…
up(&mount_sem);
好了,下边给大家一个例子看看使用信号量来实现设备只能被一个进程打开的例子:
static DECLARE_MUTEX(xxx_lock);//定义互斥锁
static int xxx_open(struct inode *inode, struct file *filp)
{
…
if(down_trylock(&xxx_lock)) //获得打开锁
return – EBUSY; //设备忙
…
return 0;//成功
}
static int xxx_release(struct inode *inode, struct file *filp)
{
up(&xxx_lock); //释放打开锁
return 0;
}
在上面介绍的有关信号量的操作中,我们提到了一个问题就是信号量的初始化问题,一般对信号量的初始化是没有限制的,但如果信号量被初始化为0,则它可以用于同步,说到这里,就不得不介绍新的一招了
出招表八:同步(它意味着一个执行单元的继续执行需要等待另一个执行单元完成其事,保证了执行的先后顺序)
在这个图中,执行单元A执行代码区域b之前,必须等待执行单元B执行完代码单元c,而信号量刚好可辅助完成这一同步过程.
这时你可能要问,像这样的同步,在现实中可是常常遇到.比如,报名时必须等前一项完成了才能完成后一项..等等,是不是同步就这么一种方式啊..
.其实,Linux系统提供了一种更好的同步机制----完成量,
完成量(completion),它用于一个执行单元等待另一个执行单元执行完某事(同步是也)
使用方法:1)定义完成量
struct completion my_completion;
2)初始化
init_completion(&my_completion); //要是觉得这两步麻烦,就再给你来个宏即定义又初始化DECLARE_COMPLETION(my_completion);
3)等待完成量
void wait_for_completion(structcompletion *c); //等待一个completion被唤醒
wait_for_completion_interruptible(struct completion *c); //可中断的wait_for_completion
unsigned long wait_for_completion_timeout(struct completion *x,unsigned long timeout); //带超时处理的wait_for_completion
4)唤醒完成量
void complete(struct completion *c); //只唤醒一个等待的执行单元。
void complete_all(struct completion *c); //唤醒所有等待这个完成量的执行单元
瞧我这记性,说了这么多,怎么就忘了给出completion结构体呢:
struct completion
{
unsigned int done;//标志是否已经做完,未做完就是负值,代表等待个数,完成为0
wait_queue_head_t wait;
}
如果觉得不太理解,不过瘾,就给你来张图:
这下感觉怎样,好多了吧...