2016年12月16日 星期五

Linux鏈結串列struct list_head 研究

 Linux kernel對雙向鏈結串列進行了很好的封裝,它是由一個struct list_head結構與相關操作函數所組成,定義在linux/list.h中。在許多驅動程式中都會看到它的身影,使用起來很方便。只要在自定義的結構中加入struct list_head結構體就可以使用Linux一系列的link-list函數而不用自行撰寫。雖然struct list_head是用在kernel與驅動程式中,但理論上一般應用程式也可以使用,所以即使平常不接觸驅動程式也值得好好研究一番。

使用情境如下:
struct list_head {
 struct list_head *next, *prev;
};
list_head只包含2個指標, 分別用來指向前一個(prev)以及後一個(next)的節點位址
struct My_DATA {
        int Data_A;
        int Data_B;
        int Data_C;
        struct list_head list;
};
list_head運作模式

接著,介紹一下list_head相關的操作函數:

LIST_HEAD(Name):
這個巨集會宣告一個struct list_head的結構變數並作初始化,也就是
pervnext指標指向自己。
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
也可以使用INIT_LIST_HEAD這個函式來初始化頭節點,但是這樣的作法要先宣告一struct list_head變數然後再餵給INIT_LIST_HEAD做初始化

void INIT_LIST_HEAD(struct list_head *head)
{
        list->next = list;
        list->prev = list;
}


list_empty
檢查這個串列否為空。
int list_empty(const struct list_head *head)
{
        return head->next == head;
}
__list_add
__list_add這個函式會將一個新的節點加入一對已知的前(prev)/(next)端節點之間,也就是說原本相連的2個端點中間會插入一個新節點。

static  void __list_add(struct list_head *new_lst,
                              struct list_head *prev,
                              struct list_head *next)
{
        next->prev = new_lst;
        new_lst->next = next;
        new_lst->prev = prev;
        prev->next = new_lst;
}


list_add
__list_add重新包裝,讓一個新的節點加入串列的開頭,新節點會變成HEAD指向的next,新加入的節點等於是向前端插入。
PS: LDD這書中把這樣的操作比喻為Stack(LIFO)
void list_add(struct list_head *new_lst, 
              struct list_head *head)
{
 __list_add(new_lst, head, head->next);
}



list_add_tail
__list_add重新包裝,讓一個新的節點加入串列末端中,新節點會變成HEAD指向的prev,新加入的節點等於是向尾端插入。
PS: LDD這書中把這樣的操作比喻為Queue(FIFO),事實上它還真的能拿來實作Queue

void list_add_tail(struct list_head *new_lst, struct list_head *head)
{
        __list_add(new_lst, head->prev, head);
}

__list_del  &  list_del 
__list_del這個函式會刪除某一個節點entry,這需要先知道entry的前(prev)/(next)端節點。entry原本的前端節點的next指標會指向entry原本的後端節點,而後端節點的prev指標會指向entry的前端節點,這樣一來就沒有其它節點的指標會指向entry,達到刪除的效果。

static  void __list_del(struct list_head * prev, 
                              struct list_head * next  )
{
        next->prev = prev;
        prev->next = next;
}
list_del除了呼叫__list_del之外,還會將entryprev/next指標指向LIST_POISON1LIST_POISON2這兩個特別位址。
void list_del(struct list_head * entry) { __list_del(entry->prev,entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; }


查了一下網路上的資料,LIST_POISON1LIST_POISON2被定義在linux/poison.h中,從注解上來看,當訪問到這兩個特別位址時會發生page faultLIST_POISON1LIST_POISON2用來標記那些沒有被初始化以及沒有在鍊表中的list項,讓它們不可被訪問。

/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)

list_for_each(pos, head)
list_for_each是一個巨集,原型如下:

#define list_for_each(pos, head) \
        for (pos = (head)->next; pos != (head); pos = pos->next)
由定義可以知道是在走訪整個鏈結串列,pos為一個指標並指向第一個項目(head->next),以此為起點一路訪問下去,終止條件是當pos指向head時,代表已經走訪完一圈了。

list_entry(ptr,type,member)
list_entry是一個巨集,是container_of這個巨集組成的,而container_of又會用到offsetof這另一個巨集。 原型如下:

#define offsetof(TYPE, MEMBER) ((unsigned int) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
               (type *)( (char *)__mptr - offsetof(type,member) );})

#define list_entry(ptr,type,member)     \
    container_of(ptr, type, member)
offsetof用來得知strucct中某一member的位址相對於該strucct起始位址的距離,而container_of是只要得知某一member的位址,就可以利用它進一步推算出strucct的起始位址。(offsetof和container_of進一步的描述之前已寫在這一篇-->Linux的container_of與offsetof巨集 )。所以,list_for_eachist_entry這兩個巨集搭配使用就可以存取整個鏈結串列。

list.h的相關函數還有很多,上面只介紹到幾個常用到的。接下來來做個範例,以下為完整的Sample code。

由於linux/list.h不能直接由一般應用程式include,所以可以直接將list.h中的struct list head結構與要用到的操作函式直接複製到code中。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

/**************** add list.h函式 ****************
* 加入上面描述的linux/list.h的操作函式。
************************************************/

struct My_DATA {
    int Data_1;
    int Data_2;
    int Data_3;
    struct list_head list;

};
訪問整個串列並印出串列項的address和data內容。
void print_List(struct list_head *head){
    struct list_head *listptr;
    struct My_DATA *entry;
 
    printf("\n*********************************************************************************\n");
    printf("(HEAD addr =  %p, next = %p, prev = %p)\n",head, head->next, head->prev);
    list_for_each(listptr, head) {
        entry = list_entry(listptr, struct My_DATA, list);
        printf("entry->Data_1= %d | list addr = %p | next = %p | prev = %p\n",
                entry->Data_1, 
                &entry->list, 
                entry->list.next,  
                entry->list.prev  );
    }
    printf("*********************************************************************************\n");
}
加入一個新的串列項,可以向串列前端或尾端加入。
struct My_DATA *add_Node_tail(struct list_head *head){
    struct My_DATA *entry;
    entry=(struct My_DATA*)malloc(sizeof(struct My_DATA));
    list_add_tail( &entry->list,head);
    return entry;
}

struct My_DATA *add_Node(struct list_head *head){
    struct My_DATA *entry;
    entry=(struct My_DATA*)malloc(sizeof(struct My_DATA));
    list_add( &entry->list,head);
    return entry;
}
刪除單一個串列項以及銷毀整個串列:
值得注意的是,刪除串列項的時候會使用到list_del函式,在這裡暫時將entry->next = LIST_POISON1和entry->prev = LIST_POISON2這2行註解掉,因為我們不是在寫驅動程式而是在user space使用,可以把entry->{prev/next}設為NULL,代表該指標沒有指向任何東西,或者也可以把它重新初始化,使用INIT_LIST_HEAD(entry)。
void remove_Node(struct My_DATA *entry){
    printf("\nremove: Data_1=%d (list %p, next %p, prev %p)\n",
            entry->Data_1,
            &entry->list,
            entry->list.next,
            entry->list.prev);

    list_del(&entry->list);
    free(entry); 
}

void free_List(struct list_head *head)
{
    struct list_head *listptr;
    struct My_DATA *entry;
 
    list_for_each(listptr, head) {
        entry = list_entry(listptr, struct My_DATA, list);
        printf("\nFree: entry->Data_1 = %d | list addr = %p | next = %p | prev = %p\n",
                        entry->Data_1,
                        &entry->list,
                        entry->list.next,
                        entry->list.prev);
 
        free(entry);
        entry=NULL;
    }
}
主程式如下: 
int main(int argc,char **argv){

    LIST_HEAD(HEAD);

    struct My_DATA *itemPtr_1 = add_Node(&HEAD);
    itemPtr_1->Data_1 = 100 ;
    struct My_DATA *itemPtr_2 = add_Node(&HEAD);
    itemPtr_2->Data_1 = 200 ;
    struct My_DATA *itemPtr_3 = add_Node(&HEAD);
    itemPtr_3->Data_1 = 300 ;
    struct My_DATA *itemPtr_4 = add_Node(&HEAD);
    itemPtr_4->Data_1 = 400 ;

    print_List(&HEAD);

    remove_Node(itemPtr_3);
    print_List(&HEAD);
    free_List(&HEAD);
}
執行結果:

從結果可以看出串列項是向前端加入,在印出串列時從head->next開始印出,當然銷毀串列時也是一樣從head->next開始,這樣的運作模式就如同LDD書中所講的LIFO(後進先出)模式。
參考:
https://lwn.net/Kernel/LDD3/
http://nano-chicken.blogspot.tw/2009/09/linuxlink-list.html
Linux Device Driver Programming  :平田豐


2016年9月9日 星期五

Linux的container_of 與 offsetof巨集

container_ofoffsetof是兩個好用的巨集,因為最近在研究linux kernellink-list結構才知道這2個東西,但它其實不只在kernel裡也可以在很多地方使用,所以在此做一下紀錄。

offsetof

offsetof定義在 <linux/stddef.h> 中,用來計算某一個struct結構的成員相對於該結構起始位址的偏移量( offset )(偏移 == 離起始位址有多遠的距離)
/***  TYPE = 結構名稱,MEMBER = 結構成員 ***/
#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)
offsetof將數值 強制轉型成TYPE指標型別,0 會被當作該TYPE的起始地址,然後再指向某成員 (  (TYPE *) 0 )->MEMBER,就可以得到MEMBER的位址,因為起始位址等於 0,所以MEMBER的位址也就等於MEMBER與起始位址  的偏移(offset)。所以若將起始位址 改成其它數值的話,得到的偏移(offset)就不對了。

寫個範例來做個簡單的測試:
#include<stdio.h>

#define offsetof(TYPE, MEMBER)   ((size_t) &((TYPE *)0)->MEMBER)
#define my_offsetof(TYPE, MEMBER)  ((size_t) &((TYPE *)1000)->MEMBER)

struct MY_DATA {
   int Data_A ,Data_B , Data_C;
   struct MY_DATA *next;
};

int main(){

   puts("----- offsetof -----");
   printf("---- next  =  %d-----\n", offsetof(struct MY_DATA, next));
   printf("---- Data_A =  %d -----\n", offsetof(struct MY_DATA,Data_A));
   printf("---- Data_B =  %d -----\n", offsetof(struct MY_DATA,Data_B));
   printf("---- Data_C =  %d -----\n", offsetof(struct MY_DATA,Data_C));

   puts("\n---- my_offsetof----");
   printf("---- next  =  %d----\n", my_offsetof(struct MY_DATA, next));
}
由執行結果可以看出各個成員的偏移量。例如成員nextstruct MY_DATA的第四個成員,前面有3int型別(4bytes)的成員,所以next和結構起始位址隔了12bytes(4bytes*3)的偏移量。

另外,我自己定義一個my_offsetof,起始位址由  變成  1000,可以從結果看出它算出的偏移量多了1000



container_of

container_of定義在 <linux/kernel.h> 中,它需要引用offsetof巨集,它的作用是用來取得struct結構的起始點,只要知道該結構的任一個成員的位址,就可以使用container_of巨集來算出該結構的起始位址。

/***
ptr:  指向該結構成員的型別的指標
Type:  結構名稱
Member:  結構成員
***/
#define container_of(ptr, type, member) ({  \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);  \
           (type *)( (char *)__mptr - offsetof(type,member) );})
container_of可分解為2個動作
1. 定義一個指向member型別的指標__mptr,然後將參數ptr餵給該指標。
2. __mptr的位址減掉member與結構起始點的偏移(利用offsetof)就可得到該結構的起始點位址。

同樣做個範例來測試:
#include<stdio.h>

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);\
              (type *)( (char *)__mptr - offsetof(type,member));})

struct MY_DATA {
   int Data_A ,Data_B , Data_C;
   struct MY_DATA *next;
};

int main(){
    struct MY_DATA  Obj_one ;
    struct MY_DATA *RET;

    Obj_one.Data_A = 123; 
    Obj_one.Data_B = 321;
    Obj_one.Data_C = 987;
   
    int *Data_B_ptr = &Obj_one.Data_B;
    RET =  container_of(Data_B_ptr, struct MY_DATA , Data_B );

    puts("\n----------- index member = Data_B -----------");
    printf("Obj_one's addr = %p\nRET addr =  %p\n",&Obj_one , RET );
    printf("RET->Data_A = %d\nRET->Data_B = %d\nRET->Data_C = %d\n",\
           RET->Data_A, \
           RET->Data_B, \
           RET->Data_C);

}

執行結果如下:
Data_B_ptr是一個指向結構成員Obj_one.Data_B的位址的指標,利用Data_B_ptr的地址與結構成員Data_B在該結構的偏移就可以回推該結構Obj_one的起始位址。


2016年4月10日 星期日

Linux下使用 doxygen工具幫助快速解析source code

               
最近拿到新的開發板,需要去study各個Modules以便日後進行移植,這免不了要去trace kernel driverkernel source實在多如牛毛(據說已超過千萬行),所以我就在找有沒有什麼工具可以使用doxygen以前雖然有聽過但從沒想過去使用以前我找code的方式通常都是直接grep + find (假裝牛B),或者靠IDE現在用了doxygen才發現原來這麼方便 。


doxygen有幾個特色:

1. 生成的document可以是線上Html格式,或者本機端的latex格式

2. 可以匯出指定的source code路徑的檔案階層。

3. 可以建立關系圖,例如各個function的呼叫路徑(call path),或者像是UML中的
    類別繼承和協作圖。

使用方式如下:

step 1 : 下載套件
    - sudo apt-get install doxygen doxygen-doc doxygen-gui graphviz graphviz-doc

               doxygen-gui : doxygen的GUI版本。
               graphviz :
                     用來生成各部件之間的關係圖工具,例如網路拓蹼、軟體架構等。
                     請參考 http://www.openfoundry.org/tw/foss-programs/8820-graphviz-

step 2 : 產生一個config組態檔,檔名可以隨便取。
    - doxygen -g MyDoxygen.config

step 3 : 設定config組態檔
    需要將組態檔的設定由NO改為YES,才會打開相應的功能,對我來說我想看
    是function的呼叫路徑,所以以下是我的設定。

EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_PACKAGE = YES
EXTRACT_STATIC = YES
/*source路徑 ,不寫的話預設是當前目錄*/
INPUT = /home/jinyo/My_source
/*包含子資料目錄*/
RECURSIVE = YES
INLINE_SOURCES = YES
/*開啟Graphviz的 dot tool*/
HAVE_DOT = YES
/*產生function的呼叫與被呼叫路徑*/
CALL_GRAPH = YES
CALLER_GRAPH = YES


step 4 : 執行Cmd , 開始生成 --> doxygen MyDoxygen.config


結果如下:

1. 生成HTML格式和latex格式,但我都直接看HTML的,latex不會用。

2. 列出資料結構 ,例如:class ,struct , enum等。


3. 列出某個function所有的呼叫路徑,或者被呼叫的路徑。

2016年1月24日 星期日

Lex與正則表示式練習

               
LEX是用來製作scanner/Lexer等工具的工具在編譯器領域常常會用到,使用LEX可以將輸入的文字資料過濾成一個個有意義的單元(token),當然這一些token由使用者自己決定。

LEX使用正則表示式來描述一個token的長相
常看網路上的高手說學會用正則表示式才算是邁向職業的程式設計師,所以趕緊來學一學。

正則表示式並不是LEX獨有正則表示式在很多程式語言都可以看到它的存在,比如神級的語言 : perl

正則表示式其實是一種科學的概念,起源於grep/sed等工具中,主要用來做字串比對,例如”  搜尋與取代”這種操作。

以下列了一些LEX正則表示式的簡單操作。

LEX基本格式如下圖,其中最重要的就是中間由2個%%包覆的規則區塊,由成對的pattern與action組成,當輸入的來源符合某個pattern時,就會去執行該pattern所對應的action。而正則表示式的功用就是可以很細膩的去描述每ㄧ個pattern。


以下是一些範例與執行結果

.    小數點

可以代表任何字元或數字,除了\n(換行)以外
例如:
        %%
.    { printf("pattern Dot ") ; }
        %%


[ ]    方括號

1.   在方括號[ ]中可以表示一串字元組的其中一個字元:
      例如:
                [123]     { printf("pattern [123] ") ; }
                上式代表若輸入是1 2 3其中一個,就執行printf

2.  可用 ‘-’符號表示某一個範圍(Range)字元 :
      例如:
                [1-9] { printf("pattern [1-9] ") ; }
               上式等同於[0123456789]

3.  若方括號的第一個字元為 ‘^’,代表反向
     例如:
               [^1-9] { printf("pattern [^1-9] ") ; } ,
        上式代表除了1到9之外的字元都符合條件

範例如下:
/*
   [\n\t ]   遇到空白與換行, 直接換行
   pattern1= [123] , pattern2 = [1-9] , pattern3 = [^1-9]
*/
%%
       [\n\t ]         {printf("\n");}
       [123]         { printf("pattern [123] ") ; }
       [1-9]         { printf("pattern [1-9] ") ; }
       [^1-9]         { printf("pattern [^1-9] ") ; }
%%
如下圖的結果,只有輸入1 ,2, 3才會被pattern 1匹配且可輸入多
次,輸入4的話就會跑到pattern 2去了,若輸入字母會執行pattern3



^    尖號
   
^ 代表一個表示式的起頭,但是若用在方括號裡面的開頭,則代表向條件。
例如:
^abcdefg       { printf("pattern letter ") ; }
[ ^abcdefg ]        { printf("pattern reverse ") ; }

$    錢字號

用來代表一個式子的結尾,放在表示式的最後面
例如:
    abcedfg$    { printf("pattern letter ") ; }

\    脫逸字元

字元 \’加在任何符號前面會讓原本有意義的字元失去功能,變成只是
單純的字元而已 。
例如:
%%
\.    { printf("pattern escape ") ; }
%%
原本'.'可以代表任何字元,但經過'\'修飾後,變成只是 '.' 這個字 。
輸入數字或字母都不會匹配,只有輸入'.' 才會執行動作 。


*     星號

在星號的前一個式子或字元(包含空白)可以被重複零次(不出現)或多次 。
例如:
[ ]*
[1-9]*

+    加號

在加號的前一個式子或字元(不包含空白)至少要出現1次以上,或多次 。
例如:
[0-9]


範例:浮點數表式
%%
[0-9 ]*\.[0-9]+     { printf("floating-point ") ; }
%%
表示式結果如下, 在小數點前面可以不出現,但後面至少要出現一次
才會執行動作 。

|   

如一般的程式語言一樣,可以將多個表示式做或|操作 。
例如:
  %%
        iphone-[1-6]+|HTC-M[7-8]+|Sony-Z[1-5]+    {printf("smart phone") ;}
   %%
當輸入其中一個字串時就執行動作。
    ps :  |’ 出現在中括號裡面就會失去功能,變成一般的字元。

{ }    大括號

大括號在當作pattern用的時候有2種不同的含義
1. 做為別名(簡稱),可以代表任何一個表示式 。
    例如:
                      letter     [ a-z]
2. 指定表示式repeat(重覆)的次數 。
例如:
        /* X要重複3= XXX ,才會執行動作 。 */
X{3}     { printf("pattern repeat ") ; }


範例如下:   
number    [0-9]+
Name        "Joe"
Age           "17-years"
%%
{number}      {printf("pattern Number ") ;}
{Name}{3}     {printf("pattern Name ") ;}
{Age}{1,3}      {printf("pattern Age ") ;}
%%


?     問號

若有一個式子或字元出現在問號前面,代表該式子可以出現一次
或者完全沒有出現。
例如 ,判斷輸入網址的格式:
    %%
          "http://"?"www"\.[a-z]+\.[a-z]+\.[a-z]+    { printf(" IP address ") ;}
    %%
執行結果如下,不管有沒有加上"http://"都符合條件。


()    小括號

就可以將多個式子用小括號()包起來,這樣就會變成一個新的表示式。
例如 :
%%
         ([a-z]+|[A-Z]+)|([0-9]+|([0-9]*\.[0-9]+))    {printf("letter and int") ;}
%%