- #define
preempt_disable() \ - do{
\ -
inc_preempt_count(); \ -
barrier(); \ - }while(0)
一、这个barrier 在干什么...
这些函数在已编译的指令流中插入硬件内存屏障;具体的插入方法是平台相关的。
关于barrier()宏实际上也是优化屏障:
#define barrier()__asm__ __volatile__("": : :"memory")
CPU越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句实际上不生成任何代码,但可使gcc在barrier()之后刷新寄存器对变量的分配。
1)set_mb(),mb(),barrier()函数追踪到底,就是__asm____volatile__("":::"memory"),而这行代码就是内存屏障。
2)__asm__用于指示编译器在此插入汇编语句
3)__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
4)memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
5)"":::表示这是个空指令。barrier()不用在此插入一条串行化汇编指令。
6)__asm__,__volatile__,memory在前面已经解释。
不论是gcc编译器的优化还是处理器本身采用的大量优化,如Writebuffer, Lock-up free, Non- blocking reading, Register allocation,Dynamic scheduling, Multipleissues等,都可能使得实际执行可能违反程序顺序,因此,引入内存屏障来保证事件的执行次序严格按程序顺序来执行。
注意,barrier()只能防止编译器对指令做乱序优化,但是不会阻止cpu的乱序执行,要真正地避免这个优化,就要使用rmb、wmb、mb一类的函数了。()
二、为什么这里只用了barrier
cpu乱序执行,并非是全乱执行,它只是对于没有依赖性的指令乱序执行。
在我上面举的这个例子中,a=i就不会在i++之前执行,因为两条指令之间有依赖,称为WAW依赖(write after write)。同样,还有RAW、WAR依赖。
所以preempt_disable中对抢占计数器加是个安全的操作,和这个计数器有关联的指令不会被乱序执行,只需要防止编译器把相关指令提前即可,用barrier足够。
那么什么时候要防止乱序呢?通常在一个块内存,既对CPU可见,又对设备可见时。举个例子:
一个结构体
三、这个preempt_disable()
先讲下linux的调度机制,linux下有两种调度方式:
1) 显式调度 , 进程自己因为缺少相应的所申请的资源 , 显式调用调度器 , 让出处理器 , 比如 : 内核申请的信号阻塞了 , 自旋锁锁住了。2) 隐式调度,整个linux系统在运行过程中的非显示的调用调度器,这又分两种情况:
1)进程被阻塞时比如申请资源时被阻塞
2)调整参数时
3)睡眠进程被唤醒时
4)中断处理完时
5)执行了preempt_enable()函数。
而我们在抢占式内核中,有三处地方需要显示的禁用抢占:
1.操作Per-CPU变量的时候,比如smp_processor_id()就是这一类问题,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题。下面是一个例子:
这里如果没有抢占保护的话some_value与something可能返回不同的值。当处理CPUID时,可以考虑使用get_pcu()/put_cpu()接口,该函数对实现了禁用抢占,取得CPUID,使能抢占的序列。算是kernel推荐的使用方法。
2.必须保护CPU的状态。这类问题是体系结构依赖的。例如,在x86上,进入和退出FPU就是一种临界区,必须在禁抢占的情况下使用。
3.获得和释放锁必须在一个进程中实现。也就是说一个锁被一个进程持有,也必须在这个进程中释放。
禁用/使能抢占的函数主要有:
spin_lock()/spin_unlock()
disable_preempt()/enable_preempt()(禁止或使能内核抢占)调用下面的inc_preempt_count()/dec_preempt_count(),并且加入了memorybarrier。
inc_preempt_count()/dec_preempt_count()
get_cpu()/put_cpu()
相关数据结构及函数如下 :
struct thread_info 中
{
unisgned int preempt_count;-----(PREEMPT 0-7 位表示内核态禁止抢占计数器,SOFTIRQ8-15表示软中断禁止计数器,HARDIRQ16-27表示中断嵌套的深度 )
}
只要 PREEMPT 为 0 时才允许内核态抢占 .
preempt_disable()-------------- 主要执行 inc_preempt_count()( 增加 PREEMPT, 从而禁止内核态抢占 )
preempt_enable()-------------- 主要执行 preempt_enable_no_resched() 和 preempt_check_resched()