内核中哪些地方会用到互斥锁?看图:
图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能.
概述
自旋锁 和 互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争.
自旋锁的特点是死守共享资源,拿不到锁,CPU选择睡眠,等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克.
互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题:
第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表.
第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式.
第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办?
答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被还原.所以一个任务的优先级会看情况时高时低.
第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级.
带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明.
互斥锁长什么样?
enum {
LOS_MUX_PRIO_NONE = 0, //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队.
LOS_MUX_PRIO_INHERIT = 1, //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。
//当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级
LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义
};
enum {
LOS_MUX_NORMAL = 0, //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测)
LOS_MUX_RECURSIVE = 1, //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。
LOS_MUX_ERRORCHECK = 2, //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。
LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁
};
typedef struct { //互斥锁的属性
UINT8 protocol; //协议
UINT8 prioceiling; //优先级上限
UINT8 type; //类型属性
UINT8 reserved; //保留字段
} LosMuxAttr;
typedef struct OsMux { //互斥锁结构体
UINT32 magic; /**< magic number */ //魔法数字
LosMuxAttr attr; /**< Mutex attribute */ //互斥锁属性
LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上
LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别.
VOID *owner; /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务
UINT16 muxCount; /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次
} LosMux;
初始化
LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr)
{ //...
SCHEDULER_LOCK(intSave); //拿到调度自旋锁
mutex->muxCount = 0; //锁定互斥量的次数
mutex->owner = NULL; //持有该锁的任务
LOS_ListInit(&mutex->muxList); //初始化等待该锁的任务链表
mutex->magic = OS_MUX_MAGIC; //固定标识,互斥锁的魔法数字
SCHEDULER_UNLOCK(intSave); //释放调度自旋锁
return LOS_OK;
}
留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务.
三种申请模式
申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。
无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有