简单分析Linux信号量机制
说明:Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述信号量 semaphore ,是操作系统中一种常用的同步与互斥的机制;信号量允许多个进程(计数值>1)同时进入临界区; 如果信号量的计数值为1,一次只允许一个进程进入临界区,这种信号量叫二值信号量; 信号量可能会引起进程睡眠,开销较大,适用于保护较长的临界区; 与读写自旋锁类似,linux内核也提供了读写信号量的机制;
本文将分析信号量与读写信号量的机制,开始吧。 2. 信号量2.1 流程分析可以将信号量比喻成一个盒子,初始化时在盒子里放入N把钥匙,钥匙先到先得,当N把钥匙都被拿走完后,再来拿钥匙的人就需要等待了,只有等到有人将钥匙归还了,等待的人才能拿到钥匙;
信号量的实现很简单,先看一下数据结构: struct semaphore { raw_spinlock_t lock; //自旋锁,用于count值的互斥访问 unsigned int count; //计数值,能同时允许访问的数量,也就是上文中的N把锁 struct list_head wait_list; //不能立即获取到信号量的访问者,都会加入到等待列表中 }; struct semaphore_waiter { struct list_head list; //用于添加到信号量的等待列表中 struct task_struct *task; //用于指向等待的进程,在实际实现中,指向current bool up; //用于标识是否已经释放 };
流程如下:
down 接口用于获取信号量,up 用于释放信号量;调用 down 时,如果sem->count > 0 时,也就是盒子里边还有多余的锁,直接自减并返回了,当sem->count == 0 时,表明盒子里边的锁被用完了,当前任务会加入信号量的等待列表中,设置进程的状态,并调用schedule_timeout 来睡眠指定时间,实际上这个时间设置的无限等待,也就是只能等着被唤醒,当前任务才能继续运行;调用 up 时,如果等待列表为空,表明没有多余的任务在等待信号量,直接将sem->count 自加即可。如果等待列表非空,表明有任务正在等待信号量,那就需要对等待列表中的第一个任务(等待时间最长)进行唤醒操作,并从等待列表中将需要被唤醒的任务进行删除操作;
更多linux内核视频教程文档资料免费领取后台私信【内核】自行获取.
2.2 信号量缺点对比下 《Linux Mutex机制分析》 说过的Mutex ,Semaphore 与Mutex 在实现上有一个重大的区别:ownership 。Mutex 被持有后有一个明确的owner ,而Semaphore 并没有owner ,当一个进程阻塞在某个信号量上时,它没法知道自己阻塞在哪个进程(线程)之上;没有 ownership 会带来以下几个问题:在保护临界区的时候,无法进行优先级反转的处理; 系统无法对其进行跟踪断言处理,比如死锁检测等; 信号量的调试变得更加麻烦;
因此,在 Mutex 能满足要求的情况下,优先使用Mutex 。2.3 其他接口
信号量提供了多种不同的信号量获取的接口,介绍如下: /* 未获取信号量时,进程轻度睡眠:TASK_INTERRUPTIBLE */ int down_interruptible(struct semaphore *sem) /* 未获取到信号量时,进程中度睡眠:TASK_KILLABLE */ int down_killable(struct semaphore *sem) /* 非等待的方式去获取信号量 */ int down_trylock(struct semaphore *sem) /* 获取信号量,并指定等待时间 */ int down_timeout(struct semaphore *sem, long timeout)3. 读写信号量
《linux spinlock/rwlock/seqlock原理剖析(基于ARM64)》 文章中,我们分析过读写自旋锁,读写信号量的功能类似,它能有效提高并发性,我们先明确下它的特点:允许多个读者同时进入临界区; 读者与写者不能同时进入临界区(读者与写者互斥); 写者与写者不能同时进入临界区(写者与写者互斥); 3.1 数据结构
读写信号量的数据结构与信号量的结构比较相似: struct rw_semaphore { atomic_long_t count; //用于表示读写信号量的计数 struct list_head wait_list; //等待列表,用于管理在该信号量上睡眠的任务 raw_spinlock_t wait_lock; //锁,用于保护count值的操作 #ifdef CONFIG_RWSEM_SPIN_ON_OWNER struct optimistic_spin_queue osq; /* spinner MCS lock */ //MCS锁,参考上一篇文章Mutex中的介绍 /* * Write owner. Used as a speculative check to see * if the owner is running on the cpu. */ struct task_struct *owner; //当写者成功获取锁时,owner会指向锁的持有者 #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif };最关键的需要看一下 count 字段,掌握了这个字段的处理,才能比较好理解读写信号量的机制;《inux spinlock/rwlock/seqlock原理剖析(基于ARM64)》 文章中提到过读写自旋锁,读写自旋锁中的lock 字段,bit[31]用于写锁的标记,bit[30:0]用于读锁的统计,而读写信号量的count 字段也大体类似;
以32位的count值为例,高16bit代表的是 waiting part ,低16bit代表的是active part ;RWSEM_UNLOCKED_VALUE :值为0,表示锁未被持有,没有读者也没有写者;RWSEM_ACTIVE_BIAS :值为1,,该值用于定义RWSEM_ACTIVE_READ_BIAS 和RWSEM_ACTIVE_WRITE_BIAS ;RWSEM_WAITING_BIAS :值为-65536,当有任务需要加入到等待列表中时,count值需要加RWSEM_WAITING_BIAS ,有任务需要从等待列表中移除时,count值需要减去RWSEM_WAITING_BIAS ;RWSEM_ACTIVE_READ_BIAS :值为1,当有读者去获取锁的时候,count值将加RWSEM_ACTIVE_READ_BIAS ,释放锁的时候,count值将减去RWSEM_ACTIVE_READ_BIAS ;RWSEM_ACTIVE_WRITE_BIAS ,值为-65535,当有写者去获取锁的时候,count值将加RWSEM_ACTIVE_WRITE_BIAS ,释放锁的时候,count值需要减去RWSEM_ACTIVE_WRITE_BIAS ;
在获取释放读锁和写锁的全过程中, count 值伴随着上述这几个宏定义的加减操作,用于标识不同的状态,可以罗列如下:0x0000000X :活跃的读者和正在申请读锁的读者总共为X 个,没有写者来干扰;0x00000000 :没有读者和写者来操作,初始化状态;0xFFFF000X :分为以下几种情况:0xFFFF000X = RWSEM_WAITING_BIAS + X * RWSEM_ACTIVE_READ_BIAS ,表示活跃的读者和正在申请读锁的读者总共有X 个,并且还有一个写者在睡眠等待;0xFFFF000X = RWSEM_ACTIVE_WRITE_BIAS + (X - 1)* RWSEM_ACTIVE_READ_BIAS ,表示有一个写者在尝试获取锁,活跃的读者和正在申请读锁的读者总共有X-1 个;0xFFFF0001 :分为以下几种情况:0xFFFF0001 = RWSEM_ACTIVE_WRITE_BIAS ,有一个活跃的写者,或者写者正在尝试获取锁,没有读者干扰;0xFFFF0001 = RWSEM_ACTIVE_READ_BIAS + RWSEM_WAITING_BIAS ,有个写者正在睡眠等待,还有一个活跃或尝试获取锁的读者;3.1 读信号量3.1.1 读者获取锁
特点:读者与读者可以并发执行,读者与写者互斥执行,因此当有写者持有锁的时候,读者将进入睡眠状态; 当 sem->count 加1后还是小于0,代表锁已经被写者持有了,读者获取锁失败,进入rwsem_down_read_failed 函数;如果 sem->wait_list 是空时,代表没有任务在等待列表中,首次加入时,sem->count 值需要加上RWSEM_WAITING_BIAS ,表示有任务在等待列表中;如果此时 sem->count == RWSEM_WAITING_BIAS 或者count > RWSEM_WAITING_BIAS && adjustment != RWSEM_ACTIVE_READ_BIAS ,表示此时写者将锁释放了,因此需要去唤醒在等待列表中的任务;如果写者没有释放锁,那就进入循环,并调用 schedule 让出CPU,直到锁被释放了,那么从代码流程中看,只有!waiter.task 时才会跳出循环,也就是waiter.task == NULL 时,才是获取成功,这个操作是在__rwsem_mark_wake 中通过smp_store_release(&waiter->task, NULL) 实现的;在等待获取锁的循环中,需要对信号进行处理,如果对应的等待任务没被唤醒,那么直接跳转到 out_nolock 处,接下来的处理就是一些逆操作了,包括从等待列表中删除,如果是等待列表中的首个任务,还需要减去RWSEM_WAITING_BIAS 等;
总结一下: 读者获取锁的时候,如果没有写者持有,那就可以支持多个读者直接获取;而如果此时写者持有了锁,读者获取失败,它将把自己添加到等待列表中,(这个等待列表中可能已经存放了其他来获取锁的读者或者写者),在将读者真正睡眠等待前,还会再一次判断此时是否有写者释放了该锁,释放了的话,那就需要对睡眠等待在该锁的任务进行唤醒操作了 3.1.2 读者释放锁
释放锁的时候 sem->count 值进行减1操作;减1操作之后得到的 count 值小于-1,并且active part 是全零,代表等待列表中有写任务在睡眠等待,因此需要进行唤醒操作;唤醒操作中,如果有自旋等待的任务,那就可以直接返回了,毕竟人家在自旋呢,又没有睡眠; 没有自旋等待任务,那就去唤醒等待列表中的任务了; 3.2 写信号量3.2.1 写者获取锁
写者的特点:看谁都不顺眼,跟谁都互斥,有我没你。只要有一个写者在持有锁,其他的读者与写者都无法获取; 在写者获取锁的时候,将 sem->count 值加上RWSEM_ACTIVE_WRITE_BIAS ,如果这个值不等于RWSEM_ACTIVE_WRITE_BIAS ,表示有其他的读者或写者持有锁,因此获取锁失败,调用rwsem_down_write_failed 来处理;调用 rwsem_optimistic_spin 进行乐观自旋去尝试获取锁,获取了的话,则直接返回,optimistic spin 可以参考《Linux Mutex机制分析》 文章中的分析,它的作用也是性能的优化,认为锁的持有者会很快释放,因此当前进程选择自旋而不是让出CPU,减少上下文切换带来的开销;如果等待列表中有读者任务在睡眠等待,此时假如写者释放了锁,那么需要先将读者任务都给唤醒了;如果等待列表中没有任务,也就意味着当前的写者是第一个任务,因此将 sem->count 值加上RWSEM_WAITING_BIAS ;循环等待获取锁,这个过程与 down_read 是类似的;
总结 写者获取锁时,只要锁被其他读者或者写者持有了,则获取锁失败,然后进行失败情况处理。在失败情况下,它本身会尝试进行optimistic spin去尝试获取锁,如果获取成功了,那就是皆大欢喜了,否则还是需要进入慢速路径。慢速路径中去判断等待列表中是否有任务在睡眠等待,并且会再次尝试去查看是否已经有写者释放了锁,写者释放了锁,并且只有读者在睡眠等待,那么此时应该优先让这些先等待的任务唤醒 3.2.2 写者释放锁
写者释放锁的时候,有一个关键的操作,将 sem->owner 进行清零操作,在写者获取锁的时候会将该值设置成持有锁的进程;释放锁的时候,需要减去 RWSEM_ACTIVE_WRITE_BIAS ,然后再去判断值,如果此时还有任务在睡眠等待,那就进行唤醒操作;3.3 总结
理解读写信号量有几个关键点: 读写信号量的特性可以与读写自旋锁进行类比(读者与读者并发、读者与写者互斥、写者与写者互斥),区别在于读写信号量可能会发生睡眠,进而带来进程切换的开销; 为了优化读写信号量的性能,引入了 MCS锁 机制,进一步减少切换开销。第一个写者获取了锁后,第二个写者去获取时自旋等待,而读者去获取时则会进入睡眠;读写信号量的 count 值很关键,代表着读写信号量不同状态的切换,因此也决定了执行流程;读者或写者释放锁的时候,去唤醒等待列表中的任务,需要分情况处理。等待列表中可能存放的是读者与写者的组合,如果第一个任务是写者,则直接唤醒该写者,否则将唤醒排在前边的连续几个读者;
首页 - 内核技术中文网 - 构建全国最权威的内核技术交流分享论坛
转载地址:深入分析Linux信号量机制 - 圈点 - 内核技术中文网 - 构建全国最权威的内核技术交流分享论坛
长期吃护肝片,对肝是好还是坏?别再乱猜,一则案例说明真相导语随着人们生活水平的不断提升,大家的生活压力和工作压力也变得越来越大,而在这些问题之下也逐渐养成了不健康的生活习惯和饮食习惯,虽说在年轻时或许看不出问题,但是随着年龄的不断增长也
盘点今年夏天自由市场重大球员本赛季常规赛只剩下20多场比赛就要结束,很快就要到了一年一度的自由市场。让我们提前来看一看今年夏天自由市场会有哪些大鱼需要关注,重点讲述哈登米德尔顿和欧文等3名球员1詹姆斯哈登哈登
苹果公司仍是黯淡的全球PC市场中最耀眼的亮点尽管整个PC市场达到了大约20年来的最低点,摩根士丹利依然坚称苹果仍然是最佳计算机生产商。近年来,全球个人电脑市场受到了很大的影响,它试图从一个可怕的假日季节中恢复过来。在一份报告
修长城模拟器中国边疆最新演示预定二季度steam发售SolidGames开发的中国生活建筑模拟器中国边疆预定今年二季度steam发售,支持中文,日前官方公布了最新演示,一起来先睹为快。中国边疆旨在探索在长城修建期间建立的华人定居点的
男性尿频原因是什么频尿急尿不尽属于很多男性非常常见的疾病,有些男性在平时很少喝水,一个晚上要撒个五到六次,自己的睡眠质量会受到非常严重的影响。了解男性尿频的原因显得非常的重要,属于男性关注自身健康最
中国围棋当代发展史把地球的故事讲给宇宙中国围棋当代的发展史我想可以分为这么几个阶段觉醒时代磨难期(韩流纪元)扳头时代(抗韩纪元)中国崛起围棋新纪元。一。觉醒时代1985年8月1988年期间,聂卫平在
NBA选中的九名中国人湖人小牛最喜欢中国球员头条创作挑战赛第九名王哲林2016孟菲斯灰熊王哲林被孟菲斯灰熊选中,超出了所有人的预料,包括王哲林自己。遗憾的是虽然王哲林被选中,但王哲林最终没有选择加盟灰熊队,挑战NBA。第八名
原因查明!不明物洞穿造成12MM孔洞或是俄罗斯自家反卫星太空垃圾据俄罗斯航天局发布的消息,经过工程师仔细检查后确认,进步MS21货运飞船上一个12毫米直径的小孔是造成2月11日冷却剂泄漏的原因,已经确定故障为外力所致,并非制造缺陷,目前该飞船已
消息称惠普暗影精灵9游戏本将换新模具,还有轻薄型号IT之家2月21日消息,据奥拉猪汪消息,惠普暗影精灵9游戏本将迎来模具升级,普及2K240Hz屏幕。此外,惠普还将推出一款轻薄游戏本型号,类似于联想拯救者Y9000X和ROG的幻1
恭喜梅内!大巴黎新帅曝光,手段强硬曾硬刚姆总,二进宫欲立新核大巴黎本赛季的战绩已经接近失败了,欧冠淘汰赛首回合面对强大的拜仁主场01落后。第二回合想要翻盘希望也不大了,而大巴黎也在着手新帅的选择,其中穆帅和齐达内已经被排除了,大巴黎新帅也大
浙江人眼里的陕西,这十几年,中国的巨大变化中学地理课本上定义,淮河秦岭以北就是中国北方。一转眼,我已经15年没有去北方旅行了。当时的北方给我留下的印象不算好,以至于我后来宁愿舍近求远去国外旅行,也不愿意在国内多跑,尤其不愿