25th
April
2007
本文作者:campos 本文出处:http://www.mykernelspace.com (转载请保留此行,谢谢)
既然说到了内存管理,就顺带说一下内存分配。内存的管理细节以及实现可以看mm部分的源码。暴露给内核程序员的基本内存分配函数是经常要用到的东东。
简单说来,有三种分配内存的方法:
- kmalloc:这个用到很多。这个函数将会分配一片连续的物理内存。通常分配连续物理内存的好处就是构造页表的时候开销很低(通常线性地址加上一个偏移就是物理地址),同时访问起来效率也高。当然连续的物理内存也是很宝贵的资源。内核中使用的buddy algorithm和slab机制都是为了尽量减少内存碎片,增加连续内存分配成功的几率。kmalloc有很多mode,比如说GFP_KERNEL, GFP_ATOMIC。这些mode其实是一些更细节的flag的组合,比如说 GFP_KERNEL 就是 __GFP_WAIT | __GFP_IO | __GFP_FS ; 而 GFP_ATOMIC 就是 __GFP_HIGH。在一些中断处理中需要内存分配立刻返回,这样就需要不同的kmalloc 模式。这些flag具体的意思可以参考LDD3的第八章。
- kmem_cache:这个是Linux内核Slab机制提供的特殊的内存分配函数。“slab”直译过来就是“水泥预制板”:) 其实这个名字非常的形象。内核中经常要分配一些常用的struct,比如说filp, task_struct, file等等。Slab是一个lookaside cache机制,在内存中会创建一个memory pool。这个pool里面当然就是这些指定大小的object。这样分配或者释放起来都很高效(省去了内存分配和初始化的过程)。
- __get_free_pages:是直接获取整页的内存(页数是2的幂)。其实kmalloc在实现的时候也调用了这个函数。当需要分配大量的内存的时候,使用这个函数能够提高效率。
- vmalloc:这个函数分配一片连续的“虚拟内存”。也就是说返回的线性地址虽然是连续的,但是映射到的物理内存是不连续的,而且跟物理地址可能不是一一对应的(不同于kmalloc和__get_free_pages)。所以在使用分配到的内存时,页表的查询比较频繁,所以效率相对较低。但是LDD3中提到了Linux内核在create_module的时候,采用的就是vmalloc。我看了看/proc/kallsyms,我load的module里面的symbol确实都分布在不同的内存区域。
posted in Linux Kernel & Driver |
24th
April
2007
本文作者:campos 本文出处:http://www.mykernelspace.com (转载请保留此行,谢谢)
接触Linux内核也有一段时间了,在开发kernel module的时候,其实有几样东西是需要牢牢掌握的。
- 内核基本的struct:其实读Understanding the Linux Kernel (ULK3)的时候很困惑,如果一章一章地读下去,没有什么感觉。其实在读第一遍的时候先掌握内核中重要的struct。这些struct在内核编程中将会频繁用到。
- 内核同步机制: user mode里面的semaphore和mutex是编程必备的工具。在内核里面,同步不仅是线程间的,而且还要考虑SMP的问题。
- 内存管理机制: 无论做什么开发,内存管理都是程序的“循环系统”,如果血液不通畅,整个module的性能肯定上不去。内核内存是很宝贵的资源。下面就简要分析一下具体Linux是如何管理内核内存的(其实是读ULK3和LDD3的读书笔记)
Linux采用的是段页式内存模型,也就是既使用了段(segment)又使用了分页(paging)技术。分段技术是历史遗留的产物,它出现的主要原因就是寄存器存储位数和地址空间位数不匹配的结果。分页和分段都是虚拟内存实现的机制。分段技术利用段基址把内存划分成很多互相重叠的内存空间。当进程在自己的段里运行的时候,自己能够访问的就是整个段空间。但是这样的后果就是进程的内存缺乏必要的保护。所以随着保护模式的引入,分页技术得到了使用。在分页技术的优势下,分段技术显得没有什么必要了,但是程序的分段结构依然保留了下来(数据段、代码段、堆栈段等等)。在程序运行的时候,依然需要配备相应的段。所以Linux采用了这种混合的段页式内存。在Linux中,只有几个固定的段可供使用:内核数据/代码段、用户数据/代码段。在所有内核进程中,段寄存器里载入的始终是内核数据/代码段的段基址,而且这个基址是固定的(当然这个基址还不是真实的物理地址)。同样在用户进程中,段寄存器里载入的始终是用户数据/代码段基址。
所有的虚拟内存实现的机制最关键的是内存虚拟地址到物理地址的映射。Linux的段页式内存模型使用了两级地址映射:虚拟地址-〉线性地址-〉物理地址(ULK3和LDD3的命名有所不同)。逻辑地址就是在程序中使用的地址,这个地址首先要经过分段处理,也就是读入相应的用户/内核段基址(已经是常量了,而且就是0×00000000)。随后就得到了一个地址值没有变化的线性地址。这个地址是32位的(这里讨论的是i386 32bit架构)。这个线性地址需要经过分页处理才能得到真正的物理地址。可以看出这里虚拟内存地址实际上在数值上都等于线性地址,只是段寄存器的内容反映了这个地址是属于用户还是内核,数据还是代码。
在Linux中,有一个著名的3Gb界限,也就是在线性地址中0~3GB的地址是留给用户空间的,3GB~4GB的地址是内核使用的。所以当你在程序中看到一个大于等于0xc0000000的地址,这个地址一定是映射到内核的内存中。Linux采用了多级页表技术来实现从线性地址到物理地址的映射。32位的地址的前10位是Page Directory的index(显然这个表最多有1024个entry)。中间10位是Page Table的index。剩下的12位是页内偏移(在这种实现下,每一个内存页的大小是4KB)。使用这种分页技术的好处是节省进程的空间,因为每一个进程都有一个页表。
Linux使用的是4KB的内存页面大小(CPU有4KB或者4MB两种设置)。实际的物理内存被划分成一个一个4KB的内存页帧,每一个页帧在内存中都对应有相应的一个32字节大小的page struct。在Linux看来,一个page struct实际上就代表了一个实际的物理页帧。每个有效的(mapped)线性地址都映射到一个相应的page struct上。但是内核的线性地址空间只有1GB,而真正的物理地址可以大到4GB,所以没有办法做到线性地址和实际的物理地址的一一对应。所以在内核的1GB线性地址空间中有一部分(896MB, Low Memory)是直接映射到物理地址上的,这部分内存叫做内核逻辑地址(根据LDD3的命名)。内核线性空间还剩下128MB (内核虚拟地址)提供给映射896MB以上的内存(High Memory)之用。内核通过三种不同的办法将896MB以上的物理内存映射到这128MB的线性地址空间中(permanent kernel mapping, temporary kernel mapping, and noncontiguous memory allocation),具体的实现方法可以参考ULK3的第八章。
在Linux处理内存的时候,定义了很多逻辑的概念,比如说page,zone, virtual memory area (VMA), mm_struct等等。每一个逻辑的概念都有对应的struct来支持。作为一个kernel module的开发者,最常用的就是在内核中申请/释放/使用内存空间,以及提供设备中的mmap实现以便让设备内存可以被映射。除了这些,还有一些底层的细节,比如说Linux的内存分配释放缓冲的机制(Buddy System, Slab)。这些底层实现是读Linux源码或者进行Linux裁减时必须要了解的东西,所以以后再来分析这些机制。
posted in Linux Kernel & Driver |