自旋锁实验核心:
一、实验核心目标
用自旋锁替代原子变量,实现同一时间仅允许一个应用程序访问LED设备的互斥机制,核心是通过状态变量配合自旋锁完成资源保护。

二、核心设计逻辑
自旋锁的关键是短临界区原则,不能直接用它保护整个open和release函数(会导致临界区过长),因此实验做了巧妙设计:
引入状态变量dev_stats
dev_stats为0时,代表设备空闲;大于0时,代表设备已被占用。
真正实现设备互斥访问的是这个状态变量,自旋锁仅用于保护对它的修改操作。
自旋锁只守护dev_stats
在open函数里,先通过自旋锁加锁,读取dev_stats判断设备是否可用;如果不可用则解锁返回占用失败,可用则将dev_stats加1后解锁,标记设备被占用。
在release函数里,同样加锁后将dev_stats减1,解锁后标记设备已释放,整个过程自旋锁仅守护对dev_stats的加减操作,保证操作的原子性。
选对自旋锁API实验用spin_lock_irqsave和spin_unlock_irqrestore替代基础的自旋锁函数,因为这组API不仅加锁,还会临时关闭本地CPU中断,避免中断上下文和进程上下文同时访问变量,从根本上保障驱动的兼容性,防止死锁。

三、代码关键细节与修正
代码存在的问题
拼写和语法错误:部分代码中存在函数名拼写错误,比如spin_unlock_irgrestore应为spin_unlock_irqrestore,还有使用了中文全角分号,这些细节会导致编译失败,需要修正。
初始化遗漏:驱动入口函数里最好显式把dev_stats初始化为0,虽然全局变量默认为0,但显式初始化能让代码更严谨,避免隐藏风险。
状态保护逻辑:release函数中对dev_stats做减1操作时,先判断其大于0才执行,已经做了边界检查,避免状态变量变成负数,逻辑是合理的。
关键操作流程
驱动初始化时,要完成自旋锁初始化,同时显式把dev_stats置为0。
open函数先申请自旋锁,判断设备状态,决定是否能使用,操作完立即释放锁。
release函数同样加锁后修改设备状态,释放设备资源,确保后续进程能申请到设备。
四、为什么选带中断保存的自旋锁
实验不使用基础的自旋锁函数,而是选择带中断保存功能的API,核心原因是考虑驱动的通用性。这组API会自动保存当前中断状态,加锁时禁用本地中断,解锁时恢复中断,能避免中断上下文和进程上下文同时操作临界资源,防止竞态条件和死锁,是驱动开发的标准做法。

五、原子变量和自旋锁的适用差异
两种方式各有适用场景,本实验虽用原子变量更合适(仅保护计数器,更简洁高效),但核心目的是教学自旋锁:
原子变量:适合保护简单的整数操作,无需额外加锁开销,代码简洁高效,适合状态标记、简单计数场景。
自旋锁:能保护包含多个操作的复杂临界区,但必须保证临界区足够短,因为忙等待机制会占用CPU,不能在临界区中睡眠。
六、测试验证要点
测试时先让第一个应用程序后台占用LED设备25秒,再立即启动第二个程序尝试操作,正常结果是第二个程序会提示打开失败,必须等待第一个程序运行结束、释放设备后,才能再次操作。卸载驱动时只需执行对应的rmmod命令即可。
七、实验总结
整个实验的核心思路是通过自旋锁保护状态变量,用状态变量实现互斥,既遵循了自旋锁短临界区的原则,又保障了设备的互斥访问。
