android读写锁,Android 深度探索(卷1)-学习笔记(原子操作、自旋锁)

-----------------------------------------------------------------

工作之后,比较少时间看书学习,也比较少时间做笔记。

买下这本书,虽然和自己预想的有些出入,但仍对自己有蛮大的帮助。

自己买了很多的书都只是看了一点就丢在一旁,浪费啊。。。这次可不能浪费了。。。

回首2014,发现自己获得的很少,原因在于自己不懂充分利用好时间,所以今年需要充分利用好时间,更加的努力,吼吼吼,聚集正能量。。。

-----------------------------------------------------------------

花了一个星期上下班的时间,看到了RCU机制,原以为今天下午能写完 原子操作、自旋锁、读写锁、顺序锁、RCU的笔记,没想到当真正实际上机操作时,发现那些自认为已经理解的知识点在机器上得不到证实。看来不能光看书,还得要多多实践实践。

-----------------------------------------------------------------

第三篇 Linux 驱动开发高级技术

----------------------------------------------------------------

第11章 Linux 驱动程序中的并发控制

驱动程序是为上层服务的,一个驱动程序可能会被多个应用进程同时使用。

一、原子操作:即不可再细分的操作,最小的执行单位,在操作完之前都不会被任何事件中断。

整型原子操作:对int类型的操作变成原子操作。

int i = 0;

i = i + 2;       

数据类型:atomic_t 在 linux/types.h 中定义。

typedef struct

{

int counter;

}atomic_t;

atomic_t.counter的变量值变化就是原子的,当然我们不能直接去读写这个变量值,要使用一些函数才能对它进行操作,

这些函数都是围绕着 atomic_t.counter 变量的修改、获取而设计的。

示例:

/* 一般定义为全局变量 */

atomic_t n = ATOMIC_INIT(3);                // 将变量 n.counter 的初始值设为 3 --> n.counter = 3

n.counter = 10;                                                // 这种写法没有意义,它并不是原子操作。

atomic_set(&n, 2);                                        // 将变量 n.counter 的初始值设为 2 --> n.counter = 2

atomic_add(5, &n);                                        // 将变量 n.counter 的值加上 5 --> n.counter += 5

atomic_dec(&n);                                                // 将变量 n.counter 的值减 1 --> n.counter -= 1

printk("n = %d", atomic_read(&n));        // 读取变量 n.counter 的值 此时 n.counter == 6

接口:

32位整型原子操作的其他的函数(列出,方便查询):

ATOMIC_INIT(int i)                                                                宏 用 i 初始化 atomic_t 类型的变量

int        atomic_read(atomic_t *v)                                        宏 读 v 的值

void atomic_set(atomic_t *v, int i);                         宏 设 v 的值为 i

void atomic_add(int i, atomic_t *v);                         宏 将 v 的值加 i

void atomic_sub(int i, atomic_t *v);                        宏 将 v 的值减 i

void atomic_inc(atomic_t *v);                                        宏 将 v 的值加 1

void atomic_dec(atomic_t *v);                                         宏 将 v 的值减 1

int atomic_sub_and_test(int i, atomic_t *v);        宏 将 v 的值减 i,(0==v) ? 非0值 : 0;

int atomic_inc_and_test(atomic_t *v);                        宏 将 v 的值加 1,(0==v) ? 非0值 : 0;

int atomic_dec_and_test(atomic_t *v);                        宏 将 v 的值减 1,(0==v) ? 非0值 : 0;

int atomic_add_negative(int i, atomic_t *v);        宏 将 v 的值加 1,(v<0) ? 非0值 : 0;

int atomic_add_return(int i, atomic_t *v);                函 将 v 的值加 i,并返回 +i 后的结果

int atomic_sub_return(int i, atomic_t *v);                 函 将 v 的值减 i,并返回 -i 后的结果

int atomic_inc_return(atomic_t *v);                         宏 将 v 的值加 1,并返回 +1 后的结果

int atomic_dec_return(atomic_t *v);                         宏 将 v 的值减 1,并返回 -1 后的结果

int atomic_add_unless(atomic_t *v, int a, int u);        涵  ( v!=u ) ? v+a,返回非0值 : 0;

int atomic_inc_not_zero(atomic_t *v);                        宏 ( v!=0 ) ? v+1,返回非0值 : 0;

64位整型原子操作:和32位整型原子操作一致,所操作的接口只是名称不同,功能一致。

ATOMIC64_INIT(int i)                                                        宏 用 i 初始化 atomic_t 类型的变量

int        atomic64_read(atomic_t *v)                                        宏 读 v 的值

void atomic64_set(atomic_t *v, int i);                         宏 设 v 的值为 i

void atomic64_add(int i, atomic_t *v);                         宏 将 v 的值加 i

void atomic64_sub(int i, atomic_t *v);                        宏 将 v 的值减 i

void atomic64_inc(atomic_t *v);                                        宏 将 v 的值加 1

void atomic64_dec(atomic_t *v);                                 宏 将 v 的值减 1

...

...

注意:

32位整型原子操作在64位下执行不会有问题,但是64位整型原子操作在32位系统下执行会造成难以预料的后果。

为了让自己的驱动程序通用,若非必要则尽量使用32位整型原子操作。

位原子操作:

这种操作的数据类型是 unsigned long, 32位系统下为32bit,64位系统下为64bit。

位原子操作函数主要功能是将 unsigned long 变量中的指定位设为0或设为1。

示例:

unsigned long value = 0;

// 设置 value 的第0位为1, value = 0000000000000000 0000000000000001

set_bit(0, &value);

// 设置 value 的第2位为1, value = 0000000000000000 0000000000000101

set_bit(2, &value);

// 设置 value 的第0位为0, value = 0000000000000000 0000000000000100

clear_bit(0, &value);

// 将 value 的第0位取反,第0位为1则设为0,为0则设为1

change_bit(0, &value);

接口:都是宏

void set_bit(int nr, void *addr);                        将addr的第nr位设为 1

void clear_bit(int nr, void *addr);                 将addr的第nr位设为 0

void change_bit(int nr, void *addr);                将addr的第nr位取反

int test_bit(int nr, void *addr);                        如果addr的第nr位为1则返回非0值,否则返回0

int test_and_set_bit(int nr, void *addr);        将addr的第nr位设为 1,设置之前该位为1则返回非0值,否则返回0

int test_and_clear_bit(int nr, void *addr); 将addr的第nr位设为 0,设置之前该位为1则返回非0值,否则返回0

int test_and_change_bit(int nr, void *addr);将addr的第nr位设取反,设置之前该位为1则返回非0值,否则返回0

总结:

整型原子操作和位原子操作都是围绕一个变量的操作做为原子操作。

可以用来限定设备能被几个进程操作,和作为计数器使用。

实例:(例如操作打印机)

#define DevNumber        1

atomic_t v = ATOMIC_INIT(DevNumber);        // 限定1个

// 打开打印机设备

int OpenPrinterDevice(unsigned char *buf, unsigned int size)

{

// 将v减1后,判断v是否为0

if(atomic_dec_and_test(v))

{

// v 为 0,表示成功得到操作权限

return 0;

}

else

{

// 表示 设备已经被占用。

return -EBUSY;

}

}

// 释放打印机设备

void ClosePrinterDevice(void)

{

atomic_set(&v, DevNumber);

}

二、自旋锁

原子操作可以让指定变量的操作是原子的。很多时候我们在处理一些数据执行某些动作的时候要保证执行过程中

不能被中断,要求是原子的,而整型、位原子操作要实现这种需求就会比较复杂一些。而使用自旋锁则简单很多。

示例:

/* 一般定义为全局变量 */

spinlock_t lock;                // 定义一把自旋锁

spin_lock_init(&lock);        // 初始化这把自旋锁

或者使用宏来定义并初始化 DEFINE_SPINLOCK(lock)

void MyLock()

{

/* 使用场合:中断下半部与中断服务程序不会进入临界区 */

spin_lock(&lock);                // 获取并上锁

// ...                                       

// 临界区代码

// ...

spin_unlock(&lock);                // 释放解锁,恢复内核的抢占

}

若中断处理函数中需要访问上面的临界区,当lock锁未被释放,同时中断产生:

void MyIRQ(void)                        // 产生中断

{

spin_lock(&lock);                // 由于该锁未被释放,所以中断服务参数就会一直自旋(双重请求)

// ...                                    // 而中断服务未退出 就无法退回MyLock(),就无法释放锁造成死锁

// 临界区代码

// ...

spin_unlock(&lock);                // 释放解锁,恢复内核的抢占

}

这种情况下需要采用以下的方式上锁:

void MyLockIrq()

{

/*使用场合:

1、中断服务函数与中断下半部都需要进入该临界区

2、中断服务函数需要进入该临界区

*/

spin_lock_irq(&lock);        // 获取并上锁 临界区的内容可能会被

// ...                                       

// 临界区代码

// ...

spin_unloc_irq(&lock);        // 释放解锁,恢复内核的抢占以及硬件中断,中断下半部也有效

}

如果访问临界区的资源的代码不是放在中断服务函数中,而是放在中断下半部也会出现相似的情况,

即在MyLock()上锁之后,产生一个硬件中断,当执行完中断服务函数之后就可能会继续执行中断下

半部的代码,因为它可以抢占进程上下文,而低半部要获取的锁已经被MyLock()上锁,形成死锁。

这种情况也可以用void MyLockIrq()这种方式,但是最好用一下方式,更快:

void MyLockBh()

{

/* 使用场合:中断下半部与进程上下文都需要进入该临界代码 */

spin_lock_bh(&lock);        // 获取并上锁

// ...                                       

// 临界区代码

// ...

spin_unloc_bh(&lock);        // 释放解锁,恢复内核的抢占以及中断下半部

}

对于一个CPU的机器来说:当有A、B进程都要执行临界区的代码时,假设A先获得锁之后,B进程不会被调度,

系统呈现假死状态,        只有当A释放锁之后,B进程才会被调度再去获取锁,此时A已经释放锁,所以B也就顺利得到锁。

对于两个CPU的机器来说:当有A、B进程都要执行临界区的代码时,假设A先获得锁之后,B进程也会去获取锁,

但是锁已经被A得到,那么B进程则会一直不停的循环检测锁是否被释放,此时系统会呈现假死状态。

(这些现象可以在VM虚拟机上验证,VM虚拟机可以调整CPU个数)

A进程:

spin_lock(&lock);                // 获取并上锁

// ...

// 临界区代码                        

// ...

spin_unlock(&lock);                // 释放解锁

B进程:

spin_lock(&lock);                //

// ...

// 临界区代码

// ...

spin_unlock(&lock);                // 释放解锁

也就说,只有一个进程能进入临界区,其他进程要想进入临界区只能自己在原地循环旋转等待。

使用注意事项:

1、自旋锁实际上是忙等待,因为在等待锁的时候是在不停的循环等待,长时间占用锁会极大降低系统性能。

2、要避免在临界区中调用可能会产生睡眠的函数,因为此时抢占、中断已经关闭,无法被唤醒导致无法解锁。

3、若数据被软中断共享,也需要加锁,因为在不同处理器上存在软中断同时执行问题。

4、注意避免死锁,例如上述例子,A进程获得了锁之后,又继续获取该锁,因为该锁已经被A获取,

所以该锁无法再次被A获取,A就会一直循环打转等待,A没有机会释放该锁,该CPU被锁死,

对于多颗CPU来说,其他进程又无法释放该锁,形成死循环,导致死机。

A进程:

spin_lock(&lock);                // 获取并上锁  关闭了内核的抢占

spin_lock(&lock);                //

// ...

// 临界区代码                        

// ...

spin_unlock(&lock);                // 没有机会释放

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值