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

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等内容进行检验,同时进行堆栈切换 -- 对不同层级的程序进行了保护。

 

posted @ 2019-04-16 17:08  IVD  阅读(2236)  评论(0编辑  收藏  举报