由於前面的學習中有用到 第十一章 核心資料結構型別 的知識,所以我先看了。要點如下: .
將linux 移植到新的體系結構時,開發者遇到的若干問題都與不正確的資料型別有關。堅持使用嚴格的資料型別和使用 -Wall -Wstrict-prototypes 進行編譯可能避免大部分的 bug。

核心資料使用的資料型別主要分為 3 個型別: 標準 C 語言型別確定大小的型別特定核心物件的型別

標準 C 語言型別

當需要“一個2位元組填充符”或“用一個4位元組字串來代表某個東西”,就不能使用標準C語言型別,因為在不同的體系結構,C 語言的資料型別所佔的空間大小不同。後面的datasize 程式實驗展示了使用者空間各種 C 的資料型別在當前平臺所佔空間的大小。而且有的構架,核心空間和使用者空間的C 資料型別所佔空間大小也可能不同。kdatasize模組顯示了當前模組的核心空間C 資料型別所佔空間大小。

儘管概念上地址是指標,但使用一個無符號整型可以更好地實現記憶體管理; 核心把實體記憶體看成一個巨型陣列, 記憶體地址就是該陣列的索引。 我們可以方便地對指標取值,但直接處理記憶體地址時,我們幾乎從不會以這種方式對他取值。使用一個整數型別避免了這種取值,因此避免了 bug。所以,利用至少在 Linux 目前支援的所有平臺上,指標和長整型始終是相同大小的這一事實,核心中記憶體地址常常是 unsigned long

C99 標準定義了 intptr_t 和 uintptr_t 型別,它們是能夠儲存指標值的整型變數。但沒在 2.6 核心中幾乎沒使用。

確定大小的型別

當需要知道你定義的資料的大小時,可以使用核心提供的下列資料型別(所有的資料宣告在 <asm/types.h>, 被包含在 <linux/types.h> ):

u8; /* unsigned byte (8 bits) */
u16; /* unsigned word (16 bits) */
u32; /* unsigned 32-bit value */
u64; /* unsigned 64-bit value */

/*雖然很少需要有符號型別,但是如果需要,只要用 s 代替 u*/

若一個使用者空間程式需要使用這些型別,可在符號前加一個雙下劃線: __u8和其它型別是獨立於 __KERNEL__ 定義的。

這些型別是 Linux 特定的,它們妨礙了移植軟體到其他的 Unix 機器。新的編譯器系統支援 C99-標準 型別,如 uint8_t 和 uint32_t。若考慮移植性,使用這些型別比 Linux特定的變體要好。

介面特定的型別(_t 型別

核心中最常用的資料型別由它們自己的 typedef 宣告,阻止了任何移植性問題。“介面特定(interface-specific)”由某個庫定義的一種資料型別, 以便為了某個特定的資料結構提供介面。很多 _t 型別在 <linux/types.h> 中定義。

注意:近來已經很少定義新的介面特定的型別。有許多核心開發者已經不再喜歡使用 typedef 語句,他們寧願看到程式碼中直接使用的真實型別資訊。很多老的介面特定型別在核心中保留,他們不會很快消失。

即使沒有定義介面特定型別,也應該始終是用和核心其他部分保持一致、適當的資料型別。只要驅動使用了這種“定製”型別的函式,但又不遵照約定,編譯器會發出警告,這時使用 -Wall 編譯器選項並小心去除所有的警告,就可以確信程式碼的可移植性了。

_t 型別的主要問題是:列印它們時,常常不容易選擇正確的 printk 或 printf 格式。列印介面特定的資料的最好方法是:將其強制轉換為可能的最大型別(常常是 long 或 unsigned long ) 並用相應的格式列印。

其他移植性問題

移植的一個通常規則是:避免使用顯式的常量值,要使用預處理巨集使常量值引數化。

時間間隔

當處理時間間隔時,不要假定每秒的jiffies個數,不是每個 Linux 平臺都以固定的速度執行.當計算時間間隔時,要使用 HZ ( 每秒的定時器中斷數 ) 來標定你的時間。s3c2410的HZ值預設為200。

頁大小

當使用記憶體時,記住一個記憶體頁是 PAGE_SIZE 位元組, 不是 4KB。相關的巨集定義是 PAGE_SIZE 和 PAGE_SHIT(包含將一個地址移位來獲得它的頁號的位數),在 <asm/page.h> 中定義。如果使用者空間程式需要這些資訊,可以使用 getpagesize 庫函式。

若一個驅動需要 16 KB 來暫存資料,一個可移植得解決方法是 get_order:

#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);/*get_order 的引數必須是 2 的冪*/

位元組序

不要假設位元組序。 程式碼應該編寫成不依賴所操作資料的位元組序的方式。

標頭檔案 <asm/byteorder.h> 定義:

#ifdef __ARMEB__
#include <linux/byteorder/big_endian.h>
#else
#include <linux/byteorder/little_endian.h>
#endif

<linux/byteorder/big_endian.h>中定義了__BIG_ENDIAN ,而在<linux/byteorder/little_endian.h>中定義了__LITTLE_ENDIAN,這些依賴處理器的位元組序當處理位元組序問題時,需要編碼一堆類似 #ifdef __LITTTLE_ENDIAN 的條件語句。

但是還有一個更好的方法:Linux 核心有一套巨集定義來處理處理器位元組序和特定位元組序之間的轉換。例如:

u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);
/*這些巨集定義將一個CPU使用的值轉換成一個無符號的32位小頭數值,無論 CPU 是大端還是小端,也不管是不是32 位處理器。在沒有轉換工作需要做時,返回未修改的值。*/

/*有很多類似的函式在 <linux/byteorder/big_endian.h> 和 <linux/byteorder/little_endian.h> 中定義*/

編寫可移植程式碼而值得考慮的最後一個問題是如何訪問未對齊的資料。存取不對齊的資料應當使用下列巨集:

#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);

這些巨集是無型別的,並對各總資料項,不管是 1、2、4或 8 個位元組,他們都有效,並且在所有核心版本中都有定義。

關於對齊的另一個問題是資料結構的跨平臺移植性。同樣的資料結構在不同的平臺上可能被不同地編譯。為了編寫可以跨體系移植的資料結構,應當始終強制資料項的自然對齊。自然對齊(natural alignment)指的是:資料項大小的整數倍的地址上儲存資料項。 應當使用填充符避免強制自然對齊時編譯器移動資料結構的欄位,在資料結構中留下空洞。

dataalign 程式實驗展示了編譯器如何強制對齊。

為了目標處理器的良好效能,編譯器可能悄悄地插入填充符到結構中,來保證每個成員是對齊的。若定義一個和裝置要求的結構體相匹配結構,自動填充符會破壞這個意圖。解決這個問題的方法是告訴編譯器這個結構必須是"緊湊的", 不能增加填充符。例如下列的定義:

struct
{
        u16 id;
        u64 lun;
        u16 reserved1;
        u32 reserved2;
}
__attribute__ ((packed)) scsi;

/*如果在 64-位平臺上編譯這個結構,若沒有 __attribute__ ((packed)), lun 成員可能在前面被新增 2 個或 6 個填充符位元組。指標和錯誤值*/

你還可以在利用ARM9和USB攝像頭進行視訊採集的servfox原始碼的spcaframe.h標頭檔案中找到這種方法的實際應用:

struct frame_t{
    char header[5];
    int nbframe;
    double seqtimes;
    int deltatimes;
    int w;
    int h;
    int size;
    int format;
    unsigned short bright;
    unsigned short contrast;
    unsigned short colors;
    unsigned short exposure;
    unsigned char wakeup;
    int acknowledge;
    } __attribute__ ((packed));
struct client_t{
    char message[4];
    unsigned char x;
    unsigned char y;
    unsigned char fps;
    unsigned char updobright;
    unsigned char updocontrast;
    unsigned char updocolors;
    unsigned char updoexposure;
    unsigned char updosize;
    unsigned char sleepon;
    } __attribute__ ((packed));

許多核心介面通過將錯誤值編碼到指標值中來返回錯誤資訊。這樣的資訊必須小心使用,因為它們的返回值不能簡單地與 NULL 比較。為幫助建立和使用這類介面<linux/err.h>提供了這樣的函式:

void*ERR_PTR(longerror);/*將錯誤值編碼到指標值中,error 是常見的負值錯誤碼*/測試返回的指標是不是一個錯誤碼*/抽取實際的錯誤碼,只有在IS_ERR 返回一個真值時使用,否則一個有效指標*/long PTR_ERR(constvoid*ptr);/*long IS_ERR(constvoid*ptr);/*

連結串列

作業系統核心常需要維護資料結構的連結串列。Linux 核心已經同時有幾個連結串列實現。為減少複製程式碼的數量, 核心已經建立了一個標準環形雙向連結串列,並鼓勵需要操作連結串列的人使用這個設施.

使用連結串列介面時,應當記住列表函式沒做加鎖。若驅動可能同一個列表併發操作,就必須實現一個鎖方案。

為使用連結串列機制,驅動必須包含檔案 <linux/list.h> ,它定義了一個簡單的list_head 型別 結構:

struct list_head {struct list_head *next,*prev;};

實際程式碼中使用的連結串列幾乎總是由某個結構型別組成, 每個結構描述連結串列中的一項. 為使用 Linux 連結串列,只需嵌入一個 list_head 在構成在這個連結串列的結構裡面。連結串列頭常常是一個獨立的 list_head 結構。下圖顯示了這個簡單的 struct list_head 是如何用來維護一個數據結構的列表的.

連結串列頭資料結構 

/*連結串列頭必須在使用前初始化,有兩種形式: */一是執行時初始化:*/二是編譯時初始化:*/在緊接著連結串列 head 後面增加新項 。注意:head 不需要是連結串列名義上的頭; 如果你傳遞一個 list_head 結構, 它在連結串列某處的中間, 新的項緊靠在它後面。 因為 Linux 連結串列是環形的, 連結串列頭通常和任何其他的項沒有區別*/在給定連結串列頭前面增加新項,即在連結串列的尾部增加一個新項。*/給定的項從佇列中去除。 如果入口項可能註冊在另外的連結串列中, 你應當使用 list_del_init, 它重新初始化這個連結串列指標.*/給定的入口項從它當前的連結串列裡去除並且增加到 head 的開始。為安放入口項在新連結串列的末尾, 使用 list_move_tail 代替*/如果給定連結串列是空, 返回一個非零值.*/list 緊接在 head 之後來連線 2 個連結串列.*/是將一個 list_head 結構指標轉換到一個指向包含它的結構體的指標 。看了原始碼你就會發現,似曾相識。是的,其實在模組的open方法中已經用到了container_of list_entry的變體好有很多,看原始碼就知道了*/#define list_entry(ptr, type, member) /
 container_of
(ptr, type, member)
/*list_entry


list_splice
(struct list_head *list,struct list_head *head);/*


list_empty
(struct list_head *head);/*


list_move
(struct list_head *entry,struct list_head *head);
list_move_tail
(struct list_head *entry,struct list_head *head);
/*


list_del
(struct list_head *entry);
list_del_init
(struct list_head *entry);/*

list_add_tail
(struct list_head *new,struct list_head *head);/*

LIST_HEAD
(todo_list);

list_add
(struct list_head *new,struct list_head *head);
/*
struct list_head todo_list;
INIT_LIST_HEAD
(&todo_list);
/*
/*

ARM9實驗板實驗

datasize實驗

實驗中有用到uname函式,介紹如下(載自《UNIX環境高階程式設計》,第6章系統資料檔案和資訊 6.8 系統標識):

POSIX.1定義了uname函式,它返回與主機和作業系統有關的資訊。
#include <sys/utsname.h>
int uname(struct utsname *n a m e) ;
返回:若成功則為非負值,若出錯則為-1

通過該函式的引數向其傳遞一個utsname結構的地址,然後該函式填寫此結構。POSIX.1只定義了該結構中至少需提供的欄位(它們都是字元陣列),而每個陣列的長度則由實現確定。某些實現在該結構中提供了另外一些欄位。在歷史上,系統 V為每個陣列分配9個位元組,其中有1個位元組用於字串結束符( null字元)
struct utsname {
char sysname[9];   /* name of the operating system */
char nodename[9];  /* name of this node */
char release[9];   /* current release of operating system */
char version[9];   /* current version of this release */
char machine[9];   /* name of hardware type */
} ;
utsname
結構中的資訊通常可用uname(1)命令列印。

實驗程式原始碼連結:

kdatasize模組實驗

實驗中有用到utsname函式,原始碼如下:

//<linux/utsname.h>
struct new_utsname {
    char sysname[65];
    char nodename[65];
    char release[65];
    char version[65];
    char machine[65];
    char domainname[65];
};

static inline struct new_utsname *utsname(void)
{
    return &current->nsproxy->uts_ns->name;
}

kdataalign模組實驗

具體試驗原理請看原始碼

實驗現象:

[[email protected]]#cd /tmp/[[email protected]]#./datasize
arch Size
:charshortintlong ptr long-long
u8 u16 u32 u64
armv4tl 1 2 4 4 4 8 1 2 4 8
[[email protected]]#cd /lib/modules/
[[email protected]]#insmod kdatasize.ko
arch Size
:charshortintlong ptr long-long
u8 u16 u32 u64
armv4tl 1 2 4 4 4 8 1 2 4 8
insmod
: cannot insert 'kdatasize.ko': No such device (-1):
No such device
[[email protected]]#insmod kdataalign.
ko
arch Align
:charshortintlong ptr long-long
u8 u16 u32 u64
armv4tl 1 2 4 4 4 4 1 2 4 4
insmod
: cannot insert 'kdataalign.ko': No such device (-1): No such device