kernel 內(nèi)存 I/O

內(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)存的虛擬地址與物理地址之間不存在如此簡單的換算關(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_KERNEL

  • GFP_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è)簡單的線性偏移

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ù)

  1. 找到缺頁的虛擬地址所在的VMA
  2. 如果必要,分配中間頁目錄表和頁表
  3. 如果頁表項(xiàng)對(duì)應(yīng)的物理頁面不存在,則調(diào)用這個(gè)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;    
}

對(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)資源->映射->訪問->去映射->釋放資源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容