• xv6源码解析(一)——系统启动


    01 系统启动

    系统启动:完成了该系统在启动过程中的配置(bootloader),将进入保护模式后堆栈的起点设在0x7c00地址处,并且引导进入kernel程序

    02 系统启动过程

    xv6操作系统过程和x86上的其他操作系统启动过程类似,先从主板上的BIOS代码开始,再转入启动扇区代码,然后转入内核启动代码,最后创建init以及shell进程

    image-20221111102426957

    03 实地址模式与保护模式

    image-20221111103034754

    04 段式管理

    实地址模式段式管理

    image-20221111104029393

    保护模式段式管理

    image-20221111104131709

    image-20221111104443288

    05 kernel分页机制

    image-20221111111904700

    image-20221111112147815

    image-20221111112241810

    06 代码分析

    BIOS

    BIOS的主要功能

    • 完成一些硬件自检的功能
    • 从启动盘里读取第一扇区的512字节数据到内存0x7c00中去,这512字节的代码就是bootloader
    • 把PC指针设置为此地址,并把控制权转交给bootloader

    bootloader

    bootasm.S

    p72页

    (1)实模式代码

    (2)保护模式代码

    (3)call bootmain

    #include "asm.h"
    #include "memlayout.h"
    #include "mmu.h"
    
    # Start the first CPU: switch to 32-bit protected mode, jump into C.
    # The BIOS loads this code from the first sector of the hard disk into
    # memory at physical address 0x7c00 and starts executing in real mode
    # with %cs=0 %ip=7c00.
    
    .code16                       # Assemble for 16-bit mode
    .globl start
    start:            
      cli                         # BIOS enabled interrupts; disable   //  关中断
    
      # Zero data segment registers DS, ES, and SS.     // 四个段寄存器清0
      xorw    %ax,%ax             # Set %ax to zero
      movw    %ax,%ds             # -> Data Segment
      movw    %ax,%es             # -> Extra Segment
      movw    %ax,%ss             # -> Stack Segment
    
      # Physical address line A20 is tied to zero so that the first PCs 
      # with 2 MB would run software that assumed 1 MB.  Undo that.
    seta20.1:
      inb     $0x64,%al               # Wait for not busy
      testb   $0x2,%al
      jnz     seta20.1
    
      movb    $0xd1,%al               # 0xd1 -> port 0x64
      outb    %al,$0x64
    
    seta20.2:
      inb     $0x64,%al               # Wait for not busy
      testb   $0x2,%al
      jnz     seta20.2
    
      movb    $0xdf,%al               # 0xdf -> port 0x60
      outb    %al,$0x60
    
      # Switch from real to protected mode.  Use a bootstrap GDT that makes
      # virtual addresses map directly to physical addresses so that the
      # effective memory map doesn't change during the transition.
      lgdt    gdtdesc                 //  为进入保护模式作准备  设置全局段描述符表
      movl    %cr0, %eax
      orl     $CR0_PE, %eax
      movl    %eax, %cr0
    
    //PAGEBREAK!
      # Complete the transition to 32-bit protected mode by using a long jmp
      # to reload %cs and %eip.  The segment descriptors are set up with no
      # translation, so that the mapping is still the identity mapping.
      ljmp    $(SEG_KCODE<<3), $start32
    
    .code32  # Tell assembler to generate 32-bit code now.
    start32:
      # Set up the protected-mode data segment registers           //  设置各个段的索引
      movw    $(SEG_KDATA<<3), %ax    # Our data segment selector
      movw    %ax, %ds                # -> DS: Data Segment
      movw    %ax, %es                # -> ES: Extra Segment
      movw    %ax, %ss                # -> SS: Stack Segment
      movw    $0, %ax                 # Zero segments not ready for use
      movw    %ax, %fs                # -> FS
      movw    %ax, %gs                # -> GS
    
      # Set up the stack pointer and call into C.    //  设置堆栈指针  0x7c00 ~ 0x7d00
      movl    $start, %esp
      call    bootmain
    
      # If bootmain returns (it shouldn't), trigger a Bochs
      # breakpoint if running under Bochs, then loop.
      movw    $0x8a00, %ax            # 0x8a00 -> port 0x8a00
      movw    %ax, %dx
      outw    %ax, %dx
      movw    $0x8ae0, %ax            # 0x8ae0 -> port 0x8a00
      outw    %ax, %dx
    spin:
      jmp     spin
    
    # Bootstrap GDT
    .p2align 2                                # force 4 byte alignment
    gdt:
      SEG_NULLASM                             # null seg
      SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)   # code seg
      SEG_ASM(STA_W, 0x0, 0xffffffff)         # data seg
    
    gdtdesc:
      .word   (gdtdesc - gdt - 1)             # sizeof(gdt) - 1
      .long   gdt                             # address gdt
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    bootmain.c

    P83页

    (1)将xv6的kernel文件读到内存

    // Boot loader.
    //
    // Part of the boot block, along with bootasm.S, which calls bootmain().
    // bootasm.S has put the processor into protected 32-bit mode.
    // bootmain() loads an ELF kernel image from the disk starting at
    // sector 1 and then jumps to the kernel entry routine.
    
    #include "types.h"
    #include "elf.h"
    #include "x86.h"
    #include "memlayout.h"
    
    #define SECTSIZE  512
    
    void readseg(uchar*, uint, uint);
    
    void
    bootmain(void)
    {
      struct elfhdr *elf;
      struct proghdr *ph, *eph;
      void (*entry)(void);
      uchar* pa;
    
      elf = (struct elfhdr*)0x10000;  // scratch space
    
      // Read 1st page off disk
      readseg((uchar*)elf, 4096, 0);
    
      // Is this an ELF executable?
      if(elf->magic != ELF_MAGIC)
        return;  // let bootasm.S handle error
    
      // Load each program segment (ignores ph flags).
      ph = (struct proghdr*)((uchar*)elf + elf->phoff);
      eph = ph + elf->phnum;
      for(; ph < eph; ph++){
        pa = (uchar*)ph->paddr;
        readseg(pa, ph->filesz, ph->off);
        if(ph->memsz > ph->filesz)
          stosb(pa + ph->filesz, 0, ph->memsz - ph->filesz);
      }
    
      // Call the entry point from the ELF header.
      // Does not return!
      entry = (void(*)(void))(elf->entry);
      entry();
    }
    
    void
    waitdisk(void)
    {
      // Wait for disk ready.
      while((inb(0x1F7) & 0xC0) != 0x40)
        ;
    }
    
    // Read a single sector at offset into dst.
    void
    readsect(void *dst, uint offset)
    {
      // Issue command.
      waitdisk();
      outb(0x1F2, 1);   // count = 1
      outb(0x1F3, offset);
      outb(0x1F4, offset >> 8);
      outb(0x1F5, offset >> 16);
      outb(0x1F6, (offset >> 24) | 0xE0);
      outb(0x1F7, 0x20);  // cmd 0x20 - read sectors
    
      // Read data.
      waitdisk();
      insl(0x1F0, dst, SECTSIZE/4);
    }
    
    // Read 'count' bytes at 'offset' from kernel into physical address 'pa'.
    // Might copy more than asked.
    void
    readseg(uchar* pa, uint count, uint offset)
    {
      uchar* epa;
    
      epa = pa + count;
    
      // Round down to sector boundary.
      pa -= offset % SECTSIZE;
    
      // Translate from bytes to sectors; kernel starts at sector 1.
      offset = (offset / SECTSIZE) + 1;
    
      // If this is too slow, we could read lots of sectors at a time.
      // We'd write more to memory than asked, but it doesn't matter --
      // we load in increasing order.
      for(; pa < epa; pa += SECTSIZE, offset++)
        readsect(pa, offset);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    kernel

    entry.S

    # The xv6 kernel starts executing in this file. This file is linked with
    # the kernel C code, so it can refer to kernel symbols such as main().
    # The boot block (bootasm.S and bootmain.c) jumps to entry below.
            
    # Multiboot header, for multiboot boot loaders like GNU Grub.
    # http://www.gnu.org/software/grub/manual/multiboot/multiboot.html
    #
    # Using GRUB 2, you can boot xv6 from a file stored in a
    # Linux file system by copying kernel or kernelmemfs to /boot
    # and then adding this menu entry:
    #
    # menuentry "xv6" {
    # 	insmod ext2
    # 	set root='(hd0,msdos1)'
    # 	set kernel='/boot/kernel'
    # 	echo "Loading ${kernel}..."
    # 	multiboot ${kernel} ${kernel}
    # 	boot
    # }
    
    #include "asm.h"
    #include "memlayout.h"
    #include "mmu.h"
    #include "param.h"
    
    # Multiboot header.  Data to direct multiboot loader.
    .p2align 2
    .text
    .globl multiboot_header
    multiboot_header:
      #define magic 0x1badb002
      #define flags 0
      .long magic
      .long flags
      .long (-magic-flags)
    
    # By convention, the _start symbol specifies the ELF entry point.
    # Since we haven't set up virtual memory yet, our entry point is
    # the physical address of 'entry'.
    .globl _start
    _start = V2P_WO(entry)
    
    # Entering xv6 on boot processor, with paging off.
    .globl entry
    entry:
      # Turn on page size extension for 4Mbyte pages
      movl    %cr4, %eax
      orl     $(CR4_PSE), %eax
      movl    %eax, %cr4
      # Set page directory
      movl    $(V2P_WO(entrypgdir)), %eax
      movl    %eax, %cr3
      # Turn on paging.
      movl    %cr0, %eax
      orl     $(CR0_PG|CR0_WP), %eax
      movl    %eax, %cr0
    
      # Set up the stack pointer.
      movl $(stack + KSTACKSIZE), %esp
    
      # Jump to main(), and switch to executing at
      # high addresses. The indirect call is needed because
      # the assembler produces a PC-relative instruction
      # for a direct jump.
      mov $main, %eax
      jmp *%eax
    
    .comm stack, KSTACKSIZE
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    main.c

    int
    main(void)
    {
      kinit1(end, P2V(4*1024*1024)); // phys page allocator
      kvmalloc();      // kernel page table
      mpinit();        // detect other processors
      lapicinit();     // interrupt controller
      seginit();       // segment descriptors
      cprintf("\ncpu%d: starting xv6\n\n", cpunum());
      picinit();       // another interrupt controller
      ioapicinit();    // another interrupt controller
      consoleinit();   // console hardware
      uartinit();      // serial port
      pinit();         // process table
      tvinit();        // trap vectors
      binit();         // buffer cache
      fileinit();      // file table
      ideinit();       // disk
      if(!ismp)
        timerinit();   // uniprocessor timer
      startothers();   // start other processors
      kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
      initsem();
      sharememinit();  // shminit  //这里添加初始化
      userinit();      // first user process
      mpmain();        // finish this processor's setup
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    python web开发过程
    Shell编程——正则表达式
    7.Consul服务注册与发现
    服务器的环境要求
    【Office】超简单,Excel快速完成不规则合并单元格排序
    Nginx之防盗链及高可用解读
    设计模式-21-Proxy模式(代理模式)
    并发之wait/notify说明
    YOLOv7改进:小目标遮挡物性能提升(SEAM、MultiSEAM),涨点神器!!!
    2、开发环境安装
  • 原文地址:https://blog.csdn.net/qq_41945053/article/details/127813077