[Linux]How to alloc memory (kmalloc) in Linux ISR

cat /proc/buddyinfolinux

分类: LINUX Make sure you're familiar with GFP_KERNEL and GFP_ATOMIC. There is a certain amount of memory reserved for ISRs etc. If you acquire it using GFP_ATOMIC, the kernel guarantees that it doesn't suspend your ISR. Acquiring memory with kmalloc(size,GFP_KERNEL), it would possibly suspend your ISR until enough memory is available (via page fault). Since your ISR probably blocks most of the other processes and even other ISRs, it could easily happen that your system locks up.程序员

Therefore it's a very good idea to never acquire memory via an ISR. There shouldn't be a need for it in a carefully designed driver. There are different approaches to overcome this issue:算法

  1. usually your driver is controlled by a user process (or the scheduler workqueue) and you can safely acquire memory.
  2. If not, you might want to install a scheduled process which pends on a flag, and you can delegate memory allocation to such a safe process, just by talking to it from the ISR and set/clear this flag.

最经常使用的标志是GFP_KERNEL, GFP_ATOMIC。其次是__GFP_DMA和__GFP_HIGHMEM。其中GFP的意思就是get free page。GFP_KERNEL和GFP_ATOMIC的区别在于前者使用在进程上下文中,后者使用在进程上下文以外,例如中断上下文。因此前者可能在没有空闲内存时使进程休眠,然后者是不能休眠的。shell

到目前为止,咱们老是用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章“字符设备 驱动程序的扩展操做”的“编写可重入的代码”一节。app

并不是使用GFP_KERNEL优先权后必定正确;有时kmalloc是在进程上下文以外调用

的-一好比,在中断处理,任务队列处理和内核定时器处理时发生。这些状况下,curre nt进程就不该该进入睡眠,这时应该就使用优先权GFP_ATOMIC。原子性(atomic)的内存 分配容许使用内存的空闲位,而与min_free_pages值无关。实际上,这个最低水平线值 的存在就是为了能知足原子性的请求。但因为内核并不容许经过换出数据或缩减文件系 统缓冲区来知足这种分配请求,因此必须还有一些真正能够得到的空闲内存。ide

为kmalloc还定义了其余一些优先权,但都不常用,其中一些只在内部的内

存管理算法中使用。另外一个值的注意的优先权是GFP_NFS,它会使得NFS文件系统缩减空 闲列表到min_free_pages值如下。显然,为使驱动程序“更快”而用GFP_NFS优先权取代 GFP_KERNEL优先权会下降整个系统的性能。

除了这些经常使用的优先权,kmalloc还能够识别一个位域:GFP_DMA。GFP_DMA标志

位要和GFP_KERNEL和GFP_ATOMIC优先权一块儿使用来分配用于直接内存访问(DMA)的内存页 。咱们将在第13章的“直接内存访问”一节讨论如何使用这个标志位。

size参数 系统物理内存的管理是由内核负责的,物理内存只能按页大小进行分配。这就需 要一个面向页的分配技术以取得计算机内存管理上最大的灵活性。相似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的各次方)。若是你能记住这一点,就能够更有效地使用内存了。例如 ,若是在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设备。下面给出了分配和释放页面的代码行及其相关的 上下文:

/* 此处分配一个单位内存 */

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++)

            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);



   注意在2.1版内核中vremap已经被重命名为ioremap。并且,Linux 2.1引入了一

个新的头文件,<linux/vmalloc.h>,使用vmalloc时应将它包含进来。

与其余内存分配函数不一样的是,vmalloc返回很“高”的地址值-这些地址要高

于物理内存的顶部。因为vmalloc对页表调整后容许用连续的“高”地址访问分配获得的 页面,所以处理器是能够访问返回获得的内存区域的。内核能和其余地址同样地使用vma lloc返回的地址,但程序中用到的这个地址与地址总线上的地址并不相同。

用vmalloc分配获得的地址是不能在微处理器以外使用的,由于它们只有在处理

器的分页单元之上才有意义。但驱动程序须要真正的物理地址时(象外设用以驱动系统总 线的DMA地址),你就不能使用vmalloc了。正确使用vmalloc函数的场合是为软件分配一 大块连续的用于缓冲的内存区域。注意vmalloc的开销要比__get_free_pages大,由于它 处理获取内存还要创建页表。所以,不值得用vmalloc函数只分配一页的内存空间。

使用vmalloc函数的一个例子函数是create_module系统调用,它利用vmalloc函

数来获取被建立模块须要的内存空间。而在insmod调用重定位模块代码后,将会调用mem cpy_fromfs函数把模块自己拷贝进分配而得的空间内。

用vmalloc分配获得的内存空间用vfree函数来释放,这就象要用kfree函数来释

放kmalloc函数分配获得的内存空间。

和vmalloc同样,vremap(或ioremap)也创建新的页表,但和vmalloc不一样的是,v

remap实际上并不分配内存。vremap的返回值是个虚拟地址,能够用来访问指定的物理内 存区域;获得的这个虚拟地址最后要调用vfree来释放掉。

vremap用于将高内存空间的PCI缓冲区映射到用户空间。例如,若是VGA设备的帧缓冲区 被映射到地址0xf0000000(典型的一个值)后,vremap就能够创建正确的页表让处理机可 以访问。而系统初始化时创建的页表只是用于访问低于物理地址空间的内存区域。系统 的初始化过程并不检测PCI缓冲区,而是由各个驱动程序本身负责管理本身的缓冲区;PC I的细节将在第15章“外设总线概貌”的“PCI接口”一节中讨论。另外,你没必要重映射 低于1MB的ISA内存区域,由于这段内存空间可用其余方法访问,参见第8章“硬件管理” 的“访问设备卡上的内存”一节。

若是你但愿驱动程序能在不一样的平台间移植,那么使用vremap时就要当心。在一

些平台上是不能直接将PCI内存区域映射处处理机的地址空间的,例如Alpha上就不行。 此时你就不能象普通内存区域那样地对重映射区域进行访问,你要用readb函数或者其余 一些I/O函数(可参见第8章的“1M内存空间之上的ISA内存”一节)。这套函数能够在不一样 平台间移植。

对vmalloc和vremap函数可分配的内存空间大小并无什么限制,但为了能检测

到程序员的犯下的一些错误,vmalloc不容许分配超过物理内存大小的内存空间。可是记 着,vmalloc函数请求过多的内存空间会产生一些和调用kmalloc函数时相同的问题。

vremap和vmalloc函数都是面向页的(它们都会修改页表);所以分配或释放的内

存空间实际上都会上调为最近的一个页边界。并且,vremap函数并不考虑如何重映射不 是页边界的物理地址。

vmalloc函数的一个小缺点是它不能在中断时间内使用,由于它的内部实现调用

了kmalloc(GFP_KERNEL)来获取页表的存储空间。但这不是什么问题-若是__get_free_p age函数都还不能知足你的中断处理程序的话,那你仍是先修改一下你的软件设计吧。

使用虚拟地址的scull: scullv 使用了vmalloc的示例程序是scullv模块。正如scullp,这个模块也是scull的一 个变种,只是使用了不一样的分配函数来获取设备用以储存数据的内存空间。

该模块每次分配16页的内存(在Alpha上是128KB,x86上是64KB)。这里内存分配

用了较大的数据块,目的是获取比scullp更好的性能,而且代表此时使用其余可行的分 配技术相对来讲会更耗时。用__get_free_pages函数来分配一页以上的内存空间容易出 错,并且即便成功了,也相对较慢。前面咱们已经看到,用vmalloc分配若干页比其余函 数要快一些,但因为存在创建页表的开销,只分配一页时却会慢一些。scullv设计得和s cullp很类似。order参数指定分配的内存空间的“幂”,缺省为4。scullv和scullp的惟 一差异在下面一段代码:

/* 此处用虚拟地址来分配一个单位内存 */

if (!dptr->data[s_pos]) {

dptr->data[s_pos] = (void *)vmalloc(PAGE_SIZE <<order);

  if (!dptr->data[s_pos])

        return -ENOMEM;



        /* 这段代码释放全部分配单元 */

        for (i = 0; i < qset; i++)

            if (dptr->data[i])

                vfree (dptr->data[i]);

若是你在编译这两个模块时都打开了调试开关,就能够经过读它们在/proc下建立的文件 来查看它们进行的数据分配。下面的快照取自个人计算机,我机器的物理地址是从0到0x 1800000(共24MB):

morgana.root# cp /bin/cp /dev/scullp0

morgana.root# cat /proc/scullpmem

Device 0: qset 500, order 0, sz 19652

Item at 0063e598, qset at 006eb018

0: 150e000

     1:  de6000

     2: 10ca000

     3:  e19000

     4:  bd1000

morgana.root# cp /zImage.last /dev/scullv0

morgana.root# cat /proc/scullvmem

Device 0: qset 500, order 4, sz 289840

Item at 0063ec98, qset at 00b3e810

0: 2034000

     1: 2045000

     2: 2056000

     3: 2067000

     4: 2078000



   从这些值能够看到,scullp分配物理地址(小于0x1800000),而scullv分配虚拟

地址(但注意实际数值与Linux 2.1会不一样,由于虚拟地址空间的组织形式变了-见第17 章“近期发展”的“虚拟内存”一节)。

“脏”的处理方法(Playing Dirty) 若是你确实须要大量的连续的内存用做缓冲区,最简单的(也是最不灵活的,但 也最容易出错的)方法是在系统启动时分配。显然,模块不能在启动时分配内存;只有直 接连到内核的设备驱动程序才能运行这种“脏”的处理方式,在启动时分配内存。

尽管在启动时就进行分配彷佛是得到大量内存缓冲区的惟一方法,但我还会在第

13章的“分配DMA缓冲区”一节中介绍到另外一种分配技术(虽然可能更很差)。在启动时分 配缓冲区有点“脏”,由于它跳过了内核内存管理机制。并且,这种技术普通用户没法 使用,由于它要修改内核。绝大多数用户仍是愿意装载模块,而并不肯意对内核打补定 或从新编译内核。尽管我不推荐你使用这种“分配技术”,但它仍是值得在此说起的, 由于在GFP_DMA被引入以前,这种技术曾是Linux的早期版本里分配可用于DAM传输的缓冲 区的惟一方法。

让咱们先看看启动是是如何进行分配的。内核启动时,它能够访问系统全部的内

存空间。而后以空闲内存区域的边界做为参数,调用内核的各个子系统的初始化函数进 行初始化。每一个初始化函数均可以“偷取”一部分空闲区域,并返回新的空闲内存下界 。因为驱动程序是在系统启动时进行内存分配的,因此能够从空闲RAM的线性数组获取连 续的内存空间。

除了不能释放获得的缓冲区,这种内存分配技术还有些缺点。驱动程序获得这些

内存页后,就没法将它们再放到空闲页面池中了;页面池是在已经物理内存的分配结束 后才创建起来的,并且我也不推荐象这样“黑客”内存管理的内部数据结构。但另外一方 面,这种技术的优点是,它能够获取用于DMA传输等用途的一段连续区域。目前这也是分 配超过32页的连续内存缓冲区的惟一的“安全”的方式,32页这个值是源于get_free_pa ges函数参数order可取的最大值为5。可是若是你须要的多个内存页能够是物理上不连续 的,最好仍是用vmalloc函数。

若是你真要在启动时获取内存的话,你必须修改内核代码中的init/main.c文件

。关于main.c文件的更多细节可参见第16章和第8章的“1M内存空间之上的ISA内存”一 节。

注意,这种“分配”只能是按页面大小的倍数进行,而页面数没必要是2的某个幂

次。

快速参考 与内存分配有关的函数和符号列在下面:

#include <linux/malloc.h>

void *kmalloc(unsigned int size, int priority);

void kfree(void *obj);

这两个函数是最经常使用的内存分配函数。

#include <linux/mm.h>

GFP_KERNEL

GFP_ATOMIC

GFP_DMA

kmalloc函数的优先权。GFP_DMA是个标志位,能够与GFP_KERNEL和/或GFP_ATOMIC相或。

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);

这些都是面向页的内存分配函数。如下划线开头的函数不清零分配而得的页。只有前两 个函数是在1.2版和2.0版的Linux间可移植的,然后二者在1.2版与2.0版中的行为并不一样 。

void free_page(unsigned long addr);

void free_pages(unsigned long addr, unsigned long order);

这些函数用于释放面向页的分配获得的内存空间。

void* vmalloc(unsigned long size);

void* vremap(unsigned long offset, unsigned long size);

void vfree(void* addr);

这些函数分配或释放连续的虚拟地址空间。vremap用虚拟地址访问物理内存(在2.1版的L inux中被称为ioremap),而vmalloc是用来分配空闲页面。两种状况下,都是用vfree来 释放分配的内存页。2.1版的Linux引入了头文件<linux/vmalloc.h>,使用这些函数时必 须先包含(#include)这个头文件。

相关文章
相关标签/搜索