Linux 内核中使用或依赖的 GCC 扩展代码示例

Linux内核因其高效性和灵活性而受到广泛赞誉,而在其基础中广泛利用了GNU编译器集合(GCC)的许多扩展功能。这些扩展在优化内核性能和确保在不同平台上的兼容性方面发挥着重要作用。在这里,我们将介绍一些主要的GCC扩展,附带代码示例,并解释它们的使用方法和优势。

GNU Compiler Collection(GCC)是由GNU项目开发的一套程序语言编译器集合。GCC由自由软件基金(FSF)发布,并在GNU通用公共许可证(GPL)下提供。

最初是为了C语言而启动的编译器,但目前支持多种编程语言,包括C++、Objective-C、Fortran、Ada、Go、D等。GCC提供了先进的优化功能,可在多个平台上提供可移植性,并使终端用户能够在许多平台上编译代码

GCC具有以下特点:

  1. 可移植性: GCC可以在各种类型的处理器和操作系统上运行,使开发人员能够在不同的架构上使用相同的代码基础。
  2. 优化: GCC提供广泛的优化选项,可用于最大化性能。
  3. 语言支持: 除了C/C++之外,由于支持许多其他语言,因此可以满足各种项目的需求。
  4. 调试和错误报告: GCC通过错误消息和警告指出代码问题,使调试变得容易。
  5. 可扩展性: GCC支持语言和体系结构特定的扩展,可以访问非标准化语言功能和体系结构特定的指令。
  6. 自由软件: 由于在GPL下发布,可以自由使用、修改和重新分发。

GCC作为一个标准的编译器被广泛应用于许多开源项目,包括编译Linux内核,其稳健性和可靠性使其成为行业标准。

属性(Attributes)

示例:打包的结构体

struct __attribute__((packed)) tcp_header {
    uint16_t source_port;
    uint16_t dest_port;
    uint32_t sequence;
    uint32_t ack_sequence;
    uint16_t flags;
    uint16_t window_size;
    uint16_t checksum;
    uint16_t urgent_pointer;
};

解释:

在这里,我们对表示TCP头的结构体应用了__attribute__((packed))属性。该属性防止编译器在结构体成员之间插入填充,从而可以精确地控制内存上的布局,以适应网络通信的需要。

其他属性:

  • __attribute__((packed)): 在结构体成员之间紧密地打包,避免插入填充。
  • __attribute__((aligned(x))): 将变量或结构体对齐到指定的边界。
  • __attribute__((noreturn)): 通知编译器函数不会返回。
  • __attribute__((section("..."))): 将变量或函数放置在特定的节(section)中。

内建函数(Built-in Functions):

例如:计算最高位。

unsigned int value = 0xFF00;
int msb = 32 - __builtin_clz(value);

解释:

__builtin_clz函数用于计算从值的开头开始的连续零的数量。在这个例子中,它被用于找到变量value的最高位(即最左侧的非零位),常用于位字段操作、资源分配等许多低级操作。它还可用于在位图中搜索空闲槽等场景。

例子:使用GCC内建函数进行内存操作

void *my_memcpy(void *dest, const void *src, size_t n) {
    return __builtin_memcpy(dest, src, n);
}

解释:

GCC内建函数__builtin_memcpy可能比标准的memcpy函数更高效,并且在特定的优化情况下可能进行了特殊处理。在内核中,使用这样的内建函数可以加速内存复制操作。

其他内建函数:

  • __builtin_popcount: 计算整数中1的位数。
  • __builtin_clz: 计算整数中开头的0的位数(Count Leading Zeros)。
  • __builtin_expect: 为了优化分支预测,向编译器提示某个条件最有可能是真的。

编译时断言

例子:编译时断言。

BUILD_BUG_ON(sizeof(mystruct) != expected_size);

解释:

BUILD_BUG_ON 是一个宏,在特定条件为真的情况下生成编译错误,这是在编译时进行的。借助于GCC的功能,内核开发者使用这个宏来确保数据结构具有期望的大小。

typeof 操作符

例子:变量类型的自动推断

#define min(x, y) ({             /
    typeof(x) _min1 = (x);       /
    typeof(y) _min2 = (y);       /
    (void) (&_min1 == &_min2);   /
    _min1 < _min2 ? _min1 : _min2; /
})

解释:

typeof 是一种由GCC引入的自动获取变量类型的扩展。在上述的 min 宏中,使用 typeof 推断了参数 xy 的类型,然后基于这些类型进行了比较。这种技巧允许在保持类型安全性的同时进行通用操作。

寄存器变量

例子:将变量分配到特定寄存器

register unsigned long sp asm ("sp");

解释:

使用这个语法,内核开发人员可以直接将变量分配到特定的CPU寄存器。这在提高关键函数性能时可能会被使用,但需要非常谨慎。

用于多线程的扩展

例子:使用线程本地存储

__thread int my_thread_variable;

解释:

__thread 存储类说明符表示变量位于线程本地存储(TLS)中。这确保每个线程都有其自己的变量实例,对于需要每个内核线程都有其独特上下文的情况非常有用。

语句表达式(Statement Expressions)

例子:在宏内进行复杂计算

#define MAX(a, b) ({ /
    typeof(a) _a = (a); /
    typeof(b) _b = (b); /
    _a > _b ? _a : _b; /
})

解释:

通过使用这个语句表达式,可以在 MAX 宏内评估 ab 的最大值,并返回结果。这种技巧在需要在宏内执行多个步骤并返回一个值的情况下非常有用。

更详细的解释:

如果不使用语句表达式,宏可能在某些情况下无法按预期工作。在不使用语句表达式的传统宏的情况下,如果参数包含具有副作用的表达式或者被多次评估,可能会导致问题。

例如,考虑以下简单的 MAX 宏:

#define MAX(a, b) ((a) > (b) ? (a) : (b))

这个宏在通常情况下可以正常工作,但如果包含有副作用的表达式,可能会导致意外的行为,例如,如果 ab 中包含增量运算符(++)。

int x = 5;
int y = 10;
int z = MAX(x++, y++);

在上述代码中,由于 MAX 宏两次评估 xy,可能导致与期望的结果不同的行为,即 xy 的值以意外的方式更改。

相比之下,使用语句表达式的 MAX 宏如下:

#define MAX(a, b) ({ /
    typeof(a) _a = (a); /
    typeof(b) _b = (b); /
    _a > _b ? _a : _b; /
})

在这里,ab 仅被评估一次,其结果分别存储在 _a_b 中。这样,即使参数包含带有副作用的表达式,也只会评估一次,从而避免了意外的副作用问题。

例如,考虑以下的宏:

#define MAX(a, b) ((a) > (b) ? (a) : (b))

这个宏如果传递包含 ++ 的表达式,例如 MAX(x++, y++),则可能被预处理器展开为以下形式:

((x++) > (y++) ? (x++) : (y++))

在展开的结果中,xy 在宏内部被多次引用。在评估条件表达式 (x++) > (y++) 时,xy 各自增加一次。然后,根据条件表达式的结果,xy 将再次增加一次。因此,xy 中的一个可能会意外地增加两次。

这种多次评估的问题,特别是涉及到具有副作用的操作(例如增量或减量)时,容易导致错误。因此,在将带有副作用的表达式作为宏的参数使用时,需要格外小心。通过使用语句表达式,每个参数只被评估一次并存储在临时变量中,可以防止这类问题的发生。

内联汇编语法(Inline Assembly)

例子:使用特定的汇编指令

void disable_interrupts() {
    __asm__ __volatile__("cli": : :"memory");
}

解释:

这个内联汇编命令 cli 会禁用 CPU 的中断。内核使用这样的低级别指令直接控制硬件,确保在关键操作期间不会发生中断。

指定初始化(Designated Initializers)

例子:结构体的初始化

struct point {
    int x;
    int y;
};

struct point p = {
    .x = 10,
    .y = 20
};

解释:

指定初始化器允许直接为结构体的特定成员分配值,使初始化更具可读性且减少错误。这种方法在只想初始化结构体的部分成员或者需要明确初始化顺序的情况下特别有用。

GCC 的扩展广泛用于 Linux 内核代码库的各个部分,通过有效地利用这些扩展,Linux 在各种硬件上展现出卓越性能。通过使用这些扩展,开发者能够更细粒度地控制内核的行为,实现高效的系统调用、硬件操作和数据处理。

为了实现 Linux 内核的高级功能和性能,它充分利用了 GCC(GNU Compiler Collection)的多样化扩展。这些扩展对于提升内核的性能、稳定性以及在不同平台上的兼容性至关重要。

The post Linux 内核中使用或依赖的 GCC 扩展代码示例 first appeared on Linux迷.

版权声明:
作者:siwei
链接:https://www.techfm.club/p/85130.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>