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

  • 时间:
  • 浏览:1

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

说明

编译器没有来越多保证生成的汇编是按照C一句话的顺序。为了效率导致 有些导致 ,它生成的汇编一句话导致 与下面的C代码是一致的:

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

         : "cc");

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

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

99dcc3e5a94ed491fbef402831d8c0bbb267f995。据提交补丁的兄弟讲,这种 补丁皮层 是有一一一三个性能优化的法律办法。为什么么让,它实际上是有一一一三个BUG。该故障会引起内核内存分配子系统的有一一一三个BUG,最终会引起内存分配子系统陷入死循环。我实际的遇到了这种 故障,可怜了我的两位兄弟,为了补救这种 故障,花了近有一一一三个月时间,今天终于被我读懂了。

Linux中的基本原子操作

/**

Paul从前讲过:在建造大桥要我,都要得明白力学的原理。要理解内存屏障,首先得明白计算机硬件体系价值形式,一阵一阵是硬件是何如管理缓存的。缓发生多核上的一致性间题是何如产生的。

                   spin_lock(&fbc->lock);/* 获得自旋锁,从前都要补救多核同时更新全局计数。 */

1.2 全部都是题外话的题外话

}

多核读写屏障

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

typedef struct { volatile int counter; } atomic_t;

真是linux分读写屏障、读屏障、写屏障,为什么么让在ARM中,它们的实现全部都是一样的,没有 严格区别不同的屏障。

读依赖屏障

         /**

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

         /**

想看 这里,我知道你大伙会真是,用每CPU变量来代替原子变量全部都是很好么?不过,发生的东西就必然在发生的理由,导致 每CPU变量用于计数有有一一一三个致使的弊端:它是不精确的。大伙设想:有32个核的系统,每个核更新买车人的CPU计数,导致 有有一一一三个核想知道计数总和怎莫办 办?简单的用有一一一三个循环将计数加起来吗?这显然是不行的。导致 某个核修改了买车人的计数变量时,有些核也能立即想看 它对这种 核的计数进行的修改。这会导致 计数总和不准。一阵一阵是某个核对计数进行了大的修改的要我,总计数看起来会严重不准。

                   __this_cpu_write(*fbc->counters, 0);

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

}

         /**

         int result;

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

         int counter;

原子的递增计数的值。

read_barrier_dependssmp_ read_barrier_depends是读依赖屏障。除了在DEC alpha架构外,linux支持的有些均不都要这种 屏障。Alpha都要它,是导致 alpha架构中,使用的缓存是split cache.所谓split cache,简单的说有些有些 有一一一三个核的缓存不止有一一一三个.在arm架构下,大伙都要简单的忽略这种 屏障。

linux3.0中,导致 有所变化:

          */

          * __volatile__是为了补救编译器乱序。与"#define atomic_read(v)          (*(volatile int *)&(v)->counter)"中的volatile同类。

         unsigned long tmp;

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

名称

1       内核同步

G_a++;

}

开抢占,与get_cpu_ptr配对使用。

Atomic_add

在多核和IO内存、缓存之间设置有一一一三个全部读写屏障

都要将它理解为数据价值形式的数组。系统的每个CPU对应数组中的有一一一三个元素。每个CPU都只访问本CPU对应的数组元素。

 * 这里强制将counter转换为volatile int并取其值。目的有些有些 为了补救编译优化。

         {

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

要深入理解内存屏障,建议大伙首先阅读以下资料:

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

2、             Armpowerpcmips那些体系价值形式全部都是存储/加载体系价值形式,它们也能直接对内存中的值进行操作。而都要将内存中的值加载到寄存器中后,将寄存器中的值加1后,再存储到内存中。导致 有一一一三个系统任务管理器都读取0值到寄存器中,并将寄存器的值递增为1后存储到内存,没有 也会丢失一次递增。

在描述原子变量和每CPU变量、有些内核同步法律办法要我,大伙先看一段代码。假设有有一一一三个系统任务管理器A和系统任务管理器B,它们的执行代码分别是foo_afoo_b,它们都操作有一一一三个全局变量g_a,如下:

__this_cpu_ptr

导致 是那些呢?

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

/**

/**

Smp_read_barrier_depends

 * 设置原子变量的值。

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

          * 导致 有些核与本核冲突,没有 寄存器值为非0,这里跳转到标号1处,重新加载内存的值并递增其值。

僵化 的东西还在顶端。接下来大伙新开一帖,讨论内核同步的有些技术:自旋锁、信号量、RCU、无锁编程。

} atomic_t;

*            amount:   本偏离 增加的计数值

 */

不管在多CPU还是单CPU中,内核抢占都导致 象中断那样破坏大伙对计数的操作。为什么么让,应当在禁用抢占的情况下访问每CPU变量。内核抢占是有一一一三个大一句话题,大伙在讲调度的要我再提这种 事情。

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

答案是没有多。

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

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

ü  对每CPU数组的并发访问没有多导致 高速缓存行的失效。补救在各个核之间引起缓存行的抖动。

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

{

G_a++;

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

设置原子变量的值。

CPU变量的主要目的是对多CPU并发访问的保护。为什么么让它也能补救同一核上的中断的影响。大伙从前讲过,在armmips等系统中,++--从前的简单计数操作,都都要有几个汇编一句话来完成。导致 在从内存中加载数据到寄存器后,还没有 将数据保存到内存中前,有中断将操作过程打断,并在中断补救函数中对同样的计数值进行操作,没有 中断中的操作将被覆盖。

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

不管哪种架构,原子计数(涵盖原子比较并交换)全部都是极耗CPU的。与单纯的加减计数指令相比,它消耗的CPU周期要高一到有一一一三个数量级。导致 是那些呢?还是光信号(电信号)的传播效率间题。要让某个核上的修改被有些核发现,都要信号在整个系统中进行传播。这在有几个核的系统中,导致 还全部都是大间题,为什么么让在1024个核以上的系统中呢?比如大伙熟知的天河系统。

函数名

读屏障

聪明的读者会说了:是全部都是都要从前声明g_a

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

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

         /**

ü  这也是为了补救出先多核之间数据覆盖的情况。对这种 点,导致 您暂时也能理解。我知道你您在内核领域实际工作几年,也会真是这种 阵一阵难于理解。不过,现在您只都要知道有没有 有一一一三个事实发生就行了。

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

锁总线是正确的,为什么么让也都要将g_a声明为valatile类型的变量。从前,在大伙分析的ARM多核上,应该怎莫办 办?

Smp_wmb

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

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

原子变量有些有些 为了补救大伙遇到的间题:导致 在共享内存的多核系统上正确的修改共享变量的计数值。

原子变量是全部都是很棒?无论有有几个个核,每个核都要修改共享内存变量,为什么么让从前的修改都要被有些核立即想看 。多核编程从前so easy

per_cpu

B = 2;

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

多核读依赖屏障

相关宏和函数:

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

typedef struct {

*            fbc:            要增加的每CPU变量

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

说明

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

                   spin_unlock(&fbc->lock);

1.1 内存屏障

Atomic_sub

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

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

 */

写屏障

{

为什么么让,请您:

恩,当您在一台真实的计算上测试这种 系统任务管理器的要我,我知道你您的直觉是对的,g_a的值真是等于count_a + count_b

每CPU数组中,确保每有一一一三个数组元素都发生不同的缓存行中。只要您有有一一一三个int型的每CPU数组,没有 每个int型随后 占用有一一一三个缓存行(有些有些系统涵盖一一一三个缓存行是32个字节),这看起来一阵一阵浪费。从前做的导致 是:

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

* 为了补救当前任务飘移到有些核上,导致 被有些核抢占,导致 计数丢失

读写屏障

         }

Unsigned long g_a;

原子的清除掩码。

2、导致 将系统任务管理器放满去armpowerpc导致 mips上运行

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

         {

要补救编译乱序,都要使用编译屏障指令barrier();

"       teq   %1, #0\n"

宏导致 函数

*/

A = 1;

     * 获得本CPU计数值并打上去计数值。

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

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

* 增加每CPU变量计数

* 这里都要关抢占。

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

1、             在多核上,有一一一三个CPU在向内存写入数据时,它并我没有了乎 有些核在向同样的内存地址写入。某有一一一三个核写入的数据导致 会覆盖有些核写入的数据。假说g_a当前值是0,没有 系统任务管理器A和系统任务管理器B同时读取它的值,当内存中的值放满去总线上后,有一一一三个系统任务管理器都认为其值是0.并同时将其值加1后提交给总线并向内存中写入1.其涵盖一一一三个系统任务管理器对g_a的递增被丢失了。

宏导致 函数

read_barrier_depends

/**

         preempt_disable();

         /**

Unsigned long volatile g_a;

         /**

多核写屏障

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

3、导致 找一台运行linux的多核x86机器运行。

__get_cpu_var

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

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

大伙现在真是多核编程有没有 有些难了吧?有一一一三个简单的计数都要搞得没有 僵化 。

          * 没有 寄存器中值有些有些 0,为什么么让非0.

Void foo_b(void *unused)

put_cpu_var

在多核之间设置有一一一三个全部读写屏障

 */

          */

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

怎莫办 补救这种 间题呢?

         While (stopped == 0)

{

         While (stopped == 0)

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

多核读屏障

B = 2;

mb

atomic_cmpxchg

 * 原子的递增计数的值。

}

1.4 每CPU变量

按照linux设计,mbrmbwmbread_barrier_depends主要用于CPU与外设IO之间。在arm及有些有些RISC系统中,通常将外设IO地址映射为一段内存地址。真是从前的内存是非缓存的,为什么么让仍然受到内存读写乱序的影响。同类,大伙要读写有一一一三个内外部IO端口的数据时,导致 会先向某个寄存器写入有一一一三个要读写的端口号,再读取从前端口得到其值。导致 要读取值要我,设置的端口号还没有 到达外设,没有 通常读取的数据是不可靠的,有时甚至会损坏硬件。这种 情况下,都要在读寄存器前,设置有一一一三个内存屏障,保证二次操作内外部端口之间没有 乱序。

         s64 count;

为了补救这种 间题,内核引用入了每CPU变量。

不过还是也能太高兴了,原子变量真是全部都是毒瘤,为什么么让也差没有来越多了。我从前遇到有一一一三个兄弟,工作十多年了吧,得意的吹嘘:“我写的代码精细得很,统计计数全部都是用的汇编实现的,汇编加法指令还用了lock前缀。”呜呼,这种 兄弟全部没有 意识到在x86体系价值形式中,这种 lock前缀对性能的影响。

1、将测试系统任务管理器运行的时间运行得久有些

         } else {

作用

Int stoped = 0;

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

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

         /**

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

          */

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

内存屏障也隐含了编译屏障的作用。所谓编译屏障,是为了补救编译乱序的间题。这种 间题的根源在于:在发明编译器的要我,多核还未出先。编译器开发者认为编译出来的二进制代码只要在单核上运行正确就都要了。甚至,只要保证单系统任务管理器内的系统任务管理器逻辑正确性即可。同类,大伙有两句赋值一句话:

}

A = 1;

 * 返回原子变量的值。

导致 没有 volatile来定义counter了。难道不都要禁止编译优化哪天?答案全部都是的。这是导致 linux3.0导致 修改了原子变量相关的函数。

wmb

rmb

Smp_rmb

         /**

/**

percpu_counter的全部实现在percpu_counter.c中。有兴趣的同学都要研究一下。下面大伙讲有一一一三个主要的函数,希望起个抛砖引玉的作用:

Atomic_read

Smp_mb

}

*/

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

*            batch:       当本CPU计数超过此值时,要确保有些核能及时想看 。                                     

Void foo_a(void *unused)

首先,大伙看一下老版本是何如定义原子变量的:

原子的递减计数的值。

DEFINE_PER_CPU

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

          */

原子比较并交换计数值。

Atomic_set

{

"       bne  1b"

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

 */

返回原子变量的值

1.3 原子变量

          */

关于第一三个导致 ,您都要参考有一一一三个内核补丁:

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

atomic_clear_mask

产生这种 间题的根本导致 是:

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

     */

          */

         __asm__ __volatile__("@ atomic_add\n"

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

get_cpu_ptr