一个操作系统的实现(6)-初识分页机制

这节仍然是从实现的角度来讲述分页机制。

为什么要引入分页机制。我们都知道分段机制是为了提供保护机制,那么为什么还要引入分页机制呢?

为什么引入分页机制

想象一下这样一种情况:假设我们用的计算机物理内存是4GB,但是我们的程序大小是5GB。那么这个时候我们无法将程序全部放到内存中,也就无法运行程序。分页机制引入的原因之一就是为了解决这个问题。分页机制的引入实现了虚拟存储器的机制。

另外,程序执行具有局部性,也就是说一段时间内,只需要程序代码中的一小部分(相对于整个程序)就可以实现程序的执行。那么我们就不用把所有代码和数据都存放在内存中,而是将现在或很近的将来需要的代码和数据放入内存就行了。要实现这个功能需要分页机制。在这种情况下,相同大小的内存在引入分页机制后可以同时存放更多的程序。这由进一步提高了存储器的容量。

在介绍分页机制之前,首先我们需要了解三种地址:

逻辑地址、线性地址、物理地址

这三种地址的关系如下图:

<font color=”red>这里是地址转换图:OS-adressAfterPagingAndSegment

引入段页式存储之后才有完整的三种地址的概念。这时候逻辑地址通过分段机制转换成线性地址,然后再通过分页机制转换成物理地址。

在没有引入页式存储的情况下,逻辑地址通过分段机制转换成的线性地址等于物理地址。

如果段式存储和页式存储都不存在。那么也就不存在逻辑地址和线性地址,我们对内存的所有操作都直接使用物理地址。

通过上面的分析,我们很容易明白分页机器就像一个函数:

物理地址 = f(线性地址)

接下来,以二级页表机制对分页机制进行描述

分页机制概述

二级页表的分页机制如下图:

上图的转换使用两级页表,第一级叫做页目录,大小为4KB,存储在一个物理页中,每个表项4字节长,共有1024个表项。每个表项对应第二级的一个页表,第二级的每一个页表也有1024个表项,每一个表项对应一个物理页。页目录表的表项简称PDE(Page Directory Entry),页表的表项简称PTE(Page Table Entry)。

进行转换时,先是从由寄存器cr3指定的页目录中根据线性地址的高10位得到页表地址,然后在页表中根据线性地址的第12到21位得到物理页首地址,将这个首地址加上线性地址低12位便得到了物理地址。

分页机制是否生效的开关位于cr0的最高位PG位。如下图:

如果PG=1,则分页机制生效。所以,当我们准备好了页目录表和页表,并将cr3指向页目录表之后,只需要置PG位,分页机制就开始工作了。

接下来描述PDE和PTE的结构和各位的详细解释:

PDE和PTE

下图是PDE的结构:

下图是PTE的结构:

下面是关于PDE和PTE中各位的解释:

  • P 存在位,表示当前条目所指向的页或页表是否在物理内存中。P=0表示页不在内存中,如果处理器试图访问此页,将会产生页异常(page-fault exception,#PF);P=1表示页在内存中。
  • R / W 指定一个页或者一组页(比如,此条目是指向页表的页目录条目)的读写权限。此位与U/S位和寄存器cr0中的WP位相互作用。R/W=0表示只读;R/W=1表示可读并可写。
  • U / S 指定一个页或者一组页(比如,此条目是指向页表的页目录条目)的特权级。此位与R/W位和寄存器cr0中的WP位相互作用。U/S=0表示系统级别(Supervisor
    Privilege Level),如果CPL为0、1或2,那么它便是在此级别;U/S=1表示用户级别(User Privilege Level),如果CPL为3,那么它便是在此级别。

    如果cr0中的WP位为0,那么即便用户级(User P.L.)页面的R/W=0,系统级(Supervisor P.L.)程序仍然具备写权限;如果WP位为1,那么系统级(Supervisor P.L.)程序也不能写入用户级(User P.L.)只读页。

  • P W T 用于控制对单个页或者页表的缓冲策略。PWT=0时使用Write-back缓冲策略;PWT=1时使用Write-through缓冲策略。当cr0寄存器的CD(Cache-Disable)位被设置时会被忽略。
  • P C D 用于控制对单个页或者页表的缓冲。PCD=0时页或页表可以被缓冲;PCD=1时页或页表不可以被缓冲。当cr0寄存器的CD(Cache-Disable)位被设置时会被忽略。
  • A 指示页或页表是否被访问。此位往往在页或页表刚刚被加载到物理内存中时被内存管理程序清零,处理器会在第一次访问此页或页面时设置此位。而且,处理器并不会自动清除此位,只有软件能清除它。
  • D 指示页或页表是否被写入。此位往往在页或页表刚刚被加载到物理内存中时被内存管理程序清零,处理器会在第一次写入此页或页面时设置此位。而且,处理器并不会自动清除此位,只有软件能清除它。

    A位和D位都是被内存管理程序用来管理页和页表从物理内存中换入和换出的。

  • P S 决定页大小。PS=0时页大小为4KB,PDE指向页表。
  • P A T 选择PAT(Page Attribute Table)条目。Pentium III以后的CPU开始支持此位,在此不予讨论,并在我们的程序中设为0。
  • G 指示全局页。如果此位被设置,同时cr4中的PGE位被置,那么此页的页表或页目录条目不会在TLB中变得无效,即便cr3被加载或者任务切换时也是如此。

处理器会将最近常用的页目录和页表项保存在一个叫做TLB(Translation Lookaside Buffer)的缓冲区中。只有在TLB中找不到被请求页的转换信息时,才会到内存中去寻找。这样就大大加快了访问页目录和页表的时间。

当页目录或页表项被更改时,操作系统应该马上使TLB中对应的条目无效,以便下次用到此条目时让它获得更新。

当cr3被加载时,所有TLB都会自动无效,除非页或页表条目的G位被设置。

接下来看看cr3的结构:

cr3

cr3的结构如下图:

cr3又叫做PDBR(Page-Directory Base Register)。它的高20位将是页目录表首地址的高20位,页目录表首地址的低12位会是零,也就是说,页目录表会是4KB对齐的。类似地,PDE中的页表基址(PageTable Base Address)以及PTE中的页基址(PageBase Address)也是用高20位来表示4KB对齐的页表和页。

至于第3位和第4位的两个标志,我们暂时可以忽略它们。

cr0cr3PDEPTE的结构有了解之后,接下来编写代码启动分页机制:

编写代码启动分页机制

这里不考虑特权级的变化,这样更能专注于分页机制的实现。

这里仅列出新增代码,完整代码会放在本文的最后。

  8 PageDirBase             equ     200000h ; 页目录开始地址: 2M
  9 PageTblBase             equ     201000h ; 页表开始地址: 2M+4K
...

 19 LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
 20 LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables
...

 34 SelectorPageDir         equ     LABEL_DESC_PAGE_DIR     - LABEL_GDT
 35 SelectorPageTbl         equ     LABEL_DESC_PAGE_TBL     - LABEL_GDT
...

166 [SECTION .s32]; 32 位代码段. 由实模式跳入.
167 [BITS   32]
168
169 LABEL_SEG_CODE32:
170         call    SetupPaging
...

202 ; 启动分页机制 --------------------------------------------------------------
203 SetupPaging:
204         ; 为简化处理, 所有线性地址对应相等的物理地址.
205
206         ; 首先初始化页目录
207         mov     ax, SelectorPageDir     ; 此段首地址为 PageDirBase
208         mov     es, ax
209         mov     ecx, 1024               ; 共 1K 个表项
210         xor     edi, edi
211         xor     eax, eax
212         mov     eax, PageTblBase | PG_P  | PG_USU | PG_RWW
213 .1:
214         stosd
215         add     eax, 4096               ; 为了简化, 所有页表在内存中是连续的.
216         loop    .1
217
218         ; 再初始化所有页表 (1K 个, 4M 内存空间)
219         mov     ax, SelectorPageTbl     ; 此段首地址为 PageTblBase
220         mov     es, ax
221         mov     ecx, 1024 * 1024        ; 共 1M 个页表项, 也即有 1M 个页
222         xor     edi, edi
223         xor     eax, eax
224         mov     eax, PG_P  | PG_USU | PG_RWW
225 .2:
226         stosd
227         add     eax, 4096               ; 每一页指向 4K 的空间
228         loop    .2
229
230         mov     eax, PageDirBase
231         mov     cr3, eax
232         mov     eax, cr0
233         or      eax, 80000000h
234         mov     cr0, eax
235         jmp     short .3
236 .3:
237         nop
238
239         ret
240 ; 分页机制启动完毕 ----------------------------------------------------------

上面的指令中,只有stosd没有学过。类似的指令有stosbstoswstosd。这三个指令就是把alaxeax的内容存储到edi指向的内存单元中,同时edi的值根据方向标志的值增加或者减少。这里使用的是loop指令。它还可以同rep前缀联合使用。这里我没找到设置方向标志位的指令,难道是初始时候方向标志位已经为0了?

上面的代码实现的功能如下图:

开头的第207行和第208行将段寄存器es对应页目录表段,下面让edi等于0,于是es:edi就指向了页目录表的开始。第214行的指令stosd第一次执行时就把eax中的PageTblBase|PG_P|PG_USU|PG_RWW存入了页目录表的第一个PDE。

那么来看看这个PDE是什么值。PageTblBase|PG_P|PG_USU|PG_RWW(第212行)让当前(第一个)PDE对应的页表首地址变成PageTblBase,而且属性显示其指向的是存在的可读可写的用户级别页表。

实际上,当为页目录表中的第一个PDE赋值时,一个循环就已经开始了。循环的每一次执行中,es:edi会自动指向下一个PDE,而第215行也将下一个页表的首地址增加4096字节,以便与上一个页表首尾相接。这样,经过1024次循环(第209行由ecx指定)之后,页目录表中的所有PDE都被赋值完毕,它们的属性相同,都为指向可读可写的用户级别页表,并且所有的页表连续排列在以

PageTblBase为首地址的4MB(4096×1024)的空间中。

接下来的工作是初始化所有页表中的PTE(第218行到第228行)。由于总共有1024×1024个PTE,于是将ecx赋值为1024×1024,以便让循环进行1024×1024次。开始对es和edi的处理让es:edi指向了页表段的首地址,即地址PageTblBase处,也是第一个页表的首地址。

第一个页表中的第一个PTE被赋值为PG_P|PG_USU|PG_RWW,不难理解,它表示此PTE指示的页首地址为0,并且是个可读可写的用户级别页。这同时意味着第0个页表中第0个PTE指示的页的首地址是0,于是线性地址0~0FFFh将被映射到物理地址0~0FFFh,即f(x)=x,其中0x0FFFh。接下来进行的循环初始化了剩下的所有页表中的PTE,将4GB空间的线性地址映射到相同的物理地址。如上图所示

这样,页目录表和所有的页表被初始化完毕。接下来到了正式启动分页机制的时候了。首先让cr3指向页目录表(第230行和第231行),然后设置cr0的PG(第232行到第234行),这样,分页机制就启动完成了。

运行结果如下:

从这里我们看到启动分页机制后。我们无法在屏幕上看到分页机制的影子。这是因为我们只是把所有的线性地址映射到完全相同的物理地址上,而并没有对其做其他的操作。所以我们看不出来表面上的变化。而且这种方式会出现两个问题:一是页表显然浪费得太多了,我们可能根本没有那么大的内存;二是我们除了“实现了”分页,并没有“得益于”分页,也就是说,我们还没有体会到分页的妙处。上面的问题我们在下节介绍。

源代码

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

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

PageDirBase        equ    200000h    ; 页目录开始地址: 2M
PageTblBase        equ    201000h    ; 页表开始地址: 2M+4K

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_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables
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        ; 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
SelectorPageDir        equ    LABEL_DESC_PAGE_DIR    - LABEL_GDT
SelectorPageTbl        equ    LABEL_DESC_PAGE_TBL    - 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:
SPValueInRealMode    dw    0
; 字符串
PMMessage:        db    "In Protect Mode now. ^-^", 0    ; 进入保护模式后显示此字符串
OffsetPMMessage        equ    PMMessage - $$
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    [SPValueInRealMode], sp

    ; 初始化 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, [SPValueInRealMode]

    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:
    call    SetupPaging

    mov    ax, SelectorData
    mov    ds, 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:    ; 显示完毕

    ; 到此停止
    jmp    SelectorCode16:0

; 启动分页机制 --------------------------------------------------------------
SetupPaging:
    ; 为简化处理, 所有线性地址对应相等的物理地址.

    ; 首先初始化页目录
    mov    ax, SelectorPageDir    ; 此段首地址为 PageDirBase
    mov    es, ax
    mov    ecx, 1024        ; 共 1K 个表项
    xor    edi, edi
    xor    eax, eax
    mov    eax, PageTblBase | PG_P  | PG_USU | PG_RWW
.1:
    stosd
    add    eax, 4096        ; 为了简化, 所有页表在内存中是连续的.
    loop    .1

    ; 再初始化所有页表 (1K 个, 4M 内存空间)
    mov    ax, SelectorPageTbl    ; 此段首地址为 PageTblBase
    mov    es, ax
    mov    ecx, 1024 * 1024    ; 共 1M 个页表项, 也即有 1M 个页
    xor    edi, edi
    xor    eax, eax
    mov    eax, PG_P  | PG_USU | PG_RWW
.2:
    stosd
    add    eax, 4096        ; 每一页指向 4K 的空间
    loop    .2

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

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

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]
时间: 2024-10-10 14:58:27

一个操作系统的实现(6)-初识分页机制的相关文章

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

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

一个操作系统的实现(7)-获取机器内存并进行合理分页

在前面的程序中,我们用了4MB的空间来存放页表,并用它映射了4GB的内存空间,而我们的物理内存不见得有这么大,这显然是太浪费了.如果我们的内存总数只有16MB的话,只是页表就占用了25%的内存空间.而实际上,如果仅仅是对等映射的话,16MB的内存只要4个页表就够了.所以,我们有必要知道内存有多大,然后根据内存大小确定多少页表是够用的.而且,一个操作系统也必须知道内存的容量,以便进行内存管理. 克勤克俭用内存 这里利用中断15h来获取计算机的内存. 在调用中断15h之前,我们需要填充下列寄存器:

Linux的分段和分页机制

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

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

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

分页机制

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

[转帖]Linux分页机制之分页机制的演变--Linux内存管理(七)

Linux分页机制之分页机制的演变--Linux内存管理(七) 2016年09月01日 20:01:31 JeanCheng 阅读数:4543 https://blog.csdn.net/gatieme/article/details/52402967 ~ 版权声明:本文为博主原创文章 && 转载请著名出处 @ http://blog.csdn.net/gatieme https://blog.csdn.net/gatieme/article/details/52402967 日期 内核版

JAVA 初识类加载机制 第13节

JAVA 初识类加载机制 第13节 从这章开始,我们就进入虚拟机类加载机制的学习了.那么什么是类加载呢?当我们写完一个Java类的时候,并不是直接就可以运行的,它还要编译成.class文件,再由虚拟机解释给当前的操作系统去执行.这些过程都是我们看不见的,我们能看见的也就是一个.class文件.既然虚拟机要解释这些.class文件给当前的操作系统听,那么他怎么获得这些.class文件呢?虚拟机获得这些.class文件的过程就是类加载了. 所以,总结来说就是:虚拟机将.class文件从磁盘或者其他地

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

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

32机的内存分页机制

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