码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2016-05-30 14:49:35      阅读:265      评论:0      收藏:0      [点我收藏+]

标签:

上面的两篇文章中,我们对可用内存进行了统计,并且合理的分配了页表的大小。这节中,我们来看看分页的好处

在此之前不知道你有没有注意过一个细节,如果你写一个程序(在Linux或Windows下均可),并改个名复制一份,然后同时调试,你会发现,从变量地址到寄存器的值,几乎全部都是一样的!而这些“一样的”地址之间完全不会混淆起来,而是各自完成着自己的职责。这就是分页机制的功劳,下面我们就来模拟一下这个效果。

线性地址到物理地址的映射

先执行某个线性地址处的模块,然后通过改变cr3来转换地址映射关系,再执行同一个线性地址处的模块,由于地址映射已经改变,所以两次得到的应该是不同的输出。

映射关系转换前的情形如下图所示:

技术分享

开始,我们让ProcPagingDemo中的代码实现向LinearAddrDemo这个线性地址的转移,而LinearAddrDemo映射到物理地址空间中
的ProcFoo处。我们让ProcFoo打印出红色的字符串Foo,所以执行时我们应该可以看到红色的Foo。随后我们改变地址映射关系,变
化成下图所示的情形。

技术分享

页目录表和页表的切换让LinearAddrDemo映射到ProcBar(物理地址空间)处,所以当我们再一次调用过程ProcPagingDemo时,程序将转移到ProcBar处执行,我们将看到红色的字符串Bar。

接下来看看新增的代码:

改变映射关系的代码实现

首先,我们用到了另外一套页目录表和页表,所以原先的页目录段和页表段已经不再够用了。事实上,前面的程序中我们用两个段分别存放页目录表和页表,是为了让我们阅读时更加直观和形象。在接下来的程序中,我们把它们放到同一个段中,同时把增加的一套页目录和页表也放到这个段中。

为了操作方便,我们新增加一个段,其线性地址空间为0~4GB。由于分页机制启动之前线性地址等同于物理地址,所以通过这个段可以方便地存取特定的物理地址。此段的代码定义如下:

 26 LABEL_DESC_FLAT_C:  Descriptor 0,        0fffffh, DA_CR|DA_32|DA_LIMIT_4K; 0~4G
 27 LABEL_DESC_FLAT_RW: Descriptor 0,        0fffffh, DA_DRW|DA_LIMIT_4K     ; 0~4G
...

 41 SelectorFlatC           equ     LABEL_DESC_FLAT_C       - LABEL_GDT
 42 SelectorFlatRW          equ     LABEL_DESC_FLAT_RW      - LABEL_GDT

之所以用了两个描述符来描述这个段,是因为我们不仅仅要读写这段内存,而且要执行其中的代码,而这对描述符的属性要求是不一样的。这两个段的段基址都是0,长度都是4GB。

下面我们就将启动分页的代码做相应的修改,如下所示:

257 ; 启动分页机制 --------------------------------------------------------------
258 SetupPaging:
259         ; 根据内存大小计算应初始化多少PDE以及多少页表
260         xor     edx, edx
261         mov     eax, [dwMemSize]
262         mov     ebx, 400000h    ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
263         div     ebx
264         mov     ecx, eax        ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
265         test    edx, edx
266         jz      .no_remainder
267         inc     ecx             ; 如果余数不为 0 就需增加一个页表
268 .no_remainder:
269         mov     [PageTableNumber], ecx  ; 暂存页表个数
270 
271         ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
272 
273         ; 首先初始化页目录
274         mov     ax, SelectorFlatRW
275         mov     es, ax
276         mov     edi, PageDirBase0       ; 此段首地址为 PageDirBase0
277         xor     eax, eax
278         mov     eax, PageTblBase0 | PG_P  | PG_USU | PG_RWW
279 .1:
280         stosd
281         add     eax, 4096               ; 为了简化, 所有页表在内存中是连续的.
282         loop    .1
283 
284         ; 再初始化所有页表
285         mov     eax, [PageTableNumber]  ; 页表个数
286         mov     ebx, 1024               ; 每个页表 1024 个 PTE
287         mul     ebx
288         mov     ecx, eax                ; PTE个数 = 页表个数 * 1024
289         mov     edi, PageTblBase0       ; 此段首地址为 PageTblBase0
290         xor     eax, eax
291         mov     eax, PG_P  | PG_USU | PG_RWW
292 .2:
293         stosd
294         add     eax, 4096               ; 每一页指向 4K 的空间
295         loop    .2
296 
297         mov     eax, PageDirBase0
298         mov     cr3, eax
299         mov     eax, cr0
300         or      eax, 80000000h
301         mov     cr0, eax
302         jmp     short .3
303 .3:
304         nop
305 
306         ret
307 ; 分页机制启动完毕 ----------------------------------------------------------

我们原来并没有把页表个数保存起来,而现在情况发生了变化,我们不只有一个页目录和页表,为了初始化另外的页表时方便起见,在这里增加了一个变量PageTableNumber,页表的个数就存在里面。

在整个初始化页目录和页表的过程中,es始终为SelectorFlatRW。这样,想存取物理地址的时候,只需将地址赋值给edi,那么es:edi指向的就是相应物理地址。比如页目录物理地址为PageDirBase0,第276行将edi赋值为PageDirBase0,es:edi于是指向地址PageDirBase0处,赋值通过指令stosd来实现。初始化页表也是同样的道理。

这样,页目录和页表的准备工作就完成了。不过我们不再在原来的位置调用它,而是新建一个函数PagingDemo,把所有与分页有关的内容全都放进里面,这样,程序看起来结构清晰一些。

根据上面两幅图,我们可以认为在这个程序的实现中有4个要关注的要素,分别是ProcPagingDemo、LinearAddrDemo、ProcFoo和ProcBar,我们把它们称为F4。因为程序开始时LinearAddrDemo指向ProcFoo并且线性地址和物理地址是对等的,所以LinearAddrDemo应该等于ProcFoo。而ProcFoo和ProcBar应该是指定的物理地址,所以LinearAddrDemo也应该是指定的物理地址。也正因为如此,我们使用它们时应该确保使用的是FLAT段,即段选择子应该是SelectorFlatC或者SelectorFlatRW。

为了将我们的代码放置在ProcFoo和ProcBar这两处地方,我们先写两个函数,在程序运行时将这两个函数的执行码复制过去就可以了。

ProcPagingDemo要调用FLAT段中的LinearAddrDemo,所以如果不想使用段间转移,我们需要把ProcPagingDemo也放进FLAT段中。我们需要写一个函数,然后把代码复制到ProcPagingDemo处。

这样看来,F4虽然都是当做函数来使用,但实际上却都是内存中指定的地址。我们把它们定义为常量。如下:

 13 LinearAddrDemo  equ     00401000h
 14 ProcFoo         equ     00401000h
 15 ProcBar         equ     00501000h
 16 ProcPagingDemo  equ     00301000h

将代码填充进这些内存地址的代码就在上文我们提到的PagingDemo中,如下:

310 ; 测试分页机制 --------------------------------------------------------------
311 PagingDemo:
312         mov     ax, cs
313         mov     ds, ax
314         mov     ax, SelectorFlatRW
315         mov     es, ax
316 
317         push    LenFoo
318         push    OffsetFoo
319         push    ProcFoo
320         call    MemCpy
321         add     esp, 12
322 
323         push    LenBar
324         push    OffsetBar
325         push    ProcBar
326         call    MemCpy
327         add     esp, 12
328 
329         push    LenPagingDemoAll
330         push    OffsetPagingDemoProc
331         push    ProcPagingDemo
332         call    MemCpy
333         add     esp, 12
334 
335         mov     ax, SelectorData
336         mov     ds, ax                  ; 数据段选择子
337         mov     es, ax
338 
339         call    SetupPaging             ; 启动分页
340 
341         call    SelectorFlatC:ProcPagingDemo
342         call    PSwitch                 ; 切换页目录,改变地址映射关系
343         call    SelectorFlatC:ProcPagingDemo
344 
345         ret
346 ; ---------------------------------------------------------------------------

其中用到了名为MemCpy的函数,它复制三个过程到指定的内存地址,类似于C语言中的memcpy。但有一点不同,它假设源数据放在ds段中,而目的在es段中。所以在函数的开头,你可以找到分别为ds和es赋值的语句。MemCpy代码如下:

131 ; ------------------------------------------------------------------------
132 ; 内存拷贝,仿 memcpy
133 ; ------------------------------------------------------------------------
134 ; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
135 ; ------------------------------------------------------------------------
136 MemCpy:
137         push    ebp
138         mov     ebp, esp
139 
140         push    esi
141         push    edi 
142         push    ecx 
143 
144         mov     edi, [ebp + 8]  ; Destination
145         mov     esi, [ebp + 12] ; Source
146         mov     ecx, [ebp + 16] ; Counter
147 .1:
148         cmp     ecx, 0          ; 判断计数器
149         jz      .2              ; 计数器为零时跳出
150 
151         mov     al, [ds:esi]            ; ┓
152         inc     esi                     ; ┃
153                                         ; ┣ 逐字节移动
154         mov     byte [es:edi], al       ; ┃
155         inc     edi                     ; ┛
156 
157         dec     ecx             ; 计数器减一
158         jmp     .1              ; 循环
159 .2:
160         mov     eax, [ebp + 8]  ; 返回值
161 
162         pop     ecx
163         pop     edi
164         pop     esi
165         mov     esp, ebp
166         pop     ebp
167 
168         ret                     ; 函数结束,返回
169 ; MemCpy 结束-------------------------------------------------------------

被复制的三个过程如下:

402 PagingDemoProc:
403 OffsetPagingDemoProc    equ     PagingDemoProc - $$
404         mov     eax, LinearAddrDemo
405         call    eax
406         retf
407 LenPagingDemoAll        equ     $ - PagingDemoProc
408 
409 foo:
410 OffsetFoo               equ     foo - $$
411         mov     ah, 0Ch                 ; 0000: 黑底    1100: 红字
412         mov     al, ‘F‘
413         mov     [gs:((80 * 17 + 0) * 2)], ax    ; 屏幕第 17 行, 第 0 列。
414         mov     al, ‘o‘
415         mov     [gs:((80 * 17 + 1) * 2)], ax    ; 屏幕第 17 行, 第 1 列。
416         mov     [gs:((80 * 17 + 2) * 2)], ax    ; 屏幕第 17 行, 第 2 列。
417         ret
418 LenFoo                  equ     $ - foo
419 
420 bar:
421 OffsetBar               equ     bar - $$
422         mov     ah, 0Ch                 ; 0000: 黑底    1100: 红字
423         mov     al, ‘B‘
424         mov     [gs:((80 * 18 + 0) * 2)], ax    ; 屏幕第 18 行, 第 0 列。
425         mov     al, ‘a‘
426         mov     [gs:((80 * 18 + 1) * 2)], ax    ; 屏幕第 18 行, 第 1 列。
427         mov     al, ‘r‘
428         mov     [gs:((80 * 18 + 2) * 2)], ax    ; 屏幕第 18 行, 第 2 列。
429         ret
430 LenBar                  equ     $ - bar

接下来继续看PagingDemo中的代码,这部分代码最重要的部分是四个call语句,如下:

339         call    SetupPaging             ; 启动分页
340 
341         call    SelectorFlatC:ProcPagingDemo
342         call    PSwitch                 ; 切换页目录,改变地址映射关系
343         call    SelectorFlatC:ProcPagingDemo

首先启动分页机制,然后调用ProcPagingDemo,再切换页目录,最后又调用一遍ProcPagingDemo。

现在ProcPagingDemo、ProcFoo以及ProcBar的内容我们都已经知道了,由于LinearAddrDemo和ProcFoo相等,并且函数SetupPaging建立起来的是对等的映射关系,所以第一次对ProcPagingDemo的调用反映的就是开始时的内存映射关系图。

接下来看看调用的PSwitch:

349 ; 切换页表 ------------------------------------------------------------------
350 PSwitch:
351         ; 初始化页目录
352         mov     ax, SelectorFlatRW
353         mov     es, ax
354         mov     edi, PageDirBase1       ; 此段首地址为 PageDirBase1
355         xor     eax, eax
356         mov     eax, PageTblBase1 | PG_P  | PG_USU | PG_RWW
357         mov     ecx, [PageTableNumber]
358 .1:
359         stosd
360         add     eax, 4096               ; 为了简化, 所有页表在内存中是连续的.
361         loop    .1
362 
363         ; 再初始化所有页表
364         mov     eax, [PageTableNumber]  ; 页表个数
365         mov     ebx, 1024               ; 每个页表 1024 个 PTE
366         mul     ebx
367         mov     ecx, eax                ; PTE个数 = 页表个数 * 1024
368         mov     edi, PageTblBase1       ; 此段首地址为 PageTblBase1
369         xor     eax, eax
370         mov     eax, PG_P  | PG_USU | PG_RWW
371 .2:
372         stosd
373         add     eax, 4096               ; 每一页指向 4K 的空间
374         loop    .2
375 
376         ; 在此假设内存是大于 8M 的
377         mov     eax, LinearAddrDemo
378         shr     eax, 22
379         mov     ebx, 4096
380         mul     ebx
381         mov     ecx, eax
382         mov     eax, LinearAddrDemo
383         shr     eax, 12
384         and     eax, 03FFh      ; 1111111111b (10 bits)
385         mov     ebx, 4
386         mul     ebx
387         add     eax, ecx
388         add     eax, PageTblBase1
389         mov     dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW
390 
391         mov     eax, PageDirBase1
392         mov     cr3, eax
393         jmp     short .3
394 .3:
395         nop
396 
397         ret
398 ; ---------------------------------------------------------------------------

这个函数前面初始化页目录表和页表的过程与SetupPaging是差不多的,只是紧接着程序增加了改变线性地址LinearAddrDemo对应的物理地址的语句。改变后,LinearAddrDemo将不再对应ProcFoo,而是对应ProcBar。<font color=”red>具体是如何改变的,不是很懂

所以,此函数调用完成之后,对ProcPagingDemo的调用就变成了后来的映射关系图。

在代码PSwitch的后半部分,我们把cr3的值改成了PageDirBase1,这个切换过程宣告完成。

程序的运行情况如下:

技术分享

我们看到红色的Foo和Bar,这说明我们的页表切换起作用了。其实,我们先前提到的不同进程有相同的地址,原理跟本例是类似的,也是在任务切换时通过改变cr3的值来切换页目录,从而改变地址映射关系。

这就是分页的妙处。其实,妙处还不仅仅如此。由于分页机制的存在,程序使用的都是线性地址空间,而不再直接是物理地址。这好像操作系统为应用程序提供了一个不依赖于硬件(物理内存)的平台,应用程序不必关心实际上有多少物理内存,也不必关心正在使用的是哪一段内存,甚至不必关心某一个地址是在物理内存里面还是在硬盘中。总之,操作系统全权负责了这其中的转换工作。

源代码

; ==========================================
; pmtest8.asm
; 编译方法:nasm pmtest8.asm -o pmtest8.com
; ==========================================

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

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

org    0100h
    jmp    LABEL_BEGIN

[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 结束

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

; GDT 选择子
SelectorNormal        equ    LABEL_DESC_NORMAL    - LABEL_GDT
SelectorFlatC        equ    LABEL_DESC_FLAT_C    - LABEL_GDT
SelectorFlatRW        equ    LABEL_DESC_FLAT_RW    - LABEL_GDT
SelectorCode32        equ    LABEL_DESC_CODE32    - LABEL_GDT
SelectorCode16        equ    LABEL_DESC_CODE16    - LABEL_GDT
SelectorData        equ    LABEL_DESC_DATA        - LABEL_GDT
SelectorStack        equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]

[SECTION .data1]     ; 数据段
ALIGN    32
[BITS    32]
LABEL_DATA:
; 实模式下使用这些符号
; 字符串
_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        - $$
MemChkBuf        equ    _MemChkBuf    - $$
PageTableNumber        equ    _PageTableNumber- $$

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


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

TopOfStack    equ    $ - LABEL_STACK - 1

; END of [SECTION .gs]


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

    mov    [LABEL_GO_BACK_TO_REAL+3], ax
    mov    [_wSPValueInRealMode], sp

    ; 得到内存数
    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:

    ; 初始化 16 位代码段描述符
    mov    ax, cs
    movzx    eax, ax
    shl    eax, 4
    add    eax, LABEL_SEG_CODE16
    mov    word [LABEL_DESC_CODE16 + 2], ax
    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
    mov    word [LABEL_DESC_CODE32 + 2], ax
    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
    mov    word [LABEL_DESC_DATA + 2], ax
    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
    mov    word [LABEL_DESC_STACK + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_STACK + 4], al
    mov    byte [LABEL_DESC_STACK + 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  处

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

LABEL_REAL_ENTRY:        ; 从保护模式跳回到实模式就到了这里
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax

    mov    sp, [_wSPValueInRealMode]

    in    al, 92h        ; ┓
    and    al, 11111101b    ; ┣ 关闭 A20 地址线
    out    92h, al        ; ┛

    sti            ; 开中断

    mov    ax, 4c00h    ; ┓
    int    21h        ; ┛回到 DOS
; END of [SECTION .s16]


[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


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

    push    szMemChkTitle
    call    DispStr
    add    esp, 4

    call    DispMemSize        ; 显示内存信息

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

    ; 到此停止
    jmp    SelectorCode16:0

; 启动分页机制 --------------------------------------------------------------
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, 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
; ---------------------------------------------------------------------------


; 切换页表 ------------------------------------------------------------------
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
; ---------------------------------------------------------------------------



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


; 显示内存信息 --------------------------------------------------------------
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]


; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN    32
[BITS    16]
LABEL_SEG_CODE16:
    ; 跳回实模式:
    mov    ax, SelectorNormal
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    gs, ax
    mov    ss, ax

    mov    eax, cr0
    and     eax, 7FFFFFFEh          ; PE=0, PG=0
    mov    cr0, eax

LABEL_GO_BACK_TO_REAL:
    jmp    0:LABEL_REAL_ENTRY    ; 段地址会在程序开始处被设置成正确的值

Code16Len    equ    $ - LABEL_SEG_CODE16

; END of [SECTION .s16code]

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

标签:

原文地址:http://blog.csdn.net/friendley/article/details/51533273

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!