Linux内核内存申请的方式有哪些?

内核申请内存的接口,如下介绍。

kmalloc

该函数一般是用于内核申请小于page size的内存,分配的内存是物理连续的,至于kmalloc的具体实现,需要参考内核内存分配器配置的是slab、slob还是slub了。函数原型是void *kmalloc(size_t size, gfp_t flags),传递的参数,除了需要申请的内存大小以外,还有内存的类型。gfp_t flags通常使用下面的配置参数:

类型意义
GFP_ATOMIC不会导致休眠,可以在中断处理函数或者其他关闭中断的临界区调用。这个标识申请内存,如果系统内存不足,将会直接返回失败而不触发内存回收机制。
因为不能触发内存回收机制,通常用于分配较小的内存块,不适用于分配较大内存。
GFP_KERNEL通用的申请类型,该类型申请可能会存在休眠。一般较大内存的申请可使用该类型,内存不足时会触发直接回收。
GFP_KERNEL_ACCOUNT和GFP_KERNEL一样,只是说GFP_KERNEL_ACCOUNT在分配内存时会被kmemcg(‌Kernel Memory Controller,‌内核内存控制器)‌统计。
GFP_NOWAIT不会导致休眠,申请不到内存立刻返回错误
GFP_NOIO用于确保在内存分配过程中不进行任何I/O操作。‌这有助于在需要快速分配内存且不想在此过程中进行I/O操作的情况下使用。‌
GFP_NOFS用于确保在内存分配过程中不进行任何文件系统调用。‌这有助于在文件系统和IO堆栈的代码路径中避免递归死锁。‌
GFP_USER用于用户空间分配,也需要由内核或硬件直接访问。它通常被硬件用于映射到用户空间(例如图形)的缓冲区,硬件仍然可以对其进行DMA。
GFP_DMA标识从ZONE_DMA申请
GFP_DMA32与GFP_DMA一致,只是反馈32bit地址
GFP_HIGHUSER用于可能映射到用户空间的用户空间分配,不需要被内核直接访问,用于从高端内存中分配内存,‌这种内存一旦被分配给用户空间使用,‌就不能再被移动。‌
GFP_HIGHUSER_MOVABLE用于内核不需要直接访问但需要访问时可以使用kmap()的用户空间分配,它们可以通过页面回收或页面迁移来移动
GFP_TRANSHUGE_LIGHT用于THP分配。它们是复合分配,如果内存不可用,通常会很快失败,并且不会在失败时唤醒kswapd/kcompactd。
GFP_TRANSHUGE_LIGHT版本根本不尝试回收/压缩,默认情况下用于页面故障路径,而非light版本由khugepage使用。

vmalloc

用于申请虚拟地址连续、物理地址不连续的高端内存区域内存。函数原型是void *vmalloc(unsigned long size),仅需要传递需要申请的大小。因为vmalloc申请的是物理地址不连续的内存,访问的时候,需要经过IOMMU映射转换,才可以得到连续的虚拟地址内存。

kmalloc()和vmalloc()函数所分配的内存都处在内核空间,即从3GB~4GB;但是位置不一样,kmalloc()分配的内存处于3GB~high_memory之间,而vmalloc()所分配的内存在VMALLOC_START~4GB之间,也就是非连续内存区。
一般情况下,在驱动程序都是调用kmalloc()来给数据结构分配内存,而vmalloc()用在为活动的交换区分配数据结构,为某些I/O驱动程序分配缓冲区,或为模块分配空间。

vmalloc()比kmalloc()要慢?

  1. kmalloc()分配的物理地址与虚拟地址只有一个PAGE_OFFSET偏移,不需要为地址段修改页表;
  2. vmalloc()申请的物理内存地址与虚拟地址不是一一映射的关系,需要通过页表进行转换,所以每次分配都需要对页表进行设置,当然效率比较低;

最重要的,分配大小的问题:如果只是需要小内存,那么一定用kmalloc()。

kmalloc和vmalloc是Linux内核中用于内存分配的两种函数,它们之间存在一些关键的区别。以下是对这些区别的详细解释:

  1. 分配的内存类型与连续性
    • kmalloc:它分配的是物理内存,且保证所分配的内存在物理上是连续的。这种连续性对于某些需要直接访问物理地址或进行DMA(直接存储器访问)操作的硬件设备来说是必要的。
    • vmalloc:它分配的是虚拟内存,这意味着在虚拟地址空间上,所分配的内存是连续的。然而,在物理内存中,这些页可能不是连续的。vmalloc通过映射物理页到连续的虚拟地址空间来实现这种分配。
  2. 使用场景与大小限制
    • kmalloc:通常用于分配较小的内存块,如结构体或链表节点等。由于它在物理内存池中分配,因此其大小受到可用物理内存页的限制。
    • vmalloc:更适用于分配较大的内存块,如大型数组或缓冲区。由于它不要求物理内存的连续性,因此可以跨越多个物理页进行分配,从而满足大内存块的需求。
  3. 分配速度与性能
    • 一般而言,kmalloc的分配速度可能更快,因为它直接从内核的内存池中分配物理连续的内存。而vmalloc由于需要映射物理页到虚拟地址空间,并可能涉及页表的修改,因此其分配速度可能稍慢。
  4. 内存位置
    • kmalloc分配的内存位于低端内存区域(物理内存地址小于896MB),这部分内存可以直接被访问。
    • vmalloc分配的内存可以位于高端内存区域(物理内存地址大于896MB),这些内存需要通过内核映射才能够被访问。

综上所述,kmallocvmalloc的主要区别在于分配的内存类型(物理或虚拟)、连续性(物理或虚拟连续)、使用场景(小块或大块内存)、分配速度以及内存位置。在选择使用哪种分配函数时,需要根据具体的应用需求和性能考虑来做出决策。

alloc_pages

以page为单位的申请物理空间连续的内存,函数原型是struct page *alloc_pages(gfp_t gfp_mask, unsigned int order),参数order表示需要申请的page的数量,为 2order 个page。 alloc_pages是直接从内存管理器中获取页,而不是通过slab分配器。

ioremap

把已知未映射的物理地址映射到虚拟地址,比如设备寄存器地址,函数原型是void __iomem *ioremap(phys_addr_t offset, size_t size),主要传递物理地址和相应的大小,映射后,就可以像访问内存般访问外设寄存器。

dma_alloc_coherent

用于分配一块内存一致的内存,即在DMA操作中对CPU和DMA设备都是一致的,常用于dma操作的buffer,不带cache。函数原型是void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp),dev 标识设备结构体指针,用于指定哪个设备需要分配内存,而size 则是分配内存的大小,dma_handle 是返回的DMA地址,用于设备访问这个内存,gfp则是内存分配标识。

在使用dma buffer的时候,还会有一种使用方式,那就是应用申请了buffer,然后将buffer的fd传递到内核,内核需要通过fd获取dma_buf的句柄再进行访问,相应的一些调用如下:

/* 通过fd或者dma_buf的句柄 */
struct dma_buf *dmabuf = dma_buf_get(dmabuffer_fd);
/* 将dma_buf与设备关联起来,以便它能够进行DMA读写操作 */
struct dma_buf_attachment *attachment = dma_buf_attach(dmabuf, &pdev->dev);
/* 将dma buffer映射到设备 */
struct sg_table *sgt = dma_buf_map_attachment(attachment, DMA_BIDIRECTIONAL);
/* 获取dma buffer地址 */
dma_addr_t dma_address = sg_dma_address(sgt->sgl);

上面的这个操作,其中的dma_buf_attach关联设备,需要确认关联的设备是正确的,否则设备访问buffer的时候,会出现L1 PageTable Invalid以及xxx is not mapped!的报错,同时,在设备的dts中,也需要配置可访问iommu,增加iommus = <&mmu_aw 0 0>;的配置信息。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部