《Linux Device Drivers》第十五章 内存映射和DMA——note
生活随笔
收集整理的這篇文章主要介紹了
《Linux Device Drivers》第十五章 内存映射和DMA——note
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
- 簡單介紹
- 很多類型的驅動程序編程都須要了解一些虛擬內存子系統怎樣工作的知識
- 當遇到更為復雜、性能要求更為苛刻的子系統時,本章所討論的內容遲早都要用到
- 本章的內容分成三個部分
- 講述mmap系統調用的實現過程
- 講述怎樣跨越邊界直接訪問用戶空間的內存頁
- 講述了直接內存訪問(DMA)I/O操作,它使得外設具有直接訪問系統內存的能力
- Linux的內存管理
- 地址類型
- Linux是一個虛擬內存系統,這意味著用戶程序所使用的地址與硬件使用的物理地址是不等同的
- 有了虛擬內存,在系統中執行的程序能夠分配比物理內存很多其它的內存,甚至一個單獨的進程都能擁有比系統物理內存很多其它的虛擬地址空間
- 以下是一個Linux使用的地址類型列表
- 用戶虛擬地址
- 這是在用戶空間程序所能看到的常規地址
- 物理地址
- 該地址在處理器和系統內存之間使用
- 總線地址
- 該地址在外圍總線和內存之間使用,通常它們與處理器使用的物理地址同樣
- 內核邏輯地址
- 內核邏輯地址組成了內核的常規地址空間
- 在大多數體系架構中。邏輯地址和與其相關聯的物理地址不同,只在它們之間存在一個固定的偏移量
- kmalloc返回的內存就是內核邏輯地址
- 內核虛擬地址
- 和內核邏輯地址的同樣之處在于。它們都將內核空間的地址映射到物理地址上
- 內核虛擬地址與物理地址的映射不必是線性的一對一的
- 全部的邏輯地址都是內核虛擬地址。可是非常多內核虛擬地址不是邏輯地址
- vmalloc分配的內存具有一個虛擬地址
- 用戶虛擬地址
- <asm/page.h>
- __pa()
- 返回其相應的物理地址
- __va()
- 將物理地址逆向映射到邏輯地址,但這僅僅對低端內存頁有效
- __pa()
- 物理地址和頁
- 物理地址被分成離散的單元。稱之為頁
- <asm/page.h>
- PAGE_SIZE
- 眼下大多數系統都使用每頁4096個字節
- 高端與低端內存
- 使用32位系統僅僅能在4GB的內存中尋址
- 內核將4GB的虛擬地址空間切割為用戶空間和內核空間,一個典型的切割是將3GB分配給用戶空間。1GB分配給內核空間
- 低端內存
- 存在于內核空間上的邏輯地址內存
- 高端內存
- 那些不存在邏輯地址的內存
- 內存映射和頁結構
- <linux/mm.h>
- struct page
- atomic_t count;
- 對該頁的訪問計數。
當計數值為0時,該頁將返回給空暇鏈表
- 對該頁的訪問計數。
- void *virtual;
- 假設頁面被映射。則指向頁的內核虛擬地址;假設未被映射則為NULL
- unsigned long flags;
- 描寫敘述頁狀態的一系列標志
- PG_locked表示內存中的頁已經被鎖住
- PG_reserved表示禁止內存管理系統訪問該頁
- atomic_t count;
- struct page *virt_to_page(void *kaddr);
- struct page *pfn_to_page(int pfn);
- 針對給定的頁幀號,返回page結構指針
- void *page_address(struct page *page);
- 返回頁的內核虛擬地址
- <linux/highmem.h>
- <asm/kmap_types.h>
- void *kmap(struct page *page);
- 對于低端內存頁來說,返回頁的邏輯地址
- 對于高端內存,在專用的內核地址空間創建特殊的映射
- void kunmap(struct page *page);
- void *kmap_atomic(struct page *page, enum km_type type);
- void kunmap_atomic(void *addr, enum km_type type);
- 頁表
- 處理器必須使用某種機制同,將虛擬地址轉換為對應的物理地址。這樣的機制被稱為頁表
- 它基本上是一個多層樹形結構。結構化的數據中包括了虛擬地址到物理地址的映射和相關的標志位
- 虛擬內存區
- 虛擬內存區(VMA)用于管理進程地址空間中不同區域的內核數據結構
- 進程的內存映射包括以下這些區域
- 程序的可運行代碼區域
- 多個數據區,當中包括初始化數據、非初始化數據以及程序堆棧
- 與每一個活動的內存映射相應的區域
- /proc/<pid>/maps
- start-end perm offset major:minor inode image
- vm_area_struct結構
- <linux/mm.h>
- struct vm_area_struct
- unsigned long vm_start;
- unsigned long vm_end;
- struct file *vm_file;
- unsigned long vm_pgoff;
- unsigned long vm_flags;
- struct vm_operations_struct *vm_ops;
- void *vm_private_data;
- struct vm_operations_struct
- void (*open) (struct vm_area_struct *vma);
- void (*close) (struct vm_area_struct *vma);
- struct page *(*nopage) (struct vm_area_struct *vma, unsigned long address, int *type);
- int (*populate) (struct vm_area_struct *vm, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
- 內存映射處理
- <linux/sched.h>
- struct mm_struct
- current->mm
- <linux/sched.h>
- 地址類型
- mmap設備操作
- 內存映射能夠提供給用戶程序直接訪問設備內存的能力
- 映射一個設備意味著將用戶空間的一段內存與設備內存關聯起來
- 像串口和其它面向流的設備就不能進行mmap抽象
- 必須以PAGE_SIZE為單位進行映射
- mmap方法是file_operations結構的一部分
- mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset)
- int (*mmap) (struct file *filp, struct vm_area_struct *vma);
- 有兩種建立頁表的方法
- 使用remap_pfn_range函數一次所有建立
- 通過nopage VMA方法每次建立一個頁表
- 使用remap_pfn_range
- int rempa_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
- int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot);
- vma
- 虛擬內存區域
- virt_addr
- 又一次映射時的起始用戶虛擬地址
- pfn
- 與物理內存相應的頁幀號。虛擬內存將要被映射到該物理內存
- 頁幀號僅僅是將物理地址右移PAGE_SHIFT位
- size
- 以字節為單位
- prot
- 新VMA要求的“保護(protection)”屬性
- 一個簡單的實現
- drivers/char/mem.c
- remap_pfn_range(vma, vma->vm_start, vm_.vm_pgoff, vma->vm_end – vma->vm_start, vma->vm_page_prot)
- 為VMA加入操作
- struct vm_operations_struct simple_remap_vm_ops = {.open = simple_vma_open, .close = simple_vma_close,}
- 使用nopage映射內存
- 假設要支持mremap系統調用。就必須實現nopage函數
- struct page *(*nopage) (struct vm_area_struct *vma, unsigned long address, int *type);
- get_page(struct page *pageptr);
- static int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma)
- {
- unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
- if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
- vm->vm_flags |= VM_IO
- vm->vm_flags |= VM_RESERVED;
- vm->vm_ops = &simple_nopage_vm_ops;
- simple_vma_open(vma);
- return 0;
- }
- struct page *simple_vma_nopage(struct vm_area_struct *vma, unsigned long address, int *type)
- {
- struct page *pageptr;
- unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
- unsigned long physaddr = address – vma->vm_start + offset;
- unsigned long pageframe = physaddr >> PAGE_SHIFT;
- if (!pfn_valid(pageframe))
- return NOPAGE_SIGBUS;
- pageptr = pfn_to_page(pageframe);
- get_page(pageptr);
- if (type)
-
- type = VM_FAULT_MINOR;
-
- return pageptr;
- }
- 又一次映射RAM
- 對remap_pfn_range函數的一個限制是:它僅僅能訪問保留頁和超出物理內存的物理地址
- remap_pfn_range不同意又一次映射常規地址
- 使用nopage方法又一次映射RAM
- 使用vm_ops->nopage一次處理一個頁錯誤
- 又一次映射內核虛擬地址
- page = vmalloc_to_page(pageptr);
- get_page(page);
- 運行直接I/O訪問
- 假設須要傳輸的數據量很大。直接進行傳輸數據。而不須要額外地從內核空間拷貝數據操作的參與,這將會大大提快速度
- 設置直接I/O的開銷很巨大
- 使用直接I/O須要write系統調用同步運行
- 在每一個寫操作完畢之前不能停止應用程序
- <linux/mm.h>
- int get_user_pages(struct task_struct *tsk, struct mm_struct *mm, unsigned long start, int len, int write, int force, struct page **pages, struct vm-area_struct **vmas);
- tsk
- 指向運行I/O的任務指針,該參數差點兒是current
- mm
- 指向描寫敘述被映射地址空間的內存管理結構的指針
- 對驅動程序來說。該參數總是current->mm
- force
- 假設write非零。對映射的頁有寫權限
- 驅動程序對該參數總是設置為0
- pages
- 假設調用成功,pages中包括了一個描寫敘述用戶空間緩沖區page結構的指針列表
- vmas
- 假設調用成功,vmas包括了對應VMA的指針
- tsk
- int get_user_pages(struct task_struct *tsk, struct mm_struct *mm, unsigned long start, int len, int write, int force, struct page **pages, struct vm-area_struct **vmas);
- 使用直接I/O的設備通常使用DMA操作
- 一旦直接I/O操作完畢,就必須釋放用戶內存頁
- <linux/page-flags.h>
- void SetPageDirty(struct page *page);
- void page_cache_release(struct page *page);
- 異步I/O
- <linux/aio.h>
- ssize_t (*aio_read) (struct kiocb *iocb, char *buffer, size_t count, loff_t offset);
- ssize_t (*aio_write) (struct kiocb *iocb, const char *buffer, size_t count, loff_t offset);
- int (*aio_fsync) (struct kiocb *iocb, int datasync);
- int is_sync_kiocb(struct kiocb *iocb);
- int aio_complete(struct kiocb *iocb, long res, long res2);
- 直接內存訪問
- DMA是一種硬件機制同,它同意外圍設備和主內存之間直接傳輸它們的I/O數據。而不須要系統處理器的參與
- 使用這樣的機制能夠大大提高與設備通信的吞吐量
- DMA傳輸數據概覽
- 有兩種方式引發傳輸數據
- 軟件對數據的請求
- 當進程調用read,驅動程序函數分配一個DMA緩沖區,并讓硬件將傳輸數據到這個緩沖區中,進程處于睡眠狀態
- 硬件將數據寫入到DMA緩沖區中,當寫入完成。產生一個中斷
- 中斷處理程序獲得輸入的數據,應答中斷。而且喚醒進程,該進程如今就可以讀取數據
- 硬件異步地將數據傳遞給系統
- 硬件產生中斷,宣告新數據的到來
- 中斷處理程序分配一個緩沖區,而且告訴硬件向哪里數據傳輸
- 外圍設備將數據寫入緩沖區,完畢后產生另外一個中斷
- 處理程序分發新數據。喚醒不論什么相關進程,然后運行清理工作
- 軟件對數據的請求
- 有兩種方式引發傳輸數據
- 分配DMA緩沖區
- 使用DMA緩沖區的主要問題是:當大于一頁時。它們必須占領連接的物理頁,這是由于設備使用ISA或者PCI系統總線數據傳輸,而這兩種方式使用的都是物理地址
- DIY分配
- get_free_pages函數能夠分配多達幾M字節的內存,可是對較大數量的請求。甚至是遠少于128KB的請求也一般會失敗,這是由于此時系統內存中充滿了內存碎片
- 當內核不能返回請求數量的內存或須要超過128KB內存時,除了返回-ENOMEM,另外一個方法是在引導時分配內存或是為緩沖區保留頂部物理RAM
- 另一個方法是使用GFP_NOFAIL分配標志來為緩沖區分配內存
- 總線地址
- 使用DMA的設備驅動程序將與連接到總線接口上的硬件通信,硬件使用的是物理地址,而程序代碼使用的是虛擬地址
- <asm/io.h>
- unsigned long virt_to_bus(volatile void *address);
- void *bus_to_virt(unsigned long address);
- 通用DMA層
- 內核提供了一個與總線體系架構無關的DMA層
- <linux/dma-mapping.h>
- 處理復雜的硬件
- int dma_set_mask(struct device *dev, u64 mask);
- 該掩碼顯示與設備能尋址能力相應的位
- 假設dma_set_mask返回0,則對該設備不能使用DMA
- int dma_set_mask(struct device *dev, u64 mask);
- DMA映射
- 一個DMA映射是要分配的DMA緩沖區與為該緩沖區生成的、設備可訪問地址的組合
- DMA映射建立了一個新的結構類型——dma_addr_t來表示總線地址
- 依據DMA緩沖區期望保留的時間長短。PCI代碼區分兩種類型的DMA映射
- 一致性DMA映射
- 這樣的類型的映射存在于驅動程序生命周期中
- 一致性映射的緩沖區必須可同一時候被CPU和外圍設備訪問
- 建立和使用一致性映射的開銷是非常大的
- 流式DMA映射
- 通常為單獨的操作建立流式映射
- 內核開發人員建議盡量使用流式映射,然后再考慮一致性映射
- 在支持映射寄存器的系統中,每一個DMA映射使用總線上的一個或者多個映射寄存器
- 在一些硬件中,流式映射能夠被優化。但優化的方法對一致性映射無效
- 一致性DMA映射
- 建立一致性DMA映射
- void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
- 返回值是緩沖區的內核虛擬地址
- 與其相關的總線地址,保存在dma_handle中
- void dma_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle);
- void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int flag);
- DMA池
- DMA池是一個生成小型、一致性DMA映射的機制
- <linux/dmapool.h>
- struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation);
- allocation不為零,表示內存邊界不能超越allocation
- void dma_pool_destroy(struct dma_pool *pool);
- void *dma_pool_alloc(struct dma_pool *pool, int mem_flags, dma_addr_t *handle);
- 返回的DMA緩沖區的地址是內核虛擬地址
- void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr);
- struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation);
- 建立流式DMA映射
- 當建立流式映射時。必須告訴內核數據流動的方向
- enum dma_data_direction
- DMA_TO_DEVICE
- DMA_FROM_DEVICE
- DMA_BIDIRECTIONAL
- DMA_NONE
- dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);
- void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
- 有幾條很重要的原則用于流式DMA映射
- 緩沖區僅僅能用于這種傳送。即其傳送方向匹配于映射時給定的方向wfhg
- 一旦緩沖區被映射,它將屬于設備,而不是處理器
- 在DMA處于活動期間內,不能撤銷對緩沖區映射,否則會嚴重破壞系統的穩定性
- void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
- void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
- 單頁流式映射
- dma_addr_t dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction direction);
- void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, enum dma_data_direction direction);
- 分散/聚焦映射
- 這是一種特殊的流式DMA映射
- 如果有幾個緩沖區,它們須要與設備雙向數據傳輸
- 有幾種方式能產生這樣的情形
- 從raedv或者writev系統調用產生
- 從集群的磁盤I/O請求產生
- 從映射的內核I/O緩沖區中的頁面鏈表產生
- 很多設備都能接受一個指針數組的分散表,以及它的長度,然后在一次DMA操作中把它們所有傳輸走
- 映射分散表的第一步是建立并填充一個描寫敘述被傳送緩沖區的scatterlist結構的數組
- <linux/scatterlist.h>
- struct scatterlist
- struct page *page;
- unsigned int length;
- unsigned int offset;
- int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);
- nents是傳入的分散表入口的數量
- 返回值是要傳送的DMA緩沖區數
- 驅動程序應該傳輸由dma_map_sg函數返回的每一個緩沖區
- dma_addr_t sg_dma_address(struct scatterlist *sg);
- unsinged int sg_dma_len(struct scatterlist *sg);
- void dma_unmap_sg(struct device *dev, struct scatterlist *list, int nents, enum dma_data_direction direction);
- nents一定是先前傳遞給dma_map_sg函數的入口項的數量
- void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);
- void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);
- PCI雙重地址周期映射
- 通常DMA支持層使用32位總線地址。其為設備的DMA掩碼所約束
- PCI總線還支持64位地址模式。既雙重地址周期(DAC)
- 假設設備須要使用放在高端內存的大塊緩沖區,能夠考慮實現DAC支持
- <linux/pci.h>
- int pci_dac_set_dma_mask(struct pci_dev *pdev, u64 mask);
- 返回0時。才干使用DAC地址
- dma64_addr_t pci_dac_page_to_dma(struct pci_dev *pdev, struct page *page, unsigned long offset, int direction);
- direction
- PCI_DMA_TODEVICE
- PCI_DMA_FROMDEVICE
- PCI_DMA_BIDIRECTIONAL
- direction
- void pci_dac_dma_sync_single_for_cpu(struct pci_dev *pdev, dma64_addr_t dma_addr, size_t len, int direction);
- void pci_dac_dma_sync_single_for_device(struct pci_dev *pdev, dma64_addr_t dma_addr, size_t len, int direction);
- ISA設備的DMA
- ISA總線同意兩種DMA傳輸:本地(native)DMA和ISA總線控制(bus-master)DMA
- 本地DMA使用主板上的標準DMA控制器電路來驅動ISA總線上的信號線
- ISA總線控制DMA全然由外圍設備控制
- 有三種實現涉及到ISA總線上的DMA傳輸數據
- 8237 DMA控制器(DMAC)
- 外圍設備
- 當設備準備傳送數據時,必須激活DMA請求信號
- 設備驅動程序
- 須要驅動程序完畢的工作非常少,它僅僅是負責提供DMA控制器的方向、總線地址、傳輸量的大小等等
- 注冊DMA
- <asm/dma.h>
- int request_dma(unsigned int channel, const char *name);
- 返回0表示運行成功
- void free_dma(unsigned int channel);
- int request_dma(unsigned int channel, const char *name);
- <asm/dma.h>
- 與DMA控制器通信
- unsigned long claim_dma_lock();
- 必須被裝入控制器的信息包括三個部分:RAM的地址、必須被傳輸的原子項個數以及傳輸的方向
- void set_dma_mode(unsigned int channel, char mode);
- mode
- DMA_MODE_READ
- DMA_MODE_WRITE
- DMA_MODE_CASCADE
- 釋放對總線的控制
- mode
- void set_dma_addr(unsigned int channel, unsigned int addr);
- void set_dma_count(unsigned int channel, unsigned int count);
- void disable_dma(unsigned int channel);
- void enable_dma(unsigned int channel);
- int get_dma_residue(unsigned int channel);
- 返回還未傳輸的字節數
- void clear_dma_ff(unsigned int channel);
總結
以上是生活随笔為你收集整理的《Linux Device Drivers》第十五章 内存映射和DMA——note的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于App开发:模拟服务器数据接口 -
- 下一篇: linux 其他常用命令