接上文深入讲解读写信号量(上)
(3)乐观自旋
① rwsem_can_spin_on_owner
rwsem_can_spin_on_owner用于判断当前任务是否可以进行乐观自旋
资料直通车:Linux内核源码技术学习路线+视频教程内核源码
② rwsem_spin_on_owner
rwsem_spin_on_owner 是rwsem自旋实现的地方,从字面就可以看出,它会不停的去查询owner的状态,实现自旋
与乐观自旋owner state相关的状态
乐观自旋失败总结:
1.writer持锁
a.reader/writer乐观自旋失败原因:need_resched() || writer不在cpu上 || nonspinable
2.reader持锁
a.reader乐观自旋失败原因:nonspinable || (handoff标记 && (need_resched() || rt_task ))
b.writer乐观自旋失败原因:nonspinable || need_reshced() || timeout || rt_task
Writer乐观自旋尝试持锁
Reader乐观自旋尝试持锁
乐观自旋成功后:
3. read持锁慢速路径
4. read持锁总结
1.reader持锁前,没有其它人持锁,快速路径中持锁成功
2.reader持锁前,有reader持锁,但是没有writer等待,没有设置handoff标记,reader持锁数量没有溢出,快速路径中持锁成功
3.reader持锁前,有reader持锁,但是有writer等待
a.乐观自旋中,拿到了osq锁,进入乐观自旋,没有设置handoff标记,则持锁成功
b.乐观自旋失败,走慢速路径,睡眠等待被唤醒
4.reader持锁前,有writer持锁,乐观自旋失败后,走慢速路径,睡眠等待被唤醒
5. read释放锁
六、rwsem write
同read一样,write持锁也有3种状态
快速路径
中速路径
慢速路径
1. write持锁快速路径
write持锁快速路径比较简单,直接调用
atomic_long_try_cmpxchg_acquire(&sem->count, &tmp, RWSEM_WRITER_LOCKED)
只有当锁没有人持有时,才能成功
2. write持锁中速路径
与reader持锁的实现是一模一样的
3.write持锁慢速路径
(1)handoff
设置RWSEM_FLAG_HANDOFF 的作用:防止处于wait list第一个等待者饿死
因为加入了osq乐观自旋,存在一种情况,任务持锁失败,被挂入wait list中的第一个,同时也存在处于rwsem乐观自旋状态的任务,owner释放了锁,那么该锁大概率会被乐观自旋的任务抢走,如果持续的有乐观自旋状态任务来抢锁,那么wait list中的任务可能会被饿死。
1.rwsem_try_read_lock_unqueued/rwsem_try_write_lock_unqueued时会直接返回
2.reader持锁情况下,另外的reader再来持锁时,也不会成功
3.rwsem_down_read_slowpath 时,如果wait list空的,且是writer持锁或者设置了handoff则不能获得锁(正常是可以的,因为是读者持锁)
4.rwsem_try_write_lock 时,可以直接返回
设置RWSEM_FLAG_HANDOFF的地方
1.在rwsem_down_write_slowpath 慢速路径被唤醒后,如果满足这个条件(wstate == WRITER_FIRST) && (rt_task(current) || time_after(jiffies, waiter.timeout),会将wstate 设置成WRITER_HANDOFF,然后调用rwsem_try_write_lock去拿锁,若锁还有人持有,则将RWSEM_FLAG_HANDOFF 标志置上,这样在其它任务正常的路径上拿锁就不会成功了。
2.在rwsem_mark_wake 流程中,如果是writer持锁,wait list第一个等待者是reader且超过timeout的时间没有拿到锁,也会把RWSEM_FLAG_HANDOFF 标志置上。
1.如果已经设置了handoff 且 wstate == WRITER_NOT_FIRST,则直接返回,在rwsem_mark_wake 里,如果reader等的时间太长,会设置handoff bit,不能让reader饿死。
锁被持有的情况,如果设置了handeroff 或者不是WRITER_HANDOFF,则直接return false。否则把handoff标置设上,然后也会return false,第二次进来的时候,如果没有人持锁,这个线程就能持锁成功,这是为了避免在这个线程被唤醒后,被spiner把锁给偷了2次。
(2)慢速路径过程
如果设置了WRITER_HANDOFF,则直接调用rwsem_spin_on_owner,如果返回的是无人持锁,则直接去拿锁,就不需要睡眠了。
如果WRITER_HANDOFF 已经设置了,就不走后面的流程了,因为后面的流程也是为了设置handoff标记。
是第一个waiter 且 是rt task 或者 超时了,会设置writer_handoff标记,这个标记可以让这个线程不会被spiner把锁再次偷走为什么要判断这个,设想一个场景,假设该writer从睡眠被唤醒,说明锁被释放后把它唤醒了,它本来可以去拿到锁,但是失败了,说明被spiner偷走了为了防止这个现象再次发生,就要设置handoff 标记,让其它人偷不了锁。
4. write释放锁
七、总结
文中所讲的有很大一部分都是源码,很细节的东西。如果大家看的云里雾里的,不要怀疑自己。尽管文档是我一字一句写出来的,并且反复回味了非常多遍,但是我在碰到很细节的问题时,仍然要回头再去看文档的描述才能弄清楚原因。对于读写信号量,我的建议如下:
1.只想了解个大概。可以看下rwsem的结构,osq的概念,搞懂reader/writer持锁后,又有reader/writer进来持锁以及有乐观自旋任务的情况下,会有哪些可能即可。
2.想深入弄懂原理。对着代码好好研究一番。rwsem还是会复杂一些,弄清除了这个,再看其它锁的实现时,应该会更得心应手了。