学习啦>学习电脑>网络知识>网络基础知识>

线性地址转物理地址

春健分享

  逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问到。那么线性地址怎么转物理地址?今天学习啦小编整理了线性地址转物理地址的相关资料,希望对大家有帮助。

  线性地址转物理地址

  线性地址(Linear Address) 是逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。

  详细说下虚拟地址的转换。

  首先,来看下内核为一个进程建立页目录和页表以及线性空间的函数。这个函数原型是:

  VOID

  MiInitMachineDependent (

  IN PLOADER_PARAMETER_BLOCK LoaderBlock

  )

  位于init386.c的762行,这个函数太长了,挑选部分如下:

  PointerPte = MiGetPdeAddress (PDE_BASE);

  PdePageNumber = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);

  CurrentProcess = PsGetCurrentProcess ();

  #if defined(_X86PAE_)

  PrototypePte.u.Soft.PageFileHigh = MI_PTE_LOOKUP_NEEDED;

  _asm {

  mov eax, cr3

  mov DirBase, eax

  }

  //

  // Note cr3 must be 32-byte aligned.

  //

  ASSERT ((DirBase & 0x1f) == 0);

  //

  // Initialize the PaeTop for this process right away.

  //

  RtlCopyMemory ((PVOID) &MiSystemPaeVa,

  (PVOID) (KSEG0_BASE | DirBase),

  sizeof (MiSystemPaeVa));

  CurrentProcess->PaeTop = &MiSystemPaeVa;

  #else

  DirBase = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte) << PAGE_SHIFT;

  #endif

  CurrentProcess->Pcb.DirectoryTableBase[0] = DirBase;

  KeSweepDcache (FALSE);

  //

  // Unmap the low 2Gb of memory.

  //

  PointerPde = MiGetPdeAddress (0);

  LastPte = MiGetPdeAddress (KSEG0_BASE);

  MiZeroMemoryPte (PointerPde, LastPte - PointerPde);

  第一句:PointerPte = MiGetPdeAddress (PDE_BASE);这句获得一个页表的指针。

  PDE_BASE是页目录的基址,这个宏的定义如下:(i386.h 1968行)

  #define PDE_BASE_X86 0xc0300000

  #define PDE_BASE_X86PAE 0xc0600000

  #define PTE_TOP_X86 0xC03FFFFF

  #define PDE_TOP_X86 0xC0300FFF

  #define PTE_TOP_X86PAE 0xC07FFFFF

  #define PDE_TOP_X86PAE 0xC0603FFF

  #if !defined (_X86PAE_)

  #define PDE_BASE PDE_BASE_X86

  #define PTE_TOP PTE_TOP_X86

  #define PDE_TOP PDE_TOP_X86

  #else

  #define PDE_BASE PDE_BASE_X86PAE

  #define PTE_TOP PTE_TOP_X86PAE

  #define PDE_TOP PDE_TOP_X86PAE

  #endif

  #define PTE_BASE 0xc0000000

  可以看出,如果CPU支持PAE或者开启了PAE(奔腾往后都支持,默认开启),那么一个进程页目录的基址为0xc0600000,这个在dbg调试的时候也可以看到,如

  kd> !pte 0000

  VA 00000000 (虚拟地址)

  PDE at C0600000 PTE at C0000000

  contains 0000000002B40067 contains 0000000000000000

  pfn 2b40 ---DA--UWEV not valid

  再来看这个MiGetPdeAddress ,他是个宏,定义如下:(mi386.h 1889行)

  #define MiGetPdeAddress(va) ((PMMPTE)(((((ULONG)(va)) >> 22) << 2) + PDE_BASE))

  可以看出他是取线性地址的最高10位,再左移2位,加上页目录的基址,得到该线性地址在该进程的 页目录的索引,而该处存放的就是其对应的页表索引,第二句:

  PdePageNumber = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);

  MI_GET_PAGE_FRAME_FROM_PTE 这个宏(mi386.h 2542行)的定义如下:

  #define MI_GET_PAGE_FRAME_FROM_PTE(PTE) ((PTE)->u.Hard.PageFrameNumber)

  他是直接取该页表对应的物理内存的地址。到这里,CPU就可以访问到实际的物理内存了。

  剩下的工作就是把cr3寄存器的内容赋给dirbase,再把dirbase赋给进程pcb的DirectoryTableBase项,这个在windbg调试的时候我说过了。后面再把该进程的低2G的用户空间清零。

  我举个例子,咱来一步步转换。(我关闭了PAE,关闭方法,网上找,xp sp3)

  假如线性地址为7c920000,这个地址一般加载的是进程的ntdll文件,首先把它转换成二进制,我在windbg里做,如下:

  kd> .formats 7c920000

  Evaluate expression:

  Hex: 7c920000

  Decimal: 2089943040

  Octal: 17444400000

  Binary: 01111100 10010010 00000000 00000000

  Chars: |...

  Time: Mon Mar 24 11:44:00 2036

  Float: low 6.0646e+036 high 0

  Double: 1.03257e-314

  可以看到高10位为01111100 10十六进制为0x1f2,

  01111100 10 ->0x1F2按照上面的宏,0x1F2在左移2位,为0x7C8,加上PDE的基址C0300000,结果为C03007C8,我们在windbg里看下,如下:

  kd> !pte 7c920000

  VA 7c920000

  PDE at C03007C8 PTE at C01F2480

  contains 03793067 contains 03791025

  pfn 3793 ---DA--UWEV pfn 3791 ----A--UREV

  可以看到,结果是吻合的,下面我们来算页表,按照上面的说法,一个进程的页目录可以达到1024项(2的10次方),而每项都是4字节,所以一个进程的页目录大小为4096bytes(4k),同理,一个页表的大小也为4096bytes(4k),而每一个页目录项对应一个4k大小的页表,所以,页表项的地址可以这么算:

  PTE = 页表基址+(页目录项索引 × 一个页表的大小) + (页表索引 × 每一个页表项的大小)

  由上面的宏定义知道在关了PAE的情况下页表的基址PTE_BASE为0xc0000000,一个页表项的大小也为4字节。而该线性地址的中间10位为010010 0000十六进制为0x120,所以,该线性地址对应的页表地址为:

  PTE = 0xC0000000 + (0x1F2 * 0x1000) + (0x120 * 0x4) = 0xC01F2480.和上面windbg显示的数据是一样的。

  在windbg里看下该处的内容为

  kd> dd C01F2480

  c01f2480 03791025 00000000 00000000 04157025

  该页表处的内容为0x03791025 ,其中,如果了解页表结构的话,其高20位为该页表指向的物理内存,为0x3791,再加上线性地址的末12位偏移,所以该线性地址对应的实际物理地址为:0X37910000.

  到这里就算完了。

  补充:

  内核定义的页表的数据结构如下:

  typedef struct _MMPTE {

  union {

  ULONG Long;

  HARDWARE_PTE Flush;

  MMPTE_HARDWARE Hard;

  MMPTE_PROTOTYPE Proto;

  MMPTE_SOFTWARE Soft;

  MMPTE_TRANSITION Trans;

  MMPTE_SUBSECTION Subsect;

  MMPTE_LIST List;

  } u;

  } MMPTE;

  typedef MMPTE *PMMPTE;

  typedef struct _MMPTE_HARDWARE {

  ULONG Valid : 1;

  #if defined(NT_UP)

  ULONG Write : 1; // UP version

  #else

  ULONG Writable : 1; // changed for MP version

  #endif

  ULONG Owner : 1;

  ULONG WriteThrough : 1;

  ULONG CacheDisable : 1;

  ULONG Accessed : 1;

  ULONG Dirty : 1;

  ULONG LargePage : 1;

  ULONG Global : 1;

  ULONG CopyOnWrite : 1; // software field

  ULONG Prototype : 1; // software field

  #if defined(NT_UP)

  ULONG reserved : 1; // software field

  #else

  ULONG Write : 1; // software field - MP change

  #endif

  ULONG PageFrameNumber : 20;

  } MMPTE_HARDWARE, *PMMPTE_HARDWARE;

  可以看出上面那个取物理地址的宏直接取的就是PageFrameNumber ,也就是该地址内容的高20位。

  而在!pte命令在还显示了一些大些字母,如D,A,W,U之类的,也在该结构中有所显示。

  看过“ 线性地址转物理地址 ”的人还看了:

1.物理地址与虚拟地址怎么转换

2.物理地址与虚拟地址映射

3.物理地址和虚拟地址

4.物理地址和逻辑地址的区别

5.物理地址可以改吗

    603816