SerenityOS 内核 AHCI 驱动中的软锁与硬锁:Lock/Mutex 与 Spinlock 的协同使用指南

发布时间:2026/9/10 13:37:49

SerenityOS 内核 AHCI 驱动中的软锁与硬锁:Lock/Mutex 与 Spinlock 的协同使用指南 SerenityOS 内核 AHCI 驱动中的软锁与硬锁Lock/Mutex 与 Spinlock 的协同使用指南【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenityAHCIAdvanced Host Controller Interface是 SerenityOS 访问 SATA 硬盘的核心驱动路径。为了保证并发安全驱动代码同时使用了两种内核锁可让出调度的软锁Lock/Mutex与关闭中断的硬锁Spinlock。本篇基于仓库文档 Documentation/Kernel/AHCILocking.md结合 AHCIPort 与 AHCIController 的真实实现系统讲解两类锁的语义、正确加锁顺序以及它们在 AHCI 硬件访问与 IOWorkQueue场景中的落地用法帮助你写出既不会死锁、也不会漏关中断的内核并发代码。引言为什么 AHCI 驱动要区分软锁与硬锁AHCI 控制器通过 HBAHost Bus Adapter寄存器与端口寄存器与硬盘交互中断、DMA、热插拔事件随时可能发生。内核在此类临界区中需要回答两个问题如何保证只有一个 CPU 在临界区内执行且不被中断打断对应硬锁如何在允许中断、允许调度的上下文如 IO 工作队列中仍保证互斥对应软锁SerenityOS 的答案是同时引入两类锁Mutex软锁与Spinlock硬锁二者在 Kernel/Locking 目录下实现AHCI 驱动则在 Kernel/Devices/Storage/AHCI 中组合使用。下面先从语义讲起。软锁Lock/Mutex语义与特性软锁本质上就是内核中的普通锁。在原文档的术语中它被称为Lock在当前仓库源码中对应的实现是 MutexAHCI 代码里写作Mutex m_lock { AHCIPortsv }见 Port.h。其关键特性是不会关闭中断临界区执行期间中断依然可以到来并被处理失败时让出调度如果锁已被占用调度器会从该临界区让出yield执行权直到下次有机会重新尝试加锁可阻塞、可休眠正因为允许让出软锁可以被长时间持有而不阻塞其他 CPU 的中断响应。作用域加锁方式原文档给出的用法是借助Locker类实现作用域化的加锁Locker locker(m_lock); ... ... return true;在当前的仓库实现中Mutex对应的作用域锁类是MutexLocker定义于 Kernel/Locking/Mutex.hAHCI 代码中实际写作MutexLocker locker(m_lock);MutexLocker在构造时调用m_lock-lock()加锁在析构时自动unlock()因此函数无论从哪个分支return锁都会被正确释放避免遗漏。AHCI 端口在 Port.cpp 的start_request、handle_interrupt排队的 work item、initialize等路径上都以软锁保护共享状态例如void AHCIPort::start_request(AsyncBlockDeviceRequest request) { MutexLocker locker(m_lock); ... m_current_request request; ... }这里持锁期间驱动会构造散列列表scatter-gather list并访问内核缓冲区这些操作可能触发页错误page fault因此必须在中断开启、可调度的软锁上下文下进行——这正是选择软锁的核心理由。硬锁Spinlock语义与特性硬锁用于内核中的关键临界区critical section。原文档中它被称为ScopedSpinLock在当前仓库实现中对应 Spinlock 配合SpinlockLocker使用。其关键特性是关闭中断加锁时调用Processor::disable_interrupts()并进入临界区计数enter_critical自旋等待若锁已被占用当前 CPU 在while (m_lock.exchange(1, ...) ! 0)上自旋配合Processor::wait_check()等待而不是让出调度保证独占同一时刻只有一个 CPU 能进入被保护的代码段且期间不会被中断打扰。这一点对硬件访问至关重要AHCI 的 HBA/端口寄存器读写、SATA 复位序列initiate_sata_reset等都是不容打断的时序敏感操作任何中断都可能在临界区中造成致命后果。作用域加锁方式原文档给出的写法ScopedSpinLock lock(m_lock); ... ... return true;仓库当前对应的实际写法是SpinlockLocker lock(m_hard_lock);Spinlock::lock()会保存进入前的InterruptsStateSpinlockLocker析构时通过unlock(previous_interrupts_state)恢复之前的中断状态因此即使调用方此前已关闭中断释放后也会正确还原而不是粗暴地无条件开启中断。在 Controller.cpp 的reset()中HBA 全局控制寄存器的操作就完全由硬锁保护ErrorOrvoid AHCIController::reset() { dmesgln_pci(*this, {}: AHCI controller reset, device_identifier().address()); { SpinlockLocker locker(m_hba_control_lock); hba().control_regs.ghc 1; ... // Note: The HBA is locked or hung if we waited more than 1 second! while (true) { if (retry 1000) return Error::from_errno(ETIMEDOUT); if (!(hba().control_regs.ghc 1)) break; microseconds_delay(1000); retry; } ... } ... }这里m_hba_control_lock的类型在 Controller.h 中声明为// Note: This lock is intended to be locked when doing changes to HBA registers // that affect its core functionality in a manner that controls all attached storage devices // to the HBA SATA ports. mutable SpinlockLockRank::None m_hba_control_lock {};该锁只被用于改动会影响所有挂载存储设备的核心 HBA 寄存器属于典型的硬锁场景。加锁顺序先软后硬绝不可颠倒当一段代码需要同时持有软锁与硬锁时原文档明确给出了正确的取锁顺序Locker locker(m_soft_lock); ScopedSpinLock lock(m_spinlock); ... ... return true;这一顺序适用于内核中任何同时获取软锁与硬锁的模式。原因如下Spinlock会关闭中断而Lock不会当软锁暂时拿不到时由于中断仍然开启系统可以安全地让出执行权给其他线程之后再回来重试如果先取Spinlock再取Lock则属于bug此时中断已被关闭这段代码将无法再让出执行权一旦软锁被占用就进退两难既不能阻塞等待又不该无限自旋破坏调度与响应性。AHCI 驱动严格遵循该顺序。例如 Port.cpp 中的reset()bool AHCIPort::reset() { MutexLocker locker(m_lock); SpinlockLocker lock(m_hard_lock); dbgln_if(AHCI_DEBUG, AHCI Port {}: Resetting, representative_port_index()); ... }recover_from_fatal_error()、initialize_without_reset()、shutdown()同样采用“先MutexLocker后SpinlockLocker”的结构。而access_device()、rebase()、start_command_list_processing()等函数内部则通过VERIFY(m_lock.is_locked())与VERIFY(m_hard_lock.is_locked())断言来强制校验两把锁均已按正确顺序持有——这是从代码层面固化“先软后硬”约定的一种手段。端口自身的两把锁声明在 Port.hSpinlockLockRank::None m_hard_lock {}; Mutex m_lock { AHCIPortsv };为什么 AHCI 需要两种锁同时存在原文档给出的答案是硬件访问的安全实现需要二者配合。Spinlock保证独占与不被中断它确保同一时刻只有一个 CPU 能运行该代码段且完全不受任何中断干扰。由于中断在关键临界区中可能是致命的硬锁是不可或缺的底线Lock覆盖其余一切场景大多数时候它与Spinlock配对使用但在把 IO 工作调度进WorkQueue时尤为重要。IO WorkQueue 场景只用软锁当 AHCI 的中断处理程序需要把耗时的收尾工作如把 DMA 缓冲区数据拷贝到请求缓冲区、处理热插拔/复位推迟到稍后执行时它会向全局 IO 工作队列g_io_work投递 work item。g_io_work定义于 Kernel/Tasks/WorkQueue.cppWorkQueue* g_io_work; void WorkQueue::initialize() { g_io_work new WorkQueue(IO WorkQueue Tasksv); ... }在WorkQueue中运行时保证中断是开启的。这意味着不能使用Spinlock因为内核需要能够处理页错误中断例如在把 DMA 数据写回请求方缓冲区时可能触发页错误但仍需要保证没有其他并发操作同时进行所以继续持有软锁Lock。这正对应 Port.cpp 中handle_interrupt()的处理收到DHRDevice to Host Register FIS或PSPIO Setup FIS中断后把后续的缓冲区读写工作投递到g_io_work在 work item 内部再取软锁if (m_interrupt_status.is_set(AHCI::PortInterruptFlag::DHR) || m_interrupt_status.is_set(AHCI::PortInterruptFlag::PS)) { m_wait_for_completion false; // Now schedule reading/writing the buffer as soon as we leave the irq handler. // This is important so that we can safely access the buffers, which could // trigger page faults if (!m_current_request) { ... } else { auto work_item_creation_result g_io_work-try_queue([this]() { ... MutexLocker locker(m_lock); VERIFY(m_current_request); VERIFY(m_current_scatter_list); ... complete_current_request(AsyncDeviceRequest::Success); }); ... } }同理热插拔PRC/PC与致命错误恢复IF/TFE/HBD/HBF的 work item 也都投递到g_io_work中执行其中recover_from_fatal_error()依然采用“软锁 硬锁”的双重保护因为错误恢复本身需要改写命令列表与 FIS 接收等硬件寄存器。从源码看锁的完整生命周期综合 Port.cpp 与 Controller.cppAHCI 驱动中两把锁的职责边界可总结为锁类型作用域保护对象典型函数AHCIPort::m_lockMutex软锁整个端口对象当前请求、散列列表、连接设备、命令列表/FIS 状态start_request、access_device、handle_interruptwork item、resetAHCIPort::m_hard_lockSpinlock硬锁端口寄存器操作PxCMD/PxSCTL/PxCI等寄存器、SATA 复位时序reset、rebase、start/stop_command_list_processing、initiate_sata_resetAHCIController::m_hba_control_lockSpinlock硬锁HBA 全局控制GHC全局主机控制、PI端口实现位图等AHCIController::reset、devices_count、device_by_port从调用结构可以推断出一个清晰的分层上层请求提交、完成回调→ 只取软锁MutexLocker允许调度与页错误中层需要动硬件寄存器但同样涉及共享状态→ 先软锁后硬锁底层HBA 全局寄存器、纯寄存器时序→ 只取硬锁SpinlockLocker绝对不容打断。这套分层既保证了响应性长路径可让出调度又保证了硬件时序的正确性短临界区关闭中断是编写设备驱动并发代码时可复用的模板。实践要点默认先取软锁凡是要同时持有两类锁一律MutexLocker locker(m_lock);在前、SpinlockLocker lock(m_hard_lock);在后不要在关中断后拿软锁Spinlock之后不允许再取Lock否则代码失去让出能力属于内核 bug区分场景选锁需要处理页错误或长时间持有时用软锁只做短小、时序敏感的寄存器操作时用硬锁用断言固化约定在需要双锁的函数中通过VERIFY(m_lock.is_locked())与VERIFY(m_hard_lock.is_locked())校验持有状态防止未来重构破坏顺序WorkQueue 中只用软锁IO 工作队列保证中断开启页错误中断必须可被处理此时只能依赖Mutex保证互斥。如需进一步阅读原文档位于 Documentation/Kernel/AHCILocking.md锁的实现可查看 Kernel/Locking/Mutex.h 与 Kernel/Locking/Spinlock.hAHCI 驱动的完整落地示例见 Kernel/Devices/Storage/AHCI/Port.cpp 和 Kernel/Devices/Storage/AHCI/Controller.cpp。【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/10 14:33:17

2026年9月最新公告:万国保养店售后养护服务声明,关于机芯检测、防水性能测试及外观翻新的官方说明

​  2026年9月最新公告:万国保养店售后养护服务声明,关于机芯检测、防水性能测试及外观翻新的官方说明。本次公告旨在明确万国腕表售后服务的核心标准,重点阐述机芯精密检测、专业防水性能复测以及合规外观翻新工艺的执行规范。万国官方售后…

2026/9/10 14:33:17

Grasscutter 资源包配置指南:从零部署到故障排查

Grasscutter 资源包配置指南:从零部署到故障排查 【免费下载链接】Grasscutter A server software reimplementation for a certain anime game. 项目地址: https://gitcode.com/GitHub_Trending/gr/Grasscutter 跑 Grasscutter 这类开源游戏服务器&#xff…

2026/9/10 14:28:11

STM32 HAL库UART2中断接收全解析:从回调机制到空闲中断

简介:基于STM32F103的HAL库UART2中断收发完整工程包,面向需要借助STM32CubeMX与HAL库实现串口通信的嵌入式工程师与学生。工程内以STM32CubeMX生成的.ioc配置为核心,包含339个C源码、108个头文件及43个汇编文件,并附有IAR链接脚本…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/10 0:00:55

目录对比去重实战:用哈希算法精准清理重复文件

我电脑里现在还有一块换了三次机的“数据墓地”硬盘,里面存着2016年以前所有旧笔记本的完整备份。平时不觉得有什么,直到前阵子想把它整理归档,发现同一个安装包、同一批照片、同一份论文草稿,在几个不同的备份目录里反复出现。更…

2026/9/10 0:00:55

Leaflet离线地图完整Demo合集:内网部署与坐标纠偏实战

简介:这是一份面向Web GIS开发者的LeafLet离线地图示例合集,帮助开发者快速掌握离线地图从搭建到交互的完整流程。压缩包共723个文件,大小14.06MB,以319个js脚本、175个html页面和29个css样式文件为主体,配合png/svg图…

2026/9/10 0:00:55

MATLAB读取Rinex 3.02观测文件:多系统GNSS数据解析实战

简介:基于MATLAB开发的Rinex3.02版观测文件(o文件)读取代码包,面向卫星定位导航方向的学习者与研究人员,用于解决新版观测文件的数据解析、历元提取与时间转换问题。压缩包共4个文件,包含两个m脚本、一个19…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码