
1.每 CPU 变量 (Per-CPU Variables)核心思想为每个 CPU 分配一份独立的数据副本避免缓存行 bouncing缓存行在多个 CPU 间来回迁移。// 定义DEFINE_PER_CPU(int,my_counter);// 或动态分配void__percpu*ptralloc_percpu(int);// 安全访问关抢占 获取当前 CPU 指针int*pget_cpu_var(my_counter);(*p);put_cpu_var(my_counter);// 远程访问其他 CPUper_cpu(my_counter,cpu_id);关键点通过get_cpu_var() / put_cpu_var()配对使用内部会关闭内核抢占防止在访问期间被调度到其他CPU数据按CPU缓存行对齐通常64/128字节避免false sharing适用于计数器、统计信息等高频写、低频聚合读的场景2.内核抢占 (Kernel Preemption)概念允许高优先级任务在内核态执行时抢占当前任务。// 抢占控制 APIpreempt_disable();// 增加抢占计数preempt_enable();// 减少抢占计数必要时检查抢占preempt_count();// 当前抢占计数// 自愿调度schedule();cond_resched();// 如果 need_resched 被设置则主动让出抢占点中断返回时irq_exit开启抢占时preempt_enable显式调用 schedule()从系统调用返回用户态时与自旋锁的关系持有自旋锁时会自动关闭抢占preempt_disable 嵌入在 spin_lock 中防止持有锁时被抢占比导致死锁。3.内存屏障 (Memory Barriers)目的防止编译器和 CPU 对内存操作进行乱序重排保证多核间的内存可见性顺序。// 编译器屏障barrier();// 通用内存屏障smp_mb();// 全屏障之前的读写都完成后才能执行之后的读写smp_rmb();// 读屏障smp_wmb();// 写屏障// 带依赖的屏障ARM/弱序架构常用smp_read_barrier_depends();// 与锁配对的隐式屏障spin_lock/unlock()// 隐含全屏障mutex_lock/unlock()// 隐含全屏障atomic_*_return()// 隐含全屏障使用原则通常不需要显式使用应优先使用原子操作、锁等高层抽象它们已内含正确的屏障语义。4.原子变量 (Atomic Variables)实现依赖底层CPU的原子指令如x86的LOCK前缀、XCHG、CMPXCHGARM的LDREX/STREX。atomic_tvATOMIC_INIT(0);atomic_set(v,5);intvalatomic_read(v);atomic_inc(v);atomic_dec_and_test(v);// 递减并测试是否为 0atomic_add_return(3,v);// 加法并返回新值含内存屏障atomic_cmpxchg(v,old,new);// CAS 操作位原子操作set_bit(0,bitmap);// 原子设置第 0 位test_and_set_bit(0,bitmap);// 测试并设置CAS 语义适用场景计数器、标志位、引用计数等极短临界区。5.自旋锁 (Spinlock)机制忙等待锁持有锁期间关闭内核抢占但不关中断。spinlock_tlock;spin_lock_init(lock);spin_lock(lock);// 获取锁关抢占// 临界区spin_unlock(lock);// 释放锁开抢占// 中断安全版本防止中断处理程序也尝试获取同一把锁导致死锁spin_lock_irqsave(lock,flags);spin_unlock_irqrestore(lock,flags);// 下半部安全版本spin_lock_bh(lock);spin_unlock_bh(lock);核心规则临界区必须极短不能睡眠、不能调度、不能访问用户空间单核非抢占内核中自旋锁退化为空操作仅关抢占锁粒度要细减少竞争5.1.spin_lock — 基础版spin_lock(lock);// 临界区spin_unlock(lock);实际做了什么preempt_disable();// 关闭内核抢占while(!trylock(lock))// 忙等待;保护范围仅屏蔽同 CPU 上的其他进程上下文通过关闭抢占以及其他 CPU 上的所有上下文通过锁变量本身。不保护本地 CPU 上的中断、软中断、NMI。5.2.spin_lock_irqsave — 中断安全版unsignedlongflags;spin_lock_irqsave(lock,flags);// 临界区spin_unlock_irqrestore(lock,flags);实际做了什么local_irq_save(flags);// 保存当前中断状态关闭本地硬中断preempt_disable();// 关闭内核抢占while(!trylock(lock));保护范围屏蔽了本地 CPU 上的一切——进程、中断、软中断、NMI。以及其他 CPU 上的所有上下文通过锁变量本身。5.3.spin_lock_bh — 下半部安全版spin_lock_bh(lock);// 临界区spin_unlock_bh(lock);实际做了什么local_bh_disable();// 关闭本地软中断包括 tasklet、timer、NET_RX 等preempt_disable();// 关闭内核抢占while(!trylock(lock));保护范围屏蔽本地 CPU 上的进程上下文和软中断上下文但不关闭硬中断。5.4.对比总结变体关闭抢占关闭硬中断关闭软中断保护范围开销spin_lock✅❌❌进程 ↔ 进程最小spin_lock_bh✅❌✅进程 ↔ 软中断中等spin_lock_irqsave✅✅✅进程 ↔ 中断/一切最大选择决策树你在进程上下文持锁 │ ├── 中断处理程序会抢这把锁 ──→ spin_lock_irqsave │ ├── 软中断/tasklet 会抢这把锁 ──→ spin_lock_bh │ └── 只有进程上下文竞争 ──→ spin_lock内核源码中的实际模式// Linux 内核中典型的分层锁使用以网络子系统为例// 1. 硬中断最内层已关中断irqreturn_trx_irq(intirq,void*dev_id){spin_lock(queue-lock);// ✅ 中断里只用基础 spin_lock// 收包入队spin_unlock(queue-lock);raise_softirq(NET_RX_SOFTIRQ);// 触发软中断}// 2. 软中断关软中断但硬中断可发生voidnet_rx_action(structsoftirq_action*h){spin_lock(queue-lock);// ✅ 软中断里也用基础 spin_lock// 协议栈处理spin_unlock(queue-lock);}// 3. 进程上下文可能和中断/软中断竞争voiddev_queue_xmit(structsk_buff*skb){spin_lock_bh(queue-lock);// ✅ 防软中断抢锁// 发包处理spin_unlock_bh(queue-lock);}// 4. 通用路径不确定上下文voidsome_generic_fn(void){unsignedlongflags;spin_lock_irqsave(queue-lock,flags);// ✅ 最安全但开销最大// ...spin_unlock_irqrestore(queue-lock,flags);}6.互斥锁 (Mutex)机制睡眠锁无法获取时当前任务进入睡眠状态让出 CPU。structmutexmtx;mutex_init(mtx);mutex_lock(mtx);// 不可中断睡眠mutex_lock_interruptible(mtx);// 可中断返回 -EINTRmutex_trylock(mtx);// 非阻塞尝试// 临界区mutex_unlock(mtx);与自旋锁对比特性自旋锁 (Spinlock)互斥锁 (Mutex)等待方式忙等待消耗 CPU睡眠让出 CPU临界区长度极短几行代码较长可睡眠可重入不可重入不可重入上下文中断上下文可用仅进程上下文开销获取快无上下文切换获取慢可能切换上下文7.信号量 (Semaphore)机制计数锁允许指定数量的持有者同时进入临界区。structsemaphoresem;sema_init(sem,1);// 二值信号量类似互斥锁sema_init(sem,5);// 计数信号量最多 5 个并发down(sem);// P 操作计数减 1若为负则睡眠down_interruptible(sem);up(sem);// V 操作计数加 1唤醒等待者现代内核中的定位二值信号量基本被 Mutex 取代Mutex 有 owner 追踪调试更友好计数信号量用于资源池限制如同时打开的 socket 数量限制8.读拷贝更新 (RCU - Read-Copy-Update)核心思想读端零开销无需锁、无需原子操作写端通过拷贝替换延迟回收实现。// 读端 rcu_read_lock();// 轻量级仅关抢占或记录 QS 状态structmy_struct*prcu_dereference(global_ptr);// 解引用含内存屏障// 读取 p 的数据禁止写操作rcu_read_unlock();// 写端 structmy_struct*new_pkmalloc(...);*new_p*old_p;// 复制旧数据new_p-fieldnew_value;// 修改副本rcu_assign_pointer(global_ptr,new_p);// 原子替换指针含写屏障// 延迟释放旧数据等待所有读端完成synchronize_rcu();// 阻塞直到所有已存在的读端结束kfree(old_p);// 或使用回调异步释放call_rcu(old_p-rcu_head,my_free_func);宽限期 (Grace Period)所有 CPU 都至少经历一次上下文切换或静止状态 quiescent state在此之后旧的读端引用已不存在可以安全释放适用场景读多写极少的链表、哈希表、路由表内核中 dentry_cache、路由表、进程链表 大量使用 RCU9.性能优化策略9.1.锁的选择层次读多写极少 → RCU读端零开销 读多写少 → 读写锁/Seqlock顺序锁写优先 读写均衡 → 自旋锁短临界区/互斥锁长临界区 计数限制 → 信号量/每 CPU 计数器聚合9.2.关键优化原则策略说明缩小临界区只保护必要的数据访问I/O、复杂计算移到锁外降低锁粒度全局锁 → 分片锁per-bucket、per-cpu、per-node避免锁用每 CPU 变量、RCU、无锁算法如llist替代锁顺序多锁时必须全局统一获取顺序防止死锁缓存对齐锁本身和数据结构按缓存行对齐避免 false sharing自适应自旋mutex在短等待时先自旋CONFIG_MUTEX_SPIN_ON_OWNER避免上下文切换开销9.3.内核中的无锁数据结构llist基于 CAS 的单链表用于工作队列percpu_ref引用计数 RCU 延迟释放seqlock写端无阻塞读端通过序列号检测冲突并重试用于 jiffies、xtime10.总结速查机制等待行为能否睡眠中断上下文典型用途每 CPU 变量无否可统计计数器原子变量无否可引用计数、标志位自旋锁忙等否可irqsave 版本极短临界区互斥锁睡眠可否一般同步信号量睡眠可否资源池限制RCU无读端否读端可读端读多写少的数据结构