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

  • 时间:
  • 浏览:2
  • 来源:彩神大发幸运飞艇_神彩大发幸运飞艇官方

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

复杂性的东西还在后面 。接下来让他们新开一帖,讨论内核同步的一些技术:自旋锁、信号量、RCU、无锁编程。

原子的递增计数的值。

相关宏和函数:

 * 返回原子变量的值。

*/

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

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

作用

CPU变量的主要目的是对多CPU并发访问的保护。而且 它只能处里同一核上的中断的影响。让他们那我讲过,在armmips等系统中,++--那我的简单计数操作,都需要有几个汇编语录来完成。以前在从内存中加载数据到寄存器后,还只能将数据保存到内存中前,有中断将操作过程打断,并在中断处里函数中对同样的计数值进行操作,只能中断中的操作将被覆盖。

2、以前将线程插进armpowerpc以前mips上运行

读屏障

         preempt_disable();

 */

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

编译器暂且保证生成的汇编是按照C语录的顺序。为了带宽以前一些原因分析 ,它生成的汇编语录以前与下面的C代码是一致的:

/**

1、             在多核上,一兩个 CPU在向内存写入数据时,它并告诉我一些核在向同样的内存地址写入。某一兩个 核写入的数据以都会覆盖一些核写入的数据。假说g_a当前值是0,只能线程A和线程B同時 读取它的值,当内存中的值插进总线上后,一兩个 线程都认为其值是0.并同時 将其值加1后提交给总线并向内存中写入1.其中一兩个 线程对g_a的递增被丢失了。

         }

Atomic_read

A = 1;

按照linux设计,mbrmbwmbread_barrier_depends主要用于CPU与外设IO之间。在arm及一些一些RISC系统中,通常将外设IO地址映射为一段内存地址。着实那我的内存是非缓存的,而且 仍然受到内存读写乱序的影响。类似于,让他们要读写一兩个 内部IO端口的数据时,以都会先向某个寄存器写入一兩个 要读写的端口号,再读取那我端口得到其值。以需要读取值以前,设置的端口号还只能到达外设,只能通常读取的数据是不可靠的,有时甚至会损坏硬件。这名具体情况下,需要在读寄存器前,设置一兩个 内存屏障,保证二次操作内部端口之间只能乱序。

 */

atomic_clear_mask

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

         /**

{

per_cpu

写屏障

ü  对每CPU数组的并发访问不需要原因分析 高速缓存行的失效。处里在各个核之间引起缓存行的抖动。

         /**

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

看得人这里,你说让他们会着实,用每CPU变量来代替原子变量都有很好么?不过,位于的东西就必然在位于的理由,以前每CPU变量用于计数一兩个 多致使的弊端:它是不精确的。让他们设想:有32个核的系统,每个核更新一些人的CPU计数,以前一兩个 多核想知道计数总和何如办?简单的用一兩个 循环将计数加起来吗?这显然是不行的。以前某个核修改了一些人的计数变量时,一些核只能立即看得人它对这名核的计数进行的修改。这会原因分析 计数总和不准。很糙是某个核对计数进行了大的修改的以前,总计数看起来会严重不准。

多核写屏障

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

 * 原子的递增计数的值。

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

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

原因分析 是那此呢?

{

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

产生这名问题的根本原因分析 是:

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

         /**

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

读依赖屏障

名称

/**

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

         {

Atomic_sub

}

让他们现在着实多核编程有只能一些难了吧?一兩个 简单的计数都可需要搞得只能复杂性。

          * 只能寄存器中值本来我0,而且 非0.

设置原子变量的值。

Void foo_b(void *unused)

多核读写屏障

聪明的读者会说了:是都有需要那我声明g_a

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

         int result;

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

         } else {

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

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

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

g_a的值都会等于count_a + count_b吗?

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

多核读屏障

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

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

linux3.0中,以前有所变化:

get_cpu_ptr

                   spin_unlock(&fbc->lock);

为了使总和大致可信,内核又引入了另并否是生活每CPU变量:percpu_counter

99dcc3e5a94ed491fbef402831d8c0bbb267f995。据提交补丁的兄弟讲,这名补丁表皮是一兩个 性能优化的法律法律依据。而且 ,它实际上是一兩个 BUG。该故障会引起内核内存分配子系统的一兩个 BUG,最终会引起内存分配子系统陷入死循环。我实际的遇到了这名故障,可怜了我的两位兄弟,为了处里这名故障,花了近一兩个 月时间,今天终于被我拿出了。

原子变量本来我为了处里让他们遇到的问题:以前在共享内存的多核系统上正确的修改共享变量的计数值。

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

读写屏障

多核读依赖屏障

1.4 每CPU变量

1.2 都有题外话的题外话

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

宏以前函数

}

          */

          */

         /**

在多核之间设置一兩个 完整读写屏障

原子的清除掩码。

__get_cpu_var

这是由linux原子操作函数的语义规定的:所有对原子变量的操作,以前需要向调用者返回结果,只能就需要增加多核内存屏障的语义。通俗的说,本来我一些核看得人本核对原子变量的操作结果时,本核在原子变量前的操作对一些核也是可见的。

          */

而且 ,请您:

 * 设置原子变量的值。

          */

Unsigned long volatile g_a;

Smp_read_barrier_depends

关于第兩个原因分析 ,您可需要参考一兩个 内核补丁:

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

* 这里需要关抢占。

恩,当您在一台真实的计算上测试这名线程的以前,你说您的直觉是对的,g_a的值着实等于count_a + count_b

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

         /**

{

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

} atomic_t;

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

atomic_cmpxchg

 * 这里强制将counter转换为volatile int并取其值。目的本来我为了处里编译优化。

*            fbc:            要增加的每CPU变量

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

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

         {

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

函数名

原子的递减计数的值。

         /**

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

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

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

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

     */

         /**

         s64 count;

2、             Armpowerpcmips那此体系价值形式都有存储/加载体系价值形式,它们只能直接对内存中的值进行操作。而需要将内存中的值加载到寄存器中后,将寄存器中的值加1后,再存储到内存中。以前一兩个 线程都读取0值到寄存器中,并将寄存器的值递增为1后存储到内存,只能也会丢失一次递增。

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

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

Atomic_add

read_barrier_depends

                   spin_lock(&fbc->lock);/* 获得自旋锁,那我可需要处里多核同時 更新全局计数。 */

G_a++;

          */

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

rmb

何如处里这名问题呢?

3、以前找一台运行linux的多核x86机器运行。

B = 2;

* 为了处里当前任务飘移到一些核上,以前被一些核抢占,原因分析 计数丢失

          */

read_barrier_dependssmp_ read_barrier_depends是读依赖屏障。除了在DEC alpha架构外,linux支持的一些均需要这名屏障。Alpha需要它,是以前alpha架构中,使用的缓存是split cache.所谓split cache,简单的说本来我一兩个 核的缓存不止一兩个 .在arm架构下,让他们可需要简单的忽略这名屏障。

*/

}

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

}

1.1 内存屏障

以前只能volatile来定义counter了。难道需要禁止编译优化什么时间?答案都有的。这是以前linux3.0以前修改了原子变量相关的函数。

锁总线是正确的,而且 也需要将g_a声明为valatile类型的变量。那我,在让他们分析的ARM多核上,应该何如办?

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

         While (stopped == 0)

G_a++;

Linux中的基本原子操作

"       bne  1b"

原子变量是都有很棒?无论有有几个个核,每个核都可需要修改共享内存变量,而且 那我的修改可需要被一些核立即看得人。多核编程那我so easy

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

}

 */

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

ü  这也是为了处里冒出多核之间数据覆盖的具体情况。对这名些,以前您暂时只能理解。你说您在内核领域实际工作几年,也会着实这很糙难于理解。不过,现在您只需要知道有只能一兩个 事实位于就行了。

在描述原子变量和每CPU变量、一些内核同步法律法律依据以前,让他们先看一段代码。假设有一兩个 线程A和线程B,它们的执行代码分别是foo_afoo_b,它们都操作一兩个 全局变量g_a,如下:

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

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

         While (stopped == 0)

/**

原子比较并交换计数值。

         __asm__ __volatile__("@ atomic_add\n"

要处里编译乱序,可需要使用编译屏障指令barrier();

          * 以前一些核与本核冲突,只能寄存器值为非0,这里跳转到标号1处,重新加载内存的值并递增其值。

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

内存屏障也隐含了编译屏障的作用。所谓编译屏障,是为了处里编译乱序的问题。这名问题的根源在于:在科学发名编译器的以前,多核还未冒出。编译器开发者认为编译出来的二进制代码本来我在单核上运行正确就可需要了。甚至,本来我保证单线程内的线程逻辑正确性即可。类似于,让他们有两句赋值语录:

Smp_rmb

wmb

1、将测试线程运行的时间运行得久一些

}

                   __this_cpu_write(*fbc->counters, 0);

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

返回原子变量的值

着实linux分读写屏障、读屏障、写屏障,而且 在ARM中,它们的实现都有一样的,只能严格区别不同的屏障。

Smp_wmb

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

         /**

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

宏以前函数

{

put_cpu_var

DEFINE_PER_CPU

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

typedef struct { volatile int counter; } atomic_t;

mb

/**

__this_cpu_ptr

typedef struct {

Smp_mb

Atomic_set

A = 1;

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

"       teq   %1, #0\n"

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

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

说明

为了处里这名问题,内核引用入了每CPU变量。

         : "cc");

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

答案是不需要。

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

Int stoped = 0;

不管在多CPU还是单CPU中,内核抢占都以前象中断那样破坏让他们对计数的操作。而且 ,应当在禁用抢占的具体情况下访问每CPU变量。内核抢占是一兩个 大语录题,让他们在讲调度的以前再提这名事情。

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

* 增加每CPU变量计数

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

Unsigned long g_a;

/**

 */

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

          * 关键代码是这里的判断。以前在ldrexstrex之间,一些核只能对原子变量变量进行加载存储操作,

1       内核同步

开抢占,与get_cpu_ptr配对使用。

1.3 原子变量

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

         unsigned long tmp;

说明

         int counter;

Void foo_a(void *unused)

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

每CPU数组中,确保每一兩个 数组元素都位于不同的缓存行中。本来我您一兩个 多int型的每CPU数组,只能每个int型都会占用一兩个 缓存行(什么都系统中一兩个 缓存行是32个字节),这看起来很糙浪费。那我做的原因分析 是:

B = 2;