Problem 52 Linux内核启动过程简述?
Ans:
1. BIOS启动阶段
CPU在上电初始化时,指令寄存器CS:EIP总是被初始化为固定值,这就是CPU复位后的第一条指令的地址。对于32位地址总线的系统来说,4GB的物理空间至少被划分为两个部分,一部分是内存的地址空间,另外一部分地址空间用于对BIOS芯片存储单元进行寻址。x86复位后工作在实模式下,该模式下CPU的寻址空间为1MB。CS:IP的复位值是FFFF:0000,物理地址为FFFF0.主板设计者必须保证把这个物理地址映射到BIOS芯片上,而不是RAM上。
Protected 100000 |
… 0A0000 |
Command Line X+10000 |
Stack/Heap X+08000 |
Setup Code X |
Boot Loader 07C00 (bootsect.S) |
… 00000 |
BIOS把Boot Loader加载到0x7C00的地方然后跳转到这里继续执行,之后BootLoader会把实模式代码setup加载到0x07C00之上的某个地址上,其中setup的前512个字节是一个引导扇区,现在这个引导扇区的作用并不是用来引导系统,而是为了兼容及传递一些参数。之后Boot Loader会跳转到setup的入口点,入口点为_start.(根据arch/x86/boot/setup.ld可知)。
注:bzImage由setup和vmlinux两部分组成,setup是实模式下的代码,vmlinux是保护模式下的代码。
2. 实模式setup阶段
用于体系结构相关的硬件初始化工作,涉及的文件有arch/x86/boot/header.S, 链接脚本setup.ld, arch/x86/boot/main.c。
传递bootloader的一些参数信息。
header.S->main.c
main函数先(调用copy_boot_params函数)把位于第一个扇区的参数复制到boot_params变量中,boot_params位于setup的数据段
->go_to_protected_mode(void) (文件arch/x86/boot/pm.c)
->protected_mode_jump(文件arch/x86/boot/pmjump.S)
->startup_32(保护模式下的入口函数)
3. 保护模式startup_32
实模式的protected_mode_jump后,跳出了bzImage的第一部分,BootLoader默认把第二部分放在0x100000处,这个入口处是startup_32,先执行arch/x86/boot/compressed/head_32.S中的startup_32,然后执行arch/x86/kernel/head_32.S中的startup_32。
拷贝boot_params以及boot_command_line, 初始化页表,开启分页机制。
4. 内核启动start_kernel()
文件init/main.c
kernel_init()->do_basic_setup()->do_initcalls()->init_post()
5. 内核启动时的参数传递
内核命令行参数在启动阶段由startup_32复制到init/main.c的boot_command_line数组中,最后由函数start_kernel()对参数进行解析处理。
start_kernel()->parse_early_param()->do_early_param()
->parse_args()(模块参数处理)
注:#define BUILD_BUG_ON_ZERO(e) (sizeof(char[1-2*!!(e)])-1), 定义中的两次(逻辑)取反是为了保证无论e为何值,其结果非0即1.
Problem 53 Some information about regparm?
Ans:
在阅读Linux内核源代码的时候,发现start_kernel函数定义的最前面是一个asmlinkage.虽知它是GNUC的扩展,但并不知其意.所以决定对其做一些研究.
i386的linkage.h里有这样的宏定义:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
CPP_ASMLINKAGE我们暂时不管,只看asmlinkage,它的主要起作用的部分是__attribute__,它在内核代码里随外可见,我想应该是对GNU编译器的一种指示操作.
在说明__attribute__((regparm(0))用处之前,我们先来看看程序在调用时的参数传递.
不同的硬件平台在默认情况下,参数调用时的参数传递机制是不一样的.如MIPS,SH4等精简指令处理器在进行子程序调用时先从寄存器里取得前几个参数,至于是几个视硬件平台而定.如SH4默认就是使用r4,r5,r6,r7作为前四个整参数的传递寄存器,同时用fr4~fr11传递前四个浮点参数,其他参数都压入栈.当然关于SH4的参数传递还有更多的细节,这里就不多说.而在i386平台上,函数的调用是基于栈的,也就是说,默认情况下,编译器在进行函数编译的时候会从栈里取得函数体需要使用的参数,而不是寄存器.
然而,参数直接从寄存器里取而不是从栈里取,前者相对来说速度要快,所以在linkage.h里也有这样的定义:
#define fastcall __attribute__((regparm(3)))
意思是,最多可以使用3个寄存器(或许是eax,edx,ecx)来传送前3个参数,其他参数则压入栈中,那么
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
的意思就是使用0个寄存器来进行参数传递,顾名思义它是想使得调用这个函数时的所有参数都压入栈中,相对于fastcall的使用方式当然是要慢一些了.