这里不提bootloader是怎么加载内核,只谈arm体系结构下linux内核如何启动的。 linux内核编译完成后生成vmlinux ELF格式文件,并经过压缩成bin格式的zImage内核映像。当bootloader经过初始化硬件把zImage影响调入内存中时,内核代码该怎么工作,才能将系统软件带入一个合适的环境。
首先zImage虽然为压缩过的文件,但并不是完全压缩了的,起始位置还有未压缩的代码,这部分代码就是解压缩,通过将后面的内核代码解压后,执行内核部分的代码,就是vmlinux。
在内核源码根目录下的Makefile中,关于vmlinux的生成规则:
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
可以看出vmlinux的依赖有$(vmlinux-lds) $(vmlinux-init) $(vmlinux-main)。其中$(vmlinux-lds)是编译连接脚本,对于ARM平台,就是 arch/arm/kernel/vmlinux-lds文件。
$(vmlinux-init)也定义在顶层Makefile中,vmlinux-init := $(head-y) $(init-y)
head-y 在 arch/arm/Makefile 中定义:
head-y:= arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
对于有MMU的处理器,MMUEXT 为空白字符串,所以arch/arm/kernel/head.o是第一个连
接的文件,而这个文件是由 arch/arm/kernel/head.S编译产生成的。
实际上,head.s程序在被编译成目标文件后与内核其他程序一起链接成system模块,head.s位于系统内核代码的最前端,处于内存绝对地址0处开始的地方。也是从这里开始,内核完全处于保护模式下运行。
1.head.S
程序的功能也比较单一。
1)设置处理器为 SVC 模式,关中断
2)读取 CPUID,调用__lookup_processor_type 查找处理器信息结构 proc_info
3)调用__lookup_machine_type 查找机器类型信息结构 machine_desc
4)调用__create_page_tables 函数为内核创建页面映射表
5)调用处理器底层初始化函数,初始化 MMU,Cache,TLB
6)跳转到__enalbe_mmu 函数,打开 MMU
7)跳转到__mmap_switched 函数,建立 C 语言运行环境(搬移数据段,清理 BSS 段),保
存 CPUID 和机器类型代码,最后跳转到 C 语言入口函数 start_kernel()
head.s最终利用返回指令将预先放置在堆栈中的init/main.c程序的入口地址弹出,即start_kernel(),进而去执行main.c程序。
2.main.c
在main.c函数中,内核进行一系列的初始化工作,包括陷阱门、块设备、字符设备和tty,包括人工设置的第一个任务task0,等所有的初始化工作完成之后就设置中断允许标志和开启中断,main()也转入到任务0中执行。
首先执行的是asmlinkage void __init start_kernel(void)函数。函数执行了一系列的初始化,中断机制初始化,定时器初始化,调度器初始化,软中断初始化,控制台初始化,最重要的是结构体系相关的初始化,在函数setup_arch(&command_line)中。
start_kernel(void)函数之后调用rest_init()函数。函数目的是创建一个入口点是 kernel_init()函数的内核线程,然后调用 cpu_idle()函数进入空闲状态。新创建的内核线程是系统的1号任务(pid =1),放入了调度队列中,而原先的初始化代码是系统的0号任务不在调度队列中的。
因此1号任务投入运行,系统转而执行init()函数。这个函数同样定义在 init/main.c 中。
kernel_init()函数接着完成系统更高层次,比如驱动程序,根文件系统等等的初始化工作。其中的do_basic_setup()函数比较重要,这个函数先调用 driver_init()函数完成驱动程序的初始化,又通过 do_initcalls()函数依次调用了系统中所有的初始化函数。
在 init()函数的最后,调用了init_post()函数。该函数打开了控制台设备,然后,函数依次尝试执行以下几个外部程序:
1)由 ramdisk_execute_command 指定的外部程序,即内核启动参数“rdinit=XXX”指定的
程序
2)由 execute_command 指定的外部程序,即内核启动参数“init=XXX”指定的程序
3)/sbin/init
4)/etc/init
5)/bin/init
6)/bin/sh
这几个程序中任何一个加载执行成功,就进入了用户态,内核启动就宣告结束。
1号任务原先是个内核线程,加载外部程序后就有了自己的用户态空间,成为一个进程,这就是系统中所有进程的祖先 1 号进程。然后 1 号进程再执行用户态的初始化程序,例如处理/etc/inittab,创建终端,等待用户登录等等,系统启动完成。
如果想详细了解代码信息的,请参见下面的网站
欢迎提出问题交流。
阅读(396) | 评论(0) | 转发(0) |