欢迎转载,转载时请保留作者信息,谢谢。
邮箱:
博客园地址:
Csdn博客地址:
墨迹这么久,总算开始内核源代码分析了。
阶段1
阶段1大部分为汇编, 以程序启动到执行到start_kernel函数为第一阶段。 大概流程如下:
变量与文件的对应关系。
THUMB: src\arch\arm\include\asm\unified.h
head.S的很多定义在src\arch\arm\kernel\head-common.S
struct proc_info_list :src\arch\arm\include\asm\procinfo.h
PROC_INFO_SZ : build\include\generated\asm-offsets.h:
proc.info.init : src\arch\arm\mm\proc-arm920.S
PAGE_OFFSET:src\arch\arm\include\asm\memory.h = CONFIG_PAGE_OFFSET = 0xC0000000
寻找入口
首先从从程序入口处开始分析,linux内核下文件多不胜数,那么从哪里开始找到程序入口地址呢?
源代码写好后,肯定是先使用编译工具编译代码,而编译工具也有相关的文件的,从这个思路,找到vmlinux.lds(src\arch\arm\kernel\vmlinux.lds),该文件指导arm-linux-ld如何工作。该文件开始有
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
…………..
. = 0xC0000000 + 0x00008000;
可见程序入口为段 stext,代码开始地址0xC0000000 + 0x00008000,即3G + 0x8000,使用source insight的search→search files,找到head.S (src\arch\arm\kernel):ENTRY(stext),点击定位到该处。往下看,有
mrc p15, 0, r9, c0, c0 @ get processor id,协处理器操作,详细见ARM920T_TRM1_S.pdf
bl __lookup_processor_type @ r5=procinfo r9=cpuid
上述r9的内容为0x41009200,见下图,详细见ARM920T_TRM1_S.pdf。
入口找到了,接下来就好办了,只要有点arm汇编基础,读懂代码是没有问题的,本来想自己慢慢写的,但是发现网上别人已经写的好了,就不重新写了。详细见:Linux内核启动分析之 __lookup_processor_type函数: 。
adr获取的为什么是实际运行的物理地址
__lookup_processor_type: adr r3, __lookup_processor_type_data @ 假设在地址16
mov r0, #1 @ 假设在地址20
__lookup_processor_type_data: .long @ 假设在地址24
如上,adr是将当前PC值加一个偏移,得到目标地址,假如程序运行到上述第一条语句时:PC = 16, 因为 __lookup_processor_type_data与其相差8个字节的偏移,所以得到r3 = PC + 8 = 24; 而因为此时程序是运行的,PC代表的就是物理地址,所以adr获取的也就是物理地址了(严格来说这是不对的,因为PC是预取的,但是为了说明问题,不考虑预取功能,不然越说越复杂了)
linux mmu未开启时是如何将虚拟地址转换为物理地址的
此处对linux的adr计算真实物理地址讨论下,挺有意思的。Arm本来有三种地址,而此时未开启mmu,有MVA = VA(虚拟地址) = PA(物理地址).
要讨论这,就不得不提下内存模型。内存有两个基本属性,地址 + 内容。
围绕地址+内容,衍生了很多东西。包括c中的函数名,经汇编后对应的是汇编语言中的标号,即函数名是地址,这就是为什么存在函数指针一说, 如下表:
编译链接后各变量有其固定的地址,即虚拟地址,运行时变量也有自己的地址,两变量不一定一致。见下图,图中的地址是假定的,实际不一定如此:
上图两个内存里的内容必然是一致的,唯一的差别就在于地址。当运行到adr r3, __lookup_processor_type_data时,获得的是运行的地址r3 = 0x8c, 而ldmia r3, {r4 - r6} 是将0x8c, 0x90, 0x94地址里的内容加载到r4,r5, r6去,因此等效于将0xc000808c, 0x c0008090, 0xc0008094地址里的内容加载到r4,r5, r6去, 0xc000808c地址的内存里面的内容就是他的地址,为0xc000808c。
因此上面r3= 0x8c为实际的物理地址,而0x8c对应的虚拟地址为0xc000808c。
根据PA1 – VA1 = PA2 – VA2 = 0x8c - 0xc000808c, 即PA – VA值确定了,编译后VA的值也确定了,所以可以反推所有的运行时刻代码所在的物理地址。
为何 ARM(add pc, r10,#PROCINFO_INITFUNC):是执行函数 __arm920_setup
Asm-offsets.c (src\arch\arm\kernel):130 中有:
DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
src/arch/arm/include/asm/procinfo.h中有__cpu_flush定义:
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
src/arch/arm/mm/pro-arm920.S中定义了struct proc_info_list __arm920_proc_info, 其段在.proc.info.init :
.section ".proc.info.init", #alloc, #execinstr
.type __arm920_proc_info,#object
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm920_setup @ __cpu_flush
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
.long cpu_arm920_name
.long arm920_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
在src\arch\arm\kernel\vmlinux.lds中有:
.init.proc.info : {
. = ALIGN(4); __proc_info_begin = .; *(.proc.info.init) __proc_info_end = .; }
.type __arm920_setup, #function
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
.size __arm920_setup, . - __arm920_setup
综上: ARM(add pc, r10,#PROCINFO_INITFUNC):是 执行函数 __arm920_setup, 该函数是对mmu作一些初始化。
__create_page_tables:虚拟地址映射页表是如何创建的
此处省略,在文章 有关于mmu比较详细的分析。
进入阶段2:
阶段2
src\init\main.c: asmlinkage void __init start_kernel(void)
start_kernel: 第二阶段启动入口,开始熟悉的c语言之旅。
src\include\linux\init.h: __init
第二阶段内容太多,不在这里具体写,后面结合具体例子再说,比如说明sysfs文件系统时,会有
/sys目录建立: start_kernel→rest_init→kernel_init→kernel_init_freeable→do_basic_setup→ driver_init的流程说明。
从start_kernel到 shell的流程:start_kernel→rest_init→kernel_init→run_init_process