Linux Kernel :: Boot Process

bootloader
Today Linux kernel has to be booted by bootloaders like GRUB2. They are all designed following boot protocol.
header.S
The header.S includes legacy boot sector and setup code. Bootloader will jump to position at an offset of 0x200 (over the legacy part) where is labeled as _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
1:
        //rest of header.S

Then the address of next executed instruction will be start_of_setup.

Note: 1f means local label 1. A short jump contains opcode and offset from next instruction(pc). 
In our case, the offset is start_of_setup - 1:, and the value of pc is address of label 1.

Now, we get into start_of_setup.

start_of_setup:
    movw    %ds, %ax
    movw    %ax, %es
    cld

This snippet dose nothing but assigning %ds to %es.

    movw    %ss, %dx
    cmpw    %ax, %dx    # %ds == %ss?
    movw    %sp, %dx
    je  2f      # -> assume %sp is reasonably set

After assignment, CPU will compare ss and ds, and assign sp to dx. If ss equals ds, CPU will jump to 2:. If not, the ss is invalid (like some old versions of LILO did), a new one have to be made up. Now, let’s go deep into the hardest part in header.S.

    movw    $_end, %dx
    testb   $CAN_USE_HEAP, loadflags
    jz  1f
    movw    heap_end_ptr, %dx
1:  addw    $STACK_SIZE, %dx
    jnc 2f
    xorw    %dx, %dx    # Prevent wraparound

When ss is invalid, kernel will put the value of _end (the address of the end of the setup code, which is declared in setup.ld) into dx and check the loadflags header field using the testb instruction to see whether we can use the heap. loadflags is defined as:

#define LOADED_HIGH (1<<0)
#define QUIET_FLAG (1<<5)
#define KEEP_SEGMENTS (1<<6)
#define CAN_USE_HEAP (1<<7)

and, as we can read in the boot protocol:

Field name: loadflags
This field is a bitmask.
Bit 7 (write): CAN_USE_HEAP
Set this bit to 1 to indicate that the value entered in the
heap_end_ptr is valid. If this field is clear, some setup code
functionality will be disabled.

So, if CAN_USE_HEAP bit of loadflags is set to 1, then assign heap_end_ptr to dx and execute next instruction whose address is 1:. If not, we just jump to 1:.

1:  addw    $STACK_SIZE, %dx
    jnc 2f
    xorw    %dx, %dx    # Prevent wraparound

There are two scenarios:

1. dx = heap_end_ptr + $STACK_SIZE (CAN_USE_HEAP is set)
2. dx = _end + $STACK_SIZE (CAN_USE_HEAP is not set)

heap_end_ptr = _end+STACK_SIZE-512, and _end is the address of the end of the setup code.

Note: heap_end_ptr seems to get changed several times, so you may check it out when you read this 
article.

Finally, we have a stack now, but we have to align it.

2:  # Now %dx should point to the end of our stack space
    andw    $~3, %dx  # dword align (might as well...)
    jnz 3f
    movw    $0xfffc, %dx   # Make sure we're not zero
3:  movw    %ax, %ss
    movzwl  %dx, %esp   # Clear upper half of %esp
    sti         # Now we should have a working stack

$~3 means inverting 3 (11b) to 0 (00b). 00b ‘and’ dx will set the last 2 bits of dx to 0 so that dx can be divided by 4 without remainder, which is so called 4 bytes alignment. The value of ax in 3: equals that in ds. Sti means start interrupt, so we enable system interruption now.

Tip: If you wonder why we call that 4 bytes alignment when an address can be divided by 4 without
remainder, let's do some calculation. If address A is N bytes alignment,  so is A + N. 
Since the unit of memory size in computers is byte, there are N bytes between A and A + N. 
You get your answer now. As to why we have to do alignment, it is a little bit complicated, 
so just google, xD.

As Linux boot protocol stated, registers will be initialized with these values:

segment = grub_linux_real_target >> 4;
state.gs = state.fs = state.es = state.ds = state.ss = segment;
state.cs = segment + 0x20;

When cs is (segment + 0x20), the first executed instruction will be located 0x200 offset from real model code. In other word, we skip legacy boot sector, and execute the short jump instruction. Therefore, we try to normalize cs.

# We will have entered with %cs = %ds+0x20, normalize %cs so
# it is on par with the other segments.
    pushw   %ds
    pushw   $6f
    lretw

Lretw has 2 parameters, the first is segment of return address, and the second is offset. Now, cs = ds, ip = 6f.

6:
# Check signature at end of setup
    cmpl    $0x5a5aaa55, setup_sig
    jne setup_bad

I’m not sure how dose the signature work, Sorry.

# Zero the bss
    movw    $__bss_start, %di
    movw    $_end+3, %cx #for 4 bytes alignment
    xorl    %eax, %eax
    subw    %di, %cx
    shrw    $2, %cx
    rep; stosl

“subw %di, %cx” means put (cx - di) into cx, which is size of bss section. Then make cx be dived by 4. The stosl instruction is used to repeatedly store the value of eax (zero) into es:di , automatically increasing(if CLD called) di by 4 until cx(minus one per time) reaches zero. The net effect of this code is that zeros are written through all words in memory from __bss_start to _end.

# Jump to C code (should not return)
    calll   main

Finally, we reach the C program part! The rest part of header.S is:

# Setup corrupt somehow...
setup_bad:
    movl    $setup_corrupt, %eax
    calll   puts
    # 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"

If you have any question about it, just google. I’ve had enough of it.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值