转自:http://tinylab.org/arm-wfe/

Zhang Binghua 创作于 2020/05/19
微信公众号   知识星球
关注 @泰晓科技

与数千位一线 Linux 工程师做朋友,您准备好了吗?

周一到周五,天天有新文。   日更实战经验与技巧!

1 背景简介

大家好,我叫张昺华,中间那个字和“饼”字一个读音,本人非常热衷技术,是个技术狂热者。

今天我想分享一个跟多核锁原理相关的东西,由于我搞 arm 居多,所以目前只研究了 arm 架构下的 WFE 指令,分享出来,如果有表述不精准或者错误的地方还请大家指出,非常感谢。研究这个原因也是只是想搞清楚所以然和来龙去脉,以后写代码可以更游刃有余。

2 我与 WFE 的初次见面

偶然看 spin_lock 的 arm 架构下的 smp 源码的时候,发现了 wfe() 这个接口:

  1. static inline void arch_spin_lock(arch_spinlock_t *lock)
  2. {
  3. unsigned long tmp;
  4. u32 newval;
  5. arch_spinlock_t lockval;
  6. prefetchw(&lock->slock);
  7. __asm__ __volatile__(
  8. "1: ldrex %0, [%3]\n"
  9. " add %1, %0, %4\n"
  10. " strex %2, %1, [%3]\n"
  11. " teq %2, #0\n"
  12. " bne 1b"
  13. : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
  14. : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
  15. : "cc");
  16. while (lockval.tickets.next != lockval.tickets.owner) {
  17. wfe();
  18. lockval.tickets.owner = READ_ONCE(lock->tickets.owner);
  19. }
  20. smp_mb();
  21. }

我印象之前的 kernel 是没有这个 wfe 这个函数的,当 cpu0 获取到锁后,如果 cpu1 再想获取锁,此时会被 lock 住,然后进入死等的状态,那么 wfe 这个指令的作用是会让 cpu 进入 low power standby,这样可以降低功耗,本来发生竞态时其他的 cpu 都要等待这个锁释放才能运行,有了这个指令,相当于是“因祸得福”了,还可以降低功耗,当然这是有条件的,后面追溯并研究了一下 wfe 这个指令的作用。

3 spinlock 与 WFE、SEV、WFI

首先 spin_lock 函数,搞内核的大家都知道,那么我把 linux-stable 的代码黏贴出来如下:

  1. static __always_inline void spin_lock(spinlock_t *lock)
  2. {
  3. raw_spin_lock(&lock->rlock);
  4. }
  5. #define raw_spin_lock(lock) _raw_spin_lock(lock)
  6. #ifndef CONFIG_INLINE_SPIN_LOCK
  7. void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
  8. {
  9. __raw_spin_lock(lock);
  10. }
  11. EXPORT_SYMBOL(_raw_spin_lock);
  12. #endif
  13. static inline void __raw_spin_lock(raw_spinlock_t *lock)
  14. {
  15. preempt_disable();
  16. spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
  17. LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
  18. }
  19. /*
  20. * We are now relying on the NMI watchdog to detect lockup instead of doing
  21. * the detection here with an unfair lock which can cause problem of its own.
  22. */
  23. void do_raw_spin_lock(raw_spinlock_t *lock)
  24. {
  25. debug_spin_lock_before(lock);
  26. arch_spin_lock(&lock->raw_lock);
  27. mmiowb_spin_lock();
  28. debug_spin_lock_after(lock);
  29. }
  30. /*
  31. * ARMv6 ticket-based spin-locking.
  32. *
  33. * A memory barrier is required after we get a lock, and before we
  34. * release it, because V6 CPUs are assumed to have weakly ordered
  35. * memory.
  36. */
  37. static inline void arch_spin_lock(arch_spinlock_t *lock)
  38. {
  39. unsigned long tmp;
  40. u32 newval;
  41. arch_spinlock_t lockval;
  42. prefetchw(&lock->slock);
  43. __asm__ __volatile__(
  44. "1: ldrex %0, [%3]\n"
  45. " add %1, %0, %4\n"
  46. " strex %2, %1, [%3]\n"
  47. " teq %2, #0\n"
  48. " bne 1b"
  49. : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
  50. : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
  51. : "cc");
  52. while (lockval.tickets.next != lockval.tickets.owner) {
  53. wfe();
  54. lockval.tickets.owner = READ_ONCE(lock->tickets.owner);
  55. }
  56. smp_mb();
  57. }

对于 arm32:

  1. #if __LINUX_ARM_ARCH__ >= 7 || \
  2. (__LINUX_ARM_ARCH__ == 6 && defined(CONFIG_CPU_32v6K))
  3. #define sev() __asm__ __volatile__ ("sev" : : : "memory")
  4. #define wfe() __asm__ __volatile__ ("wfe" : : : "memory")
  5. #define wfi() __asm__ __volatile__ ("wfi" : : : "memory")
  6. #else
  7. #define wfe() do { } while (0)
  8. #endif

对于 arm64:

  1. #define sev() asm volatile("sev" : : : "memory")
  2. #define wfe() asm volatile("wfe" : : : "memory")
  3. #define wfi() asm volatile("wfi" : : : "memory")
  4. static inline void arch_spin_unlock(arch_spinlock_t *lock)
  5. {
  6. smp_mb();
  7. lock->tickets.owner++;
  8. dsb_sev();
  9. }
  10. #define SEV __ALT_SMP_ASM(WASM(sev), WASM(nop))
  11. static inline void dsb_sev(void)
  12. {
  13. dsb(ishst);
  14. __asm__(SEV);
  15. }
  16. #ifdef CONFIG_SMP
  17. #define __ALT_SMP_ASM(smp, up) \
  18. "9998: " smp "\n" \
  19. " .pushsection \".alt.smp.init\", \"a\"\n" \
  20. " .long 9998b\n" \
  21. " " up "\n" \
  22. " .popsection\n"
  23. #else
  24. #define __ALT_SMP_ASM(smp, up) up
  25. #endif

以上我们可以看出,在 lock 的时候使用 WFE,在 unlock 的时候使用 SEV,这个必须要成对使用,原因我下面会说。

对于内核版本 Linux 3.0.56:

  1. static inline void arch_spin_lock(arch_spinlock_t *lock)
  2. {
  3. unsigned long tmp;
  4. __asm__ __volatile__(
  5. "1: ldrex %0, [%1]\n"
  6. " teq %0, #0\n"
  7. WFE("ne")
  8. " strexeq %0, %2, [%1]\n"
  9. " teqeq %0, #0\n"
  10. " bne 1b"
  11. : "=&r" (tmp)
  12. : "r" (&lock->lock), "r" (1)
  13. : "cc");
  14. smp_mb();
  15. }

对于 Linux 2.6.18:

  1. define _raw_spin_lock(lock) __raw_spin_lock(&(lock)->raw_lock)
  2. static inline void __raw_spin_lock(raw_spinlock_t *lock)
  3. {
  4. unsigned long tmp;
  5. __asm__ __volatile__(
  6. "1: ldrex %0, [%1]\n"
  7. " teq %0, #0\n"
  8. " strexeq %0, %2, [%1]\n"
  9. " teqeq %0, #0\n"
  10. " bne 1b"
  11. : "=&r" (tmp)
  12. : "r" (&lock->lock), "r" (1)
  13. : "cc");
  14. smp_mb();
  15. }

以上大家可以看出,最早期的 kernel 版本是没有 wfe 这条指令的,后面的版本才有。

4 WFE、SEV 与 WFI 的作用与工作原理

那这条指令的作用是什么呢?我们可以上 arm 官网去查看这条指令的描述:ARM Software development tools

  • SEV

    SEV causes an event to be signaled to all cores within a multiprocessor system. If SEV is implemented, WFE must also be implemented.

SEV 指令可以产生事件信号,发送给全部的 cpu,让他们唤醒。如果 SEV 实现了,那么 WFE 也必须被实现。这里的事件信号其实会表现为 Event register,这是个一 bit 的 register,如果有事件,那么此 bit 为真。

  • WFE

If the Event Register is not set, WFE suspends execution until one of the following events occurs:

• an IRQ interrupt, unless masked by the CPSR I-bit • an FIQ interrupt, unless masked by the CPSR F-bit • an Imprecise Data abort, unless masked by the CPSR A-bit • a Debug Entry request, if Debug is enabled • an Event signaled by another processor using the SEV instruction.

If the Event Register is set, WFE clears it and returns immediately. If WFE is implemented, SEV must also be implemented.

对于 WFE,如果 Event Register 没有设置,WFE 会让 cpu 进入 low-power state,直到下面列举的五个 events 产生,比如说中断等等都会唤醒当前因为 WFE 而 suspend 的cpu。

如果 Event Register 被设置了,那么 WFE 会直接返回,不让 cpu 进入low-power state,目的是因为既然有事件产生了,说明当前 cpu 需要干活,不能 suspend,所以才这样设计。

这里我很好奇 Event Register 到底是怎么理解的。因此需要阅读手册《ARM Architecture Reference Manual.pdf》,下面我会做说明。

  • WFI

WFI suspends execution until one of the following events occurs:

• an IRQ interrupt, regardless of the CPSR I-bit • an FIQ interrupt, regardless of the CPSR F-bit • an Imprecise Data abort, unless masked by the CPSR A-bit • a Debug Entry request, regardless of whether Debug is enabled.

而对于 WFI 这种,不会判断 Event register,暴力的直接让 cpu 进入 low-power state,直到有上述四个 events 产生才会唤醒 cpu。

注意,这里 WFE 比 WFI 多了一个唤醒特性:

an Event signaled by another processor using the SEV instruction.

也就是说 SEV 是不会唤醒 WFI 指令休眠的 cpu 的。这点需要特别注意。

接下来我谈下这个 Event Register 是怎么回事了。看这个文档《ARM Architecture Reference Manual.pdf》

  • The Event Register

The Event Register is a single bit register for each processor. When set, an event register indicates that an event has occurred, since the register was last cleared, that might require some action by the processor. Therefore, the processor must not suspend operation on issuing a WFE instruction.

The reset value of the Event Register is UNKNOWN.

The Event Register is set by:

• an SEV instruction • an event sent by some IMPLEMENTATION DEFINED mechanism • a debug event that causes entry into Debug state • an exception return.

As shown in this list, the Event Register might be set by IMPLEMENTATION DEFINED mechanisms. The Event Register is cleared only by a Wait For Event instruction. Software cannot read or write the value of the Event Register directly.

以上就是 Event Register 的表述,上述已经说的很明白了,Event Register 只有一个 bit,可以被 set 的情况总有四种大类型。当任意一个条件满足的时候,Event Register 都可以被 set,那么当 WFE 进入的时候会进行 Event Register 的判断,如果为真,就直接返回。

再来看看 WFE 的介绍:

Wait For Event is a hint instruction that permits the processor to enter a low-power state until one of a number of events occurs, including events signaled by executing the SEV instruction on any processor in the multiprocessor system. For more information, see Wait For Event and Send Event on page B1-1197.

In an implementation that includes the Virtualization Extensions, if HCR.TWE is set to 1, execution of a WFE instruction in a Non-secure mode other than Hyp mode generates a Hyp Trap exception if, ignoring the value of the HCR.TWE bit, conditions permit the processor to suspend execution. For more information see Trapping use of the WFI and WFE instructions on page B1-1249.

接下来上 WFE 这条指令的伪代码流程:

  1. Assembler syntax
  2. WFE{<c>}{<q>}
  3. where:
  4. <c>, <q> See Standard assembler syntax fields on page A8-285.
  5. Operation
  6. if ConditionPassed() then
  7. EncodingSpecificOperations();
  8. if EventRegistered() then
  9. ClearEventRegister();
  10. else
  11. if HaveVirtExt() && !IsSecure() && !CurrentModeIsHyp() && HCR.TWE == '1' then
  12. HSRString = Zeros(25);
  13. HSRString<0> = '1';
  14. WriteHSR('000001', HSRString);
  15. TakeHypTrapException();
  16. else
  17. WaitForEvent();
  18. Exceptions
  19. Hyp Trap.

看了上面这段 WFE 的伪代码,一目了然,首先判断 ConditionPassed(),这些函数大家可以在 arm 手册中查看其详细含义,如果 EventResigerted() 函数为真,也就是这个 1 bit 的寄存器为真,那么就清除此 bit,然后退出返回,不会让 cpu 进入 low power state;

如果不是异常处理,TakeHypTrapException(),那么就 WaitForEvent(),等待唤醒事件到来,到来了,就唤醒当前 cpu。

为什么有事件来了就直接返回呢,因为 WFE 的设计认为,如果此时有 event 事件,那么说明当前 cpu 要干活,那就没必要进入 low power state 模式。

如果没有事件产生,那么就可以进入 low power state 模式,因为 cpu 反正也是在等待锁,此时也干不了别的事情,还不如休眠还可以降低功耗。

当然,irq,fiq 等很多中断都可以让 WFE 休眠的 cpu 唤醒,那么这样做还有什么意义呢?比如说时钟中断是一直产生的,那么 cpu 很快就醒了啊,都不用等到发 SEV,那么既然是用 spin_lock,也可以在中断上半部使用,也可以在进程上下文,既然是自旋锁,就意味着保护的这段代码是要足够精简,不希望被其他东西打断,那么如果你保护的这部分代码非常长,这时候整个系统响应很可能会变慢,因为如果这时候有人也要使用这个锁的话,那么是否保护的这段代码设计上是有问题的。因此用 spin_lock 保护的函数尽可能要短,如果长的话可能需要换其他锁,或者考虑下是否真的要这么长的保护措施。

TakeHypTrapException(),是进入异常处理

  1. Pseudocode description of taking the Hyp Trap exception
  2. The TakeHypTrapException() pseudocode procedure describes how the processor takes the exception:
  3. // TakeHypTrapException()
  4. // ======================
  5. TakeHypTrapException()
  6. // HypTrapException is caused by executing an instruction that is trapped to Hyp mode as a
  7. // result of a trap set by a bit in the HCR, HCPTR, HSTR or HDCR. By definition, it can only
  8. // be generated in a Non-secure mode other than Hyp mode.
  9. // Note that, when a Supervisor Call exception is taken to Hyp mode because HCR.TGE==1, this
  10. // is not a trap of the SVC instruction. See the TakeSVCException() pseudocode for this case.
  11. preferred_exceptn_return = if CPSR.T == '1' then PC-4 else PC-8;
  12. new_spsr_value = CPSR;
  13. EnterHypMode(new_spsr_value, preferred_exceptn_return, 20);
  14. Additional pseudocode functions for exception handling on page B1-1221 defines the EnterHypMode() pseudocode
  15. procedure.

ClearEventRegister()

Clear the Event Register of the current processor 清除Event Register的bit

EventRegistered()

Determine whether the Event Register of the current processor is set Event Register bit为真,即被设置过

WaitForEvent()

Wait until WFE instruction completes

等待 Events 事件,有任何一个 Event 事件来临,都会唤醒当前被 WFE suspend 下去的 cpu, 如果是 SEV,会唤醒全部被 WFE suspend 下去的cpu。

如下是关于 WFE 的 wake up events 事件描述和列举:

WFE wake-up events

The following events are WFE wake-up events:

• the execution of an SEV instruction on any processor in the multiprocessor system • a physical IRQ interrupt, unless masked by the CPSR.I bit • a physical FIQ interrupt, unless masked by the CPSR.F bit • a physical asynchronous abort, unless masked by the CPSR.A bit • in Non-secure state in any mode other than Hyp mode: — when HCR.IMO is set to 1, a virtual IRQ interrupt, unless masked by the CPSR.I bit — when HCR.FMO is set to 1, a virtual FIQ interrupt, unless masked by the CPSR.F bit — when HCR.AMO is set to 1, a virtual asynchronous abort, unless masked by the CPSR.A bit • an asynchronous debug event, if invasive debug is enabled and the debug event is permitted • an event sent by the timer event stream, see Event streams on page B8-1934 • an event sent by some IMPLEMENTATION DEFINED mechanism.

In addition to the possible masking of WFE wake-up events shown in this list, when invasive debug is enabled and DBGDSCR[15:14] is not set to 0b00, DBGDSCR.INTdis can mask interrupts, including masking them acting as WFE wake-up events. For more information, see DBGDSCR, Debug Status and Control Register on page C11-2206. As shown in the list of wake-up events, an implementation can include IMPLEMENTATION DEFINED hardware mechanisms to generate wake-up events. NoteFor more information about CPSR masking see Asynchronous exception masking on page B1-1181. If the configuration of the masking controls provided by the Security Extensions, or Virtualization Extensions, mean that a CPSR mask bit cannot mask the corresponding exception, then the physical exception is a WFE wake-up event, regardless of the value of the CPSR mask bit.

接下来我们看下 WFI 的伪代码:

  1. Assembler syntax
  2. WFI{<c>}{<q>}
  3. where:
  4. <c>, <q> See Standard assembler syntax fields on page A8-285.
  5. Operation
  6. if ConditionPassed() then
  7. EncodingSpecificOperations();
  8. if HaveVirtExt() && !IsSecure() && !CurrentModeIsHyp() && HCR.TWI == '1' then
  9. HSRString = Zeros(25);
  10. HSRString<0> = '1';
  11. WriteHSR('000001', HSRString);
  12. TakeHypTrapException();
  13. else
  14. WaitForInterrupt();
  15. Exceptions
  16. Hyp Trap.

相关解释:

WFI

Wait For Interrupt is a hint instruction that permits the processor to enter a low-power state until one of a number of asynchronous events occurs. For more information, see Wait For Interrupt on page B1-1200. In an implementation that includes the Virtualization Extensions, if HCR.TWI is set to 1, execution of a WFE instruction in a Non-secure mode other than Hyp mode generates a Hyp Trap exception if, ignoring the value of the HCR.TWI bit, conditions permit the processor to suspend execution. For more information see Trapping use of the WFI and WFE instructions on page B1-1249.

以上我们可以看出,WFI 并没有去判断 event register,因此看伪代码可以很直观的看出 WFI 与 WFE 的区别。

以上是我个人的理解,如果有表述不精准或者不准确的地方还请大家指出,欢迎交流。

5 参考资料


猜你喜欢:

支付宝打赏   微信打赏

请作者喝杯咖啡吧

Read Related:

Read Latest:



本作品由 Zhang Binghua 创作,采用 CC BY-NC-ND 4.0 协议 进行许可。未经授权,谢绝商业使用!

 
 
Zhang Binghua (sky)

对 Linux 内核代码极度热爱,喜欢和周边的小伙伴们一起讨论研究各种技术

—— 扫码访问作者Github

CPU 多核指令 —— WFE 原理【原创】的更多相关文章

  1. 1、cpu架构和工作原理

    cpu架构和工作原理 计算机有5大基本组成部分,运算器,控制器,存储器,输入和输出.运算器和控制器封装到一起,加上寄存器组和cpu内部总线构成中央处理器(CPU).cpu的根本任务,就是执行指令,对计 ...

  2. 单片机、CPU、指令集和操作系统的关系

    郑重声明:转载自http://blog.csdn.net/zhongjin616/article/details/18765301 1> 首先讨论各种单片机与操作系统的关系 说到单片机,大家第一 ...

  3. 代码中理解CPU结构及工作原理

    一.前言 从研究生开始到工作半年,陆续在接触MCU SOC这些以CPU为核心的控制器,但由于专业的原因一直对CPU的内部结构和工作原理一知半解.今天从一篇博客中打破一直以来的盲区.特此声明,本文设计思 ...

  4. paip.提升性能---mysql 优化cpu多核以及lan性能的关系.

    paip.提升性能---mysql 优化cpu多核以及lan性能的关系. 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http:/ ...

  5. cpu读取指令时读取的长度

    CPU读取指令时,如果单字节指令,一次访存即可完成读取操作:如果是多字节指令,会根据第一次读取指令的操作码与寻址标志位,判断指令的后续长度,进而完成整个指令的读取,同时指令指针IP会自动进行修改,指向 ...

  6. CPU多核控速

    初学者很多对自己开发的软件使用硬件资源的时候并不注意,造成写出的东西不是很满意. 一般有两种情况: 1.写的都是同步单线程任务,不管你电脑有多少个核都不关我事 我就用你1个核所以不管怎么样都不会把CP ...

  7. java高并发核心要点|系列4|CPU内存指令重排序(Memory Reordering)

    今天,我们来学习另一个重要的概念. CPU内存指令重排序(Memory Reordering) 什么叫重排序? 重排序的背景 我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多.当CP ...

  8. CPU构架和工作原理

    -- CPU -- -- CPU 由三部分组成:时钟:控制单元:算术逻辑单元 -- -- -- 时钟:对CPU内部操作与系统其他硬件进行同步: -- -- -- 控制单元:控制机器指令的执行顺序: - ...

  9. ShardingSphere内核原理 原创 鸽子 架构漫谈 2021-01-09

    ShardingSphere内核原理 原创 鸽子 架构漫谈 2021-01-09

随机推荐

  1. Scala教程之:深入理解协变和逆变

    文章目录 函数的参数和返回值 可变类型的变异 在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型:使用-表示逆变类型:非转化类型不需要添加标记. 假如我们定义一个cla ...

  2. 日日算法:Kruskal算法

    介绍 克鲁斯卡尔(Kruskal)算法是用来求出连通图中最小生成树的算法. 连通图:指无向图中任意两点都能相通的图. 最小生成树:指联通图的所有生成树中边权重的总和最小的树(即,找出一个树,让其联通所 ...

  3. MySQL Change Data Directory

    为什么80%的码农都做不了架构师?>>>   Stop MySQL using the following command: sudo /etc/init.d/mysql stop ...

  4. vue做商品选择如何保持样式

    是这样的情况:我知道,在vue里,实现点击高亮,可以使用诸如: <div class="static" v-bind:class="{defaultClass ,a ...

  5. P3807【模板】卢卡斯定理

    题解大部分都是递归实现的,给出一种非递归的形式 话说上课老师讲的时候没给代码,然后自己些就写成了这样 对于质数\(p\)给出卢卡斯定理: \[\tbinom{n}{m}=\tbinom{n \bmod ...

  6. 1秒内通关扫雷?他创造属于自己的世界记录!Python实现自动扫雷

    五一劳动节假期,我们一起来玩扫雷吧.用Python+OpenCV实现了自动扫雷,突破世界记录,我们先来看一下效果吧. 中级 - 0.74秒 3BV/S=60.81 相信许多人很早就知道有扫雷这么一款经 ...

  7. 最长公共子串(Longest common substring)

    问题描述: 给定两个序列 X=<x1, x2, ..., xm>, Y<y1, y2, ..., yn>,求X和Y长度最长的公共子串.(子串中的字符要求连续) 这道题和最长公共 ...

  8. Java——SSM整合所需的Maven配置文件

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...

  9. springboot中json转换LocalDateTime失败的bug解决过程

    环境:jdk1.8.maven.springboot 问题:前端通过json传了一个日期:date:2019-03-01(我限制不了前端开发给到后端的日期为固定格式,有些人就是这么不配合),      ...

  10. MES系统介绍(一)

    由于本人从事的行业主要为Mes行业,所以这里准备介绍一下Mes系统的基础概念和实际运用,并且以自己做过的一个实际案例(包括代码)来详细描述自己对Mes系统的认识,帮助小白扫盲,望大神勿喷. MES系统 ...