linux內(nèi)核的移植性非常好, 目前的內(nèi)核也支持非常多的體系結(jié)構(gòu)(有20多個(gè)).
但是剛開始時(shí), linux也只支持 intel i386 架構(gòu), 從 v1.2版開始支持 Digital Alpha, Intel x86, MipS和SPARC(雖然支持的還不是很完善).
從 v2.0版本開始加入了對(duì) Motorala 68K和PowerPC的官方支持, v2.2版本開始新增了 ARMS, IBM S390和UltraspARC的支持.
v2.4版本支持的體系結(jié)構(gòu)數(shù)達(dá)到了15個(gè), v2.6版本支持的體系結(jié)構(gòu)數(shù)目提高到了21個(gè).
目前的我使用的系統(tǒng)是 Fedora20, 支持的體系結(jié)構(gòu)有31個(gè)之多.(源碼樹中 arch目錄下有支持的體系結(jié)構(gòu), 每種體系結(jié)構(gòu)一個(gè)文件夾)
考慮到內(nèi)核支持如此之多的架構(gòu), 在內(nèi)核開發(fā)的時(shí)候就需要考慮編碼的可移植性.
提高可移植性最重要的就是要搞明白不同體系結(jié)構(gòu)之間究竟是什么對(duì)移植代碼的影響比較大.
主要內(nèi)容:
這里的字是指處理器能夠一次完成處理的數(shù)據(jù). 字長(zhǎng)即使處理器能夠一次完成處理的數(shù)據(jù)的最大長(zhǎng)度.
目前的處理器主要有32位和64為2種, 注意這里的32位和64位并不是指操作系統(tǒng)的版本, 而是指處理器的能力.
一般來說, 32位的處理器只能安裝32位的操作系統(tǒng), 而64位的處理器可以安裝32位的操作系統(tǒng), 也可以安裝64位的操作系統(tǒng).
對(duì)于一種體系結(jié)構(gòu)來說, 處理器通用寄存器(general-purpose registers, GPR)的大小和它的字長(zhǎng)是相同的.
C語言定義的long類型總是對(duì)等于機(jī)器的字長(zhǎng), 而int型有時(shí)會(huì)比字長(zhǎng)小.
內(nèi)核編碼中涉及到字長(zhǎng)的部分時(shí), 牢記以下準(zhǔn)則:
此外, 操作系統(tǒng)有個(gè)簡(jiǎn)單的助記符來描述此系統(tǒng)中數(shù)據(jù)類型的大小.
編寫可移植性代碼時(shí), 內(nèi)核中的數(shù)據(jù)類型有以下3點(diǎn)需要注意:
2.1 不透明類型linux內(nèi)核中定義了很多不透明類型, 它們是在C語言標(biāo)準(zhǔn)類型上的一個(gè)封裝, 比如 pid_t, uid_t, gid_t 等等.
例如, pid_t的定義可以在源碼中找到:
typedef __kernel_pid_t pid_t; /* include/linux/types.h */typedef int __kernel_pid_t; /* arch/asm/include/asm/posix_types.h */
使用這些不透明類型時(shí), 以下原則需要注意:
除了不透明類型, linux內(nèi)核中還定義了一系列長(zhǎng)度明確的數(shù)據(jù)類型, 參見 include/asm-generic/int-l64.h 或者 include/asm-generic/int-ll64.h
typedef signed char s8;typedef unsigned char u8;typedef signed short s16;typedef unsigned short u16;typedef signed int s32;typedef unsigned int u32;typedef signed long s64;typedef unsigned long u64;
上面這些類型只能在內(nèi)核空間使用, 用戶空間無法使用. 用戶空間有對(duì)應(yīng)的變量類型, 名稱前多了2個(gè)下劃線:
typedef __signed__ char __s8;typedef unsigned char __u8;typedef __signed__ short __s16;typedef unsigned short __u16;typedef __signed__ int __s32;typedef unsigned int __u32;typedef __signed__ long __s64;typedef unsigned long __u64;2.3 char類型
之所以把char類型單獨(dú)拿出來說明, 是因?yàn)閏har類型在不同的體系結(jié)構(gòu)中, 有時(shí)默認(rèn)是帶符號(hào)的, 有時(shí)是不帶符號(hào)的.
比如, 最簡(jiǎn)單的例子:
/* * 某些體系結(jié)構(gòu)中, char類型默認(rèn)是帶符號(hào)的, 那么下面 i 的值就為 -1 * 某些體系結(jié)構(gòu)中, char類型默認(rèn)是不帶符號(hào)的, 那么下面 i 的值就為 255, 與預(yù)期可能有差別!!! */char i = -1;
避免上述問題的方法就是, 給char類型賦值時(shí), 明確是否帶符號(hào), 如下:
signed char i = -1; /* 明確 signed, i 的值在哪種體系結(jié)構(gòu)中都是 -1 */unsigned char i = 255; /* 明確 unsigned, i 的值在哪種體系結(jié)構(gòu)中都是 255 */3. 數(shù)據(jù)對(duì)齊
數(shù)據(jù)對(duì)齊也是增強(qiáng)可移植性的一個(gè)重要方面(有的體系結(jié)構(gòu)對(duì)數(shù)據(jù)對(duì)齊要求非常嚴(yán)格, 載入未對(duì)齊的數(shù)據(jù)可導(dǎo)致性能下降, 甚至錯(cuò)誤).
數(shù)據(jù)對(duì)齊的意思就是: 數(shù)據(jù)的內(nèi)存地址可以被 4 整除
1. 通過指針轉(zhuǎn)換類型時(shí), 不要轉(zhuǎn)換長(zhǎng)度不一樣的類型, 比如下面的代碼有可能出錯(cuò)
/* * 下面的代碼將一個(gè)變量從 char 類型轉(zhuǎn)換為 unsigned long 類型, * char 類型只占 1個(gè)字節(jié), 它的地址不一定能被4整除, 轉(zhuǎn)換為 4個(gè)字節(jié)或者8個(gè)字節(jié)的 usigned long之后, * 導(dǎo)致 unsigned long 出現(xiàn)數(shù)據(jù)不對(duì)齊的現(xiàn)象. */char wolf[] = "Like a wolf";char *p = &wolf[1];unsigned long p1 = *(unsigned long*) p;
2. 對(duì)于數(shù)組, 安裝基本數(shù)據(jù)類型進(jìn)行對(duì)齊就行.(數(shù)組元素的存放在內(nèi)存中是連續(xù)的, 第一個(gè)對(duì)齊了, 后面的都自動(dòng)對(duì)齊了)
3. 對(duì)于聯(lián)合體, 長(zhǎng)度最大的數(shù)據(jù)對(duì)齊就可以了
4. 對(duì)于結(jié)構(gòu)體, 保證結(jié)構(gòu)體中每個(gè)元素能夠正確對(duì)齊即可
如果結(jié)構(gòu)體中的元素沒有對(duì)齊, 編譯器會(huì)自動(dòng)填充結(jié)構(gòu)體, 保證它是對(duì)齊的. 比如下面的代碼, 預(yù)計(jì)應(yīng)該輸出12, 實(shí)際卻輸出了24
我的代碼運(yùn)行環(huán)境: Fedora20 x86_64
/****************************************************************************** * @file : struct_align.c * @author : wangyubin * @date : 2014-01-09 * * @brief : * history : init ******************************************************************************/#include <stdio.h>struct animal_struct{ char dog; /* 1個(gè)字節(jié) */ unsigned long cat; /* 8個(gè)字節(jié) */ unsigned short pig; /* 2個(gè)字節(jié) */ char fox; /* 1個(gè)字節(jié) */};int main(int argc, char *argv[]){ /* 在我的64bit 系統(tǒng)中是按8位對(duì)齊, 下面的代碼輸出 24 */ printf ("sizeof(animal_struct)=%d/n", sizeof(struct animal_struct)); return 0;}測(cè)試方法:
gcc -o test struct_align.c./test # 輸出24
結(jié)構(gòu)體應(yīng)該被填充成如下形式:
struct animal_struct{ char dog; /* 1個(gè)字節(jié) */ /* 此處填充了7個(gè)字節(jié) */ unsigned long cat; /* 8個(gè)字節(jié) */ unsigned short pig; /* 2個(gè)字節(jié) */ char fox; /* 1個(gè)字節(jié) */ /* 此處填充了5個(gè)字節(jié) */ };通過調(diào)整結(jié)構(gòu)體中元素順序, 可以減少填充的字節(jié)數(shù), 比如上述結(jié)構(gòu)體如果定義成如下順序:
struct animal_struct{ unsigned long cat; /* 8個(gè)字節(jié) */ unsigned short pig; /* 2個(gè)字節(jié) */ char dog; /* 1個(gè)字節(jié) */ char fox; /* 1個(gè)字節(jié) */};那么為了保證8位對(duì)齊, 只需在后面補(bǔ)充 4位即可:
struct animal_struct{ unsigned long cat; /* 8個(gè)字節(jié) */ unsigned short pig; /* 2個(gè)字節(jié) */ char dog; /* 1個(gè)字節(jié) */ char fox; /* 1個(gè)字節(jié) */ /* 此處填充了4個(gè)字節(jié) */ };調(diào)整后的代碼會(huì)輸出 16, 不是之前的24
#include <stdio.h>struct animal_struct{ unsigned long cat; /* 8個(gè)字節(jié) */ unsigned short pig; /* 2個(gè)字節(jié) */ char dog; /* 1個(gè)字節(jié) */ char fox; /* 1個(gè)字節(jié) */};int main(int argc, char *argv[]){ /* 在我的64bit 系統(tǒng)中是按8位對(duì)齊, 下面的代碼輸出 16 */ printf ("sizeof(animal_struct)=%d/n", sizeof(struct animal_struct)); return 0;}測(cè)試方法:
gcc -o test struct_align.c./test # 輸出16
注意: 雖然調(diào)整結(jié)構(gòu)體中元素的順序可以減少填充的字節(jié), 從而降低內(nèi)存的消耗.
但是對(duì)于內(nèi)核中已有的那些結(jié)構(gòu), 千萬不能隨便調(diào)整其元素順序, 因?yàn)閮?nèi)核中很多現(xiàn)存的方法都是通過元素在結(jié)構(gòu)體中位置偏移來獲取元素的.
4. 字節(jié)順序字節(jié)順序其實(shí)只有2種:
比如占有四個(gè)字節(jié)的整數(shù)的二進(jìn)制表示如下:
00000001 00000002 00000003 00000004
內(nèi)存地址方向: 高位 <--------------------> 低位
little-endian 表示如下:
00000001 00000002 00000003 00000004
big-endian 表示如下:
00000004 00000003 00000002 00000001
判斷一個(gè)體系結(jié)構(gòu)是 big-endian 還是 little-endian 非常簡(jiǎn)單.
int x = 1; /* 二進(jìn)制 00000000 00000000 00000000 00000001 *//* * 內(nèi)存地址方向: 高位 <--------------------> 低位 * little-endian 表示: 00000000 00000000 00000000 00000001 * big-endian 表示: 00000001 00000000 00000000 00000000 */if (*(char *) &x == 1) /* 這句話把int型轉(zhuǎn)為char型, 相當(dāng)于只取了int型的最低8bit */ /* little-endian */else /* big-endian */5. 時(shí)間
內(nèi)核中使用到時(shí)間相關(guān)概念時(shí), 為了提高可移植性, 不要使用時(shí)間中斷的發(fā)生頻率(也就是每秒產(chǎn)生的jiffies), 而應(yīng)該使用 HZ 來正確使用時(shí)間.
關(guān)于 jiffies 和 HZ 的概念, 可以參考之前的博客: 《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》讀書筆記(十一)- 定時(shí)器和時(shí)間管理
6. 頁長(zhǎng)度當(dāng)處理用頁管理的內(nèi)存時(shí), 不要既定頁的長(zhǎng)度為 4KB, 在不同的體系結(jié)構(gòu)中長(zhǎng)度會(huì)不一樣.
而應(yīng)該使用 PAGE_SIZE 以字節(jié)數(shù)來表示頁長(zhǎng)度, 使用 PAGE_SHIFT 表示從最右端屏蔽了多少位能夠得到該地址對(duì)應(yīng)的頁的頁號(hào).
PAGE_SIZE 和 PAGE_SHIFT 都是宏, 定義在 include/asm-generic/page.h 中
下表是一些體系結(jié)構(gòu)中頁長(zhǎng)度:
體系結(jié)構(gòu) | PAGE_SHIFT | PAGE_SIZE |
| alpha | 13 | 8KB |
| arm | 12, 14, 15 | 4KB, 16KB, 32KB |
| avr | 12 | 4KB |
| cris | 13 | 8KB |
| blackfin | 12 | 16KB |
| h8300 | 14 | 4KB |
| 12 | 4KB, 8KB, 16KB, 32KB | |
| m32r | 12, 13, 14, 16 | 4KB |
| m68k | 12 | 4KB, 8KB |
| m68knommu | 12, 13 | 4KB |
| mips | 12 | 4KB |
| min10300 | 12 | 4KB |
| parisc | 12 | 4KB |
| powerpc | 12 | 4KB |
| s390 | 12 | 4KB |
| sh | 12 | 4KB |
| sparc | 12, 13 | 4KB, 8KB |
| um | 12 | 4KB |
| x86 | 12 | 4KB |
| xtensa | 12 | 4KB |
還有最后一個(gè)和可移植性相關(guān)的注意點(diǎn)就是處理器對(duì)代碼的執(zhí)行順序, 在有些體系結(jié)構(gòu)中, 處理器并不是嚴(yán)格按照代碼編寫的順序執(zhí)行的,
可能為了優(yōu)化性能或者其他原因, 處理器執(zhí)行指令的順序與編寫的代碼的順序稍有出入.
如果我們的某段代碼需要嚴(yán)格的執(zhí)行順序, 需要在代碼中使用 rmb() wmb() 等內(nèi)存屏障來確保處理器的執(zhí)行順序.
關(guān)于rmb和wmb可以參考之前的博客: 《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》讀書筆記(十)- 內(nèi)核同步方法 第 11 小節(jié)
8. SMP, 內(nèi)核搶占, 高端內(nèi)存SMP, 內(nèi)核搶占和高端內(nèi)存本身雖然和可移植性沒有太大的關(guān)系, 但它們都是內(nèi)核中重要的配置選項(xiàng),
如果編碼時(shí)能夠考慮到這些的話, 那么即使內(nèi)核修改SMP等這些配置選項(xiàng), 我們的代碼仍然可以安全可靠的運(yùn)行.
所以, 在編寫內(nèi)核代碼時(shí)最好加上如下假設(shè):
編寫簡(jiǎn)潔, 可移植性的代碼還需要通過實(shí)踐來積累經(jīng)驗(yàn), 上面的準(zhǔn)則可以作為代碼是否滿足可移植性的一些檢測(cè)條件.
書中還提到的2點(diǎn)注意事項(xiàng), 我覺得不僅是編寫內(nèi)核代碼, 編寫任何代碼時(shí), 都應(yīng)該注意:
雖然編寫可移植性代碼需要遵守這么多的原則, 但是不能畏懼, 在學(xué)習(xí)內(nèi)核開發(fā)的過程中, 只有不斷的嘗試, 不斷的犯錯(cuò), 才能確實(shí)的掌握內(nèi)核.
新聞熱點(diǎn)
疑難解答
圖片精選