阅读 MIPS 启动部分的代码,从内核入口 kernel_entrystart_kernel 的第一个子函数 lock_kernel,很多细节我并没理解,所以不进行展开。基于 Linux kernel release 2.6.11.12。由于 Markdown 代码块语法高亮不支持汇编,此处统一用 C 标注。

函数

kernel_entry

arch/mips/kernel/head.S

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
NESTED(kernel_entry, 16, sp) # kernel entry point
    setup_c0_status_pri

#ifdef CONFIG_SGI_IP27
    GET_NASID_ASM t1
    move t2, t1 # text and data are here // t2 = t1
    MAPPED_KERNEL_SETUP_TLB
#endif /* IP27 */

    ARC64_TWIDDLE_PC

    PTR_LA t0, __bss_start # clear .bss // t0 = __bss_start
    LONG_S zero, (t0) // *(t0) = 0
    PTR_LA t1, __bss_stop - LONGSIZE // t1 = __bss_stop - LONGSIZE
1:
    PTR_ADDIU t0, LONGSIZE // t0 += LONGSIZE
    LONG_S zero, (t0) // *(t0) = 0
    bne t0, t1, 1b // if (t0 != t1) goto 1b

    LONG_S a0, fw_arg0 # firmware arguments
    LONG_S a1, fw_arg1
    LONG_S a2, fw_arg2
    LONG_S a3, fw_arg3

    PTR_LA $28, init_thread_union // gp = init_thread_union
    PTR_ADDIU sp, $28, _THREAD_SIZE - 32 // sp = gp + _THREAD_SIZE - 32
    set_saved_sp sp, t0, t1
    PTR_SUBU sp, 4 * SZREG # init stack pointer // sp = 4 * SZREG

    j start_kernel
    END(kernel_entry)

init_thread_union 定义于 arch/mips/kernel/init_task.c

start_kernel

init/main.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/*
 * Activate the first processor.
 */

asmlinkage void __init start_kernel(void)
{
    char * command_line;
    extern struct kernel_param __start___param[], __stop___param[];
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
    lock_kernel();
    ...

lock_kernel

include/linux/smp_lock.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#ifdef CONFIG_LOCK_KERNEL

extern void __lockfunc lock_kernel(void) __acquires(kernel_lock);
extern void __lockfunc unlock_kernel(void) __releases(kernel_lock);

#else

#define lock_kernel() do { } while(0)
#define unlock_kernel() do { } while(0)

#endif /* CONFIG_LOCK_KERNEL */

lock_kernelunlock_kernel 的具体实现在 lib/kernel_lock.c 中。

汇编器指令

主要参考文档 Using as

.set noreorder

设置汇编器不移动指令来填充延迟槽。

.set push.set pop

Directives to save and restore options

可以先用 .set push 保存汇编器配置,然后执行一些修改 (比如配置 .set noreorder),最后用 .set pop 恢复之前的汇编器配置。

.comm

.comm symbol , length

在大部分情况下,可以简单理解为定义一个变量,变量名为 symbol ,占 length 字节,可选的第三个参数代表对齐字节数。

.align [abs-expr[, abs-expr[, abs-expr]]]

.align [abs-expr[, abs-expr[, abs-expr]]]

通常只用到第一个表达式,如 .align 8 代表 8 字节对齐。

.type

.type

在内核中,基本只用到了 .type <name>,@<type> 的形式,用于表明函数类型。

.globl

.globl

声明变量 (供链接器使用)。

.ent

声明变量 (供调试器使用)。

.frame

.frame sp, size, ra,提供给调试器关于栈帧的信息,sp 代表指向栈帧的地址、寄存器,size 代表栈帧的大小,ra 代表保存返回地址的寄存器。

.section

.section name

.previous

.previous

.macro.endm

.macro

EXPORT

include/asm-mips/asm.h

1
2
3
4
5
6
/*
* EXPORT - export definition of symbol
*/
#define EXPORT(symbol)                  \
        .globl  symbol;                         \
symbol:

声明一个全局变量。

__INIT__FINIT

include/linux/init.h

1
2
#define __INIT .section ".init.text","ax"
#define __FINIT .previous

.section.previous 见汇编器指令章节的相关内容。

32 位和 64 位的兼容

32 位和 64 位情况下的数据长度和指令不同,具体见 include/asm-mips/asm.h,该文件中的部分宏定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
 * Size of a register
 */
#ifdef __mips64
#define SZREG 8
#else
#define SZREG 4
#endif

/*
 * How to add/sub/load/store/shift C long variables.
 */
#if (_MIPS_SZLONG == 32)
#define LONG_S sw
#endif

#if (_MIPS_SZLONG == 64)
#define LONG_S sd
#endif

/*
 * How to add/sub/load/store/shift pointers.
 */
#if (_MIPS_SZPTR == 32)
#define PTR_ADDIU addiu
#define PTR_SUBU subu
#define PTR_LA la
#endif

#if (_MIPS_SZPTR == 64)
#define PTR_ADDU daddu
#define PTR_SUBU dsubu
#define PTR_LA dla
#endif

NESTED

include/asm-mips/asm.h

1
2
3
4
5
6
7
8
9
/*
* NESTED - declare nested routine entry point
*/
#define NESTED(symbol, framesize, rpc)                  \
        .globl  symbol;                         \
        .align  2;                              \
        .type   symbol,@function;               \
        .ent    symbol,0;                       \
symbol:     .frame  sp, framesize, rpc

定义一个函数的头部。

setup_c0_status_pri

arch/mips/kernel/head.S

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    /*
     * For the moment disable interrupts, mark the kernel mode and
     * set ST0_KX so that the CPU does not spit fire when using
     * 64-bit addresses.  A full initialization of the CPU's status
     * register is done later in per_cpu_trap_init().
     */
    .macro setup_c0_status set clr
    .set push
    mfc0 t0, CP0_STATUS // t0 = $12
    or t0, ST0_CU0|\set|0x1f|\clr // t0 |= ST0_CU0 | set | 0x1f | clr
    xor t0, 0x1f|\clr // t0 ^= 0x1f | clr
    mtc0 t0, CP0_STATUS // $12 = t0
    .set noreorder
    sll zero,3 # ehb
    .set pop
    .endm

    .macro setup_c0_status_pri
#ifdef CONFIG_MIPS64
    setup_c0_status ST0_KX 0 // setup_c0_status 0x00000080 0,见宏章节的 CP0 寄存器
#else
    setup_c0_status 0 0 // setup_c0_status 0 0
#endif
    .endm

如果配置了 CONFIG_MIPS64setup_c0_status 会把 CP0 的 $12 设置为 0x80;反之则设置为 0。具体作用注释已经给出,建议参考 The MIPS64 and microMIPS64 Privileged Resource Architecture v6.03Table 9.49

CP0 寄存器

include/asm-mips/mipsregs.h

1
2
3
4
5
6
7
8
9
/*
 * Coprocessor 0 register names
 */
#define CP0_STATUS $12

/*
 * Bitfields in the R4xx0 cp0 status register
 */
#define ST0_KX 0x00000080

GET_NASID_ASM

arch/mips/kernel/head.S

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#ifdef CONFIG_SGI_IP27
    /*
     * outputs the local nasid into res.  IP27 stuff.
     */
    .macro GET_NASID_ASM res
    dli \res, LOCAL_HUB_ADDR(NI_STATUS_REV_ID)
    ld \res, (\res)
    and \res, NSRI_NODEID_MASK
    dsrl \res, NSRI_NODEID_SHFT
    .endm
#endif /* CONFIG_SGI_IP27 */

MAPPED_KERNEL_SETUP_TLB

定义于 arch/mips/kernel/head.S

ARC64_TWIDDLE_PC

arch/mips/kernel/head.S

1
2
3
4
5
6
7
8
9
    .macro ARC64_TWIDDLE_PC
#if defined(CONFIG_ARC64) || defined(CONFIG_MAPPED_KERNEL)
    /* We get launched at a XKPHYS address but the kernel is linked to
       run at a KSEG0 address, so jump there.  */
    PTR_LA t0, \@f
    jr t0
\@:
#endif
    .endm

_THREAD_SIZE

定义于 arch/mips/kernel/offset.c ,该文件会根据 arch/mips/Makefile 中的规则,生成 asm/offset.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Generate <asm/offset.h>
#
# The default rule is suffering from funny problems on MIPS so we using our
# own ...
#
# ---------------------------------------------------------------------------

define filechk_gen-asm-offset.h
    (set -e; \
     echo "#ifndef _ASM_OFFSET_H"; \
     echo "#define _ASM_OFFSET_H"; \
     echo "/*"; \
     echo " * DO NOT MODIFY."; \
     echo " *"; \
     echo " * This file was generated by arch/$(ARCH)/Makefile"; \
     echo " *"; \
     echo " */"; \
     echo ""; \
     sed -ne "/^@@@/s///p"; \
     echo "#endif /* _ASM_OFFSET_H */" )
endef

prepare: include/asm-$(ARCH)/offset.h

arch/$(ARCH)/kernel/offset.s: include/asm include/linux/version.h \
                   include/config/MARKER

include/asm-$(ARCH)/offset.h: arch/$(ARCH)/kernel/offset.s
    $(call filechk,gen-asm-offset.h)

Makefile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# filechk is used to check if the content of a generated file is updated.
# Sample usage:
# define filechk_sample
#   echo $KERNELRELEASE
# endef
# version.h : Makefile
#   $(call filechk,sample)
# The rule defined shall write to stdout the content of the new file.
# The existing file will be compared with the new one.
# - If no file exist it is created
# - If the content differ the new file is used
# - If they are equal no change, and no timestamp update

define filechk
    @set -e; \
    echo '  CHK     $@'; \
    mkdir -p $(dir $@); \
    $(filechk_$(1)) < $< > $@.tmp; \
    if [ -r $@ ] && cmp -s $@ $@.tmp; then \
        rm -f $@.tmp; \
    else \
        echo '  UPD     $@'; \
        mv -f $@.tmp $@; \
    fi
endef

查看文件中 _THREAD_SIZE 相关代码,最终该宏相当于 #define _THREAD_SIZE THREAD_SIZE

THREAD_SIZE

include/asm-mips/thread_info.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/* thread information allocation */
#if defined(CONFIG_PAGE_SIZE_4KB) && defined(CONFIG_MIPS32)
#define THREAD_SIZE_ORDER (1)
#endif
#if defined(CONFIG_PAGE_SIZE_4KB) && defined(CONFIG_MIPS64)
#define THREAD_SIZE_ORDER (2)
#endif
#ifdef CONFIG_PAGE_SIZE_8KB
#define THREAD_SIZE_ORDER (1)
#endif
#ifdef CONFIG_PAGE_SIZE_16KB
#define THREAD_SIZE_ORDER (0)
#endif
#ifdef CONFIG_PAGE_SIZE_64KB
#define THREAD_SIZE_ORDER (0)
#endif

#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

include/asm-mips/page.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/*
 * PAGE_SHIFT determines the page size
 */
#ifdef CONFIG_PAGE_SIZE_4KB
#define PAGE_SHIFT 12
#endif
#ifdef CONFIG_PAGE_SIZE_8KB
#define PAGE_SHIFT 13
#endif
#ifdef CONFIG_PAGE_SIZE_16KB
#define PAGE_SHIFT 14
#endif
#ifdef CONFIG_PAGE_SIZE_64KB
#define PAGE_SHIFT 16
#endif
#define PAGE_SIZE (1UL << PAGE_SHIFT)

set_saved_sp

定义于 include/asm-mips/stackframe.h,见后续笔记。

END

1
2
3
4
5
6
/*
* END - mark end of function
*/
#define END(function)                                   \
        .end    function;               \
        .size   function,.-function

定义一个函数的尾部。

CP0_STATUS

include/asm-mips/mipsregs.h

1
#define CP0_STATUS $12

CONFIG_SGI_IP27

arch/mips/Makefile:

1
2
3
4
5
6
7
#
# SGI-IP27 (Origin200/2000)
#
# Set the load address to >= 0xc000000000300000 if you want to leave space for
# symmon, 0xc00000000001c000 for production kernels.  Note that the value must
# be 16kb aligned or the handling of the current variable will break.
#

asmlinkage

include/linux/linkage.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <asm/linkage.h>

#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif

#ifndef asmlinkage
#define asmlinkage CPP_ASMLINKAGE
#endif

include/asm-mips/linkage.h

1
/* Nothing to see here... */

在 MIPS 下,asmlinkage 只是为了兼容 C++

__init

include/linux/init.h

1
#define __init __attribute__ ((__section__ (".init.text")))

把函数放到 .init.text 段。

__acquires__releases

include/linux/compiler.h

1
2
# define __acquires(x) __attribute__((context(0,1)))
# define __releases(x) __attribute__((context(1,0)))

给 Sparse 做代码静态检查,用于保证 __acquires__releases 成对使用。Sparse 的相关内容可以参考 The Linux Kernel documentation

__lockfunc

include/linux/spinlock.h

1
#define __lockfunc fastcall __attribute__((section(".spinlock.text")))

include/linux/linkage.h

1
2
3
4
#ifndef FASTCALL
#define FASTCALL(x) x
#define fastcall
#endif

include/asm-mips/linkage.h

1
/* Nothing to see here... */

在 MIPS 下,fastcall 为空,__lockfunc 只是为了把函数放到 .spinlock.text 段。