阻塞等待futex锁的线程在超时后,会有短暂的不一致状态,即:超时后内核定时器将其加入就绪队列准备调度,而其futex节点仍在锁的等待队列中;当线程重新运行后,才将节点摘出。这期间为不一致状态时间。为了适应这种情形,futex实现中有多处清理已醒节点的代码。由于这种不一致状态的不确定性,逻辑比较复杂易出问题。下面以WAIT操作为例简要分析几个关键环节。
OsFutexWaitTask
获取互斥锁保护以下操作过程。
OsFutexRecycleAndFindHeadNode
获取一次任务锁,调用OsFutexDeleteAlreadyWakeTaskAndGetNext
,删除已醒节点,遇到首个仍在等待的节点即停止,并返回这个firstNode节点。
OsFindAndInsertToHash
再获取一次任务锁,调用OsFutexInsertTasktoPendList
,将线程futex节点按优先级降序插入到firstNode领头的队列。
在2、3环节之间,futex操作未持有任务锁,因此定时器可能操作,前述不一致状态可能出现,也就是说,OsFutexInsertTasktoPendList
再操作的firstNode、tailNode都可能是已醒节点,其.pendList指向自身,得到的TCB将是错误数据:
LosTaskCB *taskHead = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&((*firstNode)->pendList)));
接着调用的OsFutexInsertFindFormBackToFront、OsFutexInsertFindFromFrontToBack也要删除已醒节点,甚至清空整个队列,那么计算线程TCB、优先级比较、for循环的正确性都可能出问题:
tempNode = OsFutexDeleteAlreadyWakeTaskAndGetNext(tempNode, NULL, FALSE);
taskTail = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(tempNode->pendList)));
阻塞等待futex锁的线程,按优先级降序加入锁的等待队列。但可能因为线程优先级的动态性,而使得队列的优先级是乱序。
futex的互斥锁属性采用的是默认值,即优先级继承协议,这样线程在持有互斥锁时,可能会因为别人(包括别的进程)也想获取这个互斥锁,而改变优先级,从而插入到错误的位置。
当REQUEUE操作的旧锁和新锁不是一个时,采取的是用一个获取一个的方法。这个时间差可能带来隐患。比如,已释放旧锁尚未获取新锁时,要转移的队列没有任何保护,其线程可能因超时被唤醒,并调用OsFutexDeleteTimeoutTaskNode
删除自己;即使已经获取了新锁,因超时线程持有的是旧锁,两者可能同时操作同一个节点。
解决这个问题最简单的办法是同时获取这两把锁,但这对于线程因超时需要安全删除自己来说仍是困难的:比如key1时阻塞,期间被REQUEUE为key2,再次REQUEUE为key3时超时醒来,线程只知道锁1,需要读键值确定删除是否安全;REQUEUE线程同时锁定锁2、锁3,需要修改键值;两者将不可避免地进行实际无锁保护的竞争。
目前的FutexNode数据结构,表示的基本都是锁的固有属性,只有.pendList表示的是阻塞场所,唯一地对应一个具体线程。.queueList完全可以替代.pendList的功能。
建议把现在绑定到TCB的futexNode独立出来,作为一个动态分配的数据结构表示一把用户锁,需要时创建并连接到futex哈希表。线程WAIT、WAKE时,均针对该结构进行操作,象通常的阻塞、唤醒一样,直接用TCB的.pendList成员连接、摘出阻塞链表(.queueList)。动态分配的用户锁可以在进程退出时销毁。
这样区分后,上述问题一和问题三的第二部分就不存在了,futex操作简化很多(节点清理删除、队列头替换等均不需要了)。
以上仅是静态分析,说的很多也是边际情况,并且目前futex功能支持也有限,但就设计而言,仍期望指正与探讨。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。