国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > Linux > 正文

《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》讀書筆記(十九)- 可移植性

2024-06-28 13:28:33
字體:
供稿:網(wǎng)友
《linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》讀書筆記(十九)- 可移植性

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)容:

  • 字長(zhǎng)
  • 數(shù)據(jù)類型
  • 數(shù)據(jù)對(duì)齊
  • 字節(jié)順序
  • 時(shí)間
  • 頁長(zhǎng)度
  • 處理器順序
  • SMP, 內(nèi)核搶占, 高端內(nèi)存
  • 總結(jié)

1. 字長(zhǎng)

這里的字是指處理器能夠一次完成處理的數(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)小.

  • 32位的體系結(jié)構(gòu)中, int型和long型都是32位的
  • 64位的體系結(jié)構(gòu)中, int型是32位的, long型是64位的.

內(nèi)核編碼中涉及到字長(zhǎng)的部分時(shí), 牢記以下準(zhǔn)則:

  1. ANSI C標(biāo)準(zhǔn)規(guī)定, 一個(gè)char的長(zhǎng)度一定是一個(gè)字節(jié)(8位)
  2. linux當(dāng)前所支持的體系結(jié)構(gòu)中, int型都是32位的
  3. linux當(dāng)前所支持的體系結(jié)構(gòu)中, short型都是16位的
  4. linux當(dāng)前所支持的體系結(jié)構(gòu)中, 指針和long型的長(zhǎng)度不定, 在32位和64位中變化
  5. 不能假設(shè) sizeof(int) == sizeof(long)
  6. 類似的, 不能假定 指針的長(zhǎng)度和int型相同.

此外, 操作系統(tǒng)有個(gè)簡(jiǎn)單的助記符來描述此系統(tǒng)中數(shù)據(jù)類型的大小.

  • LLP64 :: 64位的Windows, long類型和指針都是64位
  • LP64 :: 64位的Linux, long類型和指針都是64位
  • ILP32 :: 32位的Linux, int類型, long類型和指針都是32位
  • ILP64 :: int類型, long類型和指針都是64位(非Linux)

2. 數(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í), 以下原則需要注意:

  1. 不要假設(shè)該類型的長(zhǎng)度(那怕通過源碼看到了它的C語言類型), 這些類型在不同體系結(jié)構(gòu)中可能長(zhǎng)度會(huì)變, 內(nèi)核開發(fā)者也有可能修改它們
  2. 不要將這些不透明類型轉(zhuǎn)換為C標(biāo)準(zhǔn)類型來使用
  3. 編程時(shí)保證不透明類型實(shí)際存儲(chǔ)空間或者格式發(fā)生變化時(shí)代碼不受影響

2.2 長(zhǎng)度確定的類型

除了不透明類型, 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種:

  • 低位優(yōu)先 :: little-endian 數(shù)據(jù)由低位地址->高位地址存放
  • 高位優(yōu)先 :: big-endian 數(shù)據(jù)由高位地址->低位地址存放

比如占有四個(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_SIZEPAGE_SHIFT 都是宏, 定義在 include/asm-generic/page.h

下表是一些體系結(jié)構(gòu)中頁長(zhǎng)度:

體系結(jié)構(gòu)

PAGE_SHIFT

PAGE_SIZE

alpha138KB
arm12, 14, 154KB, 16KB, 32KB
avr124KB
cris138KB
blackfin1216KB
h8300144KB
124KB, 8KB, 16KB, 32KB
m32r12, 13, 14, 164KB
m68k124KB, 8KB
m68knommu12, 134KB
mips124KB
min10300124KB
parisc124KB
powerpc124KB
s390124KB
sh124KB
sparc12, 134KB, 8KB
um124KB
x86124KB
xtensa124KB

7. 處理器順序

還有最后一個(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è):

  • 假設(shè)代碼會(huì)在SMP系統(tǒng)上運(yùn)行, 要正確選擇和使用鎖
  • 假設(shè)代碼會(huì)在支持內(nèi)核搶占的情況下運(yùn)行, 要正確使用鎖和內(nèi)核搶占語句
  • 假設(shè)代碼會(huì)運(yùn)行在使用高端內(nèi)存(非永久映射內(nèi)存)的系統(tǒng)上, 必要時(shí)使用 kmap()

9. 總結(jié)

編寫簡(jiǎn)潔, 可移植性的代碼還需要通過實(shí)踐來積累經(jīng)驗(yàn), 上面的準(zhǔn)則可以作為代碼是否滿足可移植性的一些檢測(cè)條件.

書中還提到的2點(diǎn)注意事項(xiàng), 我覺得不僅是編寫內(nèi)核代碼, 編寫任何代碼時(shí), 都應(yīng)該注意:

  • 編碼盡量選取最大公因子 :: 假定任何事情都有可能發(fā)生, 任何潛在的約束也都存在
  • 編碼盡量選取最小公約數(shù) :: 不要假定給定的內(nèi)核特性是可用的, 僅僅需要最小的體系結(jié)構(gòu)功能

雖然編寫可移植性代碼需要遵守這么多的原則, 但是不能畏懼, 在學(xué)習(xí)內(nèi)核開發(fā)的過程中, 只有不斷的嘗試, 不斷的犯錯(cuò), 才能確實(shí)的掌握內(nèi)核.


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 阿荣旗| 浮梁县| 六枝特区| 伊宁县| 易门县| 潼南县| 濉溪县| 五家渠市| 平果县| 芦山县| 东莞市| 融水| 广汉市| 临漳县| 张家口市| 普安县| 特克斯县| 德令哈市| 江安县| 鄂托克前旗| 祁门县| 沿河| 鄄城县| 东光县| 丹阳市| 赫章县| 章丘市| 新丰县| 湘阴县| 白水县| 肇东市| 木兰县| 海门市| 吉木萨尔县| 巴里| 安阳市| 天峻县| 临江市| 漯河市| 开平市| 临朐县|