引入

这篇文章可能有点无聊,但笔者姑且还觉得挺有意思的,所以写了这篇文章分享给大家。(笑)

PS:本文大部分关键的代码都可以在/include/linux/page-flags.h中找到。

下面进入正文。

笔者阅读代码的时候总是会遇到一些令人头大的情况

比如说下面这段代码,这段代码作用很简单,就是把一个folio加到LRU链表上,一切看似都是那么的正常。

但很快就遇到一个奇怪的问题,注意看上面的两个VM_BUG_ON_FOLIO,产生BUG_ON的条件写在后面的括号里了,举个例子,folio_test_active()为真的时候,会触发一个BUG。

于是我们搜索folio_test_active()的定义:

一无所获啊!!!就算是全局搜索,也只能搜到调用,也根本搜不到定义,虽然稍微转转脑子,就知道这个函数实际上就是看folio有没有被设置PG_active,folio刚加入LRU的时候,会被设置为PG_active并被加入到active的LRU。虽然知道大概的意思,但是笔者强迫症犯了,看代码的时候看不到某一个函数的定义是一件非常非常非常难受的事情,而且阅读代码这种事情,最忌讳的就是想当然,还是得真正读到源码才能确定它的功能。

不只是opengrok,https://elixir.bootlin.com/上也搜不到定义。

是不是所有的folio_test_xxx函数都搜不到呢?并不是,请看,判断folio是不是私有匿名页的folio_test_anon是可以搜到的:

那更奇怪了,为什么有的函数能搜到,有的函数就搜不到呢?

到底是谁把我的folio_test_active()给藏起来了!

无独有偶,folio_test_lru()也被藏起来了。

解惑

感谢PageBuddy函数,解决了我的困惑。

PageBuddy用于判断一个page是否由Buddy System管理,换言之,判断它当前是不是空闲页,这个函数会在compact_zone()的空闲页扫描时派上大用处。

这个函数也是那种,只能找到调用但是找不到任何定义的,而这个函数会在compact常见下多出被调用,每看到一次,笔者内心的烦躁都++。

忍不了了,这次必须得搞明白是咋回事。

PAGE_TYPE_OPS()

笔者在这个函数上终于取得了突破,别问,问就是在/include/linux/page-flags.h搜索buddy关键字,运气好搜到的。()

/*
 * PageBuddy() indicates that the page is free and in the buddy system
 * (see mm/page_alloc.c).
 */
PAGE_TYPE_OPS(Buddy, buddy, buddy)

再往上看一下PAGE_TYPE_OPS的定义,看到这里真的会有一种茅塞顿开的感觉啊!!因为代码中存在大量命名形如PageXXXX的,内核为了统一定义,就采用了下面这种特殊的方式,在#define的语法中,##可以把Page和uname拼接在一起成为一个完整的字符串,对于PAGE_TYPE_OPS(Buddy, buddy, buddy)来说,传入的uname就是Buddy,也就是说相当于下面的函数是在定义PageBuddy、__SetPageBuddy以及__ClearPageBuddy。

判断page是不是目前在Buddy system中的依据是判断它的page_type是否等于PGTY_buddy。

#define PAGE_TYPE_OPS(uname, lname, fname)				\

FOLIO_TYPE_OPS(lname, fname)						\

static __always_inline int Page##uname(const struct page *page)		\

{									\

	return data_race(page->page_type >> 24) == PGTY_##lname;	\

}									\

static __always_inline void __SetPage##uname(struct page *page)		\

{									\

	if (Page##uname(page))						\

		return;							\

	VM_BUG_ON_PAGE(data_race(page->page_type) != UINT_MAX, page);	\

	page->page_type = (unsigned int)PGTY_##lname << 24;		\

}									\

static __always_inline void __ClearPage##uname(struct page *page)	\

{									\

	if (page->page_type == UINT_MAX)				\

		return;							\

	VM_BUG_ON_PAGE(!Page##uname(page), page);			\

	page->page_type = UINT_MAX;					\

}

有了这层知识之后,就很容易举一反三的想到,folio_test_xxx很可能也是用了类似的方式去判断PG_XXX标志有没有设置,那就在page-flags.h这个头文件里进一步搜索吧!

FOLIO_FLAG()

最终也是破案了:

#define FOLIO_FLAG(name, page)                                                \
FOLIO_TEST_FLAG(name, page)                                                \
FOLIO_SET_FLAG(name, page)                                                \
FOLIO_CLEAR_FLAG(name, page)

还有:

/*
 * Macros to create function definitions for page flags
 */
#define FOLIO_TEST_FLAG(name, page)                                        \
static __always_inline bool folio_test_##name(const struct folio *folio) \
{ return test_bit(PG_##name, const_folio_flags(folio, page)); }

#define FOLIO_SET_FLAG(name, page)                                        \
static __always_inline void folio_set_##name(struct folio *folio)        \
{ set_bit(PG_##name, folio_flags(folio, page)); }

#define FOLIO_CLEAR_FLAG(name, page)                                        \
static __always_inline void folio_clear_##name(struct folio *folio)        \
{ clear_bit(PG_##name, folio_flags(folio, page)); }

#define __FOLIO_SET_FLAG(name, page)                                        \
.........

可以通过FOLIO_TEST_FLAG来定义folio_test_xxx类型的函数,FOLIO_SET_FLAG来定义folio_set_xxx类型的函数,以此类推

结合一下实际使用的场景:

FOLIO_FLAG(active, FOLIO_HEAD_PAGE) //FOLIO_HEAD_PAGE为0

active会作为name传入,folio_test_active()就是这样被定义的!这个函数的作用,看下面的test_bit,folio_flags用于获取flag,而前面的PG_active用于指定检查flag里是否有设置PG_active,这下彻底悟了。set和clear就以此类推即可。

folio_flags()

该函数用于获取folio里第n个页的flags。(从上面的应用场景可以看出,n实际上就是FOLIO_HEAD_PAGE,即0)

但实际上,如果这个folio不是个单页,而是个复合页compound page,那么期望传进来的n是0,因为复合页的flag信息被保存在头页中,如果page->compound_head & 1,说明是尾页,因为尾页的compound_head的最后一位均设置为1。

访问尾页不是kernel所期望的,会触发BUG。

关于复合页这块的设置,请参考prep_compound_page()。通常是当page分配成功时,会调用prep_new_page对这个页做一些初始化,那么如果检测到申请page时加上了__GFP_COMP来申请复合页,最终会调用prep_compound_page()。

static unsigned long *folio_flags(struct folio *folio, unsigned n)
{
        struct page *page = &folio->page;

    //如果page->compound_head & 1,说明不是头页
        VM_BUG_ON_PGFLAGS(page->compound_head & 1, page);
        VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags), page);
        return &page[n].flags;
}

总结

一开始folio_test_anon()可以搜到的理由想必也很明显了,因为判断folio是不是匿名页并不依赖于PG_XXX这类flag,而是依赖于mapping参数,因此不能和folio_test_active()这类函数归于一类一起初始化。

本文虽然无聊,但是或许可以打开一些喜欢阅读代码的同学的思路,如果有时候搜不到一些函数的定义,也许这些函数就是用本文这种特殊的方式去定义的。探索的过程还是非常有意思的。