⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 (ldd) ch07-获取内存(转载).txt

📁 献给ARM初学者
💻 TXT
📖 第 1 页 / 共 2 页
字号:
(LDD) Ch07-获取内存(转载)
      7章 获取内存
       
       
      到目前为止,我们总是用kmalloc和kfree来进行内存分配。当然,只用这些函数的确是
      管理内存的捷径。本章将会介绍其他一些内存分配技术。但我们目前并不关心不同的体
      系结构实际上是如何进行内存管理的。因为内核为设备驱动程序提供了一致的接口,本
      章的模块都不必涉及分段,分页等问题。另外,本章我也不会介绍内存管理的内部细节
      ,这些问题将留到第13章“Mmap和DMA”的“Linux的内存管理”一节讨论。
       
      kmalloc函数的内幕
             kmalloc内存分配引擎功能强大,由于和malloc函数很相似,很容易就可以学会
      。这个函数运行得很快-一除非它被阻塞-一它不清零它获得的内存空间;分配给它的
      区域仍存放着原有的数据。在下面几节,我会详细介绍kmalloc函数,你可以将它和我后
      面要介绍的一些内存分配技术作个比较。
       

       
      优先权参数
             kmalloc函数的第一个参数是size(大小),我留在下个小节介绍。第二个参数,
      是优先权,更有意思,因为它会使得kmalloc函数在寻找空闲页较困难时改变它的行为。
       
       
       
             最常用的优先权是GFP_KERNEL,它的意思是该内存分配(内部是通过调用get_fre
      e_pages来实现的,所以名字中带GFP)是由运行在内核态的进程调用的。也就是说,调用
      它的函数属于某个进程的,使用GFP_KERNEL优先权允许kmalloc函数在系统空闲内存低于
      水平线min_free_pages时延迟分配函数的返回。当空闲内存太少时,kmalloc函数会使当
      前进程进入睡眠,等待空闲页的出现。
       
       
       
             新的页面可以通过以下几种途径获得。一种方法是换出其他页;因为对换需要时
      间,进程会等待它完成,这时内核可以调度执行其他的任务。因此,每个调用kmalloc(G
      FP_KERNEL)的内核函数都应该是可重入的。关于可重入的更多细节可见第5章“字符设备
      驱动程序的扩展操作”的“编写可重入的代码”一节。
       
       
       
             并非使用GFP_KERNEL优先权后一定正确;有时kmalloc是在进程上下文之外调用

             并非使用GFP_KERNEL优先权后一定正确;有时kmalloc是在进程上下文之外调用
      的-一比如,在中断处理,任务队列处理和内核定时器处理时发生。这些情况下,curre
      nt进程就不应该进入睡眠,这时应该就使用优先权GFP_ATOMIC。原子性(atomic)的内存
      分配允许使用内存的空闲位,而与min_free_pages值无关。实际上,这个最低水平线值
      的存在就是为了能满足原子性的请求。但由于内核并不允许通过换出数据或缩减文件系
      统缓冲区来满足这种分配请求,所以必须还有一些真正可以获得的空闲内存。
       
       
       
             为kmalloc还定义了其他一些优先权,但都不经常使用,其中一些只在内部的内
      存管理算法中使用。另一个值的注意的优先权是GFP_NFS,它会使得NFS文件系统缩减空
      闲列表到min_free_pages值以下。显然,为使驱动程序“更快”而用GFP_NFS优先权取代
      GFP_KERNEL优先权会降低整个系统的性能。
       
       
       
             除了这些常用的优先权,kmalloc还可以识别一个位域:GFP_DMA。GFP_DMA标志
      位要和GFP_KERNEL和GFP_ATOMIC优先权一起使用来分配用于直接内存访问(DMA)的内存页
      。我们将在第13章的“直接内存访问”一节讨论如何使用这个标志位。
       
      size参数
             系统物理内存的管理是由内核负责的,物理内存只能按页大小进行分配。这就需
      要一个面向页的分配技术以取得计算机内存管理上最大的灵活性。类似malloc函数的简

      要一个面向页的分配技术以取得计算机内存管理上最大的灵活性。类似malloc函数的简
      单的线性的分配技术不再有效了;在象Unix内核这样的面向页的系统中内存如果是线性
      分配的就很难维护。空洞的处理很快就会成为一个问题,会导致内存浪费,降低系统的
      性能。
       
       
       
             Linux是通过维护页面池来处理kmalloc的分配要求的,这样页面就可以很容易地
      放进或者取出页面池。为了能够满足超过PAGE_SIZE字节数大小的内存分配请求,fs/kma
      lloc.c文件维护页面簇的列表。每个页面簇都存放着连续若干页,可用于DMA分配。在这
      里我不介绍底层的实现细节,因为内部的数据结构可以在不影响分配语义和驱动程序代
      码的前提下加以改变。事实上,2.1.38版已经将kmalloc重新实现了。2.0版的内存分配
      实现代码可以参见文件mm/malloc.c,而新版的实现在文件mm/slab.c中。想了解2.0版实
      现的详情可参见第16章“内核代码的物理布局”的“分配和释放”一节。
       
       
       
             Linux所使用的分配策略的最终方案是,内核只能分配一些预定义的固定大小的
      字节数组。如果你申请任意大小的内存空间,那么很可能系统会多给你一点。
       
       
       
             这些预定义的内存大小一般“稍小于2的某次方”(而在更新的实现中系统管理的

             这些预定义的内存大小一般“稍小于2的某次方”(而在更新的实现中系统管理的
      内存大小恰好为2的各次方)。如果你能记住这一点,就可以更有效地使用内存了。例如
      ,如果在Linux 2.0上你需要一个2000字节左右的缓冲区,你最好还是申请2000字节,而
      不要申请2048字节。在低与2.1.38版的内核中,申请恰好是2的幂次的内存空间是最糟糕
      的情况了-内核会分配两倍于你申请空间大小的内存给你。这也就是为什么在示例程序s
      cull中每个单元(quantum)要用4000字节而不是4096字节的原因了。
       
       
       
             你可以从文件mm/malloc.c(或者mm/slab.c)得到预定义的分配块大小的确切数值
      ,但注意这些值可能在以后的版本中被改变。在当前的2.0版和2.1版的内核中,都可以
      用个小技巧-尽量分配小于4K字节的内存空间,但不能保证这种方法将来也是最优的。
       
       
       
             无论如何,Linux2.0中kmalloc函数可以分配的内存空间最大不能超过32个页-A
      lpha上的256KB或者Intel和其他体系结构上的128KB。2.1.38版和更新的内核中这个上限
      是128KB。如果你需要更多一些空间,那么有下面一些的更好的解决方法。
       
      get_free_page和相关函数
             如果模块需要分配大块的内存,那使用面向页的分配技术会更好。请求整页还有
      其他一些好处,后面第13章的“mmap设备驱动程序操作”一节将会介绍。
       

       
       
       
             分配页面可使用下面一些函数:
       
       
       
      l        get_free_page返回指向新页面的指针并将页面清零。
       
       
       
      l        __get_free_pages和get_free_page类似,但不清零页面。
       
       
       
      l        __get_free_pages返回一个指向大小为几个页的内存区域的第一个字节位置的
      指针,但也不清零这段内存区域。
       
       
       
      l        __get_dma_pages返回一个指向大小为几个页的内存区域的第一个字节位置的
      指针;这些页面在物理上是连续的,可用于DMA传输。
       

       
       
       
      这些函数的原型在Linux2.0中定义如下:
       
       
       
      unsigned long get_free_page(int priority);
       
      unsigned long __get_free_page(int priority);
       
      unsigned long __get_dma_pages(int priority, unsigned long order);
       
      unsigned long __get_free_pages(int priority, unsigned long order, int 
dma);
       
       
       
      实际上,除了__get_free_pages,这些函数或者是宏或者是最终调用了__get_free_page
      s的内联函数。
       
       
       
      当程序使用完分配给它的页面,就应该调用下面的函数。下面的第一个函数是个宏,其

      当程序使用完分配给它的页面,就应该调用下面的函数。下面的第一个函数是个宏,其
      中调用了第二个函数:
       
       
       
      void free_page(unsigned long addr);
       
      void free_pages(unsigned long addr, unsigned long order);
       
       
       
      如果你希望代码在1.2版和2.0版的Linux上都能运行,那最好还是不要直接使用函数__ge
      t_free_pages,因为它的调用方式在这两个版本间修改过2次。只使用函数get_free_pag
      e(和__get_free_page)更安全更可移植,而且也足够了。
       
       
       
      至于DMA,由于PC平台设计上的一些“特殊性”,要正确寻址ISA卡还有些问题。我在第1
      3章的“直接内存访问”一节中介绍DMA时,将只限于2.0版内核上的实现,以避免引入移
      植方面的问题。
       
       
       

       
      分配函数中的priority参数和kmalloc函数中含义是一样的。__get_free_pages函数中的
      dma参数是零或非零;如果不是零,那么对分配的页面簇可以进行DMA传输。order是你请
      求分配或释放的内存空间相对2的幂次(即log2N)。例如,如果需要1页,order为0;需要
      8页,order为3。如果order太大,分配就会失败。如果你释放的内存空间大小和分配得
      到的大小不同,那么有可能破坏内存映射。在Linux目前的版本中,order最大为5(相当
      于32个页)。总之,order越大,分配就越可能失败。
       
       
       
      这里值得强调的是,可以使用类似kmalloc函数中的priority参数调用get_free_pages和
      其他这些函数。某些情况下内存分配会失败,最经常的情形就是优先权为GFP_ATOMIC的
      时候。因此,调用这些函数的程序在分配出错时都应提供相应的的处理。
       
       
       
      我们已经说过,如果不怕冒险的话,你可以假定按优先权GFP_KERNEL调用kmalloc和底层
      的get_free_pages函数都不会失败。一般说来这是对的,但有时也未必:我那台忠实可
      靠的386,有着4MB的空闲的RAM,但当我运行一个"play-it-dangerous"(冒险)模块时却
      象疯了一样。除非你有足够的内存,想写个程序玩玩,否则我建议你总检查检查调用分
      配函数的结果。
       
       

       
       
      尽管kmalloc(GFP_KERNEL)在没有空闲内存时有时会失败,但内核总是尽可能满足该内存
      分配请求。因此,如果分配太多内存,系统的响应性能很容易就会降下来。例如,如果
      往scull设备写入大量数据,计算机可能就会死掉;为满足kmalloc分配请求而换出内存
      页,系统就会变得很慢。所有资源都被贪婪的设备所吞噬,计算机很快就变的无法使用
      了;因为此时已经无法为你的shell生成新的进程了。我没有在scull模块中提到这个问
      题,因为它只是个例子模块,并不能真的在多用户系统中使用。但作为一个编程者,你
      必须要小心,因为模块是特权代码,会带来系统的安全漏洞(比如说,很可能会造成DoS(
      "denail-of-service")安全漏洞)。
       
      使用一整页的scull: scullp
             至此,我们已经较完全地介绍了内存分配的原理,下面我会给出一些使用了页面
      分配技术的程序代码。scullp是scull模块的一个变种,它只实现了一个裸(bare)设备-
      持久性的内存区域。和scull不同,scullp使用页面分配技术来获取内存;scullp_order
      变量缺省为0,也可以在编译时或装载模块时指定。在Linux 1.2上编译的scullp设备在o
      rder大于零时会据绝被加载,原因我们前面已经说明过了。在Linux 1.2上,scullp模块
      只允许“安全的”单页的分配函数。
       
       
       
             尽管这是个实际的例子,但值的在这提到的只有两行代码,因为该设备其实只是
      分配和释放函数略加改动的scull设备。下面给出了分配和释放页面的代码行及其相关的

      分配和释放函数略加改动的scull设备。下面给出了分配和释放页面的代码行及其相关的
      上下文:
       
       
       
      /* 此处分配一个单位内存 */
       
       if (!dptr->data[s_pos]) {
       
            dptr->data[s_pos] = (void *)__get_free_pages(GFP_KERNEL,
      dptr->order,0);
       
            if (!dptr->data[s_pos])
       
                  return -ENOMEM;
       
      memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);
       
       
       
                  /* 这段代码释放所有分配单元 */
       
                  for (i = 0; i < qset; i++)

                  for (i = 0; i < qset; i++)
       
                      if (dptr->data[i])
       
                          free_pages((unsigned long)(dptr->data[i]), 
      dptr->order);
       
       
       
             从用户的角度看,可以感觉到的差异就是速度快了一些。我作了写测试,把4M字
      节的数据从scull0拷贝到scull1,然后再从scullp0拷贝到scullp1;结果表明内核空间处
      理器的使用率有所提高。
       
       
       
             但性能提高的并不多,因为kmalloc设计得也运行得很快。基于页的分配策略的
      优点实际不在速度上,而是更有效地使用了内存。按页分配不会浪费内存空间,而用kma
      lloc函数则会浪费一定数量的内存。事实上,你可能会回想起第5章的“所使用的数据结
      构”一节中我们已经提到过select_table用了__get_free_page函数。
       
       
       
             使用__get_free_page函数的最大优点是这些分配得到页面完全属于你,而且在
      理论上可以通过适当地调整页表将它们合并成一个线性区域。结果就允许用户进程对这

      理论上可以通过适当地调整页表将它们合并成一个线性区域。结果就允许用户进程对这
      些分配得到的不连续内存区域进行mmap。我将在第13章的“mmap设备驱动程序操作”一
      节中讨论mmap调用和页表的实现内幕。
       
      vmalloc和相关函数
             下面要介绍的内存分配函数是vmalloc,它分配虚拟地址空间的连续区域。尽管
      这段区域在物理上可能是不连续的(要访问其中的每个页面都必须独立地调用函数__get_
      free_page),内核却认为它们在地址上是连续的。分配的内存空间被映射进入内核数据
      段中,从用户空间是不可见的-这一点上与其他分配技术不同。vmalloc发生错误时返回
      0(NULL地址),成功时返回一个指向一个大小为size的线性地址空间的指针。
       
       
       
             该函数及其相关函数的原型如下:
       
       
       
             void* vmalloc(unsigned long size);
       
             void vfree(void* addr);
       
             void* vremap(unsigned long offset, unsigned long size);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -