内核中的alternative宏

Posted rtoax

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核中的alternative宏相关的知识,希望对你有一定的参考价值。

https://blog.csdn.net/Rong_Toa/article/details/124636910https://blog.csdn.net/Rong_Toa/article/details/124636910内核中的alternative宏 - 代码先锋网https://www.codeleading.com/article/26495403798/

通过 alternative() 宏,内核可以在运行时,通过判断当前 CPU 是否支持某些 feature, 实现对内核代码的在线优化(不关机、不换内核的情况下在线改写某些内核指令),以达到加速内核执行的目的,下面以 x86 为例描述其大概的实现过程。
1)alternative() 宏定义

在 arch/x86/include/asm/alternative-asm.h 定义了 ALTERNATIVE() 宏,如下所示:

.macro ALTERNATIVE oldinstr, newinstr, feature
140:
    \\oldinstr
141:
    .skip -(((144f-143f)-(141b-140b)) > 0) * ((144f-143f)-(141b-140b)),0x90
142:
    
    .pushsection .altinstructions,"a"
    altinstruction_entry 140b,143f,\\feature,142b-140b,144f-143f,142b-141b
    .popsection
    
    .pushsection .altinstr_replacement,"ax"
143:
    \\newinstr
144:
    .popsection
.endm

由于是根据 feature 改写指令,因此这里需要有 oldinstr 和 newinstr,还有 feature
ALTERNATIVE() 宏主要做了以下两项工作:
a、将一个 struct alt_instr 实例放入 vmlinux ELF 文件的 .altinstructions section,该结构体存储了指令改写所依赖的必要信息,如下所示:

struct alt_instr 
    s32 instr_offset;   /* original instruction */
    s32 repl_offset;    /* offset to replacement instruction */
    u16 cpuid;      /* cpuid bit set for replacement */
    u8  instrlen;       /* length of original instruction */
    u8  replacementlen; /* length of new instruction */
    u8  padlen;     /* length of build-time padding */
 __packed;

b、将上面的 newinstr 放入 .altinstr_replacement section

2)alternative() 宏调用

定义 alternative_input() 宏

#define alternative_input(oldinstr, newinstr, feature, input...)    \\
    asm_inline volatile (ALTERNATIVE(oldinstr, newinstr, feature)   \\
        : : "i" (0), ## input)

在 ./arch/x86/include/asm/processor.h 文件调用 alternative_input()

alternative_input(BASE_PREFETCH, "prefetchnta %P1", X86_FEATURE_XMM, "m" (*(const char *)x));

3)在线改写指令
在 arch/x86/kernel/alternative.c 文件中调用了 apply_alternatives()函数,它实现了指令的在线改写:

apply_alternatives(__alt_instructions, __alt_instructions_end);

其中,__alt_instructions 和 __alt_instructions_end 分别是 .altinstructions section 的起始和结束位置,它们的引用方式是:

extern struct alt_instr __alt_instructions[], __alt_instructions_end[];

它们定义在 arch/x86/kernel/vmlinux.lds.S,如下所示:

/*  
 * struct alt_inst entries. From the header (alternative.h):
 * "Alternative instructions for different CPU types or capabilities"
 * Think locking instructions on spinlocks.
 */
 . = ALIGN(8);
 .altinstructions : AT(ADDR(.altinstructions) - LOAD_OFFSET) 
     __alt_instructions = .;
     *(.altinstructions)
     __alt_instructions_end = .;
  

apply_alternatives() 的定义如下,大体流程是遍历 .altinstructions section 中的所有 struct alt_instr 实例,分别对其进行处理:

void __init_or_module noinline apply_alternatives(struct alt_instr *start,
                          struct alt_instr *end)

    struct alt_instr *a;
    u8 *instr, *replacement;
    u8 insn_buff[MAX_PATCH_LEN];
 
    DPRINTK("alt table %px, -> %px", start, end);
    /*   
     * The scan order should be from start to end. A later scanned
     * alternative code can overwrite previously scanned alternative code.
     * Some kernel functions (e.g. memcpy, memset, etc) use this order to
     * patch code.
     *
     * So be careful if you want to change the scan order to any other
     * order.
     */
    for (a = start; a < end; a++) 
        int insn_buff_sz = 0; 
 
        instr = (u8 *)&a->instr_offset + a->instr_offset;
        replacement = (u8 *)&a->repl_offset + a->repl_offset;
        BUG_ON(a->instrlen > sizeof(insn_buff));
        BUG_ON(a->cpuid >= (NCAPINTS + NBUGINTS) * 32); 
        if (!boot_cpu_has(a->cpuid)) 
            if (a->padlen > 1) 
                optimize_nops(a, instr);
 
            continue;
            
 
        DPRINTK("feat: %d*32+%d, old: (%pS (%px) len: %d), repl: (%px, len: %d), pad: %d",
            a->cpuid >> 5,
            a->cpuid & 0x1f,
            instr, instr, a->instrlen,
            replacement, a->replacementlen, a->padlen);
 
        DUMP_BYTES(instr, a->instrlen, "%px: old_insn: ", instr);
        DUMP_BYTES(replacement, a->replacementlen, "%px: rpl_insn: ", replacement);
 
        memcpy(insn_buff, replacement, a->replacementlen);
        insn_buff_sz = a->replacementlen;
        
        /*   
         * 0xe8 is a relative jump; fix the offset.
         *   
         * Instruction length is checked before the opcode to avoid
         * accessing uninitialized bytes for zero-length replacements.
         */  
        if (a->replacementlen == 5 && *insn_buff == 0xe8) 
            *(s32 *)(insn_buff + 1) += replacement - instr;
            DPRINTK("Fix CALL offset: 0x%x, CALL 0x%lx",
                *(s32 *)(insn_buff + 1),
                (unsigned long)instr + *(s32 *)(insn_buff + 1) + 5);
            
             
        if (a->replacementlen && is_jmp(replacement[0]))
            recompute_jump(a, instr, replacement, insn_buff);
             
        if (a->instrlen > a->replacementlen) 
            add_nops(insn_buff + a->replacementlen,
                 a->instrlen - a->replacementlen);
            insn_buff_sz += a->instrlen - a->replacementlen;
            
        DUMP_BYTES(insn_buff, insn_buff_sz, "%px: final_insn: ", instr);
             
        text_poke_early(instr, insn_buff, insn_buff_sz);
            
            

版权声明:本文为choumin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:内核中的alternative宏_choumin的博客-CSDN博客

以上是关于内核中的alternative宏的主要内容,如果未能解决你的问题,请参考以下文章

微内核是什么?宏内核是什么?一文带你了解操作系统内核架构!

微内核是什么?宏内核是什么?一文带你了解操作系统内核架构

高效学习Linux内核——从源码中的宏下手

每日一书丨微内核是什么?宏内核是什么?一文带你了解操作系统内核架构

内核中的位图介绍(DECLARE_BITMAP宏)

内核中的 offsetofcontainer_of 宏