• Linux 同步管理(上)


    因为现代操作系统是多处理器计算的架构,必然更容易遇到多个进程,多个线程访问共享数据的情况,如下图所示:

    图中每一种颜色代表一种竞态情况,主要归结为三类:

    1. 进程与进程之间:单核上的抢占,多核上的SMP

    2. 进程与中断之间:中断又包含了上半部与下半部,中断总是能打断进程的执行流;

    3. 中断与中断之间:外设的中断可以路由到不同的CPU上,它们之间也可能带来竞态;

    这时候就需要一种同步机制来保护并发访问的内存数据。文章分为两部分,这一章主要讨论原子操作,自旋锁,信号量和互斥锁。

    原子操作

    原子操作是在执行结束前不可打断的操作,也是最小的执行单位。以 arm 平台为例,原子操作的 API 包括如下:

    API说明
    int atomic_read(atomic_t *v)读操作
    void atomic_set(atomic_t *v, int i)设置变量
    void atomic_add(int i, atomic_t *v)增加 i
    void atomic_sub(int i, atomic_t *v)减少 i
    void atomic_inc(atomic_t *v)增加 1
    void atomic_dec(atomic_t *v)减少 1
    void atomic_inc_and_test(atomic_t *v)加 1 是否为 0
    void atomic_dec_and_test(atomic_t *v)减 1 是否为 0
    void atomic_add_negative(int i, atomic_t *v)加 i 是否为负
    void atomic_add_return(int i, atomic_t *v)增加 i 返回结果
    void atomic_sub_return(int i, atomic_t *v)减少 i 返回结果
    void atomic_inc_return(int i, atomic_t *v)加 1 返回
    void atomic_dec_return(int i, atomic_t *v)减 1 返回

    原子操作通常是内联函数,往往是通过内嵌汇编指令来实现的,如果某个函数本身就是原子的,它往往被定义成一个宏。

    可见原子操作的原子性依赖于 ldrex 与 strex 实现,ldrex 读取数据时会进行独占标记,防止其他内核路径访问,直至调用 strex 完成写入后清除标记。

    ldrex 和 strex 指令,是将单纯的更新内存的原子操作分成了两个独立的步骤:

    1. ldrex 用来读取内存中的值,并标记对该段内存的独占访问:                                                  
      ldrex Rx, [Ry]
       读取寄存器 Ry 指向的4字节内存值,将其保存到 Rx 寄存器中,同时标记对 Ry 指向内存区域的独占访问。如果执行 ldrex 指令的时候发现已经被标记为独占访问了,并不会对指令的执行产生影响。
    2. strex 在更新内存数值时,会检查该段内存是否已经被标记为独占访问,并以此来决定是否更新内存中的值:                                                                                                                
      strex Rx, Ry, [Rz] 
       如果执行这条指令的时候发现已经被标记为独占访问了,则将寄存器 Ry 中的值更新到寄存器 Rz 指向的内存,并将寄存器 Rx 设置成 0。指令执行成功后,会将独占访问标记位清除。如果执行这条指令的时候发现没有设置独占标记,则不会更新内存,且将寄存器 Rx 的值设置成 1。

    自旋锁 spin_lock

    Linux内核中最常见的锁是自旋锁,自旋锁最多只能被一个可执行线程持有。如果一个线程试图获取一个已被持有的自旋锁,这个线程会进行忙循环——旋转等待(会浪费处理器时间)锁重新可用。自旋锁持有期间不可被抢占。

    另一种处理锁争用的方式:让等待线程睡眠,直到锁重新可用时再唤醒它,这样处理器不必循环等待,可以去执行其他代码,但是这会有两次明显的上下文切换的开销,信号量便提供了这种锁机制。

    自旋锁的使用接口如下:

    API说明
    spin_lock()获取指定的自旋锁
    spin_lock_irq()禁止本地中断并获取指定的锁
    spin_lock_irqsave()保存本地中断当前状态,禁止本地中断,获取指定的锁
    spin_unlock()释放指定的锁
    spin_unlock_irq()释放指定的锁,并激活本地中断
    spin_unlock_irqrestore()释放指定的锁,并让本地中断恢复以前状态
    spin_lock_init()动态初始化指定的锁
    spin_trylock()试图获取指定的锁,成功返回0,否则返回非0
    spin_is_locked()测试指定的锁是否已被占用,已被占用返回非0,否则返回0
  • 相关阅读:
    前后端开发环境下载,java web前后端分离项目所有环境下载
    Java抽象类和接口
    CCF CSP认证 历年题目自练Day19
    关于业务库从MySQL迁移到DM8的操作指南
    【云原生 | 从零开始学Kubernetes】十六、k8s核心技术-Deployment深入使用
    Kotlin 开发Android app(七)上:Kotlin函数fun
    AI大模型:大数据+大算力+强算法
    Java反射获取内部类方法
    HarmonyOS4.0系统性深入开发34栅格布局(GridRow/GridCol)
    【SVM分类】基于matlab哈里斯鹰算法优化支持向量机SVM分类【含Matlab源码 2243期】
  • 原文地址:https://blog.csdn.net/lx123010/article/details/128206906