曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 4993|回复: 0
打印 上一主题 下一主题

选择内存屏障指令|alternative()

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2011-3-24 10:12:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
内核:2.6.24
平台:x86 32位

在使用 mb(), rmb() 以及 wmb() 等避免内存屏障的宏中,它们最终是被定义成 alternative() 宏的。

mb(), rmb() wmb() 定义如下:
[C++] 纯文本查看 复制代码
/*
 * Force strict CPU ordering.
 * And yes, this is required on UP too when we're talking
 * to devices.
 *
 * For now, "wmb()" doesn't actually do anything, as all
 * Intel CPU's follow what Intel calls a *Processor Order*,
 * in which all writes are seen in the program order even
 * outside the CPU.
 *
 * I expect future Intel CPU's to have a weaker ordering,
 * but I'd also expect them to finally get their act together
 * and add some real memory barriers if so.
 *
 * Some non intel clones support out of order store. wmb() ceases to be a
 * nop for these.
 */
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)
#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)

将上面的注释翻译过来大约是(仅供参考):
强制有严格的指令排序(指令不因优化而使用乱序)。
是的,当和硬件设备通讯时对于单处理器的情形也做如此要求。
现在,"wmb()" 实际上不做任何事情,所有的 Intel CPU 都遵从 Intel 的 “CPU 排序” 规则(总线监测技术,在软件中不需要考虑写入操作的内存屏障问题),以确保内存的按序写。
我期望将来 Intel 的 CPU 能有对于写操作也有“弱序(指令弱乱序)”的功能,这样 wmb() 也能够派上用场了。

alternative() 宏在 include/asm-x86/alternative_32.h 中定义如下:
/*
  * Alternative instructions for different CPU types or capabilities.
  *
  * This allows to use optimized instructions even on generic binary
  * kernels.
  *
  * length of oldinstr must be longer or equal the length of newinstr
  * It can be padded with nops as needed.
  *
  * For non barrier like inlines please define new variants
  * without volatile and memory clobber.
  */
#define alternative(oldinstr, newinstr, feature)            \
     asm volatile ("661:\n\t" oldinstr "\n662:\n"             \
               ".section .altinstructions,\"a\"\n"        \
               "  .align 4\n"                    \
              "  .long 661b\n"            /* label */        \
              "  .long 663f\n"          /* new instruction */    \
              "  .byte %c0\n"             /* feature bit */    \
              "  .byte 662b-661b\n"       /* sourcelen */    \
              "  .byte 664f-663f\n"       /* replacementlen */    \
              ".previous\n"                    \
              ".section .altinstr_replacement,\"ax\"\n"        \
               "663:\n\t" newinstr "\n664:\n"   /* replacement */\
               ".previous" :: "i" (feature) : "memory")
将上面的注释翻译过来大约是(仅供参考):
对不同的 CPU 类型或兼容性进行指令选择。
即使在通常的二进制内核文件中,这允许用来对指令进行优化。
oldinstr (是宏中的第1个参数,表示早先处理器上需要执行的指令)的长度必须大于或等于 newinstr (现代处理器上所支持的指令,如 mfence 等) 。如果有需要,那么还需要用 "nop" 来填充。
对于像内联汇编这样的内存栅请定义新的没有 volatile 和 内存clobber 的指令变体。

总的来说,alternative() 宏的意思是,内核在启动时会根据监测到的 CPU 来决定执行 alternative() 中的哪条指令,如果是 Pentium4 之前的 CPU 就执行 lock addl $0, 0(%%esp) (这条指令实际上啥都不做,只是用 lock 前缀来指令来锁住内存,从而使缓存无效,这样就在 LOCK 信号释放后,就能让 CPU 从新更新缓存),否则就执行 mfence 或 lfence 或 sfence 指令。

为了更清晰看到上面的汇编内容,我们将这些宏放在一个应用程序里,然后查看相应的汇编程序。应用程序代码为:
[C++] 纯文本查看 复制代码
#define X86_FEATURE_XMM2 22 

#define alternative(oldinstr, newinstr, feature)            \
    asm volatile ("661:\n\t" oldinstr "\n662:\n"             \
              ".section .altinstructions,\"a\"\n"        \
              "  .align 4\n"                    \
              "  .long 661b\n"            /* label */        \
              "  .long 663f\n"          /* new instruction */    \
              "  .byte %c0\n"             /* feature bit */    \
              "  .byte 662b-661b\n"       /* sourcelen */    \
              "  .byte 664f-663f\n"       /* replacementlen */    \
              ".previous\n"                    \
              ".section .altinstr_replacement,\"ax\"\n"        \
              "663:\n\t" newinstr "\n664:\n"   /* replacement */\
              ".previous" :: "i" (feature) : "memory")

#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)

#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)

void main()
{
    int i;
    i = 3;
    mb();
    i = i * 2;
    
}

相应的汇编代码(gcc -S alt.c 产生相应的汇编代码):
$ cat alt.s
    .file    "alt.c"
    .text
.globl main
    .type    main, @function
main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    $3, -4(%ebp)
#APP
# 26 "alt.c" 1
    661:
    lock; addl $0,0(%esp)
662:
.section .altinstructions,"a"
  .align 4
  .long 661b
  .long 663f
  .byte 22
  .byte 662b-661b
  .byte 664f-663f
.previous
.section .altinstr_replacement,"ax"
663:
    mfence
664:
.previous
# 0 "" 2
#NO_APP
    sall    -4(%ebp)
    leave
    ret
    .size    main, .-main
    .ident    "GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)"
    .section    .note.GNU-stack,"",@progbits
原来 alternative() 宏定义中的 %c0 表示的就是上面 X86_FEATURE_XMM2 这个立即数,%0 前面要用 c 这个前缀表示产生最后的结果就是去掉 $ 这个语法符号(不用则生成 $22)。

对于 .previous 指令,可以用 objdump 来查看一下程序中段的安排:
[beyes@SLinux assembly]$ objdump -h alt.o

alt.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000017  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000000  00000000  00000000  0000004c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  0000004c  2**2
                  ALLOC
  3 .altinstructions 0000000b  00000000  00000000  0000004c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  4 .altinstr_replacement 00000003  00000000  00000000  00000057  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  5 .comment      0000002e  00000000  00000000  0000005a  2**0
                  CONTENTS, READONLY
  6 .note.GNU-stack 00000000  00000000  00000000  00000088  2**0
                  CONTENTS, READONLY
关于 .previous 伪指令可以参考:http://www.groad.net/bbs/read.php?tid-3150.html

前面说过,alternative() 宏是在内核启动时根据监测到的 CPU 类型来检测是否使用旧指令还是新指令的。也就是说,以后在你写的驱动程序里使用了这个宏,使用新的还是旧的指令是在内核启动时就决定好的,而不是在 alternative() 宏的本身里确定的。那么这个检测过程发生在哪里呢?接着看 apply_alternatives() 这个函数的实现:
[C++] 纯文本查看 复制代码
/* Replace instructions with better alternatives for this CPU type.
   This runs before SMP is initialized to avoid SMP problems with
   self modifying code. This implies that assymetric systems where
   APs have less capabilities than the boot processor are not handled.
   Tough. Make sure you disable such features by hand. */

void apply_alternatives(struct alt_instr *start, struct alt_instr *end)
{
    struct alt_instr *a;
    char insnbuf[MAX_PATCH_LEN];

    DPRINTK("%s: alt table %p -> %p\n", __FUNCTION__, start, end);
    for (a = start; a < end; a++) {
        u8 *instr = a->instr;
        BUG_ON(a->replacementlen > a->instrlen);
        BUG_ON(a->instrlen > sizeof(insnbuf));
        if (!boot_cpu_has(a->cpuid))
            continue;
#ifdef CONFIG_X86_64
        /* vsyscall code is not mapped yet. resolve it manually. */
        if (instr >= (u8 *)VSYSCALL_START && instr < (u8*)VSYSCALL_END) {
            instr = __va(instr - (u8*)VSYSCALL_START + (u8*)__pa_symbol(&__vsyscall_0));
            DPRINTK("%s: vsyscall fixup: %p => %p\n",
                __FUNCTION__, a->instr, instr);
        }
#endif
        memcpy(insnbuf, a->replacement, a->replacementlen);
        add_nops(insnbuf + a->replacementlen,
             a->instrlen - a->replacementlen);
        text_poke(instr, insnbuf, a->instrlen);
    }
}

在上面的注释中说道:
对不同的 CPU 类型进行更好的指令替代。本函数在 SMP 初始化之前运行,这是为了避免 SMP 中的一些自修改代码产生的一些问题。

先看 struct alt_instr 结构体的类型:
[C++] 纯文本查看 复制代码
struct alt_instr {
    u8 *instr;         /* original instruction */
    u8 *replacement;
    u8  cpuid;        /* cpuid bit set for replacement */
    u8  instrlen;        /* length of original instruction */
    u8  replacementlen;     /* length of new instruction, <= instrlen */
    u8  pad;
};

上面结构体的定义貌似有些眼熟,比较一下,原来就是 alternative() 宏中的下面的这些汇编:
    "  .long 661b\n"            /* label */        \
              "  .long 663f\n"          /* new instruction */    \
              "  .byte %c0\n"             /* feature bit */    \
              "  .byte 662b-661b\n"       /* sourcelen */    \
              "  .byte 664f-663f\n"       /* replacementlen */    \

在此不对 apply_alternatives() 进一步展开分析,其内容大概是:
通过 boot_cpu_has(a->cpuid) 来判断 CPU 类型来选择相应的指令(或新或旧)。如果是老的 CPU 就直接 u8 *instr = a->instr; 如果是新的 CPU,那么要使用
[C++] 纯文本查看 复制代码
memcpy(insnbuf, a->replacement, a->replacementlen);
        add_nops(insnbuf + a->replacementlen,
             a->instrlen - a->replacementlen);
        text_poke(instr, insnbuf, a->instrlen);
    }

三个函数进行指令的替换。

由于本人水平十分有限,本文仅供参考,如果有不对的地方还望指出,不胜感激。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-17 02:03 , Processed in 0.064261 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表