转自:http://blog.csdn.net/linyt/article/details/6627664

Linux kernel的内存管理子系统非常复杂,为了深入了解内存管理系统,我打算分多篇文章来分析linux内存管理。本文就谈谈kernel如何收集物理内存的地址空间和大小等信息。

嵌入式arm处理器与我们平时接触到的intel处理器有点不一样,intel处理器可以通过主板或者BIOS代码来自动检测物理内存的大小。但arm处理器下的嵌入式系统没有这么幸运了,它必须由bootloader手工(或硬编码)的方式来获知kernel板上物理内存的开始地址和大小。

实际上,除了物理内存信息外,命令行参数,视频卡信息和randisk信息,都是通过bootloader一一告知kernel的。Bootloader和kernel之间必须有某种约定,才能方便让两者之间传递信息。实事上,arm-kernel约定bootloader按如下的要求来存放这些信息:

1.在bootloader跳到kernel执行时,r2寄存器值为存放这些信息的首地址。

2.上述各子信息必须按一定的格式来组合起来。

在arm-kernel里面,每个子信息项称为atag.  Struct tag的定义如下:

  1. struct tag {
  2. struct tag_header hdr;
  3. union {
  4. struct tag_core     core;
  5. struct tag_mem32    mem;
  6. struct tag_videotext    videotext;
  7. struct tag_ramdisk  ramdisk;
  8. struct tag_initrd   initrd;
  9. struct tag_serialnr serialnr;
  10. struct tag_revision revision;
  11. struct tag_videolfb videolfb;
  12. struct tag_cmdline  cmdline;
  13. struct tag_acorn    acorn;
  14. struct tag_memclk   memclk;
  15. } u;
  16. };

struct tag_header hdr类似于头部标识的作用,它用于标识后面的union的类型,以及此种结构占用内存的大小。 后面union表示,每个tag可以是上面几种信息其中的一种。

上图就是各种tag组合成tags的一个实例。Hdr里面记录后的是哪种tag,整个tag的长度是多少,这样很方跳到下一个tag去。Hdr的类型是struct tag_header,定义如下:

  1. struct tag_header {
  2. __u32 size;
  3. __u32 tag;
  4. };

为了方便分析,假定kernel已经知道了tags的地址,那么它调用parse_tags函数来对各个tags进行分析,代码如下:

  1. [arch/arm/kernel/setup.c]
  2. static void __init parse_tags(const struct tag *t)
  3. {
  4. for (; t->hdr.size; t = tag_next(t))
  5. if (!parse_tag(t))
  6. printk(KERN_WARNING
  7. "Ignoring unrecognised tag 0x%08x\n",
  8. t->hdr.tag);
  9. }

Tags假定,在排好各个tag之后,后面要跟一个hdr.size为0的空tag。所以在for中,利用t->hdr.size
为0作为结束条件。而t = tag_next(t) 就是利用t->hdr.size的大小跳到下一个tag的。tag_next定义如下:

#define tag_next(t) ((struct tag *)((__u32 *)(t) + (t)->hdr.size))

而parse_tag函数就对t所指向的tag进行分析,代码如下:

  1. static int __init parse_tag(const struct tag *tag)
  2. {
  3. extern struct tagtable __tagtable_begin, __tagtable_end;
  4. struct tagtable *t;
  5. for (t = &__tagtable_begin; t < &__tagtable_end; t++)
  6. if (tag->hdr.tag == t->tag) {
  7. t->parse(tag);
  8. break;
  9. }
  10. return t < &__tagtable_end;
  11. }

这里又使用了kernel惯用技巧,就是把各种atag关联的处理函数结构定义到一个特殊的section里面,然后在链接时,__tagtable_begin就是该section的开始地址,而__tagtable_end则是它的结束地址。从parse_tag函数可以使用,编译后该section就是一个struct
tagtable数组,类型tagtable定义如下:

  1. struct tagtable {
  2. __u32 tag;
  3. int (*parse)(const struct tag *);
  4. };

tag是它所描述的tag类型,而parse则就此种类型atag的分析处理函数。结合parse_tag函数可知,tagtable数组就是定义好各种atag的处理函数,当bootloader将atags传递到kernel里,kernel依次检查各atag的类型,然后查表(tagtable)调用它的处理函数。

实例上,bootloader向kernel传递的atags种类较多,它们各有各的用途,本文主要分析kernel是如何收集物理内存信息的。因此,我们只关心类型为ATAG_MEM的tag。我们先看ATAG_MEM类型的tagtable定义:

  1. #define __tag __used __attribute__((__section__(".taglist.init")))
  2. #define __tagtable(tag, fn) \
  3. static struct tagtable __tagtable_##fn __tag = { tag, fn }
  4. __tagtable(ATAG_MEM, parse_tag_mem32);

从这种tagtable的定义可以看出,ATAG_MEM这种tag的处理函数是parse_tag_mem32,它的代码如下:

  1. static int __init parse_tag_mem32(const struct tag *tag)
  2. {
  3. return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
  4. }

当tag->hdr.tag 为ATAG_MEM时,tag.u的数据为mem成员有效,而mem成员是下面这种类型的:

  1. struct tag_mem32 {
  2. __u32   size;
  3. __u32   start;  /* physical start address */
  4. };

故parse_tag_mem32函数,将物理内存的信息传递并调用arm_add_memory函数。在arm-kernel的启动阶段,它使用一个叫meminfo的数据结构来记录系统的所有物理内存。在arm系统架构中,SDRAM是连接到arm的bank里的,即物理内存是分bank的。故meminfo的类型,也是类似的,它的定义如下:

  1. struct membank {
  2. unsigned long start;
  3. unsigned long size;
  4. int           node;
  5. };
  6. struct meminfo {
  7. int nr_banks;
  8. struct membank bank[NR_BANKS];
  9. };

Nr_banks记录当前meminfo已存放多少个bank,而bank[i]成员则记录第i个bank内存的开始地址和大小。

毫无疑问,arm_add_memory函数的工作就是它atags里面的物理内存信息增加到meminfo结构是,代码如下:

  1. static int __init arm_add_memory(unsigned long start, unsigned long size)
  2. {
  3. struct membank *bank = &meminfo.bank[meminfo.nr_banks];
  4. if (meminfo.nr_banks >= NR_BANKS) {
  5. printk(KERN_CRIT "NR_BANKS too low, "
  6. "ignoring memory at %#lx\n", start);
  7. return -EINVAL;
  8. }
  9. /* 使start和size均为PAGE_SIZE(即4K) 的倍数
  10. * 其中start以4K向上取整,而size则以4K向下取整
  11. */
  12. size -= start & ~PAGE_MASK;
  13. bank->start = PAGE_ALIGN(start);
  14. bank->size  = size & PAGE_MASK;
  15. bank->node  = PHYS_TO_NID(start);
  16. /*
  17. * Check whether this memory region has non-zero size or
  18. * invalid node number.
  19. */
  20. if (bank->size == 0 || bank->node >= MAX_NUMNODES)
  21. return -EINVAL;
  22. meminfo.nr_banks++;
  23. return 0;
  24. }

当处理完所有的atags后,meminfo数据结构所描述的信息,就是个开发板上的物理内存信息。为了获得更直观的运行结果,我在arm_add_memory函数添加了如下的打印代码:

Printk(KERN_ERR “add memory bank(%d) 0x%08lx-0x%08lx\n”,

Meminfo.nr_banks, bank->start, bank->start + bank->size);

便可获知开发板上的物理内存情况了。就天嵌的s3c2440开板来说,它的SDRAM是接到bank6里面,故它的起始地址是0x3000000,大小是64M,它的结束地址是0x32000000。

尽管知道parse_tag是用于收到物理内存信息的,但仍不知道它是在何处被调用的。其实它是在启动阶段进行收集的,它的调用关系如下:

Start_kernel() -> setup_arch() -> parse_tags()

当执行完parse_tags函数,那么meminfo结构就收集完了物理内存信息。接下来的事情就是建立最终的内核空间的页表,以及建立临时的内存管理系统和初始化所有的物理页。

Arm-kernel 内存收集【转】的更多相关文章

  1. 【linux】arm mm内存管理

    欢迎转载,转载时请保留作者信息,谢谢. 邮箱:tangzhongp@163.com 博客园地址:http://www.cnblogs.com/embedded-tzp Csdn博客地址:http:// ...

  2. linux kernel内存回收机制

    转:http://www.wowotech.net/linux_kenrel/233.html linux kernel内存回收机制 作者:itrocker 发布于:2015-11-12 20:37 ...

  3. Linux kernel 内存泄露本地信息泄露漏洞

    漏洞名称: Linux kernel 内存泄露本地信息泄露漏洞 CNNVD编号: CNNVD-201311-467 发布时间: 2013-12-06 更新时间: 2013-12-06 危害等级:    ...

  4. Linux kernel 内存损坏漏洞

    漏洞名称: Linux kernel 内存损坏漏洞 CNNVD编号: CNNVD-201310-143 发布时间: 2013-10-11 更新时间: 2013-10-11 危害等级: 中危   漏洞类 ...

  5. linux kernel内存映射实例分析

    作者:JHJ(jianghuijun211@gmail.com)日期:2012/08/24 欢迎转载,请注明出处 引子 现在android智能手机市场异常火热,硬件升级非常迅猛,arm cortex ...

  6. Linux kernel 内存 - 页表映射(SHIFT,SIZE,MASK)和转换(32位,64位)

    0. Intro 如下是在32位下的情况,32位下,只有三级页表:PGD,PMD,PTE 在64位情况下,会有四级页表:PGD,PUD,PMD,PTE 但是原理基本上是一样的,本文主要是想记录一下页表 ...

  7. Linux ARM kernel Makefile and Kconfig

    kernel build:顶层Makefile:-->1. include build/main.mk    -->2. include build/kernel.mk         k ...

  8. 关于arm处理器 内存编址模式 与 字节对齐方式 (转)

    转自:http://bavon.bokee.com/5429805.html 在x86+Linux上写的程序,在PC机上运行得很好.可是使用ARM的gcc进行交叉编译,再送到DaVinci目标板上运行 ...

  9. CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇

    在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到. 为了分析这部分我花了一个多月的时间,期间也多次向Cor ...

随机推荐

  1. LoadRunner录制用户操作

    先说明一点,使用录制的手段拿到的测试脚本和工程师自己编写的测试脚本其实是一样的,不要觉得录制的方式low,而自己编写脚本就显得高大上,这是不对的.除非工程师本身对开发们写的代码逻辑很熟,对业务上的各个 ...

  2. 【.Net】C# 根据绝对路径获取 带后缀文件名、后缀名、文件名、不带文件名的文件路径

    1.c#根据绝对路径获取 带后缀文件名.后缀名.文件名.   1 string str =" F:\test\Default.aspx"; 2 string filename = ...

  3. HDFS集中式的缓存管理原理与代码剖析--转载

    原文地址:http://yanbohappy.sinaapp.com/?p=468 Hadoop 2.3.0已经发布了,其中最大的亮点就是集中式的缓存管理(HDFS centralized cache ...

  4. 【bzoj4542】[Hnoi2016]大数 莫队算法

    题目描述 给出一个数字串,多次询问一段区间有多少个子区间对应的数为P的倍数.其中P为质数. 输入 第一行一个整数:P.第二行一个串:S.第三行一个整数:M.接下来M行,每行两个整数 fr,to,表示对 ...

  5. varnish启动报错

    错误1.Starting Varnish Cache: Error: Cannot open socket: :80: Address family not supported by protocol ...

  6. 怎样搭建一个自有域名的 WORDPRESS 博客?

    博客搭建并不复杂,只是过程有点繁琐,适合喜欢折腾的人,主要有下面几个步骤: 新建一个博客文件 购买域名(Domain Name) 注册一个主机空间(Web Host) 域名解析(DNSPod) 安装W ...

  7. 3294 [SCOI2016]背单词

    题目描述 Lweb 面对如山的英语单词,陷入了深深的沉思,”我怎么样才能快点学完,然后去玩三国杀呢?“.这时候睿智的凤老师从远处飘来,他送给了 Lweb 一本计划册和一大缸泡椒,他的计划册是长这样的: ...

  8. 【CodeChef-SPCLN】Cleaning the Space

    https://odzkskevi.qnssl.com/7dfb262544887eff6fb35bfb444759d6?v=1502084197 做法是类似于最大割之类的东西,把每个碎片按照按钮拆点 ...

  9. 流媒体协议之RTSP客户端的实现20171014

    RtspClient是基于jrtplib实现的,目前仅支持h264格式,后续将不断迭代优化,加入对其他格式的支持,并且将实现RTSP的服务端. RtspClient的功能是接收服务端过来流,然后写入到 ...

  10. (转)IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别

    本文来自:https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral,非常感谢 ...