## 第?15?章?內存映射和 DMA
本章研究 Linux 內存管理的部分, 重點在對于設備驅動作者有用的技術. 許多類型的驅動編程需要一些對于虛擬內存子系統如何工作的理解; 我們在本章涉及到的材料來自手頭, 而不是象我們曾進入更加復雜和性能關鍵的子系統一樣. 虛擬內存子系統也是 Linux 內核核心的非常有趣的部分, 并且因而, 值得一見.
本章的材料分為 3 個部分:
-
第一部分涉及 mmap 系統調用的實現, 它允許設備內存直接映射到一個用戶進程地址空間. 不是所有的設備需要 mmap 支持, 但是, 對一些, 映射設備內存可產生可觀的性能提高.
-
我們接著看從其他的方向跨過邊界, 用對直接存取用戶空間的討論. 相對少驅動需要這個能力; 在大部分情況下, 內核做這種映射而驅動甚至不知道它. 但是了解如何映射用戶空間內存到內核(使用 get_user_pages)會有用.
-
最后一節涵蓋直接內存存取( DMA ) I/O 操作, 它提供給外設對系統內存的直接存取.
當然, 所有這些技術需要一個對 Linux 內存管理如何工作的理解, 因此我們從對這個子系統的總覽開始.
### 15.1.?Linux 中的內存管理
不是描述操作系統的內存管理理論, 本節試圖指出 Linux 實現的主要特點. 盡管你不必是一位 Linux 虛擬內存專家來實現 mmap, 一個對事情如何工作的基本了解是有用的. 下面是一個相當長的對內核使用來管理內存的數據結構的描述. 一旦必要的背景已被覆蓋, 我們就進入使用這個結構.
### 15.1.1.?地址類型
Linux 是, 當然, 一個虛擬內存系統, 意味著用戶程序見到的地址不直接對應于硬件使用的物理地址. 虛擬內存引入了一個間接層, 它允許了許多好事情. 有了虛擬內存, 系統重運行的程序可以分配遠多于物理上可用的內存; 確實, 即便一個單個進程可擁有一個虛擬地址空間大于系統的物理內存. 虛擬內存也允許程序對進程的地址空間運用多種技巧, 包括映射成員的內存到設備內存.
至此, 我們已經討論了虛擬和物理地址, 但是許多細節被掩蓋過去了. Linux 系統處理幾種類型的地址, 每個有它自己的含義. 不幸的是, 內核代碼不是一直非常清楚確切地在每個情況下在使用什么類型地地址, 因此程序員必須小心.
下面是一個 Linux 中使用的地址類型列表. 圖 [Linux 中使用的地址類型](# "圖?15.1.?Linux 中使用的地址類型")顯示了這個地址類型如何關聯到物理內存.
**圖?15.1.?Linux 中使用的地址類型**

User virtual addresses
這是被用戶程序見到的常規地址. 用戶地址在長度上是 32 位或者 64 位, 依賴底層的硬件結構, 并且每個進程有它自己的虛擬地址空間.
Physical addresses
在處理器和系統內存之間使用的地址. 物理地址是 32- 或者 64-位的量; 甚至 32-位系統在某些情況下可使用更大的物理地址.
Bus addresses
在外設和內存之間使用的地址. 經常, 它們和被處理器使用的物理地址相同, 但是這不是必要的情況. 一些體系可提供一個 I/O 內存管理單元(IOMMU), 它在總線和主內存之間重映射地址. 一個 IOMMU 可用多種方法使事情簡單(例如, 使散布在內存中的緩沖對設備看來是連續的, 例如), 但是當設定 DMA 操作時對 IOMMU 編程是一個必須做的額外的步驟. 總線地址是高度特性依賴的, 當然.
Kernel logical addresses
這些組成了正常的內核地址空間. 這些地址映射了部分(也許全部)主存并且常常被當作它們是物理內存來對待. 在大部分的體系上, 邏輯地址和它們的相關物理地址只差一個常量偏移. 邏輯地址使用硬件的本地指針大小并且, 因此, 可能不能在重裝備的 32-位系統上尋址所有的物理內存. 邏輯地址常常存儲于 unsigned long 或者 void * 類型的變量中. 從 kmalloc 返回的內存有內核邏輯地址.
Kernel virtual addresses
內核虛擬地址類似于邏輯地址, 它們都是從內核空間地址到物理地址的映射. 內核虛擬地址不必有邏輯地址空間具備的線性的, 一對一到物理地址的映射, 但是. 所有的邏輯地址是內核虛擬地址, 但是許多內核虛擬地址不是邏輯地址. 例如, vmalloc 分配的內存有虛擬地址(但沒有直接物理映射). kmap 函數(本章稍后描述)也返回虛擬地址. 虛擬地址常常存儲于指針變量.
如果你有邏輯地址, 宏 __pa() ( 在 <asm/page.h> 中定義)返回它的關聯的物理地址. 物理地址可被映射回邏輯地址使用 __va(), 但是只給低內存頁.
不同的內核函數需要不同類型地址. 如果有不同的 C 類型被定義可能不錯, 這樣請求的地址類型是明確的, 但是我們沒有這樣的好運. 在本章, 我們盡力對在哪里使用哪種類型地址保持清晰.
### 15.1.2.?物理地址和頁
物理內存被劃分為離散的單元稱為頁. 系統的許多內部內存處理在按頁的基礎上完成. 頁大小一個體系不同于另一個, 盡管大部分系統當前使用 4096-字節的頁. 常量 PAGE_SIZE (定義在 <asm/page.h>) 給出了頁大小在任何給定的體系上.
如果你查看一個內存地址 - 虛擬或物理 - 它可分為一個頁號和一個頁內的偏移. 如果使用 4096-字節頁, 例如, 12 位低有效位是偏移, 并且剩下的, 高位指示頁號. 如果你丟棄偏移并且向右移動剩下的部分 offset 位, 結果被稱為一個頁幀號 (PFN). 移位來在頁幀號和地址之間轉換是一個相當普通的操作. 宏 PAGE_SHIFT 告訴必須移動多少位來進行這個轉換.
- Linux設備驅動第三版
- 第 1 章 設備驅動簡介
- 1.1. 驅動程序的角色
- 1.2. 劃分內核
- 1.3. 設備和模塊的分類
- 1.4. 安全問題
- 1.5. 版本編號
- 1.6. 版權條款
- 1.7. 加入內核開發社團
- 1.8. 本書的內容
- 第 2 章 建立和運行模塊
- 2.1. 設置你的測試系統
- 2.2. Hello World 模塊
- 2.3. 內核模塊相比于應用程序
- 2.4. 編譯和加載
- 2.5. 內核符號表
- 2.6. 預備知識
- 2.7. 初始化和關停
- 2.8. 模塊參數
- 2.9. 在用戶空間做
- 2.10. 快速參考
- 第 3 章 字符驅動
- 3.1. scull 的設計
- 3.2. 主次編號
- 3.3. 一些重要數據結構
- 3.4. 字符設備注冊
- 3.5. open 和 release
- 3.6. scull 的內存使用
- 3.7. 讀和寫
- 3.8. 使用新設備
- 3.9. 快速參考
- 第 4 章 調試技術
- 4.1. 內核中的調試支持
- 4.2. 用打印調試
- 4.3. 用查詢來調試
- 4.4. 使用觀察來調試
- 4.5. 調試系統故障
- 4.6. 調試器和相關工具
- 第 5 章 并發和競爭情況
- 5.1. scull 中的缺陷
- 5.2. 并發和它的管理
- 5.3. 旗標和互斥體
- 5.4. Completions 機制
- 5.5. 自旋鎖
- 5.6. 鎖陷阱
- 5.7. 加鎖的各種選擇
- 5.8. 快速參考
- 第 6 章 高級字符驅動操作
- 6.1. ioctl 接口
- 6.2. 阻塞 I/O
- 6.3. poll 和 select
- 6.4. 異步通知
- 6.5. 移位一個設備
- 6.6. 在一個設備文件上的存取控制
- 6.7. 快速參考
- 第 7 章 時間, 延時, 和延后工作
- 7.1. 測量時間流失
- 7.2. 獲知當前時間
- 7.3. 延后執行
- 7.4. 內核定時器
- 7.5. Tasklets 機制
- 7.6. 工作隊列
- 7.7. 快速參考
- 第 8 章 分配內存
- 8.1. kmalloc 的真實故事
- 8.2. 后備緩存
- 8.3. get_free_page 和其友
- 8.4. 每-CPU 的變量
- 8.5. 獲得大量緩沖
- 8.6. 快速參考
- 第 9 章 與硬件通訊
- 9.1. I/O 端口和 I/O 內存
- 9.2. 使用 I/O 端口
- 9.3. 一個 I/O 端口例子
- 9.4. 使用 I/O 內存
- 9.5. 快速參考
- 第 10 章 中斷處理
- 10.1. 準備并口
- 10.2. 安裝一個中斷處理
- 10.3. 前和后半部
- 10.4. 中斷共享
- 10.5. 中斷驅動 I/O
- 10.6. 快速參考
- 第 11 章 內核中的數據類型
- 11.1. 標準 C 類型的使用
- 11.2. 安排一個明確大小給數據項
- 11.3. 接口特定的類型
- 11.4. 其他移植性問題
- 11.5. 鏈表
- 11.6. 快速參考
- 第 12 章 PCI 驅動
- 12.1. PCI 接口
- 12.2. 回顧: ISA
- 12.3. PC/104 和 PC/104+
- 12.4. 其他的 PC 總線
- 12.5. SBus
- 12.6. NuBus 總線
- 12.7. 外部總線
- 12.8. 快速參考
- 第 13 章 USB 驅動
- 13.1. USB 設備基礎知識
- 13.2. USB 和 sysfs
- 13.3. USB 的 Urbs
- 13.4. 編寫一個 USB 驅動
- 13.5. 無 urb 的 USB 傳送
- 13.6. 快速參考
- 第 14 章 Linux 設備模型
- 14.1. Kobjects, Ksets 和 Subsystems
- 14.2. 低級 sysfs 操作
- 14.3. 熱插拔事件產生
- 14.4. 總線, 設備, 和驅動
- 14.5. 類
- 14.6. 集成起來
- 14.7. 熱插拔
- 14.8. 處理固件
- 14.9. 快速參考
- 第 15 章 內存映射和 DMA
- 15.1. Linux 中的內存管理
- 15.2. mmap 設備操作
- 15.3. 進行直接 I/O
- 15.4. 直接內存存取
- 15.5. 快速參考
- 第 16 章 塊驅動
- 16.1. 注冊
- 16.2. 塊設備操作
- 16.3. 請求處理
- 16.4. 一些其他的細節
- 16.5. 快速參考
- 第 17 章 網絡驅動
- 17.1. snull 是如何設計的
- 17.2. 連接到內核
- 17.3. net_device 結構的詳情
- 17.4. 打開與關閉
- 17.5. 報文傳送
- 17.6. 報文接收
- 17.7. 中斷處理
- 17.8. 接收中斷緩解
- 17.9. 連接狀態的改變
- 17.10. Socket 緩存
- 17.11. MAC 地址解析
- 17.12. 定制 ioctl 命令
- 17.13. 統計信息
- 17.14. 多播
- 17.15. 幾個其他細節
- 17.16. 快速參考
- 第 18 章 TTY 驅動
- 18.1. 一個小 TTY 驅動
- 18.2. tty_driver 函數指針
- 18.3. TTY 線路設置
- 18.4. ioctls 函數
- 18.5. TTY 設備的 proc 和 sysfs 處理
- 18.6. tty_driver 結構的細節
- 18.7. tty_operaions 結構的細節
- 18.8. tty_struct 結構的細節
- 18.9. 快速參考