“自己动手写操作系统”学习笔记(一)

(一)

1. 在virtual box内安装ubuntu 12.04LTS版本,设定共享文件夹。

virtual box设置共享文件夹

在ubuntu的/media文件夹下可见到共享的文件夹,以sf_共享名为文件夹名。


ubuntu下可见的共享文件夹.JPG

2. 共享文件夹的用户和组都是root,复制时要使用sudo,将D盘下ainbed/os文件夹拷贝到用户当前目录

sudo cp -r /media/sf_D_DRIVE/ainbed/os .(-r代表将目录下的文件及子目录全部复制)


复制共享文件夹到当前目录.JPG

3. 为了操作该目录下的文件,将用户和组改为当前用户。

sudo chown -R os waj:waj (-R也是必不可少)


改变目录所属用户和组.JPG

4. 安装了bochs.

命令:sudo apt-get install bochs
使用命令 bochs -q -f boshsrc仿真时出现


dlopen failed for module 'x'.JPG

百度搜索一下,原因是bochs-x模块没有安装。
命令:sudo apt-get install bochs-x
紧接着又出了问题:bochs-bin: symbol lookup error: /usr/lib/bochs/plugins/libbx_x.so: undefined symbol: XpmCreatePixmap
解决办法:sudo apt-get install bochs-sdl


bochs-sdl.JPG

进入配置文件:bochsrc,注释掉keyboard_mapping:,加入:display_library:sdl后恢复正常。


bochsrc.JPG
Hello OS World!.JPG

(二))

1. 在window主机和virtual box内ubuntu主机共享文件夹,相互可以更改内容

共享文件夹.JPG

输入命令:
mkdir /home/waj/wind
chmod 777 wind
sudo mount -t vboxsf D_DRIVE /home/waj/wind
cd wind
ls可显示windows主机D盘的内容

2. 软盘镜像操作---写入给定扇区

dd if=/dev/zero of=a.img bs=512 count=2880
可以生成一个空白软盘镜像
dd if=boot.bin of=a.img bs=512 count=1 seek=0 conv=notrunc
在第一扇区写入boot.bin(512个字节),seek指明开始写的扇区,count指明写多少扇区,notrunc指明文件不截断。

    org 07c00h          ; 编译器将程序定位到此,写入引导扇区,BIOS将引导
    mov ax, cs          ; 扇区加载到此位置,然后跳转执行第一条指令
    mov ds, ax
    mov es, ax
    call    DispStr         ; 调用显示字符串例程
    jmp $           ; 无限循环
DispStr:
    mov ax, BootMessage
    mov bp, ax          ; ES:BP = 串地址
    mov cx, 17          ; CX = 串长度
    mov ax, 01301h      ; AH = 13,  AL = 01h
    mov bx, 000ch       ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
    mov dl, 0
    int 10h         ; 10h 号中断
    ret
BootMessage:        db  "Hello, Wangxiyue!"
times   510-($-$$)  db  0   ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw  0xaa55              ; 结束标志


3. 软盘镜像操作---通过文件系统写入

dd if=/dev/zero of=a.img bs=512 count=2880
先生成一个空白镜像。
sudo mkfs -t msdos a.img
将软盘镜像以FAT12文件系统格式化
sudo mount -t msdos -o loop a.img ./fd
将软盘镜像以fat12格式挂载到当前fd目录下
sudo cp boot.com ./fd
可以将boot.com复制到fd目录即为a.img的内部
sudo umount fd
将挂载到fd的软盘镜像卸载,a.img内部已经有了boot.com文件,可以在虚拟机中使用

org 0100h           ; 此时,nasm boot.asm -o boot.com
mov ax, cs          ; 第一个软盘放DOS系统,第二个软盘放此程序,
mov ds, ax                      ; 可在DOS下运行程序
mov es, ax
call    DispStr         ; 调用显示字符串例程
jmp $           ; 无限循环

4. linux系统的软盘操作

当系统有软驱时,可在系统中找到/dev/fd0这个块设备。
sudo mount -t msdos /dev/fd0 /mnt/floppy
将设备挂载到软盘,可以通过fat12文件系统操作访问
注意:软驱必须有软盘才能挂载。

5. 测试软盘启动和通过dos调试程序

以下是直接启动时的bochsrc配置文件

###############################################################
# Configuration file for Bochs
###############################################################

# how much memory the emulated machine will have
megs: 32

# filename of ROM images
romimage: file=BIOS-bochs-latest
vgaromimage: file=VGABIOS-lgpl-latest

# what disk images will be used
#floppya: 1_44=D:\ainbed\os\freedos.img, status=inserted
floppya: 1_44=D:\ainbed\os\windbg\a.img, status=inserted

# hard disk
#ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
#ata0-master: type=disk, path="c.img", cylinders=306, heads=4, spt=17

# choose the boot disk.
boot: a

# where do we send log messages?
# log: bochsout.txt

# disable the mouse
mouse: enabled=0

# enable key mapping, using US layout as default.
keyboard: keymap=keymaps/x11-pc-us.map

如果需要在dos下运行,则a盘放freedos.img,b盘放boot.com程序

利用bochsdbg -q -f bochsrc调试程序
b 0x7c00设置断点
sreg查看寄存器的值
s单步执行(进入子函数)、n单步(跳过子函数)、

(三)

1. 在调试com程序的时候,如何设置断点?

在com程序开头,都有org 0100h这句话,但是断点设置b 0x0100b并没有用。
在网上找的方法,程序里加上xchg bx,bx,然后再配置文件加上magic_break:enanbled=1

magic_break.JPG

2. 进入保护模式代码分析

; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================

%include    "pm.inc"    ; 常量, 宏, 以及一些说明

org 07c00h
    00007c00(初始地址):jmp  LABEL_BEGIN(7c24)

[SECTION .gdt]
; GDT
;                              段基址,       段界限     , 属性
00007c04(GDT基地址):LABEL_GDT:    Descriptor       0,                0, 0           ; 空描述符
00007c0c:LABEL_DESC_CODE32: Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段
00007c14:LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, DA_DRW         ; 显存首地址
; GDT 结束

GdtLen      equ $ - LABEL_GDT   ; GDT长度
00007c1c:GdtPtr     dw  GdtLen - 1  ; GDT界限
00007c1e:       dd  0       ; GDT基地址

; GDT 选择子
SelectorCode32      equ LABEL_DESC_CODE32   - LABEL_GDT
SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]

[SECTION .s16]
[BITS   16]
00007c24:LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h

    ; 初始化 32 位代码段描述符
    (7c2f)xor   eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32(7c80)
    mov word [LABEL_DESC_CODE32 + 2](7c0e), ax(0)
    shr eax, 16
    mov byte [LABEL_DESC_CODE32 + 4](7c10), al
    mov byte [LABEL_DESC_CODE32 + 7](7c13), ah

    ; 为加载 GDTR 作准备
    (7c4c)xor   eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT(7c04)        ; eax <- gdt 基地址
    mov dword [GdtPtr + 2](7c1e), eax   ; [GdtPtr + 2] <- gdt 基地址

    ; 加载 GDTR
    lgdt    [GdtPtr](00007c04,0x17=24-1)

    ; 关中断
    cli

    ; 打开地址线A20
    in  al, 92h
    or  al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    mov eax, cr0
    or  eax, 1
    mov cr0, eax

    ; 真正进入保护模式
    (7c77)jmp   dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入 cs,
                    ; 并跳转到 Code32Selector:0  处
; END of [SECTION .s16]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS   32]

LABEL_SEG_CODE32:
    (00007c80)mov   ax, SelectorVideo(0x10第二个选择子)
    mov gs, ax          ; 视频段选择子(目的)

    mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    mov al, 'P'
    mov [gs:edi], ax

    ; 到此停止
    jmp $

SegCode32Len    equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

(四)

1. LDT的使用调试

[SECTION .gdt]
; GDT
;                                         段基址,       段界限     , 属性
LABEL_GDT:         Descriptor       0,                 0, 0         ; 空描述符
LABEL_DESC_NORMAL: Descriptor       0,            0ffffh, DA_DRW    ; Normal 描述符
LABEL_DESC_CODE32: Descriptor       0,  SegCode32Len - 1, DA_C + DA_32  ; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor       0,            0ffffh, DA_C      ; 非一致代码段, 16
LABEL_DESC_DATA:   Descriptor       0,       DataLen - 1, DA_DRW+DA_DPL1    ; Data
LABEL_DESC_STACK:  Descriptor       0,        TopOfStack, DA_DRWA + DA_32; Stack, 32 位
LABEL_DESC_LDT:    Descriptor       0,        LDTLen - 1, DA_LDT    ; LDT
LABEL_DESC_VIDEO:  Descriptor 0B8000h,            0ffffh, DA_DRW    ; 显存首地址
; GDT 结束

GdtLen      equ $ - LABEL_GDT   ; GDT长度
GdtPtr      dw  GdtLen - 1  ; GDT界限
        dd  0       ; GDT基地址

此处共有8个描述符,共占据0x40字节,从0x3224:0x0104开始(这个是.com程序分配到的段地址和0x0100的开始地址决定的,一个jmp指令占据4个字节。
下面有6个字节存储GDT表长度和基地址。
数据段从0x3224:0x160开始,共60个字节(32字节对齐)。
堆栈段从0x3224:0x1a0开始,共0x200个字节。

16位代码段从0x3224:0x3a0开始,程序开始的jmp就跳转到这里。

2. 16位实模式向保护模式的转移

[SECTION .s16](3224:03a0)
[BITS   16]
LABEL_BEGIN:
    xchg    bx, bx(bochs调试的magic_break)
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h

    mov [LABEL_GO_BACK_TO_REAL+3], ax(3224:0538,在16位段里面,用于返回实模式)
    mov [SPValueInRealMode], sp(3224:0160)

    ; 初始化 16 位代码段描述符
    mov ax, cs
    movzx   eax, ax
    shl eax, 4
    add eax, LABEL_SEG_CODE16(3224:0520)
    mov word [LABEL_DESC_CODE16 + 2], ax(3224:011e第3个描述符)
    shr eax, 16
    mov byte [LABEL_DESC_CODE16 + 4], al
    mov byte [LABEL_DESC_CODE16 + 7], ah

    ; 初始化 32 位代码段描述符
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32(3224:04ac)
    mov word [LABEL_DESC_CODE32 + 2], ax(3224:0116第2个描述符)
    shr eax, 16
    mov byte [LABEL_DESC_CODE32 + 4], al
    mov byte [LABEL_DESC_CODE32 + 7], ah

    ; 初始化数据段描述符
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_DATA(3224:0160,数据段的基地址)
    mov word [LABEL_DESC_DATA + 2], ax(3224:0126第4个描述符)
    shr eax, 16
    mov byte [LABEL_DESC_DATA + 4], al
    mov byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆栈段描述符
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_STACK(3224:01a0,堆栈的基地址)
    mov word [LABEL_DESC_STACK + 2], ax(3224:012e第5个描述符)
    shr eax, 16
    mov byte [LABEL_DESC_STACK + 4], al
    mov byte [LABEL_DESC_STACK + 7], ah

    ; 初始化 LDT 在 GDT 中的描述符
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_LDT(3224:0540,LDT表的基地址,在GDT表中占据一个描述符)
    mov word [LABEL_DESC_LDT + 2], ax(3224:0136第6个描述符)
    shr eax, 16
    mov byte [LABEL_DESC_LDT + 4], al
    mov byte [LABEL_DESC_LDT + 7], ah

    ; 初始化 LDT 中的描述符
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_CODE_A(3224:0560,局部段的物理地址,描述符在LDT表中,在LDTR中会存储LDT段的选择子)
    mov word [LABEL_LDT_DESC_CODEA + 2], ax(3224:0542,LDT中第一个描述符)
    shr eax, 16
    mov byte [LABEL_LDT_DESC_CODEA + 4], al
    mov byte [LABEL_LDT_DESC_CODEA + 7], ah

    ; 为加载 GDTR 作准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT(0x3220:0104)     ; eax <- gdt 基地址
    mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址

    ; 加载 GDTR
    lgdt    [GdtPtr]

    ; 关中断
    cli

    ; 打开地址线A20
    in  al, 92h
    or  al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    mov eax, cr0
    or  eax, 1
    mov cr0, eax

    ; 真正进入保护模式
    jmp dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处

3. 在32位代码段跳入局部段执行代码

    mov ax, SelectorData
    mov ds, ax          ; 数据段选择子
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择子
    mov ax, SelectorStack
    mov ss, ax          ; 堆栈段选择子
    mov esp, TopOfStack
    ; Load LDT
    mov ax, SelectorLDT
    lldt    ax                            ;这一句是执行局部任务的关键,首先找到该LDT在GDT中的描述符,得到基地址,------然后根据跳转选择子和第2位在LDT表找到局部段的描述符
    jmp SelectorLDTCodeA:0  ; 根据局部描述符的基地址和跳转目标偏移跳入局部任务

(五)

1. 由0级跳往3级(高到低)

用返回指令retf、iret都可以实现。
返回时,要在内核堆栈顶含有3级代码的入口地址、选择子、3级堆栈地址、选择子,然后用retf指令可进入3级代码。

    mov ax, SelectorTSS
    ltr ax  ; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。

    push    SelectorStack3
    push    TopOfStack3
    push    SelectorCodeRing3
    push    0
    retf        ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。

2. 由3级进入0级

特别要注意3级进入0级,需要切换堆栈,要准备TSS段、TSS描述符、在tr寄存器加载TSS选择子,TSS段里准备好0级堆栈。
使用调用门由3级进入0级,跳转代码是:

; CodeRing3
[SECTION .ring3]
ALIGN   32
[BITS   32]
LABEL_CODE_RING3:    ;cs=0x2b,base=32ae0,limit=0x1b;ss=0x43,base=32600,limit=0x1ff,ldtr=0x50,gdtr= 0x32444,tr=0x50
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择子(目的)

    mov edi, (80 * 14 + 0) * 2  ; 屏幕第 14 行, 第 0 列。
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    mov al, '3'
    mov [gs:edi], ax

    call    SelectorCallGateTest:0  ; 测试调用门(有特权级变换),将打印字母 'C'。
    jmp $
SegCodeRing3Len equ $ - LABEL_CODE_RING3
; END of [SECTION .ring3]

SelectorCallGateTest:0前面是调用门选择子,3级RPL,调用门的DPL是3极,选择子是目标代码段的选择子,目标代码段的DPL是0级,中间的特权检验由硬件完成。

调用门工作时,发现特权级变化,要暂存当前SS和SP,然后从TSS段加载新的0级SS和SP,在0级堆栈保持3级SS和SP,保存参数,保持CS和IP。如果从0级返回,可原样恢复SS、SP、CS、IP都是由硬件自动完成。

(六)

1. 如何开启分页

需要的东西:页目录、页表、CR3存放页目录地址、CR0最高位置1
通过段:偏移得到的线性地址分为三部分:头十位是页目录索引、页目录一项代表4M,一个目录项占据4k页表、通过索引乘以4k加上页表基地址,可得对应页表地址。中间十位是页表项的偏移索引,乘以4后可得偏移字节,在前22位写入实际物理页面(4k对齐)与属性。最后十位是页内偏移。

2. 代码分析

[SECTION .gdt]
; GDT
;                                         段基址,       段界限     , 属性
LABEL_GDT:      Descriptor         0,                 0, 0              ; 空描述符
LABEL_DESC_NORMAL:  Descriptor         0,            0ffffh, DA_DRW         ; Normal 描述符
LABEL_DESC_FLAT_C:  Descriptor             0,           0fffffh, DA_CR | DA_32 | DA_LIMIT_4K; 0 ~ 4G
LABEL_DESC_FLAT_RW: Descriptor             0,           0fffffh, DA_DRW | DA_LIMIT_4K   ; 0 ~ 4G
LABEL_DESC_CODE32:  Descriptor         0,  SegCode32Len - 1, DA_CR | DA_32      ; 非一致代码段, 32
LABEL_DESC_CODE16:  Descriptor         0,            0ffffh, DA_C           ; 非一致代码段, 16
LABEL_DESC_DATA:    Descriptor         0,   DataLen - 1, DA_DRW         ; Data
LABEL_DESC_STACK:   Descriptor         0,        TopOfStack, DA_DRWA | DA_32        ; Stack, 32 位
LABEL_DESC_VIDEO:   Descriptor   0B8000h,            0ffffh, DA_DRW         ; 显存首地址
; GDT 结束

现代操作系统使用平坦模式:即逻辑地址=线性地址,基本的代码段和数据段都是偏移为0,段界限为4G。

PagingDemo:
    mov ax, cs
    mov ds, ax
    mov ax, SelectorFlatRW
    mov es, ax

    push    LenFoo
    push    OffsetFoo
    push    ProcFoo
    call    MemCpy
    add esp, 12

    push    LenBar
    push    OffsetBar
    push    ProcBar
    call    MemCpy
    add esp, 12

    push    LenPagingDemoAll
    push    OffsetPagingDemoProc
    push    ProcPagingDemo
    call    MemCpy
    add esp, 12

    mov ax, SelectorData
    mov ds, ax          ; 数据段选择子
    mov es, ax

    call    SetupPaging     ; 启动分页

    call    SelectorFlatC:ProcPagingDemo
    call    PSwitch         ; 切换页目录,改变地址映射关系
    call    SelectorFlatC:ProcPagingDemo

    ret

主代码要掌握实际的物理内存布局:
call SetpupPaging调用时,线性地址=物理地址,因此开启分页对代码的运行没有影响
call SelectorFlatC:ProcPagingDemo使用线性地址0x401000调用一个函数,这个线性地址对应Foo函数,显示Foo,
call PSwitch切换页表,改变一个4k页面的映射,线性地址0x401000映射到物理地址0x301000,这个物理地址是函数bar,
因此,再次调用SelectorFlatC:ProcPagingDemo将调用函数bar,显示bar。

3. PSwitch代码分析

    mov eax, LinearAddrDemo    ;0x4010000
    shr eax, 22                          ;右移22位得到最高十位,页目录项索引
    mov ebx, 4096                      ;乘以4k得到该页表相对于页表基地址的偏移
    mul ebx
    mov ecx, eax                        ;偏移存储在ecx
    mov eax, LinearAddrDemo
    shr eax, 12  
    and eax, 03FFh  ; 1111111111b (10 bits)  ;获取线性地址中间十位,为页表索引
    mov ebx, 4                  ;乘以4得到页表项相对于该页表的偏移
    mul ebx
    add eax, ecx                ;相加得到页表项相对于页表基地址的偏移
    add eax, PageTblBase1  ;得到该页表项的物理地址。
    mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW    ;将该表项映射的物理地址和属性填入(物理地址是4k页面,只占据前20位,最后12位填写属性。

    mov eax, PageDirBase1
    mov cr3, eax
    jmp short .3
.3:
    nop

(七)

1. 保护模式下的中断

必备条件:内存中有IDT表、每个IDT表项为中断门描述符指明GDT选择子和偏移(内存平坦模式)、将IDT表基地址加载到IDTR寄存器。
当中断发生时,通过中断向量和IDT基地址找到中断门描述符、通过选择子找到对应的段基址(一般为0,中断门主要完成特权级变换)、通过偏移找到对应的中断处理程序、通过TR寄存器和TSS段找到当前进程的进程栈、将用户态的SS和SP压栈、EFLAGS压栈、CS和IP压栈,然后跳转到对用的中断处理程序。
以下为中断描述符表:

; IDT
[SECTION .idt]
ALIGN   32
[BITS   32]
LABEL_IDT:
; 门                        目标选择子,            偏移, DCount, 属性
%rep 32
        Gate    SelectorCode32, SpuriousHandler,      0, DA_386IGate
%endrep
.020h:      Gate    SelectorCode32,    ClockHandler,      0, DA_386IGate
%rep 95
        Gate    SelectorCode32, SpuriousHandler,      0, DA_386IGate
%endrep
.080h:      Gate    SelectorCode32,  UserIntHandler,      0, DA_386IGate

IdtLen      equ $ - LABEL_IDT
IdtPtr      dw  IdtLen - 1  ; 段界限
        dd  0       ; 基地址
; END of [SECTION .idt]

以下为初始化IDTR寄存器:

    ; 为加载 IDTR 作准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_IDT      ; eax <- idt 基地址
    mov dword [IdtPtr + 2], eax ; [IdtPtr + 2] <- idt 基地址
    ; 关中断
    ;cli
    ; 加载 IDTR
    lidt    [IdtPtr]

2. 中断的返回

中断返回所做的事情:iret指令执行后LDT寄存器保持当前进程的选择子、TSS段保存当前进程的进程栈SP、依次弹出用户态CS、IP、EFLAGS、SS、SP恢复用户态程序的运行。

你可能感兴趣的:(“自己动手写操作系统”学习笔记(一))