Norlit OS —— 自制操作系统 第6章 内存管理

本文详细介绍了在自制操作系统Norlit OS中如何实现内存管理,包括分组内存、空闲链表、分配与释放内存的函数实现,以及利用TSS进行特权级切换。通过手动优化代码,提高内存管理效率,并探讨了调试断点和高端内存的处理方法。

6  内存管理

6.1         内存管理

大家在日常开发的时候经常用到mallocfree函数。但是,在内核中,我们并没有mallocfree这两个函数。这该怎么办呢?

         答案是自己规划。我们先建立kernel/memory/memory.c,然后把cstart中的内存处理相关程序移动到memory.c

         为了保持内存使用的对齐,我们将所有可用的内存分组,最少为8字节一组,然后16字节……以此类推,最大可达2GB一组。内存中,我们用链表的形式,链表节点存放在空闲内存的前4字节的位置上。

         知道原理以后,代码其实很好写。

另外,笔者观赏了一下GCC生成的代码,感觉非常狗屎,memory_free函数笔者用汇编可以21行写出的代码,GCC用了38行,而且还是手工对C代码优化过后的。这38行中包含大量的内存操作。而笔者的代码完全没有进行过内存操作。设想这是一段核心代码(还真的是核心代码),这点小小的时间差距会累积成多大!malloc函数笔者用了16行,而GCC用了39行。这就体现了为什么大型工程中的核心代码都是汇编写的。在用汇编改写之后,最后的二进制文件减小了1.5KB。在NorlitOS最初的开发阶段中,我们会尽少使用汇编,但是之后的优化工程中,汇编是必不可少的。

u_addr* memoryBlocks[30]={EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,//0-7 Bytes

                         EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,//8-17 KBs

                         EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,EMPTY,//18-27 MBs

                         EMPTY,EMPTY};// 28-29 GBs

代码6.1.1数据结构(chapter6/a/kernel/memory/memory.c)

我们不管理4Bytes以下的内存,因为管理这些内存会给系统额外的开销。对于这些内存,我们简单地丢弃它们。不过,使用mallocfree功能永远也不会产生这些多余项。EMPTY被定义成了0xFFFFFFFF,但其实0xFFFFFFD0xFFFFFFFE也是可以的。超过4KB的内存实际上应该让分页机制管理了,但是在分页机制实现之前,我们先把实现容许的最大范围变成2^31次,也就是2GB

笔者修改了loader.asm,将cpuid.inc与之合并,并且加入了检测分页机制支持的功能。

CPUInit:

   pushfd                  ;储存EFLAGS

   mov    eax,[esp]       ;保存老的EFLAGS

   bts    dword[esp],21   ;更改EFLAGS.ID

   popfd                   ;设置EFLAGS

   pushfd                  ;保存新EFLAGS

   pop    ebx              ;EBX=EFLAGS

   cmp    eax,ebx          ;检查EFLAGS.ID有没有被成功更改

 

   je     .nocpuid

   

   mov    eax,0x80000000     ; CPU扩展信息

   cpuid

   cmp    eax,0x80000001

   xor   eax,eax

   jb     .nonx     ;不支持CPU扩展信息

   

   mov    eax,0x80000001

   cpuid

   xor   eax,eax

   bt    edx,20      ;Check NX

   adc   al,0

   shl   al,1

.nonx:

   push  eax

   

   mov    eax,0x7

   xor   ecx,ecx

   cpuid

   pop   eax

   bt    ebx,7    ;Check SMEP

   adc   al,0

   shl   al,1

   

   push  eax       ;Reserved  NX SMEP PSE-36 PAT PGE PAE PSE

   

   mov   eax,0x1

   cpuid

   pop   eax

   bt    edx,17      ;Check PSE-36

   adc   al,0

   shl   al,1

   

   bt    edx,16      ;Check PAT

   adc   al,0

   shl   ax,1

   

   bt    edx,13      ;Check PAT

   adc   ax,0

   shl   ax,1

   

   bt    edx,6    ;Check PAE

   adc   ax,0

   shl   ax,1

   

   bt    edx,3    ; CheckPSE

   adc   ax,0

   

   mov   [BootParamPhyAddr+ PGInfo],eax

   bt    eax,1

   jc     PagingOnPAE

   jmp    PagingOn

   

.nocpuid:

   mov    esi, STR_FAIL

   call   DispStrPM

   cli

hlt

代码6.1.2检测机能(chapter6/a/boot/loader.asm)

当然了,由于我们修改了0x500处的结构,别忘记同时修改loader.incstruct

FASTCALLstaticvoidmemory_block_free(u_addraddress, u_addr sizep, u8 bit){

    u_addr* ll=(u_addr*)address;

    u_addr** head=&memoryBlocks[bit-2];

    u_addr*entry=*head;

    u_addr**prev=head;

   for(;entry!=EMPTY;prev=(u_addr**)entry,entry=*prev){

      if(entry>ll){

          if(!(address&sizep)&&(address+sizep==(u_addr)entry)){

             *prev=(u_addr*)(*entry);

              memory_block_free(address, sizep*2, bit+1);

             return;

          }

          break;

      }elseif((address&sizep)&&((u_addr)entry+sizep==address)){

          *prev=(u_addr*)(*entry);

           memory_block_free((u_addr)entry, sizep*2, bit+1);

          return;

      }

   }

   *prev=ll;

   *ll=(u_addr)entry;

}

 

FASTCALLvoid memory_free_nocheck(u_addr address, u_addr size){

    u8 bit;

   for(bit=2;bit<32;bit++){

       u_addr sizep=1<<bit;

      if(address&sizep){

          if(size<sizep)break;

           memory_block_free(address,sizep,bit);

           address+=sizep;

           size-=sizep;

      }

   }

   for(;bit>1;bit--){

       u_addr sizep=1<<bit;

      if(size&sizep){

           memory_block_free(address,sizep,bit);

           address+=sizep;

           size-=sizep;

      }

   }

}

 

/******************************

 FASTCALL void memory_free(u_addr, u_addr) ; Free memorywith security check

 ******************************

 memory_free:

    cmp edx, 3

    jbe .3

    test eax, 3

    jz  .1

    mov ecx, eax

    and ecx, 0xFFFFFFFC

    add ecx, 4

    sub ecx, eax

    sub edx, ecx

    add eax, ecx

    cmp edx, 3

    jbe .3

 .1:

    test edx, 3

    jz .2

    and edx, 0xFFFFFFFC

 .2:

    jmp memory_free_nocheck

 .3:

    ret

 ******************************/

FASTCALLvoid memory_free(u_addr address, u_addr size){

   if(size<0b100)return;

   if(address&0b11){

       u_addr diff=(address&0b111111111111111111111111111111100)+0b100-address;

       size-=diff;

       address+=diff;

      if(size<0b100)return;

   }

   if(size&0b11)size&=0b11111111111111111111111111111100;

    memory_free_nocheck(address,size);

}

 

/******************************

 FASTCALL void* memory_alloc(u8) ; allocate 2^bit-lengthmemory block

 ******************************

 memory_alloc:

    lea edx, [eax*4+memoryBlocks-8]

    mov ecx, [edx]

    inc ecx

    or  ecx, ecx

    jz  .1

    dec ecx

    mov eax, [ecx]

    mov [edx],eax

    mov eax, ecx

    ret

 .1:

    push ebx

    inc eax

    mov ebx, eax

    call memory_alloc

    mov ecx, ebx

    xor edx, edx

    inc edx

    shl edx, cl

    mov ebx, eax

    add eax, edx

    call memory_free_nocheck

    mov eax, ebx

    pop ebx

    ret

 ******************************/

FASTCALLvoid* memory_alloc(u8 bit){

    u_addr** head=&memoryBlocks[bit-2];

    u_addr* getit=(u_addr*)(*head);

   if(getit==EMPTY){

       getit=memory_alloc(bit+1);

       memory_free_nocheck(((u_addr)getit)+(1<<bit),1<<bit);  

   }else{

      *head=*((u_addr**)getit);

   }

   return(void*)getit;

}

 

/******************************

 FASTCALL void* malloc(u_addr); allocate a block ofmemory

 ******************************

 malloc:

    add eax, 3

    and eax, 0xFFFFFFFC

    add eax, 4

    bsr edx, eax

    push ebx

    mov ebx, eax

    btr eax, edx

    xor eax, eax

    jz .1

    inc edx

 .1:

    mov eax, edx

    push eax

    call memory_alloc

    pop ecx

    xor edx, edx

    inc edx

    shl edx, cl

    mov [eax], edx

    cmp edx, ebx

    jz .2

    push eax

    add eax, ebx

    sub edx, ebx

    call memory_free_nocheck

    pop eax

.2:

    pop ebx

    ret

 ******************************/

FASTCALLvoid* malloc(u_addr size){

    size=((size-1+sizeof(u_addr))/4+1)*4;

    u8 bit;

   for(bit=2;bit<32;bit++){

      if((1<<bit)>=size)break;

   }

   void* getit=memory_alloc(bit);

   if((1<<bit)!=size)memory_free_nocheck(((u_addr)getit)+size,(1<<bit)-size);

   *((u_addr*)getit)=1<<bit;

   return getit+sizeof(u_addr);

}

 

/******************************

 FASTCALL void free(void*)  ; free an mallocedblock

 ******************************

 free:

    sub eax, 4

    mov edx, [eax]

    jmp memory_free_nocheck

 ******************************/

FASTCALLvoid free(void* addr){

    u_addr* ad=(u_addr*)(addr-sizeof(u_addr));

    memory_free_nocheck((u_addr)ad,*ad);

}

 

FASTCALLvoid init_memory(){

    BootParam* bp=(BootParam*)0x500;

   {

       u32 pgFlag=bp->pgFlag;

      if(pgFlag&0b1)puts("PSE ");

      if(pgFlag&0b10)puts("PAE ");

      if(pgFlag&0b100)puts("PGE ");

      if(pgFlag&0b1000)puts("PAT ");

      if(pgFlag&0b10000)puts("PSE-36 ");

      if(pgFlag&0b100000)puts("SMEP ");

      if(pgFlag&0b1000000)puts("NX ");

       puts("\r\n");

   }

    u32 bplen=bp->len;

    puts("Total Items: ");dispInt(bp->len);

    puts("\r\nBase:     Limit:     Type:\r\n");

    ARDSItem* ai=bp->items;

    u64 max=0;

   for(;bplen>0;bplen--,ai++){

       dispInt(ai->base);putc(' ');

       dispInt(ai->limit);putc(' ');

      switch(ai->type){

      case5:puts("Unusable\r\n");continue;

      case6:puts("Disabled\r\n");continue;

      case1:puts("Avaliable\r\n");break;

      case3:puts("ACPI\r\n");break;

      case4:puts("NVS\r\n");break;

      default:puts("Reserved\r\n");continue;

      }

       max=ai->base+ai->limit;

   }

    puts("The Size of Your Memory: ");dispInt(max/0x100000);puts("MB\r\n");

   if(max<0x800000){

       puts("Your memory isless than 8MB");

   }

   if(max>0xFFFFFFFF&&bp->pgFlag&0b10)puts("Your memory isover 4GB. Please Turn on PAE if you want to use them.");

 

    memory_free_nocheck(0x100000+KERNEL_SIZE,0x100000-KERNEL_SIZE);

}

代码6.1.3好戏在后头(chapter6/a/kernel/memory/memory.c)

总体来看,在分配的时候还得释放,导致我们分配的速度会下降。释放时需要计算块的合并,复杂度也比较高。总体来看我们的算法不咋的,不过在实现分页之前,我们只要一个能用的算法就行。

 

6.2         题外话调试断点

在编写程序的时候,无论是操作系统还是应用软件,都可能会存在意想不到的bug。这些bug就需要我们来debug,也就是调试。但是在我们系统开发中,只能使用bochs进行调试。由于操作系统本身不带有调试信息,所以我们也不知道断点应该设置在哪里。所以,我们需要手工实现调试断点。

        调试断点一般用int 3,因为这个指令可以生成1字节的二进制代码,所以能够充当软断点的作用。我们也使用int 3。要中断时,我们在汇编中插入int 3的代码或者在c中插入__asm__(“int $3”);的代码。

break_point:

   mov   [0x0],esp

   mov   esp,0x500

   pushad

   mov   eax,[0x0]

   push  dword[eax+8]

   push  dword[eax]

   push  dword[0x0]

   call   breakpoint_disp

   add   esp,16

   popad

   mov   esp,[0x0]

   iretd

代码6.2.1中断处理程序改写(chapter6/a/kernel/interrupt.asm)

然后我们再breakpoint_disp中显示各参数。我们用pushad的本意是保存寄存器不被破坏,但是我们也可以加以利用,在breakpoint_disp中加以显示。CS的值是恒定的我们就忽略了。

ASMLINKAGEvoid breakpoint_disp(u_addr esp12, u_addr eip, u_addr eflags,

    u_addr edi, u_addr esi, u_addr ebp, u_addr temp,

    u_addr ebx, u_addr edx, u_addr ecx, u_addr eax){

    puts("\r\n#BP Breakpoint\r\nEAX: ");

    dispInt(eax);puts(" ECX: ");dispInt(ecx);puts("\r\nEDX: ");

    dispInt(edx);puts(" EBX: ");dispInt(ebx);puts("\r\nESP: ");

    dispInt(esp12+12);puts(" EBP: ");dispInt(ebp);puts("\r\nESI: ");

    dispInt(esi);puts(" EDI: ");dispInt(edi);puts("\r\nEIP: ");

    dispInt(eip);puts("\r\nEFLAGS: ");dispInt(eflags);

}

代码6.2.2调试信息(chapter6/a/kernel/int.c)

我们顺便在typedef.h里面定义BREAKPOINT__asm__(“int $3”),然后随便加一句BREAKPOINT;试试。

6.2.1成功!

 

6.3         高端内存

Linux把操作系统的内存区域放置在0xC000000以上,也就是3GB以上的逻辑地址。你应该还记得我们最初loader里面映射内存的时候把全部的内存都映射到了2MB或者4MB的区域(视PAE情况而定)。也就是说,我们几乎无需改动什么代码就可以把内核加载到高端的内存。唯一需要改动的是我们必须先分页再复制内核。接下来就可以简单地把入口点设置为0xC0100000,(别忘了改loader.inc),然后把内核中所有的常量都改为对应的0xC0开头的地址。这部分很简单,我就不贴代码了。

将来我们将把进程的地址空间放在前3GB,所以在目前的开发中,我们必须避免对低3GB的访问。我们删除掉前3GB的页表。

BootParam* bp=(BootParam*)0xC0000500;

   if(bp->pgFlag&0b10){

      for(a=0xC0003000;a<0xC0003018;a++)*a=0x0;

   }else{

      for(a=0xC0002000;a<0xC0002C00;a++)*a=0x0;

   }

    __asm__ __volatile__("mov%cr3,%eax");

    __asm__ __volatile__("mov%eax,%cr3");

__asm__ __volatile__("":::"memory");

代码6.3.1去除低端页表(chapter6/b/kernel/memory/memory.c)

这里我们用了gcc的内联汇编来刷新TLB缓存,使我们的更改即时生效,要注意的是GCC的汇编和MASMNASM不同,运算是结果在右的,也就是说mov%cr3,%eax(GCC)=mov eax, cr3(MASM&NASM)

Make一下,运行正常的话说明我们的程序已经完全运行在高端地址了。

不过这么临时的页表修改不是大计,我们接下来肯定还要完善页表管理机制,就像之前的内存管理一样。由于我们分配页表时还要考虑特权极的问题,所以,在此之前,我们切换到Ring3试试。

6.4         TSS (Task-State Segment)

TSS,根据英文来看的话是用来保存进程的状态的。然而,我们现在并没有实现进程。那我们为什么要讲述TSS呢?原因是栈。从Ring3返回Ring0时(如中断),势必要涉及到栈的切换。而一旦进入中断处理程序,一些参数已经被压栈,我们再要改变已经来不及了。所以,我们势必要在进入内核前切换栈。Intel给我们准备了一个强有力的工具-TSSTSS中能够设置栈的位置,并且在优先级变换的同时栈也会被切换。这样就保证了内核和进程互不干扰。

本来Intel让我们直接Call或者JmpTSS就可以开始进程。不过这个方法可控性不如由系统来开始。所以,我们不利用TSS的其它功能,只利用栈的切换。

6.4.1 TSS的结构

上图是TSS的图示。我们先来实现一下TSS的结构。

;=====================================================

; InitTSS(u32 esp0, u16 ss0);初始化TSS

;-----------------------------------------------------

; Entry:

;   - arg0 -> ss0

;   - arg1 -> esp0

; Exit:

;   -填充一个TSS

%macro InitTSS 2

   dw0,0;backlink

   dd%2 ; esp0

   dw%1,0   ; ss0

   dd0  ; esp1

   dw0,0;ss1

   dd0  ; esp2

   dw0,0;ss2

   dd0  ; cr3

   dd0  ; eip

   dd0  ; flags

   dd0  ; eax

   dd0  ; ecx

   dd0  ; edx

   dd0  ; ebx

   dd0  ; esp

   dd0  ; ebp

   dd0  ; esi

   dd0  ; edi

   dw0,0;es

   dw0,0;cs

   dw0,0;ss

   dw0,0;ds

   dw0,0;fs

   dw0,0;gs

   dw0,0;ldt

   dw0  ; trap

   dw104; iobase

%endmacro

代码6.4.1填充TSS(chapter6/c/boot/include/protect.inc)

然后在GDT表中加入一行

GDT_FLAT_TSS:  SegmentDescriptor0,TSS_END-TSS_START,DA_386TSS;TSS

按照我们一贯的偷懒风格,最好所有的内容都在编译时设置好。但是TSSdata段,而我们又没法再编译时知道data段的地址,所以我们还是在运行时设置Base。设置base的代码非常简单:

 

mov   eax,TSS_START

   mov   [GDT_FLAT_TSS+2],ax

   shr   eax,16

   mov   [GDT_FLAT_TSS+4],al

mov   [GDT_FLAT_TSS+7],ah

 

lgdt  [GdtPtr]

   lidt  [IdtPtr]

   mov   ax,SEL_FLAT_TSS

ltr   ax

代码6.4.2设置GDT中的TSS(chapter6/c/kernel/interrupt.asm)

 

So Easy, Right?我们打开Bochs调试,用info tss命令看一下

<bochs:2>info tss

tr:s=0x28, base=0x00000000c0101090, valid=1

ss:esp(0): 0x0010:0xc019fc00

ss:esp(1): 0x0000:0x00000000

ss:esp(2): 0x0000:0x00000000

cr3: 0x00000000

eip: 0x00000000

eflags: 0x00000000

cs: 0x0000 ds: 0x0000 ss: 0x0000

es: 0x0000 fs: 0x0000 gs: 0x0000

eax: 0x00000000  ebx: 0x00000000  ecx:0x00000000  edx: 0x00000000

esi: 0x00000000  edi: 0x00000000  ebp:0x00000000  esp: 0x00000000

ldt: 0x0000

i/o map: 0x0068

Perfect!我们已经设置完毕了TSS

接下来就可以开始转移特权极了,我们利用iretd函数:

mov   ax,32|3

   mov   ds,ax

   mov   es,ax

   mov   fs,ax

   mov   gs,ax

   push  dword32|3

   push  esp

   push  dword0x1202

   push  dword24|3

   push   ring3

iretd

 

ring3:

   int   3

   jmp   $

 

代码6.4.3最后的转移(chapter6/c/kernel/entry.asm)

这里要注意的是,如果使用软中断,目标门的DPL必须要小于等于当前CPL。所以我们修改一下我们的IDT

GateDescriptor    BREAK_POINT, SEL_FLAT_C,0, DA_386IGate + SDA_DPL3  ;3

这样就好了。至此,我们已经完成了从Ring0Ring3的切换。Make并运行一下。不好!bochs自动重启了!对了,笔者突然想到,我们初始化的页表是内核级别的页表。把lib.inc里的00000011b都换成00000111b了以后,成功运行了!我们用sreg看一下,的确在ring3运行!

<bochs:2>sreg

es:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1

        Datasegment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed

cs:0x001b, dh=0x00cffb00, dl=0x0000ffff, valid=1

        Codesegment, base=0x00000000, limit=0xffffffff, Execute/Read, Accessed,

 32-bit

ss:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1

        Datasegment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed

ds:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=7

        Datasegment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed

fs:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1

        Datasegment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed

gs:0x0023, dh=0x00cff300, dl=0x0000ffff, valid=1

        Datasegment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed

ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1

tr:0x0028, dh=0xc0008b10, dl=0x10b00068, valid=1

gdtr:base=0x00000000c0100f00, limit=0x2f

idtr:base=0x00000000c0100f30, limit=0x17f

这是在是太妙了!从今以后,我们就可以灵活地在不同特权极间转移了!我们的GDT终于也发挥了全部的作用。

 

6.5         Oh! Oh~ Start Page

我们之前的键盘中断还开着,先关了,不然有点烦。

还有,在测试的过程中,我发现malloc中大部分的时间都浪费在回收多余的内存上了

if((1<<bit)!=size)memory_free_nocheck(((u_addr)getit)+size,(1<<bit)-size)

我们稍作修改以后malloc的时间顿时减少了80%。可喜可贺。看来果真80%的时间会消耗在20%的代码上(八坂真寻:这里貌似只有1%的代码啊)。

这部分代码就自己去源代码看吧,自己写东西的时候就会发现很多时候,一些细节会影响成败,但能发现的话就再好不过了。

创建4GB内存对应的页表最多需要4MB。而我们一共也就初始化了4MB的页表。我们修改一下loader.inclib.inc,使其提供8MB的页表支持,然后修改memory.c,把这些空间加入到分配中。

然后我们创建paging.c,添加一个函数

#define ALLOC_PAGE() memory_alloc(12)

#define PAGE_OFFSET 0xC0000000

#define MAX_MAPPING 0x40000000

#define va2pa(x) ((u_addr)(x)-PAGE_OFFSET)

#define pa2va(x) ((u_addr)(x)+PAGE_OFFSET)

 

u_addr* flat_ptes=NULL;

u_addr* kernel_pde=NULL;

 

FASTCALLvoid init_paging(u_addr memsize){

    u_addr index;

    u_addr pages=memsize/0x1000;

    flat_ptes=memory_alloc(22);// Allocate at most 4MB page table entrys

    u_addr allocpages=(pages+1024-1)&~(1024-1);

   {

       memory_free_nocheck(((u_addr)flat_ptes)+allocpages*sizeof(u_addr),(1<<22)-allocpages*sizeof(u_addr));

          // Release extra memory

       u_addr* pteptr=flat_ptes;

      for(index=0;index<pages;index++,pteptr++){

          *pteptr=index*0x1000+0b111;

      }

      for(;index<allocpages;index++,pteptr++){

          *pteptr=index*0x1000;   // Page not present

      }

   }

    u_addr* kernel_pde=ALLOC_PAGE();

   {

       u_addr* pdeptr=kernel_pde;

       u_addr maxpdeentry=allocpages/1024;

       maxpdeentry=maxpdeentry>(MAX_MAPPING/0x1000/1024)?(MAX_MAPPING/0x1000/1024):maxpdeentry;

      for(index=0;index<PAGE_OFFSET/0x1000/1024;index++,pdeptr++){

          *pdeptr=0;// Page not present

      }

      for(index=0;index<maxpdeentry;index++,pdeptr++){

          *pdeptr=va2pa(flat_ptes)+index*0x1000+0b111;

      }

      for(;index<1024-(PAGE_OFFSET/0x1000/1024);index++,pdeptr++){

          *pdeptr=0;// Page not present

      }

   }

    __asm__ __volatile__("movl%0,%%cr3"::"a"(va2pa(pdes)));

}

代码6.5.1平坦分页(chapter6/d/kernel/memory/paging.c)

这里面比较重要的一点是va2pavirtualaddress to physical address),虚拟地址转换为物理地址。对于内核来说,虚拟地址等同线性地址,而线性地址又相当于物理地址+0xC0000000。不过这些数值都用宏替换来解决。本质上讲,这段代码将flat_ptes所指向的对象初始化为一个pte数组,这部分在平坦分页或者说一次性处理4MB的分页时非常有用。然后初始化了内核用的pde,只初始化了高1GB的内存。最后一句话把pde的地址转化为物理地址加载到了cr3。至此,1GB一下的内存完全被初始化到了内核空间。对了,这里初始化的分页全部是用户级别的,为了使我们之前的切换到ring3的代码正常工作。未来在实现保护机制的时候,我们还会要修改这些特权级的事。我们先实现一下进程看看。

 

前6章源代码下载地址:百度网盘

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值