转载:x86的cpu_relax解析

本文深入解析内核自旋等待过程中为何使用pause指令而非nop指令,解释了pause指令在自旋等待中的作用及优势,并讨论了rep;nop与pause指令的选择原因。

内核执行的任务在很多情况下是不加锁的,只是poll某个公有变量去保证同步。再深一步,即使是使用锁,本质上也是一个poll某个公有变量的过程。这个poll的过程需要CPU一直循环等待。

要是让我这个菜鸟来写的话,循环体内大概是什么都不会做的了,XD。而x86的内核中一般是调用cpu_relax()。这个函数又是何方神圣呢?

实际上,这个函数很简单。

?
1
2
3
4
5
#define cpu_relax() rep_nop()
static always_inline void rep_nop(void)
{
        asm volatile ( "rep;nop" : : : "memory" );
}

自旋锁里面也有rep;nop这个语句。而我很蛋痛地在想,为什么是rep;nop而不是nop;nop而不是nop;nop;nop…;nop,反正都是什么都不做嘛,为什么偏偏要选这个?众所周知,在内核这个层次,基本上每一行代码都是最优的,做出这样的选择必定事出有因。

rep;nop的机器码是f3 90,其实就是pause指令的机器码,相当于pause的一个“别名”,这是巧合吗?pause指令又是干什么的呢?

从Intel的手册里面翻出来一段话:

Improves the performance of spin-wait loops. When executing a “spin-wait loop,” a Pentium 4 or Intel Xeon processor suffers a severe performance penalty when exiting the loop because it detects a possible memory order violation. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation in most situations, which greatly improves processor performance. For this reason, it is recommended that a PAUSE instruction be placed in all spin-wait loops.

An additional fucntion of the PAUSE instruction is to reduce the power consumed by a Pentium 4 processor while executing a spin loop.

简单点说,用pause可以提示CPU接下来的指令序列是用来自旋等待,就不用做memory reorder了,cache什么的也不用废掉了——要知道,cache是很宝贵的资源啊。这指令还有附送的功能——减少能耗。其实内核代码最根本的要求就是:快,快,更加快,这条指令还有附送功能,所以何乐而不为呢?

那么为什么不直接写pause而要写rep;nop呢?理论上是等价的,但是实际上为什么不这样做,不好意思,不清楚。但是可以确定是的pause是Pentium 4才引入的,也许大家比较怀旧所以还用rep;nop也说不定

So,以后写应用程序而又蛋痛写了循环等待的话,不妨也用用pause吧。不过我想会在应用程序中写循环等待这么傻的代码的程序员,应该也想不到用pause去节能减排兼提速了吧,伤脑筋。

这段代码根据不同的架构实现了 `LF_PAUSE()` 宏,用于执行 CPU 放松操作。以下是对代码逻辑和含义的详细解释: ### 代码逻辑 - **x86_64 和 i386 架构**: 当代码在 `x86_64` 或 `i386` 架构的系统上编译时,`lf_cpu_relax` 函数使用内联汇编执行 `pause` 指令。`pause` 指令在 Pentium 4 模型中引入,用于优化自旋锁循环的执行。它会引入一个很短的延迟,加快紧跟在锁后面的代码的执行并减少能源消耗,且与早先的 80x86 微处理器模型向后兼容,对应 `rep; nop` 指令(空操作)。`LF_PAUSE()` 宏被定义为调用 `lf_cpu_relax` 函数。 ```c #if defined(__x86_64__) || defined(__i386__) static inline void lf_cpu_relax(void) { __asm__ volatile("pause" ::: "memory"); } #define LF_PAUSE() lf_cpu_relax() ``` - **aarch64 和 arm 架构**: 当代码在 `aarch64` 或 `arm` 架构的系统上编译时,`lf_cpu_relax` 函数使用内联汇编执行 `yield` 指令。`yield` 指令用于提示 CPU 当前线程可以让出处理器资源,以便其他线程有机会执行。同样,`LF_PAUSE()` 宏被定义为调用 `lf_cpu_relax` 函数。 ```c #elif defined(__aarch64__) || defined(__arm__) static inline void lf_cpu_relax(void) { __asm__ volatile("yield" ::: "memory"); } #define LF_PAUSE() lf_cpu_relax() ``` - **其他架构**: 当代码在不支持上述架构的系统上编译时,`LF_PAUSE()` 宏被定义为空操作 `((void)0)`,即不执行任何实际操作。 ```c #else #define LF_PAUSE() ((void)0) #endif ``` ### 代码含义 这段代码的主要目的是为不同的 CPU 架构提供一种统一的方式来执行 CPU 放松操作。在自旋锁等场景中,CPU 可能会在一个循环中不断尝试获取锁,这会导致 CPU 资源的浪费。通过执行 `LF_PAUSE()` 宏,可以让 CPU 在等待锁的过程中适当放松,减少能源消耗并提高系统性能。 ### 示例代码使用 ```c #include <stdio.h> #if defined(__x86_64__) || defined(__i386__) static inline void lf_cpu_relax(void) { __asm__ volatile("pause" ::: "memory"); } #define LF_PAUSE() lf_cpu_relax() #elif defined(__aarch64__) || defined(__arm__) static inline void lf_cpu_relax(void) { __asm__ volatile("yield" ::: "memory"); } #define LF_PAUSE() lf_cpu_relax() #else #define LF_PAUSE() ((void)0) #endif int main() { // 在自旋锁等待时使用 LF_PAUSE() while (1) { // 尝试获取锁 if (/* 锁可用 */) { break; } LF_PAUSE(); } printf("Lock acquired!\n"); return 0; } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值