本篇为快锁下篇,说清楚快锁在内核态的实现,解答以下问题,它们在上篇的末尾被提出来。
64
个,除去两个内核进程外,剩下的都归属用户进程,理论上用户进程可以创建很多快锁,这些快锁可以用于进程间(共享快锁)也可以用于线程间(私有快锁),在快锁的生命周期中该如何保存?系列篇多次提过,线程在内核层面叫任务,在内核任务比进程重要得多,调度也好,竞争也罢,都是围绕任务展开的。竞争快锁是任务间的竞争,自然会和任务(task
)有紧密的联系,其在内核的表达也出现在了任务表达之中。
typedef struct { // 任务控制块
...
LOS_DL_LIST pendList; /**< Task pend node | 如果任务阻塞时就通过它挂到各种阻塞情况的链表上,比如OsTaskWait时 */
...
FutexNode futex; ///< 指明任务在等待哪把快锁,一次只等一锁,锁和任务的关系是(1:N)关系
} LosTaskCB;
对 任务 不清楚的请翻看系列相关篇,一定要搞懂,它是内核最重要的概念,甚至没有之一,搞不懂任务就一定搞不懂内核整体的运行机制。
FutexNode
(快锁节点) 是快锁模块核心结构体,熟悉这块源码的钥匙。
typedef struct {
UINTPTR key; /* private:uvaddr | 私有锁,用虚拟地址 shared:paddr | 共享锁,用物理地址 */
UINT32 index; /* hash bucket index | 哈希桶索引 OsFutexKeyToIndex */
UINT32 pid; /* private:process id shared:OS_INVALID(-1) | 私有锁:进程ID , 共享锁为 -1 */
LOS_DL_LIST pendList; /* point to pendList in TCB struct | 指向 TCB 结构中的 pendList, 通过它找到任务*/
LOS_DL_LIST queueList; /* thread list blocked by this lock | 挂等待这把锁的任务,其实这里挂到是FutexNode.queueList , 通过 queueList 可以找到 pendList ,通过 pendList又可以找到真正的任务*/
LOS_DL_LIST futexList; /* point to the next FutexNode | 下一把快锁节点*/
} FutexNode;
解读
key
就是快锁,它们的关系是 1:N
的关系 ,快锁分成了 私有锁 和 共享锁 两种类型。用key
表示唯一性。共享锁用物理地址 , 私有锁用虚拟地址。为什么要这么做呢 ?N: 1
,不清楚的请查看系列篇之内存映射相关篇。index
内核使用哈希桶来检索快锁 , index
和 key
的关系通过哈希算法(FNV-1a
)来映射。注意会有同一个哈希桶中两个key
一样的锁,虽然它会以极低概率出现。快锁的内核实现代码部分,个人觉得可以优化的空间很大,应好好测试下这块 ,说不定会有意想不到的 bug
: ) 。pid
指快锁节点进程归属,作用于私有锁。pendList
指向 LosTaskCB.pendList
, 通过它去唤醒和挂起任务,但并没有在源码中看到指向动作,如有看到的请告诉站长(wx: rekaily
)。queueList
具有相同key
值的节点被queue_list
串联起来表示被同一把锁阻塞的任务队列,意思就是queueList
上面挂的都是等值为相同key
的快锁,并按任务的优先级排好序。任务优先级高的可以先获取快锁使用权。futexList
指向下一把快锁, 虽然挂的也是 FutexNode
,但是意义不一样 ! 是指queueList
链表上的首个快锁节点,即不同key
的快锁。能理解吗 ? 好吧 ,我承认这里面有点绕 。当用户态产生锁的竞争或释放需要进行相关线程的调度操作时,会触发Futex
系统调用进入内核,此时会将用户态锁的地址传入内核,并在内核的Futex
中以锁地址来区分用户态的每一把锁,因为用户态可用虚拟地址空间为1GiB
,为了便于查找、管理,内核Futex
采用哈希桶来存放用户态传入的锁。
哈希桶共有80
个,0~63
号桶用于存放私有锁(以虚拟地址进行哈希),64~79
号桶用于存放共享锁(以物理地址进行哈希),所有相同的 key
都掉进了同一个桶里。私有/共享属性通过用户态锁的初始化以及Futex
系统调用入参确定。
#define FUTEX_INDEX_PRIVATE_MAX 64 ///< 0~63号桶用于存放私有锁(以虚拟地址进行哈希),同一进程不同线程共享futex变量,表明变量在进程地址空间中的位置
///< 它告诉内核,这个futex是进程专有的,不可以与其他进程共享。它仅仅用作同一进程的线程间同步。
#define FUTEX_INDEX_SHARED_MAX 16 ///< 64~79号桶用于存放共享锁(以物理地址进行哈希),不同进程间通过文件共享futex变量,表明该变量在文件中的位置
#define FUTEX_INDEX_MAX (FUTEX_INDEX_PRIVATE_MAX + FUTEX_INDEX_SHARED_MAX) ///< 80个哈希桶
#define FUTEX_INDEX_SHARED_POS FUTEX_INDEX_PRIVATE_MAX ///< 共享锁开始位置
FutexHash g_futexHash[FUTEX_INDEX_MAX];///< 默认80个哈希桶
typedef struct {
LosMux listLock;///< 内核操作lockList的互斥锁
LOS_DL_LIST lockList;///< 用于挂载 FutexNode (Fast userspace mutex,用户态快速互斥锁)
} FutexHash;
欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。
`欢迎大家关注公众号<程序猿百晓生>,可以了解到以下知识点。`
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案)
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......
下图来源于官方文档,基本能准确的描述管理方式,暂且使用此图(后续可能重画) , 有了这张图理解上面FutexNode
会更轻松
OsFutexWaitTask
,无非就是根据任务的优先级调整queueList
futexList
queueList
这些链表上的位置 /// 将当前任务挂入等待链表中
STATIC INT32 OsFutexWaitTask(const UINT32 *userVaddr, const UINT32 flags, const UINT32 val, const UINT32 timeOut)
{
INT32 futexRet;
UINT32 intSave, lockVal;
LosTaskCB *taskCB = NULL;
FutexNode *node = NULL;
UINTPTR futexKey = OsFutexFlagsToKey(userVaddr, flags);//通过地址和flags 找到 key
UINT32 index = OsFutexKeyToIndex(futexKey, flags);//通过key找到哈希桶
FutexHash *hashNode = &g_futexHash[index];
if (OsFutexLock(&hashNode->listLock)) {//操作快锁节点链表前先上互斥锁
return LOS_EINVAL;
}
//userVaddr必须是用户空间虚拟地址
if (LOS_ArchCopyFromUser(&lockVal, userVaddr, sizeof(UINT32))) {//将值拷贝到内核空间
PRINT_ERR("Futex wait param check failed! copy from user failed!\n");
futexRet = LOS_EINVAL;
goto EXIT_ERR;
}
if (lockVal != val) {//对参数内部逻辑检查
futexRet = LOS_EBADF;
goto EXIT_ERR;
}
//注意第二个参数 FutexNode *node = NULL
if (OsFutexInsertTaskToHash(&taskCB, &node, futexKey, flags)) {// node = taskCB->futex
futexRet = LOS_NOK;
goto EXIT_ERR;
}
SCHEDULER_LOCK(intSave);
OsTaskWaitSetPendMask(OS_TASK_WAIT_FUTEX, futexKey, timeOut);
OsSchedTaskWait(&(node->pendList), timeOut, FALSE);
OsSchedLock();
LOS_SpinUnlock(&g_taskSpin);
futexRet = OsFutexUnlock(&hashNode->listLock);//
if (futexRet) {
OsSchedUnlock();
LOS_IntRestore(intSave);
goto EXIT_UNLOCK_ERR;
}
LOS_SpinLock(&g_taskSpin);
OsSchedUnlock();
/*
* it will immediately do the scheduling, so there's no need to release the
* task spinlock. when this task's been rescheduled, it will be holding the spinlock.
*/
OsSchedResched();
if (taskCB->taskStatus & OS_TASK_STATUS_TIMEOUT) {
taskCB->taskStatus &= ~OS_TASK_STATUS_TIMEOUT;
SCHEDULER_UNLOCK(intSave);
return OsFutexDeleteTimeoutTaskNode(hashNode, node);
}
SCHEDULER_UNLOCK(intSave);
return LOS_OK;
EXIT_ERR:
(VOID)OsFutexUnlock(&hashNode->listLock);
EXIT_UNLOCK_ERR:
return futexRet;
}
queueList
上挂起任务唤醒,可详细跟踪函数OsFutexWaitTask
,如果没有任务再等锁了就DeleteKey
STATIC INT32 OsFutexWakeTask(UINTPTR futexKey, UINT32 flags, INT32 wakeNumber, FutexNode **newHeadNode, BOOL *wakeAny)
{
UINT32 intSave;
FutexNode *node = NULL;
FutexNode *headNode = NULL;
UINT32 index = OsFutexKeyToIndex(futexKey, flags);
FutexHash *hashNode = &g_futexHash[index];
FutexNode tempNode = { //先组成一个临时快锁节点,目的是为了找到哈希桶中是否有这个节点
.key = futexKey,
.index = index,
.pid = (flags & FUTEX_PRIVATE) ? LOS_GetCurrProcessID() : OS_INVALID,
};
node = OsFindFutexNode(&tempNode);//找快锁节点
if (node == NULL) {
return LOS_EBADF;
}
headNode = node;
SCHEDULER_LOCK(intSave);
OsFutexCheckAndWakePendTask(headNode, wakeNumber, hashNode, newHeadNode, wakeAny);//再找到等这把锁的唤醒指向数量的任务
if ((*newHeadNode) != NULL) {
OsFutexReplaceQueueListHeadNode(headNode, *newHeadNode);
OsFutexDeinitFutexNode(headNode);
} else if (headNode->index < FUTEX_INDEX_MAX) {
OsFutexDeleteKeyFromFutexList(headNode);
OsFutexDeinitFutexNode(headNode);
}
SCHEDULER_UNLOCK(intSave);
return LOS_OK;
}
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。