11.4. 其他移植性問題

2018-02-24 15:50 更新

11.4.?其他移植性問題

除了數(shù)據(jù)類型, 當(dāng)編寫一個(gè)驅(qū)動(dòng)時(shí)有幾個(gè)其他的軟件問題要記住, 如果你想在 Linux 平臺(tái)間可移植.

一個(gè)通常的規(guī)則是懷疑顯式的常量值. 常常通過使用預(yù)處理宏, 代碼已被參數(shù)化. 這一節(jié)列出了最重要的可移植性問題. 無論何時(shí)你遇到已被參數(shù)化的值, 你可以在頭文件中以及在隨官方內(nèi)核發(fā)布的設(shè)備驅(qū)動(dòng)中找到提示.

11.4.1.?時(shí)間間隔

當(dāng)涉及時(shí)間間隔, 不要假定每秒有 1000 個(gè)嘀噠. 盡管當(dāng)前對(duì) i386 體系是真實(shí)的, 不是每個(gè) Linux 平臺(tái)都以這個(gè)速度運(yùn)行. 對(duì)于 x86 如果你使用 HZ 值(如同某些人做的那樣), 這個(gè)假設(shè)可能是錯(cuò)的, 并且沒人知道將來內(nèi)核會(huì)發(fā)生什么. 無論何時(shí)你使用嘀噠來計(jì)算時(shí)間間隔, 使用 HZ ( 每秒的定時(shí)器中斷數(shù) ) 來標(biāo)定你的時(shí)間. 例如, 檢查一個(gè)半秒的超時(shí), 用 HZ/2 和逝去時(shí)間比較. 更普遍地, msec 毫秒對(duì)應(yīng)地嘀噠數(shù)一直是 msec*HZ/1000.

11.4.2.?頁(yè)大小

當(dāng)使用內(nèi)存時(shí), 記住一個(gè)內(nèi)存頁(yè)是 PAGE_SIZE 字節(jié), 不是 4KB. 假定頁(yè)大小是 4KB 并且硬編碼這個(gè)值是一個(gè) PC 程序員常見的錯(cuò)誤, 相反, 被支持的平臺(tái)顯示頁(yè)大小從 4 KB 到 64 KB, 并且有時(shí)它們?cè)谙嗤脚_(tái)上的不同的實(shí)現(xiàn)上不同. 相關(guān)的宏定義是 PAGE_SIZE 和 PAGE_SHIT. 后者包含將一個(gè)地址移位來獲得它的頁(yè)號(hào)的位數(shù). 對(duì)于 4KB 或者更大的頁(yè)這個(gè)數(shù)當(dāng)前是 12 或者更大. 宏在 <asm/page.h> 中定義; 用戶空間程序可以使用 getpagesize 庫(kù)函數(shù), 如果它們需要這個(gè)信息.

讓我們看一下非一般的情況. 如果一個(gè)驅(qū)動(dòng)需要 16 KB 來暫存數(shù)據(jù), 它不應(yīng)當(dāng)指定一個(gè) 2 的指數(shù) 給 get_free_pages. 你需要一個(gè)可移植解決方法. 這樣的解決方法, 幸運(yùn)的是, 已經(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 的冪.

11.4.3.?字節(jié)序

小心不要假設(shè)字節(jié)序. PC 存儲(chǔ)多字節(jié)值是低字節(jié)為先(小端為先, 因此是小端), 一些高級(jí)的平臺(tái)以另一種方式(大端)工作. 任何可能的時(shí)候, 你的代碼應(yīng)當(dāng)這樣來編寫, 它不在乎它操作的數(shù)據(jù)的字節(jié)序. 但是, 有時(shí)候一個(gè)驅(qū)動(dòng)需要使用單個(gè)字節(jié)建立一個(gè)整型數(shù)或者相反, 或者它必須與一個(gè)要求一個(gè)特定順序的設(shè)備通訊.

包含文件 <asm/byteorder.h> 定義了或者 BIG_ENDIAN 或者 __LITTLE_ENDIAN, 依賴處理器的字節(jié)序. 當(dāng)處理字節(jié)序問題時(shí), 你可能編碼一堆 #ifdef LITTTLE_ENDIAN 條件語(yǔ)句, 但是有一個(gè)更好的方法. Linux 內(nèi)核定義了一套宏定義來處理之間的轉(zhuǎn)換, 在處理器字節(jié)序和你需要以特定字節(jié)序存儲(chǔ)和加載的數(shù)據(jù)之間. 例如:


u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);

這 2 個(gè)宏定義轉(zhuǎn)換一個(gè)值, 從無論 CPU 使用的什么到一個(gè)無符號(hào)的, 小端, 32 位數(shù), 并且轉(zhuǎn)換回. 它們不管你的 CPU 是小端還是大端, 不管它是不是 32-位 處理器. 在沒有事情要做的情況下它們?cè)瓨臃祷厮鼈兊膮?shù). 使用這些宏定義易于編寫可移植的代碼, 而不必使用大量的條件編譯建造.

有很多類似的函數(shù); 你可以在 <linux/byteorder/big_endian.h> 和 <linux/byteorder/little_endian.h> 中見到完整列表. 一會(huì)兒之后, 這個(gè)模式不難遵循. be64_to_cpu 轉(zhuǎn)換一個(gè)無符號(hào)的, 大端, 64-位 值到一個(gè)內(nèi)部 CPU 表示. le16_to_cpus, 相反, 處理有符號(hào)的, 小端, 16 位數(shù). 當(dāng)處理指針時(shí), 你也會(huì)使用如 cpu_to_le32p, 它使用指向一個(gè)值的指針來轉(zhuǎn)換, 而不是這個(gè)值自身. 剩下的看包含文件.

11.4.4.?數(shù)據(jù)對(duì)齊

編寫可移植代碼而值得考慮的最后一個(gè)問題是如何存取不對(duì)齊的數(shù)據(jù) -- 例如, 如何讀取一個(gè)存儲(chǔ)于一個(gè)不是 4 字節(jié)倍數(shù)的地址的4字節(jié)值. i386 用戶常常存取不對(duì)齊數(shù)據(jù)項(xiàng), 但是不是所有的體系允許這個(gè). 很多現(xiàn)代的體系產(chǎn)生一個(gè)異常, 每次程序試圖不對(duì)齊數(shù)據(jù)傳送時(shí); 數(shù)據(jù)傳輸由異常處理來處理, 帶來很大的性能犧牲. 如果你需要存取不對(duì)齊的數(shù)據(jù), 你應(yīng)當(dāng)使用下列宏:


#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);

這些宏是無類型的, 并且用在每個(gè)數(shù)據(jù)項(xiàng), 不管它是 1 個(gè), 2 個(gè), 4 個(gè), 或者 8 個(gè)字節(jié)長(zhǎng). 它們?cè)谌魏蝺?nèi)核版本中定義.

關(guān)于對(duì)齊的另一個(gè)問題是跨平臺(tái)的數(shù)據(jù)結(jié)構(gòu)移植性. 同樣的數(shù)據(jù)結(jié)構(gòu)( 在 C-語(yǔ)言 源文件中定義 )可能在不同的平臺(tái)上不同地編譯. 編譯器根據(jù)各個(gè)平臺(tái)不同的慣例來安排結(jié)構(gòu)成員對(duì)齊.

為了編寫可以跨體系移動(dòng)的數(shù)據(jù)使用的數(shù)據(jù)結(jié)構(gòu), 你應(yīng)當(dāng)一直強(qiáng)制自然的數(shù)據(jù)項(xiàng)對(duì)齊, 加上對(duì)一個(gè)特定對(duì)齊方式的標(biāo)準(zhǔn)化. 自然對(duì)齊意味著存儲(chǔ)數(shù)據(jù)項(xiàng)在是它的大小的整數(shù)倍的地址上(例如, 8-byte 項(xiàng)在 8 的整數(shù)倍的地址上). 為強(qiáng)制自然對(duì)齊在阻止編譯器以不希望的方式安排成員量的時(shí)候, 你應(yīng)當(dāng)使用填充者成員來避免在數(shù)據(jù)結(jié)構(gòu)中留下空洞.

為展示編譯器如何強(qiáng)制對(duì)齊, dataalign 程序在源碼的 misc-progs 目錄中發(fā)布, 并且一個(gè)對(duì)等的 kdataalign 模塊是 misc-modules 的一部分. 這是程序在幾個(gè)平臺(tái)上的輸出以及模塊在 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

有趣的是注意不是所有的平臺(tái)對(duì)齊 64-位值在 64-位邊界上, 因此你需要填充者成員來強(qiáng)制對(duì)齊和保證可移植性.

最后, 要知道編譯器可能自己悄悄地插入填充到結(jié)構(gòu)中來保證每個(gè)成員是對(duì)齊的, 為了目標(biāo)處理器的良好性能. 如果你定義一個(gè)結(jié)構(gòu)打算來匹配一個(gè)設(shè)備期望的結(jié)構(gòu), 這個(gè)自動(dòng)的填充可能妨礙你的企圖. 解決這個(gè)問題的方法是告訴編譯器這個(gè)結(jié)構(gòu)必須是"緊湊的", 不能增加填充者. 例如, 內(nèi)核頭文件 <linux/edd.h> 定義幾個(gè)與 x86 BIOS 接口的數(shù)據(jù)結(jié)構(gòu), 并且它包含下列的定義:


struct
{
        u16 id;
        u64 lun;
        u16 reserved1;
        u32 reserved2;
}
__attribute__ ((packed)) scsi;

如果沒有 attribute ((packed)), lun 成員可能被在前面添加 2 個(gè)填充者字節(jié)或者 6 個(gè), 如果我們?cè)?64-位平臺(tái)上編譯這個(gè)結(jié)構(gòu).

11.4.5.?指針和錯(cuò)誤值

很多內(nèi)部?jī)?nèi)核函數(shù)返回一個(gè)指針值給調(diào)用者. 許多這些函數(shù)也可能失敗. 大部分情況, 失敗由返回一個(gè) NULL 指針值來指示. 這個(gè)技術(shù)是能用的, 但是它不能通知問題的確切特性. 一些接口確實(shí)需要返回一個(gè)實(shí)際的錯(cuò)誤碼以便于調(diào)用者能夠基于實(shí)際上什么出錯(cuò)來作出正確的判斷.

許多內(nèi)核接口通過在指針值中對(duì)錯(cuò)誤值編碼來返回這個(gè)信息. 這樣的信息必須小心使用, 因?yàn)樗鼈兊姆祷刂挡荒芎?jiǎn)單地與 NULL 比較. 為幫助創(chuàng)建和使用這類接口, 一小部分函數(shù)已可用( 在 <linux/err.h>).

一個(gè)返回指針類型的函數(shù)可以返回一個(gè)錯(cuò)誤值, 使用:


void *ERR_PTR(long error);

這里, error 是常見的負(fù)值錯(cuò)誤碼. 調(diào)用者可用使用 IS_ERR 來測(cè)試是否一個(gè)返回的指針是不是一個(gè)錯(cuò)誤碼:


long IS_ERR(const void *ptr); 

如果你需要實(shí)際的錯(cuò)誤碼, 它可能被抽取到, 使用:


long PTR_ERR(const void *ptr); 

你應(yīng)當(dāng)只對(duì) IS_ERR 返回一個(gè)真值的值使用 PTR_ERR; 任何其他的值是一個(gè)有效的指針.

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)