Chinaunix首页 | 论坛 | 博客
  • 博客访问: 677491
  • 博文数量: 516
  • 博客积分: 4119
  • 博客等级: 上校
  • 技术积分: 4288
  • 用 户 组: 普通用户
  • 注册时间: 2012-10-30 17:29
文章分类

全部博文(516)

文章存档

2014年(4)

2013年(160)

2012年(352)

分类:

2012-11-01 12:36:13

原文地址:linux的内存管理 作者:kine1314

简单的看法:

线性空间跟物理空间在同一时间必须是一一映射的关系,之所以物理空间可以比较少, 是因为允许空映射的存在, 以及利用了时分复用, 这样不同的时候, 可以有不同的线性地址映射到同一物理地址

但如果物理空间大呢? 也一样, 同一时间, 只能是一一映射, 这个时候线性空间就没办法利用全部的物理空间。但也可以利用时分复用, 即下一个时候, 线性空间可以指向之前无法用到的物理空间

下面假设物理内存比较大
对linux系统来说, 一个进程可以有4G的线性空间, 其中3G是用户空间的, 1G是内核空间。 3G 的用户空间每个进程都不同, 所以不能用来映射物理空间, 因为内核不知道到底该怎么映射。 1G内核空间是固定的, 可以用来映射物理空间, 当它最多映射1G的物理内存, 所以linux的做法是前896M用来映射物理内存的前896M, 这896M一般不变, 剩下的128M用来作时分复用, 即在不同时间映射到不同的物理内存, 这样就可以使用896M以上的内存了

接着的是比较杂乱的知识:
32位系统的虚拟内存空间是4G, 其中3G用于用户空间, 1G用于内核空间

物理内存的大小不定,但内核如果要对其中的一段物理内存进行操作, 必须先将它映射到自身的1G虚拟内存中, 所有内存同一时间可以处理的物理内存少于1G(1G虚拟内存有一部分要用作别的用途), 当然不同时间可以操作不同的物理内存。
对内核空间的映射, 采用简单的线性映射,即加一个偏移量,物理的0地址+PAGESET就映射到了虚拟内存中内核空间的起始位置

linux的内存模型采用了节点(node)跟区域的概念(zone), 一个节点包含一下3个区域(如果一个物理内存只被当作一个节点,那么它就被划分为下面3个部分):
ZONE_DMA(0-16m): 给设备IO传输数据用的, 不需要地址转换?
ZONE_NORMAL(16m-896m):  这部分被映射到内核的1G虚拟内存的低部分, 是内核可以直接操作的
ZONE_HIGHMEM(896m-):

在x86上,内核1G虚拟空间中,有128m内存是用来保存page table等内核数据结构(最后128M),这部分不被映射到物理内存,虚拟空间就只剩下896m用来映射,所以ZONE_NORMAL只到896m。
内核维护着一组自己使用的页表,驻留在所谓主内核页全局目录(master kernel page global directory)中。系统初始化后,这组页表永远不会被任何进程或者任何内核线程直接使用;更确切第说,主内核页全局目录的最高目录项部分作为参考模 型,为系统中每个普通进程对应的页全局目录项提供模板。 
 
        0x2ff |--------------------|
              |                    |
              |                    |
              |                    |
              |--------------------|
              |                    |Provisional kernel Page Tables
              |--------------------|_end
              |                    |
              |                    |Uninitialized kernel data
              |                    |
              |--------------------|_edata
              |                    |
              |                    |
              |                    |Initialized kernel data
              |                    |
              |--------------------|_etext
              |                    |
              |                    |
              |                    |Kernel code
              |                    |
              |                    |
        0x100 |                    |
              |--------------------|_text (0x00100000)
              |                    |
              |                    |Unavailable page fames
         0x9f |                    |
              |--------------------|
              |                    |
              |                    |
              |                    |Available page fames
              |                    |
              |                    |
            1 |                    |
              |--------------------|
            0 |                    |Unavailable page fames
              |--------------------|
   Page frame #

            Linux 2.6的前768个页框(3MB)
            物理内存前3MB布局示意图



内核在初始化的第一阶段,可以通过与物理地址相同的线性地址或者通过从0xc0000000开始的8MB线性地址对RAM的前8MB进行寻址:
  1. 0项和0x300(768)项的地址字段置为pg0的物理地址,而1项和0x301(769)项的地址字段置为pg1的物理地址
  2. 把这四项的Present、Read/Write、User/Supervisor标志置位
  3. 把这四项的Accessed、Dirty、 PCD、 PWD、 Page Size标志清0


页全局目录放在swapper_pg_dir变量中,而映射前8MB RAM的两个目录项是pg0和pg1:
extern pgd_t swapper_pg_dir[1024];
typedef struct { unsigned long long pgd; } pgd_t;
              |--------------------|
        1023  |                    |
              |                    |(对应128MB虚拟空间)
              |                    |
              |--------------------|
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |--------------------|
        769   |                    |pg1 --> 5~8MB
              |--------------------|
        768   |                    |pg0 --> 1~4MB
              |--------------------|
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |                    |
              |--------------------|
           1  |                    |pg1 --> 5~8MB
              |--------------------|
           0  |                    |pg0 --> 1~4MB
 %%cr3------->|--------------------|
        Provisional kernel Page Tables(swapper_pg_dir)

线性地址:0xc000 0000的高20位为1100000000(2)=768



当RAM小于896MB时的最终内核页表:
  • 内核页表所提供的最终映射必须把从0xc000 0000开始的内核线性地址转化为从0开始的物理地址
  • 宏_pa用于把从PAGE_OFFSET开始的线性地址转换成相应的物理地址,而宏_va做相反的转化
  • 主内核页全局目录(The master kernel Page Global Directory)仍然保存在swapper_pg_dir变量中。它由paging_init()函数初始化
  • 线性地址的最高128MB留给几种映射取用,因此剩余的映射RAM的内核地址空间为1GB - 128MB = 896MB
进程间前3G的线性空间是不同的, 而最后1G内核空间都是相同的, 即有着同样的页表目录项跟页表, 这些页表目录称为master kernel page global directory, 保存在swapper_pg_dir(数组1024项)中

内核的启动分两个阶段:
第一阶段建立了页表目录项中的2个项, 既有2个页表, 每个页表一般有1024个项, 这样就总共可以映射8M的内存空间。启动分页?
这个8M的就可以被使用了(这个8M的映射是通过线性映射来的, 内核来静态的初始化它)

第二阶段初始化所有1G线性空间的对应的256个页表全部初始化, 对物理内存进行映射, 从页表的第768项开始, 因为之前的已经映射好了
pagetable_init() --> kernel_physical_mapping_init()

static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{
    unsigned long pfn;
    pgd_t *pgd;
    pmd_t *pmd;
    pte_t *pte;
    int pgd_idx, pmd_idx, pte_ofs;

定位主内核页全局目录(master kernel page global directory)的起始项pgd=768:
|--------------------------------------|
|   pgd_idx = pgd_index(PAGE_OFFSET);  |
|   pgd = pgd_base + pgd_idx;          |
|--------------------------------------|


物理地址从0x0000 0000开始,起始页框号(page frame number)为pfn;
|-----------------|
|   pfn = 0;      |
|-----------------|

    for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {

直接返回pgd(pmd和pgd指向同一个页目录项)
|-------------------------------------|
|       pmd = one_md_table_init(pgd); |
|-------------------------------------|
 
        if (pfn >= max_low_pfn)
            continue;
        for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {

计算第pfn个页框对应的内核空间的线性地址:
|-----------------------------------------------------------------|
|           unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET; |
|-----------------------------------------------------------------|
            if (cpu_has_pse) {
                unsigned int address2 = (pfn + PTRS_PER_PTE - 1) 
                                * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;
                if (is_kernel_text(address) || is_kernel_text(address2))
                    set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));
                else
                    set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));
                pfn += PTRS_PER_PTE;

填写一个页目录项pmd(pgd),并填写该目录项所对应的页表的所有项
为页目录项pmd分配页表pte,将该页表pte的物理地址写入pmd中,并初始化页表pte的每个页表项;
|--------------------------------------------------------------------------|
|           } else {                                                       |
|               pte = one_page_table_init(pmd);                            |
|               for (pte_ofs = 0;                                          |
|                    pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn;          |
|                    pte++, pfn++, pte_ofs++) {                            | 
|                       if (is_kernel_text(address))                       |
|                           set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));  |
|                       else                                               |
|                           set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));       |
|               }                                                          |
|           }                                                              |
|--------------------------------------------------------------------------|
        }
    }
}
LinuxKernelMeory.gif

LinuxKernelMeory2.gif
阅读(207) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~