barrier 和 preempt_disable() 学习【转】

转自:http://blog.csdn.net/joker0910/article/details/7782765

[cpp] view plain copy

  1. #define preempt_disable() \
  2. do{ \
  3. inc_preempt_count(); \
  4. barrier();    \
  5. }while(0)

一、这个barrier 在干什么...

内存屏障出现因为编译器或现在的处理器常会自作聪明地对指令序列进行一些处理,比如数据缓存,读写指令乱序执行等等。如果优化对象是普通内存,那么一般会提升性能而且不会产生逻辑错误。但如果对 I/O操作进行类似优化很可能造成致命错误。所以要使用内存屏障,以强制该语句前后的指令以正确的次序完成。其实在指令序列中放一个wmb的效果是使得指令执行到该处时,把所有缓存的数据写到该写的地方,同时使得wmb前面的写指令一定会在wmb的写指令之前执行。rmb(读内存屏障)保证了屏障之前的读操作一定会在后来的读操作执行之前完成。wmb保证写操作不会乱序,mb 指令保证了两者都不会。这些函数都是 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编译器的优化还是处理器本身采用的大量优化,如Write buffer, Lock-up free, Non- blocking reading, Register allocation, Dynamic scheduling, Multiple issues等,都可能使得实际执行可能违反程序顺序,因此,引入内存屏障来保证事件的执行次序严格按程序顺序来执行。

注意,barrier()只能防止编译器对指令做乱序优化,但是不会阻止cpu的乱序执行,要真正地避免这个优化,就要使用rmb、wmb、mb一类的函数了。()

二、为什么这里只用了barrier

[cpp] view plain copy

  1. int i = 0;
  2. int a;
  3. i ++;
  4. a = i;

cpu乱序执行,并非是全乱执行,它只是对于没有依赖性的指令乱序执行。
在我上面举的这个例子中,a=i就不会在i++之前执行,因为两条指令之间有依赖,称为WAW依赖(write after write )。同样,还有RAW、WAR依赖。

所以preempt_disable中对抢占计数器加是个安全的操作,和这个计数器有关联的指令不会被乱序执行,只需要防止编译器把相关指令提前即可,用barrier足够。
那么什么时候要防止乱序呢?通常在一个块内存,既对CPU可见,又对设备可见时。举个例子:
一个结构体

[cpp] view plain copy

  1. struct dev
  2. {
  3. int enable;
  4. void *ptr;
  5. }dev;

这个结构体所处的内存,设备和CPU都可以看到。正确操作设备的顺序是先给ptr指针赋值,在对enable写1启用设备。那么,下面的代码反应了这个过程:

[cpp] view plain copy

  1. dev.ptr = buffer;
  2. dev.enable = 1;

这里的两个写操作是没有相关性的。所以CPU可以乱序执行它们。这就造成了一个情况,ptr还没赋值之前,enable就已经写1了。那么设备可能在ptr为非法值时启动执行。我们要防止这种情况,就要用内存屏障。如下:

[cpp] view plain copy

  1. dev.ptr = buffer;
  2. wmb();
  3. dev.enable = 1;

三、这个preempt_disable()

先讲下linux的调度机制,linux下有两种调度方式:

1)显式调度,进程自己因为缺少相应的所申请的资源,显式调用调度器,让出处理器,比如:内核申请的信号阻塞了,自旋锁锁住了。
2)隐式调度,整个linux系统在运行过程中的非显示的调用调度器,这又分两种情况:
    A)用户态抢占调度  比如:在系统调用,中断处理,异常处理返回用户态时,该进程的时间片已经用完。
    B)内核态抢占调度  比如:当前内核态执行过程中事先没有禁止内核态抢占,有中断产生时,中断处理 又产生了更高级优先进程,那么就会直接抢占前面的内核态执行体。

常见的调度点
1)进程被阻塞时比如申请资源时被阻塞
2)调整参数时   比如通过sched_setscheduler() ,nice()等函数调整进程的调度策略,静态优先级时
3)睡眠进程被唤醒时  比如wake_up唤醒等待队列中的进程时,如果该进程具有更高优先级则会设置当前
               进程TIF_NEED_RESCHED,如果允许内核态抢占,则会调度一次,
               ( 这是由等待队列中的默认的唤醒函数控制的,默认的唤醒函数为:
               int default_wake_function(wait_queue_t*,unisgned int  mode,int sync,void* key)
               EXPORT_SYMBOL(default_wake_function)
               因为EXPORT_SYMBOL了default_wake_function,所以我们可以制作我们自己的唤醒函数。
4)中断处理完时  如果中断处理过程中设置了TIF_NEED_SCHED标志,中断返回时,不论是要返回内核态还是用户态,都会发生一次抢占.当然,在这也会检查有没有软中断需要处理。
5)执行了preempt_enable()函数。

而我们在抢占式内核中,有三处地方需要显示的禁用抢占:
1. 操作Per-CPU变量的时候,比如smp_processor_id()就是这一类问题,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题。下面是一个例子:
        struct this_needs_locking tux[NR_CPUS];
        tux[smp_processor_id()] = some_value;
        /* task is preempted here... */
        something = tux[smp_processor_id()];
这里如果没有抢占保护的话some_value与something可能返回不同的值。当处理CPU ID时,可以考虑使用get_pcu()/put_cpu()接口,该函数对实现了禁用抢占,取得CPU ID,使能抢占的序列。算是kernel推荐的使用方法。

2. 必须保护CPU的状态。这类问题是体系结构依赖的。例如,在x86上,进入和退出FPU就是一种临界区,必须在禁抢占的情况下使用。

3. 获得和释放锁必须在一个进程中实现。也就是说一个锁被一个进程持有,也必须在这个进程中释放。

禁用/使能抢占的函数主要有:
spin_lock()/spin_unlock()
disable_preempt()/enable_preempt()(禁止或使能内核抢占)调用下面的inc_preempt_count()/dec_preempt_count(),并且加入了memory barrier。
inc_preempt_count()/dec_preempt_count()
get_cpu()/put_cpu()

相关数据结构及函数如下:
struct thread_info中
{
unisgned int preempt_count;-----(PREEMPT 0-7位表示内核态禁止抢占计数器,SOFTIRQ 8-15表示软中断禁止计数器,HARDIRQ 16-27表示中断嵌套的深度)
}
只要PREEMPT为0时才允许内核态抢占.

preempt_disable()--------------主要执行inc_preempt_count()(增加PREEMPT,从而禁止内核态抢占)
preempt_enable()--------------主要执行preempt_enable_no_resched()和preempt_check_resched()
                          preempt_enable_no_resched()主要执行dec_preempt_count()
                          preempt_check_resched()主要执行test_thread_flag(TIF_NEED_RESCHED)

时间: 2024-08-18 07:50:08

barrier 和 preempt_disable() 学习【转】的相关文章

[转载] java多线程学习-java.util.concurrent详解(一) Latch/Barrier

转载自http://janeky.iteye.com/blog/769965 Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效.易维护.结构清晰的Java多线程程序.从这篇blog起,我将跟大家一起共同学习这些新的Java多线程构件 1. CountDownLatch     我们先来学习一下JDK1.5 API中关于这个类的详细介绍: “一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个

Hasen的linux设备驱动开发学习之旅--linux设备驱动中的并发与竞态

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:linux设备驱动中的并发与竞态 * Date:2014-11-04 */ 1.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(软件上的全 局变量,静态变量等)的访问则很容易导致竞态(race conditions). 主要的竞态发生在以下几种情况: (1)对称多处理(SMP)

关于linux操作系统中进程相关问题的学习笔记

关于linux操作系统中进程相关问题的学习笔记 1.摘要   进程的经典定义是一个执行中程序的实例.系统中的每个程序都运行在某个进程的上下文中(contest)中.上下文是由程序运行正确运行所需的状态组成的.这个状态包括存放在内存中的程序的代码和数据,它的栈.通用目的寄存器的内容.程序计数器.环境变量以及打开文件描述符合的集合.在本次学习笔记中,我是以linux为例,学习了以下三个方面的知识:操作系统如何组织进程.进程状态如何转换以及进程是如何调度的.在最后我还谈了下自己对操作系统进程模型的一些

程序员的自我修养 学习笔记(1)

本文源自在学习<程序员的自我修养>中的心得体会. 对于底层系统程序开发者来说,硬件平台可以抽象为三个主要部件,CPU.内存.I/O控制器. 早期的计算机没有复杂的图形功能,CPU和内存之间的频率差异不大,它们都是连接在同一个bus上面的.其他I/O设备,诸如显示设备.键盘.磁盘等速度比内存.CPU慢很多.为了IO设备与CPU.内存之间的协调通讯,一般每个IO设备商都有相应的IO控制器,早期的硬件结构图如下: 随着技术的进步,CPU的频率越来越高,内存跟不上CPU的速度,他们之间就需要一个转换机

&lt;Java&gt;&lt;学习路线图&gt;

Java是一个通用的编程语言,其实可以干很多事,怎么学Java就看怎么用了. 但有一些一般的步骤: 1.熟悉一种文本编辑器,比如Vim, Emacs, Notepad++, TextMate等.知道哪些是开源的,哪些是闭源的,哪些要收费.养成不用盗版软件的习惯.2. 安装JDK(建议用你的Linux发行版自带的软件包管理器安装openjdk,过程中可能需要读发行版特定的文档)3. 写一个Java的Hello world程序,并用命令行工具javac编译,再用java命令运行这个程序.过程中熟悉源

linux 内核学习之八 进程调度过程分析

一  关于进程的补充 进程调度的时机 中断处理过程(包括时钟中断.I/O中断.系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule(): 内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度: 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度. 进程的切换 为了控制进程的执行,内核必须有

linux内核学习资料链接

1. 内核学习方法,编译.调试等常见问题1.1 关于编译升级内核到2.6.0的一些问题 作者:ommm        http://linux.chinaunix.net/bbs/thread-281831-1-5.html 1.2 VMWare Workstation 6.0调试Linux Kernel,竟如此方便 作者:albcamus        http://linux.chinaunix.net/bbs/thread-896214-1-5.html 1.3 基于S3C2410的Lin

Thinking in Java---Concurrent包下的新构件学习+赛马游戏仿真

Java5的java.util.concurrent包下引入了大量的用于解决并发问题的新类:相对于前面那些基础的线程同步和通信的方法,这些新类是一种更高层次上的抽象,使用起来还是比较容易的.这篇博客就来学习其中的两个新类:CountDownLatch和CyclicBarrier:并使用CyclicBarrier来模拟一个简单的赛马游戏. 一.CountDownLatch 使用CountDownLatch对象时,我们需要给其设定一个初始的计数值,然后在这个对象上调用await()的任务都会阻塞,直

Hopfield Nets 学习笔记

最近想学RBM(限制玻尔兹曼机),找来Hinton在coursera上的神经网络课程,首先复习Hopfield 网络吧. Hopfield Nets with hidden Unit https://class.coursera.org/neuralnets-2012-001/lecture/125 简单来说,就是用隐藏节点的状态来表达输入节点的信息, 举例来说,二维图像里的一条边,在三维世界里可能对应无数条边, 如果将图像上每一条可能的线段都用一个"2D-Line"单元表示,这些单元