【Linux 驅動】第十一章 核心的資料類型

來源:互聯網
上載者:User

一,核心資料資料類型

        主要分為: 標準 C 語言類型、確定大小的類型和特定核心對象的類型。

        1)標準 C 語言類型

              當需要"一個2位元組填充符"或"用一個4位元組字串來代表某個東西",就不能使用標準C語言類型,因為在不同的體繫結構,C 語言的資料類型所佔的空間大小不同。例如:long 在32位系統和64位系統中佔用的位元組數不同。

              有的構架,核心空間和使用者空間的C 資料類型所佔空間大小也可能不同。kdatasize模組顯示了當前模組的核心空間C 資料類型所佔空間大小。

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

             

        2)確定大小的類型

              當需要知道你定義的資料的大小時,可以使用核心提供的下列資料類型(所有的資料聲明在 <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特定的變體要好。

        3)介面特定的類型(_t 類型)

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

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

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

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

二,其他移植性問題

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

        1)時間間隔

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

        2)頁大小

               當使用記憶體時,記住一個記憶體頁是 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 的冪*/
       3)位元組序

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

             標頭檔 <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> 中定義*/
 
      3)資料對齊

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

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

 

       4)指標和錯誤值

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

             void    *ERR_PTR(long error);/*將錯誤值編碼到指標值中,error 是常見的負值錯誤碼*/

             long     IS_ERR(const void *ptr); /*測試返回的指標是不是一個錯誤碼*/

             long     PTR_ERR(const void *ptr); /*抽取實際的錯誤碼,只有在IS_ERR 返回一個真值時使用,否則一個有效指標*/

三,鏈表

        作業系統核心常需要維護資料結構的鏈表。Linux 核心已經同時有幾個鏈表實現。為減少複製代碼的數量, 核心已經建立了一個標準環形雙向鏈表,並鼓勵需要操作鏈結表的人使用這個設施.
使用鏈表介面時,應當記住列表函數沒做加鎖。若驅動可能同一個列表並行作業,就必須實現一個鎖方案。

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

         struct list_head {

                     struct list_head *next, *prev;

         };

        實際代碼中使用的鏈表幾乎總是由某個結構類型組成, 每個結構描述鏈表中的一項. 為使用 Linux 鏈表,只需嵌入一個 list_head 在構成在這個鏈表的結構裡面。鏈表頭常常是一個獨立的 list_head 結構。顯示了這個簡單的 struct list_head 是如何用來維護一個資料結構的列表的.

 
       鏈表頭必須在使用前初始化,有兩種形式:

          一是運行時初始化:

               struct list_head todo_list; 

               INIT_LIST_HEAD(&todo_list); 

          二是編譯時間初始化:

               LIST_HEAD(todo_list);

               list_add(struct list_head *new, struct list_head *head);

     在緊接著鏈表 head 後面增加新項 。注意:head 不需要是鏈表名義上的頭; 如果你傳遞一個 list_head 結構, 它在鏈表某處的中間, 新的項緊靠在它後面。 因為 Linux 鏈表是環形的, 鏈表頭通常和任何其他的項沒有區別

            list_add_tail(struct list_head *new, struct list_head *head);  // 在給定鏈表頭前面增加新項,即在鏈表的尾部增加一個新項。

            list_del(struct list_head *entry);

            list_del_init(struct list_head *entry);

     給定的項從隊列中去除。 如果入口項可能註冊在另外的鏈表中, 你應當使用 list_del_init, 它重新初始化這個鏈表指標

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

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

      給定的入口項從它當前的鏈表裡去除並且增加到 head 的開始。為安放入口項在新鏈表的末尾, 使用 list_move_tail 代替

            list_empty(struct list_head *head); 

      如果給定鏈表是空, 返回一個非零值

            list_splice(struct list_head *list, struct list_head *head);
     將 list 緊接在 head 之後來串連 2 個鏈表.

            list_entry是將一個 list_head 結構指標轉換到一個指向包含它的結構體的指標 。看了源碼你就會發現,似曾相識。是的,其實在模組的open方法中已經用到了container_of 。list_entry的變體好有很多,看源碼就知道了

        #define list_entry(ptr, type, member) \
            container_of(ptr, type, member)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.