barrier 和 preempt_disable() 学习【转】
- #define preempt_disable() \
- do{ \
- inc_preempt_count(); \
- barrier(); \
- }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
- int i = 0;
- int a;
- i ++;
- a = i;
cpu乱序执行,并非是全乱执行,它只是对于没有依赖性的指令乱序执行。
在我上面举的这个例子中,a=i就不会在i++之前执行,因为两条指令之间有依赖,称为WAW依赖(write after write )。同样,还有RAW、WAR依赖。
所以preempt_disable中对抢占计数器加是个安全的操作,和这个计数器有关联的指令不会被乱序执行,只需要防止编译器把相关指令提前即可,用barrier足够。
那么什么时候要防止乱序呢?通常在一个块内存,既对CPU可见,又对设备可见时。举个例子:
一个结构体
- struct dev
- {
- int enable;
- void *ptr;
- }dev;
这个结构体所处的内存,设备和CPU都可以看到。正确操作设备的顺序是先给ptr指针赋值,在对enable写1启用设备。那么,下面的代码反应了这个过程:
- dev.ptr = buffer;
- dev.enable = 1;
这里的两个写操作是没有相关性的。所以CPU可以乱序执行它们。这就造成了一个情况,ptr还没赋值之前,enable就已经写1了。那么设备可能在ptr为非法值时启动执行。我们要防止这种情况,就要用内存屏障。如下:
- dev.ptr = buffer;
- wmb();
- 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)
barrier 和 preempt_disable() 学习【转】的更多相关文章
- Linux内核编程、调试技巧小集
1. 内核中通过lookup_symbol_name获取函数名称 内核中很多结构体成员是函数,有时可能比较复杂不知道具体使用哪一个函数.这是可以通过lookup_symbol_name来获取符号表名称 ...
- Linux内核编程、调试技巧小集【转】
转自:https://www.cnblogs.com/arnoldlu/p/7152488.html 1. 内核中通过lookup_symbol_name获取函数名称 内核中很多结构体成员是函数,有时 ...
- [转载] java多线程学习-java.util.concurrent详解(一) Latch/Barrier
转载自http://janeky.iteye.com/blog/769965 Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可 ...
- 《Linux内核分析》第八周学习笔记
<Linux内核分析>第八周学习笔记 进程的切换和系统的一般执行过程 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163 ...
- 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
- 内存屏障 & Memory barrier
Memory Barrier http://www.wowotech.net/kernel_synchronization/memory-barrier.html 这里面讲了Memory Barrie ...
- linux 内核学习之八 进程调度过程分析
一 关于进程的补充 进程调度的时机 中断处理过程(包括时钟中断.I/O中断.系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule() ...
- BigPipe学习研究
BigPipe学习研究 from: http://www.searchtb.com/2011/04/an-introduction-to-bigpipe.html 1. 技术背景 FaceBook ...
- Effective Objective-C 2.0 学习记录
由于最近入职,公司安排自由学习,于是有时间将Effective Objective-C 2.0一书学习了一遍.由于个人知识面较窄,对于书中有些内容无法理解透彻,现将所学所理解内容做一遍梳理,将个人认为 ...
随机推荐
- lambda 分组练习
public partial class Form1 : Form { public Form1() { InitializeComponent(); } List<Person> per ...
- 了解Solr
Solr 简 介 采用Java开发,基于Lucene的全文搜索服务器.同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置.可扩展并对查询性能进行了优化,并且提供了一个完善的功 ...
- Version
题目 有三个操作: \(change \ u \ v \ a \ b\) : \(u\)到\(v\)路径上的点点权加上\(a+k*b\),\(k\)为第几个点,\(u\)为第0个点. \(query ...
- 《转》vue-cli的webpack模板项目配置文件注释
一.文件结构 本文主要分析开发(dev)和构建(build)两个过程涉及到的文件,故下面文件结构仅列出相应的内容. ├─build │ ├─build.js │ ├─check-versions.js ...
- CC DGCD:Dynamic GCD——题解
https://vjudge.net/problem/CodeChef-DGCD https://www.codechef.com/problems/DGCD 题目大意: 给一颗带点权的树,两个操作: ...
- LOJ6276:果树——题解
https://loj.ac/problem/6276#submit_code NiroBC 姐姐是个活泼的少女,她十分喜欢爬树,而她家门口正好有一棵果树,正好满足了她爬树的需求.这颗果树有N 个节点 ...
- 项目管理---git----快速使用git笔记(六)------本地开发与远程仓库的交互----常用git命令
无论是我们自己把本地的项目新建了一个远程仓库 还是 从远程仓库获取到了 本地,现在我们都在本地有了一份项目代码,服务器上对应有项目代码的信息. 现在我们就开始进行交互操作了. 也就是说明一些在 正常开 ...
- idea导入web项目tomcat
概述 主要分为项目配置和tomcat配置两大步骤. 一.项目配置 打开idea,选择导入项 选择将要打开的项目路径后,继续选择项目的原本类型(后续引导设置会根据原本的项目类型更新成idea的项目),此 ...
- nfs挂载权限问题
问题: 服务器A:192.168.10.230 服务器B:192.168.10.231 由于服务器A空间不足,打算将服务器A产生的数据库日志挂载到服务器B上,刚开始设定的anonuid和anongid ...
- Caused by: java.io.IOException: Filesystem closed的处理
org.apache.hadoop.hive.ql.metadata.HiveException: Unable to rename output from: hdfs://nameservice/u ...