您的位置:首页 > 运维架构 > Linux

linux设备驱动程序之并发和竞态(二)

2015-07-05 13:16 274 查看
其实这blog都是阅读ldd3时的一些总结,巩固自己的学习,也方便后期的使用。大家也可以直接阅读ldd3原文。

锁陷阱

所谓的锁陷阱就是防止死锁。
不明确的规则:
1、不论是信号量还是 自旋锁,都不允许锁拥有者第二次获得这个锁(会死锁)。
2、系统直接调用的那些函数要获得信号量,保护要访问的设备结构。而内部函数的访问则可以根据需要上锁。

锁顺序规则:
1、如果都要获取一系列锁的话,那么可以按照一定顺序规则来获取锁,即:获取多个锁时,锁的顺序一直;
2、如果要获取自己的局部锁和系统的中心锁,则先获取自己的局部锁,然后再去获取中心锁;
3、如果要获取信号量和自旋锁,则需要先获取信号量,因为释放信号量的函数down有可能会休眠;

免锁算法:
有很多的免锁算法,ldd3中提到一种循环缓存区,感觉很有用(工作中也遇到过)。
循环缓冲区:就是比如一个数组A[9]为临界区,可以从A[0]端往A[9]端方向读取数据,从A[9]端往A[0]端写入数据。就是一个互相追逐的算法,这样可以不用锁就能保证数据的一致性;
工作中也遇到过这种缓冲区,把请求结构体往一个循环队列中放,而内核在队列的另外一端读取请求结构体去执行。

原子操作

原子操作在内核源代码中使用非常频繁,其实这个原子操作主要是对变量操作的。在本质上来说这也是一种锁,因为如果我们用一种锁机制去锁住一个变量,这样就有点浪费了。毕竟锁还是要耗费cpu的一些时间的,为了一个小小的变量,动用锁,不值当。所以原子操作就应运而生了。
头文件 <asm/atomic.h>,数据结构为:atomic_t

void atomic_set(atomic_t *v, int i); // 动态初始化,就是原子赋值:v = i
atomic_t v = ATOMIC_INIT(o);// 静态初始化,在编译时初始化

int atomic_read(atomic_t *v);// 返回V的当前值

void atomic_add(int i, atomic_t *v);// 将i 加到V指向的原子变量上,返回为void,是因为大多数情况下不需要这个返回值,所以不返回,降低函数成本;
void atomic_sub(in i, atomic_t *v);// 和上面函数功能相反

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,则返回true;否则返回false;

int atomic_add_negative(int i, atomic_t *v);// 将i加到v上,如果结果为负数,返回true,否则为false;

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);
执行上面对应的操作,然后返回操作后的新值V

位操作

和原子操作类似,位操作其本质也算是一种锁机制,只是相对来说粒度会小些。信号量和自旋锁是对一段代码,一块内存等进行保护的;原子操作是对变量进行保护的;而位操作是对变量中的各个位进行设置的,当然这也是原子操作。因为原子操作非常快,可以用单个机器指令来执行,所以不用禁止中断。
nr参数一般被定义为int,但也有些不同的系统定位为unsigned long等;

void set_bit(nr, void *addr);// 设置addr指向的数据项的第nr位
void clear_bit(nr, void *addr);//清楚addr指向的数据项的第nr位

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);
执行对应的操作,并且返回 先前 的值

seqlock

seqlock可对共享资源的快速、免锁访问。当要保护的资源很小、很简单、会频繁被访问而且写入访问很少发生且必须快速时,就可以使用seqlock。seqlock允许读取者自由访问,但要检查是否和写入者发生冲突,当发生冲突时,要重新读取数据;seqlock不保护含有指针的数据结构,因为在写入 者修改该数据的时候,读取者可能会读取一个无效的指针;
头文件 <linux/seqlock.h>
初始化:
seqlock_t lock1 = SEQLOCK_UNLOCKED;//静态初始化
seqlock_t lock2;
seqlock_init(&lock2);// 动态初始化

读取者在访问临界区时,先获取一个无符号整数,然后完成自己的操作,退出时再获取下该整数,对比下看是否相等;如果不相等,则需要重新获取个整数==》完成操作==》退出获取参数比较;代码如下:
unsigned int seq;
do {
seq = read_seqbegin(&the_lock);
/* 完成相应的工作 */
}while (read_seqretry(&the_lock, seq));
这类锁只是用来保护简单的计算,但是需要数据一致性,所以不一致就必须重新读取数据;

如果在中断处理例程中使用seqlock,则应该使用IRQ安全的版本:
unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);

int read_seqretry_irqrestore(seqlock_t *lock,unsigned int seq, unsigned long flags);

写入者进入由seqlock保护的临界区时必须获得一个互斥量:
void write_seqlock(seqlock_t *lock);
void write_sequnlock(seqlock_t *lock);

由于写入锁使用自旋锁实现,自旋锁控制写入访问,所以自旋锁的常见变种都可以使用:
void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);

void write_seqlock_irq(seqlock_t *lock);
void write_seqlock_bh(seqlock_t *lock);

void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
void write_sequnlock_irq(seqlock_t *lock,);
void write_sequnlock_bh(seqlock_t *lock);

转载地址:/article/1530574.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: