转自:http://blog.chinaunix.net/uid-21961753-id-1810628.html

今天看内核发现disable_preempt这个函数,觉得挺有意思就看了下网上资料,以下我将之整理成了两个函数来加以理解。

一、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在前面已经解释

例1:

1        int a = 5, b = 6;

2        barrier();

3        a = b;

在line 3,GCC不会用存放b的寄存器给a赋值,而是重新读内存中的b值,赋值给a。

例2:

它在进程上下文中将一个元素插入一个单向链表:

new->next=i->next;

wmb();

i->next=new;

同时,如果不加锁地遍历这个单向链表。或者在遍历链表时已经可以看到new,或者new还不在该链表中。两个内存写

事件的顺序必须按照程序顺序进行。否则可能new的next指针将指向一个无效地址,就很可能出现 OOPS!

不论是gcc编译器的优化还是处理器本身采用的大量优化,如Write buffer, Lock-up free, Non- blocking reading, Register allocation, Dynamic scheduling, Multiple issues 等,都可能使得实际执行可能违反程序顺序,因此,引入内存屏障来保证事件的执行次序严格按程序顺序来执行。

使用内存屏障强加的严格的CPU内存事件次序,保证程序的执行看上去象是遵循顺序一致性模型。在当前的实现中,wmb() 实际上是一个空操作,这是因为目前Intel的CPU系列都遵循“处理机一致性”,所有的写操作是遵循程序顺序的,不会越过前面的读写操作。但是,由于 Intel CPU系列可能会在将来采用更弱的内存一致性模型并且其他体系结构可能采用其他放松的一致性模型,仍然在内核里必须适当地插入wmb()保证内存事件的正确次序。

二、disable_preempt函数

先讲下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)
                                (是否设置了需要调度的标志)和preempt_schedule()(进行内核态抢占调度)

barrier()函数的更多相关文章

  1. GCD中的dispatch_barrier_async函数的使用(栅栏函数)

    <一>什么是dispatch_barrier_async函数 毫无疑问,dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它 ...

  2. barrier 和 preempt_disable() 学习【转】

    #define preempt_disable() \ do{ \ inc_preempt_count(); \ barrier();    \ }while(0) 一.这个barrier 在干什么. ...

  3. Linux Barrier I/O 实现分析与barrier内存屏蔽 总结

    一直以来.I/O顺序问题一直困扰着我.事实上这个问题是一个比較综合的问题,它涉及的层次比較多,从VFS page cache到I/O调度算法,从i/o子系统到存储外设.而Linux I/O barri ...

  4. 4.3 多线程进阶篇<中>(GCD)

    更正:队列名称的作用的图中,箭头标注的有些问题,已修正 本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书” 本文源码 Demo 详见 Gith ...

  5. linux线程同步(5)-屏障

    一.概述                                                    barrier(屏障)与互斥量,读写锁,自旋锁不同,它不是用来保护临界区的.相反,它跟条 ...

  6. opencl-Shader

    转载自:http://blog.csdn.net/leonwei/article/details/8956632 这里介绍关于OpenCL中program函数的写法,program函数通常是文本形式的 ...

  7. iOS 中多线程的简单使用

    iOS中常用的多线程操作有( NSThread, NSOperation GCD ) 为了能更直观的展现多线程操作在SB中做如下的界面布局: 当点击下载的时候从网络上下载图片: - (void)loa ...

  8. IOS任务管理之GCD使用

    前言: 前天学了IOS的NSOperation基本使用,我们得知NSOperation也是基于IOS GCD(Grand Central Dispatch)实现,其实在做IOS开发中GCD已经基本上能 ...

  9. iOS多线程——GCD

    最近的项目遇到了很多多线程的问题,借此机会对GCD进行了一番学习并总结.首先说一下什么是GCD,GCD全称 Grand Central Dispatch,是异步执行任务的技术之一.开发者只需要定义想要 ...

随机推荐

  1. MySQL里面的子查询实例

    一,子选择基本用法 1,子选择的定义 子迭择允许把一个查询嵌套在另一个查询当中.比如说:一个考试记分项目把考试事件分为考试(T)和测验(Q)两种情形.下面这个查询就能只找出学生们的考试成绩 selec ...

  2. 在ECSHOP后台左侧导航中增加新菜单

    在ECSHOP后台左侧导航中增加新菜单 ECSHOP教程/ ecshop教程网(www.ecshop119.com) 2011-11-08   有个别高级用户(懂PHP的),提到这样的问题: 在后台管 ...

  3. Protocol Buffer技术详解(数据编码)

    Protocol Buffer技术详解(数据编码) 之前已经发了三篇有关Protocol Buffer的技术博客,其中第一篇介绍了Protocol Buffer的语言规范,而后两篇则分别基于C++和J ...

  4. memcached安装和php-memcached扩展安装.update.2014-08-15

    服务器端主要是安装memcache服务器端,目前的最新版本是 memcached-1.3.0 .下载官网:http://www.danga.com另外,Memcache用到了libevent这个库用于 ...

  5. python购物&常用字符处理方法

    python 一个购物车的例子 1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 '''购物车''' 4 5 goods = [ 6 7 {&quo ...

  6. SpringMVC 返回JSON数据

    首先添加json包

  7. (转)win32Application和win32ApplicationConsole

    这几天在创建MFC项目时,常常遇到一下两个连接错误,例如: 1. LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _ma ...

  8. 微信稳居Android App排行榜4月份国内榜首

    根据某机构通过对Android样本访问行为的持续监测数据进行样本属性加权并根据iOS/Android用户调研数据建模推总得出中国移动互联网用户规模以及相应的用户结构数据显示,2015年4月份国内And ...

  9. LR 测试数据库总结

    今天工作中需要对mysql进行性能测试 我尝试用LR来做:但是mysql需要现在电脑上安装一个OBDC的mysql驱动器,然后在电脑的管理工具中的数据源中加入这个mysql驱动,测试连接数据库成功,O ...

  10. 淘宝(阿里百川)手机客户端开发日记第四篇 自定义ListView详解

    我们知道,如果采用官方的ListView,实现的功能在很多时候,并不能满足自己的业务需求,比如在设计到复杂的列表的时候,这一节,我们就开始动手自己实现自定义的ListView. 在上一节中,我们采用了 ...