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
- kernel启动过程
- 转载:kernel 启动过程
- Linux Kernel启动过程浅析
- linux kernel的启动过程
- kernel 启动过程之二, arch/arm/kernel/head.S
- kernel 启动过程之二, arch/arm/kernel/head.S
- kernel 启动过程之二, arch/arm/kernel/head.S
- bootloader和kernel配合启动过程/编译bootloader/编译kernel
- Linux kernel起来后Android启动过程
- linux kernel 启动过程中的"变色"问题
- Linux Kernel启动过程中的内存管理
- Linux Kernel 2.6.37 启动过程:go_to_protected_mode
- Linux Kernel 2.6.37 启动过程:startup_32
- Android系统从kernel启动过程
- kernel启动过程中的调试打印
- kernel启动过程的三个特殊进程
- U-BOOT启动kernel的过程
- The Kernel Boot Process --linux2.6.25内核启动过程
- TLV格式和JAVABEAN的转换工具
- as3 socket
- as3 vector的运用
- as3 按钮通过索引都只取到-1
- 采用数据库为Flex Tree组件的提供数据-Java与LCDS
- kernel启动过程
- CStdioFile对文件的某一行进行修改
- iPhone Application Programming Guide (3)
- 2011年Java EE生产力报告
- 小心!坐在电脑前的人容易得这些病
- java迭代器异常 java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry
- 关于rails涉及两张表排序并分页的一种解决办法(瞌睡仙)
- Erlang -- (Windows平台)文件夹说明
- 使用Java中的final变量需要注意的地方