/***********************************信号量相关操******************************************/
/*
* 注意:信号量本质上就是一个整数,获取信号量,如果信号量的值大于0,则进程继续,如果信号量为0或者更小,进程必须等待直到其他人释放该信号量,信号量必须对系统其它部分可用前被初始化,如果在拥有一个信号量时发送错误,必须在将错误状态返回之前释放该信号量,否则这段临界区资源将永远不能再次被访问,信号量必须在设备对系统其它部分可用前被初始化
*/
#include <linux/semaphire.h>
struct semaphore sem; //定义一个信号量
void sema_init(struct semaphore *sem, int val); //创建一个信号量并初始化为val
DECLARE_MUTEX(name); //初始化信号量为1
DECLARE_MUTEX_LOCKED(name); //初始化信号量为0
void init_MUTEX(struct semaphore *sem) //运行时动态初始化为1
void init_MUTEX_LOCKED(struct semaphore *sem) //运行时动态初始化为0
void down(struct semaphore *sem); //获取信号量,不可中断,如果获取不到则进入睡眠
int down_interruptible(struct semaphore *sem);
//获取信号量,可被中断,如果被中断该函数返回一个非0值,而调用者不会拥有该信号量
//用法如下:
if(down_interruptible(struct semaphore *sem))
return -ERESTARTSYS;
int down_trylock(struct semaphore *sem);
//获取信号量,但永远不会睡眠,如果信号量不可获得,则立刻返回一个非零值
void up(struct semaphore *sem); //释放该信号量,唤醒等待者
/**********************************end***********************************************/
/***************************读取者/写入者信号量*****************************************/
/*
*注意:其允许多个进程可以并发读取,但只允许一个进程写入,一个rwsem可允许一个写入者或无限多个读取者拥有该信号量,写 入这具有更高的优先级,当某个给定的写入访问试图进入临界区时,在所有写入者完成其工作前,不会允许读取者访问,在驱动程序中使用它的机会相对较少
*/
#include <linux/rwsem.h>
struct rw_semaphore rwsem; //定义读写信号量
void init_rwsem(struct rw_semaphore *sem); //初始化读写信号量
对于只读访问可使用下面的函数:
void down_read(struct rw_semaphore *sem);
//它可以和其它读取者并发地访问,并且可能将调用进程置于不可中断的睡眠
int down_read_trylock(struct rw_semaphore *sem);
//它不会在读取访问不可获得时等待,它在授以访问时返回非0 ,其它情况返回0
void up_read(struct rw_semaphore *sem); //释放读取信号量
对于写入访问可使用下面的函数:
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem); //释放写入信号量
void downgrade_write(struct rw_semaphore *sem);
//当某个快速改变获得了写入锁,而其后是更长时间的只读访问的话,我们可以再结束修改后调用它来允许其它读取者访问
/**********************************end*****************************************************/
/*********************************互斥体****************************************************/
struct mutex my_mutex; //定义互斥体
mutex_init(&my_mutex); //初始化互斥体
获取互斥体:
mutex_lock(struct mutex *lock);
mutex_lock_interruptible(struct mutex *lock); //可中断
mutex_trylock(struct mutex *lock); //尝试获取,如果获取不到就立刻返回不会休眠
释放互斥体:
mutex_unlock(struct mutex *lock);
/******************************end**********************************************/
/************************完成量(completion)**************************************/
/*注意:完成量主要用于进程的同步,它允许一个线程告诉另一个线程某个工作已经完成*/
#include <linux/completion.h>
struct completion completion;
DECLARE_COMPLETION(completion); //创建完成量
或
init_completion(&completion);
void wait_for_completion(struct completion *c); //等待其他进程的完成量,该函数执行一个非中断的等待
void completion(struct completion *c); //进程完成了它的工作,可调用它来告诉其他进程(调用wait_for_completion)其工作已经完成,其只唤醒一个等待的进程
void completion_all(struct completion *c);//其唤醒所有等待的进程
/********************************end***************************************************/
/*******************************自旋锁**************************************************/
/*
*注意:和信号量不同,自旋锁可在不能休眠的代码中使用(比如中断例程),其在获取不到自旋锁时,调用者在自旋(代码进入忙循环并重复检查这个锁,直到该锁可用为止),所有的自旋锁都是不可中断的,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止,因为它不可睡眠的
*
* 一个自旋锁是一个互斥设备,它只能有两个值:锁定和解锁。它通常实现为某个整数值中某一位,希望获得某特定锁的代码测试相关位,如果锁可用,则“锁定”位被设置,然后代码继续进入临界区,相反,如果被其他人获得,则代码进入忙并重复检查这个锁,直到该锁可用为止,当存在自旋锁时,等待执行忙循环的处理器做不了任何事情
*
* 想象一下你的驱动请求了一个自旋锁并且在它的临界区里做它的事情. 在中间某处, 你的驱动失去了处理器. 或许它已调用了一个函数( copy_from_user,假设) 使进程进入睡眠. 或者, 也许, 内核抢占发威, 一个更高优先级的进程将你的代码推到一边. 你的代码现在持有一个锁, 在可见的将来的时间不会释放这个锁. 如果某个别的线程想获得同一个锁, 它会, 在最好的情况下,等待( 在处理器中自旋 )很长时间. 最坏的情况, 系统可能完全死锁.所以适用于自旋锁的核心原则是:任何拥有自旋锁的代码都必须是原子的,它不能休眠,事实上,它不能因为任何原因放弃处理器除了服务中断以外(某些情况下,此时也不能放弃处理器),内核抢占的情况由自旋锁本身处理,任何时候,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止,而休眠时不可预期的,当我们编写需要在自旋锁下执行的代码时,必须注意每一个所调用的函数
*
* 另外当我们的代码拥有锁在访问临界资源时产生了中断,中断服务程序也要获取锁才能访问设备资源,这时处理器将永远自旋下去,这时,我们需要在拥有自旋锁时禁止中断,这就使得我们的自旋锁必须在尽可能短的时间内拥有
*
* 如果每个获得锁的函数要调用其他试图获取这个锁的函数,我们的代码会死锁,无论信号量还是锁,都不允许锁拥有者第二次获得这个锁
*/
#include <linux/spinlock.h>
spinlock_t splock=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);
//spin_lock_irqsave会在获取锁之前禁止中断,而先前的中断状态保存在flags中
void spin_lock_irq(spinlock_t *lock);
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);
//非阻塞自旋锁:这两个函数在成功时返回非零值,否则返回0
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
/********************************end***************************************************/
/**************************读取者/写入者自旋锁******************************************/
//注意:其与读取者/写入者信号量类似
#include <linux/spinlock.h>
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; //声明及其初始化
或
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);
/***********************************end***********************************************/
/*******************************原子变量操作********************************************/
//注意:原子变量是整形数据,原子操作指的是在代码执行过程中不会被别的代码路径所中断的操作,即不会睡眠或中断,内核代码可以安全的调用它们而不会被打断
#include <asm/atomic.h>
void atomic_set(atomic_t *v, int i);
atomic_t v = ATOMIC_INIT(0); //可以是 ATOMIC_INIT(1)
//设置原子变量 v 为整数值 i. 你也可在编译时使用宏定义 ATOMIC_INIT初始化原子值.
int atomic_read(atomic_t *v);
//获取 v 的当前值.,返回原子变量的值
void atomic_add(int i, atomic_t *v);
//由 v 指向的原子变量加 i. 返回值是 void, 因为有一个额外的开销来返回新值, 并且大部分时间不需要知道它.
void atomic_sub(int i, atomic_t *v);
//从 *v 减去 i.
void atomic_inc(atomic_t *v);
void atomic_dec(atomic_t *v);
//自增或自减一个原子变量.
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);
//进行一个特定的操作并且测试结果; 如果, 在操作后, 原子值是 0, 那么返回值是真; 否则, 它是假. 注意没有 atomic_add_and_test.
int atomic_add_negative(int i, atomic_t *v);
//加整数变量 i 到 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_add 和其类似函数, 除了它们返回原子变量的新值给调用者.
一般用法:
static atomic_t v = ATOMIC_INIT(1);
if(!atomic_dec_and_test(&v))
//第一个进程执行时返回0也即为真,取非后为假,所以跳过这段语句继续执行,其他进程只能执行到此就返回了
{
atomic_inc(&v);
return xxx;
}
…… ……
atomic_inc(&v);//释放
/*********************************end***********************************************/
/******************************位原子操作*********************************************/
//注意:这些函数可用来访问和修改一个共享标志
#include <asm/bitops.h>
void set_bit(nr, void *addr);
//设置在 addr 指向的数据项中的第 nr 位为1
void clear_bit(nr, void *addr);
//清除在 addr 指向的数据项中的第 nr 位为0
void change_bit(nr, void *addr);
//反转在 addr 指向的数据项中的第 nr 位
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);
//原子地动作如同前面列出的, 除了它们还返回这个位以前的值.
/*****************************end*********************************************/
注:以上总结部分选自韦东山群答疑助手:沈朝平《Linux驱动程序学习笔记》!非常感谢!