摘要:除了多核的自旋锁机制,本文会介绍下LiteOS 5.0引入的LockDep死锁检测个性。

2020年12月公布的LiteOS 5.0推出了全新的内核,反对SMP多核调度性能。想学习SMP多核调度性能,须要理解下SpinLock自旋锁。除了多核的自旋锁机制,本文还会介绍下LiteOS 5.0引入的LockDep死锁检测个性。

本文中所波及的LiteOS源码,均能够在LiteOS开源站点https://gitee.com/LiteOS/LiteOS 获取。

自旋锁SpinLock源代码、开发文档,LockDep死锁检测个性代码文档列表如下:

  • kernelincludelos_spinlock.h 自旋锁头文件

    网页获取自旋锁源码 https://gitee.com/LiteOS/Lite...。

  • spinlock.S、arch/spinlock.h 自旋锁汇编代码文件及头文件

    针对不同的CPU架构,有两套代码。因为自旋锁实用于多核,M核架构archarmcortex_m下不蕴含自旋锁的汇编文件。如下:

    • archarmcortex_a_r架构

      • 汇编代码文件
        https://gitee.com/LiteOS/Lite...。
      • 头文件
        https://gitee.com/LiteOS/Lite...。
    • archarm64架构

      • 汇编代码文件 https://gitee.com/LiteOS/Lite...。
      • 头文件
        https://gitee.com/LiteOS/Lite...。
  • 开发指南自旋锁文档

    在线文档
    https://gitee.com/LiteOS/Lite...。

  • LockDep死锁检测

    死锁检测代码蕴含:

    • 头文件
      https://gitee.com/LiteOS/Lite...
    • C代码文件
      https://gitee.com/LiteOS/Lite...。

咱们首先来看看自旋锁。

1、SpinLock 自旋锁

在多核环境中,因为应用雷同的内存空间,存在对同一资源进行拜访的状况,所以须要互斥拜访机制来保障同一时刻只有一个核进行操作。自旋锁就是这样的一种机制。

自旋锁是指当一个线程在获取锁时,如果锁曾经被其它线程获取,那么该线程将循环期待,并一直判断是否可能胜利获取锁,直到获取到锁才会退出循环。因而倡议爱护耗时较短的操作,避免对系统整体性能有显著的影响。

自旋锁与互斥锁比拟相似,它们都是为了解决对共享资源的互斥应用问题。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个持有者。然而两者在调度机制上略有不同,对于互斥锁,如果锁曾经被占用,锁申请者会被阻塞;然而自旋锁不会引起调用者阻塞,会始终循环检测自旋锁是否曾经被开释。自旋锁用于多核不同CPU核查资源的互斥拜访,互斥锁用于同一CPU核内不同工作对资源的互斥拜访。

自旋锁SpinLock外围的代码都在kernelincludelos_spinlock.h头文件中,蕴含struct Spinlock构造体定义、一些inline内联函数LOS_SpinXXX,还有一些LockDep死锁检测相干的宏定义LOCKDEP_XXXX

1.1 Spinlock 自旋锁构造体

自旋锁构造体Spinlock定义如下,次要的成员变量为size_t rawLock,这是自旋锁是否占用持有的胜利的标记:为0时,锁没有被持有,为1时示意被胜利持有。当开启LockDep死锁检测调测个性时,会使能另外3个成员变量,记录持有自旋锁的CPU核信息、工作信息。

struct Spinlock {    size_t      rawLock;            /**< 原始自旋锁 */#ifdef LOSCFG_KERNEL_SMP_LOCKDEP    UINT32      cpuid;              /**< 死锁检测个性开启时,持有自旋锁的CPU核 */    VOID        *owner;             /**< 死锁检测个性开启时,持有自旋锁的工作的TCB指针 */    const CHAR  *name;              /**< 死锁检测个性开启时,持有自旋锁的工作的名称 */#endif};

1.2 Spinlock 自旋锁罕用函数接口

LiteOS自旋锁模块为用户提供上面几种性能,蕴含自旋锁初始化,申请/开释,查问自旋锁状态等。自旋锁相干的函数、宏定义只反对SMP - Symmetric MultiProcessor模式,当单核UP - UniProcessor时,函数不失效。接口详细信息能够查看API参考。

1.2.1 自旋锁初始化

自旋锁初始化的内联函数如下,其中参数SPIN_LOCK_S *lock,即自旋锁构造体指针,其中SPIN_LOCK_SSpinlock的typedef别名,在kernelincludelos_lockdep.h文件中定义的。

自旋锁初始时,会把自旋锁标记为0:lock->rawLock = 0,当开启死锁检测个性时,也会做相应的初始化。

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_SpinInit(SPIN_LOCK_S *lock){    lock->rawLock     = 0;#ifdef LOSCFG_KERNEL_SMP_LOCKDEP    lock->cpuid       = (UINT32)-1;    lock->owner       = SPINLOCK_OWNER_INIT;    lock->name        = "spinlock";#endif}

LOS_SpinInit()是动静初始化的自旋锁,LiteOS还提供了动态初始化自旋锁的办法SPIN_LOCK_INIT(lock):

define SPIN_LOCK_INIT(lock) SPIN_LOCK_S lock = SPIN_LOCK_INITIALIZER(lock)

1.2.2 申请/开释自旋锁

初始化自旋锁后,能够以SPIN_LOCK_S *lock为参数申请、开释自旋锁。自旋锁的这些函数中,调用的LOCKDEP_结尾函数是死锁检测的函数,后文会具体讲述。外围的3个函数由汇编语言编写,这些汇编函数存,依据不同的CPU架构,能够在文件archarmcortex_a_rsrcspinlock.Sarcharm64srcspinlock.S中查看,此文不再具体讲述其汇编代码。

ArchSpinLock(&lock->rawLock); // 汇编语言编写的 申请自旋锁的函数
ArchSpinUnlock(&lock->rawLock); // 汇编语言编写的 开释自旋锁的函数
ArchSpinTrylock(&lock->rawLock); // 汇编语言编写的 尝试申请自旋锁的函数

  • STATIC INLINE VOID LOS_SpinLock(SPIN_LOCK_S *lock) 申请自旋锁

该函数尝试申请自旋锁,如果自旋锁锁被其余核占用,则循环期待,直至其余核开释自旋锁。

咱们看下代码首先执行⑴处代码,暂停任务调度,而后执行汇编函数ArchSpinLock(&lock->rawLock)申请自旋锁。

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_SpinLock(SPIN_LOCK_S *lock){⑴  LOS_TaskLock();    LOCKDEP_CHECK_IN(lock);⑵  ArchSpinLock(&lock->rawLock);    LOCKDEP_RECORD(lock);}
  • STATIC INLINE VOID LOS_SpinUnlock(SPIN_LOCK_S *lock) 开释自旋锁

开释自旋锁LOS_SpinUnlock(SPIN_LOCK_S *lock)须要和申请自旋锁的函数LOS_SpinLock(SPIN_LOCK_S *lock)成对应用。执行⑴处汇编函数ArchSpinUnlock(&lock->rawLock)开释自旋锁,而后执行⑵复原任务调度性能。

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_SpinUnlock(SPIN_LOCK_S *lock){    LOCKDEP_CHECK_OUT(lock);⑴  ArchSpinUnlock(&lock->rawLock);⑵  LOS_TaskUnlock();}
  • STATIC INLINE INT32 LOS_SpinTrylock(SPIN_LOCK_S *lock) 尝试申请自旋锁

尝试申请指定的自旋锁,如果无奈获取锁,间接返回失败,而不会始终循环期待。用户依据返回值,判断是否胜利申请到自旋锁,而后再做后续业务解决。和函数LOS_SpinLock(SPIN_LOCK_S *lock)执行的汇编函数不同,该函数调用的汇编函数为ArchSpinTrylock(&lock->rawLock),并有返回值。

LITE_OS_SEC_ALW_INLINE STATIC INLINE INT32 LOS_SpinTrylock(SPIN_LOCK_S *lock){    LOS_TaskLock();    LOCKDEP_CHECK_IN(lock);⑴  INT32 ret = ArchSpinTrylock(&lock->rawLock);    if (ret == LOS_OK) {        LOCKDEP_RECORD(lock);    }    return ret;}

1.2.3 申请/开释自旋锁(同时进行关中断爱护)

LiteOS 还提供一对反对关中断爱护的申请/开释指定自旋锁的函数,除了参数SPIN_LOCK_S *lock,还须要参数UINT32 *intSave用于关中断、复原中断。LOS_SpinLockSave()LOS_SpinUnlockRestore()必须成对应用。

  • STATIC INLINE VOID LOS_SpinLockSave(SPIN_LOCK_S lock, UINT32 intSave) 关中断后,再申请指定的自旋锁

从代码中,能够看出首先执行LOS_IntLock()关中断,而后再调用LOS_SpinLock(lock)申请自旋锁。

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_SpinLockSave(SPIN_LOCK_S *lock, UINT32 *intSave){    *intSave = LOS_IntLock();    LOS_SpinLock(lock);}
  • STATIC INLINE VOID LOS_SpinUnlockRestore(SPIN_LOCK_S lock, UINT32 intSave) 关中断后,再申请指定的自旋锁

值。

从代码中,能够看出首先调用LOS_SpinUnlock(lock)开释自旋锁,而后再调用LOS_IntRestore(intSave)复原中断。

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_SpinUnlockRestore(SPIN_LOCK_S *lock, UINT32 intSave){    LOS_SpinUnlock(lock);    LOS_IntRestore(intSave);}

1.2.4 获取自旋锁持有状态

能够应用函数BOOL LOS_SpinHeld(const SPIN_LOCK_S *lock)查问自旋锁的持有状态,返回TRUE,自旋锁锁被持有,返回FALSE时示意没有被持有:

LITE_OS_SEC_ALW_INLINE STATIC INLINE BOOL LOS_SpinHeld(const SPIN_LOCK_S *lock){    return (lock->rawLock != 0);}

2、LockDep 死锁检测调测个性

LockDepLock Dependency Check的缩写,是内核的一种死锁检测机制。这个调测个性默认是敞开的,如果须要该调测个性,须要使能宏定义LOSCFG_KERNEL_SMP_LOCKDEP。当检测到死锁谬误时,会打印产生死锁的自旋锁的相干信息,打印backtrace回溯栈信息。

2.1 LockDep 自旋锁的谬误类型及构造体定义

在文件kernelincludelos_lockdep.h中定义了死锁的枚举类型LockDepErrTypeHeldLocks构造体。

自旋锁的谬误类型有double lock反复申请锁、dead lock死锁、unlock without lock开释未持有的锁、lockdep overflow死锁检测溢出,超出定义的MAX_LOCK_DEPTH

构造体LockDep是工作LosTaskCB构造体的开启LOSCFG_KERNEL_SMP_LOCKDEP时的一个成员变量,记录该工作持有的自旋锁、须要申请的自旋锁的信息。构造体HeldLocks记录持有的自旋锁的详细信息,各个成员变量见如下正文:

typedef struct Spinlock SPIN_LOCK_S;#define MAX_LOCK_DEPTH  16Uenum LockDepErrType {    LOCKDEP_SUCEESS = 0,    LOCKDEP_ERR_DOUBLE_LOCK, // double lock 反复申请锁    LOCKDEP_ERR_DEAD_LOCK,  // dead lock 死锁    LOCKDEP_ERR_UNLOCK_WITHOUT_LOCK, // unlock without lock 开释未持有的锁    LOCKDEP_ERR_OVERFLOW, // lockdep overflow 死锁检测溢出};typedef struct {    VOID *lockPtr; // Spinlock 自旋锁的内存地址    VOID *lockAddr; // 申请锁的函数的返回地址    UINT64 waitTime; // 抢占申请自旋锁的等待时间    UINT64 holdTime;  // 持有自旋锁的工夫} HeldLocks;typedef struct {    VOID *waitLock; // 工作申请占用的自旋锁Spinlock    INT32 lockDepth; // 自旋锁的深度    HeldLocks heldLocks[MAX_LOCK_DEPTH]; // 持有的自旋锁详细信息数组} LockDep;

2.2 LockDep 死锁检测的罕用函数接口

LockDep 死锁检测个性提供了3个函数接口,在申请自旋锁前、胜利申请到自旋锁后、开释自旋锁后打点调用。另外,提供了一些其余罕用函数接口。

咱们先看下,死锁检测函数如何记录等待时间waitTime、持有工夫holdTime的。在申请自旋锁前调用OsLockDepCheckIn(),记录waitTime的终点;胜利申请到自旋锁后,调用OsLockDepRecord()记录waitTime的完结点,同时记录记录holdTime的终点;开释自旋锁后调用OsLockDepCheckOut()记录holdTime的完结点。如图所示:

2.2.1 OsLockDepCheckIn(const SPIN_LOCK_S *lock) 记录申请自旋锁

咱们一起剖析下代码,看看申请自旋锁前死锁检测个性做了哪些操作。⑴处代码获取申请自旋锁的函数返回地址。⑵获取当前任务的TCB,而后获取它的死锁检测成员LockDep *lockDep。⑶、⑽处两个函数配对应用,前者先关中断,而后期待、占用死锁检测个性、设置STATIC Atomic g_lockdepAvailable为0,后者开释锁检测个性,设置STATIC Atomic g_lockdepAvailable为1,而后复原中断。

⑷处代码判断当前任务持有的自旋锁是否超过死锁检测个性设置的自旋锁数量的最大值MAX_LOCK_DEPTH,如果超过,则报溢出谬误,跳转到OUT继续执行。⑸处代码,如果申请的自旋锁没有被任何CPU核持有,能够间接占有,无需期待,跳转到OUT继续执行。⑹处代码,如果申请的自旋锁被当前任务持有,则报反复申请自旋锁谬误,跳转到OUT继续执行。⑺处判断是否产生死锁,稍后再剖析函数OsLockDepCheckDependancy()

⑻处代码,如果检测后果通过,能够持有自旋锁,则记录相干信息,蕴含要申请的自旋锁、申请锁的函数返回地址、申请自旋锁的开始工夫。否则执行⑼处代码,输入死锁错误信息。

VOID OsLockDepCheckIn(const SPIN_LOCK_S *lock){    UINT32 intSave;    enum LockDepErrType checkResult = LOCKDEP_SUCEESS;⑴  VOID *requestAddr = (VOID *)__builtin_return_address(0);⑵  LosTaskCB *current = OsCurrTaskGet();    LockDep *lockDep = &current->lockDep;    LosTaskCB *lockOwner = NULL;    if (lock == NULL) {        return;    }⑶   OsLockDepRequire(&intSave);⑷  if (lockDep->lockDepth >= (INT32)MAX_LOCK_DEPTH) {        checkResult = LOCKDEP_ERR_OVERFLOW;        goto OUT;    }    lockOwner = lock->owner;⑸  if (lockOwner == SPINLOCK_OWNER_INIT) {        goto OUT;    }⑹  if (current == lockOwner) {        checkResult = LOCKDEP_ERR_DOUBLE_LOCK;        goto OUT;    }⑺  if (OsLockDepCheckDependancy(current, lockOwner) != TRUE) {        checkResult = LOCKDEP_ERR_DEAD_LOCK;        goto OUT;    }OUT:⑻  if (checkResult == LOCKDEP_SUCEESS) {        lockDep->waitLock = (SPIN_LOCK_S *)lock;        lockDep->heldLocks[lockDep->lockDepth].lockAddr = requestAddr;        lockDep->heldLocks[lockDep->lockDepth].waitTime = OsLockDepGetCycles(); /* start time */    } else {⑼      OsLockDepDumpLock(current, lock, requestAddr, checkResult);    }⑽  OsLockDepRelease(intSave);}

咱们再剖析下死锁检测的函数OsLockDepCheckDependancy(),循环判断嵌套申请的自旋锁是否会产生死锁,蕴含2个参数,第一个参数是申请自旋锁的工作LosTaskCB *current,第二个参数为持有自旋锁的工作LosTaskCB *lockOwner

⑴处代码,如果申请自旋锁的工作和持有锁的工作同一个,则产生死锁。⑵处代码,如果持有自旋锁的工作,还在申请其余自旋锁,则把lockOwner指向其余自旋锁的工作TCB,否则退出循环。⑶如果自旋锁被占用则始终循环。

STATIC BOOL OsLockDepCheckDependancy(const LosTaskCB *current, const LosTaskCB *lockOwner){    BOOL checkResult = TRUE;    const SPIN_LOCK_S *lockTemp = NULL;    do {⑴      if (current == lockOwner) {            checkResult = FALSE;            return checkResult;        }⑵      if (lockOwner->lockDep.waitLock != NULL) {            lockTemp = lockOwner->lockDep.waitLock;            lockOwner = lockTemp->owner;        } else {            break;        }⑶  } while (lockOwner != SPINLOCK_OWNER_INIT);    return checkResult;}

死锁检测TCB、LockDep、Spinlock关系示意图:

2.2.2 OsLockDepRecord(const SPIN_LOCK_S *lock) 记录申请到的自旋锁

咱们持续剖析,当申请自旋锁后,死锁检测个性做了哪些操作。⑴处代码获取零碎运行以来的cycle数目,而后计算waitTime,即从开始申请自旋锁到申请到自旋锁之前的cycle数目,同时记录持有自旋锁的holdTime的开始工夫。⑵处代码更新自旋锁的信息,锁被当前任务持有,CPU核设置为以后核。⑶处更新死锁检测lockDep的信息,持有锁的数目加1,期待锁置空。

VOID OsLockDepRecord(SPIN_LOCK_S *lock){    UINT32 intSave;    UINT64 cycles;    LosTaskCB *current = OsCurrTaskGet();    LockDep *lockDep = &current->lockDep;    HeldLocks *heldlock = &lockDep->heldLocks[lockDep->lockDepth];    if (lock == NULL) {        return;    }    OsLockDepRequire(&intSave);⑴  cycles = OsLockDepGetCycles();    heldlock->waitTime = cycles - heldlock->waitTime;    heldlock->holdTime = cycles;⑵  lock->owner = current;    lock->cpuid = ArchCurrCpuid();⑶  heldlock->lockPtr = lock;    lockDep->lockDepth++;    lockDep->waitLock = NULL;    OsLockDepRelease(intSave);}

2.2.3 OsLockDepCheckOut(const SPIN_LOCK_S *lock) 记录开释自旋锁

咱们再剖析下,当开释自旋锁后,死锁检测个性做了哪些操作。⑴处代码示意,当开释一个没有占用的自旋锁,会调用函数OsLockDepDumpLock()打印死锁检测错误信息。⑵处代码先获取持有锁的工作TCB的死锁检测变量lockDep,而后获取其持有锁数组的起始地址,即指针变量heldlocks。⑶获取持有锁的数目,而后执行⑷,对持有的锁进行循环遍历,定位到自旋锁*lock的数组索引,再执行⑸处代码更新持有锁的总工夫。

⑹处代码,判断如果开释的锁,不是工作持有锁数组的最初一个,则挪动数组前面的元素,数组元素也须要缩小1。最初,执行⑺更新自旋锁的没有被任何CPU核、任何工作占用。

VOID OsLockDepCheckOut(SPIN_LOCK_S *lock){    UINT32 intSave;    INT32 depth;    VOID *requestAddr = (VOID *)__builtin_return_address(0);      LosTaskCB *current = OsCurrTaskGet();    LosTaskCB *owner = NULL;    LockDep *lockDep = NULL;    HeldLocks *heldlocks = NULL;    if (lock == NULL) {        return;    }    OsLockDepRequire(&intSave);    owner = lock->owner;⑴  if (owner == SPINLOCK_OWNER_INIT) {        OsLockDepDumpLock(current, lock, requestAddr, LOCKDEP_ERR_UNLOCK_WITHOUT_LOCK);        goto OUT;    }    lockDep = &owner->lockDep;⑵  heldlocks = &lockDep->heldLocks[0];⑶  depth = lockDep->lockDepth;    while (depth-- >= 0) {⑷      if (heldlocks[depth].lockPtr == lock) {            break;        }    }    LOS_ASSERT(depth >= 0);⑸  heldlocks[depth].holdTime = OsLockDepGetCycles() - heldlocks[depth].holdTime;⑹  while (depth < lockDep->lockDepth - 1) {        lockDep->heldLocks[depth] = lockDep->heldLocks[depth + 1];        depth++;    }    lockDep->lockDepth--;⑺  lock->cpuid = (UINT32)(-1);    lock->owner = SPINLOCK_OWNER_INIT;OUT:    OsLockDepRelease(intSave);}

2.2.4 OsLockdepClearSpinlocks(VOID) 开释持有的自旋锁

该函数OsLockdepClearSpinlocks()会全副开释当前任务持有的自旋锁。在archarmcortex_a_rsrcfault.c文件中,异样处理函数OsExcHandleEntry()通过调用LOCKDEP_CLEAR_LOCKS()实现对该函数的调用。

⑴处代码获取当前任务死锁检测变量lockDep,而后⑵处循环变量持有的自旋锁,获取自旋锁并调用LOS_SpinUnlock()进行开释。

VOID OsLockdepClearSpinlocks(VOID){    LosTaskCB *task = OsCurrTaskGet();⑴  LockDep *lockDep = &task->lockDep;    SPIN_LOCK_S *lock = NULL;    while (lockDep->lockDepth) {⑵      lock = lockDep->heldLocks[lockDep->lockDepth - 1].lockPtr;        LOS_SpinUnlock(lock);    }}

小结

本文率领大家一起分析了SpinLock自旋锁,LockDep死锁检测个性的源代码,联合解说,参考官网示例程序代码,本人写写程序,理论编译运行一下,加深了解。

感激浏览,如有任何问题、倡议,都能够留言给咱们: https://gitee.com/LiteOS/Lite... 。为了更容易找到LiteOS代码仓,倡议拜访 https://gitee.com/LiteOS/LiteOS ,关注Watch、点赞Star、并Fork到本人账户下,如下图,谢谢。

本文分享自华为云社区《LiteOS内核源码剖析系列二 SpinLock自旋锁及LockDep死锁检测》,原文作者:zhushy。

点击关注,第一工夫理解华为云陈腐技术~