曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 9871|回复: 0
打印 上一主题 下一主题

Linux内核地址空间的布局

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2009-8-12 00:51:40 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
Linux内核地址空间的布局

1) Linux将整个4G线性地址空间分为用户空间和内核空间两部分,
而内核地址空间又被划分为"物理内存区", "虚拟内存分配区", "高端页面映射区",
"专用页面映射区", "系统保留映射区"几个区域.

2) 在标准配置下, 物理区最大长度为896M, 系统的物理内存被顺序映射在物理区中,
在支持扩展页长(PSE)和全局页面(PGE)的机器上, 物理区使用4M页面并作为全局页面来处理.
当系统物理内存大于896M时, 超过物理区的那部分内存称为高端内存,
低端内存和高端内存用highmem_start_page变量来定界,
内核在存取高端内存时必须将它们映射到"高端页面映射区".

3) Linux保留内核空间最顶部128K区域作为保留区,
紧接保留区以下的一段区域为专用页面映射区,
它的总尺寸和每一页的用途由fixed_address枚举结构在编绎时预定义,
用__fix_to_virt(index)可获取专用区内预定义页面的逻辑地址.
在专用页面区内为每个CPU预定义了一张高端内存映射页, 用于在中断处理中高端页面的映射操作.


4) 距离内核空间顶部32M, 长度为4M的一段区域为高端内存映射区,
它正好占用1个页帧表所表示的物理内存总量, 它可以缓冲1024个高端页面的映射.
在物理区和高端映射区之间为虚存内存分配区, 用于vmalloc()函数,
它的前部与物理区有8M隔离带, 后部与高端映射区有8K的隔离带.

5) 当系统物理内存超过4G时,
必须使用CPU的扩展分页(PAE)模式所提供的64位页目录项才能存取到4G以上的物理内存.
在PAE模式下, 线性地址到物理地址的转换使用3级页表, 第1级页目录由线性地址的最高2位索引,
每一目录项对应1G的寻址空间, 第2级页目录项以9位索引, 每一目录项对应2M的寻址空间,
第3级页目录项以9位索引, 每一目录项对应4K的页帧.
除了页目录项所描述的物理地址扩展为36位外, 64位和32位页目录项结构没有什么区别.
在PAE模式下, 包含PSE位的中级页目录项所对应的页面从4M减少为2M.


[pre]
/* Just any arbitrary offset to the start of the vmalloc VM area: the
* current 8MB value just means that there will be a 8MB "hole" after the
* physical memory until the kernel virtual memory starts.  That means that
* any out-of-bounds memory accesses will hopefully be caught.
* The vmalloc() routines leaves a hole of 4kB between each vmalloced
* area for the same reason. ;)
*/
#define VMALLOC_OFFSET    (8*1024*1024) 虚拟区与物理区的隔离宽度
#define VMALLOC_START    (((unsigned long) high_memory + 2*VMALLOC_OFFSET-1) & \
                        ~(VMALLOC_OFFSET-1)) 虚拟区的开始
#define VMALLOC_VMADDR(x) ((unsigned long)(x))
#if CONFIG_HIGHMEM
# define VMALLOC_END    (PKMAP_BASE-2*PAGE_SIZE)
#else
# define VMALLOC_END    (FIXADDR_START-2*PAGE_SIZE)
#endif

#define PKMAP_BASE (0xfe000000UL) 高端映射区的开始

#define FIXADDR_TOP    (0xffffe000UL) 专用映射区的顶部
#define FIXADDR_SIZE    (__end_of_fixed_addresses << PAGE_SHIFT) 专用映射区尺寸
#define FIXADDR_START    (FIXADDR_TOP - FIXADDR_SIZE) 专用映射区开始

#define __fix_to_virt(x)    (FIXADDR_TOP - ((x) << PAGE_SHIFT)) 取专用映射区页面

/*
* on UP currently we will have no trace of the fixmap mechanizm,
* no page table allocations, etc. This might change in the
* future, say framebuffers for the console driver(s) could be
* fix-mapped?
*/
enum fixed_addresses { 专用区页面的功能定义
#ifdef CONFIG_X86_LOCAL_APIC
    FIX_APIC_BASE,    /* local (CPU) APIC) -- required for SMP or not */
#endif
#ifdef CONFIG_X86_IO_APIC
    FIX_IO_APIC_BASE_0,
    FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1,
#endif
#ifdef CONFIG_X86_VISWS_APIC
    FIX_CO_CPU,    /* Cobalt timer */
    FIX_CO_APIC,    /* Cobalt APIC Redirection Table */
    FIX_LI_PCIA,    /* Lithium PCI Bridge A */
    FIX_LI_PCIB,    /* Lithium PCI Bridge B */
#endif
#ifdef CONFIG_HIGHMEM 用于中断处理内映射高端页面
    FIX_KMAP_BEGIN,    /* reserved pte's for temporary kernel mappings */
    FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
#endif
    __end_of_fixed_addresses
};
enum km_type {
    KM_BOUNCE_READ,
    KM_BOUNCE_WRITE,
    KM_TYPE_NR
};

; arch/i386/mm/init.c:

static void __init pagetable_init (void) 建立内核空间的页表
{
    unsigned long vaddr, end;
    pgd_t *pgd, *pgd_base;
    int i, j, k;
    pmd_t *pmd;
    pte_t *pte, *pte_base;

    /*
     * This can be zero as well - no problem, in that case we exit
     * the loops anyway due to the PTRS_PER_* conditions.
     */
    end = (unsigned long)__va(max_low_pfn*PAGE_SIZE); 取内核物理内存区域终止边界物理地址

    pgd_base = swapper_pg_dir; 取内核基准页目录表地址
#if CONFIG_X86_PAE 在PAE模式下, 首级页表只包含4个64位目录项
    for (i = 0; i < PTRS_PER_PGD; i++) {
        pgd = pgd_base + i;
        __pgd_clear(pgd);
    }
#endif
    i = __pgd_offset(PAGE_OFFSET); 取内核空间起始边界在首级页表内的地址偏移
    pgd = pgd_base + i;

    for (; i < PTRS_PER_PGD; pgd++, i++) { 扫描内核空间的页目录项
        vaddr = i*PGDIR_SIZE; 取该目录项所对应物理区域的起始地址
        if (end && (vaddr >= end)) 如果该映射区域起始边界大于或等于内核物理内存的终止边界
            break; 退出扫描
#if CONFIG_X86_PAE
        pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE); 分配中级页表
        set_pgd(pgd, __pgd(__pa(pmd) + 0x1)); 将中级页表登记到首级页表
#else
        pmd = (pmd_t *)pgd;
#endif
        if (pmd != pmd_offset(pgd, 0))
            BUG();
        for (j = 0; j < PTRS_PER_PMD; pmd++, j++) {
扫描中级页目录项,对于二级页表来说,循环一次
            vaddr = i*PGDIR_SIZE + j*PMD_SIZE; 取该目录项映射区域的起始地址
            if (end && (vaddr >= end))
                break;
            if (cpu_has_pse) { 如果CPU具有扩展页长功能
                unsigned long __pe;

                set_in_cr4(X86_CR4_PSE);
                boot_cpu_data.wp_works_ok = 1;
                __pe = _KERNPG_TABLE + _PAGE_PSE + _PAGE_USER + __pa(vaddr); 使用扩展页长目录项
                /* Make it "global" too if supported */
                if (cpu_has_pge) {
                    set_in_cr4(X86_CR4_PGE);
                    __pe += _PAGE_GLOBAL; 设置全局页目录项标志
                }
                set_pmd(pmd, __pmd(__pe)); 设置中级页表
                continue; 继续下一中级页目录项
            }
            ; 不使用扩展页长
            pte_base = pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE); 分配页帧表

            for (k = 0; k < PTRS_PER_PTE; pte++, k++) { 扫描页帧目录项
                vaddr = i*PGDIR_SIZE + j*PMD_SIZE + k*PAGE_SIZE; 取页帧目录项所映射页面的起始地址
                if (end && (vaddr >= end))
                    break;
                *pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL); 设置页帧表
            }
            set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte_base))); 设置中级页表
            if (pte_base != pte_offset(pmd, 0))
                BUG();

        }
    }

    /*
     * Fixed mappings, only the page table structure has to be
     * created - mappings will be set by set_fixmap():
     */
    vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
取专用区起始地址所在的中级页目录边界地址
    fixrange_init(vaddr, 0, pgd_base); 建立专用区页表

#if CONFIG_HIGHMEM
    /*
     * Permanent kmaps:
     */
    vaddr = PKMAP_BASE;
    fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base); 建立高端映射区页表

    pgd = swapper_pg_dir + __pgd_offset(vaddr);
    pmd = pmd_offset(pgd, vaddr);
    pte = pte_offset(pmd, vaddr);
    pkmap_page_table = pte; 设置高端映射区所在的页帧目录
#endif

#if CONFIG_X86_PAE
    /*
     * Add low memory identity-mappings - SMP needs it when
     * starting up on an AP from real-mode. In the non-PAE
     * case we already have these mappings through head.S.
     * All user-space mappings are explicitly cleared after
     * SMP startup.
     */
    pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
#endif
}
static void __init fixrange_init (unsigned long start, unsigned long end, pgd_t
*pgd_base)
{
    pgd_t *pgd;
    pmd_t *pmd;
    pte_t *pte;
    int i, j;
    unsigned long vaddr;

    vaddr = start;
    i = __pgd_offset(vaddr);
    j = __pmd_offset(vaddr);
    pgd = pgd_base + i;

    for ( ; (i < PTRS_PER_PGD) && (vaddr != end); pgd++, i++) {
#if CONFIG_X86_PAE
        if (pgd_none(*pgd)) {
            pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
            set_pgd(pgd, __pgd(__pa(pmd) + 0x1));
            if (pmd != pmd_offset(pgd, 0))
                printk("PAE BUG #02!\n");
        }
        pmd = pmd_offset(pgd, vaddr);
#else
        pmd = (pmd_t *)pgd;
#endif
        for (; (j < PTRS_PER_PMD) && (vaddr != end); pmd++, j++) {
            if (pmd_none(*pmd)) {
                pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
                set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte)));
                if (pte != pte_offset(pmd, 0))
                    BUG();
            }
            vaddr += PMD_SIZE;
        }
        j = 0;
    }
}

[/pre]
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-4-29 18:35 , Processed in 0.083375 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表