发表于 2012-4-10 15:00
 
/proc/kcore文件提供了整个机器的内存映像,和vmcore不同的是,它提供了一个运行时的内存映像,为此和vmcore一样,内核提供了一个类似的但是稍显简单的kcore_list结构体,我们比较一下它们: 
struct kcore_list { 
         struct kcore_list *next; 
         unsigned long addr; 
         size_t size; 
}; 
struct vmcore { 
         struct list_head list; 
         unsigned long long paddr; 
         unsigned long long size; 
         loff_t offset; 
}; 
可 以看到vmcore比较复杂,事实上也正是如此,因此它的操作比较复杂,而且使用环境也是很复杂的,涉及到kexec和kdump机制,也许就是这个原因 它使用了内核中最普遍的list_head结构,但是对于kcore,它的结构十分简单,目的就是为了遍历整个内存,也不需要查找,删除等操作,因此它用 了自己的next字段来组成链表,如此一来可以节省一个指针的空间。 
在系统初始化的时候,mem_init函数中将整个物理内存和vmalloc的动态内存都加入了kcore_list中,这样的话,这个链表中就最起码有 了两个元素,一个是物理内存,另一个是vmalloc动态内存。注意这里所说的物理内存就是一一映射的内存,其实也可以不是,你完全可以自己实现一个映射 方法代替这里的一一映射,linux内核默认的什么highmem,vmalloc_start等等还有一一映射抑或高端映射等等机制都只是一个更底层的 机制一些策略,这个更底层的机制就是linux内核的内存映射,因此在这个机制提出的约束上你可以实现很多种策略,区分物理一一映射和高端映射只是其中之 一罢了: 
void __init mem_init(void) 
... 
    kclist_add(&kcore_mem, __va(0), max_low_pfn << PAGE_SHIFT); 
    kclist_add(&kcore_vmalloc, (void *)VMALLOC_START,  VMALLOC_END-VMALLOC_START); 
... 
void kclist_add(struct kcore_list *new, void *addr, size_t size) 
         new->addr = (unsigned long)addr; 
         new->size = size; 
         write_lock(&kclist_lock); 
         new->next = kclist; 
         kclist = new; 
         write_unlock(&kclist_lock); 
得到kcore文件的大小,其实这个文件并不是真的占据那么大的空间,而是内核提供的“抽象”实体的意义上的大小就是那么大,这里就是整个内存映像: 
static size_t get_kcore_size(int *nphdr, size_t *elf_buflen) 
         size_t try, size; 
         struct kcore_list *m; 
         *nphdr = 1; 
         size = 0; 
         for (m=kclist; m; m=m->next) {  //找到最大的地址值加上长度后就是最后的结果,依据就是linux内核空间的映射方式 
                 try = kc_vaddr_to_offset((size_t)m->addr + m->size); 
                 if (try > size) 
                         size = try; 
                 *nphdr = *nphdr + 1; 
         } 
         *elf_buflen =   sizeof(struct elfhdr) + 
...//elf_buflen是额外的一个头部的长度 
         *elf_buflen = PAGE_ALIGN(*elf_buflen); 
         return size + *elf_buflen;   //总的长度就是实际内存大小长度加上额外的头部的长度 
procfs 是一个文件系统,是文件系统的话就要有一个file_operations结构体来实现这个文件系统的操作,可是在procfs文件系统中,每一个文件可 以有不同的操作回调函数,也就是说,procfs首先是一个文件系统,在它是文件系统的意义的基础之上,它又是另一种机制,它提供了一个内核导出信息的口 子,就是说,procfs作为文件系统的意义仅仅在于信息的导出,它里面的文件从来都不是真实的文件,但是确实有文件的接口,比如你在ls -l命令发出给/proc/kcore文件时,它给出了文件的“大小”,实际上并不会占据那么大的空间而仅仅是一个数字,该数字是从上面的 get_kcore_size中得到的。在procfs文件系统中,每个文件都是一个proc_dir_entry,这才是它真正要表达的,套在标准文件 系统之上的那一层东西,该结构中的proc_fops就是该结构代表文件的file_operations结构体,如果这么理解的话,procfs文件系 统下的每一个文件都可以有自己的file_operations了而不必统一用整个procfs的一个file_operations,就像 ext2/ext3等传统的真实文件系统一样,从OO的角度来看,procfs继承了vfs文件系统,在文件系统的基础上实现了自己的特性(其实每一个具 体文件系统都有自己的特性,都继承并实现了vfs这个抽象类,不过本文就是说procfs的一个文件的,因此它显得比较特殊)。就好像前几篇文章中所描述 的seqfile一样,它就是专门为procfs提供一个串行化读取的接口函数机制而不是一个独立的机制,它可以被用在procfs的 file_operations中,当然也可以被用到别处。我们接下来看看read_kcore,它就是/proc/kcore这个proc文件的 proc_fops即file_operations的read回调函数: 
static ssize_t read_kcore(struct file *file, char __user *buffer, size_t buflen, loff_t *fpos) 
         ssize_t acc = 0; 
         size_t size, tsz; 
         size_t elf_buflen; 
         int nphdr; 
         unsigned long start; 
         read_lock(&kclist_lock); 
         proc_root_kcore->size = size = get_kcore_size(&nphdr, ?f_buflen); 
         if (buflen == 0 || *fpos >= size) { 
                 read_unlock(&kclist_lock); 
                 return 0; 
         } 
         if (buflen > size - *fpos) 
                 buflen = size - *fpos; 
...//为读出的内容添加elf头部。 
         start = kc_offset_to_vaddr(*fpos - elf_buflen);  //物理地址到虚拟地址的转换,其实对于一一映射就是加上一个PAGE_OFFSET偏移量,这也是默认情况,当然也可以提供别的转换方式。 
         if ((tsz = (PAGE_SIZE - (start & ~PAGE_MASK))) > buflen) 
                 tsz = buflen;                 
         while (buflen) { 
                 struct kcore_list *m; 
                 read_lock(&kclist_lock); 
                 for (m=kclist; m; m=m->next) {  //寻找这个地址所属的kcore_list 
                         if (start >= m->addr && start < (m->addr+m->size)) 
                                 break; 
                 } 
                 read_unlock(&kclist_lock); 
...//没有找到的错误处理 
                 } else if ((start >= VMALLOC_START) && (start < VMALLOC_END)) {  //在这种情况下,说明用户要读取的是vmalloc空间的内存映像,那么很简单,就是遍历vmalloc空间的vm_struct结构体们,然后将之上 的数据取出来。 
                         char * elf_buf; 
                         struct vm_struct *m; 
                         unsigned long curstart = start; 
                         unsigned long cursize = tsz; 
                         elf_buf = kmalloc(tsz, GFP_KERNEL); 
                         if (!elf_buf) 
                                 return -ENOMEM; 
                         memset(elf_buf, 0, tsz); 
                         read_lock(&vmlist_lock); 
                         for (m=vmlist; m && cursize; m=m->next) { 
                                 unsigned long vmstart; 
                                 unsigned long vmsize; 
                                 unsigned long msize = m->size - PAGE_SIZE; 
                             ...//限制判断 
                                 vmstart = (curstart < (unsigned long)m->addr ? 
                                         (unsigned long)m->addr : curstart); 
                                 if (((unsigned long)m->addr + msize) > (curstart + cursize)) 
                                         vmsize = curstart + cursize - vmstart; 
                                 else 
                                         vmsize = (unsigned long)m->addr +  msize - vmstart; 
                                ...//更新数据 
                                 memcpy(elf_buf + (vmstart - start), (char *)vmstart, vmsize); 
                         } 
                         read_unlock(&vmlist_lock); 
                         if (copy_to_user(buffer, elf_buf, tsz)) //向用户拷贝内存数据 
... 
                         kfree(elf_buf); 
                 } else {  //最后一种情况就是读取物理内存了,其实也不一定,要看体系结构了,在x86上而且内核编译flatmem的情形下,这就是读取物理内存。 
                         if (kern_addr_valid(start)) { 
                                 unsigned long n; 
                                 n = copy_to_user(buffer, (char *)start, tsz); 
...//错误处理 
                 } 
...//更新偏移以及指针数据 
         } 
         return acc; 
read 函数完毕之后,整个内存就被读出来了,存到一个地方保存那么这就是当时的内存运行快照,这里不得不说的是,这个信息可以用于调试,但是对于module的 调试就不是那么简单了,虽然kcore文件可以dump出整个内存,但是对于调试来说,这些信息是不够的,我们通过这些信息只能得到它当前是什么,而不能 得到它应该是什么,要想得到它应该是什么就必须有了原始的副本,幸运的是,linux的物理内存一一映射使得这个问题简化,linux内核vmlinuz 或者用于调试的vmlinux本身就是一个elf文件,-g选项编译的内核还有很多调试信息,elf连接脚本上写了符号加载的位置,以及elf的code 节,data节等等elf的要素,一一映射使得内核连接脚本的编写很简单,而且使得该脚本连接得到的内核载入内核时很容易的映射到了很简单的虚拟内存位 置,就是一个地址加上偏移。但是简单也就到此为止了,试想一下可加载的内核模块(LKM),在sys_init_module系统调用实现函数里发现模块 都是被映射到了vmalloc动态内存空间,包括它的代码,数据等等,如此一来,module的elf文件中写的节的载入地址在linux内核映射策略面 前成了一堆废物,即使你用module的原始副本来调试从/proc/kcore导出的映像也会发现很多的调试信息对不上,因此如何调试模块也就成了一个 大问题,linux的内核开发者也在着手解决这个问题...

Linux的内存映像导出接口—kcore的更多相关文章

  1. Linux下C程序的内存映像

    2.Linux下C程序的内存映像 2.1. 代码段.只读数据段(1)对应着程序中的代码(函数),代码段在Linux中又叫文本段(.text)(2)只读数据段就是在程序运行期间只能读不能写的数据,con ...

  2. Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式

    Linux就这个范儿 第15章 七种武器  linux 同步IO: sync.fsync与fdatasync   Linux中的内存大页面huge page/large page  David Cut ...

  3. 嵌入式 linux 查看内存

    在Windows系统中查看内存的使用情况很简单,想必大家都已经耳熟能详了,那么在linux系统如何查看内存使用情况呢?下面和大家分享在Linux下查看内存使用情况的free命令: [root@scs- ...

  4. Linux下内存查看命令

    在Linux下面,我们常用top命令来查看系统进程,top也能显示系统内存.我们常用的Linux下查看内容的专用工具是free命令. Linux下内存查看命令free详解: 在Linux下查看内存我们 ...

  5. Linux操作系统的内存使用方法详细解析

    我是一名程序员,那么我在这里以一个程序员的角度来讲解Linux内存的使用. 一提到内存管理,我们头脑中闪出的两个概念,就是虚拟内存,与物理内存.这两个概念主要来自于linux内核的支持. Linux在 ...

  6. Linux下内存查看及详解

    在Linux下面,我们常用top命令来查看系统进程,top也能显示系统内存.我们常用的Linux下查看内容的专用工具是free命令. Linux下内存查看命令free详解: 在Linux下查看内存我们 ...

  7. Linux共享内存(一)

    inux系统编程我一直看 <GNU/LINUX编程指南>,只是讲的太简单了,通常是书和网络上的资料结合着来掌握才比较全面 .在掌握了书上的内容后,再来都其他资料 . 原文链接 http:/ ...

  8. Linux内核内存管理架构

    内存管理子系统可能是linux内核中最为复杂的一个子系统,其支持的功能需求众多,如页面映射.页面分配.页面回收.页面交换.冷热页面.紧急页面.页面碎片管理.页面缓存.页面统计等,而且对性能也有很高的要 ...

  9. Linux 程序设计1:深入浅出 Linux 共享内存

    笔者最近在阅读Aerospike 论文时,发现了Aerospike是利用了Linux 共享内存机制来实现的存储索引快速重建的.这种方式比传统利用索引文件进行快速重启的方式大大提高了效率.(减少了磁盘 ...

随机推荐

  1. CROC 2016 - Qualification C. Hostname Aliases map

    C. Hostname Aliases 题目连接: http://www.codeforces.com/contest/644/problem/C Description There are some ...

  2. JavaScript Diagramming

    JavaScript Diagramming Optensity

  3. Switch debouncer uses only one gate

    The circuit in Figure 1 produces a single debounced pulse each time you press S1. Moreover, the circ ...

  4. spring boot配置springMVC拦截器

    spring boot通过配置springMVC拦截器 配置拦截器比较简单, spring boot配置拦截器, 重写preHandle方法. 1.配置拦截器: 2重写方法 这样就实现了拦截器. 其中 ...

  5. (Inside Out) Web地图坐标系——TDT的奇妙

    一个GIS科班出生的研究生.把已还到课本的基础GIS知识,准备又一次学习,并结合这几年下来自身在行业中GIS的应用.总结一些有用的GIS知识点.一备不时之需,二为积累沉淀,三则是年龄越大.记性越差,加 ...

  6. iOS:带主标题、副标题、图像类型的表格视图UITableView

    制作一个通讯录,包括姓名.电话.头像,将表格视图类型设置为UITableViewCellStyleSubtitle 效果图: //创建一个联系人的类,初始化数据 在视图控制器中实现表格内容的显示 #i ...

  7. SQL PRIMARY KEY,SQL FOREIGN KEY

    A primary key is defined as a column or a group of column that their value are always be unique. Nor ...

  8. MySQL服务器安装完之后如何调节性能

    原文作者: Peter Zaitsev原文来源: http://www.mysqlperformanceblog.com/2006/09/29/what-to-tune-in-mysql-server ...

  9. MongoDB 复制一个collection里的数据到另一个collection

    mongodb shell 中执行: db.source(复制源表).find().forEach(function(x){ db.target(目的表).insert(x); })

  10. (剑指Offer)面试题43:n个骰子的点数

    题目: 把n个骰子仍在地上,所有骰子朝上一面的点数之和为s.输入n,打印出s的所有可能的值出现的概率. 思路: s可能出现的值的范围为:n--6*n 1.全排列 回溯法枚举n个骰子(6面)的全排列,然 ...