xref: /linux/Documentation/translations/zh_CN/core-api/circular-buffers.rst (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1*2e6506c1SBinbin Zhou.. SPDX-License-Identifier: GPL-2.0+
2*2e6506c1SBinbin Zhou
3*2e6506c1SBinbin Zhou.. include:: ../disclaimer-zh_CN.rst
4*2e6506c1SBinbin Zhou
5*2e6506c1SBinbin Zhou:Original: Documentation/core-api/circular-buffers.rst
6*2e6506c1SBinbin Zhou
7*2e6506c1SBinbin Zhou:翻译:
8*2e6506c1SBinbin Zhou
9*2e6506c1SBinbin Zhou 周彬彬 Binbin Zhou <zhoubinbin@loongson.cn>
10*2e6506c1SBinbin Zhou
11*2e6506c1SBinbin Zhou:校译:
12*2e6506c1SBinbin Zhou
13*2e6506c1SBinbin Zhou 司延腾 Yanteng Si <siyanteng@loongson.cn>
14*2e6506c1SBinbin Zhou 吴想成 Wu Xiangcheng <bobwxc@email.cn>
15*2e6506c1SBinbin Zhou 时奎亮 Alex Shi <alexs@kernel.org>
16*2e6506c1SBinbin Zhou
17*2e6506c1SBinbin Zhou==========
18*2e6506c1SBinbin Zhou环形缓冲区
19*2e6506c1SBinbin Zhou==========
20*2e6506c1SBinbin Zhou
21*2e6506c1SBinbin Zhou:作者: David Howells <dhowells@redhat.com>
22*2e6506c1SBinbin Zhou:作者: Paul E. McKenney <paulmck@linux.ibm.com>
23*2e6506c1SBinbin Zhou
24*2e6506c1SBinbin Zhou
25*2e6506c1SBinbin ZhouLinux 提供了许多可用于实现循环缓冲的特性。有两组这样的特性:
26*2e6506c1SBinbin Zhou
27*2e6506c1SBinbin Zhou (1) 用于确定2次方大小的缓冲区信息的便利函数。
28*2e6506c1SBinbin Zhou
29*2e6506c1SBinbin Zhou (2) 可以代替缓冲区中对象的生产者和消费者共享锁的内存屏障。
30*2e6506c1SBinbin Zhou
31*2e6506c1SBinbin Zhou如下所述,要使用这些设施,只需要一个生产者和一个消费者。可以通过序列化来处理多个
32*2e6506c1SBinbin Zhou生产者,并通过序列化来处理多个消费者。
33*2e6506c1SBinbin Zhou
34*2e6506c1SBinbin Zhou.. Contents:
35*2e6506c1SBinbin Zhou
36*2e6506c1SBinbin Zhou (*) 什么是环形缓冲区?
37*2e6506c1SBinbin Zhou
38*2e6506c1SBinbin Zhou (*) 测量2次幂缓冲区
39*2e6506c1SBinbin Zhou
40*2e6506c1SBinbin Zhou (*) 内存屏障与环形缓冲区的结合使用
41*2e6506c1SBinbin Zhou     - 生产者
42*2e6506c1SBinbin Zhou     - 消费者
43*2e6506c1SBinbin Zhou
44*2e6506c1SBinbin Zhou (*) 延伸阅读
45*2e6506c1SBinbin Zhou
46*2e6506c1SBinbin Zhou
47*2e6506c1SBinbin Zhou
48*2e6506c1SBinbin Zhou什么是环形缓冲区?
49*2e6506c1SBinbin Zhou==================
50*2e6506c1SBinbin Zhou
51*2e6506c1SBinbin Zhou首先,什么是环形缓冲区?环形缓冲区是具有固定的有限大小的缓冲区,它有两个索引:
52*2e6506c1SBinbin Zhou
53*2e6506c1SBinbin Zhou (1) 'head'索引 - 生产者将元素插入缓冲区的位置。
54*2e6506c1SBinbin Zhou
55*2e6506c1SBinbin Zhou (2) 'tail'索引 - 消费者在缓冲区中找到下一个元素的位置。
56*2e6506c1SBinbin Zhou
57*2e6506c1SBinbin Zhou通常,当tail指针等于head指针时,表明缓冲区是空的;而当head指针比tail指针少一个时,
58*2e6506c1SBinbin Zhou表明缓冲区是满的。
59*2e6506c1SBinbin Zhou
60*2e6506c1SBinbin Zhou添加元素时,递增head索引;删除元素时,递增tail索引。tail索引不应该跳过head索引,
61*2e6506c1SBinbin Zhou两个索引在到达缓冲区末端时都应该被赋值为0,从而允许海量的数据流过缓冲区。
62*2e6506c1SBinbin Zhou
63*2e6506c1SBinbin Zhou通常情况下,元素都有相同的单元大小,但这并不是使用以下技术的严格要求。如果要在缓
64*2e6506c1SBinbin Zhou冲区中包含多个元素或可变大小的元素,则索引可以增加超过1,前提是两个索引都没有超过
65*2e6506c1SBinbin Zhou另一个。然而,实现者必须小心,因为超过一个单位大小的区域可能会覆盖缓冲区的末端并
66*2e6506c1SBinbin Zhou且缓冲区会被分成两段。
67*2e6506c1SBinbin Zhou
68*2e6506c1SBinbin Zhou测量2次幂缓冲区
69*2e6506c1SBinbin Zhou===============
70*2e6506c1SBinbin Zhou
71*2e6506c1SBinbin Zhou计算任意大小的环形缓冲区的占用或剩余容量通常是一个费时的操作,需要使用模(除法)
72*2e6506c1SBinbin Zhou指令。但是如果缓冲区的大小为2次幂,则可以使用更快的按位与指令代替。
73*2e6506c1SBinbin Zhou
74*2e6506c1SBinbin ZhouLinux提供了一组用于处理2次幂环形缓冲区的宏。可以通过以下方式使用::
75*2e6506c1SBinbin Zhou
76*2e6506c1SBinbin Zhou	#include <linux/circ_buf.h>
77*2e6506c1SBinbin Zhou
78*2e6506c1SBinbin Zhou这些宏包括:
79*2e6506c1SBinbin Zhou
80*2e6506c1SBinbin Zhou (#) 测量缓冲区的剩余容量::
81*2e6506c1SBinbin Zhou
82*2e6506c1SBinbin Zhou	CIRC_SPACE(head_index, tail_index, buffer_size);
83*2e6506c1SBinbin Zhou
84*2e6506c1SBinbin Zhou     返回缓冲区[1]中可插入元素的剩余空间大小。
85*2e6506c1SBinbin Zhou
86*2e6506c1SBinbin Zhou
87*2e6506c1SBinbin Zhou (#) 测量缓冲区中的最大连续立即可用空间::
88*2e6506c1SBinbin Zhou
89*2e6506c1SBinbin Zhou	CIRC_SPACE_TO_END(head_index, tail_index, buffer_size);
90*2e6506c1SBinbin Zhou
91*2e6506c1SBinbin Zhou     返回缓冲区[1]中剩余的连续空间的大小,元素可以立即插入其中,而不必绕回到缓冲
92*2e6506c1SBinbin Zhou     区的开头。
93*2e6506c1SBinbin Zhou
94*2e6506c1SBinbin Zhou
95*2e6506c1SBinbin Zhou (#) 测量缓冲区的使用数::
96*2e6506c1SBinbin Zhou
97*2e6506c1SBinbin Zhou	CIRC_CNT(head_index, tail_index, buffer_size);
98*2e6506c1SBinbin Zhou
99*2e6506c1SBinbin Zhou     返回当前占用缓冲区[2]的元素数量。
100*2e6506c1SBinbin Zhou
101*2e6506c1SBinbin Zhou
102*2e6506c1SBinbin Zhou (#) 测量缓冲区的连续使用数::
103*2e6506c1SBinbin Zhou
104*2e6506c1SBinbin Zhou	CIRC_CNT_TO_END(head_index, tail_index, buffer_size);
105*2e6506c1SBinbin Zhou
106*2e6506c1SBinbin Zhou     返回可以从缓冲区中提取的连续元素[2]的数量,而不必绕回到缓冲区的开头。
107*2e6506c1SBinbin Zhou
108*2e6506c1SBinbin Zhou这里的每一个宏名义上都会返回一个介于0和buffer_size-1之间的值,但是:
109*2e6506c1SBinbin Zhou
110*2e6506c1SBinbin Zhou (1) CIRC_SPACE*()是为了在生产者中使用。对生产者来说,它们将返回一个下限,因为生
111*2e6506c1SBinbin Zhou     产者控制着head索引,但消费者可能仍然在另一个CPU上耗尽缓冲区并移动tail索引。
112*2e6506c1SBinbin Zhou
113*2e6506c1SBinbin Zhou     对消费者来说,它将显示一个上限,因为生产者可能正忙于耗尽空间。
114*2e6506c1SBinbin Zhou
115*2e6506c1SBinbin Zhou (2) CIRC_CNT*()是为了在消费者中使用。对消费者来说,它们将返回一个下限,因为消费
116*2e6506c1SBinbin Zhou     者控制着tail索引,但生产者可能仍然在另一个CPU上填充缓冲区并移动head索引。
117*2e6506c1SBinbin Zhou
118*2e6506c1SBinbin Zhou     对于生产者,它将显示一个上限,因为消费者可能正忙于清空缓冲区。
119*2e6506c1SBinbin Zhou
120*2e6506c1SBinbin Zhou (3) 对于第三方来说,生产者和消费者对索引的写入顺序是无法保证的,因为它们是独立的,
121*2e6506c1SBinbin Zhou     而且可能是在不同的CPU上进行的,所以在这种情况下的结果只是一种猜测,甚至可能
122*2e6506c1SBinbin Zhou     是错误的。
123*2e6506c1SBinbin Zhou
124*2e6506c1SBinbin Zhou内存屏障与环形缓冲区的结合使用
125*2e6506c1SBinbin Zhou==============================
126*2e6506c1SBinbin Zhou
127*2e6506c1SBinbin Zhou通过将内存屏障与环形缓冲区结合使用,可以避免以下需求:
128*2e6506c1SBinbin Zhou
129*2e6506c1SBinbin Zhou (1) 使用单个锁来控制对缓冲区两端的访问,从而允许同时填充和清空缓冲区;以及
130*2e6506c1SBinbin Zhou
131*2e6506c1SBinbin Zhou (2) 使用原子计数器操作。
132*2e6506c1SBinbin Zhou
133*2e6506c1SBinbin Zhou这有两个方面:填充缓冲区的生产者和清空缓冲区的消费者。在任何时候,只应有一个生产
134*2e6506c1SBinbin Zhou者在填充缓冲区,同样的也只应有一个消费者在清空缓冲区,但双方可以同时操作。
135*2e6506c1SBinbin Zhou
136*2e6506c1SBinbin Zhou
137*2e6506c1SBinbin Zhou生产者
138*2e6506c1SBinbin Zhou------
139*2e6506c1SBinbin Zhou
140*2e6506c1SBinbin Zhou生产者看起来像这样::
141*2e6506c1SBinbin Zhou
142*2e6506c1SBinbin Zhou	spin_lock(&producer_lock);
143*2e6506c1SBinbin Zhou
144*2e6506c1SBinbin Zhou	unsigned long head = buffer->head;
145*2e6506c1SBinbin Zhou	/* spin_unlock()和下一个spin_lock()提供必要的排序。 */
146*2e6506c1SBinbin Zhou	unsigned long tail = READ_ONCE(buffer->tail);
147*2e6506c1SBinbin Zhou
148*2e6506c1SBinbin Zhou	if (CIRC_SPACE(head, tail, buffer->size) >= 1) {
149*2e6506c1SBinbin Zhou		/* 添加一个元素到缓冲区 */
150*2e6506c1SBinbin Zhou		struct item *item = buffer[head];
151*2e6506c1SBinbin Zhou
152*2e6506c1SBinbin Zhou		produce_item(item);
153*2e6506c1SBinbin Zhou
154*2e6506c1SBinbin Zhou		smp_store_release(buffer->head,
155*2e6506c1SBinbin Zhou				  (head + 1) & (buffer->size - 1));
156*2e6506c1SBinbin Zhou
157*2e6506c1SBinbin Zhou		/* wake_up()将确保在唤醒任何人之前提交head */
158*2e6506c1SBinbin Zhou		wake_up(consumer);
159*2e6506c1SBinbin Zhou	}
160*2e6506c1SBinbin Zhou
161*2e6506c1SBinbin Zhou	spin_unlock(&producer_lock);
162*2e6506c1SBinbin Zhou
163*2e6506c1SBinbin Zhou这将表明CPU必须在head索引使其对消费者可用之前写入新项目的内容,同时CPU必须在唤醒
164*2e6506c1SBinbin Zhou消费者之前写入修改后的head索引。
165*2e6506c1SBinbin Zhou
166*2e6506c1SBinbin Zhou请注意,wake_up()并不保证任何形式的屏障,除非确实唤醒了某些东西。因此我们不能依靠
167*2e6506c1SBinbin Zhou它来进行排序。但是数组中始终有一个元素留空,因此生产者必须产生两个元素,然后才可
168*2e6506c1SBinbin Zhou能破坏消费者当前正在读取的元素。同时,消费者连续调用之间成对的解锁-加锁提供了索引
169*2e6506c1SBinbin Zhou读取(指示消费者已清空给定元素)和生产者对该相同元素的写入之间的必要顺序。
170*2e6506c1SBinbin Zhou
171*2e6506c1SBinbin Zhou
172*2e6506c1SBinbin Zhou消费者
173*2e6506c1SBinbin Zhou------
174*2e6506c1SBinbin Zhou
175*2e6506c1SBinbin Zhou消费者看起来像这样::
176*2e6506c1SBinbin Zhou
177*2e6506c1SBinbin Zhou	spin_lock(&consumer_lock);
178*2e6506c1SBinbin Zhou
179*2e6506c1SBinbin Zhou	/* 读取该索引处的内容之前,先读取索引 */
180*2e6506c1SBinbin Zhou	unsigned long head = smp_load_acquire(buffer->head);
181*2e6506c1SBinbin Zhou	unsigned long tail = buffer->tail;
182*2e6506c1SBinbin Zhou
183*2e6506c1SBinbin Zhou	if (CIRC_CNT(head, tail, buffer->size) >= 1) {
184*2e6506c1SBinbin Zhou
185*2e6506c1SBinbin Zhou		/* 从缓冲区中提取一个元素 */
186*2e6506c1SBinbin Zhou		struct item *item = buffer[tail];
187*2e6506c1SBinbin Zhou
188*2e6506c1SBinbin Zhou		consume_item(item);
189*2e6506c1SBinbin Zhou
190*2e6506c1SBinbin Zhou		/* 在递增tail之前完成对描述符的读取。 */
191*2e6506c1SBinbin Zhou		smp_store_release(buffer->tail,
192*2e6506c1SBinbin Zhou				  (tail + 1) & (buffer->size - 1));
193*2e6506c1SBinbin Zhou	}
194*2e6506c1SBinbin Zhou
195*2e6506c1SBinbin Zhou	spin_unlock(&consumer_lock);
196*2e6506c1SBinbin Zhou
197*2e6506c1SBinbin Zhou这表明CPU在读取新元素之前确保索引是最新的,然后在写入新的尾指针之前应确保CPU已完
198*2e6506c1SBinbin Zhou成读取该元素,这将擦除该元素。
199*2e6506c1SBinbin Zhou
200*2e6506c1SBinbin Zhou请注意,使用READ_ONCE()和smp_load_acquire()来读取反向(head)索引。这可以防止编译
201*2e6506c1SBinbin Zhou器丢弃并重新加载其缓存值。如果您能确定反向(head)索引将仅使用一次,则这不是必须
202*2e6506c1SBinbin Zhou的。smp_load_acquire()还可以强制CPU对后续的内存引用进行排序。类似地,两种算法都使
203*2e6506c1SBinbin Zhou用smp_store_release()来写入线程的索引。这记录了我们正在写入可以并发读取的内容的事
204*2e6506c1SBinbin Zhou实,以防止编译器破坏存储,并强制对以前的访问进行排序。
205*2e6506c1SBinbin Zhou
206*2e6506c1SBinbin Zhou
207*2e6506c1SBinbin Zhou延伸阅读
208*2e6506c1SBinbin Zhou========
209*2e6506c1SBinbin Zhou
210*2e6506c1SBinbin Zhou关于Linux的内存屏障设施的描述,请查看Documentation/memory-barriers.txt211