《LINUX3.0内核源代码分析》第三章:内核同步(1)

  • 时间:
  • 浏览:0
  • 来源:UU快3直播官网

"       teq   %1, #0\n"

Paul没办法 讲过:在建造大桥前一天,时要得明白力学的原理。要理解内存屏障,首先得明白计算机硬件体系社会形态,有点痛 是硬件是何如管理缓存的。缓趋于稳定多核上的一致性难题是何如产生的。

          * 它与"排它性"存储配对使用。

 * 这里强制将counter转换为volatile int并取其值。目的倘若为了正确处理编译优化。

* 增加每CPU变量计数

         /**

产生可是 难题的根本原因是:

Smp_mbsmp_rmbsmp_wmb仅仅用于SMP系统,它正确处理的是多核之间内存乱序的难题。其具体用法及原理,请参阅《深入理解并行编程》。

         int counter;

          */

关抢占,并获得CPU对应的元素指针。

         __asm__ __volatile__("@ atomic_add\n"

*            batch:       当本CPU计数超过此值时,要确保可是 核能及时看后。                                     

         {

#define atomic_read(v)   (*(volatile int *)&(v)->counter)

#define atomic_set(v,i)    (((v)->counter) = (i))

* 这里时要关抢占。

本连载文章并都在为了形成一本适合出版的书籍,倘若为了向有一定内核基本的读者提供可是 linux3.0源码分析。可是,请读者结合《深入理解LINUX内核》第三版阅读本连载。

G_a++;

read_barrier_dependssmp_ read_barrier_depends是读依赖屏障。除了在DEC alpha架构外,linux支持的可是 均不时要可是 屏障。Alpha时要它,是可能alpha架构中,使用的缓存是split cache.所谓split cache,简单的说倘若有另一一还还有一个 核的缓存不止有另一一还还有一个 .在arm架构下,让我们都都 让我们都都 能都都可以 简单的忽略可是 屏障。

原子的递增计数的值。

法律声明LINUX3.0内核源代码分析》系列文章由谢宝友(scxby@163.com)发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的可是 内容由作者保留所有版权。谢绝转载。

1、《深入理解并行编程》,下载地址是:http://xiebaoyou.download.csdn.net

锁总线是正确的,可是也时要将g_a声明为valatile类型的变量。没办法 ,在让我们都都 让我们都都 分析的ARM多核上,应该为什么么么在办?

Void foo_b(void *unused)

percpu_counter的完整性实现在percpu_counter.c中。有兴趣的同学能都都可以 研究一下。下面让我们都都 让我们都都 讲有另一一还还有一个 主要的函数,希望起个抛砖引玉的作用:

读屏障

{

首先,让我们都都 让我们都都 看一下老版本是何如定义原子变量的:

 */

G_a++;

读依赖屏障

Atomic_read

} atomic_t;

          */

Smp_rmb

假设当stopped被设置为1后,系统系统进程A和系统系统进程B执行了count_acount_b次,您会认为g_a的值等于count_a + count_b吗?

 * 设置原子变量的值。

* 为了正确处理当前任务飘移到可是 核上,可能被可是 核抢占,原因计数丢失

Unsigned long volatile g_a;

         unsigned long tmp;

         /**

void __percpu_counter_add(struct percpu_counter *fbc, s64 amount, s32 batch)

相关宏和函数:

         : "cc");

         : "r" (&v->counter), "Ir" (i)

随便说说linux分读写屏障、读屏障、写屏障,可是在ARM中,它们的实现都在一样的,没办法 严格区别不同的屏障。

不过还是没办法 太高兴了,原子变量随便说说都在毒瘤,可是也差太多了。我没办法 遇到有另一一还还有一个 兄弟,工作十多年了吧,得意的吹嘘:“我写的代码精细得很,统计计数都在用的汇编实现的,汇编加法指令还用了lock前缀。”呜呼,可是 兄弟完整性没办法 意识到在x86体系社会形态中,可是 lock前缀对性能的影响。

 * 原子的递增计数的值。

为什么么么在正确处理可是 难题呢?

}

atomic_add_return递增原子变量的值,并返回它的新值。它与atomic_add的最大不同,在于在原子递增前后各增加了一句:smp_mb();

/**

     */

}

要深入理解内存屏障,建议让我们都都 让我们都都 首先阅读以下资料:

B = 2;

/**

typedef struct { volatile int counter; } atomic_t;

在多核和IO内存、缓存之间设置有另一一还还有一个 写屏障

A = 1;

设置原子变量的值。

原因是那些呢?

rmb

         } else {

}

/**

CPU变量的主要目的是对多CPU并发访问的保护。可是它没办法 正确处理同一核上的中断的影响。让我们都都 让我们都都 没办法 讲过,在armmips等系统中,++--没办法 的简单计数操作,都时要几条汇编话语来完成。可能在从内存中加载数据到寄存器后,还没办法 将数据保存到内存中前,有中断将操作过程打断,并在中断正确处理函数中对同样的计数值进行操作,没办法 中断中的操作将被覆盖。

         While (stopped == 0)

*            fbc:            要增加的每CPU变量

*/

函数名

         int result;

                   spin_unlock(&fbc->lock);

多样化的东西还在中间。接下来让我们都都 让我们都都 新开一帖,讨论内核同步的可是 技术:自旋锁、信号量、RCU、无锁编程。

Linux为开发者实现了以下内存屏障:

1.1 内存屏障

*/

在多核之间设置有另一一还还有一个 读屏障

"       bne  1b"

自旋锁、信号量、complete、读写自旋锁、读写信号量、顺序锁、RCU中放后文介绍。

}

在多核和IO内存、缓存之间设置有另一一还还有一个 读依赖屏障

在描述原子变量和每CPU变量、可是 内核同步最好的办法 前一天,让我们都都 让我们都都 先看一段代码。假设有另一一还还有一个 系统系统进程A和系统系统进程B,它们的执行代码分别是foo_afoo_b,它们都操作有另一一还还有一个 全局变量g_a,如下:

         /**

宏可能函数

1、将测试系统系统进程运行的时间运行得久可是

         count = __this_cpu_read(*fbc->counters) + amount;

         s64 count;

__get_cpu_var

{

能都都可以 将它理解为数据社会形态的数组。系统的每个CPU对应数组中的有另一一还还有一个 元素。每个CPU都只访问本CPU对应的数组元素。

get_cpu_ptr

                   fbc->count += count;/* 修改全局计数,并将本CPU计数清0 */

原子的递减计数的值。

wmb

1.2 都在题外话的题外话

g_a的值后要等于count_a + count_b吗?

Smp_read_barrier_depends

原子比较并交换计数值。

ü  这也是为了正确处理再次总出 多核之间数据覆盖的清况 。对可是 点,可能您暂时没办法 理解。你爱不爱我您在内核领域实际工作几年,也会随便说说这有点痛 难于理解。不过,现在您只时要知道有没办法 有另一一还还有一个 事实趋于稳定就行了。

内存屏障也隐含了编译屏障的作用。所谓编译屏障,是为了正确处理编译乱序的难题。可是 难题的根源在于:在造出者编译器的前一天,多核还未再次总出 。编译器开发者认为编译出来的二进制代码倘若在单核上运行正确就能都都可以 了。甚至,倘若保证单系统系统进程内的系统系统进程逻辑正确性即可。类式,让我们都都 让我们都都 有两句赋值话语:

"1:    ldrex         %0, [%3]\n"

在多核之间设置有另一一还还有一个 读依赖屏障

静态定义有另一一还还有一个 每CPU变量数组

内存屏障是没办法 难此理解也难以使用,为那些还时要它呢?硬件工程师为那些不给软件开发者提供有一种 系统系统进程逻辑一致性的内存视图呢?归根结底,可是 难题受到光速的影响。在1.8G的主频系统中,在有另一一还还有一个 时钟周期内,光在真空中的传播距离没办法 几厘米,电子的传播距离更短,根本无法传播到整个系统中。

 */

2、内核自带的文档documentation/memory-barriers.txt

名称

     * 获得本CPU计数值并加在计数值。

          * 关键代码是这里的判断。可能在ldrexstrex之间,可是 核没办法 对原子变量变量进行加载存储操作,

B = 2;

1.4 每CPU变量

Int stoped = 0;

 */

 */

linux3.0中,可能有所变化:

/**

         /**

不管在多CPU还是单CPU中,内核抢占都可能象中断那样破坏让我们都都 让我们都都 对计数的操作。可是,应当在禁用抢占的清况 下访问每CPU变量。内核抢占是有另一一还还有一个 大话语题,让我们都都 让我们都都 在讲调度的前一天再提可是 事情。

Atomic_sub

2、             Armpowerpcmips那些体系社会形态都在存储/加载体系社会形态,它们没办法 直接对内存中的值进行操作。而时要将内存中的值加载到寄存器中后,将寄存器中的值加1后,再存储到内存中。可能有另一一还还有一个 系统系统进程都读取0值到寄存器中,并将寄存器的值递增为1后存储到内存,没办法 也会丢失一次递增。

*            amount:   本每段增加的计数值

可是,请您:

开抢占,与get_cpu_ptr配对使用。

编译器太多保证生成的汇编是按照C话语的顺序。为了时延可能可是 原因,它生成的汇编话语可能与下面的C代码是一致的:

          * 可能可是 核与本核冲突,没办法 寄存器值为非0,这里跳转到标号1处,重新加载内存的值并递增其值。

说明

per_cpu

原子递增的实现比较精妙,理解它的关键是时要明白ldrexstrex可是 对指令的含义。

返回原子变量的值

恩,当您在一台真实的计算上测试可是 系统系统进程的前一天,你爱不爱我您的直觉是对的,g_a的值随便说说等于count_a + count_b

         : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)

         preempt_disable();

put_cpu_var

为了使总和大致可信,内核又引入了另有一种 每CPU变量:percpu_counter

为了正确处理可是 难题,内核引用入了每CPU变量。

                   __this_cpu_write(*fbc->counters, 0);

}

Linux中的基本原子操作

宏可能函数

多核读写屏障

1.3 原子变量

mb

DEFINE_PER_CPU

可能没办法 volatile来定义counter了。难道不时要禁止编译优化了吗?答案都在的。这是可能linux3.0可能修改了原子变量相关的函数。

          * strex"排它性"的存储寄存器的值到内存中。类式于mipssc指令。

read_barrier_depends

在多核之间设置有另一一还还有一个 写屏障

3、可能找一台运行linux的多核x86机器运行。

要正确处理编译乱序,能都都可以 使用编译屏障指令barrier();

不管哪种架构,原子计数(中含 原子比较并交换)都在极耗CPU的。与单纯的加减计数指令相比,它消耗的CPU周期要高一到有另一一还还有一个 数量级。原因是那些呢?还是光信号(电信号)的传播时延难题。要让某个核上的修改被可是 核发现,时要信号在整个系统中进行传播。这在几条核的系统中,可能还都在大难题,可是在1024个核以上的系统中呢?比如让我们都都 让我们都都 熟知的天河系统。

__this_cpu_ptr

"       add  %0, %0, %4\n"

atomic_cmpxchg

Unsigned long g_a;

说明

         /**

摘要:本文主要讲述linux何如正确处理ARM cortex A9多核正确处理器的内核同步每段。主要包括其中的内存屏障、原子变量、每CPU变量。

Void foo_a(void *unused)

                   spin_lock(&fbc->lock);/* 获得自旋锁,没办法 能都都可以 正确处理多核一并更新全局计数。 */

          * 没办法 寄存器中值倘若0,可是非0.

          */

         if (count >= batch || count 本次修改的值较大,时要同步到全局计数中 */

          * 原子变量的值可能加载到寄存器中,这里对寄存器中的值减去指定的值。

1、             在多核上,有另一一还还有一个 CPU在向内存写入数据时,它并我倘若知道可是 核在向同样的内存地址写入。某有另一一还还有一个 核写入的数据可能会覆盖可是 核写入的数据。假说g_a当前值是0,没办法 系统系统进程A和系统系统进程B一并读取它的值,当内存中的值中放总线上后,有另一一还还有一个 系统系统进程都认为其值是0.并一并将其值加1后提交给总线并向内存中写入1.其中含 另一一还还有一个 系统系统进程对g_a的递增被丢失了。

多核读屏障

A = 1;

                   __this_cpu_write(*fbc->counters, count);/* 本次修改的计数较小,仅仅更新本CPU计数。 */

ü  对每CPU数组的并发访问不想原因高速缓存行的失效。正确处理在各个核之间引起缓存行的抖动。

在多核和IO内存、缓存之间设置有另一一还还有一个 完整性读写屏障

按照linux设计,mbrmbwmbread_barrier_depends主要用于CPU与外设IO之间。在arm及可是 可是 RISC系统中,通常将外设IO地址映射为一段内存地址。随便说说没办法 的内存是非缓存的,可是仍然受到内存读写乱序的影响。类式,让我们都都 让我们都都 要读写有另一一还还有一个 内部管理IO端口的数据时,可能会先向某个寄存器写入有另一一还还有一个 要读写的端口号,再读取没办法 端口得到其值。可能要读取值前一天,设置的端口号还没办法 到达外设,没办法 通常读取的数据是不可靠的,有时甚至会损坏硬件。可是 清况 下,时要在读寄存器前,设置有另一一还还有一个 内存屏障,保证二次操作内部管理端口之间没办法 乱序。

 * counter声明成volatile是为了正确处理编译器优化,强制从内存中读取counter的值

这是由linux原子操作函数的语义规定的:所有对原子变量的操作,可能时要向调用者返回结果,没办法 就时要增加多核内存屏障的语义。通俗的说,倘若可是 核看后本核对原子变量的操作结果时,本核在原子变量前的操作对可是 核也是可见的。

{

原子变量倘若为了正确处理让我们都都 让我们都都 遇到的难题:可能在共享内存的多核系统上正确的修改共享变量的计数值。

答案是不想。

/**

          */

原子变量是都在很棒?无论有几条个核,每个核都能都都可以 修改共享内存变量,可是没办法 的修改能都都可以 被可是 核立即看后。多核编程没办法 so easy

关于第3个原因,您能都都可以 参考有另一一还还有一个 内核补丁:

2、可能将系统系统进程中放armpowerpc可能mips上运行

原子的清除掩码。

Smp_wmb

3、             即使在x86体系社会形态中,允许直接对内存进行递增操作。也会可能编译器的原因,将内存中的值加载到内存,同第二点,也可能造成丢失一次递增。

"       strex         %1, %0, [%3]\n"

{

Smp_mb

          * __volatile__是为了正确处理编译器乱序。与"#define atomic_read(v)          (*(volatile int *)&(v)->counter)"中的volatile类式。

看后这里,你爱不爱我让我们都都 让我们都都 会随便说说,用每CPU变量来代替原子变量都在很好么?不过,趋于稳定的东西就必然在趋于稳定的理由,可能每CPU变量用于计数有另一一还还有一个 多致使的弊端:它是不精确的。让我们都都 让我们都都 设想:有32个核的系统,每个核更新此人 的CPU计数,可能有另一一还还有一个 多核想知道计数总和为什么么么在办?简单的用有另一一还还有一个 循环将计数加起来吗?这显然是不行的。可能某个核修改了此人 的计数变量时,可是 核没办法 立即看后它对可是 核的计数进行的修改。这会原因计数总和不准。有点痛 是某个核对计数进行了大的修改的前一天,总计数看起来会严重不准。

更聪明的读者会说,在写g_a时还时要锁住总线,使用汇编话语并在汇编前加lock前缀。

读写屏障

作用

聪明的读者会说了:是都在时要没办法 声明g_a

获得当前CPU在数组中的元素的指针。

atomic_clear_mask

         /**

获得当前CPU在数组中的元素的值。

多核读依赖屏障

 * 返回原子变量的值。

          */

多核写屏障

写屏障

         preempt_enable();/* 打开抢占 */

Atomic_set

static inline void atomic_add(int i, atomic_t *v)

         /**

Atomic_add

让我们都都 让我们都都 现在随便说说多核编程有没办法 可是 难了吧?有另一一还还有一个 简单的计数都能都都可以 搞得没办法 多样化。

typedef struct {

         {

理解了atomic_add,可是 原子变量的实现也就容易理解了。这里不再详述。

         /**

99dcc3e5a94ed491fbef402831d8c0bbb267f995。据提交补丁的兄弟讲,可是 补丁表层是有另一一还还有一个 性能优化的最好的办法 。可是,它实际上是有另一一还还有一个 BUG。该故障会引起内核内存分配子系统的有另一一还还有一个 BUG,最终会引起内存分配子系统陷入死循环。我实际的遇到了可是 故障,可怜了我的两位兄弟,为了正确处理可是 故障,花了近有另一一还还有一个 月时间,今天终于被我甩掉了。

获得每CPU数组中某个CPU对应的元素

}

在多核和IO内存、缓存之间设置有另一一还还有一个 读屏障

         While (stopped == 0)

          */

          * ldrexarm为了支持多核引入的新指令,表示"排它性"加载。与mipsll指令一样的效果。

         }

1       内核同步

在多核之间设置有另一一还还有一个 完整性读写屏障

除此以外,还有一组操作64位原子变量的变体,以及可是 位操作宏及函数。这里不再罗列。

每CPU数组中,确保每有另一一还还有一个 数组元素都趋于稳定不同的缓存行中。倘若您有另一一还还有一个 多int型的每CPU数组,没办法 每个int型后要占用有另一一还还有一个 缓存行(可是 系统中含 另一一还还有一个 缓存行是32个字节),这看起来有点痛 浪费。没办法 做的原因是: