Linux中的Context与同步

Linux中的Context (上下文)

基于ARM64

Context 上下文:

上下文的概念:https://en.wikipedia.org/wiki/Context_(computing)
简单来说,上下文就是程序运行的环境,在linux中,由于各种不同的执行环境,诞生了各种不同的名词用来形容上下文。

硬件相关的执行环境

ARM64 exception level

ARM64 Exception level

上面每种颜色的方框都代表一种执行环境。

  • Linux 内核运行于Normal world EL1
  • 目前手机上在EL1的Normal world只运行了一个Guest OS,Linux
  • Secure world运行的镜像是trustzone app, trustzone操作系统
  • EL2 目前用作虚拟化
  • EL3 执行资源保护的检查
    [//]: 上图仅适用于ARM64 i.e.AARCH64

Linux中的上下文

1. hardirq 硬中断 (上半部)

  1. 硬件中断中执行,发生中断后直接跳转到异常向量表执行
  2. 不同的中断之间可以相互打断(待看ARM文档确认)
  3. 同一个中断,同一时刻只会在同一个CPU上运行(IPI等中断)
  4. hardirq是硬件上的限制,linux为了提高系统响应速度,"造"出来一个软中断(将费事操作放进软中断中,从而保证硬中断的实时性)。

2. softirq 软中断 (下半部)

  1. 在硬中断执行完成返回时,判断是否有软中断事物需要执行,如果有,就跳转执行
  2. 可以中断除了硬中断之外的其他上下文
  3. 共有以下几种级别
enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    IRQ_POLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the
                numbering. Sigh! */
    RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

    NR_SOFTIRQS
};
  1. 软中断个数,以及实现函数在编译时确定,运行时不能动态注册
  2. irq_handle->gic_handle_irq()->handle_domain_irq()->irq_exit() -> invoke_softirq()
    执行顺序:按照顺序一个一个执行
  3. 同一个softirq可以在多个cpu core上同时执行,所以需要加锁同步

3. tasklet (dynamic registed softirq)

  1. 利用软中断实现的可以动态注册的底半部机制
  2. 两个softirq等级
  • tasklet_vec -------- TASKLET_SOFTIRQ --- tasklet_action
  • tasklet_hi_vec ------- HI_SOFTIRQ ------ tasklet_hi_action
  1. 由于每个动态注册的tasklet处理函数只会加入到一个softirq中,故同一个tasklet只会在同一个cpu core中执行,不会同时调度到多个cpu core中执行

4. atomic 原子上下文

  1. 从命名中可以知道,使用这种上下文是为了形成原子性的操作(将一系列操作打包成一个操作,不允许别的操作来打断)
  2. 在进程上下文中调用了spinlock, 禁止抢占,以及关闭中断的函数之后就属于原子上下文
  3. 原子上下文中不可以调用能使进程休眠的函数
    1.上述的硬中断,softirq,tasklet都属于原子上下文
    1.原子上下文中均不允许放弃cpu,从原子上下文来看,本就是为了保证不被特定上下文抢占,在原子上下文sleep,本就违反了设计原则,会被内核防呆函数直接拦截。

5. 进程上下文

  1. 普通的内核线程,普通进程,由于调度器的cpu分配,可以在运行时随时被抢占。

Linux 同步技术

为了实时性,linux搞出来如此几种上下文,最终,为了程序能正确无误的运行,linux设计了各种锁,以及规则来解决同步问题。

Unreliable Guide To Locking

各种上下文中加锁方法

IRQ Handler A IRQ Handler B Softirq A Softirq B Tasklet A Tasklet B Timer A Timer B User Context A User Context B
IRQ Handler A None
IRQ Handler B SLIS None
Softirq A SLI SLI SL
Softirq B SLI SLI SL SL
Tasklet A SLI SLI SL SL None
Tasklet B SLI SLI SL SL SL None
Timer A SLI SLI SL SL SL SL None
Timer B SLI SLI SL SL SL SL SL None
User Context A SLI SLI SLBH SLBH SLBH SLBH SLBH SLBH None
User Context B SLI SLI SLBH SLBH SLBH SLBH SLBH SLBH MLI None
Table: Table of Locking Requirements
NAME lock_type
SLIS spin_lock_irqsave
SLI spin_lock_irq
SL spin_lock
SLBH spin_lock_bh
MLI mutex_lock_interruptible

Linux中各种同步方法

原子变量

  1. 原子操作是各种锁的基石
  2. 原子操作在同步中的作用
多个core之间竞争
多个进程之间竞争(why ?)
Instance 1 Instance 2
read very_important_count (5)
read very_important_count (5)
add 1 (6)
add 1 (6)
write very_important_count (6)
write very_important_count (6)

ARM64实现:
ARMv7-A and ARMv8-A architectures both provide support for exclusive memory accesses.
In A64, this is the Load/Store exclusive (LDXR/STXR) pair.

crash64> dis _raw_spin_lock
0xffffff8f41b5e4f8 <__cpuidle_text_end>:        mrs     x2, sp_el0
0xffffff8f41b5e4fc <_raw_spin_lock+4>:  ldr     w1, [x2,#16]
0xffffff8f41b5e500 <_raw_spin_lock+8>:  add     w1, w1, #0x1
0xffffff8f41b5e504 <_raw_spin_lock+12>: str     w1, [x2,#16]
0xffffff8f41b5e508 <_raw_spin_lock+16>: prfm    pstl1strm, [x0]
0xffffff8f41b5e50c <_raw_spin_lock+20>: ldaxr   w1, [x0] //独占的load
0xffffff8f41b5e510 <_raw_spin_lock+24>: add     w2, w1, #0x10, lsl #12
0xffffff8f41b5e514 <_raw_spin_lock+28>: stxr    w3, w2, [x0] //独占的store
0xffffff8f41b5e518 <_raw_spin_lock+32>: cbnz    w3, 0xffffff8f41b5e50c  //如果w3不是0,说明有其他的设备访问过[x0],这次的读改写操作需要重新开始
0xffffff8f41b5e51c <_raw_spin_lock+36>: eor     w2, w1, w1, ror #16
0xffffff8f41b5e520 <_raw_spin_lock+40>: cbz     w2, 0xffffff8f41b5e538
0xffffff8f41b5e524 <_raw_spin_lock+44>: sevl
0xffffff8f41b5e528 <_raw_spin_lock+48>: wfe
0xffffff8f41b5e52c <_raw_spin_lock+52>: ldaxrh  w3, [x0]
0xffffff8f41b5e530 <_raw_spin_lock+56>: eor     w2, w3, w1, lsr #16
0xffffff8f41b5e534 <_raw_spin_lock+60>: cbnz    w2, 0xffffff8f41b5e528
0xffffff8f41b5e538 <_raw_spin_lock+64>: ret

Memory barrier

  1. Data Memory Barrier (DMB). This forces all earlier-in-program-order memory accesses to become globally visible before any subsequent accesses. 会强制化使所有对内存的操作可以被下边的指令可见
  2. Data Synchronization Barrier (DSB). All pending loads and stores, cache maintenance instructions, and all TLB maintenance instructions, are completed before program execution continues. A DSB behaves like a DMB, but with additional properties. 加入了更多的tlb,cache相关flush操作,比DMB更强力
  3. Instruction Synchronization Barrier (ISB). This instruction flushes the CPU pipeline and prefetch buffers, causing instructions after the ISB to be fetched (or re-fetched) from cache or memory. flush流水线,重新装载流水线指令缓存。

例子:

LDR X0, [X3]
LDNP X2, X1, [X0] // Xo may not be loaded when the instruction executes!
To correct the above, you need an explicit load barrier:
LDR X0, [X3]
DMB nshld
LDNP X2, X1, [X0]

spinlock

crash64> whatis arch_spinlock_t
typedef struct {
    u16 owner;
    u16 next;
} arch_spinlock_t;

spinlock通过两个域实现,防止多cpu竞争而导致活锁
通过汇编实现以提高性能
Linux内核同步机制之(四):spin lock

C代码(仅参考实现,有些应该原子操作的各位看官自己心里有数即可):

_raw_spin_lock(arch_spinlock_t *lock){
    arch_spinlock_t local_lock;
    local_lock = *lock;
    lock.next++;
retry:
    if (lock.owner == local_lock.next)
        return ;
    wfe;
    goto retry;
}

rwlock

typedef struct {
    volatile unsigned int lock;
} arch_rwlock_t;

同步原语

  • 同时可以有多个执行体一起执行读操作
  • 一次只能有一个执行体执行写操作
  • 写操作必须等待读操作完成

lock值定义

31 30 0
Write Thread Counter Read Thread Counter

Linux中常见同步机制设计原理

seqlock

typedef struct {
    struct seqcount seqcount;
    spinlock_t lock;
} seqlock_t;

typedef struct seqcount {
    unsigned sequence;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map dep_map;
#endif
} seqcount_t;

同步原语

  • 同时可以有多个执行体一起执行读操作
  • 一次只能有一个执行体执行写操作
  • 写操作无需等待读操作完成
  • 如果读操作被打断,需要重新开始读

seqcount值初始化为0,偶数代表有读者正在持有锁

void read(void)
{
       bool x, y;

       do {
               int s = read_seqcount_begin(&seq);

               x = X; y = Y;

         } while (read_seqcount_retry(&seq, s));
}

Tree RCU (非SRCU,原子上下文,使用RCU之后不可睡眠,不可被抢占)

同步原语

  • 无锁化操作,读写都不需要持有任何锁
  • 性能最高,但是适用范围小,适用复杂
int register_cxl_calls(struct cxl_calls *calls)
{
    if (cxl_calls)
        return -EBUSY;

    rcu_assign_pointer(cxl_calls, calls);
    return 0;
}
EXPORT_SYMBOL_GPL(register_cxl_calls);

void unregister_cxl_calls(struct cxl_calls *calls)
{
    BUG_ON(cxl_calls->owner != calls->owner);
    RCU_INIT_POINTER(cxl_calls, NULL);
    synchronize_rcu();
}
EXPORT_SYMBOL_GPL(unregister_cxl_calls);

两个概念

Grace Periodguan (GP)宽限期
  • 临界区开始到所有的CPU core都进入过一次QS的这段时期,在此时期内,认为RCU保护的旧对象不能释放
Quiescent State (QS)静止状态
  • 进行调度即说明进入了QS, rcu有bh, sched等,具体需要调用的函数,视保护变量适用的上下文而定
GP示意图

Mutex Lock

  1. Mutex Lock在获取锁失败时会让出CPU执行权,转而去执行其他程序
  2. Mutex Lock适用于需要长时间获取锁的情况
  3. Mutex Lock获取之后也可以发生进程切换以及睡眠
  4. 持锁,释放锁开销比较大

你可能感兴趣的:(Linux中的Context与同步)