1、linuxmips启动分析linux-mips启动分析 (1)系统加电起动后,MIPS 处理器默认的程序入口是0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是 0x1FC00000,即CPU从0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH的位置,Bootloader将 Linux 内核映像拷贝到 RAM 中某个空闲地址处,然后一般有个内存移动操作,目的地址在 arch/mips/Makefile 内指定: load-$(CONFIG_MIPS_PB1550) += 0xFFFFFFFF80100000,则最终bootloader定会将内
2、核移到物理地址 0x00100000 处。上面Makefile 里指定的的 load 地址,最后会被编译系统写入到 arch/mips/kernel/vmlinux.lds 中:OUTPUT_ARCH(mips)ENTRY(kernel_entry)jiffies = jiffies_64;SECTIONS. = 0xFFFFFFFF80100000;/* read-only */_text = .; /* Text and read-only data */.text : *(.text).这个文件最终会以参数 -Xlinker -script -Xlinker vmlinux.lds 的形
3、式传给 gcc,并最终传给链接器 ld 来控制其行为。ld 会将 .text 节的地址链接到 0xFFFFFFFF80100000 处。关于内核 ELF 文件的入口地址(Entry point),即 bootloader 移动完内核后,直接跳转到的地址,由ld 写入 ELF的头中,其会依次用下面的方法尝试设置入口点,当遇到成功时则停止:a. 命令行选项 -e entryb. 脚本中的 ENTRY(symbol)c. 如果有定义 start 符号,则使用start符号(symbol)d. 如果存在 .text 节,则使用第一个字节的地址。e. 地址0注意到上面的 ld script 中,用 EN
4、TRY 宏设置了内核的 entry point 是 kernel_entry,因此内核取得控制权后执行的第一条指令是在 kernel_entry 处。linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s文件开始的。而此处正是内核入口函数kernel_entry(),该函数定义在 /arch/mips/kernel/head.s文件里。kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零,最后跳转到 /init/
5、main.c 中的 start_kernel()初始化硬件平台相关的代码。NESTED(kernel_entry, 16, sp) # kernel entry point声明函数 kernel_entry,函数的堆栈为 16 byte,返回地址保存在 $sp 寄存器中。声明函数入口#define NESTED(symbol, framesize, rpc) .globl symbol; .align 2; .type symbol,function; .ent symbol,0; symbol: .frame sp, framesize, rpc汇编伪指令 frame 用来声明堆栈布局。它有
6、三个参数: 1)第一个参数 framereg:声明用于访问局部堆栈的寄存器,一般为 $sp。 2)第二个参数 framesize:申明该函数已分配堆栈的大小,应该符合 $sp framesize 原来的 $sp。 3)第三个参数 returnreg:这个寄存器用来保存返回地址。 kernel_entry_setup # cpu specific setup这个宏一般为空的,在 include/asm-mips/mach-generic/kernel-entry-init.h 文件中定义。某些MIPS CPU需要额外的设置一些控制寄存器,和具体的平台相关,一般为空宏;某些多核MIPS,启动时所有
7、的core的入口一起指向 kernel_entry,然后在该宏里分叉,boot core 继续往下,其它的则不停的判断循环,直到boot core 唤醒之 setup_c0_status_pri设置 cp0_status 寄存器 .macro setup_c0_status_pri#ifdef CONFIG_64BIT setup_c0_status ST0_KX 0#else setup_c0_status 0 0#endif .endm ARC64_TWIDDLE_PC除非 CONFIG_ARC64,否则为空操作#ifdef CONFIG_MIPS_MT_SMTC mtc0 zero, C
8、P0_TCCONTEXT_bss_start mfc0 t0, CP0_STATUS ori t0, t0, 0xff1f xori t0, t0, 0x001e mtc0 t0, CP0_STATUS#endif /* CONFIG_MIPS_MT_SMTC */宏定义 CONFIG_MIPS_MT_SMTC 是使用多核的 SMTC Linux 时定义的。一般情况下不考虑。MIPS已经开发出 SMP Linux的改进版,叫做SMTC(线程上下文对称多处理) Linux。SMTC Linux能理解轻量级 TC 的概念,并能因此减少某些与SMP Linux相关的开销。 PTR_LA t0, _b
9、ss_start # clear .bss LONG_S zero, (t0) PTR_LA t1, _bss_stop - LONGSIZE1: PTR_ADDIU t0, LONGSIZE LONG_S zero, (t0) bne t0, t1, 1b清除 BSS 段,清 0。变量 _bss_start 和 _bss_stop 在连接文件arch/mips/kernel/vmlinux.lds 中定义。 LONG_S a0, fw_arg0 # firmware arguments LONG_S a1, fw_arg1 LONG_S a2, fw_arg2 LONG_S a3, fw_a
10、rg3把 bootloader 传递给内核的启动参数保存在 fw_arg0,fw_arg1,fw_arg2,fw_arg3 变量中。变量 fw_arg0 为内核参数的个数,其余分别为字符串指针,为 XXXX 的格式。 MTC0 zero, CP0_CONTEXT # clear context register清除 CP0 的 context register,这个寄存器用来保存页表的起始地址。 PTR_LA $28, init_thread_union初始化 $gp 寄存器,这个寄存器的地址指向一个 union,THREAD_SIZE 大小,最低处是一个thread_info 结构 PTR_
11、LI sp, _THREAD_SIZE - 32 PTR_ADDU sp, $28设置 $sp 寄存器,堆栈指针。 $sp = (init_thread_union 的地址) _THREAD_SIZE - 32的得出 $sp 指向这个 union 结构的结尾地址 32 字节地址。 set_saved_sp sp, t0, t1把 这个 CPU 核的堆栈地址 $sp 保存到 kernelspNR_CPUS 数组。 如果定义了 CONFIG_SMP 宏,即多 CPU 核。 .macro set_saved_sp stackp temp temp2#ifdef CONFIG_MIPS_MT_SMTC
12、 mfc0 temp, CP0_TCBIND#else MFC0 temp, CP0_CONTEXT#endif LONG_SRL temp, PTEBASE_SHIFT LONG_S stackp, kernelsp(temp) .endm如果没有定义 CONFIG_SMP 宏,单 CPU 核。 .macro set_saved_sp stackp temp temp2 LONG_S stackp, kernelsp .endm变量 kernelsp 的定义,在 arch/mips/kernel/setup.c 文件中。unsigned long kernelspNR_CPUS;把 这个 C
13、PU 核的堆栈地址 $sp 保存到 kernelspNR_CPUS 数组。 PTR_SUBU sp, 4 * SZREG # init stack pointer j start_kernel END(kernel_entry)最后跳转到 /arch/mips/kernel/main.c 中的 start_kernel()初始化硬件平台相关的代码。这个 init_thread_union 变量在 arch/mips/kernel/init_task.c 文件中定义。union thread_union init_thread_union _attribute_(_section_(.data.
14、init_task), _aligned_(THREAD_SIZE) = INIT_THREAD_INFO(init_task) ;问题: 1)这个 init_thread_union 结构体指针是怎么初始化的?linux-mips启动分析(2) linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s文件开始的。而此处正是内核入口函数kernel_entry(),该函数定义在 /arch/mips/kernel/head.s文件里。kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备, 接着用一段循
15、环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零, 最后跳转到 /arch/mips/kernel/main.c 中的 start_kernel()初始化硬件平台相关的代码。下面讲述 start_kernel() 函数。asmlinkage void _init start_kernel(void) char * command_line; extern struct kernel_param _start_param, _stop_param; 定义了核的参数数据结构 smp_setup_processor_id(); 设置 SMP 多核的 CPU 核的 ID 号,
16、单核不进行任何操作,我们不关心。 unwind_init(); 在 MIPS 体系结构中,这个函数是个空函数(可能调用setup_arch,配置核的相关函数) lockdep_init(); 初始化核依赖关系哈希表。 local_irq_disable(); 关闭当前 CPU 核的中断 early_boot_irqs_off(); 通过一个静态全局变量 early_boot_irqs_enabled 来帮助我们调试代码, 通过这个标记可以帮助我们知道是否在“early bootup code”, 也可以通过这个标志警告是否有无效的中断打开。 和 early_boot_irqs_on() 函数配
17、置使用,参考下面。 early_init_irq_lock_class(); 每一个中断都有一个 IRQ 描述符 ( struct irq_desc )来进行描述。 这个函数的主要作用是设置所有的 IRQ 描述符 ( struct irq_desc )的锁是统一的锁, 还是每一个 IRQ 描述符 ( struct irq_desc )都有一个小锁。 参考linux-mips启动分析(2-1)。 lock_kernel(); 获取大内核锁,这种大内核锁锁定整个内核。 tick_init(); 如果没有定义 CONFIG_GENERIC_CLOCKEVENTS 宏定义,则这个函数为空函数, 如果定
18、义了这个宏,这执行初始化 tick 控制功能,注册 clockevents 的框架。 boot_cpu_init(); 对于 CPU 核的系统来说,设置第一个 CPU 核为活跃 CPU 核。 对于单 CPU 核系统来说,设置 CPU 核为活跃 CPU 核。 参考linux-mips启动分析(2-1)。 page_address_init(); 当定义了 CONFIG_HIGHMEM 宏,并且没有定义 WANT_PAGE_VIRTUAL 宏时,非空函数。 其他情况为空函数。 参考linux-mips启动分析(2-1)。 printk(KERN_NOTICE); printk(linux_bann
19、er); 输出打印版本信息。 setup_arch(&command_line); 每种体系结构都有自己的 setup_arch() 函数,这些是体系结构相关的。 如何确定编译那个体系结构的 setup_arch() 函数呢? 主要由 linux 源码树顶层 Makefile 中 ARCH 变量来决定的。 例如: MIPS 体系结构的。 SUBARCH := mips ARCH ?= $(SUBARCH) setup_command_line(command_line); 保存未改变的 comand_line 到字符数组 static_command_line 中。 保存 boot_comma
20、nd_line 到字符数组 saved_command_line 中。 参考linux-mips启动分析(2-1)。 unwind_setup(); 空函数。 setup_per_cpu_areas(); 如果没有定义 CONFIG_SMP 宏,则这个函数为空函数。 如果定义了 CONFIG_SMP 宏, 则这个 setup_per_cpu_areas() 函数给每个CPU分配内存,并拷贝 .data.percpu 段的数据。 参考linux-mips启动分析(2-1)。 如果没有定义 CONFIG_SMP 宏,则这个函数为空函数。 如果定义了 CONFIG_SMP 宏,这个函数 smp_pr
21、epare_boot_cpu(); sched_init(); 核心进程调度器初始化,调度器的初始化优先于任何中断的建立(包括 timer 中断)。 并且初始化进程 0 ,即 idle 进程,但是并没有设置 idle 进程的 NEED_RESCHED 标志, 以完成内核剩余的启动部分。 preempt_disable(); 进制内核的抢占。使当前进程的 struct thread_info 结构 preempt_count 成员的值增加 1。 参考linux-mips启动分析(2-1)。 建立各个节点的管理区的 zonelist,便于分配内存的 fallback 使用。 这个链表的作用: 这个
22、链表是为了在一个分配不能够满足时可以考察下一个管理区来设置了。 在考察结束时,分配将从 ZONE_HIGHMEM 回退到 ZONE_NORMAL, 在分配时从 ZONE_NORMAL 退回到 ZONE_DMA 就不会回退了。 build_all_zonelists(); 参考linux-mips启动分析(2-1)。 page_alloc_init(); 在 MIPS 体系结构下,这个函数已经在 arch_mem_init() 函数中调用了一次。 这个函数的具体分析详细分析,请看linux-mips启动分析(4)。 所以这个函数直接返回。 parse_early_param(); 打印 linux 启动命令行参数。 printk(KERN_NOTICE Kernel command line: %sn, boot_command_line); 这个函数在 linux-mips启动分析(4)文件有分析,可以参考。 这个函数的意思对 linux 启动命令行参数进行再分析和处理。 这两个变量 _start_param 和 _stop_param 在 链接脚本 arch/mips/kernel/vmlinux.lds 中定义。 最后一个参数为,当不能够识别 linux 启动命令行参数时,调用
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1