> For the complete documentation index, see [llms.txt](https://richardweiyang-2.gitbook.io/kernel-exploring/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-3/09-static_key.md).

# Static Key

我们在内核中常常会看到这样的判断语句：

```
	/* skip numa counters update if numa stats is disabled */
	if (!static_branch_likely(&vm_numa_stat_key))
		return;
```

当配置变化的时侯，又可以用static\_branch\_enable/static\_branch\_disable()来改变条件。

我有时候就在想，这个和一般的if判断有什么区别？原来这是内核中的一种优化，可以让代码看上去是动态判断，实际上是静态判断。

接下来我们就来看看这是怎么做到的。

## 定义

首先我们要从定义入手：

```
#define STATIC_KEY_TRUE_INIT  (struct static_key_true) { .key = STATIC_KEY_INIT_TRUE,  }
#define DEFINE_STATIC_KEY_TRUE(name)	\
	struct static_key_true name = STATIC_KEY_TRUE_INIT

DEFINE_STATIC_KEY_TRUE(vm_numa_stat_key);
```

实际上就是定义了一个static\_key的结构：

```
struct static_key {
	atomic_t enabled;
#ifdef CONFIG_JUMP_LABEL
/*
 * bit 0 => 1 if key is initially true
 *	    0 if initially false
 * bit 1 => 1 if points to struct static_key_mod
 *	    0 if points to struct jump_entry
 */
	union {
		unsigned long type;
		struct jump_entry *entries;
		struct static_key_mod *next;
	};
#endif	/* CONFIG_JUMP_LABEL */
};
```

这么看上去还是有点复杂。我们一点点展开来看。

## 简化版本

我们在定义中看到有一个#ifdef CONFIG\_JUMP\_LABEL的条件编译，所以我们先偷懒来看看没有配置这个选项的时候是怎么样的。

### 定义

```
struct static_key {
	atomic_t enabled;
};
```

如果没有配置CONFIG\_JUMP\_LABEL，static\_key就是一个原子变量了。

而当我们定义一个默认为true的static key时，展开如下:

```
#define STATIC_KEY_INIT_TRUE	{ .enabled = ATOMIC_INIT(1) }
#define STATIC_KEY_TRUE_INIT  (struct static_key_true) { .key = STATIC_KEY_INIT_TRUE,  }
#define DEFINE_STATIC_KEY_TRUE(name)	\
	struct static_key_true name = STATIC_KEY_TRUE_INIT

DEFINE_STATIC_KEY_TRUE(vm_numa_stat_key);
```

也就是将enabled的初始值设置为1，这样在后续判断中，就会返回1。

### 改变

```
#define static_branch_enable(x)			static_key_enable(&(x)->key)

static inline void static_key_enable(struct static_key *key)
{
	STATIC_KEY_CHECK_USE(key);

	if (atomic_read(&key->enabled) != 0) {
		WARN_ON_ONCE(atomic_read(&key->enabled) != 1);
		return;
	}
	atomic_set(&key->enabled, 1);
}

static inline void static_key_disable(struct static_key *key)
{
	STATIC_KEY_CHECK_USE(key);

	if (atomic_read(&key->enabled) != 1) {
		WARN_ON_ONCE(atomic_read(&key->enabled) != 0);
		return;
	}
	atomic_set(&key->enabled, 0);
}
```

由此，enable/disable就是设置这个原子变量为1或者0.

### 判断

```
static __always_inline int static_key_count(struct static_key *key)
{
	return raw_atomic_read(&key->enabled);
}

#define static_key_enabled(x)							\
({										\
	if (!__builtin_types_compatible_p(typeof(*x), struct static_key) &&	\
	    !__builtin_types_compatible_p(typeof(*x), struct static_key_true) &&\
	    !__builtin_types_compatible_p(typeof(*x), struct static_key_false))	\
		____wrong_branch_error();					\
	static_key_count((struct static_key *)x) > 0;				\
})

#define static_branch_likely(x)		likely_notrace(static_key_enabled(&(x)->key))
```

这样branch的判断，就变成了原子变量值的判断了。怎么样，这个分支是不是超级简单。

但是如果就是这么实现的话，那和普通的变量判断逻辑就一样了。

好了，偷懒偷过了，该是正经研究代码的时候了。

## 正式版本

现在我来看设置了CONFIG\_JUMP\_LABEL的版本。

### 定义

```
struct jump_entry {
	s32 code;
	s32 target;
	long key;	// key may be far away from the core kernel under KASLR
};

struct static_key {
	atomic_t enabled;
/*
 * bit 0 => 1 if key is initially true
 *	    0 if initially false
 * bit 1 => 1 if points to struct static_key_mod
 *	    0 if points to struct jump_entry
 */
	union {
		unsigned long type;
		struct jump_entry *entries;
		struct static_key_mod *next;
	};
};
```

我把条件编译的定义去掉了。这么看，static\_key除了有一个原子变量外，还有一个联合体。

其中注释上写了这个联合体的含义。恩，又是一个magic的用法。让我们继续往后看。

还是看vm\_numa\_stat\_key这个变量的定义，看看设置了CONFIG\_JUMP\_LABEL时，定义会有什么变化。

```
#define JUMP_TYPE_TRUE		1UL
#define STATIC_KEY_INIT_TRUE					\
	{ .enabled = ATOMIC_INIT(1),				\
	  .type = JUMP_TYPE_TRUE }
#define STATIC_KEY_TRUE_INIT  (struct static_key_true) { .key = STATIC_KEY_INIT_TRUE,  }
#define DEFINE_STATIC_KEY_TRUE(name)	\
	struct static_key_true name = STATIC_KEY_TRUE_INIT

DEFINE_STATIC_KEY_TRUE(vm_numa_stat_key);
```

这里的差别在与定义时设置了type=JUMP\_TYPE\_TRUE，也就是为1.

根据注释，意思就是初始化时表示true。

### 改变

还是通过static\_branch\_enable和static\_branch\_disable来改变状态。

```
static void jump_label_update(struct static_key *key)
{
	struct jump_entry *stop = __stop___jump_table;
	bool init = system_state < SYSTEM_RUNNING;
	struct jump_entry *entry;

	entry = static_key_entries(key);
	/* if there are no users, entry can be NULL */
	if (entry)
		__jump_label_update(key, entry, stop, init);
}

void static_key_enable_cpuslocked(struct static_key *key)
{
	STATIC_KEY_CHECK_USE(key);
	lockdep_assert_cpus_held();

	if (atomic_read(&key->enabled) > 0) {
		WARN_ON_ONCE(atomic_read(&key->enabled) != 1);
		return;
	}

	jump_label_lock();
	if (atomic_read(&key->enabled) == 0) {
		atomic_set(&key->enabled, -1);
		jump_label_update(key);
		/*
		 * See static_key_slow_inc().
		 */
		atomic_set_release(&key->enabled, 1);
	}
	jump_label_unlock();
}
void static_key_enable(struct static_key *key)
{
	cpus_read_lock();
	static_key_enable_cpuslocked(key);
	cpus_read_unlock();
}
```

所以更改过程中，重要的是jump\_label\_update函数。（去掉了部分代码。）

接下来，从static\_key中得到jump\_entry后，就开始工作了。

```
static __always_inline void
__jump_label_transform(struct jump_entry *entry,
		       enum jump_label_type type,
		       int init)
{
	const struct jump_label_patch jlp = __jump_label_patch(entry, type);

	...

	smp_text_poke_single((void *)jump_entry_code(entry), jlp.code, jlp.size, NULL);
}

static void __jump_label_update(struct static_key *key,
				struct jump_entry *entry,
				struct jump_entry *stop,
				bool init)
{
	for (; (entry < stop) && (jump_entry_key(entry) == key); entry++) {
		if (jump_label_can_update(entry, init))
			arch_jump_label_transform(entry, jump_label_type(entry));
	}
}
```

最后在x86上，做工作的是\_\_jump\_label\_transform()函数。

可以分成两步：

* \_\_jump\_label\_patch(): 根据jump\_entry中记录的信息生成一个jump指令，或者是nop
* smp\_text\_poke\_single(): 将jlp这个指令写道jump\_entry记录的地址中

### 判断

实际这里没有额外的判断，代码已经被改写。

要么nop什么也不执行，要么就会跳转到已经改写好的目标代码执行。

但是好像还缺了点什么。知道了部分细节后，让我们串起来看一下吧。

## 缺失的细节

虽然原理上，我们从上面的代码已经看到了，但是细节上我们还缺一些东西。

* jump\_entry是谁设置的， 又是怎么链接上static\_key的呢？

这是因为刚才偷懒，把判断部分的定义给跳过了。虽然最后逻辑上，判断的过程就是nop或者jmp，但是细节就藏在这里。

### static\_branch\_likely()不只是判断

通常我们使用static\_key时，用的这样的语句：

```
	if (!static_branch_likely(&vm_numa_stat_key))
		return;
```

我们期望的是static\_branch\_likely()就是一个判断，返回true或者false。但为了能运行时改动逻辑，这里还做了其他。

```
#define JUMP_TABLE_ENTRY(key, label)			\
	".pushsection __jump_table,  \"aw\" \n\t"	\
	_ASM_ALIGN "\n\t"				\
	ANNOTATE_DATA_SPECIAL "\n"			\
	".long 1b - . \n\t"				\
	".long " label " - . \n\t"			\
	_ASM_PTR " " key " - . \n\t"			\
	".popsection \n\t"

#define ARCH_STATIC_BRANCH_ASM(key, label)		\
	"1: .byte " __stringify(BYTES_NOP5) "\n\t"	\
	JUMP_TABLE_ENTRY(key, label)
`
static __always_inline bool arch_static_branch(struct static_key * const key, const bool branch)
{
	asm goto(ARCH_STATIC_BRANCH_ASM("%c0 + %c1", "%l[l_yes]")
		: :  "i" (key), "i" (branch) : : l_yes);

	return false;
l_yes:
	return true;
}

#define static_branch_likely(x)							\
({										\
	bool branch;								\
	if (__builtin_types_compatible_p(typeof(*x), struct static_key_true))	\
		branch = !arch_static_branch(&(x)->key, true);			\
	else if (__builtin_types_compatible_p(typeof(*x), struct static_key_false)) \
		branch = !arch_static_branch_jump(&(x)->key, true);		\
	else									\
		branch = ____wrong_branch_error();				\
	likely_notrace(branch);								\
})
```

如果我们不看arch\_static\_branch()中的asm goto()，static\_branch\_likely()返回的就是true。这和我们理解的简单逻辑一致。

那asm goto这段是什么呢？ 就两部分：

* 一个nop
* 一个jump\_entry

而且关键是这个jump\_entry不是生成在当前的代码段，而是通过pushsection放到了\_\_jump\_table的代码段。

再来看jump\_entry中三个字段分别赋值了什么：

* code: 1b， 也就是nop指令的地址
* target: l\_yes, 这样跳转到l\_yes就会变成返回true，达到目的
* key: key，也就是当前static\_key结构体的地址 和 当前是否要branch(参见jump\_entry\_is\_branch())

### \_\_jump\_table的家

刚才说到，每个jump\_entry都定义在\_\_jump\_table段中，那我们来看看这个段定义的范围。

```
#define BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, _BEGIN_, _END_)	\
	_BEGIN_##_label_ = .;						\
	KEEP(*(_sec_))							\
	_END_##_label_ = .;

#define BOUNDED_SECTION_BY(_sec_, _label_)				\
	BOUNDED_SECTION_PRE_LABEL(_sec_, _label_, __start, __stop)

#define JUMP_TABLE_DATA							\
	. = ALIGN(8);							\
	BOUNDED_SECTION_BY(__jump_table, ___jump_table)
```

这里定义了一个"\_\_jump\_table"的section，头尾的边界是\_\_start\_\_\_jump\_table和\_\_stop\_\_\_jump\_table。

还记得这个\_\_stop\_\_\_jump\_table吗？上面好像看到过。

### static\_key和jump\_entry的关联

我们在jump\_entry的定义中看到了它指向了static\_key，但是我们在static\_key的定义中，并没有看到它关联到了jump\_entry。

* DEFINE\_STATIC\_KEY\_TRUE()仅仅设置了.type，对应的entries是空的
* 在jump\_label\_update()更新时，又要从static\_key\_entries()中找出所有的entry

所以static\_key中的entries是在jump\_label\_init()中绑定的。

所以在运行的时候，一个static\_key长这样：

```
     static_key                 __start___jump_table
     +--------------+           +---------+
     |              |           |         |
     |entries       |---------->|         |
     |              |           |         |
     +--------------+           |         |
                                |         |
                                |         |
                                +---------+
```

从entries指向的位值开始，所有key是static\_key的jump\_entry都是使用了这个key的代码点。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-3/09-static_key.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
