内核同步之名词解释和原子操作

1. 名词解释

共享内存的应用程序必须要保护共享资源,防止共享资源被并发的访问。内核耶不例外。共享资源之所以要防止并发访问,是因为如果多个执行线程同时访问数据和操作数据,就有可能发生各线程之间相互覆盖共享数据的情况,造成被访问数据处于不一致状态。

所谓临界区(critical region)就是访问和操作共享数据的代码段。如果两个执行线程有可能处于同一个临界区中,那么这就是程序包含了一个bug,如果这种情况确实发生了,我们就称它是竞争条件(race condition)。避免并发和防止竞争条件被称为同步(synchronization)

 

现在可以看看下面这个例子:

i++;

该操作可以转化为类似于下面工作的机器指令序列:

1) 得到当前变量i的值并拷贝到寄存器中

2) 将寄存器中的值加1

3) 把i的新值写回到内存中

假定有两个执行线程同时进入这个临界区,如果i的初始值是7,则实际的执行序列可能如下:

线程1               线程2

获得i(7)             -

-                      获得i(7)

增加i(7->8)       -

-                     增加i(7->8)

写回i(8)             -

-                     写回i(8)

结果,变量保存下来的值是8,而不是我们期望的值9。

 

用户空间之所以需要同步,是因为用户程序会被调度程序抢占和重新调度。即使是单线程的多个进程共享数据,或者在一个程序内部处理信号(信号处理是异步发送的),也有可能长生竞争条件。这种类型的并发操作,其实两者并不是同时发生的,但它们相互交叉进行,所以称为伪并发执行

 

如果是在一台有多处理器的机器上,那么两个进程就可以真正的在临界区中同时执行,这种类型称为真并发

内核中有类似可能造成并发执行的原因,如下:

1) 中断--中断几乎可以在任何时刻异步发生,也就可能随时打断正在执行的代码

2) 软中断和tasklet--内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在执行的代码

3) 内核抢占--内核可能会被另一任务抢占

4) 睡眠及与用户空间的同步--在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行

5) 对称处理器--两个或多个处理器可以同时执行代码

解决并发可以使用加锁机制。锁有多种多样的形式,而且加锁的粒度范围不一样。各种锁机制之间的主要区别主要在于当锁被争用时的行为表现:有些锁会简单地执行忙等待;有些锁会使当前任务睡眠直到锁可用为止。linux内核提供了一组相当完备的同步方法。

 

2. 原子操作

原子操作可以保证指令以原子的方式执行,执行过程不被打断。内核提供了两组原子操作接口:一组是针对整数进行操作;另一组是针对单独的位进行操作。

 

2.1. 原子整数操作

针对整数的原子操作只能对atomic_t类型的数据处理。这里没有使用C语言的int类型,主要是因为:

1) 让原子函数只接受atomic_t类型操作数,可以确保原子操作只与这种特殊类型数据一起使用

2) 使用atomic_t类型确保编译器不对相应的值进行访问优化

3) 使用atomic_t类型可以屏蔽不同体系结构上的数据类型的差异。尽管Linux支持的所有机器上的整型数据都是32位,但是使用atomic_t的代码只能将该类型的数据当作24位来使用。这个限制完全是因为在SPARC体系结构上,原子操作的实现不同于其它体系结构:32位int类型的低8位嵌入了一个锁,因为SPARC体系结构对原子操作缺乏指令级的支持,所以只能利用该锁来避免对原子类型数据的并发访问。

 

原子整数操作最常见的用途就是实现计数器。原子整数操作列表在<asm/atomic.h>中定义。原子操作通常是内敛函数,往往通过内嵌汇编指令来实现。如果某个函数本来就是原子的,那么它往往会被定义成一个宏。

在编写内核时,操作也简单:

atomic_t  use_cnt;

atomic_set(&use_cnt, 2);

atomic_add(4, &use_cnt);

atomic_inc(use_cnt);

 

2.2. 原子性与顺序性

原子性确保指令执行期间不被打断,要么全部执行,要么根本不执行。而顺序性确保即使两条或多条指令出现在独立的执行线程中,甚至独立的处理器上,它们本该执行的顺序依然要保持。

 

2.3. 原子位操作

原子位操作定义在文件<asm/bitop.h>中。令人感到奇怪的是位操作函数是对普通的内存地址进行操作的。原子位操作在多数情况下是对一个字长的内存访问,因而位号该位于0-31之间(在64位机器上是0-63之间),但是对位号的范围没有限制。

编写内核代码,只要把指向了你希望的数据的指针给操作函数,就可以进行位操作了:

unsigned long word = 0;

set_bit(0, &word);  /*第0位被设置*/

set_bit(1, &word);  /*第1位被设置*/

clear_bit(1, &word);  /*第1位被清空*/

change_bit(0, &word);  /*翻转第0位*/

为了方便,内核还提供了一组与上述操作对应得非原子位函数。非原子位函数与原子位函数的操作完全相同,但是,前者不保证原子性,其名字前缀多了两个下划线。

内核还提供了两个例程用来从的地址开始搜索第一个被设置(未被设置)的位。

int find_first_bit(unsigned long *addr, unsigned int size);

int find_first_zero_bit(unsigned long *addr, unsigned int size);

如果搜索的范围仅限于一个字,使用__ffs()和__ffz()这两个函数更好。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值