您的位置:首页 > 运维架构 > Linux

linux内核学习(10)启动全过程概述之一

2011-01-07 10:10 441 查看
下面这段时间,我要好好分析一下内核启动过程的源代码,怎么来分析,而且更好的和网友们进行交流,我想,最好的方式莫过于采用赵炯博士编著的《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。你可以按照结构体顺序对对,非常符合。当然了,它们都有含义的。具体见表--

域名
偏移/大小
协议版本
类型
说明
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,感觉这一切来的这么突然,又好像太慢了。不过我们可以自豪的说,这
都是努力的成果。虽然我们对代码没有细致分析,这是由于这块内容不是我们关注的重点,而且本身它的复杂性也导致了我们只需略懂即可。如果后面还有充裕的时
间,可以在详细分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: