对于ARM中内核如何在启动的时候设置高低端内存的分界线(也是逻辑地址与虚拟地址分界线(虚拟地址)减去那个固定的偏移),这里我稍微引导下(内核分析使用Linux-3.0):

首先定位设置内核虚拟地址起始位置(也就是内核逻辑地址末端+1的地址)的文件:init.c (arch\arm\mm),在这个文件中的void __init bootmem_init(void)函数如下

void __init bootmem_init(void)

{

unsigned long min, max_low, max_high;

max_low = max_high = 0;

find_limits(&min, &max_low, &max_high);

arm_bootmem_init(min, max_low);

/*

* 1118.www.qixoo.qixoo.com Sparsemem tries to allocate bootmem in memory_present(),

* so must be done after the fixed reservations

*/

arm_memory_present();

/*

* sparse_init() needs the bootmem allocator up and running.

*/

sparse_init();

/*

* Now free the memory - free_area_init_node needs

* the sparse mem_map arrays initialized by sparse_init()

* for memmap_init_zone(), otherwise all PFNs are invalid.

*/

arm_bootmem_free(min, max_low, max_high);

high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) - 1) + 1;

/*

* This doesn't seem to be used by the Linux memory manager any

* more, but is used by ll_rw_block. If we can get rid of it, we

* also get rid of some of the stuff above as well.

*

* Note: max_low_pfn and max_pfn reflect the number of _pages_ in

* the system, not the maximum PFN.

*/

max_low_pfn = max_low - PHYS_PFN_OFFSET;

max_pfn = max_high - PHYS_PFN_OFFSET;

}

这个high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) – 1) + 1;语句就是关键。从这里可以知道max_low就是高端内存的起始地址(物理地址)。那么这个max_low是如何得到的?其实看上面的代码可以推测出,他其实是在find_limits(&min, &max_low, &max_high);中(在同一个文件中)被设置的:

static void __init find_limits(unsigned long *min, unsigned long *max_low,

unsigned long *max_high)

{

struct meminfo *mi = &meminfo;

int i;

*min = -1UL;

*max_low = *max_high = 0;

for_each_bank (i, mi) {

struct membank *bank = &mi->bank[i];

unsigned long start, end;

start = bank_pfn_start(bank);

end = bank_pfn_end(bank);

if (*min > start)

*min = start;

if (*max_high < end)

*max_high = end;

if (bank->highmem)

continue;

if (*max_low < end)

*max_low = end;

}
}

这个函数的意思很明显:通过扫描struct meminfo *mi = &meminfo;(结构体meminfo的数组)中的所有信息,设置三个指针所指的变量:

min :内存物理地址起始
max_low :低端内存区物理地址末端
max_high :高端内存区物理地址末端

从上面可以看出,max_low和max_high所保存的地址不同就是由于bank->highmem造成的,它是内存bank被设为高端内存的依据:

“如果这个内存bank是高端内存(bank->highmem != 0),跳过max_low = end;语句,max_low和max_high将不同(结果实际上是max_high > max_low);
否则假设没有一个内存bank是高端内存(所有bank->highmem == 0)max_low和max_high必然一致(高端内存大小为0)”

当然要实现这个函数的功能,必须保证meminfo所指数组中的所有bank是按照地址数据从小到大排序好的哦~~。但是这个大家不用担心,后面会看到的:)

经过上面的跟踪,焦点集中到了全局变量(同一个文件中):

/*
* This keeps memory configuration data used by a couple memory
* initialization functions, as well as show_mem() for the skipping
* of holes in the memory map. It is populated by arm_add_memory().
*/
struct meminfo meminfo;

这个结构体的定义(setup.h (arch\arm\include\asm)):

/*

* Memory map description

*/
#define NR_BANKS 8 /*现在ARM最大只支持到8个bank哦~*/

struct membank {

phys_addr_t start;

unsigned long size;

unsigned int highmem; /*我们关心的变量*/

};

struct meminfo {

int nr_banks;

struct membank bank[NR_BANKS]; /*我们关心的数组*/

};

extern struct meminfo meminfo;

#define for_each_bank(iter,mi) \

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内核的启动流程,以后我也会写下)中有这样一个函数:

void __init setup_arch(char **cmdline_p)

{

struct machine_desc *mdesc;

unwind_init();

setup_processor();

mdesc = setup_machine_fdt(__atags_pointer);

if (!mdesc)

mdesc = setup_machine_tags(machine_arch_type);

machine_desc = mdesc;

machine_name = mdesc->name;

if (mdesc->soft_reboot)

reboot_setup("s");

init_mm.start_code = (unsigned long) _text;

init_mm.end_code = (unsigned long) _etext;

init_mm.end_data = (unsigned long) _edata;

init_mm.brk = (unsigned long) _end;

/* 填充cmd_line以备后用,维护boot_command_line */

strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE); /* 拷贝boot_command_line中的数据到cmd_line */

*cmdline_p = cmd_line;

parse_early_param(); /* 分析boot_command_line(内核启动参数字符串)中的数据,

* 其中就分析了mem=size@start参数初始化了struct meminfo meminfo;

* 同时如果有vmalloc=size参数也会初始化 vmalloc_min */

sanity_check_meminfo(); /* 在此处设置struct meminfo meminfo中每个bank中的highmem变量,

* 通过vmalloc_min确定每个bank中的内存是否属于高端内存 */

arm_memblock_init(&meminfo, mdesc); /* 在此处排序按地址数据从小到大排序 */

paging_init(mdesc);

request_standard_resources(mdesc);

unflatten_device_tree();

#ifdef CONFIG_SMP

if (is_smp())

smp_init_cpus();

#endif

reserve_crashkernel();

cpu_init();

tcm_init();

#ifdef CONFIG_MULTI_IRQ_HANDLER

handle_arch_irq = mdesc->handle_irq;

#endif

#ifdef CONFIG_VT

#if defined(CONFIG_VGA_CONSOLE)

conswitchp = &vga_con;

#elif defined(CONFIG_DUMMY_CONSOLE)

conswitchp = &dummy_con;

#endif

#endif

early_trap_init();

if (mdesc->init_early)

mdesc->init_early();

}

在上面的注释中,我已经表明了重点和解析,下面我细化下:

(1)获取参数部分

通过parse_early_param();函数可以解析内核启动参数中的许多字符串,但是对于我们这次分析内存的话主要是分析以下两个参数:

mem=size@start参数,她为初始化struct meminfo meminfo;(我们一直关注的内存信息哦~)提供信息。具体的获取信息的函数(同样位于setup.c (arch\arm\kernel)):

vmalloc=size参数,她为初始化vmalloc_min(需要保留的内核虚拟地址空间大小,也就是这个内核虚拟地址空间中除去逻辑地址空间和必要的防止越界的保护空洞后最少要预留的地址空间)提供信息。具体的实现函数(位于mmu.c (arch\arm\mm)):

(2)在获得了必要的信息(初始化好struct meminfo meminfo和vmalloc_min)后,内核通过sanity_check_meminfo函数自动去通过vmalloc_min信息来初始化每个meminfo.bank[?]中的highmem成员。此过程中如果有必要,将可能会改变meminfo中的bank数组。处理函数位于mmu.c (arch\arm\mm):

(3)最后必须做的就是排序了,完成了这个工作就可以完全被我们上面提到的find_limits函数使用了,而这个工作就放在了接下来的arm_memblock_init(&meminfo, mdesc);中的一开头:

通过上面的分析,整个高低端内存是如何确定的基本就清晰了,这里总结一下:

ARM构架中,高-低段内存是内核通过内核启动参数( mem=size@start和vmalloc=size)来自动配置的,如果没有特殊去配置他,那么在普通的ARM系统中是不会有高端内存存在的。除非你系统的RAM很大或vmalloc配置得很大,就很可能出现高端内存。

以上是我对高-低端内存学习时跟踪代码的备忘,如果大家在其中发现什么不对的地方,欢迎拍砖、纠正~~谢谢~

Linux 内核高-低端内存设置代码跟踪(ARM构架)的更多相关文章

  1. Linux内核高端内存 转

        Linux内核地址映射模型x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图.   Linux内核地址空间划分 通 ...

  2. Linux内核高端内存

    Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型.进程代码中的地址为逻辑地址,经过段页式地址映射后,才真正访问物理内存. 段页式机制如下图. Linux内核地址空间划分 通常32位L ...

  3. linux用户空间和内核空间(内核高端内存)_转

    转自:Linux用户空间与内核空间(理解高端内存) 参考: 1. 进程内核栈.用户栈 2. 解惑-Linux内核空间 3. linux kernel学习笔记-5 内存管理   Linux 操作系统和驱 ...

  4. KSM剖析——Linux 内核中的内存去耦合

    简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging)  允许这个系统管理程序通 ...

  5. 初探Linux内核中的内存管理

    Linux内核设计与实现之内存管理的读书笔记 初探Linux内核管理 内核本身不像用户空间那样奢侈的使用内存; 内核不支持简单快捷的内存分配机制, 用户空间支持? 这种简单快捷的内存分配机制是什么呢? ...

  6. [转]linux内核分析笔记----内存管理

    转自:http://blog.csdn.net/Baiduluckyboy/article/details/9667933 内存管理,不用多说,言简意赅.在内核里分配内存还真不是件容易的事情,根本上是 ...

  7. 【转】一篇关于32位Linux内核使用大内存的文章——Hugemem Kernel Explained  &nb

    红旗DC系列Linux操作系统(x86平台)中带有四类核心: UP (支持单内核) SMP (支持多内核) hugemem Icc* (用intel C编译器编译的核心) 其中hugemem核心往往引 ...

  8. Linux内核分析之理解进程调度时机跟踪分析进程调度与进程切换的过程

    一.原理分析 1.调度时机 背景不同类型的进程有不同的调度需求第一种分类I/O-bond:频繁的进行I/O:通常会花费很多时间等待I/O操作的完成CPU-bound:计算密集型:需要大量的CPU时间进 ...

  9. linux内核分析之内存管理

    1.struct page /* Each physical page in the system has a struct page associated with * it to keep tra ...

随机推荐

  1. jquery的offset与position的区别

    这里offset取得是屏幕影藏的y轴的距离➕元素距离屏幕的y轴的距离. 而postion取得的则是,上一个父元素(包含postion定位的)的距离

  2. mybatis resultMap映射学习笔记

    这几天,百度mybatis突然看不到官网了,不知道百度怎么整的.特此贴出mybatis中文官网: http://www.mybatis.org/mybatis-3/zh/index.html 一个学习 ...

  3. ViewHolder数据错乱BUG

    需求是这样的,在列表中用一个图标标示某个item是已经被接下或者完成的任务. 对于文件有这样的操作,进入列表后第一页面展示正常,但是加载更多后同样位置出现了同样的标志.这不是我想要的效果 我的解决办法 ...

  4. android 圆角图片的实现

    图片展示的时候总觉的直角的图片不好看?好办法来了!-- public class ToRoundCorner extends Activity{ public Bitmap toRoundCorner ...

  5. linux 常用命令总结

    PS命令: 1.命令格式: ps[参数] 2.命令功能: 用来显示当前进程的状态 3.命令参数: a  显示所有进程 -a 显示同一终端下的所有程序 -A 显示所有进程 c  显示进程的真实名称 -N ...

  6. 1118ALTER TABLE tabname DISCARD TABLESPACE快速导入数据利用表空间

    -- 快速导入数据如果你有.ibd文件的一个干净的备份,你可以按如下操作从被起源的地方恢复它到MySQL安装中:相当快速 1. 发出这个ALTER TABLE语句: 2. ALTER TABLE tb ...

  7. SpringMVC学习--参数绑定

    spring参数绑定过程 从客户端请求key/value数据,经过参数绑定,将key/value数据绑定到controller方法的形参上.springmvc中,接收页面提交的数据是通过方法形参来接收 ...

  8. [转]servlet中的service, doGet, doPost方法的区别和联系

    原文地址:http://m.blog.csdn.net/blog/ghyg525/22928567 大家都知道在javax.servlet.Servlet接口中只有init, service, des ...

  9. 控件(文本类): TextBox, PasswordBox

    介绍背水一战 Windows 10 之 控件(文本类) TextBox PasswordBox 示例1.TextBox 的示例 1Controls/TextControl/TextBoxDemo1.x ...

  10. 使用redis作为session的存储方式

    (1)准备 A. 安装好redis https://github.com/MSOpenTech/redis  注意:下载release版 启动脚本如下: redis-server  redis.win ...