?? (ldd) ch10-合理使用數(shù)據(jù)類型(轉(zhuǎn)載).txt
字號:
(LDD) Ch10-合理使用數(shù)據(jù)類型(轉(zhuǎn)載)
第10章 合理使用數(shù)據(jù)類型
在進一步討論更深的主題之前,我們需要先停一停,快速地回顧一下可移植問題。L
inux1.2版本和2.0版本之間的不同就在于額外的多平臺能力;結(jié)果是,大多數(shù)源代碼級
的移植問題已經(jīng)被排除了。這意味著一個規(guī)范的Linux驅(qū)動程序也應(yīng)該是多平臺的。
但是,與內(nèi)核代碼相關(guān)的一個核心問題是,能夠同時存取各種長度已知的數(shù)據(jù)項(例
如,文件系統(tǒng)數(shù)據(jù)類型或者設(shè)備卡上的寄存器)和利用不同處理器的能力(32位和64位的體
系結(jié)構(gòu),也有可能是16位的)。
當(dāng)把x86的代碼移植到新的體系結(jié)構(gòu)上時,核心開發(fā)者遇到的好幾個問題都和不正確的數(shù)
據(jù)類型相關(guān)。堅持強數(shù)據(jù)類型以及編譯時使用-Wall -Wstrict-prototypes選項能夠防止
大部分的臭蟲。
內(nèi)核使用的數(shù)據(jù)類型劃分為三種主要類型:象int這樣的標(biāo)準(zhǔn)C語言類型,象u32這樣的確
定數(shù)據(jù)大小的類型和象pid_t這樣的接口特定類型。我們將看一下這三種類型在何時使用
和如何使用。本章的最后一節(jié)將討論把驅(qū)動器代碼從x86移植到其它平臺上可能碰到的其
它一些典型問題。
如果你遵循我提供的這些準(zhǔn)則,你的驅(qū)動程序甚至可能在那些你未能進行測試的平臺上
編譯并運行。
使用標(biāo)準(zhǔn)C類型
大部分程序員習(xí)慣于自由的使用諸如int和long這樣的標(biāo)準(zhǔn)類型,而編寫設(shè)備驅(qū)動程序就
必須細(xì)心地避免類型沖突和潛在的臭蟲。
問題是,當(dāng)你需要“2個字節(jié)填充單位(filler)”或“表示4個字節(jié)字符串的某個東西”
時,你不能使用標(biāo)準(zhǔn)類型,因為通常的C數(shù)據(jù)類型在不同的體系結(jié)構(gòu)上所占空間大小并不
相同。例如,長整數(shù)和指針類型在Alpha上和x86上所占空間大小就不一樣,下面的屏幕
快照表明了這一點:
morgana% ./datasize
system/machine: Linux i486
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4
sizeof(longlong) = 8
sizeof(pointer) = 4
wolf% ./datasize
system/machine: Linux alpha
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 8
sizeof(longlong) = 8
sizeof(pointer) = 8
sandra% ./datasize
system/machine: Linux sparc
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4
sizeof(longlong) = 8
sizeof(pointer) = 4
datasize程序是一個可以從在O'Reilly FTP站點的misc-progs目錄下獲得的小程序。
在混合使用int和long類型時,你必須小心,有時有很好的理由這樣做,一種情形就是內(nèi)
存地址,一涉及到內(nèi)核,內(nèi)存地址就變得很特殊。雖然概念上地址是指針,但是通過使
用整數(shù)類型,可以更好地實現(xiàn)內(nèi)存管理;內(nèi)核把物理內(nèi)存看做一個巨大的數(shù)組,內(nèi)存地
址就是這個數(shù)組的索引。而且,一個指針很容易被取地址(deference),而使用整數(shù)表示
內(nèi)存地址可以防止它們被取地址,這正是人們所希望的(比使用指針更安全)。因而,內(nèi)
核中的地址屬于unsigned long類型,這是利用了指針和長整數(shù)類型大小總是相同這一事
實,至少在所有Linux當(dāng)前支持的平臺上是這樣的。我們等著看看將來把Linux移植到不
符合這一規(guī)則的平臺上的時候,會發(fā)生些什么。
分配確定的空間大小給數(shù)據(jù)項
有時內(nèi)核代碼需要指定大小的數(shù)據(jù)項,或者用來匹配二進制結(jié)構(gòu)*或者用來在結(jié)構(gòu)中插入
填充字段對齊數(shù)據(jù)。
為此目的,內(nèi)核提供如下的數(shù)據(jù)類型,它們都在頭文件<asm/types.h>中聲明,這個文件
又被頭文件<linux/types.h>所包含:
u8; /* 無符號字節(jié)(8位) */
u16; /* 無符號字(16 位) */
u32; /* 無符號32位數(shù)值 */
u64; /* 無符號64位數(shù)值 */
這些數(shù)據(jù)類型只能被內(nèi)核代碼所訪問(也即,在包含頭文件<linux/types.h>之前必須先
定義__KERNEL__)。相應(yīng)的有符號類型也是存在的,但一般不用;如果你需要使用它們的
話,只要把名字中的u替換為s就可以了。
如果用戶空間的程序需要使用這些類型,可以在這些名字前面添加2個下劃線:__u8和其
它類型是獨立于__KERNEL__定義的。例如,如果一個驅(qū)動程序需要通過ioctl系統(tǒng)調(diào)用與
一個運行在用戶空間內(nèi)的程序交換二進制結(jié)構(gòu)的話,頭文件必須將結(jié)構(gòu)中的32位字段定
義為__u32。
重要的是要記住這些類型特定于Linux,使用它們就會防礙軟件向其他Unix變體的移植。
重要的是要記住這些類型特定于Linux,使用它們就會防礙軟件向其他Unix變體的移植。
但是,有些情況下也需要明確說明數(shù)據(jù)大小,而標(biāo)準(zhǔn)頭文件(在每個Unix系統(tǒng)上都能找到
的)并未聲明較合適的數(shù)據(jù)類型。
你也許注意到,有時內(nèi)核也使用一般的數(shù)據(jù)類型,象unsigned int,用于那些大小與體
系結(jié)構(gòu)無關(guān)的項。這通常是為了向后兼容。當(dāng)u32及其相關(guān)類型在1.1.67版本引入時開發(fā)
者沒辦法把存在的數(shù)據(jù)類型改成新類型,因為當(dāng)結(jié)構(gòu)字段和賦予的值之間類型不匹配時
,編譯器會發(fā)出警告+。Linus當(dāng)初可沒預(yù)料到為自己使用而編寫的這個操作系統(tǒng)會發(fā)展
成為多平臺的;因此,一些舊的結(jié)構(gòu)的數(shù)據(jù)類型定義上不是很嚴(yán)格。
接口特定的類型
內(nèi)核中最常使用的數(shù)據(jù)類型有它們自己的typedef聲明,這樣就防止了任何移植上的問題
。例如,進程號(pid)通常使用pid_t,而不是int。使用pid_t屏蔽了任何實際數(shù)據(jù)類型
之間可能的差別。我使用“接口特定”這種表述來指代特定數(shù)據(jù)項的編程接口。
屬于指定“標(biāo)準(zhǔn)”類型的其它數(shù)據(jù)項也可以認(rèn)為是接口特定的。比如,一個jiffy計數(shù)總
是屬于unsigned long類型的,獨立于它的實際大小-你喜歡那么頻繁地使用jiffy_t類
型么?這里我關(guān)注的是接口特定類型的第一類,那些以_t結(jié)尾的類型。
_t類型完整的列表在頭文件<linux/types.h>中,但是該列表幾乎沒什么用。當(dāng)需要一個
特定類型時,你可以在你要調(diào)用的函數(shù)原型或者使用的數(shù)據(jù)結(jié)構(gòu)中找到它。
只要你的驅(qū)動程序使用了需要這種“定制”類型的函數(shù),又不遵循慣例的時候,編譯器
都會發(fā)出一個警告;如果你打開-Wall編譯開關(guān)并且細(xì)心地去除了所有警告,你就可以自
信你的代碼是可移植的了。
_t數(shù)據(jù)項的主要問題是當(dāng)你需要打印它們的時候,并不總是容易選擇正確的printk或者p
rintf格式,并且你在一種體系結(jié)構(gòu)上排除了的警告,在另一種體系結(jié)構(gòu)上可能又會出現(xiàn)
。例如,當(dāng)size_t在一些平臺上是unsigned long,而在另外一些平臺上卻是unsigned
int時,你怎么打印它呢?
任何時候,當(dāng)你需要打印一些特定接口的數(shù)據(jù)的時候,最行之有效的方法就是,把它強
制轉(zhuǎn)換成最可能的類型(通常是long或unsigned long類型),然后把它用相應(yīng)的格式打印
制轉(zhuǎn)換成最可能的類型(通常是long或unsigned long類型),然后把它用相應(yīng)的格式打印
出來。這種做法不會產(chǎn)生錯誤或者警告,因為格式和類型相符,而且你也不會丟失數(shù)據(jù)
位,因為強制類型轉(zhuǎn)換要么是個空操作,要么是將該數(shù)據(jù)項向更大數(shù)據(jù)類型的擴展。
實際上,通常我們并不會去打印我們討論的這些數(shù)據(jù)項,因此只有顯示調(diào)試信息時才會
碰到這些問題。更經(jīng)常的,除了把接口特定的類型作為參數(shù)傳遞給庫或內(nèi)核函數(shù)以外,
代碼僅僅只會對它們進行些儲存和比較。
雖然大多數(shù)情形下,_t類型都是正確的解決方案,但有時候正確的類型也可能并不存在
。這會發(fā)生在一些還沒被拋棄的舊接口上。
在內(nèi)核頭文件中我發(fā)現(xiàn)一處疑點,為 I/O函數(shù)聲明數(shù)據(jù)類型時不是很嚴(yán)格(參見第8章“
硬件管理”中的“平臺相關(guān)性”一節(jié))。這種不嚴(yán)格的類型定義主要是出于歷史上的原因
,但在編寫代碼時卻會帶來問題。就我而言,我經(jīng)常在把參數(shù)交換給out函數(shù)時遇上麻煩
;而如果定義了port_t,編譯器將會指出這些錯誤。
其它與移植有關(guān)的問題
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -