kernel 内存 I/O
来源:互联网 发布:软件导刊是核心吗 编辑:程序博客网 时间:2024/04/27 20:30
内存 I/O
内存管理单元
MMU辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和cache缓存控制等硬件支持
TLB(translation lookaside buffer):转换旁路缓存,TLB是MMU的核心部件,它缓存少量的虚拟地址与物理地址的转换关系,是转换表的cache
TTW(translation table walk):转换表漫游,当TLB中没有缓冲对应的地址转换关系,需要通过对内存转换表的访问来获得
内存管理
内核地址空间 划分为 物理内存映射区、虚拟内存分配器区、高端页面映射区、专用页面映射区、系统保留映射区。
物理内存映射区:最大长度为896MB,系统的物理内存被顺序映射在内核空间的这个区域中。在低于16MB的区域,ISA设备可以做DMA,DMA区域;16MB~896MB之间为常规区域。
高端页面映射区:当系统物理内存大于896MB时,超过物理内存映射区的那部分内存称为高端内存。
系统保留映射区:linux保留内核空间最顶部的区域作为保留区。
虚拟内存分配区:用于 vmalloc() 函数,它的前部与物理内存映射区有一个隔离带,后部与高端映射区也有一个隔离带。
virt_to_phys() 和 phy_to_virt() 仅适用于DMA和常规区域,高端内存的虚拟地址与物理地址之间不存在如此简单的换算关系
buddy算法
linux最底层的内存申请都是以 2^n 为单位,避免外部碎片,任何时候区域里的空闲内存都能以2的n次方进行拆分或合并
内存存取
linux内核采用按需调页,因此当malloc()成功返回,但是内核并没有真正给这个进程内存,这个时候如果去读申请的内存,内容全为0,这个页面的映射是只读的。只有当写到页面时,内核才在页错误后,真正分配页给进程
kmalloc & __get_free_pages
kmalloc() 和 __get_free_pages() 申请的内存位于DMA和常规区域的映射区,物理上也是连续的,与真实的物理地址只有一个固定偏移
GFP_KERNEL
若暂时不能满足,则进程会睡眠等待页,引起阻塞,因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNELGFP_ATOMIC
若不存在空闲页,则不等待,直接返回GFP_USER
用来为用户空间页分配内存,可能阻塞GFP_HIGHUSER
类似GFP_USER,但是从高端内存分配GFP_DMA
从DMA区域分配内存__GFP_COLD
请求一个较长时间不访问的页__GFP_HIGH
高优先级请求,允许获得被内核保留给紧急状况使用的最后内存页
vmalloc
vmalloc() 在虚拟内存空间给出一块连续内存区,实际物理内存并不一定连续
vmalloc() 不能用在原子上下文中,因为内部实现标志为GFP_KERNEL的 kmalloc()
slab机制
建立于buddy算法之上,进行二次管理。slab申请的内存与物理内存之间是一个简单的线性偏移
static kmem_cache_t *xxx_cachep;xxx_cachep = kmem_cache_create("xxx", sizeof(struct xxx), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);struct xxx *ctx;ctx = lmem_cache_alloc(xxx_cachep, GFP_KERNEL);...kmem_cache_free(xxx_cachep, ctx);kmem_cache_destroy(xxx_cachep);
IO
IO内存访问流程
request_mem_region() 申请资源
ioremap() 映射到内核空间虚拟地址
readb()/readw()/readl()/writeb()
iounmap()
release_mem_region()
内存映射
remap_pfn_range() 创建页表项
static int xxx_mmap(struct file *filp, struct vm_area_struct *vma){ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; vma->vm_ops = &xxx_remap_vm_ops; xxx_vma_open(vma); return 0;}static void xxx_vma_open(struct vm_area_struct *vma){ ... printk("xxx VMA open, virt %lx, phys %lx\n", vma->vm_start, vma->vm_pgoff << PAGE_SHIFT);}static void xxx_vma_close(struct vm_area_struct *vma){ ... printk("xxx VMA close.\n");}static struct vm_operations_struct xxx_remap_vm_ops = { .open = xxx_vma_open, .close = xxx_vma_close, ...};
fault() 函数
1. 找到缺页的虚拟地址所在的VMA
2. 如果必要,分配中间页目录表和页表
3. 如果页表项对应的物理页面不存在,则调用这个VMA的 fault() 方法
4. 将物理页面的地址填充到页表中
static int xxx_fault(struct vm_area_struct *vma, struct vm_fault *vmf){ unsigned long paddr; unsigned long pfn; pgoff_t index = vmf->pgoff; struct vma_data *vdata = vma->vm_private_data; ... pfn = paddr >> PAGE_SHIFT; vm_insert_pfn(vma, (unsigned long)vmf->virtual_address, pfn); return VM_FAULT_NOPAGE; }
对于显示、视频等设备,建立映射可减少用户空间和内核空间之间的内存复制
DMA
内存中用于与外设交互数据的一块区域称为DMA缓冲区,在设备不支持scatter/gather操作的情况下,DMA缓冲区在物理上必须是连续的
- 基于DMA的硬件使用的是总线地址而不是物理地址,总线地址是从设备角度看到的内存地址
- 物理地址 是从CPU MMU控制器外围角度上看到的内存地址
- 虚拟地址 是CPU核角度看到的内存地址
DMA 映射包括两方面:
1. 分配DMA缓冲区
2. 为该缓冲区产生设备可访问的总线地址
一致性DMA缓冲区
dma_alloc_coherent() 申请一片DMA缓冲区,以进行地址映射并保证该DMA缓冲区的cache一致性, dma_alloc_writecombine() 分配写合并的DMA缓冲区
dma_free_coherent() 释放DMA缓冲区(unmap)
流式DMA映射
流式DMA映射操作在本质上大多进行cache的 flush或invalidate ,解决cache一致性问题
dma_map_single() dma_unmap_single()
通常情况下,设备驱动不应该访问unmap的流式DMA缓冲区,如果一定要这么做,应先获得DMA缓冲区的拥有权:
dma_sync_single_for_cpu()
在驱动访问完DMA缓冲区后,应将其所有权返还给设备:
dma_sync_single_for_device()
如果设备要求较大的DMA缓冲区,在其支持SG模式的情况下,申请多个相对较小的不连续DMA缓冲区通常是防止申请太大的连续物理空间的方法
dma_map_sg() dma_unmap_sg()
SG映射属于流式DMA映射,如果设备驱动一定要访问映射情况下的SG缓冲区,应先获得DMA缓冲区的拥有权:
dma_sync_sg_for_cpu()
访问完后,将所有权返回给设备:
dma_sync_sg_for_device()
dma engine API
申请DMA通道
dma_request_slave_channel()获取DMA描述符
dmaengine_prep_slave_single()将描述符插入队列
dmaengine_submit()发起DMA操作
dma_async_issue_pending()中断返回,调用回调函数
释放通道
dma_release_channel()
static void xxx_dma_fini_callback(void *data){ struct completion *dma_complete = data; complete(dma_complete);}issue_xxx_dma(...){ rx_desc = dmaengine_prep_slave_single(xxx->rx_chan, xxx->dst_start, t->len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); rx_desc->callback = xxx_dma_fini_callback; rx_desc->callback_param = &xxx->rx_done; dmaengine_submit(rx_desc); dma_async_issue_pending(xxx->rx_chan);}
总结
I/O内存访问流程一般为:
申请资源->映射->访问->去映射->释放资源
- kernel 内存 I/O
- 内存映射I/O
- I/O端口 与 I/O内存
- I/O端口 与 I/O内存
- I/O 端口和 I/O 内存
- I/O端口与I/O内存
- I/O 端口和 I/O 内存
- I/O端口和I/O内存
- I/O 端口和 I/O 内存
- I/O端口和I/O内存
- I-O 端口和 I-O 内存
- I/O端口和I/O内存
- I/O端口和I/O内存
- I/O端口、I/O内存
- I-O 端口和 I-O 内存
- I-O 端口和 I-O 内存
- I/O端口和I/O内存
- I/O端口和I/O内存
- 小白玩Keras
- Oracle中空值与数字相加问题
- ViewPager+ListView+Fragment+PagerSlidingTableStrip实例
- OpenCV的安装配置详解
- 最新Myeclispe 8.6注册码到2019年
- kernel 内存 I/O
- HDU 1051 Wooden Sticks 经典贪心
- android app 使用 eclipse 更换图标
- 微服务架构 (二): 从既有的架构迁移到微服务的策略
- JavaScript学习总结(九)——Javascript面向(基于)对象编程
- UVALive2701 UVA1189 POJ1426 ZOJ1530 Find The Multiple
- 给ScrollView添加一个横向的进度条
- POJ 2002 Squares hash
- hdu 5819 Joint Stacks