• 操作系统内核Hack:(四)内核雏形


    操作系统内核Hack:(四)内核雏形

    在本系列的前一篇文章《操作系统内核Hack:(三)BootLoader制作》中,我们制作出了一个两阶段引导BootLoader,并进入了一个内核的空壳main函数。本文我们继续完善引导程序和内核,让内核的内容一点点充实起来。本文的代码可以参考GitHub上的MiniOS分支kernel_prototype


    1.周边代码修改

    1.1 常量和宏提取

    像各个模块的内存这种常量,会经常被引导,所以就提取出一个单独的文件var.inc。同理,保护模式相关的常量和宏都提取到了pm.inc,这里主要是拷贝了Orange’s的代码和注释。

    ;   var.inc
    ; ############################
    ;   Constants
    ; ############################
    
    SETUPLEN    equ 4
    BOOTSEG     equ 0x07c0
    INITSEG     equ 0x9000
    SETUPSEG    equ 0x9020
    SYSSEG      equ 0x1000
    NEWSYSSEG   equ 0x0000
    
    MEMSIZE     equ 0       ; INITSEG:MEMSIZE
    
    
    ;   pm.inc
    ; ############################
    ;   Macros
    ; ############################
    
    ; 描述符类型
    DA_32       equ 4000h   ; 32 位段
    DA_LIMIT_4K equ 8000h   ; 段界限粒度为 4K 字节
    
    ; 存储段描述符类型
    DA_DR       equ 90h ; 存在的只读数据段类型值
    DA_DRW      equ 92h ; 存在的可读写数据段属性值
    DA_C        equ 98h ; 存在的只执行代码段属性值
    DA_CR       equ 9Ah ; 存在的可执行可读代码段属性值
    
    ; Descriptor macro
    %macro Descriptor 3
        dw  %2 & 0FFFFh             ; Limit 1
        dw  %1 & 0FFFFh             ; Base addr 1
        db  (%1 >> 16) & 0FFh           ; Base addr 2
        dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; Attr 1 + Limit 2 + Attr 2
        db  (%1 >> 24) & 0FFh           ; Base addr 3
    %endmacro

    1.2 Makefile修改

    因为include/的提取和后面第二阶段引导过程拆分的影响,所以Makefile也要做相应的修改:

    • 新加编译参数ASINC,编译bootsect.asm、setup.asm和head.asm时都要用到
    • 链接参数LDFLAGS中的entry地址改为startup_32,这也是head.asm中的代码起始地址
    • SYSSIZE不再除以16,而是system的真实大小,后面bootsect.asm中会用到
    ASINC   = -I include/
    ASFLAGS = -f elf
    LD  = ld
    # -Ttext org -e entry -s(omit all symbol info)
    # -x(discard all local symbols) -M(print memory map)
    LDFLAGS = -Ttext 0 -e startup_32 --oformat binary -s -x -M
    
        ...
    
    # SYSSIZE = system file size
    boot1/bootsect: boot1/bootsect.asm include/var.inc system/system
        (echo -n "SYSSIZE equ ";ls -l system/system | grep system 
            | cut -d " " -f 5 | tr '12' ' ') > tmp.asm
        cat $< >> tmp.asm
        $(AS) $(ASINC) -o $@ tmp.asm
        rm -f tmp.asm
    
    boot2/setup:    boot2/setup.asm include/var.inc include/pm.inc
        $(AS) $(ASINC) -o $@ $<
    
    system/system:  system/init/head.o system/init/main.o
        $(LD) $(LDFLAGS) 
        system/init/head.o 
        system/init/main.o 
        -o $@ > System.map
    
    system/init/head.o:     system/init/head.asm include/var.inc include/pm.inc
        $(AS) $(ASFLAGS) $(ASINC) -o $@ $<
    
        ...

    2.第一阶段引导完善

    2.1 system加载

    之前为了简化代码,避免被细枝末节干扰,所以从软盘加载system到内存的代码写的非常简单,只读取了一个扇区。随着我们的system模块越来越大,这样是肯定不行的,于是就想参考Linux源码做一下改进。

    结果不看不知道,一细研究还真吓一跳每个磁道有18个扇区,每个磁头有80个磁道,根据要加载的数据大小,自己负责切换扇区、磁道、磁头号。就这样还不行,要加载到的内存位置是用[es:bx]表示的,也就是说bx偏移逐渐增加最后要溢出的时候,我们还得修改es重置bx,避免它溢出。原来想写一段通用的从软盘加载数据的代码这么费劲啊!先看看我写的,因为之前已经加载过bootsect和setup了,所以第一个磁道还需读取13个扇区,之后的每个磁道读取18个扇区,最后一个磁道根据还剩余多少扇区没读决定要读取多少,先不考虑磁头和bx溢出问题。结果已经来回调试改进了好几遍了,还是有问题。

    ; 4) Load system module at 0x10000
    ;    Assume SYSSIZE < 1 head
    ;    1 track = 18 sectors * 512b = 9216(b)
    ;    1 head = 80 tracks * 9216 = 720(kb)
    _Sector:        db 0
    _Track:         db 0
    Sector          equ _Sector-$$
    Track           equ _Track-$$
    SECT_PER_TRACK  equ 18
    LEFT_IN_TRACK1  equ SECT_PER_TRACK - 1 - SETUPLEN
    
    load_system:
            mov     ax, SYSSEG
            mov     es, ax
            mov     bx, 0000h               ; es:bx = target(es=1000h,bx=0)
            mov     dx, 0000h               ; dx    = driver(dh)/head(dl)
            mov     cx, 0006h               ; cx    = track(ch)/sector(cl)
    
            mov     ax, SYSSIZE
            add     ax, 511
            shr     ax, 9                   ; al    = (SYSSIZE + 511) / 512, sectors to read
            mov     byte [Sector], al
    
            cmp     al, LEFT_IN_TRACK1
            jbe     .loop
            mov     al, LEFT_IN_TRACK1      ; al    = (al <= 13) ? al : 13
    .loop
            mov     ah, 02h                 ; ah    = service id(ah=02 means read)
            int     13h                     ; ignore any error
    
            sub     byte [Sector], al       ; remainingSector -= al
            cmp     byte [Sector], 0
            je      ok_load_system
    
            xor     ah, ah
            shl     ax, 9
            add     bx, ax                  ; offset += (al * 512)
    
            add     byte [Track], 1
            mov     ch, byte [Track]        ; track++
            mov     cl, 1                   ; start at first sector
    
            xor     ax, ax
            mov     al, byte [Sector]
            cmp     al, SECT_PER_TRACK
            jbe     .loop
            mov     al, SECT_PER_TRACK      ; al    = (al <= 18) ? al : 18
            jmp     .loop

    最后发现一次读取跨磁道的扇区也没关系,Bochs的BIOS支持一次最多读取72个扇区。于是就放弃了,先读取最多72个吧,对于现阶段的system的规模是暂时够用了,到时再改吧。此外,要注意的是对要加载的扇区数的计算:这里SYSSIZE是system的实际大小,而不是Linux中所谓的click数(实际size加15后左移了4位)。并且为了避免丢失余数的差一问题,我们要先加上511。

    ; 4) Load system module at 0x10000
    ;    Assume SYSSIZE < 72 sectors (36864)
    ;    1 track = 18 sectors * 512b = 9216(b)
    ;    1 head = 80 tracks * 9216 = 720(kb)
    MAX_ONE_READ    equ 72
    
    load_system:
        mov     ax, SYSSEG
        mov     es, ax
        mov     bx, 0000h       ; es:bx = target(es=1000h,bx=0)
        mov     dx, 0000h       ; dx    = driver(dh)/head(dl)
        mov     cx, 0006h       ; cx    = track(ch)/sector(cl)
    
        mov     ax, SYSSIZE
        add     ax, 511
        shr     ax, 9           ; al    = (SYSSIZE + 511) / 512 sectors to read
    
        cmp     al, MAX_ONE_READ
        jbe     .loop
        mov     al, MAX_ONE_READ    ; al    = (al <= 72) ? al : 72
    .loop
        mov     ah, 02h         ; ah    = service id(ah=02 means read)
        int     13h         ; ignore any error

    2.2 关闭软驱马达

    至此所有要加载的数据就都加载完了,所以我们可以关掉软驱的马达了。这样可以关闭软盘控制器FDC、禁止DMA和中断请求。具体细节有待深入研究。

    ; 5) Kill motor
    ok_load_system:
        mov     dx, 0x3f2       ; floppy controller port
        mov     al, 0           ; floppy A
        outb                ; output al to dx port

    3.第二阶段引导拆分

    在上一篇文章中,在setup.asm中进入了保护模式,并执行了一段32位的代码。我们其实可以将进入保护模式之后的内核初始化工作继续填到setup.asm这段32位代码中,但这样做不如Linux 0.11的方式优雅,即将这部分工作放到system模块的头部去完成。缺点是可能引导过程有些零散,但优点就是因为system会被加载到0x0处,所以后续初始化的页目录表和页表、重放置后的GDT都会在低地址,安全、集中且易于管理,这在我们的上一篇文章中也提到了。

    3.1 上半部:setup.asm

    setup.asm首先读取BIOS中的有用信息保存到0x9000,即覆盖了bootsect的内存位置,因为它已经没有用了。然后将system拷贝到0x0低地址,进入保护模式后就跳转到system。

    %include "var.inc"
    %include "pm.inc"
    
    ; ############################
    ;   Booting Process
    ; ############################
    
    [SECTION .s16]
    [BITS   16]
    
    ; 1) Read memory info from BIOS
        mov     ax, INITSEG
        mov     ds, ax          ; save to bootsect space
        mov     ah, 0x88
        int     0x15
        mov     [MEMSIZE], ax       ; ax=3c00h (15360kb=15mb)
    
    ; 2) Move system to 0x0000
    ;    round-1: 10000~1ffff => 0000~ffff
    ;       ...
    ;    round-5: 80000~8ffff => 70000~7ffff
    ; 
    ; NOTE: 8000h word = 10000h byte
    WordPerMove     equ 8000h
    
    move_system:
        mov     ax, 0h
    .loop
        mov     es, ax
        add     ax, 1000h
        mov     ds, ax
        mov     cx, WordPerMove     ; cx    = counter
        xor     si, si          ; ds:si = source
        xor     di, di          ; es:di = target
        rep     movsw           ; move
    
        cmp     ax, INITSEG
        jne     .loop
    
    ; 3) Enter protection mode
        mov     ax, cs
        mov     ds, ax
        mov     es, ax
        mov     ss, ax
        mov     sp, 0100h
    
        ; 3.1) Load gdt to gdtr
        xor     eax, eax
        mov     ax, ds
        shl     eax, 4
        add     eax, LABEL_GDT      ; eax <- gdt base addr
        mov     dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt base addr
        lgdt    [GdtPtr]
    
        ; 3.2) Disable interrupt
        cli
    
        ; 3.3) Enable A20 addr line
        in  al, 92h
        or  al, 00000010b
        out     92h, al
    
        ; 3.4) Set PE in cr0
        mov     eax, cr0
        or      eax, 1
        mov     cr0, eax
    
        ; 3.5) Jump to protective mode!
        jmp     dword SelectorSystem:0  ; 0x0000:0x0
    
    
    [SECTION .gdt]
    ;                                Base Addr,        Limit,   Attribute
    LABEL_GDT:      Descriptor      0h,           0h, 0h
    LABEL_DESC_SYSTEM:  Descriptor      0h,       0ffffh, DA_CR | DA_32 | DA_LIMIT_4K
    LABEL_DESC_DATA:    Descriptor  0h,       0ffffh, DA_DRW | DA_32 | DA_LIMIT_4K
    LABEL_DESC_VIDEO:   Descriptor 0B8000h,       0ffffh, DA_DRW
    
    GdtLen      equ $ - LABEL_GDT
    GdtPtr      dw  GdtLen - 1      ; GDT limit
            dd  0           ; GDT base addr
    
    SelectorSystem  equ LABEL_DESC_SYSTEM - LABEL_GDT
    SelectorData    equ LABEL_DESC_DATA - LABEL_GDT
    SelectorVideo   equ LABEL_DESC_VIDEO - LABEL_GDT

    读取BIOS中内存信息的方式有几种,目前setup.s中用的是最简单的一种,也是Linux 0.11中用的方式。代码只有三行,极其简单。获取结果保存在ax中,单位是KB,不是Byte:

        mov     ah, 0x88
        int     0x15
        mov     [MEMSIZE], ax       ; ax=3c00h (15360kb=15mb)

    但这种方式的缺点也同样明显,就是最大只支持64MB。因为ax只有16位,最大能表示65536。所以在Orange’s中作者用了另一种更为强大的方式,ax=0xe820的int 0x15中断,除了能获得内存大小外,还能获得到内存分布。当然缺点就是代码比前面这种方式要麻烦多了,所以这里就不细说了,知道有多种获取BIOS中内存信息的方式就可以了。

    3.2 下半部:head.asm

    这就是我们新拆分出来的head.asm,它将各个段寄存器置为内核的代码段Selector后,就开始内核初始化的工作了:

    1. 直接写显存的方式打印一条消息
    2. 重置GDT的地址。至于GDT的内容则与setup.asm的一样
    3. 将main函数的地址压到栈上,这样setup_paging就会将它当做调用的返回地址,执行ret时就会跳转到main.c中的main函数了
    4. 开启分页管理
    %include "var.inc"
    %include "pm.inc"
    
    extern main
    
    global startup_32
    
    pdt:
    
    
    [SECTION .text]
    ALIGN   32
    [BITS   32]
    
    startup_32:
        mov     ax, 16      ; SelectorData
        mov     ds, ax
        mov     es, ax
        mov     ss, ax
        mov     esp, TopOfStack
    
    ; 1) Print welcome message
        mov     ax, 24      ; SelectorVideo
        mov     gs, ax
        mov     ah, 0Ch
        mov     ebx, 0
        mov     ecx, len
        mov     edx, Message
    
    .loop:
        mov     edi, ebx
        add     edi, (80 * 20)  ; (80 * row + col) * 2
        imul    edi, 2
        mov     al, byte [edx]
        mov     [gs:edi], ax
    
        inc     ebx
        dec     ecx
        inc     edx
        cmp     ecx, 0h
        jne     .loop
    
    ; 2) Reset GDTR
        lgdt    [GdtPtr]
    
    ; 3) Prepare return address
        push    main
        jmp     setup_paging
    
    
    ; Temporary data and stack, will be overriden later
    Message:
        db  "Welcome to MiniOS"
    len     equ $ - Message
    
    
    ; LinearAddr[31~22] = 10 bits = 1024 entry (* 4B = 4096B)
    ; So PDT has 1024 entries (1024 page tables, occupy 4096b totally)
    ; LinearAddr[21~12] = 10 bits = 1024 entry (* 4B = 4096B)
    ; So PD  has 1024 entries (1024 pages, occupy 4096b totally)
    ; LinearAddr[11~0]  = 12 bits = 4096 byte 
    ; So offset (page size) is 4096b
    PdtSize     equ     1024
    PtSize      equ     1024
    EntrySize   equ     4
    PageSize    equ     4096
    
    times   PdtSize*EntrySize-($-$$) db 0
    
    pg0:
    times   PtSize*EntrySize    db 0
    
    pg1:
    times   PtSize*EntrySize    db 0
    
    pg2:
    times   PtSize*EntrySize    db 0
    
    pg3:
    times   PtSize*EntrySize    db 0
    
    
    ; 4) Setup paging
    PgRw        equ     111h
    
    ALIGN   32
    setup_paging:
    
        ; 4.1) Clear page space
        mov     ecx, PdtSize + PtSize       ; counter = 5*1024
        xor     eax, eax
        xor     edi, edi
        cld                     ; DF=0: edi move forward
        rep     stosd               ; move eax => [es:edi] by dword
    
        ; 4.2) Fill page dir
        mov     dword [pdt], pg0 + PgRw     ; 111h(7): read/write page
        mov     dword [pdt+04h], pg1 + PgRw 
        mov     dword [pdt+08h], pg2 + PgRw
        mov     dword [pdt+0ch], pg3 + PgRw
    
        ; 4.3) Fill page table
        ;      pg0~3 can represent 0h ~ fff000h (16MB) memory space
    
        ; 0x3ffc: start addr of last entry of last PT
        mov     edi, (pg3 + PtSize * EntrySize) - EntrySize     
    
        ; 0xfff000: start addr of last page represented by last entry
        mov     eax, ((4 * PtSize * PageSize) - PageSize) + PgRw    
    
        std                     ; DF=1: edi move backward
    .loop:
        stosd                   ; move eax => [es:edi] by dword
        sub     eax, PageSize
        jge     .loop
    
        ; 4.4) Set cr3 (PDBR, Page-Dir Base address Register)
        xor     eax, eax
        mov     cr3, eax
    
        ; 4.5) Set PG bit of cr0 to enable paging 
        mov     eax, cr0
        or  eax, 80000000h
        mov     cr0, eax
    
        ; 4.6) Transfer control to main()
        ret
    
    
    ; Temporary stack space
    times   100h    db  0
    TopOfStack  equ $ 
    
    
    ;[SECTION .gdt]
    ;                                Base Addr,        Limit,   Attribute
    LABEL_GDT:      Descriptor      0h,           0h, 0h
    LABEL_DESC_SYSTEM:  Descriptor      0h,       0ffffh, DA_CR | DA_32 | DA_LIMIT_4K
    LABEL_DESC_DATA:    Descriptor  0h,       0ffffh, DA_DRW | DA_32 | DA_LIMIT_4K
    times   253     dd  0x0, 0x0        ; space for LDT and TSS
    
    GdtLen      equ $ - LABEL_GDT
    GdtPtr      dw  GdtLen - 1      ; GDT limit
            dd  LABEL_GDT       ; GDT base addr
    
    SelectorSystem  equ LABEL_DESC_SYSTEM - LABEL_GDT
    SelectorData    equ LABEL_DESC_DATA - LABEL_GDT

    代码有些长,但比较清晰,真正的难点在于我们之前没有接触到的分页管理机制。其实我们此刻不是必须开启分页管理,但为了避免麻烦,我们这次就多做一点,把分页给弄好,这样以后就没有后顾之忧了。

    3.2.1 内存位置

    可能大家刚才看上面代码时没有注意标签,现在后看一下就会发现精心放置好的标签,包括pdt、pg0~3、以及后面的栈空间和GDT。这些标签和代码在运行时对应的内存空间非常重要,都是内核最重要的数据,所以它们的位置绝对不是随意放置的:

    • pdt放在最开头,使页目录表覆盖掉head.asm的部分代码
    • setup_paging位于pg3后,避免自己把自己覆盖掉
    • 栈放在setup_paging,作为临时的内核栈,因为main函数地址需要入栈
    • GDT放在最后
    --------pdt-----------
    | 0x0000 | 0000 0000 |
    |  ...   |    ...    |
    | 0x0FFC | 0000 0000 |
    |-------pg0----------|
    | 0x1000 | 0000 0000 |
    |  ...   |    ...    |
    | 0x1FFC | 0000 0000 |
    |-------pg1----------|
    | 0x2000 | 0000 0000 |
    |  ...   |    ...    |
    | 0x2FFC | 0000 0000 |
    |-------pg2----------|
    | 0x3000 | 0000 0000 |
    |  ...   |    ...    |
    | 0x3FFC | 0000 0000 |
    |-------pg3----------|
    | 0x4000 | 0000 0000 |
    |  ...   |    ...    |
    | 0x4FFC | 0000 0000 |
    |  ...   |    ...    |
    |  ...   |    ...    |
    |------stack---------|
    |  ...   |    ...    |
    |-------gdt----------|
    |  ...   |    ...    |

    3.2.2 代码解释

    《Linux 0.11中的页目录表及页表内容分析》作者对Linux 0.11中setup_paging处的代码进行了详细分析,清晰易懂,非常棒!

    首先解释一下cld和std两个命令的用处,其实很简单:“在字符串的比较、赋值、读取等一系列和rep连用的操作中,di或si是可以自动增减的而不需要人来加减它的值,cld即告诉程序si,di向前移动,std指令为设置方向,告诉程序si,di向后移动”。

    下面就重点说一下PDT和PT的初始化过程,为了使代码尽可能的清晰,很多“魔数”都提取成了常量。

        ; 4.1) Clear page space
        mov     ecx, PdtSize + PtSize       ; counter = 5*1024
        xor     eax, eax
        xor     edi, edi
        cld                     ; DF=0: edi move forward
        rep     stosd               ; move eax => [es:edi] by dword
    
        ; 4.2) Fill page dir
        mov     dword [pdt], pg0 + PgRw     ; 111h(7): read/write page
        mov     dword [pdt+04h], pg1 + PgRw 
        mov     dword [pdt+08h], pg2 + PgRw
        mov     dword [pdt+0ch], pg3 + PgRw
    
        ; 4.3) Fill page table
        ;      pg0~3 can represent 0h ~ fff000h (16MB) memory space
    
        ; 0x3ffc: start addr of last entry of last PT
        mov     edi, (pg3 + PtSize * EntrySize) - EntrySize     
    
        ; 0xfff000: start addr of last page represented by last entry
        mov     eax, ((4 * PtSize * PageSize) - PageSize) + PgRw    
    
        std                     ; DF=1: edi move backward
    .loop:
        stosd                   ; move eax => [es:edi] by dword
        sub     eax, PageSize
        jge     .loop

    3.2.3 PDT和PT的样子

    PDT和PT到底是什么样子呢?要是看完前面的代码解释还是觉得很抽象的话,我们就直观的看看初始化成功后,内存从低到高的模样!

    --------pdt-----------
    | 0x0000 | 0000 1111 | => pg0
    | 0x0004 | 0000 2111 | => pg1
    | 0x0008 | 0000 3111 | => pg2
    | 0x000C | 0000 4111 | => pg3
    | 0x0010 | 0000 0000 |
    |  ...   |    ...    |
    | 0x0FFC | 0000 0000 |
    |-------pg0----------|     Physical Address
    | 0x1000 | 0000 0111 | => [00000000~00000FFF]
    | 0x1004 | 0000 1111 | => [00001000~00001FFF]
    | 0x1008 | 0000 2111 | => [00002000~00002FFF]
    |  ...   |    ...    |
    | 0x1FFC | 003F F111 | => [003FF000~003FFFFF]
    |-------pg1----------|
    | 0x2000 | 0040 0111 | => [00400000~00400FFF]
    | 0x2004 | 0040 1111 | => [00401000~00401FFF]
    | 0x2008 | 0040 2111 | => [00402000~00402FFF]
    |  ...   |    ...    |
    | 0x2FFC | 007F F111 | => [007FF000~007FFFFF]
    |-------pg2----------|
    | 0x3000 | 0080 0111 | => [00800000~00800FFF]
    | 0x3004 | 0080 1111 | => [00801000~00801FFF]
    | 0x3008 | 0080 2111 | => [00802000~00802FFF]
    |  ...   |    ...    |
    | 0x3FFC | 00BF F111 | => [00BFF000~00BFFFFF]
    |-------pg3----------|
    | 0x4000 | 00C0 0111 | => [00C00000~00C00FFF]
    | 0x4004 | 00C0 1111 | => [00C01000~00C01FFF]
    | 0x4008 | 00C0 2111 | => [00C02000~00C02FFF]
    |  ...   |    ...    |
    | 0x4FFC | 00FF F111 | => [00CFF000~00CFFFFF]

    下面就运行起来Bochs,验证一下页表是否初始化成功了。我们查看几个关键位置就可以了,比如页目录表(0x0000),四个页表的开头部分(0x1000, 0x2000, 0x3000, 0x4000),以及pg4的最末尾部分(0x4ffc)。

    (0) Breakpoint 1, 0x00005047 in ?? ()
    Next at t=15473080
    (0) [0x0000000000005047] 0008:00005047 (unk. ctxt): xor eax, eax              ; 31c0
    <bochs:3> xp /32bx 0x00000
    [bochs]:
    0x00000000 <bogus+       0>:    0x11    0x11    0x00    0x00    0x11    0x21    0x00    0x00
    0x00000008 <bogus+       8>:    0x11    0x31    0x00    0x00    0x11    0x41    0x00    0x00
    0x00000010 <bogus+      16>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
    0x00000018 <bogus+      24>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
    
    <bochs:4> xp /32bx 0x01000
    [bochs]:
    0x00001000 <bogus+       0>:    0x11    0x01    0x00    0x00    0x11    0x11    0x00    0x00
    0x00001008 <bogus+       8>:    0x11    0x21    0x00    0x00    0x11    0x31    0x00    0x00
    0x00001010 <bogus+      16>:    0x11    0x41    0x00    0x00    0x11    0x51    0x00    0x00
    0x00001018 <bogus+      24>:    0x11    0x61    0x00    0x00    0x11    0x71    0x00    0x00
    
    <bochs:5> xp /32bx 0x02000
    [bochs]:
    0x00002000 <bogus+       0>:    0x11    0x01    0x40    0x00    0x11    0x11    0x40    0x00
    0x00002008 <bogus+       8>:    0x11    0x21    0x40    0x00    0x11    0x31    0x40    0x00
    0x00002010 <bogus+      16>:    0x11    0x41    0x40    0x00    0x11    0x51    0x40    0x00
    0x00002018 <bogus+      24>:    0x11    0x61    0x40    0x00    0x11    0x71    0x40    0x00
    
    <bochs:6> xp /32bx 0x03000
    [bochs]:
    0x00003000 <bogus+       0>:    0x11    0x01    0x80    0x00    0x11    0x11    0x80    0x00
    0x00003008 <bogus+       8>:    0x11    0x21    0x80    0x00    0x11    0x31    0x80    0x00
    0x00003010 <bogus+      16>:    0x11    0x41    0x80    0x00    0x11    0x51    0x80    0x00
    0x00003018 <bogus+      24>:    0x11    0x61    0x80    0x00    0x11    0x71    0x80    0x00
    
    <bochs:8> xp /4bx 0x04ffc
    [bochs]:
    0x00004ffc <bogus+       0>:    0x11    0xf1    0xff    0x00

    4.宝贵的参考资料

    个人感觉底层编程的学习曲线非常陡峭,要积累好多知识,爬过好多的“坑”,才能走到这一步,所以好的学习资料是非常重要的。以下就是我学习过程中常用的资料,它们的用法是:以《Linux内核完全剖析》为主,如果看不懂就去找《Orange’s:一个操作系统实现》中对应的章节对比学习。渐渐熟悉Linux的代码后,就参照Linux实现我们的操作系统。如果碰到NASM语法的相关问题,就去看一下博古以通今的博客,作者用NASM重写的代码还是靠谱的,只不过下不到全部代码了。

    4.1 Linux 0.11源码

    《完全剖析》中给出的代码包不是很方便,有热心的网友已经做了优化,“一键”就可直接编译运行起来Linux 0.11

    4.2 用NASM重写Linux 0.11

    《用nasm语言重新实现linux-0.11 bootsect.s(博古以通今)》
    《用nasm语言重新实现linux-0.11 setup.s (博古以通今)》
    《nasm重写linux-0.11 head.s (博古以通今)》

    4.3 NASM汇编指令

    官方文档提供了一份NASM汇编指令列表,令我惊讶的是这份列表貌似并不全,有些查不到的指令如jge也是可用的。

    此外,汇编语言没有高级语言那些控制结构,所以jmp对于实现逻辑就非常重要了,这是一份总结的不错的各种跳转指令的列表

  • 相关阅读:
    作业三
    源代码版本管理与项目管理软件的认识与github的注册
    每周更新的学习进度表
    电脑四则运算出题
    软件工程问题
    自我介绍
    2016.2.14-2016.2.21 中大信(北京)工程造价咨询有限公司实习有感
    《软件工程》课程总结
    结对编程项目---四则运算
    作业三:代码规范、代码复审、PSP
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157590.html
Copyright © 2020-2023  润新知