基于x86体系结构分析linux-2.6.26的启动过程
这篇文章是在校期间陈香兰老师《深入理解Linux内核》的课堂作业,大部分内容参考自《深入理解Linux内核》 第三版 陈莉君等译。
工作需要拿出来复习一下。
转载注明出处。
1 Linux启动过程概述
本文分析基于x86平台的Linux启动过程。系统启动是个复杂的过程,主要包括以下几个步骤a、系统加电复位阶段b、BIOS自举阶段c、引导装入程序阶段d、内核运行及初始化e、init进程运行。
2 结合linux-2.6.26源代码分析启动过程
2.1 史前时代:BIOS
系统加电或复位后,基于80x86的特性,中央处理器将内存中所有数据清零,并对内存进行校验。如果没有错误,则CS寄存器中全部置1,IP寄存器中全部置0,即CS=FFFF[0]H,而IP=0000[0]H。[CS]:[IP]指向的就是BIOS的入口,由此进入BIOS的启动过程。
进入BIOS后,执行以下4个操作:
a、对硬件进行一系列测试,检测现在都有什么设备及这些设备是否正常工作。此阶段称为上电自检,会显示一些信息,如BIOS版本。
b、初始化硬件设备。此阶段会显示系统中所安装的所有PCI设备的一个列表。
c、搜索一个操作系统来启动。根据BIOS的设置,可能要试图访问软盘,硬盘或光盘的第一个扇区(引导扇区)。
d、只要找到一个有效的设备,就把上一步引导扇区的内容复制到RAM中从0x00007c00开始的位置。然后跳转到这个位置,开始执行执行刚才装进来的代码。
2.2 远古时代:引导装入程序
从磁盘启动Linux内核需要一个引导装入程序,常见的是LILO。LILO被分为两部分(否则太大无法装入整个扇区),BIOS将程序的第一部分(在引导扇区)装入从0x00007c00开始位置的RAM中,然后这段程序又把自己移到地质0x00096a00,建立实模式栈(0x00096000 ~ 0x000969ff),并把LILO的第二部分装到从地址0x00096c00开始的RAM中。
第二部分从磁盘读取可用操作系统的映射表,并提供给用户一个提示符,供用户选择。然后根据用户选择引导程序就可以把相应分区的引导扇区拷贝到RAM中并执行它,或直接把内核映像拷贝到RAM中。
在加载内核之前先了解以下内核映像的结构:
(Documentation/i386/boot.txt)
For a modern bzImage kernel with
boot protocol version >= 2.02, a memory layout like the following is suggested:
~ ~
|
Protected-mode kernel |#这是上面说的保护模式代码,LILO(grub)会把它放到这里
100000 +------------------------+
|
I/O memory hole |
0A0000 +------------------------+
|
Reserved for BIOS | Leave as much as possible unused
~ ~
|
Command line
| (Can also be below the
X+10000 mark)
X+10000 +------------------------+
|
Stack/heap | For use by the kernel real-mode code.
X+08000 +------------------------+
|
Kernel setup
| The kernel real-mode
code.
|
Kernel boot sector | The kernel legacy boot sector.
#上面这两个就是vmlinuz的前面两部分的代码和数据
X +------------------------+
| Boot loader | <- Boot sector entry point 0000:7C00
001000 +------------------------+
|
Reserved for MBR/BIOS |
000800 +------------------------+
|
Typically used by MBR |
000600 +------------------------+
|
BIOS use only |
000000 +------------------------+
... where the address X is as low as the design of the boot loader permits.
导入内核映像主要执行以下步骤:
a、调用一个BIOS过程显示“Loading”信息。
b、调用BIOS过程从磁盘装入内核映像的初始部分,即将内核映像的第一个512字节从地址0x00090000开始存入RAM中,而将setup()函数的代码从0x00090200开始存入RAM中。
header.S
...
.code16 .section ".bstext", "ax" #注意这个节的名称 .global bootsect_start bootsect_start:
#这是bootsect代码,也就是vmlinuz第一个512字节的源代码,即内核映像的第一个512字节
# Normalize the start address
ljmp $BOOTSEG, $start2
# 0x07C0:0000 如果从这里开始执行,那么说明是被BIOS直接加载过来的,这是不允许的,因为现在linux需要一个bootloader,这也就是上面说的bootsect有点特殊的地方,就是说它并没打算用来执行。所以万一它被作为bootsect由BIOS直接执行,那么就直接提示reboot.
Header.s中定义了三个节.bstext,.bsdata,.header,这3个节共同构成了上vmlinuz的第一个512字节。
c、同样的方式装入剩余的内核映像,并把内核映像放入从低地址0x00010000(适用于make zImage编译的小内核映像)或者从高地址0x00100000(适用于 bzImage编译的大内核映像)开始的RAM中。这段代码是保护模式的代码。
d、跳转到setup()代码。
header.s
jmp_far(__LOAD_DS__+0x20, 0); /* Run the kernel */ 0x20加在段上就是0x200个字节(实模式下逻辑地址到线性地址的计算方法seg<<4+offset),也就是跳到vmlinuz实模式代码setup执行了.
2.3 中世纪:setup()函数
setup()汇编语言函数的代码有链接程序放在内核映像文件的偏移量0x200处。作用是初始化计算机的硬件设备,并为内核程序执行建立环境。虽然BIOS已经初始化了大部分的硬件设备,但Linux并不依赖于BIOS,而是以自己的方式重新初始化设备以增强可移植性和健壮性。
Header.s 执行到了setup代码了,也就是vmlinuz第二个512字节处。也就是_start
# offset 512, entry point .globl _start _start: # Explicitly enter this as bytes, or the assembler # tries to generate a 3-byte jump here, which causes # everything else to push off to the wrong offset. .byte 0xeb # short (2-byte) jump .byte start_of_setup-1f #第一条指令,这是一条短跳转,接下来到start_of_setup。执行设置堆栈清空setup的bss段等操作,然后跳转到boots/main.c中。 # Jump to C code (should not return) calll main #到C代码去了
#代码在boot/main.c
setup()函数和main()函数本质上执行以下操作:
a、调用BIOS例程,在RAM中建立系统物理内存布局表。
b、设置键盘重复延时和速率,初始化视频卡,初始化磁盘控制器并检测硬盘参数,检测IBM微通道总线,检查PS/2指针设备(总线鼠标),检查对高级电源(APM)BIOS的支持。
c、如果BIOS支持磁盘驱动服务,则调用相应的其过程在RAM中建立系统可用硬盘表。
d、将低装载的内核映像移动到高装载的物理地址(0x00010000)处。因为解压内核映像需要一些空闲空间作为临时缓冲区。
e、设置8042键盘控制器的A20引脚。
f、建立一个临时中断描述符表(IDT)和一个临时全局描述符表(GDT)。
g、设置浮点单元,重新编写可编程中断控制器(PIC)(以屏蔽所有终端),通过设置cr0状态寄存器中的PE位把CPU从实模式切换到保护模式。
h、跳转到startup_32()汇编语言函数。
main函数中的最后两个函数,实现从实模式到保护模式的切换
/* Do the last things and invoke
protected mode */
go_to_protected_mode();进入保护模式。这个函数进而又调用
protected_mode_jump(boot_params.hdr.code32_start,(u32)&boot_params +
(ds() << 4));
这个函数接受两个参数,第一个参数是保护模式的第一条代码,在0x100000,后面这个就是给内核传递的参数,由于切换到保护模式,所以要给出参数的线性地址,而不是有效地址,ds()函数就是ds寄存器的值。
2.4 文艺复兴时期:startup_32()函数
有两个不同的startup_32()函数,此处指arch/i386/boot/compressed/head.s文件的那个。
该函数执行以下操作:
a、初始化段寄存器和一个临时堆栈。
b、清零eflags寄存器的所有位,用0填充_edata和_end符号标示的内核未初始化数据区。
c、调用decompress_kernel()解压内核映像。移动解压后的映像到物理地址0x00100000地址开始的最终位置。
d、跳转到物理地址0x00100000处。
第二个startup_32()函数为第一个Linux进程(进程0)建立执行环境。
该函数执行以下操作:
a、把段寄存器初始化为最终值,把内核的bss段填充为0.
lgdt pa(boot_gdt_descr) movl $(__BOOT_DS),%eax movl %eax,%ds movl %eax,%es movl %eax,%fs movl %eax,%gs
* Clear BSS first so that there are no surprises... */
#清空BSS
cld xorl %eax,%eax movl $pa(__bss_start),%edi movl $pa(__bss_stop),%ecx subl %edi,%ecx shrl $2,%ecx rep ; stosl
b、初始化0号进程的页表。把页全局目录的地址存放在cr3寄存器中,并通过设置cr0寄存器的PG位启用分页。
#pg0:临时的页表项。映射前面4M 内存空间大小 movl $pa(pg0), %edi movl $pa(swapper_pg_dir), %edx movl $PTE_ATTR, %eax 10: #edi中存放了pg0的地址。PDE_ATTR(%edi):会成生一个PDE项 leal PDE_ATTR(%edi),%ecx /* Create PDE entry */ #将生成的PDE设为PGD的第0项 movl %ecx,(%edx) /* Store identity PDE entry */ #将生成的PDE设为PGD的page_pde_offset项即0x300 movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */ #即edx 指向pgd的第二项 addl $4,%edx #接下来就是设置pg0的值了. movl $1024, %ecx #设置循环次数 11: #将eax-> edi . edi存放的是pg0的地址 stosl addl $0x1000,%eax #eax = eax +0x1000 (0x1000 = 4K) loop 11b #经过上面的循环之后,pg0中的内容依次被设置为:0x007, 0x1007,0x2007...0x3FF007 #这次从线性地址0开始的第一个PGD项和从__PAGE_OFFSET开始的第一个PGD都可以对前4M 进行寻址了
c、建立进程0的内核态堆栈,并再一次清零eflags寄存器的所有位。
//开启分页 movl $pa(swapper_pg_dir),%eax movl %eax,%cr3 /* set the page table pointer.. */ movl %cr0,%eax orl $X86_CR0_PG,%eax movl %eax,%cr0 /* ..and set paging (PG) bit */ ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */ 1: /* Set up the stack pointer */ //建立内核态堆栈 lss stack_start,%esp
d、中断向量表初始化。
e、从BIOS中获取系统参数放到第一个页框中,识别处理器型号,用GDT和IDT表(由setup()函数初始化)的地址来填充gdtr和idtr寄存器。
f、跳转到start_kernel()函数。
jmp start_kernel
2.5 现代:start_kernel()函数
start_kernel()函数完成Linux内核的初始化工作,几乎所有内核都是由这个函数进行初始化的。执行后会显示“Linux version ...”信息,除此之外,在init程序和内核线程执行的最后阶段还会显示很多其他的信息。最后在控制台上初现熟悉的登录提示符,通知用户Linux内核已经启动,现在正在运行。
现在只提及初始化中的少部分:
a、调用sched_init()函数来初始化调度程序。
b、调用build_all_zonelists()函数来初始化内存管理区。
c、调用page_alloc_init()函数初始化伙伴系统分配程序。
d、调用trap_init()函数和init_IRQ()函数以完成IDT的初始化。
e、调用time_init()函数来初始化系统日期和时间。
f、调用kernel_thread()函数为进程1创建内核线程。
3 Linux启动过程分析总结
以下图来表示Linux启动的过程:
4 结合Linux启动过程时的内核输出信息分析启动过程
本例使用的是Ubuntu 8.04
sw2@sw2-desktop:~$ dmesg 执行查看命令
[ 0.000000] Linux version 2.6.24-16-generic (buildd@palmer) (gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu7)) #1 SMP Thu Apr 10 13:23:42 UTC 2008 (Ubuntu 2.6.24-16.30-generic)
#Start_kernel()开始执行后的现实的信息--内核版本,gcc版本,发行版本的发行日期
[ 0.000000] BIOS-provided physical RAM map:
[ 0.000000] BIOS-e820: 0000000000000000 - 000000000009f800 (usable) 可用
[ 0.000000] BIOS-e820: 000000000009f800 - 00000000000a0000 (reserved) 保留
[ 0.000000] BIOS-e820: 00000000000ca000 - 00000000000cc000 (reserved)
[ 0.000000] BIOS-e820: 00000000000dc000 - 00000000000e0000 (reserved)
[ 0.000000] BIOS-e820: 00000000000e4000 - 0000000000100000 (reserved)
[ 0.000000] BIOS-e820: 0000000000100000 - 000000000fef0000 (usable)
[ 0.000000] BIOS-e820: 000000000fef0000 - 000000000feff000 (ACPI data) ACPI数据
[ 0.000000] BIOS-e820: 000000000feff000 - 000000000ff00000 (ACPI NVS)ACPI NVS
[ 0.000000] BIOS-e820: 000000000ff00000 - 0000000010000000 (usable)
[ 0.000000] BIOS-e820: 00000000fec00000 - 00000000fec10000 (reserved)
[ 0.000000] BIOS-e820: 00000000fee00000 - 00000000fee01000 (reserved)
[ 0.000000] BIOS-e820: 00000000fffe0000 - 0000000100000000 (reserved)
使用e820技术显示物理内存表--从0x00000000~0x00010000
[ 0.000000] 0MB HIGHMEM available. 0M高速缓存可用
[ 0.000000] 256MB LOWMEM available. 160M低速缓存可用
[ 0.000000] found SMP MP-table at 000f6aa0 找到SMP MP-table地址
[ 0.000000] Entering add_active_range(0, 0, 65536) 0 entries of 256 used
[ 0.000000] Zone PFN ranges: 物理页编号范围
[ 0.000000] DMA 0 -> 4096
[ 0.000000] Normal 4096 -> 65536
[ 0.000000] HighMem 65536 -> 65536
页面信息
.....
.....
Kernel command line: ro root=LABEL=/
#内核命令行:根只读
Initializing
CPU#0
#初始化CPU#0
Detected
2793.547 MHz processor.
#检测cpu主频为2793.547
Console:
colour VGA+ 80x25
#console类型
Calibrating
delay loop... 5570.56 BogoMIPS
#校正回环延时,后面的数值好像是每秒运算多少百万条指令
Memory:
156648k/163840k available (1347k kernel code, 5460k reserved, 999k data, 132k
init, 0k highmem)
#可用物理内存,kernel占用1347k;5460k保留;999k数据;132k初始化;0k
高速缓存
Dentry
cache hash table entries: 32768 (order: 6, 262144 bytes)
Inode cache hash table entries: 16384 (order: 5, 131072 bytes)
Mount cache hash table entries: 512 (order: 0, 4096 bytes)
Buffer-cache hash table entries: 8192 (order: 3, 32768 bytes)
Page-cache hash table entries: 65536 (order: 6, 262144 bytes)
#各种缓存hash值
CPU:
Trace cache: 12K uops<6>CPU: L2 cache: 256K
#cpu的L1和L2
Intel
machine check architecture supported.
Intel machine check reporting enabled on CPU#0.
#硬件体系结构
CPU:
After generic, caps: 0febfbff 00000000 00000000 00000000
CPU:
Common caps: 0febfbff 00000000 00000000 00000000
CPU: Intel(R) Celeron(R) CPU 2.80GHz stepping 08
#CPU检测信息
Enabling
fast FPU save and restore... done.
#启用快速FPU读写
Enabling
unmasked SIMD FPU exception support... done.
#启用无屏蔽的单指令流FPU异常支持
Checking
'hlt' instruction... OK.
#检测hlt指令
POSIX
conformance testing by UNIFIX
#POSIX一致性测试
mtrr:
v1.40 (20010327) Richard Gooch (rgooch@atnf.csiro.au)
mtrr: detected mtrr type: Intel
#内核的MTRR支持,可提升PCI/AGP速率
PCI:
PCI BIOS revision 2.10 entry at 0xfd9a0, last bus=1
PCI: Using configuration type 1
PCI: Probing PCI hardware
PCI: Using IRQ router PIIX [8086/7110] at 00:07.0
PCI: Cannot allocate resource region 4 of device 00:07.1
#PCI扩展插槽检测
Limiting
direct PCI/PCI transfers.
#限制直连的PCI传输
isapnp:
Scanning for PnP cards...
isapnp: No Plug & Play device found
#ISA扩展插槽检测
Linux
NET4.0 for Linux 2.4
#2.4内核支持ipv4
Based
upon Swansea University Computer Society NET3.039
#基于SUCS的NET3.039
Initializing
RT netlink socket
#初始化网络接收接口
apm:
BIOS version 1.2 Flags 0x03 (Driver version 1.16)
#高级电源管理
Starting
kswapd
#启动kswpd进程,kswapd是核心调页线程,挺重要的一个核心线程
VFS:
Disk quotas vdquot_6.5.1
#虚拟文件系统
pty:
2048 Unix98 ptys configured
#伪终端支持
Serial
driver version 5.05c (2001-07-08) with MANY_PORTS MULTIPORT SHARE_IRQ
SERIAL_PCI ISAPNP enabled
#串行设备的内核支持
ttyS0
at 0x03f8 (irq = 4) is a 16550A
ttyS1 at 0x02f8 (irq = 3) is a 16550A
#初始化tty
Real
Time Clock Driver v1.10e
#始终程序
Floppy
drive(s): fd0 is 1.44M
FDC 0 is a post-1991 82077
#软驱驱动
NET4:
Frame Diverter 0.46
#网络的帧分离器版本
RAMDISK
driver initialized: 16 RAM disks of 4096K size 1024 blocksize
#随机存储器支持
Uniform
Multi-Platform E-IDE driver Revision: 7.00beta-2.4
#一致的多平台E-IDE驱动版本
ide:
Assuming 33MHz system bus speed for PIO modes; override with idebus=xx
#IDE的工作频率
PIIX4:
IDE controller at PCI slot 00:07.1
PIIX4: chipset revision 1
PIIX4: not 100% native mode: will probe irqs later
ide1: BM-DMA at 0x1478-0x147f, BIOS settings: hdc:DMA,
hdd:pio
#IDE驱动信息
hdc:
VMware Virtual IDE CDROM Drive, ATAPI CD/DVD-ROM drive
#ATAPI CD/DVD-ROM drive
ide1
at 0x170-0x177,0x376 on irq 15
#IDE1位置和IRQ
ide-floppy
driver 0.99.newide
ide-floppy driver 0.99.newide
#IDE驱动
md:
md driver 0.90.0 MAX_MD_DEVS=256, MD_SB_DISKS=27
md: Autodetecting RAID arrays.
md: autorun ...
md: ... autorun DONE.
#多线程调试
NET4:
Linux TCP/IP 1.0 for NET4.0
IP Protocols: ICMP, UDP, TCP, IGMP
IP: routing cache hash table of 1024 buckets, 8Kbytes
TCP: Hash tables configured (established 16384 bind 32768)
Linux IP multicast router 0.06 plus PIM-SM
#TCP/IP信息
NET4:
Unix domain sockets 1.0/SMP for Linux NET4.0.
RAMDISK: Compressed image found at block 0
Freeing initrd memory: 247k freed
#空闲的初始化缓存
VFS:
Mounted root (ext2 filesystem).
#挂接根文件系统
SCSI
subsystem driver Revision: 1.00
#SCSI支持
PCI:
Found IRQ 11 for device 00:10.0
#PCI IRQ支持
scsi:
***** BusLogic SCSI Driver Version 2.1.15 of 17 August 1998 *****
scsi: Copyright 1995-1998 by Leonard N. Zubkoff <lnz@dandelion.com>
scsi0: Configuring BusLogic Model BT-958 PCI Wide Ultra SCSI Host Adapter
scsi0: Firmware Version: 5.07B, I/O Address: 0x1440, IRQ Channel:
11/Level
scsi0: PCI Bus: 0, Device: 16, Address: 0xEC800000, Host Adapter
SCSI ID: 7
scsi0: Parity Checking: Enabled, Extended Translation: Enabled
scsi0: Synchronous Negotiation: Ultra, Wide Negotiation: Enabled
scsi0: Disconnect/Reconnect: Enabled, Tagged Queuing: Enabled
scsi0: Scatter/Gather Limit: 128 of 8192 segments, Mailboxes: 211
scsi0: Driver Queue Depth: 211, Host Adapter Queue Depth: 192
scsi0: Tagged Queue Depth: Automatic, Untagged Queue Depth: 3
scsi0: Error Recovery Strategy: Default, SCSI Bus Reset: Enabled
scsi0: *** BusLogic BT-958 Initialized Successfully ***
scsi0 : BusLogic BT-958
Vendor: VMware, Model: VMware Virtual S Rev: 1.0
Type: Direct-Access
ANSI SCSI revision: 02
scsi0: Target 0: Queue Depth 28, Asynchronous
scsi0: Target 1: Queue Depth 3, Asynchronous
scsi0: Target 2: Queue Depth 3, Asynchronous
scsi0: Target 3: Queue Depth 3, Asynchronous
scsi0: Target 4: Queue Depth 3, Asynchronous
scsi0: Target 5: Queue Depth 3, Asynchronous
scsi0: Target 6: Queue Depth 3, Asynchronous
scsi0: Target 7: Queue Depth 3, Asynchronous
scsi0: Target 8: Queue Depth 3, Asynchronous
scsi0: Target 9: Queue Depth 3, Asynchronous
scsi0: Target 10: Queue Depth 3, Asynchronous
scsi0: Target 11: Queue Depth 3, Asynchronous
scsi0: Target 12: Queue Depth 3, Asynchronous
scsi0: Target 13: Queue Depth 3, Asynchronous
scsi0: Target 14: Queue Depth 3, Asynchronous
scsi0: Target 15: Queue Depth 3, Asynchronous
Attached scsi disk sda at scsi0, channel 0, id 0, lun 0
SCSI device sda: 8388608 512-byte hdwr sectors (4295 MB)
#检测和调试SCSI驱动器过程
Partition
check:
sda: sda1 sda2 sda3 sda4 < sda5 >
#分区检测
Journalled
Block Device driver loaded
#驱动器载入
EXT3-fs:
INFO: recovery required on readonly filesystem.
EXT3-fs: write access will be enabled during recovery.
scsi0: Tagged Queuing now active for Target 0
kjournald starting. Commit interval 5 seconds
EXT3-fs: recovery complete.
EXT3-fs: mounted filesystem with ordered data mode.
#文件系统检测、挂接、设置
Freeing
unused kernel memory: 132k freed
#空闲的可用核心内存
usb.c:
registered new driver usbdevfs
usb.c: registered new driver hub
usb-uhci.c: $Revision: 1.275 $ time 17:59:01 Mar 13 2003
usb-uhci.c: High bandwidth mode enabled
#USB驱动支持和检测
PCI:
Found IRQ 9 for device 00:07.2
PCI: Sharing IRQ 9 with 00:12.0
#PCI设备检测
usb-uhci.c:
USB UHCI at I/O 0x1060, IRQ 9
usb-uhci.c: Detected 2 ports
usb.c: new USB bus registered, assigned bus number 1
hub.c: USB hub found
hub.c: 2 ports detected
usb-uhci.c: v1.275:USB Universal Host Controller Interface driver
usb.c: registered new driver hiddev
usb.c: registered new driver hid
#usb设备检测
hid-core.c:
v1.8.1 Andreas Gal, Vojtech Pavlik <vojtech@suse.cz>
hid-core.c: USB HID support drivers
mice: PS/2 mouse device common for all mice
EXT3 FS 2.4-0.9.19, 19 August 2002 on sd(8,2), internal journal
Adding Swap: 305192k swap-space (priority -1)
kjournald starting. Commit interval 5 seconds
EXT3 FS 2.4-0.9.19, 19 August 2002 on sd(8,1), internal journal
EXT3-fs: mounted filesystem with ordered data mode.
kjournald starting. Commit interval 5 seconds
EXT3 FS 2.4-0.9.19, 19 August 2002 on sd(8,3), internal journal
EXT3-fs: mounted filesystem with ordered data mode.
parport0: PC-style at 0x378 [PCSPP,TRISTATE]
#文件系统挂接
ip_tables:
(C) 2000-2002 Netfilter core team
#ip_tables核心线程信息
pcnet32.c:v1.27b
01.10.2002 tsbogend@alpha.franken.de
PCI: Found IRQ 10 for device 00:11.0
pcnet32: PCnet/PCI II 79C970A at 0x1080, 00 0c 29 e5 f4 4d assigned IRQ 10.
divert: allocating divert_blk for eth0
eth0: registered as PCnet/PCI II 79C970A
pcnet32: 1 cards_found.
#网卡
ip_tables:
(C) 2000-2002 Netfilter core team
ip_tables: (C) 2000-2002 Netfilter core team
ip_tables: (C) 2000-2002 Netfilter core team
5 参考资料
[1]深入理解Linux内核 第三版 陈莉君等译