龙海网站定制,展馆网站建设方案,wordpress响应式模板,电商网站储值消费系统本篇说清楚自旋锁
读本篇之前建议先读系列篇 进程/线程篇.
内核中哪些地方会用到自旋锁?看图: 概述
自旋锁顾名思义#xff0c;是一把自动旋转的锁#xff0c;这很像厕所里的锁#xff0c;进入前标记是绿色可用的#xff0c;进入格子间后#xff0c;手一带#xff0c…本篇说清楚自旋锁
读本篇之前建议先读系列篇 进程/线程篇.
内核中哪些地方会用到自旋锁?看图: 概述
自旋锁顾名思义是一把自动旋转的锁这很像厕所里的锁进入前标记是绿色可用的进入格子间后手一带里面的锁转个圈外面标记变成了红色表示在使用外面的只能等待.这是形象的比喻但实际也是如此.
在多CPU核环境中由于使用相同的内存空间存在对同一资源进行访问的情况所以需要互斥访问机制来保证同一时刻只有一个核进行操作自旋锁就是这样的一种机制。 自旋锁是指当一个线程在获取锁时如果锁已经被其它CPU中的线程获取那么该线程将循环等待并不断判断是否能够成功获取锁直到其它CPU释放锁后等锁CPU才会退出循环。 自旋锁的设计理念是它仅会被持有非常短的时间锁只能被一个任务持有而且持有自旋锁的CPU是不可以进入睡眠模式的因为其他的CPU在等待锁为了防止死锁上下文交换也是不允许的是禁止发生调度的. 自旋锁与互斥锁比较类似它们都是为了解决对共享资源的互斥使用问题。无论是互斥锁还是自旋锁在任何时刻最多只能有一个持有者。但是两者在调度机制上略有不同对于互斥锁如果锁已经被占用锁申请者会被阻塞但是自旋锁不会引起调用者阻塞会一直循环检测自旋锁是否已经被释放。
虽然都是共享资源竞争但自旋锁强调的是CPU核间的竞争而互斥量强调的是任务(包括同一CPU核)之间的竞争.
自旋锁长什么样? typedef struct Spinlock {//自旋锁结构体size_t rawLock;//原始锁#if (LOSCFG_KERNEL_SMP_LOCKDEP YES) // 死锁检测模块开关UINT32 cpuid; //持有锁的CPUVOID *owner; //持有锁任务const CHAR *name; //锁名称#endif} SPIN_LOCK_S;
结构体很简单里面有个宏用于死锁检测默认情况下是关闭的.所以真正的被使用的变量只有rawLock一个.但C语言代码中找不到变量的变化过程而是通过一段汇编代码来实现.看完本篇会明白也只能通过汇编代码来实现自旋锁.
自旋锁使用流程
自旋锁用于多CPU核的情况解决的是CPU之间竞争资源的问题.使用流程很简单三步走。 创建自旋锁使用LOS_SpinInit初始化自旋锁或者使用SPIN_LOCK_INIT初始化静态内存的自旋锁。 申请自旋锁使用接口LOS_SpinLock LOS_SpinTrylock LOS_SpinLockSave申请指定的自旋锁申请成功就继续往后执行锁保护的代码申请失败在自旋锁申请中忙等直到申请到自旋锁为止。 释放自旋锁使用LOS_SpinUnlock LOS_SpinUnlockRestore接口释放自旋锁。锁保护代码执行完毕后释放对应的自旋锁以便其他核申请自旋锁。
几个关键函数
自旋锁模块由内联函数实现见于los_spinlock.h 代码不多主要是三个函数.
ArchSpinLock(lock-rawLock);
ArchSpinTrylock(lock-rawLock)
ArchSpinUnlock(lock-rawLock);可以说掌握了它们就掌握了自旋锁但这三个函数全由汇编实现.见于los_dispatch.S文件 因为系列篇已有两篇讲过汇编代码所以很容易理解这三段代码.函数的参数由r0记录即r0保存了lock-rawLock的地址拿锁/释放锁是让lock-rawLock在01切换 下面逐一说明自旋锁的汇编代码.
ArchSpinLock 汇编代码 FUNCTION(ArchSpinLock) 死守非要拿到锁mov r1 #1 r111: 循环的作用因SEV是广播事件.不一定lock-rawLock的值已经改变了ldrex r2 [r0] r0 lock-rawLock 即 r2 lock-rawLockcmp r2 #0 r2和0比较wfene 不相等时说明资源被占用CPU核进入睡眠状态strexeq r2 r1 [r0]此时CPU被重新唤醒尝试令lock-rawLock1成功写入则r20cmpeq r2 #0 再来比较r2是否等于0如果相等则获取到了锁bne 1b 如果不相等继续进入循环dmb 用DMB指令来隔离以保证缓冲中的数据已经落实到RAM中bx lr 此时是一定拿到锁了跳回调用ArchSpinLock函数看懂了这段汇编代码就理解了自旋锁实现的真正机制为什么一定要用汇编来实现. 因为CPU宁愿睡眠也非拿要到锁不可的 注意这里可不是让线程睡眠而是让CPU进入睡眠状态能让CPU进入睡眠的只能通过汇编实现.C语言根本就写不出让CPU真正睡眠的代码.
ArchSpinTrylock 汇编代码
如果不看下面这段汇编代码你根本不可能知道 ArchSpinTrylock 和 ArchSpinLock的真正区别是什么. FUNCTION(ArchSpinTrylock) 尝试拿锁拿不到就撤mov r1 #1 r11mov r2 r0 r2 r0 ldrex r0 [r2] r2 lock-rawLock 即 r0 lock-rawLockcmp r0 #0 r0和0比较strexeq r0 r1 [r2] 尝试令lock-rawLock1成功写入则r00否则 r0 1dmb 数据存储隔离以保证缓冲中的数据已经落实到RAM中bx lr 跳回调用ArchSpinLock函数比较两段汇编代码可知ArchSpinTrylock即没有循环也不会让CPU进入睡眠直接返回了而ArchSpinLock会睡了醒 醒了睡一直守到丈夫( lock-rawLock 0的广播事件发生)回来才肯罢休. 笔者代码注释到这里那真是心潮澎湃心碎了老一地 真想给 ArchSpinLock 立一个贞节牌坊!
ArchSpinUnlock 汇编代码 FUNCTION(ArchSpinUnlock) 释放锁mov r1 #0 r10 dmb 数据存储隔离以保证缓冲中的数据已经落实到RAM中str r1 [r0] 令lock-rawLock 0dsb 数据同步隔离sev 给各CPU广播事件唤醒沉睡的CPU们bx lr 跳回调用ArchSpinLock函数代码中涉及到几个不常用的汇编指令一一说明:
汇编指令之 WFI / WFE / SEV
WFI(Wait for interrupt):等待中断到来指令. WFI一般用于cpuidleWFI 指令是在处理器发生中断或类似异常之前不需要做任何事情。
在鸿蒙源码分析系列篇(总目录)线程篇中已说过每个CPU都有自己的idle任务CPU没事干的时候就待在里面就一个死循环守着WFI指令有中断来了就触发CPU起床干活. 中断分硬中断和软中断系统调用就是通过软中断实现的而设备类的就属于硬中断都能触发CPU干活. 具体看下CPU空闲的时候在干嘛代码超级简单:
LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID) //CPU没事干的时候待在这里
{while (1) {//只有一个死循环Wfi();//WFI指令:arm core 立即进入low-power standby state等待中断进入休眠模式。}
}WFE(Wait for event):等待事件的到来指令WFE 指令是在SEV指令生成事件之前不需要执行任何操作所以用WFE的地方后续一定会对应一个SEV的指令去唤醒它. WFE的一个典型使用场景是用在自旋锁中spinlock的功能是在不同CPU core之间保护共享资源。使用WFE的流程是
开始之初资源空闲CPU核1 访问资源持有锁获得资源CPU核2 访问资源此时资源不空闲执行WFE指令让core进入low-power state(睡眠)CPU核1 释放资源释放锁释放资源同时执行SEV指令唤醒CPU核2CPU核2 获得资源
另外说一下 以往的自旋锁在获得不到资源时让CPU核进入死循环而通过插入WFE指令则大大节省功耗.
SEV(send event):发送事件指令SEV是一条广播指令它会将事件发送到多处理器系统中的所有处理器以唤醒沉睡的CPU.
SEV和WFE的实现很像设计模式的观察者模式.
汇编指令之 LDREX / STREX
LDREX用来读取内存中的值并标记对该段内存的独占访问
LDREX Rx [Ry] 上面的指令意味着读取寄存器Ry指向的4字节内存值将其保存到Rx寄存器中同时标记对Ry指向内存区域的独占访问。
如果执行LDREX指令的时候发现已经被标记为独占访问了并不会对指令的执行产生影响。
而STREX在更新内存数值时会检查该段内存是否已经被标记为独占访问并以此来决定是否更新内存中的值
STREX Rx Ry [Rz] 如果执行这条指令的时候发现已经被标记为独占访问了则将寄存器Ry中的值更新到寄存器Rz指向的内存并将寄存器Rx设置成0。指令执行成功后会将独占访问标记位清除。
而如果执行这条指令的时候发现没有设置独占标记则不会更新内存且将寄存器Rx的值设置成1。
一旦某条STREX指令执行成功后以后再对同一段内存尝试使用STREX指令更新的时候会发现独占标记已经被清空了就不能再更新了从而实现独占访问的机制。
编程实例
本实例实现如下流程。
任务Example_TaskEntry初始化自旋锁创建两个任务Example_SpinTask1、Example_SpinTask2分别运行于两个核。Example_SpinTask1、Example_SpinTask2中均执行申请自旋锁的操作同时为了模拟实际操作在持有自旋锁后进行延迟操作最后释放自旋锁。300Tick后任务Example_TaskEntry被调度运行删除任务Example_SpinTask1和Example_SpinTask2。
#include los_spinlock.h
#include los_task.h/* 自旋锁句柄id */
SPIN_LOCK_S g_testSpinlock;
/* 任务ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;VOID Example_SpinTask1(VOID)
{UINT32 i;UINTPTR intSave;/* 申请自旋锁 */dprintf(task1 try to get spinlock\n);LOS_SpinLockSave(g_testSpinlock intSave);dprintf(task1 got spinlock\n);for(i 0; i 5000; i) {asm volatile(nop);}/* 释放自旋锁 */dprintf(task1 release spinlock\n);LOS_SpinUnlockRestore(g_testSpinlock intSave);return;
}VOID Example_SpinTask2(VOID)
{UINT32 i;UINTPTR intSave;/* 申请自旋锁 */dprintf(task2 try to get spinlock\n);LOS_SpinLockSave(g_testSpinlock intSave);dprintf(task2 got spinlock\n);for(i 0; i 5000; i) {asm volatile(nop);}/* 释放自旋锁 */dprintf(task2 release spinlock\n);LOS_SpinUnlockRestore(g_testSpinlock intSave);return;
}UINT32 Example_TaskEntry(VOID)
{UINT32 ret;TSK_INIT_PARAM_S stTask1;TSK_INIT_PARAM_S stTask2;/* 初始化自旋锁 */LOS_SpinInit(g_testSpinlock);/* 创建任务1 */memset(stTask1 0 sizeof(TSK_INIT_PARAM_S));stTask1.pfnTaskEntry (TSK_ENTRY_FUNC)Example_SpinTask1;stTask1.pcName SpinTsk1;stTask1.uwStackSize LOSCFG_TASK_MIN_STACK_SIZE;stTask1.usTaskPrio 5;
#ifdef LOSCFG_KERNEL_SMP/* 绑定任务到CPU0运行 */stTask1.usCpuAffiMask CPUID_TO_AFFI_MASK(0);
#endifret LOS_TaskCreate(g_testTaskId01 stTask1);if(ret ! LOS_OK) {dprintf(task1 create failed .\n);return LOS_NOK;}/* 创建任务2 */memset(stTask2 0 sizeof(TSK_INIT_PARAM_S));stTask2.pfnTaskEntry (TSK_ENTRY_FUNC)Example_SpinTask2;stTask2.pcName SpinTsk2;stTask2.uwStackSize LOSCFG_TASK_MIN_STACK_SIZE;stTask2.usTaskPrio 5;
#ifdef LOSCFG_KERNEL_SMP/* 绑定任务到CPU1运行 */stTask1.usCpuAffiMask CPUID_TO_AFFI_MASK(1);
#endifret LOS_TaskCreate(g_testTaskId02 stTask2);if(ret ! LOS_OK) {dprintf(task2 create failed .\n);return LOS_NOK;}/* 任务休眠300Ticks */LOS_TaskDelay(300);/* 删除任务1 */ret LOS_TaskDelete(g_testTaskId01);if(ret ! LOS_OK) {dprintf(task1 delete failed .\n);return LOS_NOK;}/* 删除任务2 */ret LOS_TaskDelete(g_testTaskId02);if(ret ! LOS_OK) {dprintf(task2 delete failed .\n);return LOS_NOK;}return LOS_OK;
}运行结果
task2 try to get spinlock
task2 got spinlock
task1 try to get spinlock
task2 release spinlock
task1 got spinlock
task1 release spinlock总结
自旋锁用于解决CPU核间竞争资源的问题因为自旋锁会让CPU陷入睡眠状态所以锁的代码不能太长否则容易导致意外出现也影响性能.必须由汇编代码实现因为C语言写不出让CPU进入真正睡眠核间竞争的代码.
经常有很多小伙伴抱怨说不知道学习鸿蒙开发哪些技术不知道需要重点掌握哪些鸿蒙应用开发知识点
为了能够帮助到大家能够有规划的学习这里特别整理了一套纯血版鸿蒙HarmonyOS Next全栈开发技术的学习路线包含了鸿蒙开发必掌握的核心知识要点内容有ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等鸿蒙HarmonyOS NEXT技术知识点。 《鸿蒙 (Harmony OS)开发学习手册》共计892页
如何快速入门
1.基本概念 2.构建第一个ArkTS应用 3.…… 开发基础知识:
1.应用基础知识 2.配置文件 3.应用数据管理 4.应用安全管理 5.应用隐私保护 6.三方应用调用管控机制 7.资源分类与访问 8.学习ArkTS语言 9.…… 基于ArkTS 开发
1.Ability开发 2.UI开发 3.公共事件与通知 4.窗口管理 5.媒体 6.安全 7.网络与链接 8.电话服务 9.数据管理 10.后台任务(Background Task)管理 11.设备管理 12.设备使用信息统计 13.DFX 14.国际化开发 15.折叠屏系列 16.…… 鸿蒙开发面试真题含参考答案 OpenHarmony 开发环境搭建
《OpenHarmony源码解析》
搭建开发环境Windows 开发环境的搭建Ubuntu 开发环境搭建Linux 与 Windows 之间的文件共享……系统架构分析构建子系统启动流程子系统分布式任务调度子系统分布式通信子系统驱动子系统…… OpenHarmony 设备开发学习手册 写在最后
如果你觉得这篇内容对你还蛮有帮助我想邀请你帮我三个小忙
点赞转发有你们的 『点赞和评论』才是我创造的动力。关注小编同时可以期待后续文章ing不定期分享原创知识。想要获取更多完整鸿蒙最新学习资源请移步前往