## 11.4.?其他移植性問題
除了數據類型, 當編寫一個驅動時有幾個其他的軟件問題要記住, 如果你想在 Linux 平臺間可移植.
一個通常的規則是懷疑顯式的常量值. 常常通過使用預處理宏, 代碼已被參數化. 這一節列出了最重要的可移植性問題. 無論何時你遇到已被參數化的值, 你可以在頭文件中以及在隨官方內核發布的設備驅動中找到提示.
### 11.4.1.?時間間隔
當涉及時間間隔, 不要假定每秒有 1000 個嘀噠. 盡管當前對 i386 體系是真實的, 不是每個 Linux 平臺都以這個速度運行. 對于 x86 如果你使用 HZ 值(如同某些人做的那樣), 這個假設可能是錯的, 并且沒人知道將來內核會發生什么. 無論何時你使用嘀噠來計算時間間隔, 使用 HZ ( 每秒的定時器中斷數 ) 來標定你的時間. 例如, 檢查一個半秒的超時, 用 HZ/2 和逝去時間比較. 更普遍地, msec 毫秒對應地嘀噠數一直是 msec*HZ/1000.
### 11.4.2.?頁大小
當使用內存時, 記住一個內存頁是 PAGE_SIZE 字節, 不是 4KB. 假定頁大小是 4KB 并且硬編碼這個值是一個 PC 程序員常見的錯誤, 相反, 被支持的平臺顯示頁大小從 4 KB 到 64 KB, 并且有時它們在相同平臺上的不同的實現上不同. 相關的宏定義是 PAGE_SIZE 和 PAGE_SHIT. 后者包含將一個地址移位來獲得它的頁號的位數. 對于 4KB 或者更大的頁這個數當前是 12 或者更大. 宏在 <asm/page.h> 中定義; 用戶空間程序可以使用 getpagesize 庫函數, 如果它們需要這個信息.
讓我們看一下非一般的情況. 如果一個驅動需要 16 KB 來暫存數據, 它不應當指定一個 2 的指數 給 get_free_pages. 你需要一個可移植解決方法. 這樣的解決方法, 幸運的是, 已經由內核開發者寫好并且稱為 get_order:
~~~
#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);
~~~
記住, get_order 的參數必須是 2 的冪.
### 11.4.3.?字節序
小心不要假設字節序. PC 存儲多字節值是低字節為先(小端為先, 因此是小端), 一些高級的平臺以另一種方式(大端)工作. 任何可能的時候, 你的代碼應當這樣來編寫, 它不在乎它操作的數據的字節序. 但是, 有時候一個驅動需要使用單個字節建立一個整型數或者相反, 或者它必須與一個要求一個特定順序的設備通訊.
包含文件 <asm/byteorder.h> 定義了或者 __BIG_ENDIAN 或者 __LITTLE_ENDIAN, 依賴處理器的字節序. 當處理字節序問題時, 你可能編碼一堆 #ifdef __LITTTLE_ENDIAN 條件語句, 但是有一個更好的方法. Linux 內核定義了一套宏定義來處理之間的轉換, 在處理器字節序和你需要以特定字節序存儲和加載的數據之間. 例如:
~~~
u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);
~~~
這 2 個宏定義轉換一個值, 從無論 CPU 使用的什么到一個無符號的, 小端, 32 位數, 并且轉換回. 它們不管你的 CPU 是小端還是大端, 不管它是不是 32-位 處理器. 在沒有事情要做的情況下它們原樣返回它們的參數. 使用這些宏定義易于編寫可移植的代碼, 而不必使用大量的條件編譯建造.
有很多類似的函數; 你可以在 <linux/byteorder/big_endian.h> 和 <linux/byteorder/little_endian.h> 中見到完整列表. 一會兒之后, 這個模式不難遵循. be64_to_cpu 轉換一個無符號的, 大端, 64-位 值到一個內部 CPU 表示. le16_to_cpus, 相反, 處理有符號的, 小端, 16 位數. 當處理指針時, 你也會使用如 cpu_to_le32p, 它使用指向一個值的指針來轉換, 而不是這個值自身. 剩下的看包含文件.
### 11.4.4.?數據對齊
編寫可移植代碼而值得考慮的最后一個問題是如何存取不對齊的數據 -- 例如, 如何讀取一個存儲于一個不是 4 字節倍數的地址的4字節值. i386 用戶常常存取不對齊數據項, 但是不是所有的體系允許這個. 很多現代的體系產生一個異常, 每次程序試圖不對齊數據傳送時; 數據傳輸由異常處理來處理, 帶來很大的性能犧牲. 如果你需要存取不對齊的數據, 你應當使用下列宏:
~~~
#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);
~~~
這些宏是無類型的, 并且用在每個數據項, 不管它是 1 個, 2 個, 4 個, 或者 8 個字節長. 它們在任何內核版本中定義.
關于對齊的另一個問題是跨平臺的數據結構移植性. 同樣的數據結構( 在 C-語言 源文件中定義 )可能在不同的平臺上不同地編譯. 編譯器根據各個平臺不同的慣例來安排結構成員對齊.
為了編寫可以跨體系移動的數據使用的數據結構, 你應當一直強制自然的數據項對齊, 加上對一個特定對齊方式的標準化. 自然對齊意味著存儲數據項在是它的大小的整數倍的地址上(例如, 8-byte 項在 8 的整數倍的地址上). 為強制自然對齊在阻止編譯器以不希望的方式安排成員量的時候, 你應當使用填充者成員來避免在數據結構中留下空洞.
為展示編譯器如何強制對齊, dataalign 程序在源碼的 misc-progs 目錄中發布, 并且一個對等的 kdataalign 模塊是 misc-modules 的一部分. 這是程序在幾個平臺上的輸出以及模塊在 SPARC64 的輸出:
~~~
arch Align: char short int long ptr long-long u8 u16 u32 u64
i386 1 2 4 4 4 4 1 2 4 4
i686 1 2 4 4 4 4 1 2 4 4
alpha 1 2 4 8 8 8 1 2 4 8
armv4l 1 2 4 4 4 4 1 2 4 4
ia64 1 2 4 8 8 8 1 2 4 8
mips 1 2 4 4 4 8 1 2 4 8
ppc 1 2 4 4 4 8 1 2 4 8
sparc 1 2 4 4 4 8 1 2 4 8
sparc64 1 2 4 4 4 8 1 2 4 8
x86_64 1 2 4 8 8 8 1 2 4 8
kernel: arch Align: char short int long ptr long-long u8 u16 u32 u64
kernel: sparc64 1 2 4 8 8 8 1 2 4 8
~~~
有趣的是注意不是所有的平臺對齊 64-位值在 64-位邊界上, 因此你需要填充者成員來強制對齊和保證可移植性.
最后, 要知道編譯器可能自己悄悄地插入填充到結構中來保證每個成員是對齊的, 為了目標處理器的良好性能. 如果你定義一個結構打算來匹配一個設備期望的結構, 這個自動的填充可能妨礙你的企圖. 解決這個問題的方法是告訴編譯器這個結構必須是"緊湊的", 不能增加填充者. 例如, 內核頭文件 <linux/edd.h> 定義幾個與 x86 BIOS 接口的數據結構, 并且它包含下列的定義:
~~~
struct
{
u16 id;
u64 lun;
u16 reserved1;
u32 reserved2;
}
__attribute__ ((packed)) scsi;
~~~
如果沒有 __attribute__ ((packed)), lun 成員可能被在前面添加 2 個填充者字節或者 6 個, 如果我們在 64-位平臺上編譯這個結構.
### 11.4.5.?指針和錯誤值
很多內部內核函數返回一個指針值給調用者. 許多這些函數也可能失敗. 大部分情況, 失敗由返回一個 NULL 指針值來指示. 這個技術是能用的, 但是它不能通知問題的確切特性. 一些接口確實需要返回一個實際的錯誤碼以便于調用者能夠基于實際上什么出錯來作出正確的判斷.
許多內核接口通過在指針值中對錯誤值編碼來返回這個信息. 這樣的信息必須小心使用, 因為它們的返回值不能簡單地與 NULL 比較. 為幫助創建和使用這類接口, 一小部分函數已可用( 在 <linux/err.h>).
一個返回指針類型的函數可以返回一個錯誤值, 使用:
~~~
void *ERR_PTR(long error);
~~~
這里, error 是常見的負值錯誤碼. 調用者可用使用 IS_ERR 來測試是否一個返回的指針是不是一個錯誤碼:
~~~
long IS_ERR(const void *ptr);
~~~
如果你需要實際的錯誤碼, 它可能被抽取到, 使用:
~~~
long PTR_ERR(const void *ptr);
~~~
你應當只對 IS_ERR 返回一個真值的值使用 PTR_ERR; 任何其他的值是一個有效的指針.
- 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. 快速參考