内核同步方法(Linux Kernel Development)

本文转载:http://blog.chinaunix.net/uid-20779306-id-1845697.html

所谓同步问题,也就是说采用锁机制或者相应的方法来避免竞态的发生。理论上已经有所了解,这篇笔记记录的是内核同步的方法。

一,原子操作

原子操作可以保证指令以原子的方式执行,原子操作是不能够被分割的指令。 原子操作分为原子整数操作和原子位操作

1,原子整数操作:

原子整数操作操作的数据必须是atomic_t类型的。如果想把atomic_t转换成int类型,可以用函数atomic_read()来实现。

原子整数操作最常见的用途就是实现计数器

原子整数操作列表( <asm/atomic.h> ):

2,原子位操作:

原子位操作函数是对普通的内存地址进行操作。所操作的数据的数据类型没有特殊的限制。

原子位操作的列表( <asm/bitops.h> ):

内核提供了两个函数来从指定的地址开始搜索第一个被设置(或未被设置)的位。

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

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


二,自旋锁

发生争用时,自旋锁使得请求它的线程自旋(特别浪费处理器时间),直到获得锁为止。所以自旋锁不应该长时间被持有。这点和信号量不同,信号量当申请锁发生争用时,线程睡眠,内核转而执行其他的程序,直到锁被释放,唤醒等待的线程。

自旋锁的设计目标:在短期内进行轻量级加锁。

自旋锁相关的操作在<asm/spinlock.h>中定义。

基本使用形式如下:

spinlock_t my_lock= SPIN_LOCK_UNLOCKED;

spin_lock(&my_lock,flags);

/*临界区*/

spin_unlock(&my_lock,flags);

注意:自旋锁是不可递归的。否则将出现死锁。

自旋锁可以使用在中断处理程序中(此情况下不可用信号量,因为可能导致睡眠)。此时,一定要在获得锁之前,首先禁止本地中断。否则可能被另一个中断处理程序抢占,然后申请同一个锁,这样会发生死锁。

内核提供了禁止中断的同时请求锁的接口,使用起来很方便:

spinlock_t my_lock= SPIN_LOCK_UNLOCKED;
unsigned long flags;

spin_lock_irqsave(&my_lock,flags);

/*临界区*/

spin_unlock_irqrestore(&my_lock,flags);

spin_lock_irqsave():保存中断的当前状态,并禁止本地中断,然后再去申请指定的锁。

注意,我们应该对数据加锁,而不是代码加锁

其他针对自旋锁的操作:


三,读-写自旋锁

当我们对某个数据结构的操作有读/写两种操作时,可以考虑用读/写锁这样的机制。linux提供了专门的读-写自旋锁。这种自旋锁为读和写分别提供了不同的锁。一个或多个读任务可以并发的持有读者锁。相反,用于写的锁最多只能被一个写任务持有。而且此时不能有并发的读操作

读-写自旋锁的使用方法如下:

初始化:

rwlock_t mr_rwlock = RW_LOCK_UNLOCKED;

然后在读代码中:

read_lock(&mr_rwlock);

/*临界区(只读)*/

read_unlock(&mr_rwlock);

在写代码中:

write_lock(&mr_rwlock);

/*临界区(读写)*/

write_unlock(&mr_rwlock);

通常情况下,读锁和写锁应该分别放在不同的代码段中。注意,如果放一起,则要注意避免死锁。比如说不能把读锁升级为一个写锁

read_lock(&mr_rwlock);
write_lock(&mr_rwlock);

读-写自旋锁的方法列表:

在使用读-写自旋锁时要知道, 这种锁机制是优先读者的。比如说当一个读者拥有锁时,锁因为要互斥访问而进行自旋,此时读者却可以继续成功的访问共享资源,从而导致写者会饥饿。
如果加锁时间不长并且代码不会睡眠(比如中断处理程序),则利用自旋锁是最佳选择

四,信号量(一种睡眠锁)
linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时处理器可以去执行别的程序。
选择自旋锁还是信号量??
通常情况下可以根据 持锁时间长短是否需要睡眠来决定使用哪一个。还可以根据下表:
信号量:
1)计数大于1时:计数信号量。不常用。
2)计数等于1时:二值信号量或 互斥信号量常用
1,创建和初始化信号量
静态:

static DECLARE_SEMAPHORE_GENERIC(name,count);/*一般的信号量*/   
static DECLARE_MUTEX(name);/*互斥信号量*/

更常见的情况是:信号量作为一个大数据结构的一部分被动态创建。

动态:

sema_init(sem,count);
init_MUTEX(sem);

此处的命名有些不规范。

2,使用信号量。

记得以前学os课程的时候有两个操作叫p()操作和v()操作。这里对应的是down()和up()操作。

通常情况下,使用down_interruptible()更为普遍,因为:

down_interruptible()获得指定的信号量,失败后进程以TASK_INTERRUPTIBLE状态进入睡眠,可以被信号唤醒。

down()获得指定的信号量,失败后会以TASK_UNINTERRUPTIBLE状态进入睡眠,此时不可以被信号唤醒。

要释放指定的信号量,可以用up()。

大体框架如下:

static DECLARE_MUTEX(mr_sem);

if( down_interruptible(&mr_sem))
{
    /*信号量获取失败,进入等待队列*/   
}

/*临界区*/

/*释放指定的信号量*/
up(&mr_sem);


五,读写信号量

读者-写者机制使用是有条件的,只有可以自然地界定出读/写时才有价值

有读-写信号量都是互斥信号量。所有读-写锁的睡眠都不会被信号打断,所以它只是一个版本的down()操作。

静态创建:

static DECLARE_RWSEM(name);

动态创建:

init_rwsem(struct rw_semaphore *sem);

大体框架如下:

static DECLARE_RWSEM(mr_rwsem);

/*试图获取信号量用于读*/
down_read(&mr_rwsem);

/*临界区(只读)*/

/*释放信号量*/
up_read(&mr_rwsem);

/*...*/

/*试图获取信号量用于写*/
down_write(&mr_rwsem);

/*临界区(读和写)*/

up_write(&mr_rwsem);

读-写信号量相比读-写自旋锁多了一种特有的操作:downgrade_writer(),这个函数可以动态的将获取的写锁转换成读锁。


六,完成变量

如果在内核中一个任务需要发出信号通知另一个任务发生了某个特定的事件,利用完成变量可以做这个活。是使两个任务得以同步的简单方法。

完成变量由结构体completion表示,定义在<linux/completion.h>中。

创建及初始化:

静态:DECLARE_COMPLETION(mr_comp);

动态:init_completion();

在一个指定的完成变量上,需要等待的任务调用wait_for_completion()来等待特定事件。特定事件发生后,产生时间的任务调用complete()来发送信号唤醒正在等待的任务。

          A               B(等A中的complete完成)

         工作             等待:wait_for_complete()
         ...              执行工作
        complete()


七,顺序和屏障

当处理多处理器之间或硬件设备之间的同步问题时,有时需要在程序代码中以指定的顺序发出读内存或者写内存的指令。举个最简单的例子:

a=1;
b=2;

因为编译器和处理器看不出a和b之间有什么关系,也就是说认为a和b之间是独立的。这时编译器会按这种顺序进行编译,但是处理器可能会为了某种优化而进行重新动态排序。这样在a赋值为1之前,b就可能赋值为2了。

通常这种重新排序的发生是因为现代处理器为了优化其传送管道,打乱了分派和提交指令的顺序。

注意:x86处理器不会这样,但是别的处理器有的会这么做。

用来确保顺序的指令称为屏障。

rmb()提供了一个读内存屏障。它确保跨越rmb()的载入动作不会发生重新排序。

wmb()提供了一个写内存屏蔽。它确保跨越wmb()的存储不会发生重新排序。

mb()提供了读屏蔽也提供了写屏蔽。

如:

a=1;
mb();
b=2;

这样就能确保a的赋值先于b的赋值了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值