# 内存屏障

这部分的内容，主要来自于[1](https://docs.kernel.org/core-api/wrappers/memory-barriers.html)的学习笔记。

## 为什么需要内存屏障

因为下面几个原因，导致需要内存屏障：

* 编译器优化代码
* CPU乱序执行
* 内存一致性
* 内存合并操作
* 内存预读取
* 分支预测

PS: 在[1](https://docs.kernel.org/core-api/wrappers/memory-barriers.html)的GUARANTEES部分，提到了编译器和CPU会保证顺序的情况--访问的变量有前后依赖的情况。

而内存屏障提供了一种方法，让代码的执行顺序能按照我们写的来。

PS: 实际上应该是内存屏障和编译屏障一起提供保障。

## 内存屏障的种类

通常我们看到的种类有：

* Write memory barrier
* Read memory barrier
* General memory barrier

这几个的定义可以用一句话概括：

> all the LOAD/STORE operations specified before the barrier will appear to **happen before** all the LOAD/STORE operations specified after the barrier with respect to the other components of the system.

以及隐含变体：

* ACQUIRE operation
* RELEASE operation

这两个我感觉以RCU里面的subscribe/publish来理解可能容易点。

RELEASE对应的是publish，而ACQUIRE对应的是subscribe。之前我一直认为RELEASE是释放的意思，但感觉这里解释为发布更为合适。

## Address Dependency Barrier(Historical)

我们先看一个例子：

```
        CPU 1                 CPU 2
        ===============       ===============
        { A == 1, B == 2, C == 3, P == &A, Q == &C }
        B = 4;
        <write barrier>
        WRITE_ONCE(P, &B);
                              Q = READ_ONCE_OLD(P);
                              D = *Q;
```

这里面有个很神奇的情况，就是从CPU2的角度看P已经赋值为\&B，但是B还是2。（这种情况会发生在split cache的机器上）

具体解决方法是使用READ\_ONCE()，因为当前的READ\_ONCE()里隐藏了address dependency。

```
        CPU 1                 CPU 2
        ===============       ===============
        { A == 1, B == 2, C == 3, P == &A, Q == &C }
        B = 4;
        <write barrier>
        WRITE_ONCE(P, &B);
                              Q = READ_ONCE(P);
                              <implicit address-dependency barrier>
                              D = *Q;
```

这里需要关注的是，如果没有隐藏的address dependency，会影响rcu的功能。 我们可以把P看作一个全局指针，B是一个新的版本。当CPU2认为P已经更新到B这个版本时，如果看到的B里内容不是最新的，那就有问题了。

## Control Dependency

这部分主要是因为编译器会对if这样的判断语句做优化，导致代码不按照我们的预期执行。

从形式上看，又可以分成两类：

* load-load control dependency
* load-store control dependency

### load-load

比如这样的情况

```
        q = READ_ONCE(a);
        <implicit address-dependency barrier>
        if (q) {
                /* BUG: No address dependency!!! */
                p = READ_ONCE(b);
        }
```

两个READ\_ONCE之间没有地址上的依赖，一个是从a读，一个是从b读，所以CPU可以打乱两者的顺序。 所以正确的写法是

```
        q = READ_ONCE(a);
        if (q) {
                <read barrier>
                p = READ_ONCE(b);
        }
```

PS: 如果是单线程纯内存访问，不加barrier可能也没问题。最后还是要判断q是不是非空，才会赋值到p。但是如果是访问设备寄存器的话，就必须加barrier了。

### load-store

写的情况稍微好些，因为之间有一定的地址依赖：

```
        q = READ_ONCE(a);
        if (q) {
                WRITE_ONCE(b, 1);
        }
```

PS: 其实我好像没有看出来有依赖，但意思就是能。而且必须是先读后写。

另外重要的是READ\_ONCE和WRITE\_ONCE是必须要的。

后面还有几个编译器能预测出if结果的例子，这里就不展开了。

## SMP Barrier Pairing

这一部分主要关注的是多个CPU访问同一段内存的情况，这个问题是由内存一致性引入的。

为了解决这个问题，通常需要**内存屏障成对出现**。

例如：

```
        CPU 1                   CPU 2
        ======================= =======================
                { A = 0, B = 9 }
        STORE A=1
        <write barrier>
        STORE B=2
                                LOAD B
                                <read barrier>
                                LOAD A
```

CPU1/CPU2中各自要加上屏障，才能保证CPU2上读到B==2后，A等于1。

## 内核中显式屏障

这个又分成两种：

* compiler barrier
* cpu memory barrier

### Compiler Barrier

* barrier()
* READ\_ONCE()/WRITE\_ONCE()

不过READ\_ONCE()/WRITE\_ONCE()还有点cpu memory barrier的作用。

比如这个例子：

```
        a[0] = READ_ONCE(x);
        a[1] = READ_ONCE(x);
```

说是可以防止a[1](https://docs.kernel.org/core-api/wrappers/memory-barriers.html)的值不会比a\[0]的旧，而仅仅是编译器屏障，是不能保证cpu不会乱序执行的。

另外还有个例子：

作者说下面的代码

```
        while (tmp = a)
                do_something_with(tmp);
```

会被编译器优化成

```
        if (tmp = a)
                for (;;)
                        do_something_with(tmp);
```

所以以后像while/if这种判断里面，最好是不要做赋值操作了。

或者也会因为寄存器不够用，将上面的代码优化成：

```
        while (a)
                do_something_with(a);
```

这样如果有另一个线程在do\_something\_with()前更改了a，那就不是我们想要的行为了。

所以这种情况需要写成：

```
        while (tmp = READ_ONCE(a))
                do_something_with(tmp);
```

### CPU memory barrier

内核中其中基本的内存屏障：

| Type               | Mandatory | SMP Conditional |
| ------------------ | --------- | --------------- |
| General            | mb()      | smp\_mb()       |
| Write              | wmb()     | smp\_wmb()      |
| Read               | rmb()     | smp\_rmb()      |
| Address Dependency |           | READ\_ONCE()    |

除了Address Dependency, 都隐含了编译器屏障。

## 内核中隐含屏障

除了显式内存屏障，内核中有些接口隐含调用了内存屏障：

* 锁
* 睡眠/唤醒
* 调度

## 哪里需要用到内存屏障？

代码执行顺序重排，在下面几个情况会产生问题：

* interprocessor interaction
* atomic operation
* accessing device
* interrupt

## 内存模型模拟

在[1](https://docs.kernel.org/core-api/wrappers/memory-barriers.html)中，提到了一个内存模型的模拟器herd7，以及内核中相关验证代码在tools/memory-model目录。

另外还找到[一篇文章](https://www.joelfernandes.org/resources/lkmm_herd7.pdf)简述了herd7的使用。

## 一些例子

### Double-checked locking

内核文档 Documentation/litmus-tests/locking/DCL-broken.litmus 写了一个很有意思的问题。

对应还有解释在tools/memory-model/Documentation/locking.txt。

下面这段代码是有问题的

```
	void CPU0(void)
	{
		r0 = READ_ONCE(flag);                 --------+
		if (r0 == 0) {                                |
			spin_lock(&lck);                      |
			r1 = READ_ONCE(flag);                 |
			if (r1 == 0) {                        |
				WRITE_ONCE(data, 1);  --+     |
				WRITE_ONCE(flag, 1);  --+     |
			}                                     |
			spin_unlock(&lck);                    |
		}                                             |
		r2 = READ_ONCE(data);                 --------+
	}
	/* CPU1() is the exactly the same as CPU0(). */
```

因为在上面标出来的两对代码可能会被重排，所以需要用smp\_load\_acquire()/smp\_store\_release()修复。

## 参考资料

[Memory Barriers](https://docs.kernel.org/core-api/wrappers/memory-barriers.html)


---

# Agent Instructions: 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:

```
GET https://richardweiyang-2.gitbook.io/kernel-exploring/00-index-1/02-memory_barrier.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
