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