kernel启动过程

来源:互联网 发布:二叉树算法 编辑:程序博客网 时间:2024/06/05 18:03

一。获得可运行的Linux内核

二。内核装载时的内存空间映射

三。内核启始相关文件分析

四。arch/i386/boot/bootsect.S

五。arch/i386/boot/setup.S

六。arch/i386/boot/compressed/head.S

七。arch/i386/kernel/head.S

八。start_kernel

九。第一个内核线程 - kernel_init

十。参考资料

一。获得可运行的Linux内核

当我们从www.kernel.org获得Linux源码并正确编译后,在源码根目录下会生成文件vmlinux,同时在arch/i386/boot/目录下会生成bzImage文件。下面我们看看vmlinux和bzImage分别是如何得到的。没有特殊说明,本系列中Linux的参考对象都为版本2.6.22。

1。vmlinux的获得

vmlinux是Linux源码编译后未压缩的内核,我们查看源码根目录下的.vmlinux.cmd文件,可以看到:

cmd_vmlinux := ld -m elf_i386 -m elf_i386 -o vmlinux -T arch/i386/kernel/vmlinux.lds arch/i386/kernel/head.o arch/i386/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/i386/kernel/built-in.o arch/i386/mm/built-in.o arch/i386/mach-default/built-in.o arch/i386/crypto/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o lib/lib.a arch/i386/lib/lib.a lib/built-in.o arch/i386/lib/built-in.o drivers/built-in.o sound/built-in.o arch/i386/pci/built-in.o net/built-in.o --end-group .tmp_kallsyms2.o

这说明vmlinux是由arch/i386/kernel/head.o和arch/i386/kernel/init_task.o以及各个相关子目录下的built-in.o链接而成的。注意按照链接顺序我们可以发现arch/i386/kernel/head.S的目标文件似乎比较靠前。

2。bzImage的获得

bzImage是内核的压缩版本,一般可以是vmlinux大小的三分之一左右。

首先查看生成bzImage的链接文件arch/i386/boot/.bzImage.cmd

cmd_arch/i386/boot/bzImage := arch/i386/boot/tools/build -b arch/i386/boot/bootsect arch/i386/boot/setup arch/i386/boot/vmlinux.bin CURRENT > arch/i386/boot/bzImage

接下去根据线索我们查看生成vmlinux.bin的链接文件arch/i386/boot/.vmlinux.bin.cmd

cmd_arch/i386/boot/vmlinux.bin := objcopy -O binary -R .note -R .comment -S arch/i386/boot/compressed/vmlinux arch/i386/boot/vmlinux.bin

然后查看生成vmlinux的链接文件arch/i386/boot/compressed/.vmlinux.cmd

cmd_arch/i386/boot/compressed/vmlinux := ld -m elf_i386 -m elf_i386 -T arch/i386/boot/compressed/vmlinux.lds arch/i386/boot/compressed/head.o arch/i386/boot/compressed/misc.o arch/i386/boot/compressed/piggy.o -o arch/i386/boot/compressed/vmlinux

接下去查看生成piggy.o的链接文件arch/i386/boot/compressed/.piggy.o.cmd

cmd_arch/i386/boot/compressed/piggy.o := ld -m elf_i386 -m elf_i386 -r --format binary --oformat elf32-i386 -T arch/i386/boot/compressed/vmlinux.scr arch/i386/boot/compressed/vmlinux.bin.gz -o arch/i386/boot/compressed/piggy.o

然后接下去查看生成vmlinux.bin.gz的链接文件arch/i386/boot/compressed/.vmlinux.bin.gz.cmd

cmd_arch/i386/boot/compressed/vmlinux.bin.gz := gzip -f -9 < arch/i386/boot/compressed/vmlinux.bin > arch/i386/boot/compressed/vmlinux.bin.gz

最后我们查看生成vmlinux.bin的链接文件arch/i386/boot/compressed/.vmlinux.bin.cmd,注意这里的vmlinux就是根目录下的vmlinux。

cmd_arch/i386/boot/compressed/vmlinux.bin := objcopy -O binary -R .note -R .comment -S vmlinux arch/i386/boot/compressed/vmlinux.bin

下面我们将生成bzImage的过程总结一下:

a。由vmlinux文件strip掉符号表得到arch/i386/boot/compressed/vmlinux.bin

b。将vmlinux.bin压缩成vmlinux.bin.gz

c。将vmlinux.scr和vmlinux.bin.gz链接成piggy.o

d。将head.o、misc.o和piggy.o链接成当前目录下的vmlinux

e。将vmlinux文件strip掉符号表得到arch/i386/boot/vmlinux.bin

f。将bootsect、setup和vmlinux.bin拼接成bzImage

二。内核装载时的内存空间映射

下面是文件Documentation/i386/boot.txt中提供的bzImage在内存中的映射图,和本实例略有出入,下面我们会指出,但基本描述了bzImage在内存中的分布情况。

下图是传统的Image或zImage内存映射图

结合上一章在bootloader中boot_func所讲的实际情况,内核在内存中的地址映射应该是这样的:

0x100000以上:内核保护模式代码

0x99000-0x99100:内核参数命令

0x90000-0x99000:内核bootsect和setup实模式代码,bootsect大小512字节,setup0x1400字节

0x9000开始:内核栈地址

三。内核启始相关文件分析

从以上bzImage的生成过程,我们可以发现,arch/i386/boot/bootsect和arch/i386/boot/setup应该是做初始工作的,接下来应该是arch/i386/boot/compressed/head.o,然后可能就是vmlinux是由arch/i386/kernel/head.o。那么我们就按照顺序从arch/i386/boot/bootsect.S开始分析。

下面图片是bzImage的前0x240个字节内容。

四。arch/i386/boot/bootsect.S

bootsect.S生成的文件bootsect大小只有512字节,也就是上图中的0x0000到0x01ff的内容,是不是有点眼熟,其实里面另有玄机。下面我们来看bootsect.S的内容。

_start:

  • jmpl $BOOTSEG, $start2 ;这里的BOOTSEG就是段地址0x07C0,使用一个长跳转到start2

start2:

  • movw %cs, %ax
  • movw %ax, %ds
  • movw %ax, %es
  • movw %ax, %ss
  • movw $0x7c00, %sp
  • sti
  • cld
  • movw $bugger_off_msg, %si ;bugger_off_msg中的信息为"Direct booting from floppy is no longer supported./r/nPlease use a boot loader program instead./r/n/nRemove disk and press any key to reboot . . ./r/n"

msg_loop:

  • lodsb
  • andb %al, %al
  • jz die ;打印完成后跳转到die
  • movb $0xe, %ah
  • movw $7, %bx
  • int $0x10 ;使用int10打印信息到屏幕
  • jmp msg_loop

die:

  • xorw %ax, %ax
  • int $0x16 ;允许用户按任意一键重启
  • int $0x19
  • ljmp $0xf000,$0xfff0 ;一般上面的中断调用后不会到这里了,如果有例外情况,直接跳转到BIOS的重启代码

从这里可以看出,此处内核的bootsect其实没有任何意义,实际上在2.6版本的linux中,必须要有另外的bootloader才能启动内核,例如grub。在前面我们分析grub的boot_func中的big_linux_boot里,描述了实际上grub的stage2将内核的bootsect和setup实模式代码载入到地址0x90000后,是skip了头0x200个字节的,直接跳转到地址0x90200处执行的。

五。arch/i386/boot/setup.S

setup.S是真正内核的开始,上面图片从0x200开始就是setup的内容。

从0x0202开始的4个字节是特征值"HdrS"。

0x206开始的内容0x0206是版本号,其实是Linux内核头协议号。

0x20c开始的内容是SYSSEG,即系统载入的段地址0x1000。

接下来是kernel_version内容的偏移量,在这里是0x11b8,实际上就是setup的启始地址0x200+0x11b8=0x13b8,在这里因为太长没有给出图片,可以告诉大家实际内容是"2.6.22 ( root@FG4DEV ) #6 SMP Thu Aug 2 16:57:24 CST 2007"。

0x211内容为1,指出此内核为big-kernel。

0x212开始的内容是0x8000,代表setup_move_size的大小,后面将会遇到。

0x214的内容代表了内核将要加载到的地址,在这里是0x100000。

从0x240到0xeff是E820和EDD的保留空间。

下面我们介绍主要流程。

start:

  • jmp trampoline

trampoline:

  • call start_of_setup

start_of_setup:

  • movw $0x01500, %ax
  • movb $0x81, %dl
  • int $0x13

1。检查特征值

  • movw %cs, %ax ;此时cs代码段地址为SETUPSEG,即0x9020
  • movw %ax, %ds
  • cmpw $SIG1, setup_sig1 ;检查偏移地址setup_sig1处内容是否为0xAA55 ,这一般在编译生成setup时就写好了
  • jne bad_sig
  • cmpw $SIG2, setup_sig2 ;检查偏移地址setup_sig2处内容是否为0x5A5A
  • jne bad_sig
  • jmp good_sig1 ;检查特征值没问题

good_sig1:

  • jmp good_sig

good_sig:

  • movw %cs, %ax
  • subw $DELTA_INITSEG, %ax ;这里DELTA_INITSEG = SETUPSEG - INITSEG = 0x9020 - 0x9000 = 0x0020
  • movw %ax, %ds

2。检查是否载入的是big-kernel

  • testb $LOADED_HIGH, %cs:loadflags ;检查是否big-kernel,实际就是看bzImage的0x211处的值是否为1,在本实例中是big-kernel ,将会被加载到高位0x100000,从bzImage的0x214开始的内容也可以看出。
  • jz loader_ok
  • cmpb $0, %cs:type_of_loader ;确认是否有loader可以处理接下来的工作,在这里是没有的,值为0,在偏移地址0x210处
  • jnz loader_ok
  • pushw %cs
  • popw %ds
  • lea loader_panic_mess, %si
  • call prtstr;打印"Wrong loader, giving up..."
  • jmp no_sig_loop;挂起,进入死循环

3。检查cpu情况

loader_ok:

  • call verify_cpu ;具体在arch/i386/kernel/verify_cpu.S中,这里就不做详细介绍了
  • testl %eax,%eax
  • jz cpu_ok
  • movw %cs,%ax
  • movw %ax,%ds
  • lea cpu_panic_mess,%si
  • call prtstr ;打印"PANIC: CPU too old for this kernel."

1:

  • jmp 1b ;进入死循环

cpu_ok:

4。获取内存大小,在这里共使用了3种不同方式检测内存:通过e820h方式获取内存地图,通过e801h方式获得32位内存尺寸,最后通过88h获得0-64m 。有关e820h可以访问www.acpi.info获得ACPI 2.0规范的详细内容

下面的e820h方式

  • xorl %eax, %eax
  • movl %eax, (0x1e0)
  • movb %al, (E820NR) ;E820NR=0x1e8

meme820:

  • xorl %ebx, %ebx
  • movw $E820MAP, %di ;E820MAP=0x2d0

jmpe820:

  • movl $0x0000e820, %eax
  • movl $SMAP, %edx ;SMAP就是"SMAP"
  • movl $20, %ecx
  • pushw %ds
  • popw %es
  • int $0x15
  • jc bail820
  • cmpl $SMAP, %eax
  • jne bail820

good820:

  • movb (E820NR), %al
  • cmpb $E820MAX, %al ;E820MAX=128,即E820MAP实例的个数
  • jae bail820 ;128个后获得后跳出循环,内容都在地址0x1e8 开始的128个实例中
  • incb (E820NR)
  • movw %di, %ax
  • addw $20, %ax
  • movw %ax, %di

again820:

  • cmpl $0, %ebx
  • jne jmpe820

bail820:

下面是e801h方式

meme801:

  • stc
  • xorw %cx,%cx
  • xorw %dx,%dx ;据说这是为了避免有问题的BIOS产生错误
  • movw $0xe801, %ax
  • int $0x15
  • jc mem88
  • cmpw $0x0, %cx
  • jne e801usecxdx
  • cmpw $0x0, %dx
  • jne e801usecxdx
  • movw %ax, %cx
  • movw %bx, %dx

e801usecxdx:

  • andl $0xffff, %edx
  • shll $6, %edx
  • movl %edx, (0x1e0)
  • andl $0xffff, %ecx
  • addl %ecx, (0x1e0) ;内容放到地址0x1e0中

这里是88h方式,最古老的方式,难道最后内容放在地址0x02中?

mem88:

  • movb $0x88, %ah
  • int $0x15
  • movw %ax, (2)

5。设置键盘敲击速率到最大

  • movw $0x0305, %ax
  • xorw %bx, %bx
  • int $0x16

6。检查显示设备参数并设置模式,具体看arch/i386/boot/video.S,这里不做介绍了

  • call video

7。获取hd0数据

  • xorw %ax, %ax
  • movw %ax, %ds
  • ldsw (4 * 0x41), %si
  • movw %cs, %ax
  • subw $DELTA_INITSEG, %ax
  • pushw %ax
  • movw %ax, %es
  • movw $0x0080, %di
  • movw $0x10, %cx
  • pushw %cx
  • cld
  • rep
  • movsb

8。获取hd1数据

  • xorw %ax, %ax
  • movw %ax, %ds
  • ldsw (4 * 0x46), %si
  • popw %cx
  • popw %es
  • movw $0x0090, %di
  • rep
  • movsb

9。检查是否有hd1

  • movw $0x01500, %ax
  • movb $0x81, %dl
  • int $0x13
  • jc no_disk1
  • cmpb $3, %ah
  • je is_disk1

no_disk1:

  • movw %cs, %ax
  • subw $DELTA_INITSEG, %ax
  • movw %ax, %es
  • movw $0x0090, %di
  • movw $0x10, %cx
  • xorw %ax, %ax
  • cld
  • rep
  • stosb

is_disk1:

10。检查微通道总线MCA,IBM提出的早期总线,目前一般系统都不带MCA总线了

  • movw %cs, %ax
  • subw $DELTA_INITSEG, %ax
  • movw %ax, %ds
  • xorw %ax, %ax
  • movw %ax, (0xa0)
  • movb $0xc0, %ah
  • stc
  • int $0x15
  • jc no_mca
  • pushw %ds
  • movw %es, %ax
  • movw %ax, %ds
  • movw %cs, %ax
  • subw $DELTA_INITSEG, %ax
  • movw %ax, %es
  • movw %bx, %si
  • movw $0xa0, %di
  • movw (%si), %cx
  • addw $2, %cx
  • cmpw $0x10, %cx
  • jc sysdesc_ok
  • movw $0x10, %cx

sysdesc_ok:

  • rep
  • movsb
  • popw %ds

no_mca:

11。检测PS/2点设备

  • movw %cs, %ax
  • subw $DELTA_INITSEG, %ax
  • movw %ax, %ds
  • movb $0, (0x1ff)
  • int $0x11
  • testb $0x04, %al
  • jz no_psmouse
  • movb $0xAA, (0x1ff) ;设备存在

no_psmouse:

12。准备进入保护模式

  • cmpw $0, %cs:realmode_swtch ;在这里realmod_swtch实际上是0,所以跳转到rmodeswtch_normal
  • jz rmodeswtch_normal
  • lcall *%cs:realmode_swtch
  • jmp rmodeswtch_end

rmodeswtch_normal:

  • pushw %cs
  • call default_switch ;default_switch调用的实际操作是在真正进入保护模式前关闭中断并禁止NMI

rmodeswtch_end:

13。将系统移到正确的位置,如果是big-kernel我们就不移动了

  • testb $LOADED_HIGH, %cs:loadflags
  • jz do_move0
  • jmp end_move

do_move0:

  • movw $0x100, %ax
  • movw %cs, %bp
  • subw $DELTA_INITSEG, %bp
  • movw %cs:start_sys_seg, %bx
  • cld

do_move:

  • movw %ax, %es
  • incb %ah
  • movw %bx, %ds
  • addw $0x100, %bx
  • subw %di, %di
  • subw %si, %si
  • movw $0x800, %cx
  • rep
  • movsw
  • cmpw %bp, %bx
  • jb do_move

end_move:

14。载入段地址,确认bootloader是否支持启动协议版本2.02,决定是否需要移动代码到0x90000 ,关于启动协议可以参考Documentation/i386/boot.txt ,本实例中是不需要移动的

  • movw %cs, %ax
  • movw %ax, %ds
  • cmpl $0, cmd_line_ptr ;检查是否需要向下兼容小于2.01的bootloader ,在此例中cmd_line_ptr是0,为版本2.02以上
  • jne end_move_self
  • cmpb $0x20, type_of_loader
  • je end_move_self
  • movw %cs, %ax ;bootloader不支持2.02协议,如果代码段不在0x90000,需要将其移动到0x90000
  • cmpw $SETUPSEG, %ax ;SETUPSEG=0x9020
  • je end_move_self
  • cli
  • subw $DELTA_INITSEG, %ax ;DELTA_INITSEG=0x0020
  • movw %ss, %dx
  • cmpw %ax, %dx
  • jb move_self_1
  • addw $INITSEG, %dx ;INITSEG=0x9000
  • subw %ax, %dx

move_self_1:

  • movw %ax, %ds
  • movw $INITSEG, %ax
  • movw %ax, %es
  • movw %cs:setup_move_size, %cx
  • std
  • movw %cx, %di
  • decw %di
  • movw %di, %si
  • subw $move_self_here+0x200, %cx
  • rep
  • movsb
  • ljmp $SETUPSEG, $move_self_here

move_self_here:

  • movw $move_self_here+0x200, %cx
  • rep
  • movsb
  • movw $SETUPSEG, %ax
  • movw %ax, %ds
  • movw %dx, %ss

end_move_self:

15。打开A20 ,A20地址线是一个历史遗留问题,早期为了使用1M以上内存而使用的开关,目前一般硬件缺省就是打开的

a20_try_loop:

a20_none:

  • call a20_test ;先直接看看是否成功,万一系统就不需要打开A20,直接跳转就可以了
  • jnz a20_done

a20_bios:

  • movw $0x2401, %ax
  • pushfl
  • int $0x15 ;尝试使用int15设置A20
  • popfl
  • call a20_test ;看看是否成功
  • jnz a20_done

a20_kbc:

  • call empty_8042 ;尝试通过键盘控制器设置A20
  • call a20_test ;看看是否成功
  • jnz a20_done
  • movb $0xD1, %al
  • outb %al, $0x64
  • call empty_8042
  • movb $0xDF, %al
  • outb %al, $0x60 ;开启A20命令
  • call empty_8042

a20_kbc_wait:

  • xorw %cx, %cx

a20_kbc_wait_loop:

  • call a20_test ;看看是否成功
  • jnz a20_done
  • loop a20_kbc_wait_loop

a20_fast:

  • inb $0x92, %al;最后的尝试,通过配置Port A
  • orb $0x02, %al
  • andb $0xFE, %al
  • outb %al, $0x92

a20_fast_wait:

  • xorw %cx, %cx

a20_fast_wait_loop:

  • call a20_test ;看看是否成功
  • jnz a20_done
  • loop a20_fast_wait_loop
  • decb (a20_tries) ;尝试了a20_tries=A20_ENABLE_LOOPS=255次
  • jnz a20_try_loop
  • movw $a20_err_msg, %si
  • call prtstr;仍然没有效果,打印"linux: fatal error: A20 gate not responding!"

a20_die:

  • hlt
  • jmp a20_die;打开A20失败,进入死循环

a20_done:

16。设置gdt、idt和32位的启动地址

  • lidt idt_48
  • xorl %eax, %eax
  • movw %ds, %ax
  • shll $4, %eax
  • addl %eax, code32 ;设置32位启动地址,修改code32指定的地址,将增加了代码段后的数据写入code32指定的内存地址中
  • addl $gdt, %eax
  • movl %eax, (gdt_48+2) ;将下面将介绍gdt的地址写到gdt_48+2指定的地址中
  • lgdt gdt_48

17。复位所有可能存在的协处理器

  • xorw %ax, %ax
  • outb %al, $0xf0
  • call delay
  • outb %al, $0xf1
  • call delay

18。屏蔽所有中断

  • movb $0xFF, %al
  • outb %al, $0xA1
  • call delay
  • movb $0xFB, %al
  • outb %al, $0x21;又打开了中断2,因为irq2是cascaded

19。真正进入保护模式,跳转到arch/i386/boot/compressed/head.S中的startup_32

  • movw $1, %ax
  • lmsw %ax ;真正进入保护模式
  • jmp flush_instr

flush_instr:

  • xorw %bx, %bx
  • xorl %esi, %esi
  • movw %cs, %si
  • subw $DELTA_INITSEG, %si
  • shll $4, %esi
  • .byte 0x66, 0xea ;这里实际上是硬写入代码指令66 ea,即进入到保护模式下的长跳转

code32:

  • long startup_32 ;跳转到startup_32
  • .word BOOT_CS ;要跳转的代码段地址BOOT_CS=GDT_ENTRY_BOOT_CS * 8=2*8

startup_32:

  • movl $(BOOT_DS), %eax ;数据段地址BOOT_DS=GDT_ENTRY_BOOT_DS * 8=(GDT_ENTRY_BOOT_CS + 1)*8=(2+1)*8
  • movl %eax, %ds
  • movl %eax, %es
  • movl %eax, %fs
  • movl %eax, %gs
  • movl %eax, %ss ;在启动时的保护模式里,设置内核里的地址段都为代码段地址
  • xorl %eax, %eax

1:

  • incl %eax
  • movl %eax, 0x00000000
  • cmpl %eax, 0x00100000
  • je 1b ;检查A20是否开启,如果没开启就一直死循环
  • jmpl *(code32_start - start + (DELTA_INITSEG << 4))(%esi) ;这里跳转到arch/i386/boot/compressed/head.S里的startup_32

20。初始化时第一次设定的gdt和idt

  • align 16

gdt:

  • fill GDT_ENTRY_BOOT_CS,8,0
  • .word 0xFFFF ;4Gb - (0x100000*0x1000 = 4Gb)
  • .word 0 ;基地址为0
  • .word 0x9A00 ;代码段的属性是可读可执行
  • .word 0x00CF
  • .word 0xFFFF ;4Gb - (0x100000*0x1000 = 4Gb)
  • .word 0 ;基地址为0
  • .word 0x9200 ;数据段的属性是可读可写
  • .word 0x00CF

gdt_end:

  • align 4
  • word 0

idt_48:

  • word 0
  • word 0, 0
  • word 0

gdt_48:

  • word gdt_end - gdt - 1 ;gdt的尺寸
  • word 0, 0 ;gdt的地址,在运行时才填写实际的绝对地址

六。arch/i386/boot/compressed/head.S

arch/i386/boot/compressed/head.S负责将压缩的内核解压缩,并跳转到解压后的内核执行,主要流程如下:

1。段地址准备,在内核里都为BOOT_DS=GDT_ENTRY_BOOT_DS*8=(GDT_ENTRY_BOOT_CS + 1)*8=(2+1)*8

2。拷贝压缩的内核到缓存结尾以保证安全

3。计算内核启始地址

4。将压缩内核解压

call decompress_kernel

5。跳转到解压后的内核执行

xorl %ebx,%ebx

jmp *%ebp

七。arch/i386/kernel/head.S

arch/i386/kernel/head.S是真正的32位启动代码。

1。段地址准备

2。内核启动参数准备

3。初始化页面表

4。设置idt

5。检查cpu类型

6。跳转到start_kernel

jmp start_kernel

八。start_kernel

开始进入C语言的启动流程,其中一些内存管理、设备初始化、调度等相关细节将在后续章节详细介绍,这里只是简要叙述基本流程。

1。初始化tick控制

2。页面地址表page_address_maps和page_address_htable初始化

3。初始化内核代码、数据段并计算页面数

4。设置内存页面表映射

5。初始化调度

6。建立zonelists

7。设置系统trap调用

8。设置系统中断调用

9。初始化pidhash

10。设置时钟软中断TIMER_SOFTIRQ调用

11。设置高分辨率时钟软中断HRTIMER_SOFTIRQ调用

12。设置软中断TASKLET_SOFTIRQ和HI_SOFTIRQ的调用

13。初始化终端设备

14。初始化dcache和inode

15。内核空间内存地址分配

16。初始化slab机制

17。优先树结构index_bits_to_maxindex初始化

18。初始化fork机制

19。基树结构height_to_maxindex初始化

20。初始化信号机制

21。初始化acpi

22。启动第一个内核线程kernel_init

九。第一个内核线程 - kernel_init

内核最后的初始化,准备开始进入第一个应用层程序。

1。初始化工作队列

2。设备框架初始化

例如一些需要满足sys文件系统的初始化,bus、class等

3。初始化所有的initcalls

initcalls机制在2.6早期版本是不完全的,例如把网络相关的sock_init仍然以调用方式初始化,现今的内核版本已经把所有的子系统都改为以do_initcalls的形式初始化了。

4。运行第一个应用程序

打开系统终端,依次寻找/sbin/init、/etc/init、/bin/init和/bin/sh执行,若都没有成功,则打印错误信息,挂起系统。

十。参考资料

http://www.linux.org/docs/ldp/howto/Linux-i386-Boot-Code-HOWTO/index.html

原创粉丝点击