Linux内核高-低端内存设置代码跟踪(ARM构架)

发布者:快乐的舞蹈最新更新时间:2016-04-20 来源: eefocus关键字:Linux内核  内存设置  代码跟踪  ARM构架 手机看文章 扫描二维码
随时随地手机看文章
对于ARM中内核如何在启动的时候设置高低端内存的分界线(也是逻辑地址与虚拟地址分界线(虚拟地址)减去那个固定的偏移),这里我稍微引导下(内核分析使用Linux-3.0):
   首先定位设置内核虚拟地址起始位置(也就是内核逻辑地址末端+1的地址)的文件:init.c (arch\arm\mm),在这个文件中的void __init bootmem_init(void)函数如下
  1. void __init bootmem_init(void)
  2. {
  3.     unsigned long min, max_low, max_high;
  4.  
  5.     max_low max_high 0;
  6.  
  7.     find_limits(&min, &max_low, &max_high);
  8.  
  9.     arm_bootmem_init(min, max_low);
  10.  
  11.     /*
  12.      Sparsemem tries to allocate bootmem in memory_present(),
  13.      so must be done after the fixed reservations
  14.      */
  15.     arm_memory_present();
  16.  
  17.     /*
  18.      sparse_init() needs the bootmem allocator up and running.
  19.      */
  20.     sparse_init();
  21.  
  22.     /*
  23.      Now free the memory free_area_init_node needs
  24.      the sparse mem_map arrays initialized by sparse_init()
  25.      for memmap_init_zone(), otherwise all PFNs are invalid.
  26.      */
  27.     arm_bootmem_free(min, max_low, max_high);
  28.  
  29.     high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) - 1) + 1;
  30.  
  31.     /*
  32.      This doesn't seem to be used by the Linux memory manager any
  33.      more, but is used by ll_rw_block. If we can get rid of it, we
  34.      also get rid of some of the stuff above as well.
  35.      *
  36.      Note: max_low_pfn and max_pfn reflect the number of _pages_ in
  37.      the system, not the maximum PFN.
  38.      */
  39.     max_low_pfn max_low PHYS_PFN_OFFSET;
  40.     max_pfn max_high PHYS_PFN_OFFSET;
  41. }
   这个high_memory __va(((phys_addr_t)max_low << PAGE_SHIFT) 1) 1;语句就是关键。从这里可以知道max_low就是高端内存的起始地址(物理地址)。那么这个max_low是如何得到的?其实看上面的代码可以推测出,他其实是在find_limits(&min, &max_low, &max_high);中(在同一个文件中)被设置的:
  1. static void __init find_limits(unsigned long *min, unsigned long *max_low,
  2.     unsigned long *max_high)
  3. {
  4.     struct meminfo *mi &meminfo;
  5.     int i;
  6.  
  7.     *min -1UL;
  8.     *max_low *max_high 0;
  9.  
  10.     for_each_bank (i, mi) {
  11.         struct membank *bank &mi->bank[i];
  12.         unsigned long start, end;
  13.  
  14.         start bank_pfn_start(bank);
  15.         end bank_pfn_end(bank);
  16.  
  17.         if (*min start)
  18.             *min start;
  19.         if (*max_high end)
  20.             *max_high end;
  21.         if (bank->highmem)
  22.             continue;
  23.         if (*max_low < end)
  24.             *max_low = end;
  25.     }
  26. }
    这个函数的意思很明显:通过扫描struct meminfo *mi &meminfo;(结构体meminfo的数组)中的所有信息,设置三个指针所指的变量:
  1. min :内存物理地址起始
  2. max_low :低端内存区物理地址末端
  3. max_high :高端内存区物理地址末端
    从上面可以看出,max_low和max_high所保存的地址不同就是由于bank->highmem造成的,它是内存bank被设为高端内存的依据:
  1. “如果这个内存bank是高端内存(bank->highmem != 0),跳过max_low = end;语句,max_low和max_high将不同(结果实际上是max_high > max_low);
  2. 否则假设没有一个内存bank是高端内存(所有bank->highmem == 0)max_low和max_high必然一致(高端内存大小为0)”
   当然要实现这个函数的功能,必须保证meminfo所指数组中的所有bank是按照地址数据从小到大排序好的哦~~。但是这个大家不用担心,后面会看到的:)
   
   经过上面的跟踪,焦点集中到了全局变量(同一个文件中):
  1. struct meminfo meminfo;
   这个结构体的定义(setup.h (arch\arm\include\asm)):
  1. /*
  2.  * Memory map description
  3.  */
  4. #define NR_BANKS 8  
  5.  
  6. struct membank {
  7.     phys_addr_t start;
  8.     unsigned long size;
  9.     unsigned int highmem;  
  10. };
  11.  
  12. struct meminfo {
  13.     int nr_banks;
  14.     struct membank bank[NR_BANKS];  
  15. };
  16.  
  17. extern struct meminfo meminfo;
  18.  
  19. #define for_each_bank(iter,mi)                \
  20.     for (iter = 0; iter < (mi)->nr_banks; iter++)
  #define bank_pfn_start(bank) __phys_to_pfn((bank)->start)
  #define bank_pfn_end(bank) __phys_to_pfn((bank)->start + (bank)->size)
  #define bank_pfn_size(bank) ((bank)->size >> PAGE_SHIFT)
  #define bank_phys_start(bank) (bank)->start
  #define bank_phys_end(bank) ((bank)->start + (bank)->size)
  #define bank_phys_size(bank) (bank)->size
  只要找到初始化这个全局变量并完成排序的地方,就可以知道高端内存是如何配置的了!!OK,明确目标,go on~~~
 
   通过查找代码,我们可以在setup.c (arch\arm\kernel)这个文件中找到相关的代码。在系统启动早期会运行的函数(具体的顺序你可以自行分析下ARM内核的启动流程,以后我也会写下)中有这样一个函数:
  1. void __init setup_arch(char **cmdline_p)
  2. {
  3.     struct machine_desc *mdesc;
  4.  
  5.     unwind_init();
  6.  
  7.     setup_processor();
  8.     mdesc = setup_machine_fdt(__atags_pointer);
  9.     if (!mdesc)
  10.         mdesc = setup_machine_tags(machine_arch_type);
  11.     machine_desc = mdesc;
  12.     machine_name = mdesc->name;
  13.  
  14.     if (mdesc->soft_reboot)
  15.         reboot_setup("s");
  16.  
  17.     init_mm.start_code = (unsigned long) _text;
  18.     init_mm.end_code = (unsigned long) _etext;
  19.     init_mm.end_data = (unsigned long) _edata;
  20.     init_mm.brk     = (unsigned long) _end;
  21.  
  22.     
  23.     strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);   
  24.     *cmdline_p = cmd_line;
  25.  
  26.     parse_early_param();          
  27.  
  28.     sanity_check_meminfo();    
  29.     arm_memblock_init(&meminfo, mdesc);  
  30.  
  31.     paging_init(mdesc);
  32.     request_standard_resources(mdesc);
  33.  
  34.     unflatten_device_tree();
  35.  
  36. #ifdef CONFIG_SMP
  37.     if (is_smp())
  38.         smp_init_cpus();
  39. #endif
  40.     reserve_crashkernel();
  41.  
  42.     cpu_init();
  43.     tcm_init();
  44.  
  45. #ifdef CONFIG_MULTI_IRQ_HANDLER
  46.     handle_arch_irq = mdesc->handle_irq;
  47. #endif
  48.  
  49. #ifdef CONFIG_VT
  50. #if defined(CONFIG_VGA_CONSOLE)
  51.     conswitchp = &vga_con;
  52. #elif defined(CONFIG_DUMMY_CONSOLE)
  53.     conswitchp = &dummy_con;
  54. #endif
  55. #endif
  56.     early_trap_init();
  57.  
  58.     if (mdesc->init_early)
  59.         mdesc->init_early();
  60. }
在上面的注释中,我已经表明了重点和解析,下面我细化下:
(1)获取参数部分
     通过parse_early_param();函数可以解析内核启动参数中的许多字符串,但是对于我们这次分析内存的话主要是分析以下两个参数:
     mem=size@start参数,她为初始化struct meminfo meminfo;(我们一直关注的内存信息哦~)提供信息。具体的获取信息的函数(同样位于setup.c (arch\arm\kernel)):
  1. int __init arm_add_memory(phys_addr_t start, unsigned long size)
  2. {
  3.     struct membank *bank = &meminfo.bank[meminfo.nr_banks];
  4.  
  5.     if (meminfo.nr_banks >= NR_BANKS) {
  6.         printk(KERN_CRIT "NR_BANKS too low, "
  7.             "ignoring memory at 0xllx\n", (long long)start);
  8.         return -EINVAL;
  9.     }
  10.  
  11.     /*
  12.      * Ensure that start/size are aligned to a page boundary.
  13.      * Size is appropriately rounded down, start is rounded up.
  14.      */
  15.     size -= start & ~PAGE_MASK;
  16.     bank->start = PAGE_ALIGN(start);
  17.     bank->size = size & PAGE_MASK;
  18.  
  19.     /*
  20.      * Check whether this memory region has non-zero size or
  21.      * invalid node number.
  22.      */
  23.     if (bank->size == 0)
  24.         return -EINVAL;
  25.  
  26.     meminfo.nr_banks++;
  27.     return 0;
  28. }
  29.  
  30. /*
  31.  * Pick out the memory size. We look for mem=size@start,
  32.  * where start and size are "size[KkMm]"
  33.  */
  34. static int __init early_mem(char *p)
  35. {
  36.     static int usermem __initdata = 0;
  37.     unsigned long size;
  38.     phys_addr_t start;
  39.     char *endp;
  40.  
  41.     /*
  42.      * If the user specifies memory size, we
  43.      * blow away any automatically generated
  44.      * size.
  45.      */
  46.     if (usermem == 0) {
  47.         usermem = 1;
  48.         meminfo.nr_banks = 0;
  49.     }
  50.  
  51.     start = PHYS_OFFSET;
  52.     size = memparse(p, &endp);
  53.     if (*endp == '@')
  54.         start = memparse(endp + 1, NULL);
  55.  
  56.     arm_add_memory(start, size);
  57.  
  58.     return 0;
  59. }
  60. early_param("mem", early_mem);
     vmalloc=size参数,她为初始化vmalloc_min(需要保留的内核虚拟地址空间大小,也就是这个内核虚拟地址空间中除去逻辑地址空间和必要的防止越界的保护空洞后最少要预留的地址空间)提供信息。具体的实现函数(位于mmu.c (arch\arm\mm)):
  1. static void * __initdata vmalloc_min = (void *)(VMALLOC_END - SZ_128M); 
 
  1.  
  2. /*
  3.  * vmalloc=size forces the vmalloc area to be exactly 'size'
  4.  * bytes. This can be used to increase (or decrease) the vmalloc
  5.  * area - the default is 128m.
  6.  */
  7. static int __init early_vmalloc(char *arg)
  8. {
  9.     unsigned long vmalloc_reserve = memparse(arg, NULL);
  10.  
  11.     if (vmalloc_reserve < SZ_16M) {
  12.         vmalloc_reserve = SZ_16M;
  13.         printk(KERN_WARNING
  14.             "vmalloc area too small, limiting to %luMB\n",
  15.             vmalloc_reserve >> 20);
  16.     
  17.  
  18.     if (vmalloc_reserve > VMALLOC_END - (PAGE_OFFSET + SZ_32M)) {
  19.         vmalloc_reserve = VMALLOC_END - (PAGE_OFFSET + SZ_32M);
  20.         printk(KERN_WARNING
  21.             "vmalloc area is too big, limiting to %luMB\n",
  22.             vmalloc_reserve >> 20);
  23.     }  
  24.  
  25.     vmalloc_min = (void *)(VMALLOC_END - vmalloc_reserve);   
  26.      
  27.     return 0;
  28. }
  29. early_param("vmalloc", early_vmalloc);
(2)在获得了必要的信息(初始化好struct meminfo meminfo和vmalloc_min)后,内核通过sanity_check_meminfo函数自动去通过vmalloc_min信息来初始化每个meminfo.bank[?]中的highmem成员。此过程中如果有必要,将可能会改变meminfo中的bank数组。处理函数位于mmu.c (arch\arm\mm):
  1. static phys_addr_t lowmem_limit __initdata = 0;
  2.  
  3. void __init sanity_check_meminfo(void)
  4. {
  5.     int i, j, highmem = 0;
  6.  
  7.     for (i = 0, j = 0; i < meminfo.nr_banks; i++) {
  8.         struct membank *bank = &meminfo.bank[j];
  9.         *bank = meminfo.bank[i];
  10.  
  11. #ifdef CONFIG_HIGHMEM
  12.         if (__va(bank->start) >= vmalloc_min ||
  13.          __va(bank->start) < (void *)PAGE_OFFSET)
  14.             highmem = 1;
  15.  
  16.         bank->highmem = highmem;
  17.  
  18.         /*
  19.          * Split those memory banks which are partially overlapping
  20.          * the vmalloc area greatly simplifying things later.
  21.          */
  22.         if (__va(bank->start) < vmalloc_min &&
  23.          bank->size > vmalloc_min - __va(bank->start)) {
  24.             if (meminfo.nr_banks >= NR_BANKS) {
  25.                 printk(KERN_CRIT "NR_BANKS too low, "
  26.                          "ignoring high memory\n");
  27.             } else {
  28.                 memmove(bank + 1, bank,
  29.                     (meminfo.nr_banks - i) * sizeof(*bank));
  30.                 meminfo.nr_banks++;
  31.                 i++;
  32.                 bank[1].size -= vmalloc_min - __va(bank->start);
  33.                 bank[1].start = __pa(vmalloc_min - 1) + 1;
  34.                 bank[1].highmem = highmem = 1;
  35.                 j++;
  36.             }
  37.             bank->size = vmalloc_min - __va(bank->start);
  38.         }
  39. #else
  40.         bank->highmem = highmem;
  41.  
  42.         /*
  43.          * Check whether this memory bank would entirely overlap
  44.          * the vmalloc area.
  45.          */
  46.         if (__va(bank->start) >= vmalloc_min ||
  47.          __va(bank->start) < (void *)PAGE_OFFSET) {
  48.             printk(KERN_NOTICE "Ignoring RAM at %.8llx-%.8llx "
  49.              "(vmalloc region overlap).\n",
  50.              (unsigned long long)bank->start,
  51.              (unsigned long long)bank->start + bank->size - 1);
  52.             continue;
  53.         }
  54.  
  55.         /*
  56.          * Check whether this memory bank would partially overlap
  57.          * the vmalloc area.
  58.          */
  59.         if (__va(bank->start + bank->size) > vmalloc_min ||
  60.          __va(bank->start + bank->size) < __va(bank->start)) {
  61.             unsigned long newsize = vmalloc_min - __va(bank->start);
  62.             printk(KERN_NOTICE "Truncating RAM at %.8llx-%.8llx "
  63.              "to -%.8llx (vmalloc region overlap).\n",
  64.              (unsigned long long)bank->start,
  65.              (unsigned long long)bank->start + bank->size - 1,
  66.              (unsigned long long)bank->start + newsize - 1);
  67.             bank->size = newsize;
  68.         }
  69. #endif
  70.         if (!bank->highmem && bank->start + bank->size > lowmem_limit)
  71.             lowmem_limit = bank->start + bank->size;
  72.  
  73.         j++;
  74.     }
  75. #ifdef CONFIG_HIGHMEM
  76.     if (highmem) {
  77.         const char *reason = NULL;
  78.  
  79.         if (cache_is_vipt_aliasing()) {
  80.             /*
  81.              * Interactions between kmap and other mappings
  82.              * make highmem support with aliasing VIPT caches
  83.              * rather difficult.
  84.              */
  85.             reason = "with VIPT aliasing cache";
  86.         }
  87.         if (reason) {
  88.             printk(KERN_CRIT "HIGHMEM is not supported %s, ignoring high memory\n",
  89.                 reason);
  90.             while (j > 0 && meminfo.bank[j - 1].highmem)
  91.                 j--;
  92.         }
  93.     }
  94. #endif
  95.     meminfo.nr_banks = j;
  96.     memblock_set_current_limit(lowmem_limit);
  97. }
(3)最后必须做的就是排序了,完成了这个工作就可以完全被我们上面提到的find_limits函数使用了,而这个工作就放在了接下来的arm_memblock_init(&meminfo, mdesc);中的一开头:
  1. static int __init meminfo_cmp(const void *_a, const void *_b)
  2. {
  3.     const struct membank *a = _a, *b = _b;
  4.     long cmp = bank_pfn_start(a) - bank_pfn_start(b);
  5.     return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
  6. }
  7.  
  8. void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
  9. {
  10.     int i;
  11.  
  12.     sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);  
  13.  
  14.     memblock_init();
  15.     for (i = 0; i < mi->nr_banks; i++)
  16.         memblock_add(mi->bank[i].start, mi->bank[i].size);
  17.  
  18.     /* Register the kernel text, kernel data and initrd with memblock. */
  19. #ifdef CONFIG_XIP_KERNEL
  20.     memblock_reserve(__pa(_sdata), _end - _sdata);
  21. #else
  22.     memblock_reserve(__pa(_stext), _end - _stext);
  23. #endif
  24. #ifdef CONFIG_BLK_DEV_INITRD
  25.     if (phys_initrd_size &&
  26.      !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
  27.         pr_err("INITRD: 0xlx+0xlx is not a memory region - disabling initrd\n",
  28.          phys_initrd_start, phys_initrd_size);
  29.         phys_initrd_start = phys_initrd_size = 0;
  30.     }
  31.     if (phys_initrd_size &&
  32.      memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
  33.         pr_err("INITRD: 0xlx+0xlx overlaps in-use memory region - disabling initrd\n",
  34.          phys_initrd_start, phys_initrd_size);
  35.         phys_initrd_start = phys_initrd_size = 0;
  36.     }
  37.     if (phys_initrd_size) {
  38.         memblock_reserve(phys_initrd_start, phys_initrd_size);
  39.  
  40.         /* Now convert initrd to virtual addresses */
  41.         initrd_start = __phys_to_virt(phys_initrd_start);
  42.         initrd_end = initrd_start + phys_initrd_size;
  43.     }
  44. #endif
  45.  
  46.     arm_mm_memblock_reserve();
  47.     arm_dt_memblock_reserve();
  48.  
  49.     /* reserve any platform specific memblock areas */
  50.     if (mdesc->reserve)
  51.         mdesc->reserve();
  52.  
  53.     memblock_analyze();
  54.     memblock_dump_all();
  55. }
 
通过上面的分析,整个高低端内存是如何确定的基本就清晰了,这里总结一下:
ARM构架中,高-低段内存是内核通过内核启动参数( mem=size@start和vmalloc=size)来自动配置的,如果没有特殊去配置他,那么在普通的ARM系统中是不会有高端内存存在的。除非你系统的RAM很大或vmalloc配置得很大,就很可能出现高端内存。
关键字:Linux内核  内存设置  代码跟踪  ARM构架 引用地址:Linux内核高-低端内存设置代码跟踪(ARM构架)

上一篇:JTAG接口技术及ETM
下一篇:如何安装arm-linux-gcc编译器

推荐阅读最新更新时间:2024-03-16 14:51

Linux内核定义的常量
1 初始定义 定义了你的机器上的地址转换 __virt_to_phys() 。这个宏用于把虚拟地址转换为一个物理地址。通常情况下: phys = virt - PAGE_OFFSET PHYS_OFFSET 2 解压缩符号 l ZTEXTADDR 解压缩器的地址地址。由于当你调用解压缩器代码时,通常关闭 MMU ,因此这里并不讨论虚拟地址和物理地址的问题。通常你在这个地址处调用内核,开始引导内核。它不需要在 RAM 中,只需要位于 FLASH 或其他只读或读 / 写的可寻址的存储设备中。 l ZBSSADDR 解压缩器的初始化为 0 的工作区的起始地址。必
[嵌入式]
Linux内核的Nand驱动流程分析
最近在做Linux内核移植,总体的感觉是这样的,想要彻底的阅读Linux内核代码几乎是不可能的,至少这还不是嵌入式学期初期的重要任务。内核代码解压后有250M左右,据统计,有400多万行,而且涉及到了软件和硬件两方面的诸多知识,凭一人之力在短时间内阅读Linux内核代码是根本不可能的,强行阅读可能会打消我们嵌入式学习的积极性,最后甚至可能放弃嵌入式学习,如果真的想阅读内核代码来提高自己水平的话可以等熟练掌握嵌入式以后再回过头来阅读,这样理解也会更深刻,更透彻。 我认为Linux内核移植的初期阶段应该将重点放在分析内核设备驱动上。实际上,Linux内核的移植就是设备驱动的移植,内核本身不会直接访问硬件,是通过驱动程序来间接控制硬件的,
[单片机]
STM32的Code/RO/RW/ZI区、Flash/Ram的占用情况、堆栈大小的设置
以cortex-M3为例,例如STM32F103 这篇文章要讲2个问题: 1、编译出的程序(指令)、变量的存放位置、大小? 2、在代码和keil中,“堆、栈”两者的大小如何设置? keil编译完成后,会有提示,形如: Program Size: Code=1148 RO-data=424 RW-data=20 ZI-data=1636 其中: ① Code为代码,本质上就是一大堆ARM指令; ② RO为只读的数据,例如,char *name = TOM ;//TOM三个字符就存放在ROM中作为RO-DATA;又如,为了减小sin的计算量,把sin的各个值直接制作成表,const float sinVal =
[单片机]
STM32的Code/RO/RW/ZI区、Flash/<font color='red'>Ram</font>的占用情况、堆栈大小的<font color='red'>设置</font>
ARMv8 Linux内核异常处理过程分析
1.1 Linux内核异常处理相关文件 Linux内核中,异常处理主要由两个文件完成,entry.S和traps.c,当然还有一些其它异常处理函数分布于fault.c, memory.c等等。entry.S包含异常的入口、进入异常处理C函数前的压栈、退出C函数前的出栈、一些fork函数相关的处理代码(暂不分析)、任务切换汇编处理过程(cpu_switch_to函数,暂不分析)。traps.c主要包含异常处理C函数。 本文主要分析entry.S,对于traps.c作简要介绍。 1.2 执行kernel_entry之前的栈 1.3 执行kernel_entry时的栈 1.4 执行kernel_exit 时的栈 1.
[单片机]
ARMv8 <font color='red'>Linux内核</font>异常处理过程分析
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
热门活动
换一批
更多
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

更多每日新闻
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved