Linux 内核里的数据结构——位数组

Summary: 除了不同的基于链式和树的数据结构以外，Linux 内核也为位数组（或称为位图bitmap）提供了 API。

### Linux 内核中的位数组和位操作

![](/data/attachment/album/201608/23/110922nrtd4oex2b2eedt8.jpg)

除了不同的基于[链式](https://en.wikipedia.org/wiki/Linked_data_structure)和[树](https://en.wikipedia.org/wiki/Tree_%28data_structure%29)的数据结构以外，Linux 内核也为[位数组](https://en.wikipedia.org/wiki/Bit_array)（或称为<ruby> 位图 <rp>  （ </rp> <rt>  bitmap </rt> <rp>  ） </rp></ruby>）提供了 [API](https://en.wikipedia.org/wiki/Application_programming_interface)。位数组在 Linux 内核里被广泛使用，并且在以下的源代码文件中包含了与这样的结构搭配使用的通用 `API`：

* [lib/bitmap.c](https://github.com/torvalds/linux/blob/master/lib/bitmap.c)
* [include/linux/bitmap.h](https://github.com/torvalds/linux/blob/master/include/linux/bitmap.h)

除了这两个文件之外，还有体系结构特定的头文件，它们为特定的体系结构提供优化的位操作。我们将探讨 [x86\_64](https://en.wikipedia.org/wiki/X86-64) 体系结构，因此在我们的例子里，它会是

* [arch/x86/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h)

头文件。正如我上面所写的，`位图`在 Linux 内核中被广泛地使用。例如，`位数组`常常用于保存一组在线/离线处理器，以便系统支持[热插拔](https://www.kernel.org/doc/Documentation/cpu-hotplug.txt)的 CPU（你可以在 [cpumasks](https://0xax.gitbooks.io/linux-insides/content/Concepts/cpumask.html) 部分阅读更多相关知识 ），一个<ruby> 位数组 <rp>  （ </rp> <rt>  bit array </rt> <rp>  ） </rp></ruby>可以在 Linux 内核初始化等期间保存一组已分配的[中断处理](https://en.wikipedia.org/wiki/Interrupt_request_%28PC_architecture%29)。

因此，本部分的主要目的是了解<ruby> 位数组 <rp>  （ </rp> <rt>  bit array </rt> <rp>  ） </rp></ruby>是如何在 Linux 内核中实现的。让我们现在开始吧。

### 位数组声明

在我们开始查看`位图`操作的 `API` 之前，我们必须知道如何在 Linux 内核中声明它。有两种声明位数组的通用方法。第一种简单的声明一个位数组的方法是，定义一个 `unsigned long` 的数组，例如：

```
unsigned long my_bitmap[8]

```

第二种方法，是使用 `DECLARE_BITMAP` 宏，它定义于 [include/linux/types.h](https://github.com/torvalds/linux/blob/master/include/linux/types.h) 头文件：

```
#define DECLARE_BITMAP(name,bits) \
    unsigned long name[BITS_TO_LONGS(bits)]

```

我们可以看到 `DECLARE_BITMAP` 宏使用两个参数：

* `name` - 位图名称;
* `bits` - 位图中位数;

并且只是使用 `BITS_TO_LONGS(bits)` 元素展开 `unsigned long` 数组的定义。 `BITS_TO_LONGS` 宏将一个给定的位数转换为 `long` 的个数，换言之，就是计算 `bits` 中有多少个 `8` 字节元素：

```
#define BITS_PER_BYTE           8
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
#define BITS_TO_LONGS(nr)       DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))

```

因此，例如 `DECLARE_BITMAP(my_bitmap, 64)` 将产生：

```
>>> (((64) + (64) - 1) / (64))
1

```

与：

```
unsigned long my_bitmap[1];

```

在能够声明一个位数组之后，我们便可以使用它了。

### 体系结构特定的位操作

我们已经看了上面提及的一对源文件和头文件，它们提供了位数组操作的 [API](https://en.wikipedia.org/wiki/Application_programming_interface)。其中重要且广泛使用的位数组 API 是体系结构特定的且位于已提及的头文件中 [arch/x86/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h)。

首先让我们查看两个最重要的函数：

* `set_bit`;
* `clear_bit`.

我认为没有必要解释这些函数的作用。从它们的名字来看，这已经很清楚了。让我们直接查看它们的实现。如果你浏览 [arch/x86/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h) 头文件，你将会注意到这些函数中的每一个都有[原子性](https://en.wikipedia.org/wiki/Linearizability)和非原子性两种变体。在我们开始深入这些函数的实现之前，首先，我们必须了解一些有关<ruby> 原子 <rp>  （ </rp> <rt>  atomic </rt> <rp>  ） </rp></ruby>操作的知识。

简而言之，原子操作保证两个或以上的操作不会并发地执行同一数据。`x86` 体系结构提供了一系列原子指令，例如， [xchg](http://x86.renejeschke.de/html/file_module_x86_id_328.html)、[cmpxchg](http://x86.renejeschke.de/html/file_module_x86_id_41.html) 等指令。除了原子指令，一些非原子指令可以在 [lock](http://x86.renejeschke.de/html/file_module_x86_id_159.html) 指令的帮助下具有原子性。现在你已经对原子操作有了足够的了解，我们可以接着探讨 `set_bit` 和 `clear_bit` 函数的实现。

我们先考虑函数的<ruby> 非原子性 <rp>  （ </rp> <rt>  non-atomic </rt> <rp>  ） </rp></ruby>变体。非原子性的 `set_bit` 和 `clear_bit` 的名字以双下划线开始。正如我们所知道的，所有这些函数都定义于 [arch/x86/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h) 头文件，并且第一个函数就是 `__set_bit`:

```
static inline void __set_bit(long nr, volatile unsigned long *addr)
{
    asm volatile("bts %1,%0" : ADDR : "Ir" (nr) : "memory");
}

```

正如我们所看到的，它使用了两个参数：

* `nr` - 位数组中的位号（LCTT 译注：从 0 开始）
* `addr` - 我们需要置位的位数组地址

注意，`addr` 参数使用 `volatile` 关键字定义，以告诉编译器给定地址指向的变量可能会被修改。 `__set_bit` 的实现相当简单。正如我们所看到的，它仅包含一行[内联汇编代码](https://en.wikipedia.org/wiki/Inline_assembler)。在我们的例子中，我们使用 [bts](http://x86.renejeschke.de/html/file_module_x86_id_25.html) 指令，从位数组中选出一个第一操作数（我们的例子中的 `nr`）所指定的位，存储选出的位的值到 [CF](https://en.wikipedia.org/wiki/FLAGS_register) 标志寄存器并设置该位（LCTT 译注：即 `nr` 指定的位置为 1）。

注意，我们了解了 `nr` 的用法，但这里还有一个参数 `addr` 呢！你或许已经猜到秘密就在 `ADDR`。 `ADDR` 是一个定义在同一个头文件中的宏，它展开为一个包含给定地址和 `+m` 约束的字符串：

```
#define ADDR                BITOP_ADDR(addr)
#define BITOP_ADDR(x) "+m" (*(volatile long *) (x))

```

除了 `+m` 之外，在 `__set_bit` 函数中我们可以看到其他约束。让我们查看并试着理解它们所表示的意义：

* `+m` - 表示内存操作数，这里的 `+` 表明给定的操作数为输入输出操作数；
* `I` - 表示整型常量；
* `r` - 表示寄存器操作数

除了这些约束之外，我们也能看到 `memory` 关键字，其告诉编译器这段代码会修改内存中的变量。到此为止，现在我们看看相同的<ruby> 原子性 <rp>  （ </rp> <rt>  atomic </rt> <rp>  ） </rp></ruby>变体函数。它看起来比<ruby> 非原子性 <rp>  （ </rp> <rt>  non-atomic </rt> <rp>  ） </rp></ruby>变体更加复杂：

```
static __always_inline void
set_bit(long nr, volatile unsigned long *addr)
{
    if (IS_IMMEDIATE(nr)) {
        asm volatile(LOCK_PREFIX "orb %1,%0"
            : CONST_MASK_ADDR(nr, addr)
            : "iq" ((u8)CONST_MASK(nr))
            : "memory");
    } else {
        asm volatile(LOCK_PREFIX "bts %1,%0"
            : BITOP_ADDR(addr) : "Ir" (nr) : "memory");
    }
}

```

（LCTT 译注：BITOP\_ADDR 的定义为：`#define BITOP_ADDR(x) "=m" (*(volatile long *) (x))`，ORB 为字节按位或。）

首先注意，这个函数使用了与 `__set_bit` 相同的参数集合，但额外地使用了 `__always_inline` 属性标记。 `__always_inline` 是一个定义于 [include/linux/compiler-gcc.h](https://github.com/torvalds/linux/blob/master/include/linux/compiler-gcc.h) 的宏，并且只是展开为 `always_inline` 属性：

```
#define __always_inline inline __attribute__((always_inline))

```

其意味着这个函数总是内联的，以减少 Linux 内核映像的大小。现在让我们试着了解下 `set_bit` 函数的实现。首先我们在 `set_bit` 函数的开头检查给定的位的数量。`IS_IMMEDIATE` 宏定义于相同的[头文件](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h)，并展开为 [gcc](https://en.wikipedia.org/wiki/GNU_Compiler_Collection) 内置函数的调用：

```
#define IS_IMMEDIATE(nr)        (__builtin_constant_p(nr))

```

如果给定的参数是编译期已知的常量，`__builtin_constant_p` 内置函数则返回 `1`，其他情况返回 `0`。假若给定的位数是编译期已知的常量，我们便无须使用效率低下的 `bts` 指令去设置位。我们可以只需在给定地址指向的字节上执行 [按位或](https://en.wikipedia.org/wiki/Bitwise_operation#OR) 操作，其字节包含给定的位，掩码位数表示高位为 `1`，其他位为 0 的掩码。在其他情况下，如果给定的位号不是编译期已知常量，我们便做和 `__set_bit` 函数一样的事。`CONST_MASK_ADDR` 宏：

```
#define CONST_MASK_ADDR(nr, addr)   BITOP_ADDR((void *)(addr) + ((nr)>>3))

```

展开为带有到包含给定位的字节偏移的给定地址，例如，我们拥有地址 `0x1000` 和位号 `0x9`。因为 `0x9` 代表 `一个字节 + 一位`，所以我们的地址是 `addr + 1`:

```
>>> hex(0x1000 + (0x9 >> 3))
'0x1001'

```

`CONST_MASK` 宏将我们给定的位号表示为字节，位号对应位为高位 `1`，其他位为 `0`：

```
#define CONST_MASK(nr)          (1 << ((nr) & 7))

```

```
>>> bin(1 << (0x9 & 7))
'0b10'

```

最后，我们应用 `按位或` 运算到这些变量上面，因此，假如我们的地址是 `0x4097` ，并且我们需要置位号为 `9` 的位为 1：

```
>>> bin(0x4097)
'0b100000010010111'
>>> bin((0x4097 >> 0x9) | (1 << (0x9 & 7)))
'0b100010'

```

`第 9 位` 将会被置位。（LCTT 译注：这里的 9 是从 0 开始计数的，比如0010，按照作者的意思，其中的 1 是第 1 位）

注意，所有这些操作使用 `LOCK_PREFIX` 标记，其展开为 [lock](http://x86.renejeschke.de/html/file_module_x86_id_159.html) 指令，保证该操作的原子性。

正如我们所知，除了 `set_bit` 和 `__set_bit` 操作之外，Linux 内核还提供了两个功能相反的函数，在原子性和非原子性的上下文中清位。它们是 `clear_bit` 和 `__clear_bit`。这两个函数都定义于同一个[头文件](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h) 并且使用相同的参数集合。不仅参数相似，一般而言，这些函数与 `set_bit` 和 `__set_bit` 也非常相似。让我们查看非原子性 `__clear_bit` 的实现吧：

```
static inline void __clear_bit(long nr, volatile unsigned long *addr)
{
    asm volatile("btr %1,%0" : ADDR : "Ir" (nr));
}

```

没错，正如我们所见，`__clear_bit` 使用相同的参数集合，并包含极其相似的内联汇编代码块。它只是使用 [btr](http://x86.renejeschke.de/html/file_module_x86_id_24.html) 指令替换了 `bts`。正如我们从函数名所理解的一样，通过给定地址，它清除了给定的位。`btr` 指令表现得像 `bts`（LCTT 译注：原文这里为 btr，可能为笔误，修正为 bts）。该指令选出第一操作数所指定的位，存储它的值到 `CF` 标志寄存器，并且清除第二操作数指定的位数组中的对应位。

`__clear_bit` 的原子性变体为 `clear_bit`：

```
static __always_inline void
clear_bit(long nr, volatile unsigned long *addr)
{
    if (IS_IMMEDIATE(nr)) {
        asm volatile(LOCK_PREFIX "andb %1,%0"
            : CONST_MASK_ADDR(nr, addr)
            : "iq" ((u8)~CONST_MASK(nr)));
    } else {
        asm volatile(LOCK_PREFIX "btr %1,%0"
            : BITOP_ADDR(addr)
            : "Ir" (nr));
    }
}

```

并且正如我们所看到的，它与 `set_bit` 非常相似，只有两处不同。第一处差异为 `clear_bit` 使用 `btr` 指令来清位，而 `set_bit` 使用 `bts` 指令来置位。第二处差异为 `clear_bit` 使用否定的位掩码和 `按位与` 在给定的字节上置位，而 `set_bit` 使用 `按位或` 指令。

到此为止，我们可以在任意位数组置位和清位了，我们将看看位掩码上的其他操作。

在 Linux 内核中对位数组最广泛使用的操作是设置和清除位，但是除了这两个操作外，位数组上其他操作也是非常有用的。Linux 内核里另一种广泛使用的操作是知晓位数组中一个给定的位是否被置位。我们能够通过 `test_bit` 宏的帮助实现这一功能。这个宏定义于 [arch/x86/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h) 头文件，并根据位号分别展开为 `constant_test_bit` 或 `variable_test_bit` 调用。

```
#define test_bit(nr, addr)          \
    (__builtin_constant_p((nr))                 \
     ? constant_test_bit((nr), (addr))          \
     : variable_test_bit((nr), (addr)))

```

因此，如果 `nr` 是编译期已知常量，`test_bit` 将展开为 `constant_test_bit` 函数的调用，而其他情况则为 `variable_test_bit`。现在让我们看看这些函数的实现，让我们从 `variable_test_bit` 开始看起：

```
static inline int variable_test_bit(long nr, volatile const unsigned long *addr)
{
    int oldbit;

    asm volatile("bt %2,%1\n\t"
             "sbb %0,%0"
             : "=r" (oldbit)
             : "m" (*(unsigned long *)addr), "Ir" (nr));

    return oldbit;
}

```

`variable_test_bit` 函数使用了与 `set_bit` 及其他函数使用的相似的参数集合。我们也可以看到执行 [bt](http://x86.renejeschke.de/html/file_module_x86_id_22.html) 和 [sbb](http://x86.renejeschke.de/html/file_module_x86_id_286.html) 指令的内联汇编代码。`bt` （或称 `bit test`）指令从第二操作数指定的位数组选出第一操作数指定的一个指定位，并且将该位的值存进标志寄存器的 [CF](https://en.wikipedia.org/wiki/FLAGS_register) 位。第二个指令 `sbb` 从第二操作数中减去第一操作数，再减去 `CF` 的值。因此，这里将一个从给定位数组中的给定位号的值写进标志寄存器的 `CF` 位，并且执行 `sbb` 指令计算： `00000000 - CF`，并将结果写进 `oldbit` 变量。

`constant_test_bit` 函数做了和我们在 `set_bit` 所看到的一样的事：

```
static __always_inline int constant_test_bit(long nr, const volatile unsigned long *addr)
{
    return ((1UL << (nr & (BITS_PER_LONG-1))) &
        (addr[nr >> _BITOPS_LONG_SHIFT])) != 0;
}

```

它生成了一个位号对应位为高位 `1`，而其他位为 `0` 的字节（正如我们在 `CONST_MASK` 所看到的），并将 [按位与](https://en.wikipedia.org/wiki/Bitwise_operation#AND) 应用于包含给定位号的字节。

下一个被广泛使用的位数组相关操作是改变一个位数组中的位。为此，Linux 内核提供了两个辅助函数：

* `__change_bit`;
* `change_bit`.

你可能已经猜测到，就拿 `set_bit` 和 `__set_bit` 例子说，这两个变体分别是原子和非原子版本。首先，让我们看看 `__change_bit` 函数的实现：

```
static inline void __change_bit(long nr, volatile unsigned long *addr)
{
    asm volatile("btc %1,%0" : ADDR : "Ir" (nr));
}

```

相当简单，不是吗？ `__change_bit` 的实现和 `__set_bit` 一样，只是我们使用 [btc](http://x86.renejeschke.de/html/file_module_x86_id_23.html) 替换 `bts` 指令而已。 该指令从一个给定位数组中选出一个给定位，将该为位的值存进 `CF` 并使用求反操作改变它的值，因此值为 `1` 的位将变为 `0`，反之亦然：

```
>>> int(not 1)
0
>>> int(not 0)
1

```

`__change_bit` 的原子版本为 `change_bit` 函数：

```
static inline void change_bit(long nr, volatile unsigned long *addr)
{
    if (IS_IMMEDIATE(nr)) {
        asm volatile(LOCK_PREFIX "xorb %1,%0"
            : CONST_MASK_ADDR(nr, addr)
            : "iq" ((u8)CONST_MASK(nr)));
    } else {
        asm volatile(LOCK_PREFIX "btc %1,%0"
            : BITOP_ADDR(addr)
            : "Ir" (nr));
    }
}

```

它和 `set_bit` 函数很相似，但也存在两点不同。第一处差异为 `xor` 操作而不是 `or`。第二处差异为 `btc`（ LCTT 译注：原文为 `bts`，为作者笔误） 而不是 `bts`。

目前，我们了解了最重要的体系特定的位数组操作，是时候看看一般的位图 API 了。

### 通用位操作

除了 [arch/x86/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h) 中体系特定的 API 外，Linux 内核提供了操作位数组的通用 API。正如我们本部分开头所了解的一样，我们可以在 [include/linux/bitmap.h](https://github.com/torvalds/linux/blob/master/include/linux/bitmap.h) 头文件和 [lib/bitmap.c](https://github.com/torvalds/linux/blob/master/lib/bitmap.c) 源文件中找到它。但在查看这些源文件之前，我们先看看 [include/linux/bitops.h](https://github.com/torvalds/linux/blob/master/include/linux/bitops.h) 头文件，其提供了一系列有用的宏，让我们看看它们当中一部分。

首先我们看看以下 4 个 宏：

* `for_each_set_bit`
* `for_each_set_bit_from`
* `for_each_clear_bit`
* `for_each_clear_bit_from`

所有这些宏都提供了遍历位数组中某些位集合的迭代器。第一个宏迭代那些被置位的位。第二个宏也是一样，但它是从某一个确定的位开始。最后两个宏做的一样，但是迭代那些被清位的位。让我们看看 `for_each_set_bit` 宏：

```
#define for_each_set_bit(bit, addr, size) \
    for ((bit) = find_first_bit((addr), (size));        \
         (bit) < (size);                    \
         (bit) = find_next_bit((addr), (size), (bit) + 1))

```

正如我们所看到的，它使用了三个参数，并展开为一个循环，该循环从作为 `find_first_bit` 函数返回结果的第一个置位开始，到小于给定大小的最后一个置位为止。

除了这四个宏， [arch/x86/include/asm/bitops.h](https://github.com/torvalds/linux/blob/master/arch/x86/include/asm/bitops.h) 也提供了 `64-bit` 或 `32-bit` 变量循环的 API 等等。

下一个 [头文件](https://github.com/torvalds/linux/blob/master/include/linux/bitmap.h) 提供了操作位数组的 API。例如，它提供了以下两个函数：

* `bitmap_zero`;
* `bitmap_fill`.

它们分别可以清除一个位数组和用 `1` 填充位数组。让我们看看 `bitmap_zero` 函数的实现：

```
static inline void bitmap_zero(unsigned long *dst, unsigned int nbits)
{
    if (small_const_nbits(nbits))
        *dst = 0UL;
    else {
        unsigned int len = BITS_TO_LONGS(nbits) * sizeof(unsigned long);
        memset(dst, 0, len);
    }
}

```

首先我们可以看到对 `nbits` 的检查。 `small_const_nbits` 是一个定义在同一个[头文件](https://github.com/torvalds/linux/blob/master/include/linux/bitmap.h) 的宏：

```
#define small_const_nbits(nbits) \
    (__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG)

```

正如我们可以看到的，它检查 `nbits` 是否为编译期已知常量，并且其值不超过 `BITS_PER_LONG` 或 `64`。如果位数目没有超过一个 `long` 变量的位数，我们可以仅仅设置为 0。在其他情况，我们需要计算有多少个需要填充位数组的 `long` 变量并且使用 [memset](http://man7.org/linux/man-pages/man3/memset.3.html) 进行填充。

`bitmap_fill` 函数的实现和 `biramp_zero` 函数很相似，除了我们需要在给定的位数组中填写 `0xff` 或 `0b11111111`：

```
static inline void bitmap_fill(unsigned long *dst, unsigned int nbits)
{
    unsigned int nlongs = BITS_TO_LONGS(nbits);
    if (!small_const_nbits(nbits)) {
        unsigned int len = (nlongs - 1) * sizeof(unsigned long);
        memset(dst, 0xff,  len);
    }
    dst[nlongs - 1] = BITMAP_LAST_WORD_MASK(nbits);
}

```

除了 `bitmap_fill` 和 `bitmap_zero`，[include/linux/bitmap.h](https://github.com/torvalds/linux/blob/master/include/linux/bitmap.h) 头文件也提供了和 `bitmap_zero` 很相似的 `bitmap_copy`，只是仅仅使用 [memcpy](http://man7.org/linux/man-pages/man3/memcpy.3.html) 而不是 [memset](http://man7.org/linux/man-pages/man3/memset.3.html) 这点差异而已。它也提供了位数组的按位操作，像 `bitmap_and`, `bitmap_or`, `bitamp_xor`等等。我们不会探讨这些函数的实现了，因为如果你理解了本部分的所有内容，这些函数的实现是很容易理解的。无论如何，如果你对这些函数是如何实现的感兴趣，你可以打开并研究 [include/linux/bitmap.h](https://github.com/torvalds/linux/blob/master/include/linux/bitmap.h) 头文件。

本部分到此为止。

### 链接

* [bitmap](https://en.wikipedia.org/wiki/Bit_array)
* [linked data structures](https://en.wikipedia.org/wiki/Linked_data_structure)
* [tree data structures](https://en.wikipedia.org/wiki/Tree_%28data_structure%29)
* [hot-plug](https://www.kernel.org/doc/Documentation/cpu-hotplug.txt)
* [cpumasks](https://0xax.gitbooks.io/linux-insides/content/Concepts/cpumask.html)
* [IRQs](https://en.wikipedia.org/wiki/Interrupt_request_%28PC_architecture%29)
* [API](https://en.wikipedia.org/wiki/Application_programming_interface)
* [atomic operations](https://en.wikipedia.org/wiki/Linearizability)
* [xchg instruction](http://x86.renejeschke.de/html/file_module_x86_id_328.html)
* [cmpxchg instruction](http://x86.renejeschke.de/html/file_module_x86_id_41.html)
* [lock instruction](http://x86.renejeschke.de/html/file_module_x86_id_159.html)
* [bts instruction](http://x86.renejeschke.de/html/file_module_x86_id_25.html)
* [btr instruction](http://x86.renejeschke.de/html/file_module_x86_id_24.html)
* [bt instruction](http://x86.renejeschke.de/html/file_module_x86_id_22.html)
* [sbb instruction](http://x86.renejeschke.de/html/file_module_x86_id_286.html)
* [btc instruction](http://x86.renejeschke.de/html/file_module_x86_id_23.html)
* [man memcpy](http://man7.org/linux/man-pages/man3/memcpy.3.html)
* [man memset](http://man7.org/linux/man-pages/man3/memset.3.html)
* [CF](https://en.wikipedia.org/wiki/FLAGS_register)
* [inline assembler](https://en.wikipedia.org/wiki/Inline_assembler)
* [gcc](https://en.wikipedia.org/wiki/GNU_Compiler_Collection)

---

via: <https://github.com/0xAX/linux-insides/blob/master/DataStructures/bitmap.md>

作者：[0xAX](https://twitter.com/0xAX) 译者：[cposture](https://github.com/cposture) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译，[Linux中国](https://linux.cn/) 荣誉推出


Fedora 25 将是第一个默认采用 Wayland 显示服务器的发行版

Summary: 上周，Fedora Wiki 网站上发布了一篇新功能建议草案，提议 Fedora 25 的 Workstation 版本采用 Wayland 做 GNOME 桌面环境的默认显示服务器。

为什么Wayland 不支持NVIDIA

来自江苏南京的 Internet Explorer 11.0|Windows 7 用户

拖了多少年了

你这问题本身就不对。只有 NVIDIA 是否支持 Wayland，哪能反过来？

Fedora 开发团队正在非常努力的开发下一代版本 Fedora 25，这将带来最新的技术改进。

![](/data/attachment/album/201608/23/235902ir1a7skoasas4mk6.jpg)

Wayland 就是一个新的技术，这个下一代显示服务器的设计目标即是替代老旧的 X.Org 服务器（即 X11）。X11 被几乎所有的 GNU/Linux 操作系统用作默认的显示服务器，但是 X11 本身有很多安全隐患，却由于种种原因而不能修复。所以，多年以来一直有呼声要求设计新的显示服务器以取代已经用了几十年的 X11 服务器，而 Wayland 就是被寄予期望的一个替代品。

Wayland 的取代过程虽然很慢，但是一直在继续。许多开源软件，比如说 GNOME 和 KDE 家族的那些软件都在积极支持新的 Wayland 显示服务器，除此之外， Enlightenment 家族和其它那些虽然不属于桌面环境家族，但也活跃开发的软件也对 Wayland 表示了支持。另外，Canonical 公司开发了一个他们自己的显示服务器，叫 Mir，也是用来替代 X11 的，不过目前只在 Ubuntu 家族取得了一定进展。

Fedora 作为 Linux 发行版界的技术先锋，总是积极地在他们的发行版中采用各种新的技术，比如 Systemd，也比如 Wayland。虽然因此带来一些负面结果——比如发布延期、稳定性和兼容性有时候不太好，但是作为一个为 RHEL 和 CentOS 趟路的发行版，似乎也无可厚非——Just for fun，新技术总是好玩的，不是么？

从 Fedora 24 开始，他们就想着在 Fedora 里面默认采用 Wayland 显示服务器，但是直到发布时，也没完全搞好，只能将这个重任放到下一个版本，即 Fedora 25 中。而在上周，Fedora Wiki 网站上发布了一篇新功能建议[草案](https://fedoraproject.org/wiki/Changes/WaylandByDefault)，提议 Fedora 25 的 Workstation 版本采用 Wayland 做 GNOME 桌面环境的默认显示服务器。

在该[草案](https://fedoraproject.org/wiki/Changes/WaylandByDefault)中说“我们将在 GNOME 中让 GDM 默认使用 Wayland。如果 Wayland 不可用（比如使用 nvidia 显卡时），代码会自动切换到 Xorg。用户可以在 /etc/gdm/custom.conf 中设置 WaylandEnable=false 来禁用 Wayland，但是不再为 GNOME 分别设置 X11 和 Wayland 两个入口菜单了。”

用户不会注意到采用不同的显示服务器的明显不同，事实上，采用 Wayland 会让应用彼此之间以及和底层系统隔离的更好。换句话说， Wayland 比 X11 更安全。

Fedora 25 目前计划将在今年的 11 月 15 日到来，当然，按照 Fedora 的“传统”，延期是一定的，现在已经延期一次了。但是，不要紧，等着等着就习惯了。在 Fedora 25 发布之前，你还可以在 Fedora 24 中使用 Wayland 显示服务器，并将你发现的问题反馈给社区，让他们可以更好的改善 Wayland 显示服务器的表现。总之，希望 Fedora 25 能给我们带来惊喜，将 Wayland 作为默认显示服务器显然会带来引导作用，必然会引领一些发行版跟进。

参考：[softpedia](http://news.softpedia.com/news/fedora-25-linux-os-to-arrive-on-november-15-ship-with-wayland-by-default-507547.shtml)。


使用 Docker Swarm 部署可扩展的 Python3 应用

以前一直在微信端看，非常喜欢，今天注册个账号，更方便些，感谢网站，提供了很多linux编程方面的知识

[Ben Firshman](https://blog.docker.com/author/bfirshman/) 最近在 [Dockercon](http://dockercon.com/) 做了一个关于使用 Docker 构建无服务应用的演讲，你可以在[这里查看详情](https://blog.docker.com/author/bfirshman/)（还有视频）。之后，我写了一篇关于如何使用 [AWS Lambda](https://aws.amazon.com/lambda/) 构建微服务系统的[文章](http://www.giantflyingsaucer.com/blog/?p=5730)。

![](/data/attachment/album/201608/24/085955kwrcach76z1zrncc.jpg)

今天，我想展示给你的就是如何使用 [Docker Swarm](https://docs.docker.com/swarm/) 部署一个简单的 Python Falcon REST 应用。这里我不会使用[dockerrun](https://github.com/bfirsh/dockerrun) 或者是其他无服务特性，你可能会惊讶，使用 Docker Swarm 部署（复制）一个 Python（Java、Go 都一样）应用是如此的简单。

注意：这展示的部分步骤是截取自 [Swarm Tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/)，我已经修改了部分内容，并且[增加了一个 Vagrant Helper 的仓库](https://github.com/chadlung/vagrant-docker-swarm)来启动一个可以让 Docker Swarm 工作起来的本地测试环境。请确保你使用的是 1.12 或以上版本的 Docker Engine。我写这篇文章的时候，使用的是 1.12RC2 版本。注意的是，这只是一个测试版本，可能还会有修改。

你要做的第一件事，就是如果你想本地运行的话，你要保证 [Vagrant](https://www.vagrantup.com/) 已经正确的安装和运行了。你也可以按如下步骤使用你最喜欢的云服务提供商部署 Docker Swarm 虚拟机系统。

我们将会使用这三台 VM：一个简单的 Docker Swarm 管理平台和两台 worker。

安全注意事项：Vagrantfile 代码中包含了部分位于 Docker 测试服务器上的 shell 脚本。这是一个潜在的安全问题，它会运行你不能控制的脚本，所以请确保你会在运行代码之前[审查过这部分的脚本](https://test.docker.com/)。

```
$ git clone https://github.com/chadlung/vagrant-docker-swarm
$ cd vagrant-docker-swarm
$ vagrant plugin install vagrant-vbguest
$ vagrant up

```

Vagrant up 命令需要一些时间才能完成。

SSH 登录进入 manager1 虚拟机：

```
$ vagrant ssh manager1

```

在 manager1 的 ssh 终端会话中执行如下命令：

```
$ sudo docker swarm init --listen-addr 192.168.99.100:2377

```

现在还没有 worker 注册上来：

```
$ sudo docker node ls

```

让我们注册两个新的 worker，请打开两个新的终端会话（保持 manager1 会话继续运行）：

```
$ vagrant ssh worker1

```

在 worker1 的 ssh 终端会话上执行如下命令：

```
$ sudo docker swarm join 192.168.99.100:2377

```

在 worker2 的 ssh 终端会话上重复这些命令。

在 manager1 终端上执行如下命令：

```
$ docker node ls

```

你将会看到：

![](/data/attachment/album/201608/24/100853ey7a897wz1raw0aa.png)

在 manager1 的终端里部署一个简单的服务。

```
sudo docker service create --replicas 1 --name pinger alpine ping google.com

```

这个命令将会部署一个服务，它会从 worker 之一 ping google.com。（或者 manager，manager 也可以运行服务，不过如果你只是想 worker 运行容器的话，[也可以禁用这一点](https://docs.docker.com/engine/reference/commandline/swarm_init/)）。可以使用如下命令，查看哪些节点正在执行服务:

```
$ sudo docker service tasks pinger

```

结果会和这个比较类似：

![](/data/attachment/album/201608/24/100927l9c02c8zmcl62yyq.png)

所以，我们知道了服务正跑在 worker1 上。我们可以回到 worker1 的会话里，然后进入正在运行的容器：

```
$ sudo docker ps

```

![](/data/attachment/album/201608/24/100956x0u0ze9neibb124u.png)

你可以看到容器的 id 是: ae56769b9d4d，在我的例子中，我运行如下的代码：

```
$ sudo docker attach ae56769b9d4d

```

![](/data/attachment/album/201608/24/101022ktqizbgbpnntsbpo.png)

你可以按下 CTRL-C 来停止服务。

回到 manager1，然后移除这个 pinger 服务。

```
$ sudo docker service rm pinger

```

现在，我们将会部署可复制的 Python 应用。注意，为了保持文章的简洁，而且容易复制，所以部署的是一个简单的应用。

你需要做的第一件事就是将镜像放到 [Docker Hub](https://hub.docker.com/)上，或者使用我[已经上传的一个](https://hub.docker.com/r/chadlung/hello-app/)。这是一个简单的 Python 3 Falcon REST 应用。它有一个简单的入口: /hello 带一个 value 参数。

放在 [chadlung/hello-app](https://hub.docker.com/r/chadlung/hello-app/) 上的 Python 代码看起来像这样：

```
import json
from wsgiref import simple_server

import falcon

class HelloResource(object):
    def on_get(self, req, resp):
        try:
            value = req.get_param('value')

            resp.content_type = 'application/json'
            resp.status = falcon.HTTP_200
            resp.body = json.dumps({'message': str(value)})
        except Exception as ex:
            resp.status = falcon.HTTP_500
            resp.body = str(ex)

if __name__ == '__main__':
    app = falcon.API()
    hello_resource = HelloResource()
    app.add_route('/hello', hello_resource)
    httpd = simple_server.make_server('0.0.0.0', 8080, app)
    httpd.serve_forever()

```

Dockerfile 很简单：

```
FROM python:3.4.4

RUN pip install -U pip
RUN pip install -U falcon

EXPOSE 8080

COPY . /hello-app
WORKDIR /hello-app

CMD ["python", "app.py"]

```

上面表示的意思很简单，如果你想，你可以在本地运行该进行来访问这个入口： <http://127.0.0.1:8080/hello?value=Fred>

这将返回如下结果：

```
{"message": "Fred"}

```

在 Docker Hub 上构建和部署这个 hello-app（修改成你自己的 Docker Hub 仓库或者[使用这个](https://hub.docker.com/r/chadlung/hello-app/)）：

```
$ sudo docker build . -t chadlung/hello-app:2
$ sudo docker push chadlung/hello-app:2

```

现在，我们可以将应用部署到之前的 Docker Swarm 了。登录 manager1 的 ssh 终端会话，并且执行：

```
$ sudo docker service create -p 8080:8080 --replicas 2 --name hello-app chadlung/hello-app:2
$ sudo docker service inspect --pretty hello-app
$ sudo docker service tasks hello-app

```

现在，我们已经可以测试了。使用任何一个 Swarm 节点的 IP 来访问 /hello 入口。在本例中，我在 manager1 的终端里使用 curl 命令：

注意，Swarm 中的所有的 IP 都可以，不管这个服务是运行在一台还是更多的节点上。

```
$ curl -v -X GET "http://192.168.99.100:8080/hello?value=Chad"
$ curl -v -X GET "http://192.168.99.101:8080/hello?value=Test"
$ curl -v -X GET "http://192.168.99.102:8080/hello?value=Docker"

```

结果：

```
* Hostname was NOT found in DNS cache
*   Trying 192.168.99.101...
* Connected to 192.168.99.101 (192.168.99.101) port 8080 (#0)
> GET /hello?value=Chad HTTP/1.1
> User-Agent: curl/7.35.0
> Host: 192.168.99.101:8080
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Date: Tue, 28 Jun 2016 23:52:55 GMT
< Server: WSGIServer/0.2 CPython/3.4.4
< content-type: application/json
< content-length: 19
<
{"message": "Chad"}

```

从浏览器中访问其他节点：

![](/data/attachment/album/201608/24/101056s9l9yqtoyll69jh9.png)

如果你想看运行的所有服务，你可以在 manager1 节点上运行如下代码：

```
$ sudo docker service ls

```

如果你想添加可视化控制平台，你可以安装 [Docker Swarm Visualizer](https://github.com/ManoMarks/docker-swarm-visualizer)（这对于展示非常方便）。在 manager1 的终端中执行如下代码：

```
$ sudo docker run -it -d -p 5000:5000 -e HOST=192.168.99.100 -e PORT=5000 -v /var/run/docker.sock:/var/run/docker.sock manomarks/visualizer

```

打开你的浏览器，并且访问： <http://192.168.99.100:5000/>

结果如下（假设已经运行了两个 Docker Swarm 服务）：

![](/data/attachment/album/201608/24/101143ry1518zi0p1gpwcy.png)

要停止运行 hello-app（已经在两个节点上运行了），可以在 manager1 上执行这个代码：

```
$ sudo docker service rm hello-app

```

如果想停止 Visualizer， 那么在 manager1 的终端中执行：

```
$ sudo docker ps

```

获得容器的 ID，这里是： f71fec0d3ce1，从 manager1 的终端会话中执行这个代码：

```
$ sudo docker stop f71fec0d3ce1

```

祝你成功使用 Docker Swarm。这篇文章主要是以 1.12 版本来进行描述的。

---

via: <http://www.giantflyingsaucer.com/blog/?p=5923>

作者：[Chad Lung](http://www.giantflyingsaucer.com/blog/?author=2) 译者：[MikeCoder](https://github.com/MikeCoder) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出