重点

_mmapswitched 位于文件 arch/arm/kernel/head-common.S 中,会执行一些特殊的事情。

首先是一条异常语句,又是因为原地执行(XIP):尽管内核的 .text 段可以继续在ROM中执行,但无法在 .data 段中保存任何变量。所以首先需要通过将该段复制到RAM中,或者使用某些代码将其解压到RAM中的方式(比较节省芯片)来设置。

接下来将 .bss 段清零,因为Linux内核需要静态变量的初始值为零。其他的C运行时可能不需要这样做,但在运行Linux内核时,你可以可靠地认为在进入函数时静态变量的值为零。

现在机器已经切换到虚拟内存,完全可以执行C运行时环境了。我们还给所有的交叉引用打了物理内存到虚拟内存的补丁。现在一切就绪。

接下来我们将处理器ID、机器类型和ATAG或DTB指针保存下来,然后分支到符号 start_kernel()。这个符号会解析成绝对地址,它是 init/main.c 靠下的地方定义的一个C函数。它是完全通用的,任何Linux架构都会调用该函数,所以我们已经到达了C编写的通用内核代码处。

我们来看看现在在哪里。我使用了工具链中的objdump工具来反汇编内核,然后用管道输出至less命令:

arm-linux-gnueabihf-objdump -D vmlinux |less

在less中使用 /start_kernel 命令搜索 start_kernel,然后跳转到第二次出现的位置:

c088c9d8 <start_kernel>:


c088c9d8:       e92d4ff0        push    {r4, r5, r6, r7, r8, r9, sl, fp, lr}


c088c9dc:       e59f53e8        ldr     r5, [pc, #1000] ; c088cdcc


c088c9e0:       e59f03e8        ldr     r0, [pc, #1000] ; c088cdd0


c088c9e4:       e5953000        ldr     r3, [r5]


c088c9e8:       e24dd024        sub     sp, sp, #36     ; 0x24


c088c9ec:       e58d301c        str     r3, [sp, #28]


c088c9f0:       ebde25e8        bl      c0016198 <set_task_stack_end_magic>

非常好!我们在执行 0xC088C9D8 处的C代码,现在可以随便反汇编和调试内核了。每当遇到随机崩溃转储的情况,我通常会使用同样的方法,配合使用objdump和less来反汇编内核,并搜索崩溃处的符号,来查找可能出现的问题。

内核开发人员常用的另一个技巧是启用底层内核调试,并在start_kernel()处放置一条print语句,这样就能知道执行到了该点。我个人的做法如下(只需在 start_kernel() 中插入这些行):

#if defined(CONFIG_ARM) && defined(CONFIG_DEBUG_LL)
{
    extern void printascii(char *);
    printascii("start_kernel\n");
}
#endif

可见,要想让类似于此的底层调试print正常工作,需要启用 CONFIG_DEBUG_LL,然后就能在内核的标志“Linux…”打印之前看到一个标志。

Linux的内核开发人员应该都很熟悉该文件和该函数了,所以闲暇时间就可以阅读该文件中的代码。这些代码就是Linux启动的通用代码。

通用代码总是短暂的,因为一会儿就要调用setup_arch(),又要回到arch/arm中了。我们可以确定的是,初始转译表会被一个更详细的转译表替换。目前还没有用户空间的虚拟内存到物理内存的映射。不过这是另外一个话题了。

原文:https://people.kernel.org/linusw/how-the-arm32-kernel-starts