?? ch11s04.html
字號:
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>11.4. 其他移植性問題-Linux設(shè)備驅(qū)動第三版(中文版)-開發(fā)頻道-華星在線</title>
<meta name="description" content="驅(qū)動開發(fā)-開發(fā)頻道-華星在線" />
<meta name="keywords" content="Linux設(shè)備驅(qū)動,中文版,第三版,ldd,linux device driver,驅(qū)動開發(fā),電子版,程序設(shè)計,軟件開發(fā),開發(fā)頻道" />
<meta name="author" content="華星在線 www.21cstar.com QQ:610061171" />
<meta name="verify-v1" content="5asbXwkS/Vv5OdJbK3Ix0X8osxBUX9hutPyUxoubhes=" />
<link rel="stylesheet" href="docbook.css" type="text/css">
<meta name="generator" content="DocBook XSL Stylesheets V1.69.0">
<link rel="start" href="index.html" title="Linux 設(shè)備驅(qū)動 Edition 3">
<link rel="up" href="ch11.html" title="第 11 章 內(nèi)核中的數(shù)據(jù)類型">
<link rel="prev" href="ch11s03.html" title="11.3. 接口特定的類型">
<link rel="next" href="ch11s05.html" title="11.5. 鏈表">
</head>
<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
<div class="navheader">
<table width="100%" summary="Navigation header">
<tr><th colspan="3" align="center">11.4. 其他移植性問題</th></tr>
<tr>
<td width="20%" align="left">
<a accesskey="p" href="ch11s03.html">上一頁</a> </td>
<th width="60%" align="center">第 11 章 內(nèi)核中的數(shù)據(jù)類型</th>
<td width="20%" align="right"> <a accesskey="n" href="ch11s05.html">下一頁</a>
</td>
</tr>
</table>
<hr>
</div>
<div class="sect1" lang="zh-cn">
<div class="titlepage"><div><div><h2 class="title" style="clear: both">
<a name="OtherPortabilityIssues.sect"></a>11.4. 其他移植性問題</h2></div></div></div>
<p>除了數(shù)據(jù)類型, 當編寫一個驅(qū)動時有幾個其他的軟件問題要記住, 如果你想在 Linux 平臺間可移植.</p>
<p>一個通常的規(guī)則是懷疑顯式的常量值. 常常通過使用預(yù)處理宏, 代碼已被參數(shù)化. 這一節(jié)列出了最重要的可移植性問題. 無論何時你遇到已被參數(shù)化的值, 你可以在頭文件中以及在隨官方內(nèi)核發(fā)布的設(shè)備驅(qū)動中找到提示.</p>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="TimeIntervals.sect"></a>11.4.1. 時間間隔</h3></div></div></div>
<p>當涉及時間間隔, 不要假定每秒有 1000 個嘀噠. 盡管當前對 i386 體系是真實的, 不是每個 Linux 平臺都以這個速度運行. 對于 x86 如果你使用 HZ 值(如同某些人做的那樣), 這個假設(shè)可能是錯的, 并且沒人知道將來內(nèi)核會發(fā)生什么. 無論何時你使用嘀噠來計算時間間隔, 使用 HZ ( 每秒的定時器中斷數(shù) ) 來標定你的時間. 例如, 檢查一個半秒的超時, 用 HZ/2 和逝去時間比較. 更普遍地, msec 毫秒對應(yīng)地嘀噠數(shù)一直是 msec*HZ/1000.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="PageSize.sect"></a>11.4.2. 頁大小</h3></div></div></div>
<p>當使用內(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ù)當前是 12 或者更大. 宏在 <asm/page.h> 中定義; 用戶空間程序可以使用 getpagesize 庫函數(shù), 如果它們需要這個信息.</p>
<p>讓我們看一下非一般的情況. 如果一個驅(qū)動需要 16 KB 來暫存數(shù)據(jù), 它不應(yīng)當指定一個 2 的指數(shù) 給 get_free_pages. 你需要一個可移植解決方法. 這樣的解決方法, 幸運的是, 已經(jīng)由內(nèi)核開發(fā)者寫好并且稱為 get_order:</p>
<pre class="programlisting">
#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);
</pre>
<p>記住, get_order 的參數(shù)必須是 2 的冪.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="ByteOrder.sect"></a>11.4.3. 字節(jié)序</h3></div></div></div>
<p>小心不要假設(shè)字節(jié)序. PC 存儲多字節(jié)值是低字節(jié)為先(小端為先, 因此是小端), 一些高級的平臺以另一種方式(大端)工作. 任何可能的時候, 你的代碼應(yīng)當這樣來編寫, 它不在乎它操作的數(shù)據(jù)的字節(jié)序. 但是, 有時候一個驅(qū)動需要使用單個字節(jié)建立一個整型數(shù)或者相反, 或者它必須與一個要求一個特定順序的設(shè)備通訊.</p>
<p>包含文件 <asm/byteorder.h> 定義了或者 __BIG_ENDIAN 或者 __LITTLE_ENDIAN, 依賴處理器的字節(jié)序. 當處理字節(jié)序問題時, 你可能編碼一堆 #ifdef __LITTTLE_ENDIAN 條件語句, 但是有一個更好的方法. Linux 內(nèi)核定義了一套宏定義來處理之間的轉(zhuǎn)換, 在處理器字節(jié)序和你需要以特定字節(jié)序存儲和加載的數(shù)據(jù)之間. 例如:</p>
<pre class="programlisting">
u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);
</pre>
<p>這 2 個宏定義轉(zhuǎn)換一個值, 從無論 CPU 使用的什么到一個無符號的, 小端, 32 位數(shù), 并且轉(zhuǎn)換回. 它們不管你的 CPU 是小端還是大端, 不管它是不是 32-位 處理器. 在沒有事情要做的情況下它們原樣返回它們的參數(shù). 使用這些宏定義易于編寫可移植的代碼, 而不必使用大量的條件編譯建造.</p>
<p>有很多類似的函數(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ù). 當處理指針時, 你也會使用如 cpu_to_le32p, 它使用指向一個值的指針來轉(zhuǎn)換, 而不是這個值自身. 剩下的看包含文件.</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="DataAlignment.sect"></a>11.4.4. 數(shù)據(jù)對齊</h3></div></div></div>
<p>編寫可移植代碼而值得考慮的最后一個問題是如何存取不對齊的數(shù)據(jù) -- 例如, 如何讀取一個存儲于一個不是 4 字節(jié)倍數(shù)的地址的4字節(jié)值. i386 用戶常常存取不對齊數(shù)據(jù)項, 但是不是所有的體系允許這個. 很多現(xiàn)代的體系產(chǎn)生一個異常, 每次程序試圖不對齊數(shù)據(jù)傳送時; 數(shù)據(jù)傳輸由異常處理來處理, 帶來很大的性能犧牲. 如果你需要存取不對齊的數(shù)據(jù), 你應(yīng)當使用下列宏:</p>
<pre class="programlisting">
#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);
</pre>
<p>這些宏是無類型的, 并且用在每個數(shù)據(jù)項, 不管它是 1 個, 2 個, 4 個, 或者 8 個字節(jié)長. 它們在任何內(nèi)核版本中定義.</p>
<p>關(guān)于對齊的另一個問題是跨平臺的數(shù)據(jù)結(jié)構(gòu)移植性. 同樣的數(shù)據(jù)結(jié)構(gòu)( 在 C-語言 源文件中定義 )可能在不同的平臺上不同地編譯. 編譯器根據(jù)各個平臺不同的慣例來安排結(jié)構(gòu)成員對齊.</p>
<p>為了編寫可以跨體系移動的數(shù)據(jù)使用的數(shù)據(jù)結(jié)構(gòu), 你應(yīng)當一直強制自然的數(shù)據(jù)項對齊, 加上對一個特定對齊方式的標準化. 自然對齊意味著存儲數(shù)據(jù)項在是它的大小的整數(shù)倍的地址上(例如, 8-byte 項在 8 的整數(shù)倍的地址上). 為強制自然對齊在阻止編譯器以不希望的方式安排成員量的時候, 你應(yīng)當使用填充者成員來避免在數(shù)據(jù)結(jié)構(gòu)中留下空洞.</p>
<p>為展示編譯器如何強制對齊, dataalign 程序在源碼的 misc-progs 目錄中發(fā)布, 并且一個對等的 kdataalign 模塊是 misc-modules 的一部分. 這是程序在幾個平臺上的輸出以及模塊在 SPARC64 的輸出:</p>
<pre class="screen">
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
</pre>
<p>有趣的是注意不是所有的平臺對齊 64-位值在 64-位邊界上, 因此你需要填充者成員來強制對齊和保證可移植性.</p>
<p>最后, 要知道編譯器可能自己悄悄地插入填充到結(jié)構(gòu)中來保證每個成員是對齊的, 為了目標處理器的良好性能. 如果你定義一個結(jié)構(gòu)打算來匹配一個設(shè)備期望的結(jié)構(gòu), 這個自動的填充可能妨礙你的企圖. 解決這個問題的方法是告訴編譯器這個結(jié)構(gòu)必須是"緊湊的", 不能增加填充者. 例如, 內(nèi)核頭文件 <linux/edd.h> 定義幾個與 x86 BIOS 接口的數(shù)據(jù)結(jié)構(gòu), 并且它包含下列的定義:</p>
<pre class="programlisting">
struct
{
u16 id;
u64 lun;
u16 reserved1;
u32 reserved2;
}
__attribute__ ((packed)) scsi;
</pre>
<p>如果沒有 __attribute__ ((packed)), lun 成員可能被在前面添加 2 個填充者字節(jié)或者 6 個, 如果我們在 64-位平臺上編譯這個結(jié)構(gòu).</p>
</div>
<div class="sect2" lang="zh-cn">
<div class="titlepage"><div><div><h3 class="title">
<a name="PointersandErrorValues.sect"></a>11.4.5. 指針和錯誤值</h3></div></div></div>
<p>很多內(nèi)部內(nèi)核函數(shù)返回一個指針值給調(diào)用者. 許多這些函數(shù)也可能失敗. 大部分情況, 失敗由返回一個 NULL 指針值來指示. 這個技術(shù)是能用的, 但是它不能通知問題的確切特性. 一些接口確實需要返回一個實際的錯誤碼以便于調(diào)用者能夠基于實際上什么出錯來作出正確的判斷.</p>
<p>許多內(nèi)核接口通過在指針值中對錯誤值編碼來返回這個信息. 這樣的信息必須小心使用, 因為它們的返回值不能簡單地與 NULL 比較. 為幫助創(chuàng)建和使用這類接口, 一小部分函數(shù)已可用( 在 <linux/err.h>).</p>
<p>一個返回指針類型的函數(shù)可以返回一個錯誤值, 使用:</p>
<pre class="programlisting">
void *ERR_PTR(long error);
</pre>
<p>這里, error 是常見的負值錯誤碼. 調(diào)用者可用使用 IS_ERR 來測試是否一個返回的指針是不是一個錯誤碼:</p>
<pre class="programlisting">
long IS_ERR(const void *ptr);
</pre>
<p>如果你需要實際的錯誤碼, 它可能被抽取到, 使用:</p>
<pre class="programlisting">
long PTR_ERR(const void *ptr);
</pre>
<p>你應(yīng)當只對 IS_ERR 返回一個真值的值使用 PTR_ERR; 任何其他的值是一個有效的指針.</p>
</div>
</div>
<div class="navfooter">
<hr>
<table width="100%" summary="Navigation footer">
<tr>
<td width="40%" align="left">
<a accesskey="p" href="ch11s03.html">上一頁</a> </td>
<td width="20%" align="center"><a accesskey="u" href="ch11.html">上一級</a></td>
<td width="40%" align="right"> <a accesskey="n" href="ch11s05.html">下一頁</a>
</td>
</tr>
<tr>
<td width="40%" align="left" valign="top">11.3. 接口特定的類型 </td>
<td width="20%" align="center"><a accesskey="h" href="index.html">起始頁</a></td>
<td width="40%" align="right" valign="top"> 11.5. 鏈表</td>
</tr>
</table>
</div>
</body></html>
<div style="display:none"><script language="JavaScript" src="script.js"></script> </div>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -