linux内核的设计模式

原文来自:http://lwn.net/Articles/336224/

选择感兴趣内容简单翻译了下: 

在内核社区一直以来的兴趣是保证质量.我们需要保证和改善质量是显而易见的.但是如何做到却不是那么简单.一个广泛的办法是找到一些成功之处来增加内核在多方面的透明性.这将使得这些方面的质量变得更加明朗,因此将改变内核质量.

采用多种形式增加透明性:

  • checkpatch.pl脚本突出显示了从已接受代码书写风格上的背离.这将鼓励使用此脚本的人去改正格式问题.因此,通过增加风格引导的透明性,我们增加了代码表现上的一致性,而且在一定程度上改善了质量.
  • 内嵌的"lockdep"系统动态估量锁之间的依赖和相关状态(比如当可打断时).它将在锁异常时报告所用发生的事情.异常不只是死锁或者类似问题,而是许多事情,并且死锁可能被移除.因此通过增加锁依赖图的透明性,可以提高质量.
  • 内核包含多种其他的透明性改善,比如定位未使用内存的位置来提高有效访问的透明性,或者在堆栈跟踪时用象征性名字而不是16进制地址使得bug报告更加有用.
  • 在更高层面,用git版本来追踪软件改变,看到每个人何时做了什么.事实是它鼓励在patch上加注释来回答这段代码为什么这样写.这种透明性可以增进对代码的理解,而且随着其他开发者更好被通知而改善质量.

内核里还要很多其他地方以提高透明性的做法或者可以改善质量.我们将挖掘改善质量的透明性的地方.并称之为内核相关的设计模式.

设计模式

设计模式首先在架构领域被提出,并带入计算机工程,特别是在OO编程领域.通过1994年出版的<设计模式:重用面向对象软件的元素.

简单来说,一种设计模式描述了设计问题的一种特定类型和解决此类问题已经证明有效解决办法的细节.设计模式的益处是它把问题描述和解决办法描述组合在一起并给他们一个命名.给一种模式一个简单和可记忆的名字是十分有价值的.如果开发者和复审代码者都知道同一个模式的相同命名,那么一个明确的设计决策可以用一两个单词来交流,因此把这种决定更透明.

在linux代码中有很多有效的设计模式.然而大多模式没有被文档化,因此其他开发者不容易发现.我希望这些模式能够被文档化,帮助人们更广泛地使用他们,开发者能够面对同类问题时能获得有效的解决办法.

在这个系列我们将会看到3个领域的问题,并发现一些截然不同的重要设计模式.我们做这里的目标不仅是指出它,并要展示这些模式的适用范围和价值,使得其他开发者发现他们看到的设计模式.

这个系列将会展示linux内核的大量具有启发性模式的大量例子.代码来自2.6.30-rc3.

引用计数

引用计数的思想来管理一个对象生命周期是十分常见的.其核心思想是一旦一个新的引用发生就要使得计数器增加1,释放了就减1.当计数器=0就释放对象资源(如内存).

管理引用计数的机制看起来相当直接.然而有一些细节使得很容易用错这个机制.部分是因为这个原因,自从2004年后linux内核有一个叫"kref"的数据类型.这些封装了细节,特别的澄清了一个给定的计数器将会被用做一定方式的引用计数.如上所述,对设计模式命名很有价值,提供内核开发者使用的名字将对复审者很有用.

Andrew Morton说:

我会很小心地说:"啊哈,它使用了kref.我理解引用计数机制,我知道好好调试,我知道它将会跟踪常见错误."这比"oh,这个实现了我自己的引用计数,我需要检查常见错误."

这个kref结论在linux内核里给出了一个tick和一个明显的设计模式的支持.A tick意味着kref清楚地封装了一种重要设计模式.这里有一些引用计数的使用,kref模型不是特别适用.引用计数不提供希望的函数功能实际上会出错,有时候人们在不该用的时候用来kref,事实上并不有效.

一个有用的步骤去理解引用计数复杂性是理解对一个对象引用的两种常见不同分类,事实上有3种或者更多,但是泛化的有两种.我们将会以"内部"和"外部"为例,或者说"强引用"和"弱引用"更加合适.

一个外部引用是我们最常见的.他们可以用"get"和"put"来被与管理它对象的子系统完全不同的子系统来使用.一个被计数的外部引用的存在是强和简单的意味着:这个对象在使用.

与之对应的,内部引用不被计数,只是被持有它的系统来管理对象.不要的内部引用能够有不同的意义,因此有不同的实现.

最常见的内部引用的例子是一个提供"查询名字"服务的缓存.如果你知道一个对象的名字,你可以想缓存申请得到一个实际只存在与缓存的外部引用.这样一个缓存将会在链表上保存每个对象,或者一堆链表:比如哈希表.在链表上的对象是对这个对象的引用.然而,它很可能不是引用计数.它并不意味"这个对象在使用"而是"这个对象在某些情况下悬着一旦一些人需要它".对象直到外部引用完全消失才被从链表移除.或者到那时也不被立即移除.内部引用的存在和特征与引用计数的实现有关.

划分不同引用计数风格的有效方法是"put"操作的实现."get"操作常常是一样的.它消除一个外部引用并产生另外一个外部引用.常常这样实现它:

    assert(obj->refcount > 0) ; increment(obj->refcount);

或者在linux内核

    BUG_ON(atomic_read(&obj->refcnt)) ; atomic_inc(&obj->refcnt);

注意get不能用在未被引用对象.

put操作有三种不同实现.在使用者有重叠.如下:

   1      atomic_dec(&obj->refcnt);

   2      if (atomic_dec_and_test(&obj->refcnt)) { ... do stuff ... }

   3      if (atomic_dec_and_lock(&obj->refcnt, &subsystem_lock)) {
                 ..... do stuff ....
		 spin_unlock(&subsystem_lock);
	  }

kref风格:

风格2是kref的风格.当一个对象在最后一个外部引用失效时这种风格是合适 的.当引用计数为0,这个对象需要被释放或者做其他处理.因此,需要检测是否未0.

适用与这种风格的对象常常没有任何内部引用的担忧.因为大多数对象在此情况下在sysfs,是kref的重操作者.如果用kref风格的对象没有内部引用,不允许从一个内部引用上创建外部引用,除非已知其他外部引用.如果必要:

     atomic_inc_not_zero(&obj->refcnt);

增加一个不是0的值,并返回结果暗示成功或失败.atomic_inc_not_zero是linux的一个现代的发明,在2005年晚些时候作为无锁页缓存工作.因为这个原因它还没被广泛使用,一些代码能从它使用spinlocks替代.悲剧的是,kref包没有用到这个特性.

An interesting example of this style of reference that does not use kref, and does not even useatomic_dec_and_test() (though it could and arguably should) are the two ref counts in struct super:s_count and s_active.

一个没有使用kref的有趣的例子,甚至没有使用atomic_dec_test是两个引用计数在supers:count和是a_active.

a_active正好适合kref风格.一个超级块开始于s-atcive=1(在alloc_super里设置,当s_active为0,再次外部引用是不允许的.)这个规则在grab_super里编码,尽管不是非常清楚.现在的代码增加一个非常大的值,当s_active不为0,并且grab_super特使s_count超过S_BIAS而不是s_active为0.用atomic_inc_not_zero将会很好测试,而避免使用自旋锁.

s_count提供了不同的引用,同时有内部和外部引用.内部引用是在语义上比s_active_count引用弱.s_count引用只意味者超级块不能说明它当前实际有效.外部引用更像kref开始与1,并且当变为0超级块被清掉.

因此两种引用可以被两个krefs替代:

  • S_BAIS设置为1
  • grab_super用atomic_inc_not_zero而不是检测S_BIAS

一大堆的自旋锁可以滚了.

kref风格

The Linux kernel doesn‘t have a "kcref" object, but that is a name that seems suitable to propose for the next style of reference count. The "c" stands for "cached" as this style is very often used in caches. So it is a Kernel Cached REFerence.

A kcref uses atomic_dec_and_lock() as given in option 3 above. It does this because, on the last put, it needs to be freed or checked to see if any other special handling is needed. This needs to be done under a lock to ensure no new reference is taken while the current state is being evaluated.

A simple example here is the i_count reference counter in struct inode. The important part of iput()reads:

    if (atomic_dec_and_lock(&inode->i_count, &inode_lock))
	iput_final(inode);

where iput_final() examines the state of the inode and decides if it can be destroyed, or left in the cache in case it could get reused soon.

Among other things, the inode_lock prevents new external references being created from the internal references of the inode hash table. For this reason converting internal references to external references is only permitted while the inode_lock is held. It is no accident that the function supporting this is called iget_locked() (or iget5_locked()).

A slightly more complex example is in struct dentry, where d_count is managed like a kcref. It is more complex because two locks need to be taken before we can be sure no new reference can be taken - both dcache_lock and de->d_lock. This requires that either we hold one lock, thenatomic_dec_and_lock() the other (as in prune_one_dentry()), or that we atomic_dec_and_lock() the first, then claim the second and retest the refcount - as in dput(). This is good example of the fact that you can never assume you have encapsulated all possible reference counting styles. Needing two locks could hardly be foreseen.

An even more complex kcref-style refcount is mnt_count in struct vfsmount. The complexity here is the interplay of the two refcounts that this structure has: mnt_count, which is a fairly straightforward count of external references, and mnt_pinned, which counts internal references from the process accounting module. In particular it counts the number of accounting files that are open on the filesystem (and as such could use a more meaningful name). The complexity comes from the fact that when there are only internal references remaining, they are all converted to external references. Exploring the details of this is again left as an exercise for the interested reader.

The "plain" style

The final style for refcounting involves just decrementing the reference count (atomic_dec()) and not doing anything else. This style is relatively uncommon in the kernel, and for good reason. Leaving unreferenced objects just lying around isn‘t a good idea.

One use of this style is in struct buffer_head, managed by fs/buffer.c and <linux/buffer_head.h>. Theput_bh() function is simply:

    static inline void put_bh(struct buffer_head *bh)
    {
        smp_mb__before_atomic_dec();
        atomic_dec(&bh->b_count);
    }

This is OK because buffer_heads have lifetime rules that are closely tied to a page. One or more buffer_heads get allocated to a page to chop it up into smaller pieces (buffers). They tend to remain there until the page is freed at which point all the buffer_heads will be purged (bydrop_buffers() called from try_to_free_buffers()).

In general, the "plain" style is suitable if it is known that there will always be an internal reference so that the object doesn‘t get lost, and if there is some process whereby this internal reference will eventually get used to find and free the object.

Anti-patterns

To wrap up this little review of reference counting as an introduction to design patterns, we will discuss the related concept of an anti-pattern. While design patterns are approaches that have been shown to work and should be encouraged, anti-patterns are approaches that history shows us do not work well and should be discouraged.

Your author would like to suggest that the use of a "bias" in a refcount is an example of an anti-pattern. A bias in this context is a large value that is added to, or subtracted from, the reference count and is used to effectively store one bit of information. We have already glimpsed the idea of a bias in the management of s_count for superblocks. In this case the presence of the bias indicates that the value of s_active is non-zero, which is easy enough to test directly. So the bias adds no value here and only obscures the true purpose of the code.

Another example of a bias is in the management of struct sysfs_dirent, in fs/sysfs/sysfs.h andfs/sysfs/dir.c. Interestingly, sysfs_dirent has two refcounts just like superblocks, also called s_countand s_active. In this case s_active has a large negative bias when the entry is being deactivated. The same bit of information could be stored just as effectively and much more clearly in the flag words_flags. Storing single bits of information in flags is much easier to understand that storing them as a bias in a counter, and should be preferred.

In general, using a bias does not add any clarity as it is not a common pattern. It cannot add more functionality than a single flag bit can provide, and it would be extremely rare that memory is so tight that one bit cannot be found to record whatever would otherwise be denoted by the presence of the bias. For these reasons, biases in refcounts should be considered anti-patterns and avoided if at all possible.

Conclusion

This brings to a close our exploration of the various design patterns surrounding reference counts. Simply having terminology such a "kref" versus "kcref" and "external" versus "internal" references can be very helpful in increasing the visibility of the behaviour of different references and counts. Having code to embody this as we do with kref and could with kcref, and using this code at every opportunity, would be a great help both to developers who might find it easy to choose the right model first time, and to reviewers who can see more clearly what is intended.

The design patterns we have covered in this article are:

  • kref: When the lifetime of an object extends only to the moment that the last external reference is dropped, a kref is appropriate. If there are any internal reference to the object, they can only be promoted to external references with atomic_inc_not_zero(). Examples:s_active and s_count in struct super_block.

  • kcref: With this the lifetime of an object can extend beyond the dropping of the last external reference, the kcref with its atomic_dec_and_lock() is appropriate. An internal reference can only be converted to an external reference will the subsystem lock is held. Examples: i_countin struct inode.

  • plain: When the lifetime of an object is subordinate to some other object, the plain reference pattern is appropriate. Non-zero reference counts on the object must be treated as internal reference to the parent object, and converting internal references to external references must follow the same rules as for the parent object. Examples: b_count in struct buffer_head.

  • biased-reference: When you feel the need to use add a large bias to the value in a reference count to indicate some particular state, don‘t. Use a flag bit elsewhere. This is an anti-pattern.

Next week we will move on to another area where the Linux kernel has proved some successful design patterns and explore the slightly richer area of complex data structures. (Part 2 and part 3of this series are now available).

Exercises

As your author has been reminded while preparing this series, there is nothing like a directed study of code to clarify understanding of these sorts of issues. With that in mind, here are some exercises for the interested reader.

  1. Replace s_active and s_count in struct super with krefs, discarding S_BIAS in the process. Compare the result with the original using the trifecta of Correctness, Maintainability, and Performance.

  2. Choose a more meaningful name for mnt_pinned and related functions that manipulate it.

  3. Add a function to the kref library that makes use of atomic_inc_not_zero(), and using it (or otherwise) remove the use of atomic_dec_and_lock() on a kref in net/sunrpc/svcauth.c - a usage which violates the kref abstraction.

  4. Examine the _count reference count in struct page (see mm_types.h for example) and determine whether it behaves most like a kref or a kcref (hint: it is not "plain"). This should involve identifying any and all internal references and related locking rules. Identify why the page cache (struct address_space.page_tree) owns a counted reference or explain why it should not. This will involve understanding page_freeze_refs() and its usage in__remove_mapping(), as well as page_cache_{get,add}_speculative().

Bonus credit: provide a series of minimal self-contained patches to implement any changes that the above investigations proved useful.



linux内核的设计模式,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。