当前位置: 首页 >> 程序设计 >> 内核同步方法大全
 

内核同步方法大全

作者:      来源:zz     发表时间:2008-09-13     浏览次数:      字号:    

一.为什么内核需要同步方法
并发指的是多个执行单元同时,并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量,静态变量等)的访问则很容易导致竞态。
主要竞态发生如下:
1.对称多处理器(SMP)多个CPU
 SMP是一种紧耦合,共享存储的系统模型,它的特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。
 
2.单CPU内进程与抢占它的进程
Linux2.6内核支持抢占调度,一个进程在内核执行的时候被另一高优先级的进程打断,进程与抢占它的进程访问共享资源的情况类似于SMP

3.中断(硬中断,软中断,Tasklet,底半部)与进程之间
中断可以打断正在执行的进程,如果中断处理程序访问进程正在访问的资源,则竞态也会发生。

此外,中断也有可能被新的更高级优先级中断中断,因此,多个中断之间本身也可能引起竞态。

那是不是就没有办法呢?当然不是,记住linux开源的力量是无穷的。
解决竞态问题的途径是保证对共享资源的互斥访问,互斥访问就是一个执行单元在访问的时候,其他执行单元禁止访问。
访问共享资源的代码区域成为临界区,临界区需要互斥机制加以保护。
中断屏蔽,原子操作,自旋锁和信号量等是linux互斥操作的途径。



 
二.内核的同步方法分类
1.中断屏蔽
中断屏蔽使得中断与进程之间的并发不再发生,linux内核进程调度等操作都依赖中断实现,内核抢占进程之间的并发就可以避免了。
但是,由于linux内核的异步I/O,进程调度等都依赖中断,所以长时间屏蔽中断很危险。
且中断屏蔽只能禁止本CPU内的中断,不能解决SMP引发的竞态。
因此中断屏蔽不是值得推荐的方法,它适宜与自旋锁联合使用。

2.原子操作
原子操作保证所有指令以原子方式执行(执行过程不能被中断)。
例:
线程1                                     线程2
increment(2->3)                           ...
...                                       increment(3->4)
两个操作不可能并发访问同一个变量,绝对不可能引起竞态。

2.1>针对原子整数操作只能对atomic_t类型的数据处理。
定义在<asm/atomic.h>  typedef struct { int counter; } atomic_t;
使用atomic_t而不是int类型确保编译器不对相应的值进行优化,使得原子操作接受正确的地址。强类型匹配。


原子整数操作API简介:

atomic_read(atomic_t * v);            该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。

atomic_set(atomic_t * v, int i);      该函数设置原子类型的变量v的值为i。

void atomic_add(int i, atomic_t *v);   该函数给原子类型的变量v增加值i。

atomic_sub(int i, atomic_t *v);        该函数从原子类型的变量v中减去i。

int atomic_sub_and_test(int i, atomic_t *v);
该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。

void atomic_inc(atomic_t *v);          该函数对原子类型变量v原子地增加1。

void atomic_dec(atomic_t *v);          该函数对原子类型的变量v原子地减1。

int atomic_dec_and_test(atomic_t *v);
该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_inc_and_test(atomic_t *v);
该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。

int atomic_add_negative(int i, atomic_t *v);
该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。

注:原子整数的操作主要用途实现计数器。



2.2>原子位操作
位操作函数是对普通函数内存地址进行操作,它的参数是一个指针和一个位号。

原子位操作API简介:
void set_bit(int nr, void *addr)        原子设置addr所指的第nr位

void clear_bit(int nr, void *addr)      原子的清空所指对象的第nr位

void change_bit(nr, void *addr)         原子的翻转addr所指的第nr位

int test_bit(nr, void *addr)            原子的返回addr位所指对象nr位

int test_and_set_bit(nr, void *addr)    原子设置addr所指对象的第nr位,并返回原先的值

int test_and_clear_bit(nr, void *addr)  原子清空addr所指对象的第nr位,并返回原先的值

int test_and_change_bit(nr, void *addr)  原子翻转addr所指对象的第nr位,并返回原先的值

如:标志寄存器EFLSGS的系统标志,用于控制I/O访问,可屏蔽硬件中断。共32位,不同的位代表不同的信息,
对其中信息改变都是通过位操作实现的。


3.自旋锁

3.1>自旋锁
如果执行单元(一般针对线程)申请自旋锁已经被别的执行单元占用,申请者就一直循环在那里看是否该执行单元释放了自旋锁。
其作用是为了解决某项资源的互斥使用。虽然它的效率比互斥锁高,但是它也有些不足之处:
    1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行自旋,所以占用着CPU。所以自旋琐不应该长时间持有。
    2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、
    copy_from_user()、kmalloc()等。
    
自旋锁主要针对SMP,在单CPU中它仅仅设置内核抢占机制的是否启用的开关。 在内核不支持抢占的系统中,自旋锁退为空操作。
尽管自旋琐可以保证临界区不受别的CPU和本进程的抢占进程打扰,但执行临界区还受到中断和底半部(bh)的影响。所以与中断屏蔽
联系使用。

自旋锁的API:
spin_lock_init(spinlock_t *x);   //自旋锁在真正使用前必须先初始化
   
  获得自旋锁:spin_lock(x);   //只有在获得锁的情况下才返回,否则一直“自旋”
                           spin_trylock(x);  //如立即获得锁则返回真,否则立即返回假
      释放锁:spin_unlock(x);
   
结合以上有以下代码段:

    spinlock_t lock;        //定义一个自旋锁
    spin_lock_init(&lock);
    spin_lock(&lock);   
    临界区
    spin_unlock(&lock);   //释放锁
   

spin_lock_irqsave(lock, flags)
该宏获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中//断。相当于:spin_lock()+local_irq_save()
spin_unlock_irqrestore(lock, flags)
该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的//值。它与spin_lock_irqsave配对使用。
相当于:spin_unlock()+local_irq_restore()

spin_lock_irq(lock)
该宏类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。相当         //于:spin_lock()+local_irq_disable()
spin_unlock_irq(lock)
该宏释放自旋锁lock的同时,也使能本地中断。它与spin_lock_irq配对应用。相当于: spin_unlock()+local_irq+enable()

spin_lock_bh(lock)
该宏在得到自旋锁的同时失效本地软中断。相当于:  //spin_lock()+local_bh_disable()
spin_unlock_bh(lock)
该宏释放自旋锁lock的同时,也使能本地的软中断。它与spin_lock_bh配对//使用。相当于:spin_unlock()+local_bh_enable()


3.2>读写自旋锁
读写自旋锁为读和写分别提供了不同的锁。一个或多个读任务并发的持有读写锁;用于写的锁最多只能被一个写任务持有,
此时不能并发的读操作。

注:读锁和写锁要完全分割在代码的分支中
read_lock(&mr_rwlock)
write_lock(&mr_rwlock)  将会带来死锁,因为写锁会不断自旋。
读写自旋锁相当于自旋锁的读的一种优化,具有自旋锁的特点,如:不宜长时间持有锁等。
关于读写自旋锁的API函数自己分析,其中读写各有自己的API函数。

3.3>顺序锁
顺序锁是对读写锁的一种优化,读执行单元绝不会被写执行单元阻塞。读执行单元可以在写执行单元对被顺序锁保护的共享资源
进行写操作时还可以继续读,而不必等待写执行单元完成写操作,写执行单元也不需要等待所有读执行单元完成读操作才进行写操作。

注:顺序锁要求被保护的共享资源不含有指针,因为写执行单元可能使指针失效,读执行单元正在访问该资源,导致Oops。

重要:读执行单元访问共享资源时,如果有写操作执行完成,读执行单元就需要重新进行读操作。
do{
  seqnum=read_seqbegin(&seqlock_a);
  //读执行代码
  ...
  }while(read_seqretry(&seqlock_a,seqnum));      //判断是否需要重读
 

[1] [2] [3]

编辑 webmaster

 
 
 
评论更多>>
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •