barrier 和 preempt_disable() 学习

2023-12-12 12:18
文章标签 学习 disable barrier preempt

本文主要是介绍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越过内存屏障后,将刷新自己对存储器的缓冲状态。这条语句实际上不生成任何代码,但可使gccbarrier()之后刷新寄存器对变量的分配。

1set_mb(),mb(),barrier()函数追踪到底,就是__asm__ __volatile__("":::"memory"),而这行代码就是内存屏障。
2__asm__用于指示编译器在此插入汇编语句
3__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
4memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registerscache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registerscache中的数据用于去优化指令,而避免去访问内存。
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_SYMBOLdefault_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_valuesomething可能返回不同的值。当处理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 和 preempt_disable() 学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/484623

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件