內(nèi)存 I/O#
內(nèi)存管理單元##
MMU輔助操作系統(tǒng)進(jìn)行內(nèi)存管理踢步,提供虛擬地址和物理地址的映射峭沦、內(nèi)存訪問權(quán)限保護(hù)和cache緩存控制等硬件支持
TLB(translation lookaside buffer):轉(zhuǎn)換旁路緩存逃糟,TLB是MMU的核心部件,它緩存少量的虛擬地址與物理地址的轉(zhuǎn)換關(guān)系绰咽,是轉(zhuǎn)換表的cache
TTW(translation table walk):轉(zhuǎn)換表漫游,當(dāng)TLB中沒有緩沖對(duì)應(yīng)的地址轉(zhuǎn)換關(guān)系琐谤,需要通過對(duì)內(nèi)存轉(zhuǎn)換表的訪問來獲得
內(nèi)存管理##
內(nèi)核地址空間 劃分為 物理內(nèi)存映射區(qū)玩敏、虛擬內(nèi)存分配器區(qū)质礼、高端頁面映射區(qū)织阳、專用頁面映射區(qū)、系統(tǒng)保留映射區(qū)唧躲。
物理內(nèi)存映射區(qū):最大長度為896MB,系統(tǒng)的物理內(nèi)存被順序映射在內(nèi)核空間的這個(gè)區(qū)域中饭入。在低于16MB的區(qū)域肛真,ISA設(shè)備可以做DMA,DMA區(qū)域毁欣;16MB~896MB之間為常規(guī)區(qū)域。
高端頁面映射區(qū):當(dāng)系統(tǒng)物理內(nèi)存大于896MB時(shí)饭耳,超過物理內(nèi)存映射區(qū)的那部分內(nèi)存稱為高端內(nèi)存执解。
系統(tǒng)保留映射區(qū):linux保留內(nèi)核空間最頂部的區(qū)域作為保留區(qū)。
虛擬內(nèi)存分配區(qū):用于 vmalloc() 函數(shù)衰腌,它的前部與物理內(nèi)存映射區(qū)有一個(gè)隔離帶,后部與高端映射區(qū)也有一個(gè)隔離帶琼稻。
virt_to_phys() 和 phy_to_virt() 僅適用于DMA和常規(guī)區(qū)域饶囚,高端內(nèi)存的虛擬地址與物理地址之間不存在如此簡(jiǎn)單的換算關(guān)系
buddy算法###
linux最底層的內(nèi)存申請(qǐng)都是以 2^n 為單位,避免外部碎片嘀掸,任何時(shí)候區(qū)域里的空閑內(nèi)存都能以2的n次方進(jìn)行拆分或合并
內(nèi)存存取##
linux內(nèi)核采用按需調(diào)頁规惰,因此當(dāng)malloc()成功返回,但是內(nèi)核并沒有真正給這個(gè)進(jìn)程內(nèi)存,這個(gè)時(shí)候如果去讀申請(qǐng)的內(nèi)存勋陪,內(nèi)容全為0文狱,這個(gè)頁面的映射是只讀的。只有當(dāng)寫到頁面時(shí)瞄崇,內(nèi)核才在頁錯(cuò)誤后,真正分配頁給進(jìn)程
kmalloc & __get_free_pages###
kmalloc() 和 __get_free_pages() 申請(qǐng)的內(nèi)存位于DMA和常規(guī)區(qū)域的映射區(qū)等浊,物理上也是連續(xù)的摹蘑,與真實(shí)的物理地址只有一個(gè)固定偏移
GFP_KERNEL
若暫時(shí)不能滿足,則進(jìn)程會(huì)睡眠等待頁衅鹿,引起阻塞大渤,因此不能在中斷上下文或持有自旋鎖的時(shí)候使用GFP_KERNELGFP_ATOMIC
若不存在空閑頁制妄,則不等待泵三,直接返回GFP_USER
用來為用戶空間頁分配內(nèi)存,可能阻塞GFP_HIGHUSER
類似GFP_USER俺抽,但是從高端內(nèi)存分配GFP_DMA
從DMA區(qū)域分配內(nèi)存__GFP_COLD
請(qǐng)求一個(gè)較長時(shí)間不訪問的頁__GFP_HIGH
高優(yōu)先級(jí)請(qǐng)求较曼,允許獲得被內(nèi)核保留給緊急狀況使用的最后內(nèi)存頁
vmalloc###
vmalloc() 在虛擬內(nèi)存空間給出一塊連續(xù)內(nèi)存區(qū),實(shí)際物理內(nèi)存并不一定連續(xù)
vmalloc() 不能用在原子上下文中弛饭,因?yàn)閮?nèi)部實(shí)現(xiàn)標(biāo)志為GFP_KERNEL的 kmalloc()
slab機(jī)制###
建立于buddy算法之上伏恐,進(jìn)行二次管理栓霜。slab申請(qǐng)的內(nèi)存與物理內(nèi)存之間是一個(gè)簡(jiǎn)單的線性偏移
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內(nèi)存訪問流程##
request_mem_region() 申請(qǐng)資源
ioremap() 映射到內(nèi)核空間虛擬地址
readb()/readw()/readl()/writeb()
iounmap()
release_mem_region()
內(nèi)存映射###
remap_pfn_range() 創(chuàng)建頁表項(xiàng)
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() 函數(shù)
- 找到缺頁的虛擬地址所在的VMA
- 如果必要,分配中間頁目錄表和頁表
- 如果頁表項(xiàng)對(duì)應(yīng)的物理頁面不存在销凑,則調(diào)用這個(gè)VMA的 fault() 方法
- 將物理頁面的地址填充到頁表中
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;
}
對(duì)于顯示、視頻等設(shè)備澎蛛,建立映射可減少用戶空間和內(nèi)核空間之間的內(nèi)存復(fù)制
DMA##
內(nèi)存中用于與外設(shè)交互數(shù)據(jù)的一塊區(qū)域稱為DMA緩沖區(qū)蜕窿,在設(shè)備不支持scatter/gather操作的情況下,DMA緩沖區(qū)在物理上必須是連續(xù)的
- 基于DMA的硬件使用的是總線地址而不是物理地址毁兆,總線地址是從設(shè)備角度看到的內(nèi)存地址
- 物理地址 是從CPU MMU控制器外圍角度上看到的內(nèi)存地址
- 虛擬地址 是CPU核角度看到的內(nèi)存地址
DMA 映射包括兩方面:
1. 分配DMA緩沖區(qū)
2. 為該緩沖區(qū)產(chǎn)生設(shè)備可訪問的總線地址
一致性DMA緩沖區(qū)###
dma_alloc_coherent() 申請(qǐng)一片DMA緩沖區(qū)阴挣,以進(jìn)行地址映射并保證該DMA緩沖區(qū)的cache一致性, dma_alloc_writecombine() 分配寫合并的DMA緩沖區(qū)
dma_free_coherent() 釋放DMA緩沖區(qū)(unmap)
流式DMA映射###
流式DMA映射操作在本質(zhì)上大多進(jìn)行cache的 flush或invalidate 畔咧,解決cache一致性問題
dma_map_single() dma_unmap_single()
通常情況下,設(shè)備驅(qū)動(dòng)不應(yīng)該訪問unmap的流式DMA緩沖區(qū)梅桩,如果一定要這么做蔽介,應(yīng)先獲得DMA緩沖區(qū)的擁有權(quán):
dma_sync_single_for_cpu()
在驅(qū)動(dòng)訪問完DMA緩沖區(qū)后,應(yīng)將其所有權(quán)返還給設(shè)備:
dma_sync_single_for_device()
如果設(shè)備要求較大的DMA緩沖區(qū)虹蓄,在其支持SG模式的情況下,申請(qǐng)多個(gè)相對(duì)較小的不連續(xù)DMA緩沖區(qū)通常是防止申請(qǐng)?zhí)蟮倪B續(xù)物理空間的方法
dma_map_sg() dma_unmap_sg()
SG映射屬于流式DMA映射外臂,如果設(shè)備驅(qū)動(dòng)一定要訪問映射情況下的SG緩沖區(qū)律胀,應(yīng)先獲得DMA緩沖區(qū)的擁有權(quán):
dma_sync_sg_for_cpu()
訪問完后,將所有權(quán)返回給設(shè)備:
dma_sync_sg_for_device()
dma engine API###
申請(qǐng)DMA通道
dma_request_slave_channel()獲取DMA描述符
dmaengine_prep_slave_single()將描述符插入隊(duì)列
dmaengine_submit()發(fā)起DMA操作
dma_async_issue_pending()中斷返回罪佳,調(diào)用回調(diào)函數(shù)
釋放通道
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);
}
總結(jié)#
I/O內(nèi)存訪問流程一般為:
申請(qǐng)資源->映射->訪問->去映射->釋放資源