




已閱讀5頁(yè),還剩2頁(yè)未讀, 繼續(xù)免費(fèi)閱讀
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第10章 合理使用數(shù)據(jù)類型在進(jìn)一步討論更深的主題之前,我們需要先停一停,快速地回顧一下可移植問題。Linux1.2版本和2.0版本之間的不同就在于額外的多平臺(tái)能力;結(jié)果是,大多數(shù)源代碼級(jí)的移植問題已經(jīng)被排除了。這意味著一個(gè)規(guī)范的Linux驅(qū)動(dòng)程序也應(yīng)該是多平臺(tái)的。但是,與內(nèi)核代碼相關(guān)的一個(gè)核心問題是,能夠同時(shí)存取各種長(zhǎng)度已知的數(shù)據(jù)項(xiàng)(例如,文件系統(tǒng)數(shù)據(jù)類型或者設(shè)備卡上的寄存器)和利用不同處理器的能力(32位和64位的體系結(jié)構(gòu),也有可能是16位的)。當(dāng)把x86的代碼移植到新的體系結(jié)構(gòu)上時(shí),核心開發(fā)者遇到的好幾個(gè)問題都和不正確的數(shù)據(jù)類型相關(guān)。堅(jiān)持強(qiáng)數(shù)據(jù)類型以及編譯時(shí)使用-Wall -Wstrict-prototypes選項(xiàng)能夠防止大部分的臭蟲。內(nèi)核使用的數(shù)據(jù)類型劃分為三種主要類型:象int這樣的標(biāo)準(zhǔn)C語(yǔ)言類型,象u32這樣的確定數(shù)據(jù)大小的類型和象pid_t這樣的接口特定類型。我們將看一下這三種類型在何時(shí)使用和如何使用。本章的最后一節(jié)將討論把驅(qū)動(dòng)器代碼從x86移植到其它平臺(tái)上可能碰到的其它一些典型問題。如果你遵循我提供的這些準(zhǔn)則,你的驅(qū)動(dòng)程序甚至可能在那些你未能進(jìn)行測(cè)試的平臺(tái)上編譯并運(yùn)行。使用標(biāo)準(zhǔn)C類型大部分程序員習(xí)慣于自由的使用諸如int和long這樣的標(biāo)準(zhǔn)類型,而編寫設(shè)備驅(qū)動(dòng)程序就必須細(xì)心地避免類型沖突和潛在的臭蟲。問題是,當(dāng)你需要“2個(gè)字節(jié)填充單位(filler)”或“表示4個(gè)字節(jié)字符串的某個(gè)東西”時(shí),你不能使用標(biāo)準(zhǔn)類型,因?yàn)橥ǔ5腃數(shù)據(jù)類型在不同的體系結(jié)構(gòu)上所占空間大小并不相同。例如,長(zhǎng)整數(shù)和指針類型在Alpha上和x86上所占空間大小就不一樣,下面的屏幕快照表明了這一點(diǎn):morgana% ./datasizesystem/machine: Linux i486sizeof(char) = 1sizeof(short) = 2sizeof(int) = 4sizeof(long) = 4sizeof(longlong) = 8sizeof(pointer) = 4wolf% ./datasizesystem/machine: Linux alphasizeof(char) = 1sizeof(short) = 2sizeof(int) = 4sizeof(long) = 8sizeof(longlong) = 8sizeof(pointer) = 8 sandra% ./datasizesystem/machine: Linux sparcsizeof(char) = 1sizeof(short) = 2sizeof(int) = 4sizeof(long) = 4sizeof(longlong) = 8sizeof(pointer) = 4 datasize程序是一個(gè)可以從在OReilly FTP站點(diǎn)的misc-progs目錄下獲得的小程序。在混合使用int和long類型時(shí),你必須小心,有時(shí)有很好的理由這樣做,一種情形就是內(nèi)存地址,一涉及到內(nèi)核,內(nèi)存地址就變得很特殊。雖然概念上地址是指針,但是通過使用整數(shù)類型,可以更好地實(shí)現(xiàn)內(nèi)存管理;內(nèi)核把物理內(nèi)存看做一個(gè)巨大的數(shù)組,內(nèi)存地址就是這個(gè)數(shù)組的索引。而且,一個(gè)指針很容易被取地址(deference),而使用整數(shù)表示內(nèi)存地址可以防止它們被取地址,這正是人們所希望的(比使用指針更安全)。因而,內(nèi)核中的地址屬于unsigned long類型,這是利用了指針和長(zhǎng)整數(shù)類型大小總是相同這一事實(shí),至少在所有Linux當(dāng)前支持的平臺(tái)上是這樣的。我們等著看看將來把Linux移植到不符合這一規(guī)則的平臺(tái)上的時(shí)候,會(huì)發(fā)生些什么。分配確定的空間大小給數(shù)據(jù)項(xiàng)有時(shí)內(nèi)核代碼需要指定大小的數(shù)據(jù)項(xiàng),或者用來匹配二進(jìn)制結(jié)構(gòu)* 讀分區(qū)表時(shí),執(zhí)行二進(jìn)制文件時(shí)或者解碼一個(gè)網(wǎng)絡(luò)包時(shí),就會(huì)發(fā)生這種情況?;蛘哂脕碓诮Y(jié)構(gòu)中插入填充字段對(duì)齊數(shù)據(jù)。為此目的,內(nèi)核提供如下的數(shù)據(jù)類型,它們都在頭文件中聲明,這個(gè)文件又被頭文件所包含:u8; /* 無符號(hào)字節(jié)(8位) */u16; /* 無符號(hào)字(16 位) */u32; /* 無符號(hào)32位數(shù)值 */u64; /* 無符號(hào)64位數(shù)值 */這些數(shù)據(jù)類型只能被內(nèi)核代碼所訪問(也即,在包含頭文件之前必須先定義_KERNEL_)。相應(yīng)的有符號(hào)類型也是存在的,但一般不用;如果你需要使用它們的話,只要把名字中的u替換為s就可以了。如果用戶空間的程序需要使用這些類型,可以在這些名字前面添加2個(gè)下劃線:_u8和其它類型是獨(dú)立于_KERNEL_定義的。例如,如果一個(gè)驅(qū)動(dòng)程序需要通過ioctl系統(tǒng)調(diào)用與一個(gè)運(yùn)行在用戶空間內(nèi)的程序交換二進(jìn)制結(jié)構(gòu)的話,頭文件必須將結(jié)構(gòu)中的32位字段定義為_u32。重要的是要記住這些類型特定于Linux,使用它們就會(huì)防礙軟件向其他Unix變體的移植。但是,有些情況下也需要明確說明數(shù)據(jù)大小,而標(biāo)準(zhǔn)頭文件(在每個(gè)Unix系統(tǒng)上都能找到的)并未聲明較合適的數(shù)據(jù)類型。你也許注意到,有時(shí)內(nèi)核也使用一般的數(shù)據(jù)類型,象unsigned int,用于那些大小與體系結(jié)構(gòu)無關(guān)的項(xiàng)。這通常是為了向后兼容。當(dāng)u32及其相關(guān)類型在1.1.67版本引入時(shí)開發(fā)者沒辦法把存在的數(shù)據(jù)類型改成新類型,因?yàn)楫?dāng)結(jié)構(gòu)字段和賦予的值之間類型不匹配時(shí),編譯器會(huì)發(fā)出警告+ 實(shí)際上,即使兩種類型僅是同一對(duì)象的不同名字,例如PC上的unsigned long和u32類型,編譯器也會(huì)發(fā)出類型不匹配的信號(hào)。Linus當(dāng)初可沒預(yù)料到為自己使用而編寫的這個(gè)操作系統(tǒng)會(huì)發(fā)展成為多平臺(tái)的;因此,一些舊的結(jié)構(gòu)的數(shù)據(jù)類型定義上不是很嚴(yán)格。接口特定的類型內(nèi)核中最常使用的數(shù)據(jù)類型有它們自己的typedef聲明,這樣就防止了任何移植上的問題。例如,進(jìn)程號(hào)(pid)通常使用pid_t,而不是int。使用pid_t屏蔽了任何實(shí)際數(shù)據(jù)類型之間可能的差別。我使用“接口特定”這種表述來指代特定數(shù)據(jù)項(xiàng)的編程接口。屬于指定“標(biāo)準(zhǔn)”類型的其它數(shù)據(jù)項(xiàng)也可以認(rèn)為是接口特定的。比如,一個(gè)jiffy計(jì)數(shù)總是屬于unsigned long類型的,獨(dú)立于它的實(shí)際大小你喜歡那么頻繁地使用jiffy_t類型么?這里我關(guān)注的是接口特定類型的第一類,那些以_t結(jié)尾的類型。_t類型完整的列表在頭文件中,但是該列表幾乎沒什么用。當(dāng)需要一個(gè)特定類型時(shí),你可以在你要調(diào)用的函數(shù)原型或者使用的數(shù)據(jù)結(jié)構(gòu)中找到它。只要你的驅(qū)動(dòng)程序使用了需要這種“定制”類型的函數(shù),又不遵循慣例的時(shí)候,編譯器都會(huì)發(fā)出一個(gè)警告;如果你打開-Wall編譯開關(guān)并且細(xì)心地去除了所有警告,你就可以自信你的代碼是可移植的了。_t數(shù)據(jù)項(xiàng)的主要問題是當(dāng)你需要打印它們的時(shí)候,并不總是容易選擇正確的printk或者printf格式,并且你在一種體系結(jié)構(gòu)上排除了的警告,在另一種體系結(jié)構(gòu)上可能又會(huì)出現(xiàn)。例如,當(dāng)size_t在一些平臺(tái)上是unsigned long,而在另外一些平臺(tái)上卻是unsigned int時(shí),你怎么打印它呢?任何時(shí)候,當(dāng)你需要打印一些特定接口的數(shù)據(jù)的時(shí)候,最行之有效的方法就是,把它強(qiáng)制轉(zhuǎn)換成最可能的類型(通常是long或unsigned long類型),然后把它用相應(yīng)的格式打印出來。這種做法不會(huì)產(chǎn)生錯(cuò)誤或者警告,因?yàn)楦袷胶皖愋拖喾?,而且你也不?huì)丟失數(shù)據(jù)位,因?yàn)閺?qiáng)制類型轉(zhuǎn)換要么是個(gè)空操作,要么是將該數(shù)據(jù)項(xiàng)向更大數(shù)據(jù)類型的擴(kuò)展。實(shí)際上,通常我們并不會(huì)去打印我們討論的這些數(shù)據(jù)項(xiàng),因此只有顯示調(diào)試信息時(shí)才會(huì)碰到這些問題。更經(jīng)常的,除了把接口特定的類型作為參數(shù)傳遞給庫(kù)或內(nèi)核函數(shù)以外,代碼僅僅只會(huì)對(duì)它們進(jìn)行些儲(chǔ)存和比較。雖然大多數(shù)情形下,_t類型都是正確的解決方案,但有時(shí)候正確的類型也可能并不存在。這會(huì)發(fā)生在一些還沒被拋棄的舊接口上。在內(nèi)核頭文件中我發(fā)現(xiàn)一處疑點(diǎn),為 I/O函數(shù)聲明數(shù)據(jù)類型時(shí)不是很嚴(yán)格(參見第8章“硬件管理”中的“平臺(tái)相關(guān)性”一節(jié))。這種不嚴(yán)格的類型定義主要是出于歷史上的原因,但在編寫代碼時(shí)卻會(huì)帶來問題。就我而言,我經(jīng)常在把參數(shù)交換給out函數(shù)時(shí)遇上麻煩;而如果定義了port_t,編譯器將會(huì)指出這些錯(cuò)誤。其它與移植有關(guān)的問題除了數(shù)據(jù)類型定義問題之外,如果想讓你編寫的驅(qū)動(dòng)程序能在不同的Linux平臺(tái)間移植的話,還必須注意到其它一些軟件上的問題:時(shí)間間隔在處理時(shí)間間隔時(shí),不能假定每秒一定有100個(gè)jiffy。雖然對(duì)當(dāng)前的Linux-x86而言這是對(duì)的,但并不是所有Linux平臺(tái)都是以100HZ運(yùn)行。如果你改變了HZ的數(shù)值,那么即使對(duì)x86,這種假設(shè)也是錯(cuò)誤的,何況沒人知道未來的內(nèi)核會(huì)發(fā)生些什么變化。使用jiffy計(jì)算時(shí)間間隔的時(shí)候,應(yīng)該把時(shí)間轉(zhuǎn)換成以HZ為單位。例如,為了檢測(cè)半秒鐘的超時(shí),可以把消逝的時(shí)間和HZ/2作比較。更常見的,與msec毫秒對(duì)應(yīng)的jiffy的數(shù)目總是msec*HZ/1000。許多的網(wǎng)絡(luò)驅(qū)動(dòng)程序在移植到Alpha上時(shí)都必須修正該細(xì)節(jié);有些開始是為PC設(shè)計(jì)的驅(qū)動(dòng)程序給超時(shí)明確定義了一個(gè)jiffy值,但是Alpha卻有著不同的HZ數(shù)值。頁(yè)大小使用內(nèi)存時(shí),要記住內(nèi)存頁(yè)的大小為PAGE_SIZE字節(jié),而不是4KB。假設(shè)頁(yè)大小就是4KB并硬編碼該數(shù)值是PC程序員常犯的錯(cuò)誤Alpha頁(yè)大小是這的兩倍。相關(guān)的宏有PAGE_SIZE和PAGE_SHIFT。后者包含要得到一個(gè)地址所在頁(yè)的頁(yè)號(hào)時(shí)需要對(duì)該地址右移的位數(shù)。對(duì)當(dāng)前的4KB和8KB的頁(yè),這個(gè)數(shù)值通常是12或者13。這些宏在頭文件中定義。讓我們來看一種簡(jiǎn)單的情況。如果驅(qū)動(dòng)程序需要16KB空間來存放臨時(shí)數(shù)據(jù),它不應(yīng)當(dāng)指定get_free_pages函數(shù)的參數(shù)order(“2”的冪)。需要一種可移植的解決辦法。此時(shí),可以使用條件編譯#ifdef _alpha_,但這只適用于已知的平臺(tái),而如果要支持別的平臺(tái),它就不能奏效了。我建議使用下面的代碼:buf = get_free_pages(GFP_KERNEL, 14 - PAGE_SHIFT, 0 /*dma*/);或者,更好一些的代碼:int order = (14 - PAGE_SHIFT 0) ? 14 - PAGE_SHIFT : 0;buf = get_free_pages(GFP_KERNEL, order, 0 /*dma*/);兩種解決辦法都利用了16KB等于114這一常識(shí)。兩個(gè)數(shù)的商就是它們對(duì)數(shù)的差(的冪),而14和PAGE_SHIFT都是冪。第二種解決辦法就更好,因?yàn)樗梢苑乐拱岩粋€(gè)負(fù)的order值傳遞給get_free_pages函數(shù);order值時(shí)在編譯時(shí)就計(jì)算好的,沒有運(yùn)行時(shí)的額外開銷,而且,上面給出的實(shí)現(xiàn)方法是不依賴于PAGE_SIZE來分配任何2的冪次大小的內(nèi)存空間的安全方法。字節(jié)序要小心的是不要主觀假設(shè)字節(jié)序。雖然PC是按低字節(jié)優(yōu)先的方式存儲(chǔ)多個(gè)字節(jié)(“小印地安,little endian”),但是大多數(shù)更高級(jí)的平臺(tái)是以另一種方式工作的(“大印地安,big endian”)。雖然好的程序不會(huì)依賴于字節(jié)序,但有時(shí)驅(qū)動(dòng)程序需要?jiǎng)?chuàng)建占一個(gè)字節(jié)以上的整數(shù),或者相反(一個(gè)字節(jié)以下)。此時(shí),代碼中就應(yīng)該將頭文件包含進(jìn)來,并且檢測(cè)頭文件中是否定義了_BIG_ENDIAN或_LITTLE_ENDIAN。起始的下劃線在Linux-1.2之后版本的頭文件中卻去掉了,在頭文件后再包含scull示例程序中的頭文件sysdep.h就可以修正這個(gè)不兼容。當(dāng)字節(jié)序相關(guān)問題與網(wǎng)絡(luò)傳輸有聯(lián)系的時(shí)候,就應(yīng)當(dāng)使用下面各種函數(shù)來進(jìn)行16位和32位數(shù)值的轉(zhuǎn)換,這些函數(shù)也都是在頭文件中定義的:unsigned long ntohl(unsigned long);unsigned short ntohs(unsigned short);unsigned long htonl(unsigned long);unsigned short htons(unsigned short);在網(wǎng)絡(luò)程序員當(dāng)中,這些函數(shù)是眾所周知的。它們得名于“Network TO Host Long”(從網(wǎng)絡(luò)到主機(jī)的long類型)或類似的短語(yǔ)。2.1.10版的內(nèi)核增加了cpu-to-little-endian和cpu-to-big-endian兩種轉(zhuǎn)換,2.1.43版的內(nèi)核在這方面又加以擴(kuò)充。新增的一些實(shí)用函數(shù)將在第17章“近期發(fā)展”中的“轉(zhuǎn)換函數(shù)”一節(jié)中描述。數(shù)據(jù)對(duì)齊在編寫可移植代碼時(shí)最后一個(gè)值得考慮的問題是如何訪問未對(duì)齊數(shù)據(jù)例如,當(dāng)一個(gè)4字節(jié)的數(shù)值被儲(chǔ)存在不是4字節(jié)的整數(shù)倍的地址中時(shí),如何將它讀出來。PC的用戶常常訪問未對(duì)齊的數(shù)據(jù)項(xiàng),但并不是所有體系結(jié)構(gòu)都允許這樣做。舉個(gè)例子,在Alpha上,每當(dāng)程序試圖傳送未對(duì)齊數(shù)據(jù)時(shí),都會(huì)產(chǎn)生一個(gè)異常。如果你需要訪問未對(duì)齊數(shù)據(jù),可以使用下面這些宏:#include get_unaligned(ptr);put_unaligned(val,ptr);這些宏是與類型無關(guān)的。對(duì)各種數(shù)據(jù)項(xiàng),不管它是1字節(jié),2字節(jié),4字節(jié)還是8字節(jié),這些宏都有效。在2.0版以前的內(nèi)核并不提供這些宏,但在1.2版的內(nèi)核在頭文件sysdep.h中對(duì)它們作了定義。一個(gè)通用準(zhǔn)則就對(duì)顯式的常數(shù)值持懷疑態(tài)度。通常,使用預(yù)編譯的宏來使代碼參數(shù)化使代碼更通用。雖然我不能在此列出所有參數(shù)化的值,但你可以在頭文件中找到正確的提示。不幸的是,有些地方問題還沒有得到解決,例如對(duì)磁盤扇區(qū)數(shù)據(jù)的處理。出于歷史的原因,Linux只能處理.5KB的磁盤扇區(qū)。所幸的是,現(xiàn)存所有設(shè)備都滿足這個(gè)限制。目前正在逐漸地改進(jìn)代碼以支持不同的扇區(qū)大小。但要找到代碼中所有在.5KB的假設(shè)下進(jìn)行硬編碼的地方卻非常困難。扇區(qū)大小的問題將在第12章“加載塊設(shè)備驅(qū)動(dòng)程序”中進(jìn)一步描述??焖賲⒖荚诒菊乱肓巳缦乱恍┓?hào):#i
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 市場(chǎng)投訴處理管理制度
- 制造行業(yè)設(shè)備管理制度
- DB62T 4461-2021 小麥品種 隴紫麥2號(hào)
- 蟲災(zāi)治理方案(3篇)
- 宗祠修繕募資方案(3篇)
- 政協(xié)2022工作報(bào)告
- 物業(yè)資產(chǎn)利用方案(3篇)
- 智能社區(qū)標(biāo)準(zhǔn)商品房租賃服務(wù)協(xié)議
- 成都離婚協(xié)議書模板與婚后財(cái)產(chǎn)分割監(jiān)督合同
- 草莓苗綠色種植技術(shù)引進(jìn)與推廣合同
- 第六章+平面向量及其應(yīng)用+小結(jié) 高一下學(xué)期數(shù)學(xué)人教A版(2019)必修第二冊(cè)
- 2024年山東省聊城市冠縣中考一模英語(yǔ)試題(原卷版)
- 山東省青島市平度市2024屆中考二模語(yǔ)文試題含解析
- 國(guó)開可編程控制器應(yīng)用形考實(shí)訓(xùn)任務(wù)六
- 周志華-機(jī)器學(xué)習(xí)-Chap01緒論-課件
- 電力儲(chǔ)能用鋰離子電池
- 華為MPR+LTC項(xiàng)目項(xiàng)目總體方案+P183
- 自然資源調(diào)查監(jiān)測(cè)技能競(jìng)賽理論考試題庫(kù)大全-中(多選題)
- 水質(zhì)監(jiān)測(cè)服務(wù)水質(zhì)自動(dòng)監(jiān)測(cè)系統(tǒng)運(yùn)行維護(hù)方案
- 小學(xué)生創(chuàng)新大賽創(chuàng)新設(shè)計(jì)案例
- MOOC 斷層影像解剖學(xué)-山東大學(xué) 中國(guó)大學(xué)慕課答案
評(píng)論
0/150
提交評(píng)論