全部博文(71)
分类: LINUX
2009-11-07 09:59:56
奔腾时间戳计数器
时间戳计数器(TSC)是奔腾处理器里面的一个64位寄存器,它对处理器启动以来所消耗的时钟周期进行计数。由于TSC以处理器周期速度增长,所有它提供了高精度定时器。TSC通常用来剖析和调试(instrument)代码。 TSC滴答可以通过除以CPU时钟速度转化为秒,CPU时钟速度可以从内核变量cpu_khz中读到。
下列代码片段中,low_tsc_ticks包含TSC的低32位,high_tsc_ticks包含高32位。低32位会在若干秒后溢出,但对于一些代码指令是足够的了:
unsigned long low_tsc_ticks0, high_tsc_ticks0;
unsigned long low_tsc_tick1, high_tsc_ticks1;
unsigned long exec_time;
rdtsc(low_tsc_ticks0, high_tsc_ticks0);
printk("hello world\n");
rdtsc(low_tsc_ticks1, high_tsc_ticks1);
exec_time = low_tsc_ticks1-low_tsc_ticks0;
内核中的并发
随着多核笔记本的到来,SMP不再是高科技用户的领地了。SMP和内核抢占是产生多线程执行的情景,线程可以同时对共享内核数据结构进行操作,由此对这些结构的访问需要串行化。
自旋锁和信号量
代码访问到的共享资源称为临界区。自旋锁和信号量是内核中最基本的两种保护临界区的机制。
自旋锁保证在某一时间里仅有一个线程可以进入临界区,任何其他想进入该临界区的线程必须在门口旋转到知道第一个线程退出。注意这里的线程是指一个执行线程,而不是内核线程。自旋锁的基本用法如下:
#include
spinlock_t mylock = SPIN_LOCK_UNLOCKED;
spin_lock(&mylock);
/*....临界区....*/
spin_unlock(&mylock);
信号量则将竞争线程放入休眠状态直到轮到它们占用临界区。因为消费处理器周期来自选是很糟糕的,信号量在等待时间长时更适合保护临界区。
在许多方面,你很容易觉得是选择使用自旋锁还是信号量:
1、如果临界区需要睡眠,你别无选择的用信号量了。在得到自旋锁后在等待队列上调度、抢占或在休眠是不合法的。
2、因为竞争关系信号量将调用线程放入睡眠,在中断处理中你只能使用自旋锁。
信号量的使用:
#include
static DEFINE_MUTEX(mymutex);
mutex_lock(&mymutex);
/*....临界区...*/
mutex_unlock(&mymutex);
mutex接口取代了旧有的semaphore接口,起源自-rt树而且并入了2.6.16内核主线发行版。 semaphore接口依然存在,基本的使用如下:
#include <asm/semaphore.h>
/*静态声明一个semaphore,动态声明请用init_MUTEX()*/
static DECLARE_MUTEX(mysem);
down(&mysem);
/*...临界区...*/
up(&mysem);
四个并发场景:
情景一:进程上下午,单CPU,非抢占
最简单情形,不需要锁。
情景二:进程上下文和中断上下文,单CPU,非抢占
此种情况下你需要禁止中断来保护临界区。假设A和B都是进程上下文中的线程,C是中断上下文线程,他们都竞争进入相同的临界区。因为C在中断上下文中执行并一直运行到结束才能让给A或B,不用担心资源保护。因此,A和B需要防护以阻止C突然闯进临界区的可能。他们通常在进入临界区之前禁止中断来实现这个:
Point A:
local_irq_disable();
/*...临界区...*/
local_irq_enable();
但是如果当达到执行点A时中断已经被禁止,local_irq_enable()让中断再次允许而不是存储中断状态字,产生不让人喜欢的边缘影响。可以通过下面代码修复:
unsigned long flags;
Point A:
local_irq_save(flags);
/*...临界区...*/
local_irq_restore(flags);
这个工作的很好不管Point A处中断状态如何。
情景三:进程和中断上下文,单CPU,抢占式内核
如果抢占允许,单单禁止中断并不会保护你的临界区。仍存在进程上下文中多线程同时进入临界区的可能性。线程之间现在需要保护它们自己。很明显解决方法是禁止/允许中断外,并在进入临界区之前禁止内核抢占然后在结束时设置允许抢占。为做到这些,线程A和线程B使用自旋锁变量irq:
unsigned long flags;
Point A:
/*保存中断状态。禁止中断,隐含禁止了抢占*/
spin_lock_irqsave(&mylock, flags);
/*...临界区...*/
/*恢复中断状态到执行点A*/
spin_unlock_irqrestore(&mylock, flags);
执行点A处的抢占状态不需显式存储,因为内核内部已经通过一个变量preemption counter为你做了。计数器每当抢占被禁止时加一(使用preempt_disable()),每当抢占被允许时减一(使用preempt_enable())。当计数器为0时才发生抢占。
情景四:进程和中断上下文,SMP机器,抢占
到目前位置,自旋锁除了使能/禁止抢占和中断上没做多少重要事。实际上的锁功能被编译出去了。在SMP中,锁逻辑却是编译进来了,来保证SMP安全。
unsigend long flags;
Point A:
/*保存本地CPU的中断状态
禁止本地CPU的中断,隐式的禁止了抢占
锁住段以控制其他CPU的访问
*/
spin_lock_irqsave(&mylock, flags);
/*...临界区...*/
spin_unlock_irqrestore(&mylock, flags);
在SMP系统中,当得到自旋锁时仅仅本地CPU中断被禁止,所以一个进程上下文线程可能在一个CPU上运行,而一个中断处理函数在另外一个CPU上执行。非本地处理器上的中断处理函数因此需要自旋等待直到本地处理器中的进程上下文中代码退出临界区。中断上下文代码调用spin_lock()/spin_unlock()做到这些:
spin_lock(&mylock);
/*...临界区...*/
spin_unlock(&mylock);
和irq变量相似,自旋锁也有下半部(BH)风味。 得到锁时spin_lock_bh()禁止下半部,但锁释放时spin_unlock_bh()使能下半部。
原子操作
原子操作用来执行轻量级的一次性完成的操作,如计数器、条件加一和设置比特位。原子操作用来保证串行化而且不需要任何锁来防止并发访问。原子操作是架构相关的。
为在释放一个内核网络缓存(称为anskbuff)前检查是否有剩余的数据被引用,net/core/skbuff.c中的skb_release_data()这样做的:
if (!skb->cloned || /*原子减一并且检查返回值是否为0*/
!atomic_sub_return(skb->nohdr?(1<
/*....*/
kfree(skb->head);
}
当skb_release_data()执行时,另一个线程使用skbuff_clone()(定义在同一文件中)可能会同时增加引用计数器:
/*....*/
atomic_inc(&(skb_shinfo(skb)->dataref));
/*.....*/
原子操作的使用可防止数据引用计数器被两个线程弄坏(trample),它也可以消除使用锁的争论来保护一个单个整数变量被并发访问到。
内核也支持set_bit()、clear_bit()和test_and_set_bit()来原子性参与位操作。参看include/asm-you-arch/atomic.h得到你的架构所支持的原子操作。