购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.2 引用计数

引用计数的特点是在引用计数为0的时候触发特殊效果(如回收资源),或者不允许增加引用计数,所以引用计数使用的原子操作需要能判断引用计数是否为0,或者根据引用计数是否为0来执行加法,如下。

img
img
img

还有一个带锁的引用计数,叫作lockref,其实现位于lib/lockref.c中,是一个针对特定场景做性能优化的引用计数。原子操作保证对引用计数的操作是原子的,但是并不保证对一个结构体的操作是原子的,如果想要保证一个结构体中的数据只能被一个线程修改,就需要额外的自旋锁,这时在结构体中就会存在一个自旋锁和一个引用计数。引用计数的获得与自旋锁的获得在特定的情况下是强相关的,这个特定的情况就是当引用计数变成0的时候。

lockref的所有锁操作都要在自旋锁没有被持有的时候才能生效,快速路径是在减少一个引用计数后,引用计数的结果仍然是在大于/等于1的情况下使用,这样就只需要减少一个引用计数的值即可,不需要处理引用计数变为0的情况,或者在引用计数大于0的时候,只简单地增加引用计数即可。

img
img

上面代码中使用lockref_get_or_lock和lockref_put_or_lock两个函数就是引用计数加/减的快速路径方法。lockref_get_or_lock函数在增加引用计数的时候,如果当前引用计数大于0,就只简单地增加引用计数的值。这里之所以不直接使用atomic_inc_not_zero()函数增加引用计数的值,是因为引用计数的值可以是负数,引用计数为负数存在两种情况。一种是引用计数的值不是-128的负数,负数代表与目录项相关的inode对象不存在(相应的磁盘索引节点可能已经被删除),dentry对象的d_inode指针为NULL,但这种dentry对象仍然保存在dcache中,以便后续对同一个文件名的查找能够快速完成。负数状态和0状态的dentry被称作unused,统一被放进LRU中。另外一种是特别的状态,叫作死亡,死亡状态的引用计数的值固定是-128,死亡状态的dcache一定会被内存回收。

引用计数在dentry下一共存在-128、负数、0、正数四种状态。-128代表马上进入被删除的状态,任何入口都不可达;负数代表没有inode,但是可以再关联一个新的inode;0代表有inode,但是没有程序在使用;正数则代表有程序正在使用inode。

对于引用计数和自旋锁来说,当引用计数降低到小于1的时候,就需要对整个结构体加锁,然后进行放入LRU队列的操作。由于降低引用计数到0然后加锁的操作很普遍,所以内核很快就发现这是一个比较严重的性能问题,于是对应的从0变为1的操作需要将dentry从LRU中取出,也需要对dentry进行加锁,这是一个非常高频的操作。

为了解决高频操作带来的性能问题,内核设计了lockref这个可以同时完成加锁与引用计数变化的结构。lockref的特点是当其值大于1的时候降低引用计数,或者在其值大于0的时候增加引用计数,自旋锁的加锁与解锁是极高性能,甚至都没有自旋锁发生;而在小于/等于1的时候降低引用计数,或者在等于0的时候增加引用计数,虽然lockref的快速路径会不成功,但是会自动进行加锁。

lockref是一个包含了引用计数和自旋锁的性能增强数据结构,lockref的定义如下。

img
img

从lockref的定义中可以看到,其将一个4字节的引用计数与一个4字节的自旋锁放到一个连续的8字节空间,性能的关键在aligned_u64这个类型,该类型要求64位边界对齐。整个8字节的结构体的操作都是在内存I/O中完成的,也就是在64位下,一条指令可以同时操作引用计数和自旋锁,在32位下需要多条指令来模拟实现cmpxchg64_relaxed这个指令。 WDyIyuj8vvXS3wgQ+eH5xHHdS6B6XU+zlC+hp1qYwVoGMiqAL5nFCqe1CPqYb6Ek

点击中间区域
呼出菜单
上一章
目录
下一章
×