栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux内存从0到1学习笔记(九,内存优化调试之二 - DMA/CMA)

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux内存从0到1学习笔记(九,内存优化调试之二 - DMA/CMA)

一、DMA

DMA是一种无需CPU参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而提高系统的吞吐率。DMA通常与硬件体系结构,特别是外设的总线技术密切相关。

1.1 CPU方式数据传输

通过CPU方式的数据传输,分为轮询(CPU不断查询外设接口数据准备情况或接收情况)、中断(外设发出请求CPU暂停保存现场后执行中断程序传送数据)占用CPU计算周期。如下:

 摘至:原来 8 张图,就可以搞懂「零拷贝」了

  • CPU 发出对应的指令给磁盘控制器,然后返回;

  • 磁盘控制器收到指令后,于是就开始准备数据,会把数据放入到磁盘控制器的内部缓冲区中,然后产生一个中断;

  • CPU 收到中断信号后,停下手头的工作,接着把磁盘控制器的缓冲区的数据一次一个字节地读进自己的寄存器,然后再把寄存器里的数据写入到内存,而在数据传输的期间 CPU 是无法执行其他任务的。

1.2 DMA方式数据传输

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 的信号,知道数据已经准备好,于是将数据从内核拷贝到用户空间,系统调用返回;

1.3 DMA的组成部分

如下图:

二、CMA

Contiguous Memory Allocator, CMA,连续内存分配器,是Linux Kernel内存初始化时预留一块的连续内存,用于分配连续的大块内存。主要是在内存碎片化严重时通过调用dma_alloc_contiguous接口并且gfp指定为__GFP_DIRECT_RECLAIM从预留的那块连续内存中分配大块连续内存。

由于一些设备不支持散点图和IO映射,因此需要连续的内存块才能运行。如GPU,Camera,HDMI,硬件视频编解码器等设备都需要预留大量连续内存。此类设备通常需要大的内存缓冲区(例如,全高清帧的大小大于2兆像素,即,内存大小超过6 MB),这使得kmalloc()之类的机制无效。

CMA分配器,会Reserve一片物理内存区域:

  • 设备驱动不用时,内存管理系统将该区域用于分配和管理可移动类型页面;
  • 设备驱动使用时,用于连续内存分配,此时已经分配的页面需要进行迁移;

三、CMA与DMA的关系

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剩余内存
4.2 CMA内存占用分解 - /sys/kernel/debug/dma-buf/bufinfo

其他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
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/828401.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号