歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

Linux內核--內核數據類型

將Linux 移植到新的體系結構時,開發者遇到的若干問題都與不正確的數據類型有關。堅持使用嚴格的數據類型和使用 -Wall -Wstrict-prototypes 進行編譯可能避免大部分的 bug。

-Wall 顯示所有的警告 -Wstrict-prototypes 嚴格的檢測原型,如果不一致,則出現警告

內核數據使用的數據類型主要分為3個類型: 標准C語言類型、確定大小的類型和特定內核對象的類型。

標准 C 語言類型

當需要“一個2字節填充符”或“用一個4字節字串來代表某個東西”,就不能使用標准C語言類型,因為在不同的體系結構,C 語言的數據類型所占的空間大小不同。而且有的構架,內核空間和用戶空間的C數據類型所占空間大小也可能不同。

內核中的地址是unsigned long類型,指針大小和long類型相同。 盡管概念上地址是指針,但使用一個無符號整型可以更好地實現內存管理; 內核把物理內存看成一個巨型數組, 內存地址就是該數組的索引。我們可以方便地對指針取值,但直接處理內存地址時,我們幾乎從不會以這種方式對他取值。使用一個整數類型避免了這種取值,因此避免了bug。所以,利用至少在 Linux 目前支持的所有平台上,指針和長整型始終是相同大小的這一事實,內核中內存地址常常是unsigned long。 C99 標准定義了 intptr_t 和 uintptr_t 類型,它們是能夠保存指針值的整型變量。但沒在 2.6 內核中幾乎沒使用。  

確定大小的類型

內核:當需要知道你定義的數據的大小時,可以使用內核提供的下列數據類型:

u8; /* unsigned byte (8 bits) */
u16; /* unsigned word (16 bits) */
u32; /* unsigned 32-bit value */
u64; /* unsigned 64-bit value */
/*雖然很少需要有符號類型,但是如果需要,只要用 s 代替 u*/ 用戶空間:若一個程序用戶空間需要使用這些類型,可在符號前加一個雙下劃線: __u8和其它類型是獨立於 __KERNEL__ 定義的。

接口特定的類型(_t 類型

內核中最常用的數據類型由它們自己的 typedef 聲明,阻止了任何移植性問題。
“接口特定(interface-specific)”由某個庫定義的一種數據類型, 以便為了某個特定的數據結構提供接口。
注意

近來已經很少定義新的接口特定的類型。有許多內核開發者已經不再喜歡使用 typedef 語句,他們寧願看到代碼中直接使用的真實類型信息。
很多老的接口特定類型在內核中保留,他們不會很快消失。
即使沒有定義接口特定類型,也應該始終是用和內核其他部分保持一致、適當的數據類型。只要驅動使用了這種“定制”類型的函數,但又不遵照約定,編譯器會發出警告,這時使用 -Wall 編譯器選項並小心去除所有的警告,就可以確信代碼的可移植性了。 _t 類型的主要問題: 打印它們時,常常不容易選擇正確的 printk 或 printf 格式。 打印接口特定的數據的最好方法是:將其強制轉換為可能的最大類型(常常是 long 或 unsigned long ) 並用相應的格式打印。  

時間間隔

當處理時間間隔時,不要假定每秒的jiffies個數,不是每個 Linux 平台都以固定的速度運行。當計算時間間隔時,要使用 HZ ( 每秒的定時器中斷數 ) 來標定你的時間。

Hz:Linux核心每隔固定周期會發出timer interrupt (IRQ 0),HZ是用來定義每一秒有幾次timer interrupts。舉例來說,HZ為1000,代表每秒有1000次timer interrupts。 Tick:Tick是HZ的倒數,意即timer interrupt每發生一次中斷的時間。如HZ為250時,tick為4毫秒(millisecond)。 Jiffies:Jiffies為Linux核心變數(unsigned long),它被用來記錄系統自開機以來,已經過了多少tick。每發生一次timer interrupt,Jiffies變數會被加一。  

頁大小

當使用內存時,記住一個內存頁是 PAGE_SIZE 字節, 不是 4KB。相關的宏定義是 PAGE_SIZE 和 PAGE_SHIFT(包含將一個地址移位來獲得它的頁號的位數)。如果用戶空間程序需要這些信息,可以使用 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 的冪*/  

字節存儲順序

不要假設字節序。 代碼應該編寫成不依賴所操作數據的字節序的方式。
頭文件定義:

#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 位處理器。在沒有轉換工作需要做時,返回未修改的值。*/

數據對齊

編寫可移植代碼而值得考慮的最後一個問題是如何訪問未對齊的數據。存取不對齊的數據應當使用下列宏:
#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);

這些宏是無類型的,並對各總數據項,不管是 1、2、4或 8 個字節,他們都有效,並且在所有內核版本中都有定義。

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

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

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));

Linux Kernel 的詳細介紹:請點這裡
Linux Kernel 的下載地址:請點這裡

Copyright © Linux教程網 All Rights Reserved