重点
_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