linux内核学习(10)启动全过程概述之一
2010-12-27 20:34
323 查看
下面这段时间,我要好好分析一下内核启动过程的源代码,怎么来分析,而且更好的和网友们进行交流,我想,最好的方式莫过于采用赵炯博士编著的《linux内核完全注释》一书的编写规范。将中文注释夹杂在代码中是最好的方式了吧。我将采用分段注释,以免代码太长导致读了后面的忘记了前面的,在其中会有些重要的知识点也是我们要好好学习的。
要找到第一个源代码文件不是太困难,它就是始源,注意我们这里全是在x86机器上,内核版本为2.6.36.2。那么arch/x86/boot目录则是我们的入口点,看看里面的Makefile。
setup-y += a20.o bioscall.o cmdline.o copy.o cpu.o cpucheck.o
setup-y += early_serial_console.o edd.o header.o main.o mca.o memory.o
setup-y += pm.o pmjump.o printf.o regs.o string.o tty.o video.o
setup-y += video-mode.o version.o
setup-$(CONFIG_X86_APM_BOOT) += apm.o
# The link order of the video-*.o modules can matter. In particular,
# video-vga.o *must* be listed first, followed by video-vesa.o.
# Hardware-specific drivers should follow in the order they should be
# probed, and video-bios.o should typically be last.
setup-y += video-vga.o
setup-y += video-vesa.o
setup-y += video-bios.o
可以看见setup-y目标,就是我们前面那篇说的setup.elf,我们可以发现header.o,可以想到,就是我们要找的始源。它是由header.S汇编文件产生的,顺便说一下,x86的汇编是AT&T,如果不晓得最好自己把这块学习一下。
来自:arch/x86/boot/header.S:
/*
* header.S
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* Based on bootsect.S and setup.S
* modified by more people than can be counted
*
* Rewritten as a common file by H. Peter Anvin (Apr 2007)
*
* BIG FAT NOTE: We're in real mode using 64k segments. Therefore segment
* addresses must be multiplied by 16 to obtain their respective linear
* addresses. To avoid confusion, linear addresses are written using leading
* hex while segment addresses are written as segment:offset.
*
*/
#include <asm/segment.h>
#include <generated/utsrelease.h>
#include <asm/boot.h>
#include <asm/e820.h>
#include <asm/page_types.h>
#include <asm/setup.h>
#include "boot.h"
#include "voffset.h"
#include "zoffset.h"
BOOTSEG = 0x07C0 /* original address of boot-sector */
SYSSEG = 0x1000 /* historical load address >> 4 */
#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
#define RAMDISK 0
#endif
#ifndef ROOT_RDONLY
#define ROOT_RDONLY 1
#endif
.code16
.section ".bstext", "ax"
.global bootsect_start
bootsect_start:
# Normalize the start address
ljmp $BOOTSEG, $start2
start2:
movw %cs, %ax #现在的cs=0x7c00
movw %ax, %ds #初始化段寄存器
movw %ax, %es
movw %ax, %ss #注意堆栈段为0x7c00
xorw %sp, %sp
sti #开中断
cld #di,si ++
movw $bugger_off_msg, %si #bugger_off_msg在下面
msg_loop: #打印信息
lodsb #将ds:si处的字节读入al
andb %al, %al
jz bs_die #如果al==0,则跳转到bs_die
movb $0xe, %ah
movw $7, %bx
int $0x10 #INT0x10是BIOS视频中断,打印字符。功能号AH=0x0e,表示在Teletype模式下显示字符,
#AL=字符,BH=页码,BL=前景色(图形模式)
jmp msg_loop
bs_die:
# Allow the user to press a key, then reboot
xorw %ax, %ax
int $0x16 #键盘中断,接收一个字符
int $0x19
# int 0x19 should never return. In case it does anyway,
# invoke the BIOS reset code...
ljmp $0xf000,$0xfff0 #jump to 0xffff0, 重新设置BIOS
.section ".bsdata", "a"
bugger_off_msg:
.ascii "Direct booting from floppy is no longer supported./r/n"
.ascii "Please use a boot loader program instead./r/n"
.ascii "/n"
.ascii "Remove disk and press any key to reboot . . ./r/n"
.byte 0
# Kernel attributes; used by setup. This is part 1 of the
# header, from the old boot sector.
.section ".header", "a"
.globl hdr
hdr: #这个hdr会对应setup_header结构体,待会儿在说
setup_sects: .byte 0 /* Filled in by build.c */
root_flags: .word ROOT_RDONLY
syssize: .long 0 /* Filled in by build.c */
ram_size: .word 0 /* Obsolete */
vid_mode: .word SVGA_MODE
root_dev: .word 0 /* Filled in by build.c */
boot_flag: .word 0xAA55
这段就是启动扇区,俗称“bootsect”,其实细心的你可能会发现这段代码编译链接后生成的机器码会有512个字节吗,怎么算都没有。这里把犹豫了很长时间呢,还好找到了一个非常重要的文件boot/setup.ld,这是一个链接脚本文件。看一下关键的。
. = 0;
.bstext : { *(.bstext) }
.bsdata : { *(.bsdata) }
. = 497;
.header : { *(.header) }
看到.=0、.=497没,算算 .section ".header", "a"下面这些变量其实刚好为512-497=15个字节。现在应该清楚了。
然后我们说说这个启动扇区,启动如果正常,是不会执行的。现在我们从PC加电开始。
当PC启动后,x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址。PC 机的BIOS将执行某些系统的检测,在物理地址0处开始初始化中断向量。然后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给操作系统Boot Loader。Boot Loader将内核映象从硬盘上读到 RAM 中,也即将setup.elf读到0x90000处,还有vmlinux读到x0100000处,将然后跳转到内核的入口点去运行,也即开始启动操作系统,这个入口点是0x90200处,刚好略过512B启动扇区。如果从软盘启动的话就会直接将启动扇区读到RAM中,然后开始执行的代码就是上面的,于是会打印出: Direct booting from floppy is no longer supported.,也就是bugger_off_msg的第一条信息。现在的内核启动不支持从软盘启动。也就是说真正的开始处在0x90200处。
接着arch/x86/boot/header.S:
# offset 512, entry point
.globl _start
_start: # 当BootLoader执行完后就会跳到这里执行,也就是所谓的0x90200处
# 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:
1:
# Part 2 of the header, from the old setup.S
.ascii "HdrS" # header signature
.word 0x020a # header version number (>= 0x0105)
# or else old loadlin-1.5 will fail)
.globl realmode_swtch
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
start_sys_seg: .word SYSSEG # obsolete and meaningless, but just
# in case something decided to "use" it
.word kernel_version-512 # pointing to kernel version string
# above section of header is compatible
# with loadlin-1.5 (header v1.5). Don't
# change it.
type_of_loader: .byte 0 # 0 means ancient bootloader, newer
# bootloaders know to change this.
# See Documentation/i386/boot.txt for
# assigned ids
# flags, unused bits must be zero (RFU) bit within loadflags
loadflags:
LOADED_HIGH = 1 # If set, the kernel is loaded high
CAN_USE_HEAP = 0x80 # If set, the loader also has set
# heap_end_ptr to tell how much
# space behind setup.S can be used for
# heap purposes.
# Only the loader knows what is free
.byte LOADED_HIGH
setup_move_size: .word 0x8000 # size to move, when setup is not
# loaded at 0x90000. We will move setup
# to 0x90000 then just before jumping
# into the kernel. However, only the
# loader knows how much data behind
# us also needs to be loaded.
code32_start: # here loaders can put a different
# start address for 32-bit code.
.long 0x100000 # 0x100000 = default for big kernel
ramdisk_image: .long 0 # address of loaded ramdisk image
# Here the loader puts the 32-bit
# address where it loaded the image.
# This only will be read by the kernel.
ramdisk_size: .long 0 # its size in bytes
bootsect_kludge:
.long 0 # obsolete
heap_end_ptr: .word _end+STACK_SIZE-512
# (Header version 0x0201 or later)
# space from here (exclusive) down to
# end of setup code can be used by setup
# for local heap purposes.
ext_loader_ver:
.byte 0 # Extended boot loader version
ext_loader_type:
.byte 0 # Extended boot loader type
cmd_line_ptr: .long 0 # (Header version 0x0202 or later)
# If nonzero, a 32-bit pointer
# to the kernel command line.
# The command line should be
# located between the start of
# setup and the end of low
# memory (0xa0000), or it may
# get overwritten before it
# gets read. If this field is
# used, there is no longer
# anything magical about the
# 0x90000 segment; the setup
# can be located anywhere in
# low memory 0x10000 or higher.
ramdisk_max: .long 0x7fffffff
# (Header version 0x0203 or later)
# The highest safe address for
# the contents of an initrd
# The current kernel allows up to 4 GB,
# but leave it at 2 GB to avoid
# possible bootloader bugs.
kernel_alignment: .long CONFIG_PHYSICAL_ALIGN #physical addr alignment
#required for protected mode
#kernel
#ifdef CONFIG_RELOCATABLE
relocatable_kernel: .byte 1
#else
relocatable_kernel: .byte 0
#endif
min_alignment: .byte MIN_KERNEL_ALIGN_LG2 # minimum alignment
pad3: .word 0
cmdline_size: .long COMMAND_LINE_SIZE-1 #length of the command line,
#added with boot protocol
#version 2.06
hardware_subarch: .long 0 # subarchitecture, added with 2.07
# default to 0 for normal x86 PC
hardware_subarch_data: .quad 0
payload_offset: .long ZO_input_data
payload_length: .long ZO_z_input_len
setup_data: .quad 0 # 64-bit physical pointer to
# single linked list of
# struct setup_data
pref_address: .quad LOAD_PHYSICAL_ADDR # preferred load addr
#define ZO_INIT_SIZE (ZO__end - ZO_startup_32 + ZO_z_extract_offset)
#define VO_INIT_SIZE (VO__end - VO__text)
#if ZO_INIT_SIZE > VO_INIT_SIZE
#define INIT_SIZE ZO_INIT_SIZE
#else
#define INIT_SIZE VO_INIT_SIZE
#endif
init_size: .long INIT_SIZE # kernel initialization size
# End of setup header #####################################################
上面这些其实就是和那个hdr下标一块的,组成了一个结构,在x86/include/asm/bootparam.h中,struct setup_header。你可以按照结构体顺序对对,非常符合。当然了,它们都有含义的。具体见表--
其实这个表,对于我们不是很重要,可以选择飘过~,继续看代码。
.section ".entrytext", "ax"
start_of_setup: #上面跳到这里执行真正的代码段
#ifdef SAFE_RESET_DISK_CONTROLLER
# Reset the disk controller. #复位硬盘控制器
movw $0x0000, %ax # Reset disk controller
movb $0x80, %dl # All disks
int $0x13
#endif
# Force %es = %ds
movw %ds, %ax
movw %ax, %es
cld #di,si ++
# Apparently some ancient versions of LILO invoked the kernel with %ss != %ds,
# which happened to work by accident for the old code. Recalculate the stack
# pointer if %ss is invalid. Otherwise leave it alone, LOADLIN sets up the
# stack behind its own code, so we can't blindly put it directly past the heap.
# 一些旧版本的LILO在进入内核时偶尔发生%ss != %ds。
# 如果%ss无效,重计算栈指针,否则,不用管它,
# 装载器LOADLIN在它的代码后面会建立栈,
# 因此,不要盲目把栈直接放在堆后面
movw %ss, %dx
cmpw %ax, %dx # %ds == %ss? # because ax==ds
movw %sp, %dx
je 2f # -> assume %sp is reasonably set #有效ss段
# Invalid %ss, make up a new stack
movw $_end, %dx # _end为setup.elf代码的末尾
testb $CAN_USE_HEAP, loadflags
jz 1f
movw heap_end_ptr, %dx # 看上面_end+STACK_SIZE-512
1: addw $STACK_SIZE, %dx # 给栈分配空间
jnc 2f # 如果溢出,即超过了0xffff
xorw %dx, %dx # Prevent wraparound # 将dx=0
2: # Now %dx should point to the end of our stack space
andw $~3, %dx # dword align (might as well...) # 双字对齐
jnz 3f # 如果没有溢出,则jump to 3f:
movw $0xfffc, %dx # Make sure we're not zero
3: movw %ax, %ss # 这里的ax为上面设置的ds段
movzwl %dx, %esp # Clear upper half of %esp #将esp的高端清0
sti # Now we should have a working stack #开中断
# We will have entered with %cs = %ds+0x20, normalize %cs so
# it is on par with the other segments.
pushw %ds
pushw $6f
lretw # 就是跳到6f:,作用在于将cs设置成ds,和整个setup.elf的装入地址一致
6:
# Check signature at end of setup
# 检查位于setup.elf代码末尾的签名标志“AA55”或者“5A5A”,以确定setup.elf代码是否全部装入。
# 如果没有找到签名标志,说明setup.elf并未完全装入,程序控制转移至setup_bad
cmpl $0x5a5aaa55, setup_sig # setup_sig在boot/setup.ld文件中
jne setup_bad
# Zero the bss # 清空bss段,一些标记在boot/setup.ld中可以找到
movw $__bss_start, %di
movw $_end+3, %cx
xorl %eax, %eax
subw %di, %cx
shrw $2, %cx
rep # if(cx--!=0)[es:di]=eax
# Jump to C code (should not return) # main函数位于boot/main.c,我们的下一站
calll main # call后面还有个l代表main这个地址是long型
# Setup corrupt somehow...
# 发生错误则打印错误信息
setup_bad:
movl $setup_corrupt, %eax
calll puts # 在boot/tty.c中可以找到,打印函数
# 不过这里与C调用约定违背了,应该将字符串首地址入栈才对,这个疑问希望得到大家的帮助?
# Fall through...
# 写个死机函数
.globl die
.type die, @function
die:
hlt
jmp die
.size die, .-die
.section ".initdata", "a"
setup_corrupt:
.byte 7
.string "No setup signature found.../n"
总结一下,做了哪些事情:
1、硬盘复位
2、检查并设置堆栈
3、检查setup.elf是否装入完全
4、将.bss段清0
5、跳入main函数(boot/main.c)
功夫不负有心人,我们终于正式走入了内核源代码,而且分析了第一个文件header.S,感觉这一切来的这么突然,又好像太慢了。不过我们可以自豪的说,这都是努力的成果。虽然我们对代码没有细致分析,这是由于这块内容不是我们关注的重点,而且本身它的复杂性也导致了我们只需略懂即可。如果后面还有充裕的时间,可以在详细分析。
要找到第一个源代码文件不是太困难,它就是始源,注意我们这里全是在x86机器上,内核版本为2.6.36.2。那么arch/x86/boot目录则是我们的入口点,看看里面的Makefile。
setup-y += a20.o bioscall.o cmdline.o copy.o cpu.o cpucheck.o
setup-y += early_serial_console.o edd.o header.o main.o mca.o memory.o
setup-y += pm.o pmjump.o printf.o regs.o string.o tty.o video.o
setup-y += video-mode.o version.o
setup-$(CONFIG_X86_APM_BOOT) += apm.o
# The link order of the video-*.o modules can matter. In particular,
# video-vga.o *must* be listed first, followed by video-vesa.o.
# Hardware-specific drivers should follow in the order they should be
# probed, and video-bios.o should typically be last.
setup-y += video-vga.o
setup-y += video-vesa.o
setup-y += video-bios.o
可以看见setup-y目标,就是我们前面那篇说的setup.elf,我们可以发现header.o,可以想到,就是我们要找的始源。它是由header.S汇编文件产生的,顺便说一下,x86的汇编是AT&T,如果不晓得最好自己把这块学习一下。
来自:arch/x86/boot/header.S:
/*
* header.S
*
* Copyright (C) 1991, 1992 Linus Torvalds
*
* Based on bootsect.S and setup.S
* modified by more people than can be counted
*
* Rewritten as a common file by H. Peter Anvin (Apr 2007)
*
* BIG FAT NOTE: We're in real mode using 64k segments. Therefore segment
* addresses must be multiplied by 16 to obtain their respective linear
* addresses. To avoid confusion, linear addresses are written using leading
* hex while segment addresses are written as segment:offset.
*
*/
#include <asm/segment.h>
#include <generated/utsrelease.h>
#include <asm/boot.h>
#include <asm/e820.h>
#include <asm/page_types.h>
#include <asm/setup.h>
#include "boot.h"
#include "voffset.h"
#include "zoffset.h"
BOOTSEG = 0x07C0 /* original address of boot-sector */
SYSSEG = 0x1000 /* historical load address >> 4 */
#ifndef SVGA_MODE
#define SVGA_MODE ASK_VGA
#endif
#ifndef RAMDISK
#define RAMDISK 0
#endif
#ifndef ROOT_RDONLY
#define ROOT_RDONLY 1
#endif
.code16
.section ".bstext", "ax"
.global bootsect_start
bootsect_start:
# Normalize the start address
ljmp $BOOTSEG, $start2
start2:
movw %cs, %ax #现在的cs=0x7c00
movw %ax, %ds #初始化段寄存器
movw %ax, %es
movw %ax, %ss #注意堆栈段为0x7c00
xorw %sp, %sp
sti #开中断
cld #di,si ++
movw $bugger_off_msg, %si #bugger_off_msg在下面
msg_loop: #打印信息
lodsb #将ds:si处的字节读入al
andb %al, %al
jz bs_die #如果al==0,则跳转到bs_die
movb $0xe, %ah
movw $7, %bx
int $0x10 #INT0x10是BIOS视频中断,打印字符。功能号AH=0x0e,表示在Teletype模式下显示字符,
#AL=字符,BH=页码,BL=前景色(图形模式)
jmp msg_loop
bs_die:
# Allow the user to press a key, then reboot
xorw %ax, %ax
int $0x16 #键盘中断,接收一个字符
int $0x19
# int 0x19 should never return. In case it does anyway,
# invoke the BIOS reset code...
ljmp $0xf000,$0xfff0 #jump to 0xffff0, 重新设置BIOS
.section ".bsdata", "a"
bugger_off_msg:
.ascii "Direct booting from floppy is no longer supported./r/n"
.ascii "Please use a boot loader program instead./r/n"
.ascii "/n"
.ascii "Remove disk and press any key to reboot . . ./r/n"
.byte 0
# Kernel attributes; used by setup. This is part 1 of the
# header, from the old boot sector.
.section ".header", "a"
.globl hdr
hdr: #这个hdr会对应setup_header结构体,待会儿在说
setup_sects: .byte 0 /* Filled in by build.c */
root_flags: .word ROOT_RDONLY
syssize: .long 0 /* Filled in by build.c */
ram_size: .word 0 /* Obsolete */
vid_mode: .word SVGA_MODE
root_dev: .word 0 /* Filled in by build.c */
boot_flag: .word 0xAA55
这段就是启动扇区,俗称“bootsect”,其实细心的你可能会发现这段代码编译链接后生成的机器码会有512个字节吗,怎么算都没有。这里把犹豫了很长时间呢,还好找到了一个非常重要的文件boot/setup.ld,这是一个链接脚本文件。看一下关键的。
. = 0;
.bstext : { *(.bstext) }
.bsdata : { *(.bsdata) }
. = 497;
.header : { *(.header) }
看到.=0、.=497没,算算 .section ".header", "a"下面这些变量其实刚好为512-497=15个字节。现在应该清楚了。
然后我们说说这个启动扇区,启动如果正常,是不会执行的。现在我们从PC加电开始。
当PC启动后,x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址。PC 机的BIOS将执行某些系统的检测,在物理地址0处开始初始化中断向量。然后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给操作系统Boot Loader。Boot Loader将内核映象从硬盘上读到 RAM 中,也即将setup.elf读到0x90000处,还有vmlinux读到x0100000处,将然后跳转到内核的入口点去运行,也即开始启动操作系统,这个入口点是0x90200处,刚好略过512B启动扇区。如果从软盘启动的话就会直接将启动扇区读到RAM中,然后开始执行的代码就是上面的,于是会打印出: Direct booting from floppy is no longer supported.,也就是bugger_off_msg的第一条信息。现在的内核启动不支持从软盘启动。也就是说真正的开始处在0x90200处。
接着arch/x86/boot/header.S:
# offset 512, entry point
.globl _start
_start: # 当BootLoader执行完后就会跳到这里执行,也就是所谓的0x90200处
# 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:
1:
# Part 2 of the header, from the old setup.S
.ascii "HdrS" # header signature
.word 0x020a # header version number (>= 0x0105)
# or else old loadlin-1.5 will fail)
.globl realmode_swtch
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
start_sys_seg: .word SYSSEG # obsolete and meaningless, but just
# in case something decided to "use" it
.word kernel_version-512 # pointing to kernel version string
# above section of header is compatible
# with loadlin-1.5 (header v1.5). Don't
# change it.
type_of_loader: .byte 0 # 0 means ancient bootloader, newer
# bootloaders know to change this.
# See Documentation/i386/boot.txt for
# assigned ids
# flags, unused bits must be zero (RFU) bit within loadflags
loadflags:
LOADED_HIGH = 1 # If set, the kernel is loaded high
CAN_USE_HEAP = 0x80 # If set, the loader also has set
# heap_end_ptr to tell how much
# space behind setup.S can be used for
# heap purposes.
# Only the loader knows what is free
.byte LOADED_HIGH
setup_move_size: .word 0x8000 # size to move, when setup is not
# loaded at 0x90000. We will move setup
# to 0x90000 then just before jumping
# into the kernel. However, only the
# loader knows how much data behind
# us also needs to be loaded.
code32_start: # here loaders can put a different
# start address for 32-bit code.
.long 0x100000 # 0x100000 = default for big kernel
ramdisk_image: .long 0 # address of loaded ramdisk image
# Here the loader puts the 32-bit
# address where it loaded the image.
# This only will be read by the kernel.
ramdisk_size: .long 0 # its size in bytes
bootsect_kludge:
.long 0 # obsolete
heap_end_ptr: .word _end+STACK_SIZE-512
# (Header version 0x0201 or later)
# space from here (exclusive) down to
# end of setup code can be used by setup
# for local heap purposes.
ext_loader_ver:
.byte 0 # Extended boot loader version
ext_loader_type:
.byte 0 # Extended boot loader type
cmd_line_ptr: .long 0 # (Header version 0x0202 or later)
# If nonzero, a 32-bit pointer
# to the kernel command line.
# The command line should be
# located between the start of
# setup and the end of low
# memory (0xa0000), or it may
# get overwritten before it
# gets read. If this field is
# used, there is no longer
# anything magical about the
# 0x90000 segment; the setup
# can be located anywhere in
# low memory 0x10000 or higher.
ramdisk_max: .long 0x7fffffff
# (Header version 0x0203 or later)
# The highest safe address for
# the contents of an initrd
# The current kernel allows up to 4 GB,
# but leave it at 2 GB to avoid
# possible bootloader bugs.
kernel_alignment: .long CONFIG_PHYSICAL_ALIGN #physical addr alignment
#required for protected mode
#kernel
#ifdef CONFIG_RELOCATABLE
relocatable_kernel: .byte 1
#else
relocatable_kernel: .byte 0
#endif
min_alignment: .byte MIN_KERNEL_ALIGN_LG2 # minimum alignment
pad3: .word 0
cmdline_size: .long COMMAND_LINE_SIZE-1 #length of the command line,
#added with boot protocol
#version 2.06
hardware_subarch: .long 0 # subarchitecture, added with 2.07
# default to 0 for normal x86 PC
hardware_subarch_data: .quad 0
payload_offset: .long ZO_input_data
payload_length: .long ZO_z_input_len
setup_data: .quad 0 # 64-bit physical pointer to
# single linked list of
# struct setup_data
pref_address: .quad LOAD_PHYSICAL_ADDR # preferred load addr
#define ZO_INIT_SIZE (ZO__end - ZO_startup_32 + ZO_z_extract_offset)
#define VO_INIT_SIZE (VO__end - VO__text)
#if ZO_INIT_SIZE > VO_INIT_SIZE
#define INIT_SIZE ZO_INIT_SIZE
#else
#define INIT_SIZE VO_INIT_SIZE
#endif
init_size: .long INIT_SIZE # kernel initialization size
# End of setup header #####################################################
上面这些其实就是和那个hdr下标一块的,组成了一个结构,在x86/include/asm/bootparam.h中,struct setup_header。你可以按照结构体顺序对对,非常符合。当然了,它们都有含义的。具体见表--
域名 | 偏移/大小 | 协议版本 | 类型 | 说明 |
setup_sect | 0x1f1/1 | 所有 | 读 | setup代码的大小在512字节扇区中,如果该域为0,则实际值为4,实模式代码由启动扇区(512字节)加上setup代码组成。 |
root_flags | 0x1f2/2 | 所有 | 修改(可选) | 该值为非0,表示根目录仅读,还可以代替地使用命令行选项"ro"或"rw"设置根目录为仅读或读写。 |
syssize | 0x1f4/4 | 2.04+ | 读 | 保护模式代码的尺寸,以16字节段为单位。 |
ram_size | 0x1fa/2 | 所有 | 内核内部 | 不使用 - 为了仅仅给bootsect.S使用。 |
vid_mode | 0x1fa/2 | 所有 | 视频模式控制。 | |
root_dev | 0x1fc/2 | 所有 | 修改(可选) | 缺省的root设备,还可替代地在命令行使用命令选项"root="。root设备对于启动来说是/boot所在目录。 |
boot_flag | 0x1fe/2 | 所有 | 读 | 含有魔数0xAA55,是启动扇区引导结束的标志。 |
jump | 0x200/2 | 2.00+ | 读 | 2个字节,为0xEB和跳转偏移字节。x86短跳转指令编码为0xEB。可用来判定内核头的大小。 |
header | 0x202/4 | 2.00+ | 读 | 含有魔数值"HdrS"(0x53726448)。如果没在偏移0x202处发现 "HdrS" (0x53726448) 魔数值,启动协议版本是旧的,装载旧的内核,即内核映像类型为zImage、不支持initrd、实模式内核必须位于0x90000。 |
version | 0x206/2 | 2.00+ | 读 | 启动协议版本号,格式为(major << 8)+minor,如:版本2.04值为 0x0204。 |
readmode_swtch | 0x208/4 | 2.00+ | 修改(可选) | Boot loader hook。16位实模式远程子例程,在进入保护模式之前立即被调用。缺省的例程是关闭NMI。 |
start_sys | 0x20c/4 | 2.00+ | 读 | 装载的低位段(0x1000),已不用了,仅仅给bootsect.S使用。 |
kernel_version | 0x20e/2 | 2.00+ | 读 | 指向内核版本字符串,可用于显示内核版本给用户,值应小于( 0x200*setup_sects)。例如:该值设为 0x1c00,则内核版本字符串可以内核映像文件的偏移 0x1e00处找到。 |
type_of_loader | 0x210/1 | 2.00+ | 写 | Boot Loader的ID,格式为 0xTV,其中,T为Boot Loader的ID,V为其版本号。ID值列出如下: 0 LILO (0x00保留给2.00版本以前的Boot Loader使用) 1 Loadlin 2 bootsect-loader(0x20, 所有其他值保留) 3 SYSLINUX 4 EtherBoot 5 ELILO 7 GRuB 8 U-BOOT 9 Xen A Gujin B Qemu |
loadflags | 0x211/1 | 2.00+ | 修改 | 启动协议选项标识位掩码。各位说明如下: Bit 0 (读):LOADED_HIGH - 0表示保护模式代码装载在0x10000。 - 1表示保护模式代码装载在0x100000。 Bit 6 (写): KEEP_SEGMENTS :在版本2.07+存在 :- 0表示重装载段寄存器在32位入口处。 :- 1表示不重装载段寄存器在32位入口处,假定%cs %ds %ss %es都设置为基地址为0的平面段。 Bit 7 (写): CAN_USE_HEAP :设置此位为1,表示heap_end_ptr的值有效,heap_end_ptr表示在setup.S后有多少空间可用于堆。如果清除此位,表示一些setup代码功能被关闭。 |
setup_move_size | 0x212/2 | 2.00-2.01 | 修改 | 在使用版本为2.00或2.01时,如果实模式内核不装载在 0x90000,此值表示移动的尺寸。 |
code32_start | 0x214/4 | 2.00+ | 修改 | 它是在内核解压缩之前立即跳转到的32位扁平模式(flat-mode)例程。 Boot Loader用来决定合适的装载地址。除了CS外,没有段建立,用户应设置段到KERNEL_DS (0x18)。完成了应户的hook后,应跳转到用户Boot Loader覆盖写此域之前此域所在的地址。有两种目的修改此域:(1)作为Boot Loader hook。(2)没有安装hook的Boot Loader装载可重定位内核在非标准位置。 |
ramdisk_image | 0x218/4 | 2.00+ | 写 | initrd(initial ramdisk)或ramfs的32位线性地址。 |
ramdisk_size | 0x21c/4 | 2.00+ | 写 | initrd(initial ramdisk)或ramfs映像的大小。 |
bootsect_kludge | 0x220/4 | 2.00+ | 内核内部 | 不再使用,仅仅给bootsect.S使用。 |
heap_end_ptr | 0x224/2 | 2.01+ | 写 | 是在setup结尾后面的空闲内存,它指向setup堆的偏移限制值,当loadflags设置了CAN_USE_HEAP(0x80位)时,此域才有效。heap_end_ptr为相对于setup开始处(0x0200)的值,即绝对值需要减去0x0200。 |
cmd_line_ptr | 0x228/4 | 2.02+ | 写 | 内核命令行的线性地址。内核命令行可以位于setup heap 堆结尾与0xA0000之间的任何位置。它不能位于与实模式代码本身相同的同一64K段。 |
initrd_addr_max | 0x22c/4 | 2.03 | 读 | initrd(initial ramdisk)/ramfs内容占用的最大地址。例如:initrd为131072字节长,此域值为 0x37FFFFFF,则initrd从0x37FE0000.开始。 |
kernel_alignment | 0x230/4 | 2.05+ | 读 | 内核要求的对齐单位(如果relocatable_kernel为true)。 |
relocatable_kernel | 0x234/1 | 2.05+ | 读 | 内核是否对齐。此域为非0,表示内核的保护模式部分可以位于满足kernel_alignment 的任何地址,在loading后,Boot Loader必须设置 code32_start域指向装载的代码或Boot Loader hook。 |
cmdline_size | 0x238/4 | 2.06+ | 读 | 命令行的最大尺寸(不包括结尾符0)。 |
hardware_subarch | 0x23c/4 | 2.07+ | 写 | 此域允许Boot Loader通知内核硬件的低级构架,在并行虚拟化环境中,硬件低级构架环境(如:中断处理、页表处理和访问进程控制寄存器)需要不同处理。此域允许Boot Loader通知内核多个这样的环境。 0x00000000缺省的x86/PC环境 0x00000001lguest 0x00000002Xen |
hardware_subarch_data | 0x240/8 | 2.07+ | 写 | 指向特定硬件子构架的数据。 |
.section ".entrytext", "ax"
start_of_setup: #上面跳到这里执行真正的代码段
#ifdef SAFE_RESET_DISK_CONTROLLER
# Reset the disk controller. #复位硬盘控制器
movw $0x0000, %ax # Reset disk controller
movb $0x80, %dl # All disks
int $0x13
#endif
# Force %es = %ds
movw %ds, %ax
movw %ax, %es
cld #di,si ++
# Apparently some ancient versions of LILO invoked the kernel with %ss != %ds,
# which happened to work by accident for the old code. Recalculate the stack
# pointer if %ss is invalid. Otherwise leave it alone, LOADLIN sets up the
# stack behind its own code, so we can't blindly put it directly past the heap.
# 一些旧版本的LILO在进入内核时偶尔发生%ss != %ds。
# 如果%ss无效,重计算栈指针,否则,不用管它,
# 装载器LOADLIN在它的代码后面会建立栈,
# 因此,不要盲目把栈直接放在堆后面
movw %ss, %dx
cmpw %ax, %dx # %ds == %ss? # because ax==ds
movw %sp, %dx
je 2f # -> assume %sp is reasonably set #有效ss段
# Invalid %ss, make up a new stack
movw $_end, %dx # _end为setup.elf代码的末尾
testb $CAN_USE_HEAP, loadflags
jz 1f
movw heap_end_ptr, %dx # 看上面_end+STACK_SIZE-512
1: addw $STACK_SIZE, %dx # 给栈分配空间
jnc 2f # 如果溢出,即超过了0xffff
xorw %dx, %dx # Prevent wraparound # 将dx=0
2: # Now %dx should point to the end of our stack space
andw $~3, %dx # dword align (might as well...) # 双字对齐
jnz 3f # 如果没有溢出,则jump to 3f:
movw $0xfffc, %dx # Make sure we're not zero
3: movw %ax, %ss # 这里的ax为上面设置的ds段
movzwl %dx, %esp # Clear upper half of %esp #将esp的高端清0
sti # Now we should have a working stack #开中断
# We will have entered with %cs = %ds+0x20, normalize %cs so
# it is on par with the other segments.
pushw %ds
pushw $6f
lretw # 就是跳到6f:,作用在于将cs设置成ds,和整个setup.elf的装入地址一致
6:
# Check signature at end of setup
# 检查位于setup.elf代码末尾的签名标志“AA55”或者“5A5A”,以确定setup.elf代码是否全部装入。
# 如果没有找到签名标志,说明setup.elf并未完全装入,程序控制转移至setup_bad
cmpl $0x5a5aaa55, setup_sig # setup_sig在boot/setup.ld文件中
jne setup_bad
# Zero the bss # 清空bss段,一些标记在boot/setup.ld中可以找到
movw $__bss_start, %di
movw $_end+3, %cx
xorl %eax, %eax
subw %di, %cx
shrw $2, %cx
rep # if(cx--!=0)[es:di]=eax
# Jump to C code (should not return) # main函数位于boot/main.c,我们的下一站
calll main # call后面还有个l代表main这个地址是long型
# Setup corrupt somehow...
# 发生错误则打印错误信息
setup_bad:
movl $setup_corrupt, %eax
calll puts # 在boot/tty.c中可以找到,打印函数
# 不过这里与C调用约定违背了,应该将字符串首地址入栈才对,这个疑问希望得到大家的帮助?
# Fall through...
# 写个死机函数
.globl die
.type die, @function
die:
hlt
jmp die
.size die, .-die
.section ".initdata", "a"
setup_corrupt:
.byte 7
.string "No setup signature found.../n"
总结一下,做了哪些事情:
1、硬盘复位
2、检查并设置堆栈
3、检查setup.elf是否装入完全
4、将.bss段清0
5、跳入main函数(boot/main.c)
功夫不负有心人,我们终于正式走入了内核源代码,而且分析了第一个文件header.S,感觉这一切来的这么突然,又好像太慢了。不过我们可以自豪的说,这都是努力的成果。虽然我们对代码没有细致分析,这是由于这块内容不是我们关注的重点,而且本身它的复杂性也导致了我们只需略懂即可。如果后面还有充裕的时间,可以在详细分析。
相关文章推荐
- linux内核学习(10)启动全过程概述之一
- linux内核学习(12)启动全过程概述之三
- linux内核学习(9)启动全过程概述之内核映像结构
- linux内核学习(12)启动全过程概述之三
- linux内核学习(9)启动全过程概述之内核映像结构
- 移植u-boot学习笔记1-----实验及分析启动过程之概述
- Linux内核启动过程概述
- Linux内核分析第三周学习博客——跟踪分析Linux内核的启动过程
- 操作系统学习(一)--概述启动过程
- 学习Linux内核启动过程:从start_kernel到init
- 学习笔记 --- LINUX内核启动第二阶段分析(不考虑自解压过程)
- linux内核分析学习笔记:用gdb跟踪linux内核启动过程
- Linux内核设计第三周学习总结 跟踪分析Linux内核的启动过程
- Linux内核启动过程概述
- linux内核启动过程学习总结
- Linux内核分析之跟踪内核启动过程
- Android系统启动过程学习
- 用Bochs学习Minix(3)-调试启动过程
- Linux内核启动过程和Bootloader(总述)