终于弄明白了Linux内核的LOCK_PREFIX的含义

本文深入探讨了x86架构中SMP配置下原子整数操作的实现细节,特别是LOCK_PREFIX宏的使用及如何针对单处理器环境进行优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 x86 架构的内核实现原子整数的时候,用到了 LOCK_PREFIX 这个宏

    static __inline__ void atomic_add(int i, atomic_t *v)

    {

     __asm__ __volatile__(

     LOCK_PREFIX "addl %1,%0"

     :"+m" (v->counter)

     :"ir" (i));

    }

    在 CONFIG_SMP 的时候:

    #define LOCK_PREFIX \

     ".section .smp_locks,\"a\"\n" \

     " .align 4\n" \

     " .long 661f\n" /* address */ \

     ".previous\n" \

     "661:\n\tlock; "

    展开后变成这样:

    .section .smp_locks,"a"

     .align 4

     .long 661f

    .previous

    661:

     lock;

    本来觉得直接加上 lock 前缀即可,前面一堆干吗用的一直不明白,终于决定要搞懂,

    翻开 as 手册,查了个明白,现逐条解释如下:

    .section .smp_locks,"a"

    下面的代码生成到 .smp_locks 段里,属性为"a", allocatable,参考 as 7.76 .section

    name

     .align 4

    四字节对齐

     .long 661f

    生成一个整数,值为下面的 661 标号的实际地址,f 表示向前引用,如果 661 标号出现

    在前面,要写 661b。

    .previous

    代码生成恢复到原来的段,也就是 .text

    661:

    数字标号是局部标号,5.3 Symbol Names

     lock;

    开始生成指令,lock 前缀

    这段代码汇编后,在 .text 段生成一条 lock 指令前缀 0xf0,在 .smp_locks 段生成

    四个字节的 lock 前缀的地址,链接的时候,所有的 .smp_locks 段合并起来,形成一个

    所有 lock 指令地址的数组,这样统计 .smp_locks 段就能知道代码里有多少个加锁的

    指令被生成,猜测是为了调试目的。

    ----

    搜索完成,果然找到了引用处:

    linux-2.6.23.12\arch\i386\kernel\module.c

    当一个内核模块被加载后调用这个

    int module_finalize(const Elf_Ehdr *hdr,

     const Elf_Shdr *sechdrs,

     struct module *me)

    {

     const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL,

     *para = NULL;

     char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;

     for (s = sechdrs; s e_shnum; s++) {

     if (!strcmp(".text", secstrings + s->sh_name))

     text = s;

     if (!strcmp(".altinstructions", secstrings + s->sh_name))

     alt = s;

     if (!strcmp(".smp_locks", secstrings + s->sh_name))

     locks= s;

     if (!strcmp(".parainstructions", secstrings + s->sh_name))

     para = s;

     }

     if (alt) {

     /* patch .altinstructions */

     void *aseg = (void *)alt->sh_addr;

     apply_alternatives(aseg, aseg + alt->sh_size);

     }

     if (locks && text) {

     void *lseg = (void *)locks->sh_addr;

     void *tseg = (void *)text->sh_addr;

     alternatives_smp_module_add(me, me->name,

     lseg, lseg + locks->sh_size,

     tseg, tseg + text->sh_size);

     }

     if (para) {

     void *pseg = (void *)para->sh_addr;

     apply_paravirt(pseg, pseg + para->sh_size);

     }

     return module_bug_finalize(hdr, sechdrs, me);

    }

    上面的代码说,如果模块有 .text 和 .smp_locks 段,就调这个来处理,做什么呢?

    void alternatives_smp_module_add(struct module *mod, char *name,

     void *locks, void *locks_end,

     void *text, void *text_end)

    {

     struct smp_alt_module *smp;

     unsigned long flags;

     if (noreplace_smp)

     return;

     if (smp_alt_once) {

     if (boot_cpu_has(X86_FEATURE_UP))

     alternatives_smp_unlock(locks, locks_end,

     text, text_end);

     return;

     }

    。。。

    }

    上面的代码说,如果是单处理器(UP),就调这个:

    static void alternatives_smp_unlock(u8 **start, u8 **end, u8 *text, u8

    *text_end)

    {

     u8 **ptr;

     char insn[1];

     if (noreplace_smp)

     return;

     add_nops(insn, 1);

     for (ptr = start; ptr text_end)

     continue;

     text_poke(*ptr, insn, 1);

     };

    }

    看到这里就能明白,这是内核配置了 smp,但是实际运行到单处理器上时,通过运行期间

    打补丁,根据 .smp_locks 里的记录,把 lock 指令前缀替换成 nop 以消除指令加锁的

    开销,这个优化真是极致了……,可能考虑很多用户直接使用的是配置支持 SMP 编译好

    的内核而特地对 x86/x64 做的这个优化。

    看来学点汇编还是有用的。

    顺手搜了一下:

    http://www.google.cn/search?hl=zh-CN&newwindow=1&q=alternative+kernel+smp&meta=&

    aq=f&oq=

    又看到这段注释……,本来仔细看一下就明白了,折腾啊。

    /*

     * Alternative inline assembly for SMP.

     *

     * The LOCK_PREFIX macro defined here replaces the LOCK and

     * LOCK_PREFIX macros used everywhere in the source tree.

     *

     * SMP alternatives use the same data structures as the other

     * alternatives and the X86_FEATURE_UP flag to indicate the case of a

     * UP system running a SMP kernel. The existing apply_alternatives()

     * works fine for patching a SMP kernel for UP.

     *

     * The SMP alternative tables can be kept after boot and contain both

     * UP and SMP versions of the instructions to allow switching back to

     * SMP at runtime, when hotplugging in a new CPU, which is especially

     * useful in virtualized environments.

     *

     * The very common lock prefix is handled as special case in a

     * separate table which is a pure address list without replacement ptr

     * and size information. That keeps the table sizes small.

     */

<think>好的,用户想了解raw_spin_lock_irq的实现,特别是如何通过Acquire屏障来保护sem->count的访问顺序。首先,我需要回忆一下Linux内核中自旋锁和内存屏障的相关知识。 根据引用[1],raw_spin_lock最终会调用arch_spin_lock,这可能涉及到体系结构相关的代码。对于x86架构来说,自旋锁的实现通常使用原子操作和内存屏障。Acquire语义确保在获取锁之后的操作不会被重排到锁获取之前,这对保护临界区中的数据访问顺序至关重要。 接下来,用户提到要证明Acquire屏障保护sem->count的访问。这里可能需要查看rw_semaphore的实现,引用[3]中的__down_read函数使用了atomic_long_inc_return_acquire,其中acquire内存屏障确保了sem->count的修改在获取锁之后对其他CPU可见。 在自旋锁的实现中,释放锁时会使用Release屏障,确保临界区内的修改在释放锁之前完成。而获取锁时的Acquire屏障则防止临界区内的操作被重排到锁获取之前。例如,在arch_spin_lock中,可能会包含这样的屏障指令,确保后续的sem->count访问不会乱序。 需要确认具体体系结构的实现,比如ARM使用ldrex/strex指令,而x86可能依赖LOCK前缀和mfence指令。另外,spin_lock_irq会禁用中断,防止中断处理程序导致死锁,但用户主要关注的是内存屏障部分。 综合来看,Acquire屏障在获取锁时插入,确保临界区的数据访问在锁之后执行,从而保护sem->count的正确性。需要引用相关的内核代码或文档来支持这一结论,可能涉及arch_spin_lock中的屏障实现和atomic操作的acquire语义。</think>根据Linux内核实现分析,`raw_spin_lock_irq`通过内存屏障和原子操作实现Acquire语义,保护`sem->count`的访问顺序。以下是关键实现逻辑的分步说明: 1. **锁获取与内存屏障** 在x86架构中,`raw_spin_lock_irq`的底层实现会调用`arch_spin_lock`[^1],其核心步骤包含: ```c LOCK_PREFIX "decl %0\n\t" //原子操作修改锁状态 jns 1f\n\t" //判断是否成功获取锁 "2:\n\t" "rep;nop\n\t" //自旋等待 "cmpl $0, %0\n\t" "jle 2b\n\t" "jmp 1b\n\t" "1:\n\t" : "+m" (lock->slock) : : "memory"); //强制插入编译器内存屏障 ``` 这里的`LOCK_PREFIX`生成原子指令前缀,`"memory"`约束强制编译器不重排内存访问顺序。 2. **Acquire语义实现** Acquire屏障通过`__acquire()`宏实现,确保临界区内的内存访问不会重排到锁获取之前: ```c #define raw_spin_lock_irq(lock) \ do { \ local_irq_disable(); \ preempt_disable(); \ arch_spin_lock(&(lock)->raw_lock); \ __acquire(lock); \ //插入Acquire内存屏障 } while (0) ``` 3. **保护sem->count的机制** 在读写信号量实现中,`down_read()`会调用`atomic_long_inc_return_acquire(&sem->count)`[^3],其中`_acquire`后缀明确使用了Acquire语义: ```c #define atomic_long_inc_return_acquire(l) \ (ATOMIC_LONG_PFX(_inc_return_acquire)(l)) ``` 该原子操作会插入硬件级内存屏障(如x86的`LOCK`前缀),保证锁获取后对`sem->count`的访问能看到最新值。 4. **与自旋锁的协同** Seqlock实现中的`spin_lock`成员[^2]也遵循相同的内存屏障规则,写锁获取时通过`spin_lock_irqsave`的Release屏障确保写操作完成后才释放锁,与读侧的Acquire屏障形成配对。 --- **访问顺序保护证明** 当线程A通过`raw_spin_lock_irq`获取锁时: - Acquire屏障禁止后续的`sem->count`访问被重排到锁获取之前 - 线程B释放锁时的Release屏障确保其对`sem->count`的修改在释放前完成 - 根据内存一致性模型,这两个屏障形成同步关系,确保线程A看到的`sem->count`是最新值[^4] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值