• 自制操作系统流程笔记(一)


    1.第一节:最小的操作系统(引导扇区)

    1.1环境准备

    1.vmware虚拟机:

    建立一个新的空白的虚拟机,命名为Tinix.添加软盘,使用自制的映像文件

    虚拟机设置:

      

    2.nasm编译器

    3.notepad++

    1.2制作映像文件

    1.boot.asm文件

        org 07c00h
        mov ax,cs
        mov ds,ax
        mov es,ax
        call DispStr
        jmp $
    DispStr:
        mov ax,BootMessage
        mov bp,ax
        mov cx,16
        mov ax,01301h
        mov bx,000ch
        mov dl,0
        int 10h
        ret
    BootMessage: db "Hello, OS world!"
    times 510-($-$$) db 0
    dw 0xaa55

    2.使用nasm编译

    cmd.exe中使用命令:

    nasm boot.asm -o boot.bin

    1.3 启动系统

    将boot.bin文件放如软盘中,启动Tinix系统:

    至此完成自制操作系统的第一步(boot sector)引导扇区.

    第二小节:保护模式

    2.1 实模式到保护模式 GDT表

    参考:https://blog.csdn.net/yihaolovem/article/details/23483927

    备注:寄存器参考:https://www.cnblogs.com/joey-hua/p/5347257.html

             https://blog.csdn.net/jnu_simba/article/details/11712675

         段页机制:https://www.cnblogs.com/chenwb89/p/operating_system_003.html

            https://blog.csdn.net/suppercoder/article/details/9422093

            https://blog.csdn.net/fallingu/article/details/75221276

            https://blog.csdn.net/bian1029/article/details/49124593(GDT表第一项为0)

    ; ==========================================
    ; pmtest1.asm
    ; 编译方法:nasm pmtest1.asm -o pmtest1.com
    ; ==========================================
    
    %include    "pm.inc"    ; 常量, 宏, 以及一些说明
    
    org    0100h
        jmp    LABEL_BEGIN
    
    [SECTION .gdt]
    ; GDT
    ;                                         段基址,      段界限     , 属性
    LABEL_GDT:        Descriptor           0,                0, 0             ; 空描述符
    LABEL_DESC_CODE32:    Descriptor           0, SegCode32Len - 1, DA_C + DA_32    ; 非一致代码段, 32
    LABEL_DESC_VIDEO:    Descriptor     0B8000h,           0ffffh, DA_DRW        ; 显存首地址
    ; GDT 结束
    
    GdtLen        equ    $ - LABEL_GDT    ; GDT长度
    GdtPtr        dw    GdtLen - 1    ; GDT界限
            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]
    LABEL_BEGIN:
        mov    ax, cs
        mov    ds, ax
        mov    es, ax
        mov    ss, ax
        mov    sp, 0100h
    
        ; 初始化 32 位代码段描述符
        xor    eax, eax
        mov    ax, cs
        shl    eax, 4
        add    eax, LABEL_SEG_CODE32
        mov    word [LABEL_DESC_CODE32 + 2], ax
        shr    eax, 16
        mov    byte [LABEL_DESC_CODE32 + 4], al
        mov    byte [LABEL_DESC_CODE32 + 7], ah
    
        ; 为加载 GDTR 作准备
        xor    eax, eax
        mov    ax, ds
        shl    eax, 4
        add    eax, LABEL_GDT        ; 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  处
    ; END of [SECTION .s16]
    
    
    [SECTION .s32]; 32 位代码段. 由实模式跳入.
    [BITS    32]
    
    LABEL_SEG_CODE32:
        mov    ax, SelectorVideo
        mov    gs, ax            ; 视频段选择子(目的)
    
        mov    edi, (80 * 10 + 0) * 2    ; 屏幕第 10 行, 第 0 列。
        mov    ah, 0Ch            ; 0000: 黑底    1100: 红字
        mov    al, 'P'
        mov    [gs:edi], ax
    
        ; 到此停止
        jmp    $
    
    SegCode32Len    equ    $ - LABEL_SEG_CODE32
    ; END of [SECTION .s32]

    1、[SECTION .XXX]为何物?
    SECTION和SEGMENT的作用相类似,就是代表“段”的意思。从整个程序来看,该程序分为3个模块,分别是[SECTION .gdt]、[SECITON .s16]、[SECTION .s32]三部分。我们很容易就可以看出,其中的[SECTION .gdt]应该是数据段,其他的两个是代码段。通过[SECTION .XXX]将程序分成不同模块,完成不同的功能,使得程序看起来清晰明了。

    2. 段描述符宏定义和初始化段描述符

    描述符宏定义:

    ; 宏 ------------------------------------------------------------------------------------------------------
    ;
    ; 描述符
    ; usage: Descriptor Base, Limit, Attr  定义三个变量,段基址,段界限,段属性
    ;        Base:  dd  4个字节
    ;        Limit: dd (low 20 bits available)  4个字节
    ;        Attr:  dw (lower 4 bits of higher byte are always 0)  2个字节
    %macro Descriptor 3          ;%macro是宏定义,Descriptor是宏名,3表示该宏有三个参数
        dw    %2 & 0FFFFh                ; 段界限1,段界限值0-15位 注:汇编语言中数据不能以字母开头
        dw    %1 & 0FFFFh                ; 段基址1,段基地址0-15
        db    (%1 >> 16) & 0FFh            ; 段基址2,段基地址16-23位
        dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ; 属性1 + 段界限2 + 属性2
        db    (%1 >> 24) & 0FFh            ; 段基址3,基地址24-31位
    %endmacro ; 共 8 字节

    Base是%1,Limit是%2,Attr是%3。

    段描述符结构图:

    P,present位,1表示所描述的段存在(有效),为0表示所描述的段无效,使用该描述符会引起异常 
    DPL,Descriptor privilege,描述符特权级别,说明所描述段的特权级别 
    S,描述符类型位,1说明当前描述符为存储段描述符,0为系统描述符或门描述符. 
    TYPE: 
    位0:A(accessed)位,表明描述符是否已被访问;把选择子装入段寄存器时,该位被标记为1 
    位3:E(EXECUTABLE?)位,0说明所描述段为数据段;1为可执行段(代码段)
    当为数据段时, 
       位1为W位,说明该数据段是否可写(0只读,1可写) 
       位2为ED位,说明该段的扩展方向(0向高位扩展,1向低位扩展) 
    当为可执行段是, 
       位1为R位,说明该执行段是否可读(0只执行,1可读) 
       位2为C位,0说明该段不是一致码段(普通代码段),1为一致码段 
    G为粒度位,0说明LIMIT粒度为字节,1为4K字节. 
    D位: 
       1.在可执行段中,D为1,表示使用32位地址,32/8位操作数;为0表示使用16位地址,16/8位操作数 
       2.在由SS寻址的段描述符(堆栈段?)中,D为1表示隐含操作(如PUSH/POP)使用ESP为堆栈指针,
         为0使用SP(隐含操作:未明确定义段属性类型USE16/USE32?66H,67H?) 
       3.在向低扩展的存储段中,D为1,表示段的上限为4G;为0上限为64K

    参考:https://images2015.cnblogs.com/blog/790694/201606/790694-20160619001102057-493760916.png

    GDT的作用是用来提供段式存储机制,这种机制是段寄存器和GDT中的描述符共同提供的。每个描述符在GDT中占8字节,也就是 2 个双字,或者说是 64 位。图中,下面是低32位,上面是高32位。

    将Limit低16位赋值给描述符的BYTE0和BYTE1
    将Base低16位赋值给描述符的BYTE2和BYTE3
    将Base右移16位后的低8位(也就是原Base的第16—23位)赋值给描述符的BYTE4
    将Limit右移8位之后的第8—11位和Attr的0—7和12—15位,组合起来存储到描述符的BYTE5和BYTE6
    将Base右移24位后的低8位(也就是原Base的24—32位)赋值给描述符的BYTE7

    初始化段描述符:

        ; 初始化 32 位代码段描述符
        xor    eax, eax
        mov    ax, cs
        shl    eax, 4       ;左移四位 = cs*16
        add    eax, LABEL_SEG_CODE32   ;有效地址 = 段值*16 + offset
        mov    word [LABEL_DESC_CODE32 + 2], ax
        shr    eax, 16
        mov    byte [LABEL_DESC_CODE32 + 4], al
        mov    byte [LABEL_DESC_CODE32 + 7], ah

    为什么要初始化?你会发现这里只是修改了段描述符基地址,即LABEL_DESC_CODE32的BYTE2,BYTE4,BYTE7。是不是突然恍然大悟?因为在我们初始化该LABEL_DESC_CODE32描述符时,将其基地址初始化为0,所以我们要修改描述符的基地址为其实际的地址。这也是在前面介绍段描述符的时候,我提醒大家需要注意的地方,即描述符的基地址所占有的字节是BYTE2,BYTE4,BYTE7(共32bit)。

    详解:

    mov ax,cs

    shl eax,4

    将cs的值(16bit 实模式下即为当前代码段的基地址) 

    左移4bit即得到当前代码段的物理基地址

    add eax,LABEL_SEG_CODE32

    此时eax即为实模式下LABEL_SEG_CODE32的物理地址

    然后再用此地址分为三部分去初始化LABEL_DESC_CODE32

    3.加载GDTR

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

    这个很好理解,我们就是对GdtPtr进行赋值,主要是初始化GDT的基地址。也就是将GDT的初始地址,赋值给GdtPtr的BYTE2,BYTE3,BYTE4,BYTE5。使GdtPtr的数据结构刚好符合GDTR,然后执行lgdt [GdtPtr],加载全局描述符表寄存器。将GDT的基地址和界限赋值给GDTR。

    2.2 LDT 局部描述符表

    LDT和GDT本质上都是段描述符表,具有相同的结构,区别仅在于全局和局部的不同。

    参考:https://blog.csdn.net/wrx1721267632/article/details/52056910

    1. LDT在系统中可以存在多个
    2. LDT不是全局可见的,它们只对引用它们的任务可见,每个任务最多可以拥有一个LDT。
    3. 每一个LDT自身作为一个段存在,它们的段描述符被放在GDT中。

    LDTR寄存器: 

    IA-32为LDT的入口地址也提供了一个寄存器LDTR,因为在任何时刻只能有一个任务在运行,所以LDT寄存器全局也只需要有一个。如果一个任务拥有自身的LDT,那么当它需要引用自身的LDT时,它需要通过lldt指令将其LDT的段描述符装入此寄存器。lldt指令与lgdt指令不同的时,lgdt指令的操作数是一个32-bit的内存地址,这个内存地址处存放的是一个32-bit GDT的入口地址,以及16-bit的GDT Limit。而lldt指令的操作数是一个16-bit的选择子,这个选择子主要内容是:被装入的LDT的段描述符在GDT中的索引值。

    至此,我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。如图:

    段选择子:

    引用GDT和LDT中的段描述符所描述的段,是通过一个16-bit的数据结构来实现的,这个数据结构叫做Segment Selector——段选择子。它的高13位作为被引用的段描述符在GDT/LDT中的下标索引,bit 2用来指定被引用段描述符被放在GDT中还是到LDT中,bit 0和bit 1是RPL——请求特权等级,被用来做保护目的。如图所示:

    2.3 特权级

    在IA32的分段机制中,特权级总共有四个特权级别,从高到低分别对应0,1,2,3。数字越小表示特权越大。

     作用:防止低特权级应用访问高特权级数据,代码。

    特权级检验主要通过CPL,DPL,RPL来实现。

    1.CPL

    当前执行程序或任务的特权级。

    被存储在CS的第0位和SS的第一位上。

    通常表示代码段的特权级。

    2.DPL

    表示段或门的特权级。

    存储在段描述符或门描述符的DPL字段中。

    3.RPL

    段选择子的第0位和第1位。

    系统调用时使用RPL作为调用者的特权级。

    补充:

    参考:https://www.cnblogs.com/LittleHann/p/3850655.html

    门描述符:

    处理器对程序的执行主要时顺序和跳转两种方式,跳转也就是程序控制的转移,可以通过指令jmp、call、ret、sysenter、

    sysexit、int n、iret引起,也可以由中断和异常机制引起。

    在I386CPU中,除了"段描述符"(描述某种内存段)之外还有一种描述符叫做"门描述符"(描述控制转移的入口点,也就是异常控制中断的入口点),通过这种门可以实现特权级的转变和任务的切换。门描述符主要由以下几部分组成:

    1. 选择子
    2. 偏移地址
    3. DPL

    分为4种类型:

    • 调用门
    • 中断门
    • 陷阱门
    • 任务门
    1. 调用门描述符
    调用门一般用在特权级的切换,存在于GDT中或者LDT中。调用门的选择子指向代码段描述符,偏移地址对应代码段中的偏移量。当jump和call指令的操作数是调用门的时候,就会跳转到对应的代码处,并发生特权级的变化,也就会发生
    堆栈的切换
    
    2. 任务门描述符
    任务门一般用在任务的切换,可以存放在GDT、LDT或IDT中。任务门的选择子指向GDT中的TSS选择符,偏移地址没有意义。当jmp和Call指令的操作数是任务门的时候,就会发生任务的切换。
    
    3. 中断门描述符
    4. 陷阱门描述符 中断门描述符、陷阱门描述符用来对中断服务例程进行寻址,从原理上来理解,中断例程的寻址本质上也是内存的寻址

    保护模式初步总结:

    1. 描述符中段基址、段界限、段属性对段的一种保护
    2. 门描述符中特权级之间的变换
    3. 涉及到特权级的每一步中,处理器都会对CPL、DPL、RPL等内容进行比较。

    关于段描述符和门描述符参考:https://blog.csdn.net/barech/article/details/4401417

    可以理解为,段描述符是对存储访问的管理控制,门描述符是对调用,特权级转变的管理控制。2019-04-25

     保护模式总结更新:

    1. 在GDT、LDT、IDT中,每一个描述符都有自己的界限和属性 -- 对描述符所描述对象的一种限定和保护
    2. 分页机制中的PDE和PTE都含有R/W和U/S位 -- 页级保护
    3. 页式存储使应用程序使用的是线性地址空间 -- 物理内存被保护起来
    4. 中断不再像实模式下一样使用 -- 特权检验,中断调用被保护
    5. I/O指令不再随便使用 -- 端口被保护起来
    6. 在程序运行过程中,如遇到不同特权级间的访问,会对CPL、RPL、DPL、IOPL等内容进行检验,同时进行堆栈切换 -- 对不同层级的程序进行了保护。
  • 相关阅读:
    王道训练营-C语言-1
    错排问题
    王道训练营-C语言-1
    图文处理 生成海报
    根据经纬度按距离排序
    vue总结
    docker+consul
    doker es
    知识库
    php笔记
  • 原文地址:https://www.cnblogs.com/x-police/p/10718349.html
Copyright © 2020-2023  润新知