
操作系统驱动开发【免费下载链接】darwin-xnuLegacy mirror of Darwin Kernel. Replaced by https://github.com/apple-oss-distributions/xnu项目地址https://gitcode.com/gh_mirrors/da/darwin-xnu点击查看免费下载导读本文以 darwin-xnu 仓库中的 doc/atomics.md 为核心骨架系统讲解 XNU 内核及驱动、用户态库中使用原子操作与内存屏障的官方最佳实践为什么 C11stdatomic.h在 XNU 中是一把双刃剑、os/atomic_private.h提供的os_atomic_*接口如何规避其陷阱、每个接口的语义与用法、以及独有的dependency内存序与os_atomic_rmw_loop高级构造。读完本文你将掌握在 XNU 内核代码中正确选择原子接口、写出可移植且可被编译器正确优化的并发代码的完整方法论并能在仓库源码中定位每一处接口的真实实现与典型用法。1. 背景为什么 XNU 需要一份专门的原子操作指南XNU 是 Apple 操作系统的内核其并发基石是一套经过深思熟虑的原子操作与内存屏障约定。仓库中的 doc/atomics.md 明确给出了这份指南的三重目的作为 XNU 中使用原子操作与内存屏障的最佳实践指引警告在 C 语言中处理原子操作时的各种潜在陷阱在 C11 内存模型之上说明 XNU 所采取的自由与取舍explains the liberties XNU takes with said model。文档假定读者已对 C11 内存模型其头部注释明确写着此文件为 C11stdatomic.h定义了更简洁、更安全的封装nicer (terser and safer) wrappers并直接指向doc/atomics.md作为更详细的文档——两者互为印证。文档还特别提示Linux 内核的Documentation/memory-barriers.txt对内存屏障有详尽论述其中大部分内容与平台无关值得交叉阅读。1.1 词汇约定本文沿袭原文档的命名约定直接使用 C11 的六种内存序名relaxed、consume、acquire、release、acq_rel、seq_cst省略memory_order_前缀os_atomic还特别区分了编译器屏障compiler barriers——限制编译器对代码的重排——与内存栅栏memory fences。2. C11stdatomic.h的隐患文档毫不避讳地指出C11 内存模型是现代 C 语言最重要的补充之一但它在最纯粹的 C 传统中是一把锋利的工具in the purest C tradition, it is a sharp tool。2.1 两种默认变体与隐式 seq_cstC11 为每个原子操作提供了两种变体显式explicit变体可指定内存序如atomic_load_explicit(p, memory_order_relaxed)普通变体等价于使用seq_cst内存序如atomic_load(p)。当_Atomic限定的变量被直接访问不经过任何atomic_*_explicit()函数时编译器会替你生成对应的seq_cst原子操作。顺序一致sequentially consistent的世界对编译器和硬件的重排与优化极其安全但代价是巨大的内存屏障开销。2.2 显式内存序的意外优化放松原子也会被重排显式指定内存序尤其是relaxed看起来非常诱人但文档警告编译器有权对放松relaxed原子执行大量开发者不会预料到的优化——它完全可以像对待普通内存访问那样对放松原子做合并coalescing、重排reordering、循环提升hoisting out of loops等优化。原文档给出了一个极具说服力的例子由于 LTO链接时优化编译器在 XNU 中几乎总是能知道doit在做什么因此它有权将void perform_with_progress(int steps, long _Atomic *progress) { for (int i 0; i steps; i) { doit(i); atomic_store_explicit(progress, i, memory_order_relaxed); } }改写成void perform_with_progress(int steps, long _Atomic *progress) { for (int i 0; i steps; i) { doit(i); } atomic_store_explicit(progress, steps, memory_order_relaxed); }这显然彻底摧毁了progress存在的意义——调用方再也无法在循环执行过程中观察到进度。这正是放松原子可以被任意合并/提升的真实写照放松序提供的是最终会一致的最弱保证而编译器有权把多次放松存储折叠为一次。3.os_atomic_*如何解决stdatomic.h的陷阱libkern/os/atomic_private.h 通过以下四条设计原则来规避上述问题无需_Atomic/volatile限定传给os_atomic_*函数的内存位置不需要被标记为_Atomic或volatile也不必是_Atomic volatile这允许在 C11 诞生之前的旧代码中使用原子操作。不过文档仍然推荐新代码使用_Atomic限定符。不可被编译器合并os_atomic_*的所有访问都如同其类型带_Atomic volatile限定那样执行编译器无法把多次os_atomic_store合并成一次。只有显式变体os_atomic_*只提供显式内存序变体必须显式给出排序。排序名与 C11 相同去掉memory_order_前缀relaxed、acquire、release、acq_rel、seq_cst此外还支持纯编译器屏障排序compiler_acquire、compiler_release、compiler_acq_rel。自动发出正确的编译器屏障os_atomic_*会使用atomic_signal_fence()发出与所请求内存序对应的编译器屏障。3.1 从实现看编译器屏障如何工作在 libkern/os/atomic_private.h 中os_compiler_barrier的实现正是依托atomic_signal_fence#define os_compiler_barrier(b...) \ os_atomic_std(atomic_signal_fence)(_os_compiler_barrier_##b)而 libkern/os/atomic_private_impl.h 中的_os_atomic_mo_*宏揭示了符号内存序到实际 C11 内存序的映射在 SMP 配置OS_ATOMIC_CONFIG_SMP默认 1下acquire/release/acq_rel/seq_cst原样映射到对应的 C11 内存序并携带硬件栅栏语义而在非 SMP 配置下它们全部降级为relaxed——因为单处理器上无需硬件内存栅栏只需保留编译器屏障。这正是文档所言os_atomic_*始终携带匹配的编译器屏障语义的实现基础。需要说明atomic_private_impl.h是内部实现细节头文件文档与头文件均明确不得直接包含、不承诺接口稳定。4. XNU 中原子操作的最佳实践文档给出了一套明确的接口选用准则通用代码优先使用os/atomic_private.h的os_atomic_*函数不要使用__sync_*、__c11_*和__atomic_*编译器内建函数可以使用stdatomic.h的函数前提是你确实希望编译器进行合并/重排例如某些引用计数实现鼓励用_Atomic甚至_Atomic volatile限定原子变量但作者必须意识到直接访问该变量会产生相当重的内存屏障因为直接访问等价于seq_cst不要使用consume内存序参见本文第 7 节的dependency内存序注意libkern/OSAtomic.h提供了一批历史遗留的原子接口但该头文件已被视为过时新代码不应使用。仓库源码忠实遵循了这些准则。例如 bsd/kern/kern_credential.c 第 4721 行用os_atomic_inc_orig(cred-cr_ref, relaxed)维护凭据引用计数第 4746 行用os_atomic_dec_orig递减——典型的放松序 orig 变体组合bsd/dev/dtrace/dtrace_subr.c 第 383 行用os_atomic_inc_orig(next_minor, relaxed) % DTRACE_NCLIENTS分配次设备号bsd/kern/kern_aio.c 第 397 行用os_atomic_dec_orig(...) 0判断异步 I/O 计数归零。这些都是_orig变体用于判断原值/触发条件的教科书式用法。5.os_atomic_*接口总览5.1 编译器屏障与内存栅栏os_compiler_barrier(mem_order?)提供编译器屏障可带可选的内存序参数。基于 C11 的atomic_signal_fence()实现参数可省略缺省为acq_rel编译器屏障阻止编译器在屏障两侧向任何方向重排代码。os_atomic_thread_fence(mem_order)按atomic_thread_fence()语义提供内存栅栏即使在 UP单处理器系统上也总是隐含等价的os_compiler_barrier()。其头文件实现为硬件栅栏SMP 版内存序 编译器栅栏atomic_signal_fence的组合。5.2 初始化、加载与存储os_atomic_init、os_atomic_load、os_atomic_store分别等价于atomic_init、atomic_load_explicit、atomic_store_explicit。关键承诺os_atomic_load与os_atomic_store保证编译成一条普通 load / store 指令当内存序为relaxed时。实现中通过os_atomic_load_is_plain(p)/os_atomic_store_is_plain(p)检查——当类型大小不超过指针大小时成立——并以_Static_assert强制约束见 libkern/os/atomic_private.h 第 278-333 行。对于宽度超过一个指针、需要更昂贵代码生成如比较交换循环的原子加载/存储应改用os_atomic_load_wide/os_atomic_store_wide。注意os_atomic_init并非原子地执行只能用于对象在被其他线程/核心看到之前的初始化阶段——这是它与 load/store 的本质区别。5.3 基本 RMW读-修改-写原子操作os_atomic_*提供以下基础原子 RMW 操作操作语义等价 C11 操作inc原子自增等价于加 1fetch_adddec原子自减等价于减 1fetch_subadd原子加fetch_addsub原子减fetch_subor原子按位或fetch_orxor原子按位异或fetch_xorand原子按位与fetch_andandnot原子按位与非等价于对~value做 andfetch_andmin原子最小值Clang__atomic_fetch_minmax原子最大值Clang__atomic_fetch_max每个操作都有两个变体os_atomic_${op}_orig如os_atomic_add_orig返回原子操作发生之前存储在指定位置的值os_atomic_${op}如os_atomic_add返回原子操作发生之后存储在指定位置的值。这一命名约定是刻意的理由有二os_atomic_add(p, value, ...)本质上等价于 C 语言的就地加法(*p value)——后者返回运算结果而非*p的原值大多数微妙的原子算法恰恰需要原值尤其是位操作场景(os_atomic_or_orig(p, bit, relaxed) bit)原子地执行*p | bit同时告诉你bit是否原本就已置位。显式使用原值对读者更有意义多敲的五个字母_orig物有所值。原文档给出的典型示例static int _Atomic i 0; printf(%d\n, os_atomic_inc_orig(i)); // prints 0 printf(%d\n, os_atomic_inc(i)); // prints 2第一次inc_orig返回原值 0此时i变为 1第二次inc返回新值 2。若读者想验证这一语义与实现的一致性可在 libkern/os/atomic_private.h 第 390-409 行看到os_atomic_add_orig走_os_atomic_c11_op_orig(p, v, m, fetch_add)、os_atomic_add走_os_atomic_c11_op(p, v, m, fetch_add, )的分工。5.4 原子交换与比较交换swap / compare-and-swapos_atomic_xchg(p, v, m)atomic_exchange_explicit的简单封装返回交换前变量中的值。os_atomic_cmpxchg(address, expected, new_value, mem_order)若address处的当前值等于expected则原子地将new_value存入返回 1/true 表示成功0/false 表示失败。os_atomic_cmpxchgv(address, expected, new_value, orig_value, mem_order)多一个orig_value参数必须是指向局部变量的指针。无论比较交换成功与否它都会被填入address处的当前值成功时必为expected失败时则为实际当前值——这对重新驱动比较交换循环非常有帮助。与atomic_compare_exchange_strong_explicit不同os_atomic_cmpxchg*只指定一个内存序且仅作用于成功的比较交换失败情形在 C11 语境下总是relaxed——因为这是绝大多数场景下的实际需求头文件实现中失败序确实硬编码为_os_atomic_mo_relaxed见 libkern/os/atomic_private.h 第 651-704 行。os_atomic_*不提供atomic_compare_exchange_weak_explicit的封装因为os_atomic_rmw_loop为 CAS 循环提供了更好的替代方案。6.os_atomic_rmw_loop可读的 CAS 循环6.1 为什么需要它os_atomic_rmw_loop是一个极具表现力的构造让比较交换循环变得极其简洁、可读并且能比比较交换循环更高效地利用 LL/SCload-linked/store-conditional指令。传统 C11 CAS 循环长这样int _Atomic *address; int old_value, new_value; bool success false; old_value atomic_load_explicit(address, memory_order_relaxed); do { if (!validate(old_value)) { break; } new_value compute_new_value(old_value); success atomic_compare_exchange_weak_explicit(address, old_value, new_value, memory_order_acquire, memory_order_relaxed); } while (__improbable(!success));同样的逻辑用os_atomic_rmw_loop表达int _Atomic *address; int old_value, new_value; bool success; success os_atomic_rmw_loop(address, old_value, new_value, acquire, { if (!validate(old_value)) { os_atomic_rmw_loop_give_up(break); } new_value compute_new_value(old_value); });两者的关键差异在于可读性os_atomic_rmw_loop让读者按程序顺序立刻知道这是一个 CAS 循环并把内存序前置暴露而传统 CAS 循环必须跳到代码末尾才能看懂全貌。6.2 规则与限制任何试图跳出循环作用域的控制流都必须用os_atomic_rmw_loop_give_up包裹这样 LL/SC 架构可以中止其已开启的 LL/SC 事务。因为循环体本质上是 LL/SC 事务在循环内对内存执行任何 store 是未定义行为寄存器操作没问题否则可能导致 store-conditional 永远失败。尤其是嵌套os_atomic_rmw_loop是无效的。在os_atomic_rmw_loop内使用continue也是无效的应改用os_atomic_rmw_loop_give_up(goto again)跳到循环前的again:标签。原文档给出了需要在循环中做破坏性 store的完整示范int _Atomic *address; int old_value, new_value; bool success; again: success os_atomic_rmw_loop(address, old_value, new_value, acquire, { if (needs_some_store_that_can_thwart_the_transaction(old_value)) { os_atomic_rmw_loop_give_up({ // 做任何需要做的、会令事务永远失败的中央内存 store do_my_rmw_loop_breaking_store(); // 然后才重新驱动 goto again; }); } if (!validate(old_value)) { os_atomic_rmw_loop_give_up(break); } new_value compute_new_value(old_value); });从实现看os_atomic_rmw_loop的返回语义是0 表示通过os_atomic_rmw_loop_give_up()中止1 表示循环正常完成其内部先以普通指针做一次初始relaxed加载随后在do { __VA_ARGS__; ... } while (__builtin_expect(!_result, 0))中反复执行用户代码块与atomic_compare_exchange_weak_explicit失败序恒为 relaxed见 libkern/os/atomic_private.h 第 706-757 行os_atomic_rmw_loop_give_up(...)则展开为({ __VA_ARGS__; break; })第 759-769 行。文档中出现的__improbable与实现中的__builtin_expect(!_result, 0)是同一思路——在分支预测上提示失败是小概率事件。7. 专属dependency内存序7.1 动机consume 的破碎与 dependency 的重建C11 的consume内存序在诸多方面是破碎的包括 clang 在内的大多数编译器将其实现为memory_order_acquire的等价物。但consume的概念对某些算法确实有用。作为替代尝试os/atomic_private.h实现了全新的dependency内存序。其目的提供一个relaxed 加载 隐式编译器屏障作为一条硬件依赖链chain of hardware dependencies的根用于与同一地址上带 release 语义的 store 配对——这与consume的初衷完全一致。与consume不同编译器必须自动追踪依赖dependency序依赖显式注解来标记依赖何时发生通过以dependency序加载得到的指针来解引用loads through a pointer loaded with dependency会提供硬件依赖依赖可以通过os_atomic_load_with_dependency_on和os_atomic_inject_dependency接口注入到其他不经过该指针的加载中。7.2 使用示例发布-订阅模式原文档给出的经典场景是发布者写值 读者按标志位读取struct foo { long value; long _Atomic flag; }; void publish(struct foo *p, long value) { p-value value; os_atomic_store(p-flag, 1, release); } bool broken_read(struct foo *p, long *value) { /* * 这不安全这里完全没有硬件依赖。 * 改用 acquire 屏障当然能修复但代价相当高…… */ if (os_atomic_load(p-flag, relaxed)) { *value p-value; return true; } return false; } bool valid_read(struct foo *p, long *value) { long flag os_atomic_load(p-flag, dependency); if (flag) { /* * 把依赖链进一步延伸到所有经过 p 的加载 * 使其恰当地与 publish 中的 release 屏障配对。 */ *value os_atomic_load_with_dependency_on(p-value, flag); return true; } return false; }broken_read是错误的relaxed加载没有建立任何硬件依赖读者可能看到flag 1却读到未发布的旧valuevalid_read则先以dependency序加载flag作为依赖根再用os_atomic_load_with_dependency_on把依赖延续到p-value的加载上从而与publish的 release 存储正确配对。7.3 四个依赖相关接口涉及硬件依赖的共有 4 个接口全部实现在 libkern/os/atomic_private.h 第 805-921 行仅当OS_ATOMIC_CONFIG_MEMORY_ORDER_DEPENDENCY开启时编译os_atomic_load(..., dependency)启动硬件依赖的根应与带 release 或更强语义release、acq_rel、seq_cst的 store/RMW 配对os_atomic_inject_dependency(p, e)把由dependency加载或任何已被注入依赖的值提供的依赖注入到指针p上返回与p相等但延续了依赖链的指针os_atomic_load_with_dependency_on(p, e)做一次看似 relaxed 的加载但延续依赖链本质是os_atomic_load(os_atomic_inject_dependency(p, e), dependency)的简写os_atomic_make_dependency(v)从给定依赖根创建不透明令牌os_atomic_dependency_t以便把同一条依赖注入到多个加载中。其中os_atomic_make_dependency在架构特定头文件 libkern/os/atomic_private_arch.h 中会被覆盖为在目标指令集上真正制造数据依赖的形式例如通过 XOR 零寄存器等手法而os_atomic_dependency_t内部是一个__opaque_zero字段——实现特意确保编译器无法得知其恒为 0。因此用户绝不能测试该令牌的值连 assert 都不行否则编译器就能推理其值并消去依赖注入从而彻底击穿该构造的意义。OS_ATOMIC_DEPENDENCY_NONE则用于不携带依赖的场合此时注入操作退化为无操作。7.4 适用边界与配置开关原文档明确警告当编译器能推理你正在操作的指针时该技术不安全——例如编译器知道指针只可能取少数几个值就会丢弃这些手工构造的依赖链。文档寄希望于未来的 C2Y 标准能以语言特性形式提供类似构造。在配置层面dependency序由 libkern/os/atomic_private.h 中的OS_ATOMIC_CONFIG_MEMORY_ORDER_DEPENDENCY宏控制默认关闭0但在XNU_KERNEL_PRIVATE内核构建下自动开启1。头文件注释强调它被用于修复 C11 的 consume 序应仅限于每个周期都极其珍贵的超复杂算法因无编译器支持而天然带风险是专家且高度领域特定代码的保留地。8. 从源码验证接口行为与平台差异8.1 SMP 与架构相关的语义降级libkern/os/atomic_private_impl.h 展示了两个关键机制SMP 开关OS_ATOMIC_CONFIG_SMP默认 1。在非 SMP 配置下所有硬件内存序acquire/release/acq_rel/seq_cst都降级为relaxed仅保留编译器屏障语义——这正是UP 系统不需要硬件栅栏的实现表达。LL/SC 能力libkern/os/atomic_private.h 通过OS_ATOMIC_HAS_LLSCi386/x86_64 为 0arm/arm64 为 1与OS_ATOMIC_USE_LLSCarmv8.2 及以上使用原生原子指令时为 0描述平台差异OS_ATOMIC_HAS_STARVATION_FREE_RMW仅在非 LL/SC 配置下为真。文档rmw_loop 能比 CAS 循环更高效利用 LL/SC的论断正对应这些宏背后的指令集事实。8.2 饿死防护配置OS_ATOMIC_CONFIG_STARVATION_FREE_ONLY用于隐藏在某些硬件/构建配置下可能导致饿死starvation的接口在 armv8 非对称核心大小核上基于 load/store exclusive 的原子操作在极热代码或不可抢占上下文中可能饿死。默认值随构建场景变化——XNU 内核按 SoC 构建已保证安全XNU_KERNEL_PRIVATE下为 0kext 默认避免可用KERNEL下为 1用户态默认允许0。当该宏开启时所有可能饿死的接口被替换为编译期_Static_assert报错见 libkern/os/atomic_private.h 第 771-803 行从可用性层面强制规避风险。8.3 内核中的真实调用场景引用计数bsd/kern/kern_credential.c 第 4721、4746 行以relaxed序对cred-cr_ref做os_atomic_inc_orig/os_atomic_dec_orig归零判断依赖返回值——印证了_orig变体返回原值以驱动条件分支的典型用法。编号分配bsd/dev/dtrace/dtrace_subr.c 第 383 行以os_atomic_inc_orig(...) % DTRACE_NCLIENTS做原子取模分配。计数耗尽检测bsd/kern/kern_aio.c 第 397 行os_atomic_dec_orig(aio_anchor.aio_total_count, relaxed) 0。队列保留计数bsd/kern/kern_event.c 第 2807 行os_atomic_inc_orig(kqwl-kqwl_retains, relaxed)。此外 libkern/os/refcnt.c 也是os_atomic_*的密集使用者之一。这些真实调用点共同验证了本文第 4 节的最佳实践准则——通用代码默认os_atomic_*、默认relaxed、按需选择_orig变体。9. 总结XNU 原子操作的决策清单场景推荐接口理由通用并发代码os_atomic_*来自os/atomic_private.h显式内存序、不可合并、自动编译器屏障希望编译器合并/重排stdatomic.h函数例如引用计数实现单指令宽度的 load/storeos_atomic_load/os_atomic_store保证编译为一条普通 load/store超宽 load/storeos_atomic_load_wide/os_atomic_store_wide可能生成比较交换循环等昂贵代码需要原值的 RMWos_atomic_${op}_orig位操作、条件触发场景CAS 循环os_atomic_rmw_loop可读性、LL/SC 效率、显式 give_uprelease 配对的依赖读dependency序 依赖注入接口替代破碎的 consume仅限专家场景遗留代码迁移到os_atomic_*libkern/OSAtomic.h已过时禁止__sync_*、__c11_*、__atomic_*内建、consume序最佳实践明确禁止核心文档 doc/atomics.md 与实现头文件 libkern/os/atomic_private.h及其架构/实现依赖 libkern/os/atomic_private_impl.h 与 libkern/os/atomic_private_arch.h共同构成了一套文档-实现-调用点互相印证的完整体系。无论是初涉 XNU 并发编程的开发者还是需要深入理解内核原子语义的维护者遵循这套准则都能写出既正确、又能在目标硬件上获得最优性能的并发代码。赞分享操作系统驱动开发【免费下载链接】darwin-xnuLegacy mirror of Darwin Kernel. Replaced by https://github.com/apple-oss-distributions/xnu项目地址https://gitcode.com/gh_mirrors/da/darwin-xnu点击查看免费下载相关推荐QEMU 原子操作与内存屏障完全指南qemu/atomic.h 的设计、语义与实践QEMU 原子操作与内存屏障完全指南qemu/atomic.h 的设计、语义与实践 导读 本文以 QEMU 官方开发者文档 docs/devel/atomic虚拟化硬件仿真TinyWebServer无锁编程原子操作与内存屏障TinyWebServer无锁编程原子操作与内存屏障 在高并发的Linux服务器开发中传统锁机制如互斥锁、信号量常常成为性能瓶颈。TinyWebServ后端网络/通信Slang 着色器编程中的内存一致性Memory Consistency深入指南数据竞争、内存序、原子操作与内存屏障Slang 着色器编程中的内存一致性Memory Consistency深入指南数据竞争、内存序、原子操作与内存屏障 导读 在多线程 GPU 着色器程序中编译器图形学编程语言创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考