- 论坛徽章:
- 3
|
回复 3# eexplorer
的确如你所言, 不存在竞争条件,每个调用路径下,newrefcnt看到的都是自己的值,总结起来其实是我错误的理解了atomic_dec_return
下面是我的总结分析:
dst_release 存不存在竞争条件,关键在于 atomic_dec_return 能不能保证 "递减并且返回递减之后的值" 这两步是不是原子的。
看了下atomic_dec_return反汇编代码, 看起来atomic_dec_return的确能保证"递减并且返回递减之后的值" 是个原子操作。
而我之前理解atomic_dec_return 只能保证”递减“这一步是原子的,”返回递减之后的值“之前有可能被打断(也就有可能返回之前递减被执行了多次)
我之前认为是这样的:
newrefcnt = atomic_dec_return(&dst->__refcnt);
会被分成两步执行,这两步之间可能被打断,于是产生了竞争条件。
1. dst->__refcnt原子递减1
2. dst->__refcnt赋值给newrefcnt
在执行2之前,步骤1可能会被多个并发路径调用,这样newrefcnt看到的值就有可能是被递减了多次之后的dst->__refcnt, 这存在了竞争条件。
但是实际上是这样执行的:
1. dst->__refcnt赋值给newrefcnt, 然后dst->__refcnt递减1, 注意这两个动作被合并到一起,整体是一个原子操作
2. newrefcnt递减1
这样就保证了newrefcnt看到的值是dst->__refcnt减1之后的值,不存在前面的竞争条件
在gdb下反汇编dst_release,用"disassemble dst_release"命令显示如下:
/* %r12d 就是 newrefcnt, 先置为-1 */
0xffffffff814fecae <+30>: mov $0xffffffff,%r12d
/* %r12d 是 newrefcnt, 0x80(%rdi) 是 dst->__refcnt, xadd交换目的和源操作数,然后载入两个值的和到目的操作数
这样xadd执行完之后newrefcnt存储的是 dst->__refcnt的旧值(减1之前的值), dst->__refcnt存放减1之后的值
lock 前缀保证了xadd是原子操作。
*/
0xffffffff814fecb4 <+36>: lock xadd %r12d,0x80(%rdi)
/*上一句是dst->__refcnt递减1,这句是newrefcnt递减1,这两句保证了atomic_dec_return不存在竞争条件*/
0xffffffff814fecbd <+45>: sub $0x1,%r12d
//如果递减为负数,就触发WARN_ON
0xffffffff814fecc1 <+49>: js 0xffffffff814fecfe <dst_release+110>
//测试r12d如果为0, 也就是newrefcnt,如果为0, 就进一步判断dst->flags & DST_NOCACHE
0xffffffff814fecc3 <+51>: test %r12d,%r12d
0xffffffff814fecc6 <+54>: je 0xffffffff814fecd8 <dst_release+72>
0xffffffff814fecc8 <+56>: mov (%rsp),%rbx
0xffffffff814feccc <+60>: mov 0x8(%rsp),%r12 |
|