内核技术中文网»首页 论坛 圈点 查看内容

0 评论

0 收藏

分享

物理内存映射

问题1:在系统启动是,arm linux内核如何知道系统中有多大的内存空间?

物理内存大小定义在设备树的memory节点中,系统启动时会通过dtb解析memory节点,从而获得内存大小。

内存大小定义

在arm vexpress平台中,内存定义在vexpress-v2p-ca9.dts中。起始地址为0x60000000,大小为0x40000000(1G)。设备树语法参考链接Device Tree 详解 - 魅族内核团队

start_kernel->setup_arch->setup_machine_fdt->early_init_dt_scan_nodes->of_scan_flat_dt->early_init_dt_scan_memory

在函数early_init_dt_scan_memory会解析memory节点

/**
 * early_init_dt_scan_memory - Look for an parse memory nodes
 */
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
                     int depth, void *data)
{
    ..........................
    while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
        u64 base, size;
        ..............................................
        printk(KERN_EMERG "base %llx, size %llx\n", base, size);
        early_init_dt_add_memory_arch(base, size);
    }
    return 0;
}

输出的刚好是dts里面定义的起始地址和大小

sudo qemu-system-arm -M vexpress-a9 -m 1024M。如果-m 512M

则会输出base 60000000, size 20000000(512M)

感觉是二者取较小值

物理内存映射

page_init来完成系统分页机制的初始化工作,建立页表,从而内核可以完成虚拟地址到物理地址的映射关系

void __init paging_init(const struct machine_desc *mdesc)
{
    void *zero_page;
    printk(KERN_EMERG "\r\npaging_init begin\n");
    /* 给静态全局变量mem_types赋值 */
    build_mem_type_table();
    prepare_page_table();//将页表项清零
    map_lowmem();//初始化页表,真正创建页表,重新建立从物理地址起始点到high_mem的起始点的一一映射
    /* 建立DMA映射表 */
    dma_contiguous_remap();
    /* 为设备IO空间和中断向量表创建页表,并刷新TLB和缓存 */
    devicemaps_init(mdesc);
    /* 进行永久内存映射的初始化,存储在pkmap_page_table中 */
    kmap_init();
    /* TCM初始化,TCM是一个固定大小的RAM,紧密地耦合至处理器内核,提供与cache相当的性能 */
    tcm_init();

    top_pmd = pmd_off_k(0xffff0000);

    /* allocate the zero page. */
    zero_page = early_alloc(PAGE_SIZE);
    /* bootmem_init初始化内存管理 */
    bootmem_init();

    /* 分配一个0页,该页用于写时复制机制。 */
    empty_zero_page = virt_to_page(zero_page);
    __flush_dcache_page(NULL, empty_zero_page);
}

prepare_page_table:把页表项清零

static inline void prepare_page_table(void)
{
    unsigned long addr;
    phys_addr_t end;

    /*
     * Clear out all the mappings below the kernel image.
     */
    for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
        pmd_clear(pmd_off_k(addr));

#ifdef CONFIG_XIP_KERNEL
    /* The XIP kernel is mapped in the module area -- skip over it */
    addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK;
#endif
    for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
        pmd_clear(pmd_off_k(addr));

    /*
     * Find the end of the first block of lowmem.
     */
    end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
    if (end >= arm_lowmem_limit)
        end = arm_lowmem_limit;

    /*
     * Clear out all the kernel space mappings, except for the first
     * memory bank, up to the vmalloc region.
     */
    for (addr = __phys_to_virt(end);
         addr < VMALLOC_START; addr += PMD_SIZE)
        pmd_clear(pmd_off_k(addr));
    printk(KERN_EMERG "\r\n MODULES_VADDR %lx, PAGE_OFFSET %lx, VMALLOC_START %lx\n", \
            (unsigned long)MODULES_VADDR, (unsigned long)PAGE_OFFSET, (unsigned long)VMALLOC_START);
}

对三段地址调用pmd_clear()清除一级页表的内容

0x0--MODULES_VADDR ;

MODULES_VADDR--PAGE_OFFSET;

arm_lowmem_limit--VMALLOC_START

arm_lowmem_limit a0000000

MODULES_VADDR 7f000000, PAGE_OFFSET 80000000, VMALLOC_START c0800000 暂时不知道这几个地址是什么意思,难道这个地址范围内都是页表??

PAGE_OFFSET:对于内核空间,给定一个虚拟地址x,其物理地址为x-PAGE_OFFSET。1、感觉这个也只是针对线性映射区吧。2、那这个不说明内核虚拟地址起始位置为PAGE_OFFSET=0x80000000(2G)??。那4G地址空间就是2:2进行划分的

map_lowmem:初始化页表

static void __init map_lowmem(void)
{
    struct memblock_region *reg;
    unsigned long kernel_x_start = round_down(__pa(_stext), SECTION_SIZE);
    unsigned long kernel_x_end = round_up(__pa(__init_end), SECTION_SIZE);
    printk(KERN_EMERG "\r\n arm_lowmem_limit %lx", arm_lowmem_limit);
    printk(KERN_EMERG "\r\n kernel_x_start %lx,kernel_x_end %lx", kernel_x_start, kernel_x_end);
    /* Map all the lowmem memory banks. */
    for_each_memblock(memory, reg) {
        phys_addr_t start = reg->base;
        phys_addr_t end = start + reg->size;
        struct map_desc map;

        if (end > arm_lowmem_limit)
            end = arm_lowmem_limit;
        if (start >= end)
            break;
        printk(KERN_EMERG "\r\n start %lx, end %lx, size %lx\n", start, end, reg->size);
        if (end < kernel_x_start || start >= kernel_x_end) {
            /* 将物理地址转为物理page number, 这里是4k为一页 */
            map.pfn = __phys_to_pfn(start);
            map.virtual = __phys_to_virt(start);
            map.length = end - start;
            map.type = MT_MEMORY_RWX;

            /* create_mapping进行线性映射 */
            create_mapping(&map);
        } else {
            /* This better cover the entire kernel */
            if (start < kernel_x_start) {
                map.pfn = __phys_to_pfn(start);
                map.virtual = __phys_to_virt(start);
                map.length = kernel_x_start - start;
                map.type = MT_MEMORY_RW;

                create_mapping(&map);
            }
            /* 映射kernel image区域 */
            map.pfn = __phys_to_pfn(kernel_x_start);
            map.virtual = __phys_to_virt(kernel_x_start);
            map.length = kernel_x_end - kernel_x_start;
            map.type = MT_MEMORY_RWX;

            create_mapping(&map);
            /* 书上说映射低端内存,为什么是低端内存呢 */
            if (kernel_x_end < end) {
                map.pfn = __phys_to_pfn(kernel_x_end);
                map.virtual = __phys_to_virt(kernel_x_end);
                map.length = end - kernel_x_end;
                map.type = MT_MEMORY_RW;

                create_mapping(&map);
            }
        }
    }
}

只循环了一次,memblock只有一个。起始地址0x60000000, 长度0x40000000

#define for_each_memblock(memblock_type, region)                    \
    for (region = memblock.memblock_type.regions;               \
         region < (memblock.memblock_type.regions + memblock.memblock_type.cnt);    \
         region++)

可以看到这里的 map_lowmem函数里for_each_memblock(memory, reg)就是在使用之前memblock子系统里面的动态内存部分。在关于memblock子系统部分,关于memory_type为memory,即动态内存的部分,也只加入了一个区域,即dts里面定义的memory节点。因此这里也只循环了一次

end = start+size = 0xa0000000(这里的地址究竟是啥地址哦,虚拟地址or物理地址??).刚好是dts里面定义的memory节点。__phys_to_pfn感觉这里的地址都是物理地址。

但是物理内存只有1G,物理地址范围不是0-1G嘛?为什么会有0x60000000这种物理地址呢

我看网上说,memory节点里面的reg = <0x60000000 0x40000000>;表示物理内存起始地址,以及大小。因此物理地址范围[0x60000000,0xa0000000]。看来物理地址不一定是从0x0开始

低端内存:32位CPU能够访问4G地址空间。linux将4G地址空间为了用户和内核地址空间,一般是3:1(这个比例可以调整2:2)。内核只能访问1G的地址空间(3G-4G)。

如果物理内存大于1G.那么内核需要如何管理地址内存呢?

因此linux把内核1G的虚拟地址空间分为了两个部分。线性映射区,和非线性映射区。线性映射的物理内存,也成为了低端内存,剩下的内存被称为高端内存。线性区的内存是提前映射好了。高端内存是在使用时动态映射,使用非线性映射区去管理大于(1G,896或者说,线性映射区管理不了的高端内存)。因此,内存的前896M(物理内存)(低端内存)被一一映射,即线性映射,到了内核地址空间3G--3G+896M(虚拟地址空间)的部分,剩下的128M的虚拟地址空间,用kmap动态映射,管理超过1G的高端内存

static void __init map_lowmem(void)
{
    ....................
        if (end < kernel_x_start || start >= kernel_x_end) {
           /*不走这里*/.................
        } else {
            /* This better cover the entire kernel */
            if (start < kernel_x_start) {
                /* 也不走这里 */................................
            }
            /* 映射kernel image区域 */
            map.pfn = __phys_to_pfn(kernel_x_start);
            map.virtual = __phys_to_virt(kernel_x_start);
            map.length = kernel_x_end - kernel_x_start;
            map.type = MT_MEMORY_RWX;

            create_mapping(&map);
            /*
             书上说映射低端内存,为什么是低端内存呢?
             因为这部分内存是进行线性映射的,因此属于低端内存(zone_dma & zone_normal )
             */
            if (kernel_x_end < end) {
                map.pfn = __phys_to_pfn(kernel_x_end);
                map.virtual = __phys_to_virt(kernel_x_end);
                map.length = end - kernel_x_end;
                map.type = MT_MEMORY_RW;

                create_mapping(&map);
            }
        }
    }
}

map_lowmem:

如上面代码所示,该函数会对两个区域进行映射

区间1:kernel_x_start, kernel_x_end

物理地址:kernel_x_start(0x60000000)-- kernel_x_end (0x60600000) 大小共6M

虚拟地址:0x80000000-0x80600000

区间2:[kernel_x_end, end](物理地址)

物理地址:kernel_x_end (0x60600000)--end(0xa0000000)。共(1018M)

这里感觉和书上写的不一样。那这1G的物理内存全部都拿去做线性映射了,没有所谓的高端内存

下图里面的lowmem刚好也是1024M

书上的arm_lowmem_limit=0x8f800000,意味着低端内存大小为0x8f800000-0x60000000=760M

那高端内存还有1024-760 = 264M.

arm_lowmem_limit(是个物理地址):那这个值感觉就是低端内存的上界。高于arm_lowmem_limit都是属于高端内存。

原文作者:这个我好像学过

原文链接:https://blog.csdn.net/qq_42693685/article/details/126210560(版权归原文作者所有,侵权联系删除

回复

举报 使用道具

全部回复
暂无回帖,快来参与回复吧
主题 1545
回复 0
粉丝 2
扫码获取每晚技术直播链接
快速回复 返回顶部 返回列表