DMA是一种无需CPU参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而提高系统的吞吐率。DMA通常与硬件体系结构,特别是外设的总线技术密切相关。
1.1 CPU方式数据传输通过CPU方式的数据传输,分为轮询(CPU不断查询外设接口数据准备情况或接收情况)、中断(外设发出请求CPU暂停保存现场后执行中断程序传送数据)占用CPU计算周期。如下:
摘至:原来 8 张图,就可以搞懂「零拷贝」了
-
CPU 发出对应的指令给磁盘控制器,然后返回;
-
磁盘控制器收到指令后,于是就开始准备数据,会把数据放入到磁盘控制器的内部缓冲区中,然后产生一个中断;
-
CPU 收到中断信号后,停下手头的工作,接着把磁盘控制器的缓冲区的数据一次一个字节地读进自己的寄存器,然后再把寄存器里的数据写入到内存,而在数据传输的期间 CPU 是无法执行其他任务的。
DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发执行其他任务。当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后续处理。
摘至:原来 8 张图,就可以搞懂「零拷贝」了
-
用户进程调用 read 方法,向操作系统发出 I/O 请求,请求读取数据到自己的内存缓冲区中,进程进入阻塞状态;
-
操作系统收到请求后,进一步将 I/O 请求发送 DMA,然后让 CPU 执行其他任务;
-
DMA 进一步将 I/O 请求发送给磁盘;
-
磁盘收到 DMA 的 I/O 请求,把数据从磁盘读取到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被读满后,向 DMA 发起中断信号,告知自己缓冲区已满;
-
DMA 收到磁盘的信号,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用 CPU,CPU 可以执行其他任务;
-
当 DMA 读取了足够多的数据,就会发送中断信号给 CPU;
-
CPU 收到 DMA 的信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回;
如下图:
二、CMAContiguous Memory Allocator, CMA,连续内存分配器,是Linux Kernel内存初始化时预留一块的连续内存,用于分配连续的大块内存。主要是在内存碎片化严重时通过调用dma_alloc_contiguous接口并且gfp指定为__GFP_DIRECT_RECLAIM从预留的那块连续内存中分配大块连续内存。
由于一些设备不支持散点图和IO映射,因此需要连续的内存块才能运行。如GPU,Camera,HDMI,硬件视频编解码器等设备都需要预留大量连续内存。此类设备通常需要大的内存缓冲区(例如,全高清帧的大小大于2兆像素,即,内存大小超过6 MB),这使得kmalloc()之类的机制无效。
CMA分配器,会Reserve一片物理内存区域:
- 设备驱动不用时,内存管理系统将该区域用于分配和管理可移动类型页面;
- 设备驱动使用时,用于连续内存分配,此时已经分配的页面需要进行迁移;
CMA通常被集成到DMA子系统,所以以前调用DMA API(例如dma_alloc_coherent())的地方应该照常工作。事实上,设备驱动永远不需要直接调用CMA API,因为它是在页和页帧编号(PFNs)上操作而无关总线地址和内核映射,并且也不提供维护缓存一致性的机制。
在内核启动期间,当dma_congiguous_reserve()和/或者dma_declare_contiguous()方法被调用的时候,CMA在memblock中预留一部分RAM,并在随后将其返还给伙伴系统,仅将其页面块的迁移类型置为MIGRATE_CMA。最终的结果是所有预留的页都在伙伴系统里, 所以它们都可以用于可移动页的分配 。
3.1 CMA分配内存CMA分配内存的调用链路如下:
dma_alloc_from_contiguous() --> cma_alloc() --> alloc_contig_range();
linux_mainline-5.17.0/kernel/dma/contiguous.c
245
257 struct page *dma_alloc_from_contiguous(struct device *dev, size_t count,
258 unsigned int align, bool no_warn)
259 {
260 if (align > CONFIG_CMA_ALIGNMENT)
261 align = CONFIG_CMA_ALIGNMENT;
262
263 return cma_alloc(dev_get_cma_area(dev), count, align, no_warn);
264 }
linux_mainline-5.17.0/mm/page_alloc.c
416
426 struct page *cma_alloc(struct cma *cma, unsigned long count,
427 unsigned int align, bool no_warn)
428 {
429 unsigned long mask, offset;
430 unsigned long pfn = -1;
431 unsigned long start = 0;
432 unsigned long bitmap_maxno, bitmap_no, bitmap_count;
433 unsigned long i;
434 struct page *page = NULL;
435 int ret = -ENOMEM;
436
437 if (!cma || !cma->count || !cma->bitmap)
438 goto out;
439
440 pr_debug("%s(cma %p, count %lu, align %d)n", __func__, (void *)cma,
441 count, align);
442
443 if (!count)
444 goto out;
445
446 trace_cma_alloc_start(cma->name, count, align);
447
448 mask = cma_bitmap_aligned_mask(cma, align);
449 offset = cma_bitmap_aligned_offset(cma, align);
450 bitmap_maxno = cma_bitmap_maxno(cma);
451 bitmap_count = cma_bitmap_pages_to_bits(cma, count);
452
453 if (bitmap_count > bitmap_maxno)
454 goto out;
455
456 for (;;) {
457 spin_lock_irq(&cma->lock);
458 bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap,
459 bitmap_maxno, start, bitmap_count, mask,
460 offset);
461 if (bitmap_no >= bitmap_maxno) {
462 spin_unlock_irq(&cma->lock);
463 break;
464 }
465 bitmap_set(cma->bitmap, bitmap_no, bitmap_count);
466
471 spin_unlock_irq(&cma->lock);
472
473 pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit);
474 ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA,
475 GFP_KERNEL | (no_warn ? __GFP_NOWARN : 0));
476
477 if (ret == 0) {
478 page = pfn_to_page(pfn);
479 break;
480 }
481
482 cma_clear_bitmap(cma, pfn, count);
483 if (ret != -EBUSY)
484 break;
485
486 pr_debug("%s(): memory range at %p is busy, retryingn",
487 __func__, pfn_to_page(pfn));
488
489 trace_cma_alloc_busy_retry(cma->name, pfn, pfn_to_page(pfn),
490 count, align);
491
492 start = bitmap_no + mask + 1;
493 }
494
495 trace_cma_alloc_finish(cma->name, pfn, page, count, align);
496
497
502 if (page) {
503 for (i = 0; i < count; i++)
504 page_kasan_tag_reset(page + i);
505 }
506
507 if (ret && !no_warn) {
508 pr_err_ratelimited("%s: %s: alloc failed, req-size: %lu pages, ret: %dn",
509 __func__, cma->name, count, ret);
510 cma_debug_show_areas(cma);
511 }
512
513 pr_debug("%s(): returned %pn", __func__, page);
514 out:
515 if (page) {
516 count_vm_event(CMA_ALLOC_SUCCESS);
517 cma_sysfs_account_success_pages(cma, count);
518 } else {
519 count_vm_event(CMA_ALLOC_FAIL);
520 if (cma)
521 cma_sysfs_account_fail_pages(cma, count);
522 }
523
524 return page;
525 }
linux_mainline-5.17.0/mm/page_alloc.c
9079
9100 int alloc_contig_range(unsigned long start, unsigned long end,
9101 unsigned migratetype, gfp_t gfp_mask)
9102 {
9103 unsigned long outer_start, outer_end;
9104 unsigned int order;
9105 int ret = 0;
9106
9107 struct compact_control cc = {
9108 .nr_migratepages = 0,
9109 .order = -1,
9110 .zone = page_zone(pfn_to_page(start)),
9111 .mode = MIGRATE_SYNC,
9112 .ignore_skip_hint = true,
9113 .no_set_skip_hint = true,
9114 .gfp_mask = current_gfp_context(gfp_mask),
9115 .alloc_contig = true,
9116 };
9117 INIT_LIST_HEAD(&cc.migratepages);
9118
9119
9142
9143 ret = start_isolate_page_range(pfn_max_align_down(start),
9144 pfn_max_align_up(end), migratetype, 0);
9145 if (ret)
9146 return ret;
9147
9148 drain_all_pages(cc.zone);
9149
9150
9160 ret = __alloc_contig_migrate_range(&cc, start, end);
9161 if (ret && ret != -EBUSY)
9162 goto done;
9163 ret = 0;
9164
9165
9181
9182 order = 0;
9183 outer_start = start;
9184 while (!PageBuddy(pfn_to_page(outer_start))) {
9185 if (++order >= MAX_ORDER) {
9186 outer_start = start;
9187 break;
9188 }
9189 outer_start &= ~0UL << order;
9190 }
9191
9192 if (outer_start != start) {
9193 order = buddy_order(pfn_to_page(outer_start));
9194
9195
9201 if (outer_start + (1UL << order) <= start)
9202 outer_start = start;
9203 }
9204
9205
9206 if (test_pages_isolated(outer_start, end, 0)) {
9207 ret = -EBUSY;
9208 goto done;
9209 }
9210
9211
9212 outer_end = isolate_freepages_range(&cc, outer_start, end);
9213 if (!outer_end) {
9214 ret = -EBUSY;
9215 goto done;
9216 }
9217
9218
9219 if (start != outer_start)
9220 free_contig_range(outer_start, start - outer_start);
9221 if (end != outer_end)
9222 free_contig_range(end, outer_end - end);
9223
9224 done:
9225 undo_isolate_page_range(pfn_max_align_down(start),
9226 pfn_max_align_up(end), migratetype);
9227 return ret;
9228 }
9229 EXPORT_SYMBOL(alloc_contig_range);
3.2 CMA释放内存
cma释放调用链路,如下:
dma_release_from_contiguous() --> cma_release() --> free_contig_range();
最终free_contig_range函数迭代所有的页面并将其返还给伙伴系统。
linux_mainline-5.17.0/kernel/dma/contiguous.c
266
276 bool dma_release_from_contiguous(struct device *dev, struct page *pages,
277 int count)
278 {
279 return cma_release(dev_get_cma_area(dev), pages, count);
280 }
281
546
556 bool cma_release(struct cma *cma, const struct page *pages,
557 unsigned long count)
558 {
559 unsigned long pfn;
560
561 if (!cma_pages_valid(cma, pages, count))
562 return false;
563
564 pr_debug("%s(page %p, count %lu)n", __func__, (void *)pages, count);
565
566 pfn = page_to_pfn(pages);
567
568 VM_BUG_ON(pfn + count > cma->base_pfn + cma->count);
569
570 free_contig_range(pfn, count);
571 cma_clear_bitmap(cma, pfn, count);
572 trace_cma_release(cma->name, pfn, pages, count);
573
574 return true;
575 }
576
9327 void free_contig_range(unsigned long pfn, unsigned long nr_pages)
9328 {
9329 unsigned long count = 0;
9330
9331 for (; nr_pages--; pfn++) {
9332 struct page *page = pfn_to_page(pfn);
9333
9334 count += page_count(page) != 1;
9335 __free_page(page);
9336 }
9337 WARN(count != 0, "%lu pages are still in use!n", count);
9338 }
9339 EXPORT_SYMBOL(free_contig_range);
四、CMA内存占用拆解调试
4.1 CMA内存使用概况 - /proc/meminfo
bill@bill-VirtualBox:~$ cat /proc/meminfo
MemTotal: 4026388 kB
MemFree: 2567448 kB
MemAvailable: 3205968 kB
Buffers: 58916 kB
Cached: 775540 kB
SwapCached: 0 kB
Active: 725812 kB
Inactive: 572904 kB
Active(anon): 461940 kB
Inactive(anon): 18792 kB
Active(file): 263872 kB
Inactive(file): 554112 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 1942896 kB
SwapFree: 1942896 kB
Dirty: 8152 kB
Writeback: 0 kB
AnonPages: 464256 kB
Mapped: 233624 kB
Shmem: 19676 kB
KReclaimable: 46880 kB
Slab: 87576 kB
SReclaimable: 46880 kB
SUnreclaim: 40696 kB
KernelStack: 6704 kB
PageTables: 26368 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 3956088 kB
Committed_AS: 3108644 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 32860 kB
VmallocChunk: 0 kB
Percpu: 360 kB
HardwareCorrupted: 0 kB
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
FileHugePages: 0 kB
FilePmdMapped: 0 kB
CmaTotal: 0 kB
CmaFree: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
DirectMap4k: 112576 kB
DirectMap2M: 4081664 kB
- CmaTotal; CMA预留内存总量
- CmaFree; CMA剩余内存
其他kernel版本的数据参考如下:
Dma-buf Objects: size flags mode count exp_name 00004096 00000000 00000005 00000001 exporter_dummy Attached Devices: Total 0 devices attached Total 1 objects, 4096 bytes



