操作系统实践(7)——分页机制

为什么分页,分页有什么好处?

从苦逼码农的角度,用一个租房的例子来说,原来分段机制,就好比房子整租,当租客不想租的时候,就整个房子退还给房东,房东再找其它人整租,这样有个缺点,在北京,整租一套房子,很贵,而且很多租客都是一个人或者两个人,用不着租那么大,于是一方面房东如果找不到租客,房子就空闲着,一方面要是租客咬咬牙租下整套房子,很多房间也是很浪费。于是,下面要讨论的分页机制,就是现在市场上各种单间出租了。单间出租的好处很明显,房子利用率很高,一个租客不租了,很快就能找到下一个租客。其实分页的好处还体现在:对于寻址方式的影响。

分页与寻址方式

举个简单的例子,相信一开始使用u盘的时候体会过,一开始我们新建一个文件夹,然后把所有文件都往里面丢,当文件慢慢增多的时候,回头要找里面某个文件的时候,发现特别难找。于是,我们就会在顶层目录下,新建几个分类的文件夹,把不同的文件放到不同的类型的文件夹下,找的时候先找文件夹,再找文件。

细心的你一定会想,虽然放不同文件夹,但是打开几个文件夹也需要点几下鼠标,也是需要时间的,是的,cpu的分页机制的实现,在寻址的时候,多了几层间接寻址,虽然提高了灵活性,但是中间必然有损耗,从内存读入寄存器是需要花时间的,而且还有缓存失效的问题,所以,这一步就需要硬件支持了,根据我目前了解,cpu在这一层会做一些缓存处理,加快转换,减少中间间接寻址的损耗。

分页机制的实现

首先看图:

(1)根据cr3寄存器,找到页目录表在物理内存的位置;

(2)根据线性地址的高10位,在页目录表中,找到相应的页表地址。

(3)根据页表地址,找到页表。再根据中间10位,找到对应的页表中的一项。

(4)一个页表,映射的物理空间大小为4M (2^12 * 2^10项),所以所以每个页表的第一项映射到物理地址的4M*n,页表中的第q项,则映射到4M*n+4k*q,最后根据线性地址的低12位,找到最终的物理地址。



《orange’s一个操作系统的实现》里面,有一节专门写了个程序,让读者体验分页机制带来的好处。作者的思路:

1.拷贝两份代码到内存的不同地址,分别叫Foo和Bar。

2.初始化一个页目录和页表,访问一个线性地址(这里叫LinearAddr吧),这个线性地址跳转到Foo的代码执行,屏幕打印出Foo。

3.初始化另外一个页目录和页表,并修改LinearAddr这个线性地址对应的页表项,使访问LinearAddr的时候,会跳转到Bar的代码执行,屏幕打印出Bar。

体现出来的效果:同样访问的都是LinearAddr这个线性地址,切换页表后执行的是不同的代码。

这里非常关键的一点,就是修改页表项映射到的内存区域。

我这里举个不是特别恰当的例子来展示这种思想,用高级语言java举个例子:

interface Animal{
void duck();
}
class Dog implements Animal{
    @Override
    void duck(){
        println("wang wang~");
    }
}
class Cat implements Animal{
    @Override
    void duck(){
        println("miao miao~");
    }
}
Animal animal = new Dog();
animal.duck();//"wang wang~"
animal = new Cat();
animal.duck();//"miao miao~"


下面是用晦涩的汇编实现书中具的例子,做了简单的修改,修改的原因在于,我用书中的代码编译通过了,但是执行的时候bochs重启了,出错的代码在这两句

    mov ax,cs
    mov ds,ax

这两句看起来没什么问题,但实际上,我的实验结果,在32位保护模式下,把选择子赋值给ds,貌似cpu有寄存器的保护机制,如果是把代码段的选择子赋值给ds,则会出错,如果用数据段的选择子赋值给ds则没问题。如果读者有更好的见解,请留言,谢谢。

下面是实践结果和代码:

org 07c00h
    [BITS 16]

START:
    mov ax,cs
    mov ds,ax
    mov es,ax

    ;拷贝软盘中的代码到内存区
COPY:
    mov bx, COPY_CODE_START ;07c00h + 512(0100h) == 07e00h
    mov dl,0        ;驱动器号,软驱从0开始:0:软驱A,1:软驱B
                    ;磁盘从80h开始,80h:C盘,81h:D盘
    mov dh,0        ;磁头号,对于软盘即面号,一个面用一个磁头来读写
    mov ch,0        ;磁道号
    mov cl,2        ;扇区号
    mov al,0x0a     ;读取的扇区数
    mov ah,2        ;13h的功能号(2表示读扇区),es:bx指向
                    ;接收从扇区读入数据的内存区
    int 13h
    jc COPY         ;读取失败,CF表示为1,重试读取   

    jmp LABEL_BEGIN ;把程序读到内存区后,跳转到新的执行点

    ;补全512字节
    times 510-($-$$) db 0
    dw 0xaa55

COPY_CODE_START:

%include "inc.asm"

PageDirBase0        equ 200000h ; 页目录开始地址:  2M
PageTblBase0        equ 201000h ; 页表开始地址:       2M +  4K
PageDirBase1        equ 210000h ; 页目录开始地址:  2M + 64K
PageTblBase1        equ 211000h ; 页表开始地址:       2M + 64K + 4K

LinearAddrDemo  equ 00401000h
ProcFoo     equ 00401000h
ProcBar     equ 00501000h
ProcPagingDemo  equ 00301000h

[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_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致代码段,32
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_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW+DA_DPL3    ; 显存首地址

LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3
LABEL_DESC_STACK3:     Descriptor 0,       TopOfStack3, DA_DRWA+DA_32+DA_DPL3
LABEL_DESC_TSS:        Descriptor 0,          TSSLen-1, DA_386TSS      ;TSS

; 门                               目标选择子,偏移,DCount, 属性
;LABEL_CALL_GATE_TEST: Gate SelectorCodeDest,   0,     0, DA_386CGate+DA_DPL0
LABEL_CALL_GATE_TEST:   Gate          SelectorCodeDest,          0,      0, DA_386CGate + DA_DPL3

;分页
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_CODE_DATA:   Descriptor    0,      CodeDataLen-1, DA_DRW    ; 代码拷贝

; GDT 结束

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

; GDT 选择子
SelectorNormal      equ LABEL_DESC_NORMAL   - LABEL_GDT
SelectorCode32      equ LABEL_DESC_CODE32   - LABEL_GDT
SelectorCodeDest    equ LABEL_DESC_CODE_DEST    - LABEL_GDT
SelectorData        equ LABEL_DESC_DATA     - LABEL_GDT
SelectorStack       equ LABEL_DESC_STACK    - LABEL_GDT
SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT
SelectorCallGateTest    equ LABEL_CALL_GATE_TEST    - LABEL_GDT

SelectorCodeRing3   equ LABEL_DESC_CODE_RING3   - LABEL_GDT + SA_RPL3
SelectorStack3      equ LABEL_DESC_STACK3   - LABEL_GDT + SA_RPL3
SelectorTSS     equ LABEL_DESC_TSS      - LABEL_GDT

SelectorFlatC       equ LABEL_DESC_FLAT_C   - LABEL_GDT
SelectorFlatRW      equ LABEL_DESC_FLAT_RW  - LABEL_GDT
SelectorCodeData    equ LABEL_DESC_CODE_DATA    - LABEL_GDT

; END of [SECTION .gdt]

[SECTION .data1]     ; 数据段
ALIGN   32
[BITS   32]
LABEL_DATA:
; 字符串
PMMessage:      db  "In Protect Mode now. ^-^", 0   ; 在保护模式中显示
OffsetPMMessage     equ PMMessage - $$

_szPMMessage:           db  "In Protect Mode now. ^-^ =_= - -", 0Ah, 0Ah, 0 ; 进入保护模式后显示此字符串
_szMemChkTitle:         db  "BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0   ; 进入保护模式后显示此字符串
_szRAMSize          db  "RAM size:", 0
_szReturn           db  0Ah, 0
; 变量
_wSPValueInRealMode     dw  0
_dwMCRNumber:           dd  0   ; Memory Check Result
_dwDispPos:         dd  (80 * 6 + 0) * 2    ; 屏幕第 6 行, 第 0 列。
_dwMemSize:         dd  0
_ARDStruct:         ; Address Range Descriptor Structure
    _dwBaseAddrLow:     dd  0
    _dwBaseAddrHigh:    dd  0
    _dwLengthLow:       dd  0
    _dwLengthHigh:      dd  0
    _dwType:        dd  0
_PageTableNumber        dd  0

_MemChkBuf: times   256 db  0

; 保护模式下使用这些符号
szPMMessage     equ _szPMMessage    - $$
szMemChkTitle		equ	_szMemChkTitle	- $$
szRAMSize       equ _szRAMSize  - $$
szReturn		equ	_szReturn	- $$
dwDispPos       equ _dwDispPos  - $$
dwMemSize		equ	_dwMemSize	- $$
dwMCRNumber     equ _dwMCRNumber    - $$;多少块内存描述
ARDStruct		equ	_ARDStruct	- $$
    dwBaseAddrLow   equ _dwBaseAddrLow  - $$
	dwBaseAddrHigh	equ	_dwBaseAddrHigh	- $$
    dwLengthLow equ _dwLengthLow    - $$
	dwLengthHigh	equ	_dwLengthHigh	- $$
    dwType      equ _dwType     - $$
PageTableNumber		equ	_PageTableNumber- $$
MemChkBuf       equ _MemChkBuf  - $$

DataLen         equ $ - LABEL_DATA
; END of [SECTION .data1]

[SECTION .data2]     ; 数据段
ALIGN   32
[BITS   32]
LABEL_CODE_DATA: 

PagingDemoProc:
OffsetPagingDemoProc    equ PagingDemoProc - $$
    mov eax, LinearAddrDemo
    call    eax
    retf
LenPagingDemoAll    equ $ - PagingDemoProc

foo:
OffsetFoo       equ foo - $$
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    mov al, ‘F‘
    mov [gs:((80 * 17 + 0) * 2)], ax    ; 屏幕第 17 行, 第 0 列。
    mov al, ‘o‘
    mov [gs:((80 * 17 + 1) * 2)], ax    ; 屏幕第 17 行, 第 1 列。
    mov [gs:((80 * 17 + 2) * 2)], ax    ; 屏幕第 17 行, 第 2 列。
    ret
LenFoo          equ $ - foo

bar:
OffsetBar       equ bar - $$
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    mov al, ‘B‘
    mov [gs:((80 * 18 + 0) * 2)], ax    ; 屏幕第 18 行, 第 0 列。
    mov al, ‘a‘
    mov [gs:((80 * 18 + 1) * 2)], ax    ; 屏幕第 18 行, 第 1 列。
    mov al, ‘r‘
    mov [gs:((80 * 18 + 2) * 2)], ax    ; 屏幕第 18 行, 第 2 列。
    ret
LenBar          equ $ - bar

CodeDataLen         equ $ - LABEL_CODE_DATA
; END of [SECTION .data2]

; 全局堆栈段
[SECTION .gs]
ALIGN   32
[BITS   32]
LABEL_STACK:
    times 512 db 0

TopOfStack  equ $ - LABEL_STACK - 1

; END of [SECTION .gs]

; 堆栈段ring3
[SECTION .s3]
ALIGN   32
[BITS   32]
LABEL_STACK3:
    times 512 db 0
TopOfStack3 equ $ - LABEL_STACK3 - 1
; END of [SECTION .s3]

; TSS ---------------------------------------------------------------------------------------------
[SECTION .tss]
ALIGN   32
[BITS   32]
LABEL_TSS:
        DD  0           ; Back
        DD  TopOfStack      ; 0 级堆栈
        DD  SelectorStack       ;
        DD  0           ; 1 级堆栈
        DD  0           ;
        DD  0           ; 2 级堆栈
        DD  0           ;
        DD  0           ; CR3
        DD  0           ; EIP
        DD  0           ; EFLAGS
        DD  0           ; EAX
        DD  0           ; ECX
        DD  0           ; EDX
        DD  0           ; EBX
        DD  0           ; ESP
        DD  0           ; EBP
        DD  0           ; ESI
        DD  0           ; EDI
        DD  0           ; ES
        DD  0           ; CS
        DD  0           ; SS
        DD  0           ; DS
        DD  0           ; FS
        DD  0           ; GS
        DD  0           ; LDT
        DW  0           ; 调试陷阱标志
        DW  $ - LABEL_TSS + 2  ; I/O位图基址
        DB  0ffh            ; I/O位图结束标志
TSSLen      equ $ - LABEL_TSS
; TSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[SECTION .s16]
[BITS   16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax

    ; 得到内存数
    mov ebx, 0
    mov di, _MemChkBuf
    .loop:
    mov eax, 0E820h
    mov ecx, 20
    mov edx, 0534D4150h
    int 15h
    jc  LABEL_MEM_CHK_FAIL
    add di, 20
    inc dword [_dwMCRNumber]
    cmp ebx, 0
    jne .loop
    jmp LABEL_MEM_CHK_OK
    LABEL_MEM_CHK_FAIL:
        mov dword [_dwMCRNumber], 0
    LABEL_MEM_CHK_OK:

    ; 初始化 32 位代码段描述符
    InitCodeDescriptor LABEL_SEG_CODE32,LABEL_DESC_CODE32

    ; 初始化数据段描述符
    InitDataDescriptor LABEL_DATA,LABEL_DESC_DATA

    ; 初始化堆栈段描述符
    InitDataDescriptor LABEL_STACK,LABEL_DESC_STACK

    ; 初始化堆栈段描述符(ring3)
    InitDataDescriptor LABEL_STACK3,LABEL_DESC_STACK3

    ; 初始化Ring3描述符
    InitDataDescriptor LABEL_CODE_RING3,LABEL_DESC_CODE_RING3

    ; 初始化 TSS 描述符
    InitDataDescriptor LABEL_TSS,LABEL_DESC_TSS

    ; 初始化测试调用门的代码段描述符
    InitCodeDescriptor LABEL_SEG_CODE_DEST,LABEL_DESC_CODE_DEST

    ; 初始化数据段描述符
    InitDataDescriptor LABEL_CODE_DATA,LABEL_DESC_CODE_DATA

    ; 为加载 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  处

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

LABEL_SEG_CODE32:
    mov ax, SelectorData
    mov ds, ax          ; 数据段选择子
    mov es, ax
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择子

    mov ax, SelectorStack
    mov ss, ax          ; 堆栈段选择子

    mov esp, TopOfStack

    ; 下面显示一个字符串
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    xor esi, esi
    xor edi, edi
    mov esi, OffsetPMMessage    ; 源数据偏移
    mov edi, (80 * 10 + 0) * 2  ; 目的数据偏移。屏幕第 10 行, 第 0 列。
    cld
.1:
    lodsb
    test    al, al
    jz  .2
    mov [gs:edi], ax
    add edi, 2
    jmp .1
.2: ; 显示完毕

    ;清屏
    push edi
    mov edi,0
    mov cx,4000
    mov al,0
    cld
.clear:
    mov [gs:edi],ax
    add edi, 2
    loop .clear
    pop edi

    ; 下面显示一个字符串
    push    szPMMessage
    call    DispStr
    add esp, 4

    push    szMemChkTitle
    call    DispStr
    add esp, 4

    call    DispReturn

    call    DispMemSize     ; 显示内存信息

    call    PagingDemo      ; 演示改变页目录的效果

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

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

    ; 测试调用门(无特权级变换),将打印字母 ‘C‘
    ;call   SelectorCallGateTest:0
    ;call   SelectorCodeDest:0
    ;jmp $

; 启动分页机制 --------------------------------------------------------------
SetupPaging:
    ; 根据内存大小计算应初始化多少PDE以及多少页表
    xor edx, edx
    mov eax, [dwMemSize]
    mov ebx, 400000h    ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
    div ebx
    mov ecx, eax    ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
    test    edx, edx
    jz  .no_remainder
    inc ecx     ; 如果余数不为 0 就需增加一个页表
.no_remainder:
    mov [PageTableNumber], ecx  ; 暂存页表个数

    ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.

    ; 首先初始化页目录
    mov ax, SelectorFlatRW
    mov es, ax
    mov edi, PageDirBase0   ; 此段首地址为 PageDirBase0
    xor eax, eax
    mov eax, PageTblBase0 | PG_P  | PG_USU | PG_RWW
.1:
    stosd
    add eax, 4096       ; 为了简化, 所有页表在内存中是连续的.
    loop    .1

    ; 再初始化所有页表
    mov eax, [PageTableNumber]  ; 页表个数
    mov ebx, 1024       ; 每个页表 1024 个 PTE
    mul ebx
    mov ecx, eax        ; PTE个数 = 页表个数 * 1024
    mov edi, PageTblBase0   ; 此段首地址为 PageTblBase0
    xor eax, eax
    mov eax, PG_P  | PG_USU | PG_RWW
.2:
    stosd
    add eax, 4096       ; 每一页指向 4K 的空间
    loop    .2

    mov eax, PageDirBase0
    mov cr3, eax
    mov eax, cr0
    or  eax, 80000000h
    mov cr0, eax
    jmp short .3
.3:
    nop

    ret
; 分页机制启动完毕 ----------------------------------------------------------

; 测试分页机制 --------------------------------------------------------------
PagingDemo:
    mov ax, SelectorCodeData
    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
; ---------------------------------------------------------------------------

; 切换页表 ------------------------------------------------------------------
PSwitch:
    ; 初始化页目录
    mov ax, SelectorFlatRW
    mov es, ax
    mov edi, PageDirBase1   ; 此段首地址为 PageDirBase1
    xor eax, eax
    mov eax, PageTblBase1 | PG_P  | PG_USU | PG_RWW
    mov ecx, [PageTableNumber]
.1:
    stosd
    add eax, 4096       ; 为了简化, 所有页表在内存中是连续的.
    loop    .1

    ; 再初始化所有页表
    mov eax, [PageTableNumber]  ; 页表个数
    mov ebx, 1024       ; 每个页表 1024 个 PTE
    mul ebx
    mov ecx, eax        ; PTE个数 = 页表个数 * 1024
    mov edi, PageTblBase1   ; 此段首地址为 PageTblBase1
    xor eax, eax
    mov eax, PG_P  | PG_USU | PG_RWW
.2:
    stosd
    add eax, 4096       ; 每一页指向 4K 的空间
    loop    .2

    ; 在此假设内存是大于 8M 的
    mov eax, LinearAddrDemo
    shr eax, 22
    mov ebx, 4096
    mul ebx
    mov ecx, eax
    mov eax, LinearAddrDemo
    shr eax, 12
    and eax, 03FFh  ; 1111111111b (10 bits)
    mov ebx, 4
    mul ebx
    add eax, ecx
    add eax, PageTblBase1
    mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW

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

    ret
; ---------------------------------------------------------------------------

; 显示内存信息 --------------------------------------------------------------
DispMemSize:
    push    esi
    push    edi
    push    ecx

    mov esi, MemChkBuf
    mov ecx, [dwMCRNumber]  ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构
.loop:                  ;{
    mov edx, 5          ;   for(int j=0;j<5;j++)    // 每次得到一个ARDS中的成员,共5个成员
    mov edi, ARDStruct      ;   {           // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type
.1:                 ;
    push    dword [esi]     ;
    call    DispInt         ;       DispInt(MemChkBuf[j*4]); // 显示一个成员
    pop eax         ;
    stosd               ;       ARDStruct[j*4] = MemChkBuf[j*4];
    add esi, 4          ;
    dec edx         ;
    cmp edx, 0          ;
    jnz .1          ;   }
    call    DispReturn      ;   printf("\n");
    cmp dword [dwType], 1   ;   if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2
    jne .2          ;   {
    mov eax, [dwBaseAddrLow]    ;
    add eax, [dwLengthLow]  ;
    cmp eax, [dwMemSize]    ;       if(BaseAddrLow + LengthLow > MemSize)
    jb  .2          ;
    mov [dwMemSize], eax    ;           MemSize = BaseAddrLow + LengthLow;
.2:                 ;   }
    loop    .loop           ;}
                    ;
    call    DispReturn      ;printf("\n");
    push    szRAMSize       ;
    call    DispStr         ;printf("RAM size:");
    add esp, 4          ;
                    ;
    push    dword [dwMemSize]   ;
    call    DispInt         ;DispInt(MemSize);
    add esp, 4          ;

    pop ecx
    pop edi
    pop esi
    ret
; ---------------------------------------------------------------------------

%include "lib.inc"

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

[SECTION .sdest]; 调用门目标段
[BITS   32]

LABEL_SEG_CODE_DEST:
;mov    ax, SelectorVideo
;mov    gs, ax          ; 视频段选择子(目的)
;
;mov    edi, (80 * 12 + 0) * 2  ; 屏幕第 12 行, 第 0 列。
;mov    ah, 0Ch         ; 0000: 黑底    1100: 红字
;mov    al, ‘C‘
;mov    [gs:edi], ax

    retf

SegCodeDestLen  equ $ - LABEL_SEG_CODE_DEST
; END of [SECTION .sdest]

; CodeRing3
[SECTION .ring3]
ALIGN   32
[BITS   32]
LABEL_CODE_RING3:
;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]

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-01 11:52:11

操作系统实践(7)——分页机制的相关文章

操作系统篇-浅析分页机制

|| 版权声明:本文为博主原创文章,未经博主允许不得转载. 一.前言 在我们进行程序开发的时候,一般情况下,是不需要管理内存的,也不需要操心内存够不够用,其实,这就是分页机制给我们带来的好处.它是实现虚拟存储的关键,位于线性地址与物理地址之间,在使用这种内存分页管理方法时,每个执行中的进程(任务)可以使用比实际内存容量大得多的连续地址空间.而且当系统内存实际上被分成很多凌乱的块时,它可以建立一个大而连续的内存空间的映象,好让程序不用操心和管理这些分散的内存块.分页机制增强了分段机制的性能.页地址

操作系统实现之内存分页机制.虚拟空间

内存虚拟存储主要是为了将一个进程分为不同页.存储到不同物理页中.然而不同进程的虚拟地址是可以相同的.因为MMU把进程的虚拟地址映射到各个不同的物理地址中. 以下操作系统采用二级分页.一开始CS:IP寄存器.将CS的基地址跟IP的偏移地址进行相加.得到线性地址.接着.线性地址的高10位用于当作页目录表的索引.页目录表保存的是页表的物理地址.接着.线性地址的低10位用于当作页表的索引.页表保存的是4k大小的页块.线性地址低10位那就是页块的偏移地址.由此虚拟地址->物理地址转换结束.当然操作系统在创

操作系统学习(三)、分页机制

目录 1.分页机制介绍 2.页表结构 3.页表项格式 4.虚拟存储 5.直达底部 分页机制介绍 分页机制是 80x86 内存管理机制的第二部分.它在分段机制的基础上完成虚拟地址到物理地址的转换过程.分段机制把逻辑地址转换成线性地址,而分页机制则把线性地址转换成物理地址.分页机制可用于任何一种分段模型.处理器分页机制会把线性地址空间划分成页面,然后这些线性地址空间页面被映射到物理地址空间的页面上.分页机制的几种页面级保护措施,可和分段机制保护措施和用或替代分段机制的保护措施. 通过设置控制寄存器

分页机制

本文为<x86汇编语言:从实模式到保护模式> 第16章笔记 因为段的长度不定, 在分配内存时, 可能会发生内存中的空闲区域小于要加载的段, 或者空闲区域远远大于要加载的段. 在前一种情况下, 需要另外寻找合适的空闲区域; 在后一种情况下, 分配会成功, 但太过于浪费. 为了解决这个问题, 从80386处理器开始, 引入了分页机制. 分页功能从总体上来说, 是用长度固定的页来代替长度不一定的段, 藉此解决因段长度不同而带来的内存空间管理问题. 尽管操作系统也可以用软件来实施固定长度的内存分配,

Linux的分段和分页机制

1 基于80x86的Linux分段机制 80386的两种工作模式:80386的工作模式包括实地址模式和虚地址模式(保护模式).Linux主要工作在保护模式下. 在保护模式下,80386虚地址空间可达16K个段,每段大小可变,最大达4GB.逻辑地址到线性地址的转换由80386分段机制管理.段寄存器CS.DS.ES.SS.FS或GS各标识一个段.这些段寄存器作为段选择器,用来选择该段的描述符. 分段逻辑地址到线性地址转换图: Linux对80386的分段机制使用得很有限,因为Linux的设计目标是支

32机的内存分页机制

在实模式下寻址的时候,"段寄存器+偏移地址"进过转换计算以后得到的地址是"物理地址",也就是在物理内存中的实际地址,而在保护模式下,"段选择器+偏移地址"转换后的地址被称为"线性地址"而不是"物理地址",那么线性地址就是物理地址吗? 答案可能是,也可能不是,这取决于80386的内存分页机制是否被使用. 为什么有内存分页机制? 我们回顾一下,单任务的DOS系统中,一个应用程序可以使用所有的空闲内存,程序退出以

x86 分页机制——虚拟地址到物理地址寻址

x86下的分页机制有一个特点:PAE模式 PAE模式 物理地址扩展,是基于x86 的服务器的一种功能,它使运行 Windows Server 2003, Enterprise Edition 和 Windows Server 2003,Datacenter Edition 的计算机可以支持4GB 以上物理内存.物理地址扩展 (PAE) 允许将最多64GB 的物理内存用作常规的4 KB 页面,并扩展内核能使用的位数以将物理内存地址从32扩展到36. 控制寄存器与分页机制相关的标志位 未开启PAE模

Linux内存寻址之分页机制

http://blog.xiaohansong.com/2015/10/05/Linux内存寻址之分页机制/ 在上一篇文章Linux内存寻址之分段机制中,我们了解逻辑地址通过分段机制转换为线性地址的过程.下面,我们就来看看更加重要和复杂的分页机制. 分页机制在段机制之后进行,以完成线性-物理地址的转换过程.段机制把逻辑地址转换为线性地址,分页机制进一步把该线性地址再转换为物理地址. 硬件中的分页 分页机制由CR0中的PG位启用.如PG=1,启用分页机制,并使用本节要描述的机制,把线性地址转换为物

一个操作系统的实现(8)-进一步体会分页机制

上面的两篇文章中,我们对可用内存进行了统计,并且合理的分配了页表的大小.这节中,我们来看看分页的好处 在此之前不知道你有没有注意过一个细节,如果你写一个程序(在Linux或Windows下均可),并改个名复制一份,然后同时调试,你会发现,从变量地址到寄存器的值,几乎全部都是一样的!而这些"一样的"地址之间完全不会混淆起来,而是各自完成着自己的职责.这就是分页机制的功劳,下面我们就来模拟一下这个效果. 线性地址到物理地址的映射 先执行某个线性地址处的模块,然后通过改变cr3来转换地址映射