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

Linux 内核同步机制

2011-09-19 14:16 363 查看
1.概述

对于共享资源,要防止多个线程并发访问。如果多个线程并发的访问共享资源,那么可能出现覆盖共享数据的情况,造成共享数据状态不一致的现象。临界区是指访问和操作共享数据的代码段,当多个执行线程并发的访问同一个资源通常是不安全的,为了为了避免并发访问,必须保证这些代码原子的执行。

内核有可能造成并发执行的原因:

中断:中断可以随时发生,打断当前执行的代码。

软中断或tasklet:内核在执行软中断的时候,也有可能打断当前正在执行的代码。

内核抢占:因为内核具有抢占性,所以内核中的任务可能会被另一个任务抢占。

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

对称处理器:两个或者多个处理器可以同时执行代码。

2.内核同步的方法

内核同步的方法主要有原子操作,自旋锁,信号量与completions机制。

(1)自旋锁

Linux最常用的加锁机制是自旋锁,自旋锁最多可以被一个执行线程持有,当一个执行线程得到自旋锁时,其它的自旋锁只能一直进行循环等待,直到这个锁可用。自旋锁只是一直的在循环等待,而不会引起进程的睡眠。

自旋锁是不可递归的: 这指的是如果一个执行线程已经获得了自旋锁,此时,它又试图去获得自己已经得到的锁,等待自己释放这个锁,但由于处于自旋忙等待当中,所以永远没有机会释放锁,于是自己被死锁了。

双重请求死锁: 自旋锁可以在中断处理程序中使用,此处不能用信号量,因为信号量可能引起睡眠,在中断处理程序中使用自旋锁,一定要注意,在获得锁之前,首先要禁止本地中断,否则,中断处理程序会打断当前正在持有锁的内核代码,有可能试图争用这个已经被持有的自旋锁。但由于锁有持有者在这个中断处理程序完成之前不会执行。所以会出现双重请求死锁。

锁什么?

锁真正保护的是数据而不是代码,针对代码加锁会使程序难以理解,容易引起竞争条件。正确的做法应该对数据而不是对代码加锁。

自旋锁与下半部:

由于下半部可以抢占进程上下文代码,所以当下半部与进程上下文共享数据时,必须对进程上下文中的共享数据进行保护,所以需要加锁的同时还要禁止下半部的执行。同样,由于中断处理程序可以抢占下半部,所以如果中断处理程序与下半部共享数据时,那么获得锁之后还要禁止中断。

同类的tasklet不可能同时运行,所以对同类的tasklet中的共享数据不需要保护。但数据被不同的tasklet共享时,就需要在访问下半部时先获得一个普通的自旋锁。这里不需要禁止tasklet,因为同一个处理器上决不会有tasklet相互抢占。

对于软中断,无论是否是同种类型,如果数据被软中断共享,那么必须加锁保护。这是因为即使是同种类型的两个软中断也可以运行在一个系统的多个处理器上。但是,同一个处理器上的一个软中断也不会抢占另外一个软中断,因此,根本没必要禁止下半部。

(2).信号量

Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其去睡眠。这样,处理器会执行其它的代码,就如同让出处理器。

信号量的睡眠特性中的一些结论:

(1)由于争用信号量的进程在等待锁时得重新可用时会睡眠,所以信号量适用于锁被长时间持有的情况。如果是锁被短时间持有,那么睡眠,维护等待队列以及唤醒花费的开销可能比锁被占用的全部时间还会长。

(2)信号量只能在进程上下文中使用,而不能在中断上下文中使用,由于中断上下文中没有进程的概念,所以是不能调度的。

(3)在持有信号量的时候,不能持有自旋锁,因为信号量可能引起睡眠,而自旋锁是不会睡眠的。

信号量可以被任意数量的线程持有,而自旋锁在一个时刻最多允许一个任务持有它。信号量同时允许持有者的数量在声明时指定。

(3)完成变量

如果内核中一个任务需要发出信号通知另一个任务发生了某个特定的事件,利用完成变量使得两个任务同步。

init_completion(struct completion*)初始化动态创建的完成变量

wait_for_completion(struct completion*)等待指定的完成变量接受信号

complete(struct completion*)发信号唤醒任何等待任务

3.自旋锁与信号量的比较

在中断上下文中只能使用自旋锁,而在任务睡眠时只能使用信号量。

低开销加锁和短期加锁优先使用自旋锁,而长期加锁,优先使用信号量。

4.自旋锁与信号量的接口函数

信号量:

定义信号量:

struct semaphore sem;

初始化信号量:

void sema_init(struct semaphore *sem,int val);

该函数初始化信号量,将设置信号量的sem值为val.

void init_MUTEX(struct semaphore *sem);

该函数用于初始化一个互斥锁,将信号量的初始值设置为1.相当于sema_init(struct semaphore* sem,1);

void init_MUTEX_LOCKED(struct semaphore*sem); 也是用来初始化一个互斥锁,相当于把信号量的值设置为0,相当于

void sema_init(struct semaphore* sem,0);

获得信号量:

void down(struct semaphore*sem);

int down_interruptible(struct semaphore*sem)

试图去获取指定的信号量,如果获取失败,以TASK_INTERRUPTIBLE状态睡眠。进程的这个状态可以被信号唤醒。

int down_trylock(struct semaphore* sem) 如果信号量被占用时,立刻返回非0值。否则得到信号量返回0。

void up(struct semaphore* sem)释放信号量。

自旋锁:

定义自旋锁:

spinlock_t spin

初始化自旋锁:

spin_lock_init(lock)

该宏用于动态初始化自旋锁

也可以在编译时初始化自旋锁:

spinlock_t spin=SPIN_LOCK_UNLOCKED;

获得自旋锁:

spin_lock(lock)

该宏用于获得自旋锁lock,如果能够立即获得自旋锁,它就马上返回,否则,它就自旋在那里,直到自旋锁的保持者释放。

spin_trylock(lock)

该宏用于获得自旋锁lock,如果获得就马上返回真,否则,立即返回假,实际上不再"原地打转"

释放自旋锁:

spin_unlock(lock)

关于Linux下的内核同步机制就介绍到这里了。

转自:http://blog.csdn.net/chenjin_zhong/article/details/6285520
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: